From ac11aeadad58ec45ede3aa51992f7dc4d7393b92 Mon Sep 17 00:00:00 2001 From: juxuan27 Date: Fri, 25 Aug 2023 02:47:39 +0800 Subject: [PATCH] fix bugs --- .gitignore | 6 +- .gitmodules | 7 +- README.md | 102 +- assets/pose/demo.npz | Bin 0 -> 3129 bytes comparison_models/ControlNet/.gitignore | 143 ++ comparison_models/ControlNet/LICENSE | 201 ++ comparison_models/ControlNet/README.md | 348 ++++ .../ControlNet/annotator/canny/__init__.py | 6 + .../ControlNet/annotator/ckpts/ckpts.txt | 1 + .../ControlNet/annotator/hed/__init__.py | 96 + .../ControlNet/annotator/midas/LICENSE | 21 + .../ControlNet/annotator/midas/__init__.py | 42 + .../ControlNet/annotator/midas/api.py | 169 ++ .../annotator/midas/midas/__init__.py | 0 .../annotator/midas/midas/base_model.py | 16 + .../annotator/midas/midas/blocks.py | 342 ++++ .../annotator/midas/midas/dpt_depth.py | 109 + .../annotator/midas/midas/midas_net.py | 76 + .../annotator/midas/midas/midas_net_custom.py | 128 ++ .../annotator/midas/midas/transforms.py | 234 +++ .../ControlNet/annotator/midas/midas/vit.py | 491 +++++ .../ControlNet/annotator/midas/utils.py | 189 ++ .../ControlNet/annotator/mlsd/LICENSE | 201 ++ .../ControlNet/annotator/mlsd/__init__.py | 43 + .../annotator/mlsd/models/mbv2_mlsd_large.py | 292 +++ .../annotator/mlsd/models/mbv2_mlsd_tiny.py | 275 +++ .../ControlNet/annotator/mlsd/utils.py | 580 ++++++ .../ControlNet/annotator/openpose/LICENSE | 108 + .../ControlNet/annotator/openpose/__init__.py | 49 + .../ControlNet/annotator/openpose/body.py | 219 ++ .../ControlNet/annotator/openpose/hand.py | 86 + .../ControlNet/annotator/openpose/model.py | 219 ++ .../ControlNet/annotator/openpose/util.py | 164 ++ .../ControlNet/annotator/uniformer/LICENSE | 203 ++ .../annotator/uniformer/__init__.py | 27 + .../configs/_base_/datasets/ade20k.py | 54 + .../configs/_base_/datasets/chase_db1.py | 59 + .../configs/_base_/datasets/cityscapes.py | 54 + .../_base_/datasets/cityscapes_769x769.py | 35 + .../configs/_base_/datasets/drive.py | 59 + .../uniformer/configs/_base_/datasets/hrf.py | 59 + .../configs/_base_/datasets/pascal_context.py | 60 + .../_base_/datasets/pascal_context_59.py | 60 + .../configs/_base_/datasets/pascal_voc12.py | 57 + .../_base_/datasets/pascal_voc12_aug.py | 9 + .../configs/_base_/datasets/stare.py | 59 + .../configs/_base_/default_runtime.py | 14 + .../configs/_base_/models/ann_r50-d8.py | 46 + .../configs/_base_/models/apcnet_r50-d8.py | 44 + .../configs/_base_/models/ccnet_r50-d8.py | 44 + .../uniformer/configs/_base_/models/cgnet.py | 35 + .../configs/_base_/models/danet_r50-d8.py | 44 + .../configs/_base_/models/deeplabv3_r50-d8.py | 44 + .../_base_/models/deeplabv3_unet_s5-d16.py | 50 + .../_base_/models/deeplabv3plus_r50-d8.py | 46 + .../configs/_base_/models/dmnet_r50-d8.py | 44 + .../configs/_base_/models/dnl_r50-d8.py | 46 + .../configs/_base_/models/emanet_r50-d8.py | 47 + .../configs/_base_/models/encnet_r50-d8.py | 48 + .../configs/_base_/models/fast_scnn.py | 57 + .../configs/_base_/models/fcn_hr18.py | 52 + .../configs/_base_/models/fcn_r50-d8.py | 45 + .../configs/_base_/models/fcn_unet_s5-d16.py | 51 + .../configs/_base_/models/fpn_r50.py | 36 + .../configs/_base_/models/fpn_uniformer.py | 35 + .../configs/_base_/models/gcnet_r50-d8.py | 46 + .../configs/_base_/models/lraspp_m-v3-d8.py | 25 + .../configs/_base_/models/nonlocal_r50-d8.py | 46 + .../configs/_base_/models/ocrnet_hr18.py | 68 + .../configs/_base_/models/ocrnet_r50-d8.py | 47 + .../configs/_base_/models/pointrend_r50.py | 56 + .../configs/_base_/models/psanet_r50-d8.py | 49 + .../configs/_base_/models/pspnet_r50-d8.py | 44 + .../_base_/models/pspnet_unet_s5-d16.py | 50 + .../configs/_base_/models/upernet_r50.py | 44 + .../_base_/models/upernet_uniformer.py | 43 + .../configs/_base_/schedules/schedule_160k.py | 9 + .../configs/_base_/schedules/schedule_20k.py | 9 + .../configs/_base_/schedules/schedule_40k.py | 9 + .../configs/_base_/schedules/schedule_80k.py | 9 + .../exp/upernet_global_small/config.py | 38 + .../uniformer/exp/upernet_global_small/run.sh | 10 + .../exp/upernet_global_small/test.sh | 10 + .../exp/upernet_global_small/test_config_g.py | 38 + .../upernet_global_small/test_config_h32.py | 39 + .../upernet_global_small/test_config_w32.py | 39 + .../annotator/uniformer/mmcv/__init__.py | 15 + .../uniformer/mmcv/arraymisc/__init__.py | 4 + .../uniformer/mmcv/arraymisc/quantization.py | 55 + .../annotator/uniformer/mmcv/cnn/__init__.py | 41 + .../annotator/uniformer/mmcv/cnn/alexnet.py | 61 + .../uniformer/mmcv/cnn/bricks/__init__.py | 35 + .../uniformer/mmcv/cnn/bricks/activation.py | 92 + .../mmcv/cnn/bricks/context_block.py | 125 ++ .../uniformer/mmcv/cnn/bricks/conv.py | 44 + .../cnn/bricks/conv2d_adaptive_padding.py | 62 + .../uniformer/mmcv/cnn/bricks/conv_module.py | 206 ++ .../uniformer/mmcv/cnn/bricks/conv_ws.py | 148 ++ .../bricks/depthwise_separable_conv_module.py | 96 + .../uniformer/mmcv/cnn/bricks/drop.py | 65 + .../mmcv/cnn/bricks/generalized_attention.py | 412 ++++ .../uniformer/mmcv/cnn/bricks/hsigmoid.py | 34 + .../uniformer/mmcv/cnn/bricks/hswish.py | 29 + .../uniformer/mmcv/cnn/bricks/non_local.py | 306 +++ .../uniformer/mmcv/cnn/bricks/norm.py | 144 ++ .../uniformer/mmcv/cnn/bricks/padding.py | 36 + .../uniformer/mmcv/cnn/bricks/plugin.py | 88 + .../uniformer/mmcv/cnn/bricks/registry.py | 16 + .../uniformer/mmcv/cnn/bricks/scale.py | 21 + .../uniformer/mmcv/cnn/bricks/swish.py | 25 + .../uniformer/mmcv/cnn/bricks/transformer.py | 595 ++++++ .../uniformer/mmcv/cnn/bricks/upsample.py | 84 + .../uniformer/mmcv/cnn/bricks/wrappers.py | 180 ++ .../annotator/uniformer/mmcv/cnn/builder.py | 30 + .../annotator/uniformer/mmcv/cnn/resnet.py | 316 +++ .../uniformer/mmcv/cnn/utils/__init__.py | 19 + .../uniformer/mmcv/cnn/utils/flops_counter.py | 599 ++++++ .../uniformer/mmcv/cnn/utils/fuse_conv_bn.py | 59 + .../uniformer/mmcv/cnn/utils/sync_bn.py | 59 + .../uniformer/mmcv/cnn/utils/weight_init.py | 684 +++++++ .../annotator/uniformer/mmcv/cnn/vgg.py | 175 ++ .../uniformer/mmcv/engine/__init__.py | 8 + .../annotator/uniformer/mmcv/engine/test.py | 202 ++ .../uniformer/mmcv/fileio/__init__.py | 11 + .../uniformer/mmcv/fileio/file_client.py | 1148 +++++++++++ .../mmcv/fileio/handlers/__init__.py | 7 + .../uniformer/mmcv/fileio/handlers/base.py | 30 + .../mmcv/fileio/handlers/json_handler.py | 36 + .../mmcv/fileio/handlers/pickle_handler.py | 28 + .../mmcv/fileio/handlers/yaml_handler.py | 24 + .../annotator/uniformer/mmcv/fileio/io.py | 151 ++ .../annotator/uniformer/mmcv/fileio/parse.py | 97 + .../uniformer/mmcv/image/__init__.py | 28 + .../uniformer/mmcv/image/colorspace.py | 306 +++ .../uniformer/mmcv/image/geometric.py | 728 +++++++ .../annotator/uniformer/mmcv/image/io.py | 258 +++ .../annotator/uniformer/mmcv/image/misc.py | 44 + .../uniformer/mmcv/image/photometric.py | 428 ++++ .../uniformer/mmcv/model_zoo/deprecated.json | 6 + .../uniformer/mmcv/model_zoo/mmcls.json | 31 + .../uniformer/mmcv/model_zoo/open_mmlab.json | 50 + .../annotator/uniformer/mmcv/ops/__init__.py | 81 + .../uniformer/mmcv/ops/assign_score_withk.py | 123 ++ .../uniformer/mmcv/ops/ball_query.py | 55 + .../annotator/uniformer/mmcv/ops/bbox.py | 72 + .../uniformer/mmcv/ops/border_align.py | 109 + .../uniformer/mmcv/ops/box_iou_rotated.py | 45 + .../annotator/uniformer/mmcv/ops/carafe.py | 287 +++ .../uniformer/mmcv/ops/cc_attention.py | 83 + .../uniformer/mmcv/ops/contour_expand.py | 49 + .../uniformer/mmcv/ops/corner_pool.py | 161 ++ .../uniformer/mmcv/ops/correlation.py | 196 ++ .../uniformer/mmcv/ops/deform_conv.py | 405 ++++ .../uniformer/mmcv/ops/deform_roi_pool.py | 204 ++ .../uniformer/mmcv/ops/deprecated_wrappers.py | 43 + .../uniformer/mmcv/ops/focal_loss.py | 212 ++ .../mmcv/ops/furthest_point_sample.py | 83 + .../mmcv/ops/fused_bias_leakyrelu.py | 268 +++ .../uniformer/mmcv/ops/gather_points.py | 57 + .../uniformer/mmcv/ops/group_points.py | 224 ++ .../annotator/uniformer/mmcv/ops/info.py | 36 + .../annotator/uniformer/mmcv/ops/iou3d.py | 85 + .../annotator/uniformer/mmcv/ops/knn.py | 77 + .../uniformer/mmcv/ops/masked_conv.py | 111 + .../uniformer/mmcv/ops/merge_cells.py | 149 ++ .../mmcv/ops/modulated_deform_conv.py | 282 +++ .../mmcv/ops/multi_scale_deform_attn.py | 358 ++++ .../annotator/uniformer/mmcv/ops/nms.py | 417 ++++ .../uniformer/mmcv/ops/pixel_group.py | 75 + .../uniformer/mmcv/ops/point_sample.py | 336 +++ .../uniformer/mmcv/ops/points_in_boxes.py | 133 ++ .../uniformer/mmcv/ops/points_sampler.py | 177 ++ .../annotator/uniformer/mmcv/ops/psa_mask.py | 92 + .../annotator/uniformer/mmcv/ops/roi_align.py | 223 ++ .../uniformer/mmcv/ops/roi_align_rotated.py | 177 ++ .../annotator/uniformer/mmcv/ops/roi_pool.py | 86 + .../uniformer/mmcv/ops/roiaware_pool3d.py | 114 ++ .../uniformer/mmcv/ops/roipoint_pool3d.py | 77 + .../annotator/uniformer/mmcv/ops/saconv.py | 145 ++ .../uniformer/mmcv/ops/scatter_points.py | 135 ++ .../annotator/uniformer/mmcv/ops/sync_bn.py | 279 +++ .../uniformer/mmcv/ops/three_interpolate.py | 68 + .../annotator/uniformer/mmcv/ops/three_nn.py | 51 + .../annotator/uniformer/mmcv/ops/tin_shift.py | 68 + .../annotator/uniformer/mmcv/ops/upfirdn2d.py | 330 +++ .../annotator/uniformer/mmcv/ops/voxelize.py | 132 ++ .../uniformer/mmcv/parallel/__init__.py | 13 + .../uniformer/mmcv/parallel/_functions.py | 79 + .../uniformer/mmcv/parallel/collate.py | 84 + .../uniformer/mmcv/parallel/data_container.py | 89 + .../uniformer/mmcv/parallel/data_parallel.py | 89 + .../uniformer/mmcv/parallel/distributed.py | 112 + .../mmcv/parallel/distributed_deprecated.py | 70 + .../uniformer/mmcv/parallel/registry.py | 8 + .../uniformer/mmcv/parallel/scatter_gather.py | 59 + .../uniformer/mmcv/parallel/utils.py | 20 + .../uniformer/mmcv/runner/__init__.py | 47 + .../uniformer/mmcv/runner/base_module.py | 195 ++ .../uniformer/mmcv/runner/base_runner.py | 542 +++++ .../uniformer/mmcv/runner/builder.py | 24 + .../uniformer/mmcv/runner/checkpoint.py | 707 +++++++ .../mmcv/runner/default_constructor.py | 44 + .../uniformer/mmcv/runner/dist_utils.py | 164 ++ .../mmcv/runner/epoch_based_runner.py | 187 ++ .../uniformer/mmcv/runner/fp16_utils.py | 410 ++++ .../uniformer/mmcv/runner/hooks/__init__.py | 29 + .../uniformer/mmcv/runner/hooks/checkpoint.py | 167 ++ .../uniformer/mmcv/runner/hooks/closure.py | 11 + .../uniformer/mmcv/runner/hooks/ema.py | 89 + .../uniformer/mmcv/runner/hooks/evaluation.py | 509 +++++ .../uniformer/mmcv/runner/hooks/hook.py | 92 + .../uniformer/mmcv/runner/hooks/iter_timer.py | 18 + .../mmcv/runner/hooks/logger/__init__.py | 15 + .../mmcv/runner/hooks/logger/base.py | 166 ++ .../mmcv/runner/hooks/logger/dvclive.py | 58 + .../mmcv/runner/hooks/logger/mlflow.py | 78 + .../mmcv/runner/hooks/logger/neptune.py | 82 + .../mmcv/runner/hooks/logger/pavi.py | 117 ++ .../mmcv/runner/hooks/logger/tensorboard.py | 57 + .../mmcv/runner/hooks/logger/text.py | 256 +++ .../mmcv/runner/hooks/logger/wandb.py | 56 + .../uniformer/mmcv/runner/hooks/lr_updater.py | 670 ++++++ .../uniformer/mmcv/runner/hooks/memory.py | 25 + .../mmcv/runner/hooks/momentum_updater.py | 493 +++++ .../uniformer/mmcv/runner/hooks/optimizer.py | 508 +++++ .../uniformer/mmcv/runner/hooks/profiler.py | 180 ++ .../mmcv/runner/hooks/sampler_seed.py | 20 + .../mmcv/runner/hooks/sync_buffer.py | 22 + .../mmcv/runner/iter_based_runner.py | 273 +++ .../uniformer/mmcv/runner/log_buffer.py | 41 + .../mmcv/runner/optimizer/__init__.py | 9 + .../mmcv/runner/optimizer/builder.py | 44 + .../runner/optimizer/default_constructor.py | 249 +++ .../uniformer/mmcv/runner/priority.py | 60 + .../annotator/uniformer/mmcv/runner/utils.py | 93 + .../uniformer/mmcv/utils/__init__.py | 69 + .../annotator/uniformer/mmcv/utils/config.py | 688 +++++++ .../annotator/uniformer/mmcv/utils/env.py | 95 + .../uniformer/mmcv/utils/ext_loader.py | 71 + .../annotator/uniformer/mmcv/utils/logging.py | 110 + .../annotator/uniformer/mmcv/utils/misc.py | 377 ++++ .../uniformer/mmcv/utils/parrots_jit.py | 41 + .../uniformer/mmcv/utils/parrots_wrapper.py | 107 + .../annotator/uniformer/mmcv/utils/path.py | 101 + .../uniformer/mmcv/utils/progressbar.py | 208 ++ .../uniformer/mmcv/utils/registry.py | 315 +++ .../annotator/uniformer/mmcv/utils/testing.py | 140 ++ .../annotator/uniformer/mmcv/utils/timer.py | 118 ++ .../annotator/uniformer/mmcv/utils/trace.py | 23 + .../uniformer/mmcv/utils/version_utils.py | 90 + .../annotator/uniformer/mmcv/version.py | 35 + .../uniformer/mmcv/video/__init__.py | 11 + .../annotator/uniformer/mmcv/video/io.py | 318 +++ .../annotator/uniformer/mmcv/video/optflow.py | 254 +++ .../uniformer/mmcv/video/processing.py | 160 ++ .../uniformer/mmcv/visualization/__init__.py | 9 + .../uniformer/mmcv/visualization/color.py | 51 + .../uniformer/mmcv/visualization/image.py | 152 ++ .../uniformer/mmcv/visualization/optflow.py | 112 + .../uniformer/mmcv_custom/__init__.py | 5 + .../uniformer/mmcv_custom/checkpoint.py | 500 +++++ .../uniformer/mmseg/apis/__init__.py | 9 + .../uniformer/mmseg/apis/inference.py | 136 ++ .../annotator/uniformer/mmseg/apis/test.py | 238 +++ .../annotator/uniformer/mmseg/apis/train.py | 116 ++ .../uniformer/mmseg/core/__init__.py | 3 + .../mmseg/core/evaluation/__init__.py | 8 + .../mmseg/core/evaluation/class_names.py | 152 ++ .../mmseg/core/evaluation/eval_hooks.py | 109 + .../mmseg/core/evaluation/metrics.py | 326 +++ .../uniformer/mmseg/core/seg/__init__.py | 4 + .../uniformer/mmseg/core/seg/builder.py | 8 + .../mmseg/core/seg/sampler/__init__.py | 4 + .../core/seg/sampler/base_pixel_sampler.py | 12 + .../core/seg/sampler/ohem_pixel_sampler.py | 76 + .../uniformer/mmseg/core/utils/__init__.py | 3 + .../uniformer/mmseg/core/utils/misc.py | 17 + .../uniformer/mmseg/datasets/__init__.py | 19 + .../annotator/uniformer/mmseg/datasets/ade.py | 84 + .../uniformer/mmseg/datasets/builder.py | 169 ++ .../uniformer/mmseg/datasets/chase_db1.py | 27 + .../uniformer/mmseg/datasets/cityscapes.py | 217 ++ .../uniformer/mmseg/datasets/custom.py | 400 ++++ .../mmseg/datasets/dataset_wrappers.py | 50 + .../uniformer/mmseg/datasets/drive.py | 27 + .../annotator/uniformer/mmseg/datasets/hrf.py | 27 + .../mmseg/datasets/pascal_context.py | 103 + .../mmseg/datasets/pipelines/__init__.py | 16 + .../mmseg/datasets/pipelines/compose.py | 51 + .../mmseg/datasets/pipelines/formating.py | 288 +++ .../mmseg/datasets/pipelines/loading.py | 153 ++ .../mmseg/datasets/pipelines/test_time_aug.py | 133 ++ .../mmseg/datasets/pipelines/transforms.py | 889 ++++++++ .../uniformer/mmseg/datasets/stare.py | 27 + .../annotator/uniformer/mmseg/datasets/voc.py | 29 + .../uniformer/mmseg/models/__init__.py | 12 + .../mmseg/models/backbones/__init__.py | 17 + .../uniformer/mmseg/models/backbones/cgnet.py | 367 ++++ .../mmseg/models/backbones/fast_scnn.py | 375 ++++ .../uniformer/mmseg/models/backbones/hrnet.py | 555 +++++ .../mmseg/models/backbones/mobilenet_v2.py | 180 ++ .../mmseg/models/backbones/mobilenet_v3.py | 255 +++ .../mmseg/models/backbones/resnest.py | 314 +++ .../mmseg/models/backbones/resnet.py | 688 +++++++ .../mmseg/models/backbones/resnext.py | 145 ++ .../uniformer/mmseg/models/backbones/unet.py | 429 ++++ .../mmseg/models/backbones/uniformer.py | 422 ++++ .../uniformer/mmseg/models/backbones/vit.py | 459 +++++ .../uniformer/mmseg/models/builder.py | 46 + .../mmseg/models/decode_heads/__init__.py | 28 + .../mmseg/models/decode_heads/ann_head.py | 245 +++ .../mmseg/models/decode_heads/apc_head.py | 158 ++ .../mmseg/models/decode_heads/aspp_head.py | 107 + .../decode_heads/cascade_decode_head.py | 57 + .../mmseg/models/decode_heads/cc_head.py | 42 + .../mmseg/models/decode_heads/da_head.py | 178 ++ .../mmseg/models/decode_heads/decode_head.py | 234 +++ .../mmseg/models/decode_heads/dm_head.py | 140 ++ .../mmseg/models/decode_heads/dnl_head.py | 131 ++ .../mmseg/models/decode_heads/ema_head.py | 168 ++ .../mmseg/models/decode_heads/enc_head.py | 187 ++ .../mmseg/models/decode_heads/fcn_head.py | 81 + .../mmseg/models/decode_heads/fpn_head.py | 68 + .../mmseg/models/decode_heads/gc_head.py | 47 + .../mmseg/models/decode_heads/lraspp_head.py | 90 + .../mmseg/models/decode_heads/nl_head.py | 49 + .../mmseg/models/decode_heads/ocr_head.py | 127 ++ .../mmseg/models/decode_heads/point_head.py | 349 ++++ .../mmseg/models/decode_heads/psa_head.py | 196 ++ .../mmseg/models/decode_heads/psp_head.py | 101 + .../models/decode_heads/sep_aspp_head.py | 101 + .../mmseg/models/decode_heads/sep_fcn_head.py | 51 + .../mmseg/models/decode_heads/uper_head.py | 126 ++ .../uniformer/mmseg/models/losses/__init__.py | 12 + .../uniformer/mmseg/models/losses/accuracy.py | 78 + .../mmseg/models/losses/cross_entropy_loss.py | 198 ++ .../mmseg/models/losses/dice_loss.py | 119 ++ .../mmseg/models/losses/lovasz_loss.py | 303 +++ .../uniformer/mmseg/models/losses/utils.py | 121 ++ .../uniformer/mmseg/models/necks/__init__.py | 4 + .../uniformer/mmseg/models/necks/fpn.py | 212 ++ .../mmseg/models/necks/multilevel_neck.py | 70 + .../mmseg/models/segmentors/__init__.py | 5 + .../uniformer/mmseg/models/segmentors/base.py | 273 +++ .../segmentors/cascade_encoder_decoder.py | 98 + .../models/segmentors/encoder_decoder.py | 298 +++ .../uniformer/mmseg/models/utils/__init__.py | 13 + .../uniformer/mmseg/models/utils/drop.py | 31 + .../mmseg/models/utils/inverted_residual.py | 208 ++ .../mmseg/models/utils/make_divisible.py | 27 + .../uniformer/mmseg/models/utils/res_layer.py | 94 + .../uniformer/mmseg/models/utils/se_layer.py | 57 + .../models/utils/self_attention_block.py | 159 ++ .../mmseg/models/utils/up_conv_block.py | 101 + .../mmseg/models/utils/weight_init.py | 62 + .../annotator/uniformer/mmseg/ops/__init__.py | 4 + .../annotator/uniformer/mmseg/ops/encoding.py | 74 + .../annotator/uniformer/mmseg/ops/wrappers.py | 50 + .../uniformer/mmseg/utils/__init__.py | 4 + .../uniformer/mmseg/utils/collect_env.py | 17 + .../annotator/uniformer/mmseg/utils/logger.py | 27 + .../ControlNet/annotator/util.py | 38 + comparison_models/ControlNet/cldm/cldm.py | 435 ++++ .../ControlNet/cldm/ddim_hacked.py | 317 +++ comparison_models/ControlNet/cldm/hack.py | 111 + comparison_models/ControlNet/cldm/logger.py | 76 + comparison_models/ControlNet/cldm/model.py | 28 + comparison_models/ControlNet/config.py | 1 + .../ControlNet/docs/annotator.md | 49 + comparison_models/ControlNet/docs/faq.md | 21 + comparison_models/ControlNet/docs/low_vram.md | 15 + comparison_models/ControlNet/docs/train.md | 276 +++ comparison_models/ControlNet/environment.yaml | 35 + .../ControlNet/font/DejaVuSans.ttf | Bin 0 -> 757076 bytes .../ControlNet/gradio_annotator.py | 160 ++ .../ControlNet/gradio_canny2image.py | 97 + .../ControlNet/gradio_depth2image.py | 98 + .../ControlNet/gradio_fake_scribble2image.py | 102 + .../ControlNet/gradio_hed2image.py | 98 + .../ControlNet/gradio_hough2image.py | 100 + .../ControlNet/gradio_normal2image.py | 99 + .../ControlNet/gradio_pose2image.py | 98 + .../ControlNet/gradio_scribble2image.py | 92 + .../gradio_scribble2image_interactive.py | 102 + .../ControlNet/gradio_seg2image.py | 97 + .../ControlNet/ldm/data/__init__.py | 0 comparison_models/ControlNet/ldm/data/util.py | 24 + .../ControlNet/ldm/models/autoencoder.py | 219 ++ .../ldm/models/diffusion/__init__.py | 0 .../ControlNet/ldm/models/diffusion/ddim.py | 336 +++ .../ControlNet/ldm/models/diffusion/ddpm.py | 1797 +++++++++++++++++ .../models/diffusion/dpm_solver/__init__.py | 1 + .../models/diffusion/dpm_solver/dpm_solver.py | 1154 +++++++++++ .../models/diffusion/dpm_solver/sampler.py | 87 + .../ControlNet/ldm/models/diffusion/plms.py | 244 +++ .../ldm/models/diffusion/sampling_util.py | 22 + .../ControlNet/ldm/modules/attention.py | 341 ++++ .../ldm/modules/diffusionmodules/__init__.py | 0 .../ldm/modules/diffusionmodules/model.py | 852 ++++++++ .../modules/diffusionmodules/openaimodel.py | 786 +++++++ .../ldm/modules/diffusionmodules/upscaling.py | 81 + .../ldm/modules/diffusionmodules/util.py | 270 +++ .../ldm/modules/distributions/__init__.py | 0 .../modules/distributions/distributions.py | 92 + .../ControlNet/ldm/modules/ema.py | 80 + .../ldm/modules/encoders/__init__.py | 0 .../ldm/modules/encoders/modules.py | 213 ++ .../ldm/modules/image_degradation/__init__.py | 2 + .../ldm/modules/image_degradation/bsrgan.py | 730 +++++++ .../modules/image_degradation/bsrgan_light.py | 651 ++++++ .../modules/image_degradation/utils/test.png | Bin 0 -> 441072 bytes .../modules/image_degradation/utils_image.py | 916 +++++++++ .../ControlNet/ldm/modules/midas/__init__.py | 0 .../ControlNet/ldm/modules/midas/api.py | 170 ++ .../ldm/modules/midas/midas/__init__.py | 0 .../ldm/modules/midas/midas/base_model.py | 16 + .../ldm/modules/midas/midas/blocks.py | 342 ++++ .../ldm/modules/midas/midas/dpt_depth.py | 109 + .../ldm/modules/midas/midas/midas_net.py | 76 + .../modules/midas/midas/midas_net_custom.py | 128 ++ .../ldm/modules/midas/midas/transforms.py | 234 +++ .../ControlNet/ldm/modules/midas/midas/vit.py | 491 +++++ .../ControlNet/ldm/modules/midas/utils.py | 189 ++ comparison_models/ControlNet/ldm/util.py | 197 ++ .../ControlNet/models/cldm_v15.yaml | 79 + .../ControlNet/models/cldm_v21.yaml | 85 + comparison_models/ControlNet/share.py | 8 + .../ControlNet/tool_add_control.py | 50 + .../ControlNet/tool_add_control_sd21.py | 50 + .../ControlNet/tool_transfer_control.py | 59 + .../ControlNet/tutorial_dataset.py | 39 + .../ControlNet/tutorial_dataset_test.py | 12 + .../ControlNet/tutorial_train.py | 35 + .../ControlNet/tutorial_train_sd21.py | 35 + comparison_models/T2IAdapter/.gitignore | 127 ++ comparison_models/T2IAdapter/LICENSE | 201 ++ comparison_models/T2IAdapter/README.md | 331 +++ .../T2IAdapter/adapters/coadapters.py | 134 ++ .../adapters/t2i_adapters_for_canny.py | 47 + .../adapters/t2i_adapters_for_style.py | 63 + comparison_models/T2IAdapter/app.py | 178 ++ comparison_models/T2IAdapter/app_coadapter.py | 223 ++ .../T2IAdapter/assets/DejaVuSans.ttf | Bin 0 -> 757076 bytes .../T2IAdapter/assets/doodle.mp4 | Bin 0 -> 1076777 bytes comparison_models/T2IAdapter/assets/logo.png | Bin 0 -> 12895 bytes comparison_models/T2IAdapter/assets/logo2.png | Bin 0 -> 46324 bytes .../T2IAdapter/assets/logo_coadapter.png | Bin 0 -> 59004 bytes .../T2IAdapter/assets/overview1.png | Bin 0 -> 172369 bytes .../T2IAdapter/assets/overview2.png | Bin 0 -> 122294 bytes .../configs/mm/faster_rcnn_r50_fpn_coco.py | 182 ++ .../configs/mm/hrnet_w48_coco_256x192.py | 169 ++ .../configs/pl_train/coadapter-v1-train.yaml | 176 ++ .../configs/stable-diffusion/app.yaml | 87 + .../stable-diffusion/sd-v1-inference.yaml | 65 + .../configs/stable-diffusion/sd-v1-train.yaml | 86 + .../stable-diffusion/train_keypose.yaml | 87 + .../configs/stable-diffusion/train_mask.yaml | 87 + .../stable-diffusion/train_sketch.yaml | 87 + comparison_models/T2IAdapter/dist_util.py | 91 + .../T2IAdapter/docs/AdapterZoo.md | 16 + comparison_models/T2IAdapter/docs/FAQ.md | 5 + .../T2IAdapter/docs/coadapter.md | 50 + comparison_models/T2IAdapter/docs/examples.md | 43 + .../T2IAdapter/examples/README.md | 0 .../T2IAdapter/examples/download_examples.py | 48 + .../T2IAdapter/ldm/data/__init__.py | 0 .../T2IAdapter/ldm/data/dataset_coco.py | 36 + .../T2IAdapter/ldm/data/dataset_depth.py | 35 + .../T2IAdapter/ldm/data/dataset_laion.py | 144 ++ .../T2IAdapter/ldm/data/dataset_wikiart.py | 67 + .../T2IAdapter/ldm/data/utils.py | 84 + .../T2IAdapter/ldm/inference_base.py | 300 +++ .../T2IAdapter/ldm/lr_scheduler.py | 98 + .../T2IAdapter/ldm/models/autoencoder.py | 211 ++ .../ldm/models/diffusion/__init__.py | 0 .../T2IAdapter/ldm/models/diffusion/ddim.py | 293 +++ .../T2IAdapter/ldm/models/diffusion/ddpm.py | 1329 ++++++++++++ .../models/diffusion/dpm_solver/__init__.py | 1 + .../models/diffusion/dpm_solver/dpm_solver.py | 1217 +++++++++++ .../models/diffusion/dpm_solver/sampler.py | 87 + .../T2IAdapter/ldm/models/diffusion/plms.py | 243 +++ .../T2IAdapter/ldm/modules/attention.py | 344 ++++ .../ldm/modules/diffusionmodules/__init__.py | 0 .../ldm/modules/diffusionmodules/model.py | 852 ++++++++ .../modules/diffusionmodules/openaimodel.py | 798 ++++++++ .../ldm/modules/diffusionmodules/util.py | 270 +++ .../ldm/modules/distributions/__init__.py | 0 .../modules/distributions/distributions.py | 92 + .../T2IAdapter/ldm/modules/ema.py | 80 + .../ldm/modules/encoders/__init__.py | 0 .../ldm/modules/encoders/adapter.py | 339 ++++ .../ldm/modules/encoders/modules.py | 441 ++++ .../ldm/modules/extra_condition/__init__.py | 1 + .../ldm/modules/extra_condition/api.py | 269 +++ .../modules/extra_condition/midas/__init__.py | 0 .../ldm/modules/extra_condition/midas/api.py | 175 ++ .../extra_condition/midas/midas/__init__.py | 0 .../extra_condition/midas/midas/base_model.py | 16 + .../extra_condition/midas/midas/blocks.py | 342 ++++ .../extra_condition/midas/midas/dpt_depth.py | 109 + .../extra_condition/midas/midas/midas_net.py | 76 + .../midas/midas/midas_net_custom.py | 128 ++ .../extra_condition/midas/midas/transforms.py | 234 +++ .../extra_condition/midas/midas/vit.py | 491 +++++ .../modules/extra_condition/midas/utils.py | 189 ++ .../ldm/modules/extra_condition/model_edge.py | 653 ++++++ .../extra_condition/openpose/__init__.py | 0 .../modules/extra_condition/openpose/api.py | 35 + .../modules/extra_condition/openpose/body.py | 211 ++ .../modules/extra_condition/openpose/hand.py | 77 + .../modules/extra_condition/openpose/model.py | 178 ++ .../modules/extra_condition/openpose/util.py | 203 ++ .../ldm/modules/extra_condition/utils.py | 72 + .../ldm/modules/image_degradation/__init__.py | 2 + .../ldm/modules/image_degradation/bsrgan.py | 730 +++++++ .../modules/image_degradation/bsrgan_light.py | 651 ++++++ .../modules/image_degradation/utils/test.png | Bin 0 -> 441072 bytes .../modules/image_degradation/utils_image.py | 916 +++++++++ comparison_models/T2IAdapter/ldm/util.py | 200 ++ comparison_models/T2IAdapter/models/README.md | 6 + comparison_models/T2IAdapter/requirements.txt | 18 + comparison_models/T2IAdapter/test_adapter.py | 80 + .../T2IAdapter/test_composable_adapters.py | 101 + comparison_models/T2IAdapter/train.py | 524 +++++ comparison_models/T2IAdapter/train_depth.py | 281 +++ comparison_models/T2IAdapter/train_seg.py | 372 ++++ comparison_models/T2IAdapter/train_sketch.py | 399 ++++ configs/humansd/humansd-inference.yaml | 2 +- requirements.txt | 1 + scripts/gradio/pose2img.py | 5 - scripts/pose2img.py | 552 +++++ utils/download_data.py | 35 + 532 files changed, 80503 insertions(+), 51 deletions(-) create mode 100644 assets/pose/demo.npz create mode 100644 comparison_models/ControlNet/.gitignore create mode 100644 comparison_models/ControlNet/LICENSE create mode 100644 comparison_models/ControlNet/README.md create mode 100644 comparison_models/ControlNet/annotator/canny/__init__.py create mode 100644 comparison_models/ControlNet/annotator/ckpts/ckpts.txt create mode 100644 comparison_models/ControlNet/annotator/hed/__init__.py create mode 100644 comparison_models/ControlNet/annotator/midas/LICENSE create mode 100644 comparison_models/ControlNet/annotator/midas/__init__.py create mode 100644 comparison_models/ControlNet/annotator/midas/api.py create mode 100644 comparison_models/ControlNet/annotator/midas/midas/__init__.py create mode 100644 comparison_models/ControlNet/annotator/midas/midas/base_model.py create mode 100644 comparison_models/ControlNet/annotator/midas/midas/blocks.py create mode 100644 comparison_models/ControlNet/annotator/midas/midas/dpt_depth.py create mode 100644 comparison_models/ControlNet/annotator/midas/midas/midas_net.py create mode 100644 comparison_models/ControlNet/annotator/midas/midas/midas_net_custom.py create mode 100644 comparison_models/ControlNet/annotator/midas/midas/transforms.py create mode 100644 comparison_models/ControlNet/annotator/midas/midas/vit.py create mode 100644 comparison_models/ControlNet/annotator/midas/utils.py create mode 100644 comparison_models/ControlNet/annotator/mlsd/LICENSE create mode 100644 comparison_models/ControlNet/annotator/mlsd/__init__.py create mode 100644 comparison_models/ControlNet/annotator/mlsd/models/mbv2_mlsd_large.py create mode 100644 comparison_models/ControlNet/annotator/mlsd/models/mbv2_mlsd_tiny.py create mode 100644 comparison_models/ControlNet/annotator/mlsd/utils.py create mode 100644 comparison_models/ControlNet/annotator/openpose/LICENSE create mode 100644 comparison_models/ControlNet/annotator/openpose/__init__.py create mode 100644 comparison_models/ControlNet/annotator/openpose/body.py create mode 100644 comparison_models/ControlNet/annotator/openpose/hand.py create mode 100644 comparison_models/ControlNet/annotator/openpose/model.py create mode 100644 comparison_models/ControlNet/annotator/openpose/util.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/LICENSE create mode 100644 comparison_models/ControlNet/annotator/uniformer/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/ade20k.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/chase_db1.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/cityscapes.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/cityscapes_769x769.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/drive.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/hrf.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_context.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_context_59.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_voc12.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_voc12_aug.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/stare.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/default_runtime.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ann_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/apcnet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ccnet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/cgnet.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/danet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3_unet_s5-d16.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3plus_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/dmnet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/dnl_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/emanet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/encnet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fast_scnn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_hr18.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_unet_s5-d16.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fpn_r50.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fpn_uniformer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/gcnet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/lraspp_m-v3-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/nonlocal_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ocrnet_hr18.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ocrnet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pointrend_r50.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/psanet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pspnet_r50-d8.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pspnet_unet_s5-d16.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/upernet_r50.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/upernet_uniformer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_160k.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_20k.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_40k.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_80k.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/config.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/run.sh create mode 100644 comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test.sh create mode 100644 comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_g.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_h32.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_w32.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/arraymisc/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/arraymisc/quantization.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/alexnet.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/activation.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/context_block.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv2d_adaptive_padding.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv_module.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv_ws.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/depthwise_separable_conv_module.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/drop.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/generalized_attention.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/hsigmoid.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/hswish.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/non_local.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/norm.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/padding.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/plugin.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/registry.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/scale.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/swish.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/transformer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/upsample.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/wrappers.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/builder.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/resnet.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/flops_counter.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/fuse_conv_bn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/sync_bn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/weight_init.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/vgg.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/engine/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/engine/test.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/file_client.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/base.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/json_handler.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/pickle_handler.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/yaml_handler.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/io.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/parse.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/image/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/image/colorspace.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/image/geometric.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/image/io.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/image/misc.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/image/photometric.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/deprecated.json create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/mmcls.json create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/open_mmlab.json create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/assign_score_withk.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/ball_query.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/bbox.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/border_align.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/box_iou_rotated.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/carafe.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/cc_attention.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/contour_expand.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/corner_pool.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/correlation.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deform_conv.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deform_roi_pool.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deprecated_wrappers.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/focal_loss.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/furthest_point_sample.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/fused_bias_leakyrelu.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/gather_points.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/group_points.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/info.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/iou3d.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/knn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/masked_conv.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/merge_cells.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/modulated_deform_conv.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/multi_scale_deform_attn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/nms.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/pixel_group.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/point_sample.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/points_in_boxes.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/points_sampler.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/psa_mask.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_align.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_align_rotated.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_pool.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roiaware_pool3d.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roipoint_pool3d.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/saconv.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/scatter_points.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/sync_bn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/three_interpolate.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/three_nn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/tin_shift.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/upfirdn2d.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/ops/voxelize.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/_functions.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/collate.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/data_container.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/data_parallel.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/distributed.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/distributed_deprecated.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/registry.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/scatter_gather.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/utils.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/base_module.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/base_runner.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/builder.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/checkpoint.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/default_constructor.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/dist_utils.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/epoch_based_runner.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/fp16_utils.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/checkpoint.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/closure.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/ema.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/evaluation.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/hook.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/iter_timer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/base.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/dvclive.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/mlflow.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/neptune.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/pavi.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/tensorboard.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/text.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/wandb.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/lr_updater.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/memory.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/momentum_updater.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/optimizer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/profiler.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/sampler_seed.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/sync_buffer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/iter_based_runner.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/log_buffer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/builder.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/default_constructor.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/priority.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/runner/utils.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/config.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/env.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/ext_loader.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/logging.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/misc.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/parrots_jit.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/parrots_wrapper.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/path.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/progressbar.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/registry.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/testing.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/timer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/trace.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/utils/version_utils.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/version.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/video/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/video/io.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/video/optflow.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/video/processing.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/color.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/image.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/optflow.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv_custom/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmcv_custom/checkpoint.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/apis/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/apis/inference.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/apis/test.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/apis/train.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/class_names.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/eval_hooks.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/metrics.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/builder.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/base_pixel_sampler.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/ohem_pixel_sampler.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/utils/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/core/utils/misc.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/ade.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/builder.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/chase_db1.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/cityscapes.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/custom.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/dataset_wrappers.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/drive.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/hrf.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pascal_context.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/compose.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/formating.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/loading.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/test_time_aug.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/transforms.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/stare.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/voc.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/cgnet.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/fast_scnn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/hrnet.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/mobilenet_v2.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/mobilenet_v3.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnest.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnet.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnext.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/unet.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/uniformer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/vit.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/builder.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ann_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/apc_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/aspp_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/cascade_decode_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/cc_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/da_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/decode_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/dm_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/dnl_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ema_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/enc_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/fcn_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/fpn_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/gc_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/lraspp_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/nl_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ocr_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/point_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/psa_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/psp_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/sep_aspp_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/sep_fcn_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/uper_head.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/accuracy.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/cross_entropy_loss.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/dice_loss.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/lovasz_loss.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/utils.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/fpn.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/multilevel_neck.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/base.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/cascade_encoder_decoder.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/encoder_decoder.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/drop.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/inverted_residual.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/make_divisible.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/res_layer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/se_layer.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/self_attention_block.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/up_conv_block.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/weight_init.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/ops/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/ops/encoding.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/ops/wrappers.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/utils/__init__.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/utils/collect_env.py create mode 100644 comparison_models/ControlNet/annotator/uniformer/mmseg/utils/logger.py create mode 100644 comparison_models/ControlNet/annotator/util.py create mode 100644 comparison_models/ControlNet/cldm/cldm.py create mode 100644 comparison_models/ControlNet/cldm/ddim_hacked.py create mode 100644 comparison_models/ControlNet/cldm/hack.py create mode 100644 comparison_models/ControlNet/cldm/logger.py create mode 100644 comparison_models/ControlNet/cldm/model.py create mode 100644 comparison_models/ControlNet/config.py create mode 100644 comparison_models/ControlNet/docs/annotator.md create mode 100644 comparison_models/ControlNet/docs/faq.md create mode 100644 comparison_models/ControlNet/docs/low_vram.md create mode 100644 comparison_models/ControlNet/docs/train.md create mode 100644 comparison_models/ControlNet/environment.yaml create mode 100644 comparison_models/ControlNet/font/DejaVuSans.ttf create mode 100644 comparison_models/ControlNet/gradio_annotator.py create mode 100644 comparison_models/ControlNet/gradio_canny2image.py create mode 100644 comparison_models/ControlNet/gradio_depth2image.py create mode 100644 comparison_models/ControlNet/gradio_fake_scribble2image.py create mode 100644 comparison_models/ControlNet/gradio_hed2image.py create mode 100644 comparison_models/ControlNet/gradio_hough2image.py create mode 100644 comparison_models/ControlNet/gradio_normal2image.py create mode 100644 comparison_models/ControlNet/gradio_pose2image.py create mode 100644 comparison_models/ControlNet/gradio_scribble2image.py create mode 100644 comparison_models/ControlNet/gradio_scribble2image_interactive.py create mode 100644 comparison_models/ControlNet/gradio_seg2image.py create mode 100644 comparison_models/ControlNet/ldm/data/__init__.py create mode 100644 comparison_models/ControlNet/ldm/data/util.py create mode 100644 comparison_models/ControlNet/ldm/models/autoencoder.py create mode 100644 comparison_models/ControlNet/ldm/models/diffusion/__init__.py create mode 100644 comparison_models/ControlNet/ldm/models/diffusion/ddim.py create mode 100644 comparison_models/ControlNet/ldm/models/diffusion/ddpm.py create mode 100644 comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/__init__.py create mode 100644 comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/dpm_solver.py create mode 100644 comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/sampler.py create mode 100644 comparison_models/ControlNet/ldm/models/diffusion/plms.py create mode 100644 comparison_models/ControlNet/ldm/models/diffusion/sampling_util.py create mode 100644 comparison_models/ControlNet/ldm/modules/attention.py create mode 100644 comparison_models/ControlNet/ldm/modules/diffusionmodules/__init__.py create mode 100644 comparison_models/ControlNet/ldm/modules/diffusionmodules/model.py create mode 100644 comparison_models/ControlNet/ldm/modules/diffusionmodules/openaimodel.py create mode 100644 comparison_models/ControlNet/ldm/modules/diffusionmodules/upscaling.py create mode 100644 comparison_models/ControlNet/ldm/modules/diffusionmodules/util.py create mode 100644 comparison_models/ControlNet/ldm/modules/distributions/__init__.py create mode 100644 comparison_models/ControlNet/ldm/modules/distributions/distributions.py create mode 100644 comparison_models/ControlNet/ldm/modules/ema.py create mode 100644 comparison_models/ControlNet/ldm/modules/encoders/__init__.py create mode 100644 comparison_models/ControlNet/ldm/modules/encoders/modules.py create mode 100644 comparison_models/ControlNet/ldm/modules/image_degradation/__init__.py create mode 100644 comparison_models/ControlNet/ldm/modules/image_degradation/bsrgan.py create mode 100644 comparison_models/ControlNet/ldm/modules/image_degradation/bsrgan_light.py create mode 100644 comparison_models/ControlNet/ldm/modules/image_degradation/utils/test.png create mode 100644 comparison_models/ControlNet/ldm/modules/image_degradation/utils_image.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/__init__.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/api.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/midas/__init__.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/midas/base_model.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/midas/blocks.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/midas/dpt_depth.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/midas/midas_net.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/midas/midas_net_custom.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/midas/transforms.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/midas/vit.py create mode 100644 comparison_models/ControlNet/ldm/modules/midas/utils.py create mode 100644 comparison_models/ControlNet/ldm/util.py create mode 100644 comparison_models/ControlNet/models/cldm_v15.yaml create mode 100644 comparison_models/ControlNet/models/cldm_v21.yaml create mode 100644 comparison_models/ControlNet/share.py create mode 100644 comparison_models/ControlNet/tool_add_control.py create mode 100644 comparison_models/ControlNet/tool_add_control_sd21.py create mode 100644 comparison_models/ControlNet/tool_transfer_control.py create mode 100644 comparison_models/ControlNet/tutorial_dataset.py create mode 100644 comparison_models/ControlNet/tutorial_dataset_test.py create mode 100644 comparison_models/ControlNet/tutorial_train.py create mode 100644 comparison_models/ControlNet/tutorial_train_sd21.py create mode 100644 comparison_models/T2IAdapter/.gitignore create mode 100644 comparison_models/T2IAdapter/LICENSE create mode 100644 comparison_models/T2IAdapter/README.md create mode 100644 comparison_models/T2IAdapter/adapters/coadapters.py create mode 100644 comparison_models/T2IAdapter/adapters/t2i_adapters_for_canny.py create mode 100644 comparison_models/T2IAdapter/adapters/t2i_adapters_for_style.py create mode 100644 comparison_models/T2IAdapter/app.py create mode 100644 comparison_models/T2IAdapter/app_coadapter.py create mode 100644 comparison_models/T2IAdapter/assets/DejaVuSans.ttf create mode 100644 comparison_models/T2IAdapter/assets/doodle.mp4 create mode 100644 comparison_models/T2IAdapter/assets/logo.png create mode 100644 comparison_models/T2IAdapter/assets/logo2.png create mode 100644 comparison_models/T2IAdapter/assets/logo_coadapter.png create mode 100644 comparison_models/T2IAdapter/assets/overview1.png create mode 100644 comparison_models/T2IAdapter/assets/overview2.png create mode 100644 comparison_models/T2IAdapter/configs/mm/faster_rcnn_r50_fpn_coco.py create mode 100644 comparison_models/T2IAdapter/configs/mm/hrnet_w48_coco_256x192.py create mode 100644 comparison_models/T2IAdapter/configs/pl_train/coadapter-v1-train.yaml create mode 100644 comparison_models/T2IAdapter/configs/stable-diffusion/app.yaml create mode 100644 comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-inference.yaml create mode 100644 comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-train.yaml create mode 100644 comparison_models/T2IAdapter/configs/stable-diffusion/train_keypose.yaml create mode 100644 comparison_models/T2IAdapter/configs/stable-diffusion/train_mask.yaml create mode 100644 comparison_models/T2IAdapter/configs/stable-diffusion/train_sketch.yaml create mode 100644 comparison_models/T2IAdapter/dist_util.py create mode 100644 comparison_models/T2IAdapter/docs/AdapterZoo.md create mode 100644 comparison_models/T2IAdapter/docs/FAQ.md create mode 100644 comparison_models/T2IAdapter/docs/coadapter.md create mode 100644 comparison_models/T2IAdapter/docs/examples.md create mode 100644 comparison_models/T2IAdapter/examples/README.md create mode 100644 comparison_models/T2IAdapter/examples/download_examples.py create mode 100644 comparison_models/T2IAdapter/ldm/data/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/data/dataset_coco.py create mode 100644 comparison_models/T2IAdapter/ldm/data/dataset_depth.py create mode 100644 comparison_models/T2IAdapter/ldm/data/dataset_laion.py create mode 100644 comparison_models/T2IAdapter/ldm/data/dataset_wikiart.py create mode 100644 comparison_models/T2IAdapter/ldm/data/utils.py create mode 100644 comparison_models/T2IAdapter/ldm/inference_base.py create mode 100644 comparison_models/T2IAdapter/ldm/lr_scheduler.py create mode 100644 comparison_models/T2IAdapter/ldm/models/autoencoder.py create mode 100644 comparison_models/T2IAdapter/ldm/models/diffusion/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/models/diffusion/ddim.py create mode 100644 comparison_models/T2IAdapter/ldm/models/diffusion/ddpm.py create mode 100644 comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/dpm_solver.py create mode 100644 comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/sampler.py create mode 100644 comparison_models/T2IAdapter/ldm/models/diffusion/plms.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/attention.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/diffusionmodules/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/diffusionmodules/model.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/diffusionmodules/openaimodel.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/diffusionmodules/util.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/distributions/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/distributions/distributions.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/ema.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/encoders/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/encoders/adapter.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/encoders/modules.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/api.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/api.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/base_model.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/blocks.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/dpt_depth.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/midas_net.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/midas_net_custom.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/transforms.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/vit.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/utils.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/model_edge.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/api.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/body.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/hand.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/model.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/util.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/extra_condition/utils.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/image_degradation/__init__.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/image_degradation/bsrgan.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/image_degradation/bsrgan_light.py create mode 100644 comparison_models/T2IAdapter/ldm/modules/image_degradation/utils/test.png create mode 100644 comparison_models/T2IAdapter/ldm/modules/image_degradation/utils_image.py create mode 100644 comparison_models/T2IAdapter/ldm/util.py create mode 100644 comparison_models/T2IAdapter/models/README.md create mode 100644 comparison_models/T2IAdapter/requirements.txt create mode 100644 comparison_models/T2IAdapter/test_adapter.py create mode 100644 comparison_models/T2IAdapter/test_composable_adapters.py create mode 100644 comparison_models/T2IAdapter/train.py create mode 100644 comparison_models/T2IAdapter/train_depth.py create mode 100644 comparison_models/T2IAdapter/train_seg.py create mode 100644 comparison_models/T2IAdapter/train_sketch.py create mode 100644 scripts/pose2img.py create mode 100644 utils/download_data.py diff --git a/.gitignore b/.gitignore index ab7f35c..bdaa91b 100644 --- a/.gitignore +++ b/.gitignore @@ -165,6 +165,6 @@ cython_debug/ .vscode/ # Data -humansd_data -logs -sd_logs \ No newline at end of file +humansd_data/checkpoints +humansd_data/datasets +logs \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index f509cf4..46ad2ea 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ -[submodule "comparison_models/T2I-Adapter"] - path = comparison_models/T2I-Adapter - url = git@github.com:TencentARC/T2I-Adapter.git [submodule "comparison_models/ControlNet"] path = comparison_models/ControlNet - url = git@github.com:lllyasviel/ControlNet-v1-1-nightly.git + url = https://github.com/lllyasviel/ControlNet.git [submodule "comparison_models/T2IAdapter"] path = comparison_models/T2IAdapter - url = git@github.com:TencentARC/T2I-Adapter.git + url = https://github.com/TencentARC/T2I-Adapter.git diff --git a/README.md b/README.md index af89907..18af8f8 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,15 @@ HumanSD shows its superiorities in terms of (I) challenging poses, (II) accurate **Table of Contents** +- [HumanSD](#humansd) + - [TODO](#todo) - [Model Overview](#model-overview) - [Getting Started](#getting-started) - [Environment Requirement](#environment-requirement) - [Model and Checkpoints](#model-and-checkpoints) - [Quick Demo](#quick-demo) - [Dataset](#dataset) - - [Training](#training) + - [Training](#training) - [Quantitative Results](#quantitative-results) - [Qualitative Results](#qualitative-results) - [Natural Scene](#natural-scene) @@ -72,7 +74,7 @@ HumanSD has been implemented and tested on Pytorch 1.12.1 with python 3.9. Clone the repo: ```bash -git clone --recursive git@github.com:IDEA-Research/HumanSD.git +git clone git@github.com:IDEA-Research/HumanSD.git ``` We recommend you first install `pytorch` following [official instructions](https://pytorch.org/get-started/previous-versions/). For example: @@ -88,15 +90,10 @@ Then, you can install required packages thourgh: pip install -r requirements.txt ``` -You also need to install MMPose following [here](https://github.com/open-mmlab/mmpose). Noted that you only need to install MMPose as a python package. +You also need to install MMPose following [here](https://github.com/open-mmlab/mmpose). Noted that you only need to install MMPose as a python package. PS: Because of the update of MMPose, we recommend you to install 0.29.0 version of MMPose. ### Model and Checkpoints - - - -**Checkpoints** - Download necessary checkpoints of HumanSD, which can be found [here](https://drive.google.com/drive/folders/1NLQAlF7i0zjEpd-XY0EcVw9iXP5bB5BJ?usp=sharing). The data structure should be like: ``` @@ -110,26 +107,25 @@ Download necessary checkpoints of HumanSD, which can be found [here](https://dri Noted that v2-1_512-ema-pruned.ckpt should be download from [Stable Diffusion](https://github.com/Stability-AI/stablediffusion). -**models** +### Quick Demo -You also need to prepare the configs of MMPose models. You can directly download [mmpose/configs](https://github.com/open-mmlab/mmpose/tree/main/configs) and put it into humansd_data. Then the data structure will be: +You can run demo either through command line or gradio. + +You can run demo through command line with: ``` -|-- humansd_data - |-- models - |-- mmpose - |-- configs - |-- _base_ - |-- animal - |-- ... +python scripts/pose2img.py --prompt "oil painting of girls dancing on the stage" --pose_file assets/pose/demo.npz ``` +You can also run demo compared with ControlNet and T2I-Adapter: +``` +python scripts/pose2img.py --prompt "oil painting of girls dancing on the stage" --pose_file assets/pose/demo.npz --controlnet --t2i +``` -### Quick Demo -You can run demo through: +You can run gradio demo through: ``` python scripts/gradio/pose2img.py @@ -177,27 +173,34 @@ You may refer to the code [here](ldm/data/humansd.py) for loading the data. **Laion-Human** -You may apply for access of Laion-Human [here](https://forms.gle/ANxDTjxcE2Ua45oU8). Noted that we have provide the pose annotations, images' .parquet file and mapping file, please download the images according to .parquet. The `key` in .parquet is the corresponding image index. For example, image with `key=338717` in 00033.parquet is corresponding to images/00000/000338717.jpg. If you download the LAION-Aesthetics in tar files, which is different from our data structure, we recommend you extract the tar file through code: +You may apply for access of Laion-Human [here](https://forms.gle/ANxDTjxcE2Ua45oU8). Noted that we have provide the pose annotations, images' .parquet file and mapping file, please download the images according to .parquet. The `key` in .parquet is the corresponding image index. For example, image with `key=338717` in 00033.parquet is corresponding to images/00000/000338717.jpg. -```python -import tarfile -tar_file="00000.tar" # 00000.tar - 00286.tar -present_tar_path=f"xxxxxx/{tar_file}" -save_dir="humansd_data/datasets/Laion/Aesthetics_Human/images" -with tarfile.open(present_tar_path, "r") as tar_file: - for present_file in tar_file.getmembers(): - if present_file.name.endswith(".jpg"): - print(f" image:- {present_file.name} -") - image_save_path=os.path.join(save_dir,tar_file.replace(".tar",""),present_file.name) - present_image_fp=TarIO.TarIO(present_tar_path, present_file.name) - present_image=Image.open(present_image_fp) - present_image_numpy=cv2.cvtColor(np.array(present_image),cv2.COLOR_RGB2BGR) - if not os.path.exists(os.path.dirname(image_save_path)): - os.makedirs(os.path.dirname(image_save_path)) - cv2.imwrite(image_save_path,present_image_numpy) +After downloading the images and pose, you need to extract zip files and make it looks like: + + +``` +|-- humansd_data + |-- datasets + |-- Laion + |-- Aesthetics_Human + |-- images + |-- 00000.parquet + |-- 00001.parquet + |-- ... + |-- pose + |-- 00000 + |-- 000000000.npz + |-- 000000001.npz + |-- ... + |-- 00001 + |-- ... + |-- mapping_file_training.json ``` -The file data structure should be like: +Then, you can use `python utils/download_data.py` to download all images. + + +Then, the file data structure should be like: ``` |-- humansd_data @@ -205,6 +208,9 @@ The file data structure should be like: |-- Laion |-- Aesthetics_Human |-- images + |-- 00000.parquet + |-- 00001.parquet + |-- ... |-- 00000 |-- 000000000.jpg |-- 000000001.jpg @@ -221,6 +227,28 @@ The file data structure should be like: |-- mapping_file_training.json ``` + + +If you download the LAION-Aesthetics in tar files, which is different from our data structure, we recommend you extract the tar file through code: + +```python +import tarfile +tar_file="00000.tar" # 00000.tar - 00286.tar +present_tar_path=f"xxxxxx/{tar_file}" +save_dir="humansd_data/datasets/Laion/Aesthetics_Human/images" +with tarfile.open(present_tar_path, "r") as tar_file: + for present_file in tar_file.getmembers(): + if present_file.name.endswith(".jpg"): + print(f" image:- {present_file.name} -") + image_save_path=os.path.join(save_dir,tar_file.replace(".tar",""),present_file.name) + present_image_fp=TarIO.TarIO(present_tar_path, present_file.name) + present_image=Image.open(present_image_fp) + present_image_numpy=cv2.cvtColor(np.array(present_image),cv2.COLOR_RGB2BGR) + if not os.path.exists(os.path.dirname(image_save_path)): + os.makedirs(os.path.dirname(image_save_path)) + cv2.imwrite(image_save_path,present_image_numpy) +``` + **Human-Art** diff --git a/assets/pose/demo.npz b/assets/pose/demo.npz new file mode 100644 index 0000000000000000000000000000000000000000..c59db82f6a4a2f72d22c5b2f344d035c605b762d GIT binary patch literal 3129 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 Qs3(M~$4u4rvF7~$A54&&_5c6? literal 0 HcmV?d00001 diff --git a/comparison_models/ControlNet/.gitignore b/comparison_models/ControlNet/.gitignore new file mode 100644 index 0000000..325e5fd --- /dev/null +++ b/comparison_models/ControlNet/.gitignore @@ -0,0 +1,143 @@ +.idea/ + +training/ +lightning_logs/ +image_log/ + +*.pth +*.pt +*.ckpt +*.safetensors + +gradio_pose2image_private.py +gradio_canny2image_private.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/comparison_models/ControlNet/LICENSE b/comparison_models/ControlNet/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/comparison_models/ControlNet/LICENSE @@ -0,0 +1,201 @@ + 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 [yyyy] [name of copyright owner] + + 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/comparison_models/ControlNet/README.md b/comparison_models/ControlNet/README.md new file mode 100644 index 0000000..e526089 --- /dev/null +++ b/comparison_models/ControlNet/README.md @@ -0,0 +1,348 @@ +# News: A nightly version of ControlNet 1.1 is released! + +[ControlNet 1.1](https://github.com/lllyasviel/ControlNet-v1-1-nightly) is released. Those new models will be merged to this repo after we make sure that everything is good. + +# Below is ControlNet 1.0 + +Official implementation of [Adding Conditional Control to Text-to-Image Diffusion Models](https://arxiv.org/abs/2302.05543). + +ControlNet is a neural network structure to control diffusion models by adding extra conditions. + +![img](github_page/he.png) + +It copys the weights of neural network blocks into a "locked" copy and a "trainable" copy. + +The "trainable" one learns your condition. The "locked" one preserves your model. + +Thanks to this, training with small dataset of image pairs will not destroy the production-ready diffusion models. + +The "zero convolution" is 1×1 convolution with both weight and bias initialized as zeros. + +Before training, all zero convolutions output zeros, and ControlNet will not cause any distortion. + +No layer is trained from scratch. You are still fine-tuning. Your original model is safe. + +This allows training on small-scale or even personal devices. + +This is also friendly to merge/replacement/offsetting of models/weights/blocks/layers. + +### FAQ + +**Q:** But wait, if the weight of a conv layer is zero, the gradient will also be zero, and the network will not learn anything. Why "zero convolution" works? + +**A:** This is not true. [See an explanation here](docs/faq.md). + +# Stable Diffusion + ControlNet + +By repeating the above simple structure 14 times, we can control stable diffusion in this way: + +![img](github_page/sd.png) + +In this way, the ControlNet can **reuse** the SD encoder as a **deep, strong, robust, and powerful backbone** to learn diverse controls. Many evidences (like [this](https://jerryxu.net/ODISE/) and [this](https://vpd.ivg-research.xyz/)) validate that the SD encoder is an excellent backbone. + +Note that the way we connect layers is computational efficient. The original SD encoder does not need to store gradients (the locked original SD Encoder Block 1234 and Middle). The required GPU memory is not much larger than original SD, although many layers are added. Great! + +# Features & News + +2023/0/14 - We released [ControlNet 1.1](https://github.com/lllyasviel/ControlNet-v1-1-nightly). Those new models will be merged to this repo after we make sure that everything is good. + +2023/03/03 - We released a discussion - [Precomputed ControlNet: Speed up ControlNet by 45%, but is it necessary?](https://github.com/lllyasviel/ControlNet/discussions/216) + +2023/02/26 - We released a blog - [Ablation Study: Why ControlNets use deep encoder? What if it was lighter? Or even an MLP?](https://github.com/lllyasviel/ControlNet/discussions/188) + +2023/02/20 - Implementation for non-prompt mode released. See also [Guess Mode / Non-Prompt Mode](#guess-anchor). + +2023/02/12 - Now you can play with any community model by [Transferring the ControlNet](https://github.com/lllyasviel/ControlNet/discussions/12). + +2023/02/11 - [Low VRAM mode](docs/low_vram.md) is added. Please use this mode if you are using 8GB GPU(s) or if you want larger batch size. + +# Production-Ready Pretrained Models + +First create a new conda environment + + conda env create -f environment.yaml + conda activate control + +All models and detectors can be downloaded from [our Hugging Face page](https://huggingface.co/lllyasviel/ControlNet). Make sure that SD models are put in "ControlNet/models" and detectors are put in "ControlNet/annotator/ckpts". Make sure that you download all necessary pretrained weights and detector models from that Hugging Face page, including HED edge detection model, Midas depth estimation model, Openpose, and so on. + +We provide 9 Gradio apps with these models. + +All test images can be found at the folder "test_imgs". + +## ControlNet with Canny Edge + +Stable Diffusion 1.5 + ControlNet (using simple Canny edge detection) + + python gradio_canny2image.py + +The Gradio app also allows you to change the Canny edge thresholds. Just try it for more details. + +Prompt: "bird" +![p](github_page/p1.png) + +Prompt: "cute dog" +![p](github_page/p2.png) + +## ControlNet with M-LSD Lines + +Stable Diffusion 1.5 + ControlNet (using simple M-LSD straight line detection) + + python gradio_hough2image.py + +The Gradio app also allows you to change the M-LSD thresholds. Just try it for more details. + +Prompt: "room" +![p](github_page/p3.png) + +Prompt: "building" +![p](github_page/p4.png) + +## ControlNet with HED Boundary + +Stable Diffusion 1.5 + ControlNet (using soft HED Boundary) + + python gradio_hed2image.py + +The soft HED Boundary will preserve many details in input images, making this app suitable for recoloring and stylizing. Just try it for more details. + +Prompt: "oil painting of handsome old man, masterpiece" +![p](github_page/p5.png) + +Prompt: "Cyberpunk robot" +![p](github_page/p6.png) + +## ControlNet with User Scribbles + +Stable Diffusion 1.5 + ControlNet (using Scribbles) + + python gradio_scribble2image.py + +Note that the UI is based on Gradio, and Gradio is somewhat difficult to customize. Right now you need to draw scribbles outside the UI (using your favorite drawing software, for example, MS Paint) and then import the scribble image to Gradio. + +Prompt: "turtle" +![p](github_page/p7.png) + +Prompt: "hot air balloon" +![p](github_page/p8.png) + +### Interactive Interface + +We actually provide an interactive interface + + python gradio_scribble2image_interactive.py + +~~However, because gradio is very [buggy](https://github.com/gradio-app/gradio/issues/3166) and difficult to customize, right now, user need to first set canvas width and heights and then click "Open drawing canvas" to get a drawing area. Please do not upload image to that drawing canvas. Also, the drawing area is very small; it should be bigger. But I failed to find out how to make it larger. Again, gradio is really buggy.~~ (Now fixed, will update asap) + +The below dog sketch is drawn by me. Perhaps we should draw a better dog for showcase. + +Prompt: "dog in a room" +![p](github_page/p20.png) + +## ControlNet with Fake Scribbles + +Stable Diffusion 1.5 + ControlNet (using fake scribbles) + + python gradio_fake_scribble2image.py + +Sometimes we are lazy, and we do not want to draw scribbles. This script use the exactly same scribble-based model but use a simple algorithm to synthesize scribbles from input images. + +Prompt: "bag" +![p](github_page/p9.png) + +Prompt: "shose" (Note that "shose" is a typo; it should be "shoes". But it still seems to work.) +![p](github_page/p10.png) + +## ControlNet with Human Pose + +Stable Diffusion 1.5 + ControlNet (using human pose) + + python gradio_pose2image.py + +Apparently, this model deserves a better UI to directly manipulate pose skeleton. However, again, Gradio is somewhat difficult to customize. Right now you need to input an image and then the Openpose will detect the pose for you. + +Prompt: "Chief in the kitchen" +![p](github_page/p11.png) + +Prompt: "An astronaut on the moon" +![p](github_page/p12.png) + +## ControlNet with Semantic Segmentation + +Stable Diffusion 1.5 + ControlNet (using semantic segmentation) + + python gradio_seg2image.py + +This model use ADE20K's segmentation protocol. Again, this model deserves a better UI to directly draw the segmentations. However, again, Gradio is somewhat difficult to customize. Right now you need to input an image and then a model called Uniformer will detect the segmentations for you. Just try it for more details. + +Prompt: "House" +![p](github_page/p13.png) + +Prompt: "River" +![p](github_page/p14.png) + +## ControlNet with Depth + +Stable Diffusion 1.5 + ControlNet (using depth map) + + python gradio_depth2image.py + +Great! Now SD 1.5 also have a depth control. FINALLY. So many possibilities (considering SD1.5 has much more community models than SD2). + +Note that different from Stability's model, the ControlNet receive the full 512×512 depth map, rather than 64×64 depth. Note that Stability's SD2 depth model use 64*64 depth maps. This means that the ControlNet will preserve more details in the depth map. + +This is always a strength because if users do not want to preserve more details, they can simply use another SD to post-process an i2i. But if they want to preserve more details, ControlNet becomes their only choice. Again, SD2 uses 64×64 depth, we use 512×512. + +Prompt: "Stormtrooper's lecture" +![p](github_page/p15.png) + +## ControlNet with Normal Map + +Stable Diffusion 1.5 + ControlNet (using normal map) + + python gradio_normal2image.py + +This model use normal map. Rightnow in the APP, the normal is computed from the midas depth map and a user threshold (to determine how many area is background with identity normal face to viewer, tune the "Normal background threshold" in the gradio app to get a feeling). + +Prompt: "Cute toy" +![p](github_page/p17.png) + +Prompt: "Plaster statue of Abraham Lincoln" +![p](github_page/p18.png) + +Compared to depth model, this model seems to be a bit better at preserving the geometry. This is intuitive: minor details are not salient in depth maps, but are salient in normal maps. Below is the depth result with same inputs. You can see that the hairstyle of the man in the input image is modified by depth model, but preserved by the normal model. + +Prompt: "Plaster statue of Abraham Lincoln" +![p](github_page/p19.png) + +## ControlNet with Anime Line Drawing + +We also trained a relatively simple ControlNet for anime line drawings. This tool may be useful for artistic creations. (Although the image details in the results is a bit modified, since it still diffuse latent images.) + +This model is not available right now. We need to evaluate the potential risks before releasing this model. Nevertheless, you may be interested in [transferring the ControlNet to any community model](https://github.com/lllyasviel/ControlNet/discussions/12). + +![p](github_page/p21.png) + + + +# Guess Mode / Non-Prompt Mode + +The "guess mode" (or called non-prompt mode) will completely unleash all the power of the very powerful ControlNet encoder. + +See also the blog - [Ablation Study: Why ControlNets use deep encoder? What if it was lighter? Or even an MLP?](https://github.com/lllyasviel/ControlNet/discussions/188) + +You need to manually check the "Guess Mode" toggle to enable this mode. + +In this mode, the ControlNet encoder will try best to recognize the content of the input control map, like depth map, edge map, scribbles, etc, even if you remove all prompts. + +**Let's have fun with some very challenging experimental settings!** + +**No prompts. No "positive" prompts. No "negative" prompts. No extra caption detector. One single diffusion loop.** + +For this mode, we recommend to use 50 steps and guidance scale between 3 and 5. + +![p](github_page/uc2a.png) + +No prompts: + +![p](github_page/uc2b.png) + +Note that the below example is 768×768. No prompts. No "positive" prompts. No "negative" prompts. + +![p](github_page/uc1.png) + +By tuning the parameters, you can get some very intereting results like below: + +![p](github_page/uc3.png) + +Because no prompt is available, the ControlNet encoder will "guess" what is in the control map. Sometimes the guess result is really interesting. Because diffusion algorithm can essentially give multiple results, the ControlNet seems able to give multiple guesses, like this: + +![p](github_page/uc4.png) + +Without prompt, the HED seems good at generating images look like paintings when the control strength is relatively low: + +![p](github_page/uc6.png) + +The Guess Mode is also supported in [WebUI Plugin](https://github.com/Mikubill/sd-webui-controlnet): + +![p](github_page/uci1.png) + +No prompts. Default WebUI parameters. Pure random results with the seed being 12345. Standard SD1.5. Input scribble is in "test_imgs" folder to reproduce. + +![p](github_page/uci2.png) + +Below is another challenging example: + +![p](github_page/uci3.png) + +No prompts. Default WebUI parameters. Pure random results with the seed being 12345. Standard SD1.5. Input scribble is in "test_imgs" folder to reproduce. + +![p](github_page/uci4.png) + +Note that in the guess mode, you will still be able to input prompts. The only difference is that the model will "try harder" to guess what is in the control map even if you do not provide the prompt. Just try it yourself! + +Besides, if you write some scripts (like BLIP) to generate image captions from the "guess mode" images, and then use the generated captions as prompts to diffuse again, you will get a SOTA pipeline for fully automatic conditional image generating. + +# Combining Multiple ControlNets + +ControlNets are composable: more than one ControlNet can be easily composed to multi-condition control. + +Right now this feature is in experimental stage in the [Mikubill' A1111 Webui Plugin](https://github.com/Mikubill/sd-webui-controlnet): + +![p](github_page/multi2.png) + +![p](github_page/multi.png) + +As long as the models are controlling the same SD, the "boundary" between different research projects does not even exist. This plugin also allows different methods to work together! + +# Use ControlNet in Any Community Model (SD1.X) + +This is an experimental feature. + +[See the steps here](https://github.com/lllyasviel/ControlNet/discussions/12). + +Or you may want to use the [Mikubill' A1111 Webui Plugin](https://github.com/Mikubill/sd-webui-controlnet) which is plug-and-play and does not need manual merging. + +# Annotate Your Own Data + +We provide simple python scripts to process images. + +[See a gradio example here](docs/annotator.md). + +# Train with Your Own Data + +Training a ControlNet is as easy as (or even easier than) training a simple pix2pix. + +[See the steps here](docs/train.md). + +# Related Resources + +Special Thank to the great project - [Mikubill' A1111 Webui Plugin](https://github.com/Mikubill/sd-webui-controlnet) ! + +We also thank Hysts for making [Hugging Face Space](https://huggingface.co/spaces/hysts/ControlNet) as well as more than 65 models in that amazing [Colab list](https://github.com/camenduru/controlnet-colab)! + +Thank haofanwang for making [ControlNet-for-Diffusers](https://github.com/haofanwang/ControlNet-for-Diffusers)! + +We also thank all authors for making Controlnet DEMOs, including but not limited to [fffiloni](https://huggingface.co/spaces/fffiloni/ControlNet-Video), [other-model](https://huggingface.co/spaces/hysts/ControlNet-with-other-models), [ThereforeGames](https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/7784), [RamAnanth1](https://huggingface.co/spaces/RamAnanth1/ControlNet), etc! + +Besides, you may also want to read these amazing related works: + +[Composer: Creative and Controllable Image Synthesis with Composable Conditions](https://github.com/damo-vilab/composer): A much bigger model to control diffusion! + +[T2I-Adapter: Learning Adapters to Dig out More Controllable Ability for Text-to-Image Diffusion Models](https://github.com/TencentARC/T2I-Adapter): A much smaller model to control stable diffusion! + +[ControlLoRA: A Light Neural Network To Control Stable Diffusion Spatial Information](https://github.com/HighCWu/ControlLoRA): Implement Controlnet using LORA! + +And these amazing recent projects: [InstructPix2Pix Learning to Follow Image Editing Instructions](https://www.timothybrooks.com/instruct-pix2pix), [Pix2pix-zero: Zero-shot Image-to-Image Translation](https://github.com/pix2pixzero/pix2pix-zero), [Plug-and-Play Diffusion Features for Text-Driven Image-to-Image Translation](https://github.com/MichalGeyer/plug-and-play), [MaskSketch: Unpaired Structure-guided Masked Image Generation](https://arxiv.org/abs/2302.05496), [SEGA: Instructing Diffusion using Semantic Dimensions](https://arxiv.org/abs/2301.12247), [Universal Guidance for Diffusion Models](https://github.com/arpitbansal297/Universal-Guided-Diffusion), [Region-Aware Diffusion for Zero-shot Text-driven Image Editing](https://github.com/haha-lisa/RDM-Region-Aware-Diffusion-Model), [Domain Expansion of Image Generators](https://arxiv.org/abs/2301.05225), [Image Mixer](https://twitter.com/LambdaAPI/status/1626327289288957956), [MultiDiffusion: Fusing Diffusion Paths for Controlled Image Generation](https://multidiffusion.github.io/) + +# Citation + + @misc{zhang2023adding, + title={Adding Conditional Control to Text-to-Image Diffusion Models}, + author={Lvmin Zhang and Maneesh Agrawala}, + year={2023}, + eprint={2302.05543}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + +[Arxiv Link](https://arxiv.org/abs/2302.05543) diff --git a/comparison_models/ControlNet/annotator/canny/__init__.py b/comparison_models/ControlNet/annotator/canny/__init__.py new file mode 100644 index 0000000..cb0da95 --- /dev/null +++ b/comparison_models/ControlNet/annotator/canny/__init__.py @@ -0,0 +1,6 @@ +import cv2 + + +class CannyDetector: + def __call__(self, img, low_threshold, high_threshold): + return cv2.Canny(img, low_threshold, high_threshold) diff --git a/comparison_models/ControlNet/annotator/ckpts/ckpts.txt b/comparison_models/ControlNet/annotator/ckpts/ckpts.txt new file mode 100644 index 0000000..1978551 --- /dev/null +++ b/comparison_models/ControlNet/annotator/ckpts/ckpts.txt @@ -0,0 +1 @@ +Weights here. \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/hed/__init__.py b/comparison_models/ControlNet/annotator/hed/__init__.py new file mode 100644 index 0000000..a6a8fc7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/hed/__init__.py @@ -0,0 +1,96 @@ +# This is an improved version and model of HED edge detection with Apache License, Version 2.0. +# Please use this implementation in your products +# This implementation may produce slightly different results from Saining Xie's official implementations, +# but it generates smoother edges and is more suitable for ControlNet as well as other image-to-image translations. +# Different from official models and other implementations, this is an RGB-input model (rather than BGR) +# and in this way it works better for gradio's RGB protocol + +import os +import cv2 +import torch +import numpy as np + +from einops import rearrange +from annotator.util import annotator_ckpts_path + + +class DoubleConvBlock(torch.nn.Module): + def __init__(self, input_channel, output_channel, layer_number): + super().__init__() + self.convs = torch.nn.Sequential() + self.convs.append(torch.nn.Conv2d(in_channels=input_channel, out_channels=output_channel, kernel_size=(3, 3), stride=(1, 1), padding=1)) + for i in range(1, layer_number): + self.convs.append(torch.nn.Conv2d(in_channels=output_channel, out_channels=output_channel, kernel_size=(3, 3), stride=(1, 1), padding=1)) + self.projection = torch.nn.Conv2d(in_channels=output_channel, out_channels=1, kernel_size=(1, 1), stride=(1, 1), padding=0) + + def __call__(self, x, down_sampling=False): + h = x + if down_sampling: + h = torch.nn.functional.max_pool2d(h, kernel_size=(2, 2), stride=(2, 2)) + for conv in self.convs: + h = conv(h) + h = torch.nn.functional.relu(h) + return h, self.projection(h) + + +class ControlNetHED_Apache2(torch.nn.Module): + def __init__(self): + super().__init__() + self.norm = torch.nn.Parameter(torch.zeros(size=(1, 3, 1, 1))) + self.block1 = DoubleConvBlock(input_channel=3, output_channel=64, layer_number=2) + self.block2 = DoubleConvBlock(input_channel=64, output_channel=128, layer_number=2) + self.block3 = DoubleConvBlock(input_channel=128, output_channel=256, layer_number=3) + self.block4 = DoubleConvBlock(input_channel=256, output_channel=512, layer_number=3) + self.block5 = DoubleConvBlock(input_channel=512, output_channel=512, layer_number=3) + + def __call__(self, x): + h = x - self.norm + h, projection1 = self.block1(h) + h, projection2 = self.block2(h, down_sampling=True) + h, projection3 = self.block3(h, down_sampling=True) + h, projection4 = self.block4(h, down_sampling=True) + h, projection5 = self.block5(h, down_sampling=True) + return projection1, projection2, projection3, projection4, projection5 + + +class HEDdetector: + def __init__(self): + remote_model_path = "https://huggingface.co/lllyasviel/Annotators/resolve/main/ControlNetHED.pth" + modelpath = os.path.join(annotator_ckpts_path, "ControlNetHED.pth") + if not os.path.exists(modelpath): + from basicsr.utils.download_util import load_file_from_url + load_file_from_url(remote_model_path, model_dir=annotator_ckpts_path) + self.netNetwork = ControlNetHED_Apache2().float().cuda().eval() + self.netNetwork.load_state_dict(torch.load(modelpath)) + + def __call__(self, input_image): + assert input_image.ndim == 3 + H, W, C = input_image.shape + with torch.no_grad(): + image_hed = torch.from_numpy(input_image.copy()).float().cuda() + image_hed = rearrange(image_hed, 'h w c -> 1 c h w') + edges = self.netNetwork(image_hed) + edges = [e.detach().cpu().numpy().astype(np.float32)[0, 0] for e in edges] + edges = [cv2.resize(e, (W, H), interpolation=cv2.INTER_LINEAR) for e in edges] + edges = np.stack(edges, axis=2) + edge = 1 / (1 + np.exp(-np.mean(edges, axis=2).astype(np.float64))) + edge = (edge * 255.0).clip(0, 255).astype(np.uint8) + return edge + + +def nms(x, t, s): + x = cv2.GaussianBlur(x.astype(np.float32), (0, 0), s) + + f1 = np.array([[0, 0, 0], [1, 1, 1], [0, 0, 0]], dtype=np.uint8) + f2 = np.array([[0, 1, 0], [0, 1, 0], [0, 1, 0]], dtype=np.uint8) + f3 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.uint8) + f4 = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]], dtype=np.uint8) + + y = np.zeros_like(x) + + for f in [f1, f2, f3, f4]: + np.putmask(y, cv2.dilate(x, kernel=f) == x, x) + + z = np.zeros_like(y, dtype=np.uint8) + z[y > t] = 255 + return z diff --git a/comparison_models/ControlNet/annotator/midas/LICENSE b/comparison_models/ControlNet/annotator/midas/LICENSE new file mode 100644 index 0000000..277b5c1 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Intel ISL (Intel Intelligent Systems Lab) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/comparison_models/ControlNet/annotator/midas/__init__.py b/comparison_models/ControlNet/annotator/midas/__init__.py new file mode 100644 index 0000000..3678976 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/__init__.py @@ -0,0 +1,42 @@ +# Midas Depth Estimation +# From https://github.com/isl-org/MiDaS +# MIT LICENSE + +import cv2 +import numpy as np +import torch + +from einops import rearrange +from .api import MiDaSInference + + +class MidasDetector: + def __init__(self): + self.model = MiDaSInference(model_type="dpt_hybrid").cuda() + + def __call__(self, input_image, a=np.pi * 2.0, bg_th=0.1): + assert input_image.ndim == 3 + image_depth = input_image + with torch.no_grad(): + image_depth = torch.from_numpy(image_depth).float().cuda() + image_depth = image_depth / 127.5 - 1.0 + image_depth = rearrange(image_depth, 'h w c -> 1 c h w') + depth = self.model(image_depth)[0] + + depth_pt = depth.clone() + depth_pt -= torch.min(depth_pt) + depth_pt /= torch.max(depth_pt) + depth_pt = depth_pt.cpu().numpy() + depth_image = (depth_pt * 255.0).clip(0, 255).astype(np.uint8) + + depth_np = depth.cpu().numpy() + x = cv2.Sobel(depth_np, cv2.CV_32F, 1, 0, ksize=3) + y = cv2.Sobel(depth_np, cv2.CV_32F, 0, 1, ksize=3) + z = np.ones_like(x) * a + x[depth_pt < bg_th] = 0 + y[depth_pt < bg_th] = 0 + normal = np.stack([x, y, z], axis=2) + normal /= np.sum(normal ** 2.0, axis=2, keepdims=True) ** 0.5 + normal_image = (normal * 127.5 + 127.5).clip(0, 255).astype(np.uint8) + + return depth_image, normal_image diff --git a/comparison_models/ControlNet/annotator/midas/api.py b/comparison_models/ControlNet/annotator/midas/api.py new file mode 100644 index 0000000..1ab9f15 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/api.py @@ -0,0 +1,169 @@ +# based on https://github.com/isl-org/MiDaS + +import cv2 +import os +import torch +import torch.nn as nn +from torchvision.transforms import Compose + +from .midas.dpt_depth import DPTDepthModel +from .midas.midas_net import MidasNet +from .midas.midas_net_custom import MidasNet_small +from .midas.transforms import Resize, NormalizeImage, PrepareForNet +from annotator.util import annotator_ckpts_path + + +ISL_PATHS = { + "dpt_large": os.path.join(annotator_ckpts_path, "dpt_large-midas-2f21e586.pt"), + "dpt_hybrid": os.path.join(annotator_ckpts_path, "dpt_hybrid-midas-501f0c75.pt"), + "midas_v21": "", + "midas_v21_small": "", +} + +remote_model_path = "https://huggingface.co/lllyasviel/ControlNet/resolve/main/annotator/ckpts/dpt_hybrid-midas-501f0c75.pt" + + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +def load_midas_transform(model_type): + # https://github.com/isl-org/MiDaS/blob/master/run.py + # load transform only + if model_type == "dpt_large": # DPT-Large + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid": # DPT-Hybrid + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21": + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + elif model_type == "midas_v21_small": + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + else: + assert False, f"model_type '{model_type}' not implemented, use: --model_type large" + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + return transform + + +def load_model(model_type): + # https://github.com/isl-org/MiDaS/blob/master/run.py + # load network + model_path = ISL_PATHS[model_type] + if model_type == "dpt_large": # DPT-Large + model = DPTDepthModel( + path=model_path, + backbone="vitl16_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid": # DPT-Hybrid + if not os.path.exists(model_path): + from basicsr.utils.download_util import load_file_from_url + load_file_from_url(remote_model_path, model_dir=annotator_ckpts_path) + + model = DPTDepthModel( + path=model_path, + backbone="vitb_rn50_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21": + model = MidasNet(model_path, non_negative=True) + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + elif model_type == "midas_v21_small": + model = MidasNet_small(model_path, features=64, backbone="efficientnet_lite3", exportable=True, + non_negative=True, blocks={'expand': True}) + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + else: + print(f"model_type '{model_type}' not implemented, use: --model_type large") + assert False + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + return model.eval(), transform + + +class MiDaSInference(nn.Module): + MODEL_TYPES_TORCH_HUB = [ + "DPT_Large", + "DPT_Hybrid", + "MiDaS_small" + ] + MODEL_TYPES_ISL = [ + "dpt_large", + "dpt_hybrid", + "midas_v21", + "midas_v21_small", + ] + + def __init__(self, model_type): + super().__init__() + assert (model_type in self.MODEL_TYPES_ISL) + model, _ = load_model(model_type) + self.model = model + self.model.train = disabled_train + + def forward(self, x): + with torch.no_grad(): + prediction = self.model(x) + return prediction + diff --git a/comparison_models/ControlNet/annotator/midas/midas/__init__.py b/comparison_models/ControlNet/annotator/midas/midas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/ControlNet/annotator/midas/midas/base_model.py b/comparison_models/ControlNet/annotator/midas/midas/base_model.py new file mode 100644 index 0000000..5cf4302 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/midas/base_model.py @@ -0,0 +1,16 @@ +import torch + + +class BaseModel(torch.nn.Module): + def load(self, path): + """Load model from file. + + Args: + path (str): file path + """ + parameters = torch.load(path, map_location=torch.device('cpu')) + + if "optimizer" in parameters: + parameters = parameters["model"] + + self.load_state_dict(parameters) diff --git a/comparison_models/ControlNet/annotator/midas/midas/blocks.py b/comparison_models/ControlNet/annotator/midas/midas/blocks.py new file mode 100644 index 0000000..2145d18 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/midas/blocks.py @@ -0,0 +1,342 @@ +import torch +import torch.nn as nn + +from .vit import ( + _make_pretrained_vitb_rn50_384, + _make_pretrained_vitl16_384, + _make_pretrained_vitb16_384, + forward_vit, +) + +def _make_encoder(backbone, features, use_pretrained, groups=1, expand=False, exportable=True, hooks=None, use_vit_only=False, use_readout="ignore",): + if backbone == "vitl16_384": + pretrained = _make_pretrained_vitl16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [256, 512, 1024, 1024], features, groups=groups, expand=expand + ) # ViT-L/16 - 85.0% Top1 (backbone) + elif backbone == "vitb_rn50_384": + pretrained = _make_pretrained_vitb_rn50_384( + use_pretrained, + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) + scratch = _make_scratch( + [256, 512, 768, 768], features, groups=groups, expand=expand + ) # ViT-H/16 - 85.0% Top1 (backbone) + elif backbone == "vitb16_384": + pretrained = _make_pretrained_vitb16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [96, 192, 384, 768], features, groups=groups, expand=expand + ) # ViT-B/16 - 84.6% Top1 (backbone) + elif backbone == "resnext101_wsl": + pretrained = _make_pretrained_resnext101_wsl(use_pretrained) + scratch = _make_scratch([256, 512, 1024, 2048], features, groups=groups, expand=expand) # efficientnet_lite3 + elif backbone == "efficientnet_lite3": + pretrained = _make_pretrained_efficientnet_lite3(use_pretrained, exportable=exportable) + scratch = _make_scratch([32, 48, 136, 384], features, groups=groups, expand=expand) # efficientnet_lite3 + else: + print(f"Backbone '{backbone}' not implemented") + assert False + + return pretrained, scratch + + +def _make_scratch(in_shape, out_shape, groups=1, expand=False): + scratch = nn.Module() + + out_shape1 = out_shape + out_shape2 = out_shape + out_shape3 = out_shape + out_shape4 = out_shape + if expand==True: + out_shape1 = out_shape + out_shape2 = out_shape*2 + out_shape3 = out_shape*4 + out_shape4 = out_shape*8 + + scratch.layer1_rn = nn.Conv2d( + in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer2_rn = nn.Conv2d( + in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer3_rn = nn.Conv2d( + in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer4_rn = nn.Conv2d( + in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + + return scratch + + +def _make_pretrained_efficientnet_lite3(use_pretrained, exportable=False): + efficientnet = torch.hub.load( + "rwightman/gen-efficientnet-pytorch", + "tf_efficientnet_lite3", + pretrained=use_pretrained, + exportable=exportable + ) + return _make_efficientnet_backbone(efficientnet) + + +def _make_efficientnet_backbone(effnet): + pretrained = nn.Module() + + pretrained.layer1 = nn.Sequential( + effnet.conv_stem, effnet.bn1, effnet.act1, *effnet.blocks[0:2] + ) + pretrained.layer2 = nn.Sequential(*effnet.blocks[2:3]) + pretrained.layer3 = nn.Sequential(*effnet.blocks[3:5]) + pretrained.layer4 = nn.Sequential(*effnet.blocks[5:9]) + + return pretrained + + +def _make_resnet_backbone(resnet): + pretrained = nn.Module() + pretrained.layer1 = nn.Sequential( + resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, resnet.layer1 + ) + + pretrained.layer2 = resnet.layer2 + pretrained.layer3 = resnet.layer3 + pretrained.layer4 = resnet.layer4 + + return pretrained + + +def _make_pretrained_resnext101_wsl(use_pretrained): + resnet = torch.hub.load("facebookresearch/WSL-Images", "resnext101_32x8d_wsl") + return _make_resnet_backbone(resnet) + + + +class Interpolate(nn.Module): + """Interpolation module. + """ + + def __init__(self, scale_factor, mode, align_corners=False): + """Init. + + Args: + scale_factor (float): scaling + mode (str): interpolation mode + """ + super(Interpolate, self).__init__() + + self.interp = nn.functional.interpolate + self.scale_factor = scale_factor + self.mode = mode + self.align_corners = align_corners + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: interpolated data + """ + + x = self.interp( + x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners + ) + + return x + + +class ResidualConvUnit(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.relu = nn.ReLU(inplace=True) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + out = self.relu(x) + out = self.conv1(out) + out = self.relu(out) + out = self.conv2(out) + + return out + x + + +class FeatureFusionBlock(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock, self).__init__() + + self.resConfUnit1 = ResidualConvUnit(features) + self.resConfUnit2 = ResidualConvUnit(features) + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + output += self.resConfUnit1(xs[1]) + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=True + ) + + return output + + + + +class ResidualConvUnit_custom(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features, activation, bn): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.bn = bn + + self.groups=1 + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + if self.bn==True: + self.bn1 = nn.BatchNorm2d(features) + self.bn2 = nn.BatchNorm2d(features) + + self.activation = activation + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + + out = self.activation(x) + out = self.conv1(out) + if self.bn==True: + out = self.bn1(out) + + out = self.activation(out) + out = self.conv2(out) + if self.bn==True: + out = self.bn2(out) + + if self.groups > 1: + out = self.conv_merge(out) + + return self.skip_add.add(out, x) + + # return out + x + + +class FeatureFusionBlock_custom(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock_custom, self).__init__() + + self.deconv = deconv + self.align_corners = align_corners + + self.groups=1 + + self.expand = expand + out_features = features + if self.expand==True: + out_features = features//2 + + self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1) + + self.resConfUnit1 = ResidualConvUnit_custom(features, activation, bn) + self.resConfUnit2 = ResidualConvUnit_custom(features, activation, bn) + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + res = self.resConfUnit1(xs[1]) + output = self.skip_add.add(output, res) + # output += res + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=self.align_corners + ) + + output = self.out_conv(output) + + return output + diff --git a/comparison_models/ControlNet/annotator/midas/midas/dpt_depth.py b/comparison_models/ControlNet/annotator/midas/midas/dpt_depth.py new file mode 100644 index 0000000..4e9aab5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/midas/dpt_depth.py @@ -0,0 +1,109 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from .base_model import BaseModel +from .blocks import ( + FeatureFusionBlock, + FeatureFusionBlock_custom, + Interpolate, + _make_encoder, + forward_vit, +) + + +def _make_fusion_block(features, use_bn): + return FeatureFusionBlock_custom( + features, + nn.ReLU(False), + deconv=False, + bn=use_bn, + expand=False, + align_corners=True, + ) + + +class DPT(BaseModel): + def __init__( + self, + head, + features=256, + backbone="vitb_rn50_384", + readout="project", + channels_last=False, + use_bn=False, + ): + + super(DPT, self).__init__() + + self.channels_last = channels_last + + hooks = { + "vitb_rn50_384": [0, 1, 8, 11], + "vitb16_384": [2, 5, 8, 11], + "vitl16_384": [5, 11, 17, 23], + } + + # Instantiate backbone and reassemble blocks + self.pretrained, self.scratch = _make_encoder( + backbone, + features, + False, # Set to true of you want to train from scratch, uses ImageNet weights + groups=1, + expand=False, + exportable=False, + hooks=hooks[backbone], + use_readout=readout, + ) + + self.scratch.refinenet1 = _make_fusion_block(features, use_bn) + self.scratch.refinenet2 = _make_fusion_block(features, use_bn) + self.scratch.refinenet3 = _make_fusion_block(features, use_bn) + self.scratch.refinenet4 = _make_fusion_block(features, use_bn) + + self.scratch.output_conv = head + + + def forward(self, x): + if self.channels_last == True: + x.contiguous(memory_format=torch.channels_last) + + layer_1, layer_2, layer_3, layer_4 = forward_vit(self.pretrained, x) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return out + + +class DPTDepthModel(DPT): + def __init__(self, path=None, non_negative=True, **kwargs): + features = kwargs["features"] if "features" in kwargs else 256 + + head = nn.Sequential( + nn.Conv2d(features, features // 2, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear", align_corners=True), + nn.Conv2d(features // 2, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + super().__init__(head, **kwargs) + + if path is not None: + self.load(path) + + def forward(self, x): + return super().forward(x).squeeze(dim=1) + diff --git a/comparison_models/ControlNet/annotator/midas/midas/midas_net.py b/comparison_models/ControlNet/annotator/midas/midas/midas_net.py new file mode 100644 index 0000000..8a95497 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/midas/midas_net.py @@ -0,0 +1,76 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, Interpolate, _make_encoder + + +class MidasNet(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=256, non_negative=True): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + print("Loading weights: ", path) + + super(MidasNet, self).__init__() + + use_pretrained = False if path is None else True + + self.pretrained, self.scratch = _make_encoder(backbone="resnext101_wsl", features=features, use_pretrained=use_pretrained) + + self.scratch.refinenet4 = FeatureFusionBlock(features) + self.scratch.refinenet3 = FeatureFusionBlock(features) + self.scratch.refinenet2 = FeatureFusionBlock(features) + self.scratch.refinenet1 = FeatureFusionBlock(features) + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, 128, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(128, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + ) + + if path: + self.load(path) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) diff --git a/comparison_models/ControlNet/annotator/midas/midas/midas_net_custom.py b/comparison_models/ControlNet/annotator/midas/midas/midas_net_custom.py new file mode 100644 index 0000000..50e4acb --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/midas/midas_net_custom.py @@ -0,0 +1,128 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, FeatureFusionBlock_custom, Interpolate, _make_encoder + + +class MidasNet_small(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=64, backbone="efficientnet_lite3", non_negative=True, exportable=True, channels_last=False, align_corners=True, + blocks={'expand': True}): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + print("Loading weights: ", path) + + super(MidasNet_small, self).__init__() + + use_pretrained = False if path else True + + self.channels_last = channels_last + self.blocks = blocks + self.backbone = backbone + + self.groups = 1 + + features1=features + features2=features + features3=features + features4=features + self.expand = False + if "expand" in self.blocks and self.blocks['expand'] == True: + self.expand = True + features1=features + features2=features*2 + features3=features*4 + features4=features*8 + + self.pretrained, self.scratch = _make_encoder(self.backbone, features, use_pretrained, groups=self.groups, expand=self.expand, exportable=exportable) + + self.scratch.activation = nn.ReLU(False) + + self.scratch.refinenet4 = FeatureFusionBlock_custom(features4, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet3 = FeatureFusionBlock_custom(features3, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet2 = FeatureFusionBlock_custom(features2, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet1 = FeatureFusionBlock_custom(features1, self.scratch.activation, deconv=False, bn=False, align_corners=align_corners) + + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, features//2, kernel_size=3, stride=1, padding=1, groups=self.groups), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(features//2, 32, kernel_size=3, stride=1, padding=1), + self.scratch.activation, + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + if path: + self.load(path) + + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + if self.channels_last==True: + print("self.channels_last = ", self.channels_last) + x.contiguous(memory_format=torch.channels_last) + + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) + + + +def fuse_model(m): + prev_previous_type = nn.Identity() + prev_previous_name = '' + previous_type = nn.Identity() + previous_name = '' + for name, module in m.named_modules(): + if prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d and type(module) == nn.ReLU: + # print("FUSED ", prev_previous_name, previous_name, name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name, name], inplace=True) + elif prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d: + # print("FUSED ", prev_previous_name, previous_name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name], inplace=True) + # elif previous_type == nn.Conv2d and type(module) == nn.ReLU: + # print("FUSED ", previous_name, name) + # torch.quantization.fuse_modules(m, [previous_name, name], inplace=True) + + prev_previous_type = previous_type + prev_previous_name = previous_name + previous_type = type(module) + previous_name = name \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/midas/midas/transforms.py b/comparison_models/ControlNet/annotator/midas/midas/transforms.py new file mode 100644 index 0000000..350cbc1 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/midas/transforms.py @@ -0,0 +1,234 @@ +import numpy as np +import cv2 +import math + + +def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA): + """Rezise the sample to ensure the given size. Keeps aspect ratio. + + Args: + sample (dict): sample + size (tuple): image size + + Returns: + tuple: new size + """ + shape = list(sample["disparity"].shape) + + if shape[0] >= size[0] and shape[1] >= size[1]: + return sample + + scale = [0, 0] + scale[0] = size[0] / shape[0] + scale[1] = size[1] / shape[1] + + scale = max(scale) + + shape[0] = math.ceil(scale * shape[0]) + shape[1] = math.ceil(scale * shape[1]) + + # resize + sample["image"] = cv2.resize( + sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method + ) + + sample["disparity"] = cv2.resize( + sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST + ) + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + tuple(shape[::-1]), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return tuple(shape) + + +class Resize(object): + """Resize sample to given size (width, height). + """ + + def __init__( + self, + width, + height, + resize_target=True, + keep_aspect_ratio=False, + ensure_multiple_of=1, + resize_method="lower_bound", + image_interpolation_method=cv2.INTER_AREA, + ): + """Init. + + Args: + width (int): desired output width + height (int): desired output height + resize_target (bool, optional): + True: Resize the full sample (image, mask, target). + False: Resize image only. + Defaults to True. + keep_aspect_ratio (bool, optional): + True: Keep the aspect ratio of the input sample. + Output sample might not have the given width and height, and + resize behaviour depends on the parameter 'resize_method'. + Defaults to False. + ensure_multiple_of (int, optional): + Output width and height is constrained to be multiple of this parameter. + Defaults to 1. + resize_method (str, optional): + "lower_bound": Output will be at least as large as the given size. + "upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.) + "minimal": Scale as least as possible. (Output size might be smaller than given size.) + Defaults to "lower_bound". + """ + self.__width = width + self.__height = height + + self.__resize_target = resize_target + self.__keep_aspect_ratio = keep_aspect_ratio + self.__multiple_of = ensure_multiple_of + self.__resize_method = resize_method + self.__image_interpolation_method = image_interpolation_method + + def constrain_to_multiple_of(self, x, min_val=0, max_val=None): + y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if max_val is not None and y > max_val: + y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if y < min_val: + y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int) + + return y + + def get_size(self, width, height): + # determine new height and width + scale_height = self.__height / height + scale_width = self.__width / width + + if self.__keep_aspect_ratio: + if self.__resize_method == "lower_bound": + # scale such that output size is lower bound + if scale_width > scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "upper_bound": + # scale such that output size is upper bound + if scale_width < scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "minimal": + # scale as least as possbile + if abs(1 - scale_width) < abs(1 - scale_height): + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + else: + raise ValueError( + f"resize_method {self.__resize_method} not implemented" + ) + + if self.__resize_method == "lower_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, min_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, min_val=self.__width + ) + elif self.__resize_method == "upper_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, max_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, max_val=self.__width + ) + elif self.__resize_method == "minimal": + new_height = self.constrain_to_multiple_of(scale_height * height) + new_width = self.constrain_to_multiple_of(scale_width * width) + else: + raise ValueError(f"resize_method {self.__resize_method} not implemented") + + return (new_width, new_height) + + def __call__(self, sample): + width, height = self.get_size( + sample["image"].shape[1], sample["image"].shape[0] + ) + + # resize sample + sample["image"] = cv2.resize( + sample["image"], + (width, height), + interpolation=self.__image_interpolation_method, + ) + + if self.__resize_target: + if "disparity" in sample: + sample["disparity"] = cv2.resize( + sample["disparity"], + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + + if "depth" in sample: + sample["depth"] = cv2.resize( + sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST + ) + + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return sample + + +class NormalizeImage(object): + """Normlize image by given mean and std. + """ + + def __init__(self, mean, std): + self.__mean = mean + self.__std = std + + def __call__(self, sample): + sample["image"] = (sample["image"] - self.__mean) / self.__std + + return sample + + +class PrepareForNet(object): + """Prepare sample for usage as network input. + """ + + def __init__(self): + pass + + def __call__(self, sample): + image = np.transpose(sample["image"], (2, 0, 1)) + sample["image"] = np.ascontiguousarray(image).astype(np.float32) + + if "mask" in sample: + sample["mask"] = sample["mask"].astype(np.float32) + sample["mask"] = np.ascontiguousarray(sample["mask"]) + + if "disparity" in sample: + disparity = sample["disparity"].astype(np.float32) + sample["disparity"] = np.ascontiguousarray(disparity) + + if "depth" in sample: + depth = sample["depth"].astype(np.float32) + sample["depth"] = np.ascontiguousarray(depth) + + return sample diff --git a/comparison_models/ControlNet/annotator/midas/midas/vit.py b/comparison_models/ControlNet/annotator/midas/midas/vit.py new file mode 100644 index 0000000..ea46b1b --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/midas/vit.py @@ -0,0 +1,491 @@ +import torch +import torch.nn as nn +import timm +import types +import math +import torch.nn.functional as F + + +class Slice(nn.Module): + def __init__(self, start_index=1): + super(Slice, self).__init__() + self.start_index = start_index + + def forward(self, x): + return x[:, self.start_index :] + + +class AddReadout(nn.Module): + def __init__(self, start_index=1): + super(AddReadout, self).__init__() + self.start_index = start_index + + def forward(self, x): + if self.start_index == 2: + readout = (x[:, 0] + x[:, 1]) / 2 + else: + readout = x[:, 0] + return x[:, self.start_index :] + readout.unsqueeze(1) + + +class ProjectReadout(nn.Module): + def __init__(self, in_features, start_index=1): + super(ProjectReadout, self).__init__() + self.start_index = start_index + + self.project = nn.Sequential(nn.Linear(2 * in_features, in_features), nn.GELU()) + + def forward(self, x): + readout = x[:, 0].unsqueeze(1).expand_as(x[:, self.start_index :]) + features = torch.cat((x[:, self.start_index :], readout), -1) + + return self.project(features) + + +class Transpose(nn.Module): + def __init__(self, dim0, dim1): + super(Transpose, self).__init__() + self.dim0 = dim0 + self.dim1 = dim1 + + def forward(self, x): + x = x.transpose(self.dim0, self.dim1) + return x + + +def forward_vit(pretrained, x): + b, c, h, w = x.shape + + glob = pretrained.model.forward_flex(x) + + layer_1 = pretrained.activations["1"] + layer_2 = pretrained.activations["2"] + layer_3 = pretrained.activations["3"] + layer_4 = pretrained.activations["4"] + + layer_1 = pretrained.act_postprocess1[0:2](layer_1) + layer_2 = pretrained.act_postprocess2[0:2](layer_2) + layer_3 = pretrained.act_postprocess3[0:2](layer_3) + layer_4 = pretrained.act_postprocess4[0:2](layer_4) + + unflatten = nn.Sequential( + nn.Unflatten( + 2, + torch.Size( + [ + h // pretrained.model.patch_size[1], + w // pretrained.model.patch_size[0], + ] + ), + ) + ) + + if layer_1.ndim == 3: + layer_1 = unflatten(layer_1) + if layer_2.ndim == 3: + layer_2 = unflatten(layer_2) + if layer_3.ndim == 3: + layer_3 = unflatten(layer_3) + if layer_4.ndim == 3: + layer_4 = unflatten(layer_4) + + layer_1 = pretrained.act_postprocess1[3 : len(pretrained.act_postprocess1)](layer_1) + layer_2 = pretrained.act_postprocess2[3 : len(pretrained.act_postprocess2)](layer_2) + layer_3 = pretrained.act_postprocess3[3 : len(pretrained.act_postprocess3)](layer_3) + layer_4 = pretrained.act_postprocess4[3 : len(pretrained.act_postprocess4)](layer_4) + + return layer_1, layer_2, layer_3, layer_4 + + +def _resize_pos_embed(self, posemb, gs_h, gs_w): + posemb_tok, posemb_grid = ( + posemb[:, : self.start_index], + posemb[0, self.start_index :], + ) + + gs_old = int(math.sqrt(len(posemb_grid))) + + posemb_grid = posemb_grid.reshape(1, gs_old, gs_old, -1).permute(0, 3, 1, 2) + posemb_grid = F.interpolate(posemb_grid, size=(gs_h, gs_w), mode="bilinear") + posemb_grid = posemb_grid.permute(0, 2, 3, 1).reshape(1, gs_h * gs_w, -1) + + posemb = torch.cat([posemb_tok, posemb_grid], dim=1) + + return posemb + + +def forward_flex(self, x): + b, c, h, w = x.shape + + pos_embed = self._resize_pos_embed( + self.pos_embed, h // self.patch_size[1], w // self.patch_size[0] + ) + + B = x.shape[0] + + if hasattr(self.patch_embed, "backbone"): + x = self.patch_embed.backbone(x) + if isinstance(x, (list, tuple)): + x = x[-1] # last feature if backbone outputs list/tuple of features + + x = self.patch_embed.proj(x).flatten(2).transpose(1, 2) + + if getattr(self, "dist_token", None) is not None: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + dist_token = self.dist_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, dist_token, x), dim=1) + else: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + x = torch.cat((cls_tokens, x), dim=1) + + x = x + pos_embed + x = self.pos_drop(x) + + for blk in self.blocks: + x = blk(x) + + x = self.norm(x) + + return x + + +activations = {} + + +def get_activation(name): + def hook(model, input, output): + activations[name] = output + + return hook + + +def get_readout_oper(vit_features, features, use_readout, start_index=1): + if use_readout == "ignore": + readout_oper = [Slice(start_index)] * len(features) + elif use_readout == "add": + readout_oper = [AddReadout(start_index)] * len(features) + elif use_readout == "project": + readout_oper = [ + ProjectReadout(vit_features, start_index) for out_feat in features + ] + else: + assert ( + False + ), "wrong operation for readout token, use_readout can be 'ignore', 'add', or 'project'" + + return readout_oper + + +def _make_vit_b16_backbone( + model, + features=[96, 192, 384, 768], + size=[384, 384], + hooks=[2, 5, 8, 11], + vit_features=768, + use_readout="ignore", + start_index=1, +): + pretrained = nn.Module() + + pretrained.model = model + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + # 32, 48, 136, 384 + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitl16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_large_patch16_384", pretrained=pretrained) + + hooks = [5, 11, 17, 23] if hooks == None else hooks + return _make_vit_b16_backbone( + model, + features=[256, 512, 1024, 1024], + hooks=hooks, + vit_features=1024, + use_readout=use_readout, + ) + + +def _make_pretrained_vitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_pretrained_deitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_deit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_pretrained_deitb16_distil_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model( + "vit_deit_base_distilled_patch16_384", pretrained=pretrained + ) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, + features=[96, 192, 384, 768], + hooks=hooks, + use_readout=use_readout, + start_index=2, + ) + + +def _make_vit_b_rn50_backbone( + model, + features=[256, 512, 768, 768], + size=[384, 384], + hooks=[0, 1, 8, 11], + vit_features=768, + use_vit_only=False, + use_readout="ignore", + start_index=1, +): + pretrained = nn.Module() + + pretrained.model = model + + if use_vit_only == True: + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + else: + pretrained.model.patch_embed.backbone.stages[0].register_forward_hook( + get_activation("1") + ) + pretrained.model.patch_embed.backbone.stages[1].register_forward_hook( + get_activation("2") + ) + + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + if use_vit_only == True: + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + else: + pretrained.act_postprocess1 = nn.Sequential( + nn.Identity(), nn.Identity(), nn.Identity() + ) + pretrained.act_postprocess2 = nn.Sequential( + nn.Identity(), nn.Identity(), nn.Identity() + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitb_rn50_384( + pretrained, use_readout="ignore", hooks=None, use_vit_only=False +): + model = timm.create_model("vit_base_resnet50_384", pretrained=pretrained) + + hooks = [0, 1, 8, 11] if hooks == None else hooks + return _make_vit_b_rn50_backbone( + model, + features=[256, 512, 768, 768], + size=[384, 384], + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) diff --git a/comparison_models/ControlNet/annotator/midas/utils.py b/comparison_models/ControlNet/annotator/midas/utils.py new file mode 100644 index 0000000..9a9d3b5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/midas/utils.py @@ -0,0 +1,189 @@ +"""Utils for monoDepth.""" +import sys +import re +import numpy as np +import cv2 +import torch + + +def read_pfm(path): + """Read pfm file. + + Args: + path (str): path to file + + Returns: + tuple: (data, scale) + """ + with open(path, "rb") as file: + + color = None + width = None + height = None + scale = None + endian = None + + header = file.readline().rstrip() + if header.decode("ascii") == "PF": + color = True + elif header.decode("ascii") == "Pf": + color = False + else: + raise Exception("Not a PFM file: " + path) + + dim_match = re.match(r"^(\d+)\s(\d+)\s$", file.readline().decode("ascii")) + if dim_match: + width, height = list(map(int, dim_match.groups())) + else: + raise Exception("Malformed PFM header.") + + scale = float(file.readline().decode("ascii").rstrip()) + if scale < 0: + # little-endian + endian = "<" + scale = -scale + else: + # big-endian + endian = ">" + + data = np.fromfile(file, endian + "f") + shape = (height, width, 3) if color else (height, width) + + data = np.reshape(data, shape) + data = np.flipud(data) + + return data, scale + + +def write_pfm(path, image, scale=1): + """Write pfm file. + + Args: + path (str): pathto file + image (array): data + scale (int, optional): Scale. Defaults to 1. + """ + + with open(path, "wb") as file: + color = None + + if image.dtype.name != "float32": + raise Exception("Image dtype must be float32.") + + image = np.flipud(image) + + if len(image.shape) == 3 and image.shape[2] == 3: # color image + color = True + elif ( + len(image.shape) == 2 or len(image.shape) == 3 and image.shape[2] == 1 + ): # greyscale + color = False + else: + raise Exception("Image must have H x W x 3, H x W x 1 or H x W dimensions.") + + file.write("PF\n" if color else "Pf\n".encode()) + file.write("%d %d\n".encode() % (image.shape[1], image.shape[0])) + + endian = image.dtype.byteorder + + if endian == "<" or endian == "=" and sys.byteorder == "little": + scale = -scale + + file.write("%f\n".encode() % scale) + + image.tofile(file) + + +def read_image(path): + """Read image and output RGB image (0-1). + + Args: + path (str): path to file + + Returns: + array: RGB image (0-1) + """ + img = cv2.imread(path) + + if img.ndim == 2: + img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) / 255.0 + + return img + + +def resize_image(img): + """Resize image and make it fit for network. + + Args: + img (array): image + + Returns: + tensor: data ready for network + """ + height_orig = img.shape[0] + width_orig = img.shape[1] + + if width_orig > height_orig: + scale = width_orig / 384 + else: + scale = height_orig / 384 + + height = (np.ceil(height_orig / scale / 32) * 32).astype(int) + width = (np.ceil(width_orig / scale / 32) * 32).astype(int) + + img_resized = cv2.resize(img, (width, height), interpolation=cv2.INTER_AREA) + + img_resized = ( + torch.from_numpy(np.transpose(img_resized, (2, 0, 1))).contiguous().float() + ) + img_resized = img_resized.unsqueeze(0) + + return img_resized + + +def resize_depth(depth, width, height): + """Resize depth map and bring to CPU (numpy). + + Args: + depth (tensor): depth + width (int): image width + height (int): image height + + Returns: + array: processed depth + """ + depth = torch.squeeze(depth[0, :, :, :]).to("cpu") + + depth_resized = cv2.resize( + depth.numpy(), (width, height), interpolation=cv2.INTER_CUBIC + ) + + return depth_resized + +def write_depth(path, depth, bits=1): + """Write depth map to pfm and png file. + + Args: + path (str): filepath without extension + depth (array): depth + """ + write_pfm(path + ".pfm", depth.astype(np.float32)) + + depth_min = depth.min() + depth_max = depth.max() + + max_val = (2**(8*bits))-1 + + if depth_max - depth_min > np.finfo("float").eps: + out = max_val * (depth - depth_min) / (depth_max - depth_min) + else: + out = np.zeros(depth.shape, dtype=depth.type) + + if bits == 1: + cv2.imwrite(path + ".png", out.astype("uint8")) + elif bits == 2: + cv2.imwrite(path + ".png", out.astype("uint16")) + + return diff --git a/comparison_models/ControlNet/annotator/mlsd/LICENSE b/comparison_models/ControlNet/annotator/mlsd/LICENSE new file mode 100644 index 0000000..d855c6d --- /dev/null +++ b/comparison_models/ControlNet/annotator/mlsd/LICENSE @@ -0,0 +1,201 @@ + 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 2021-present NAVER Corp. + + 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. \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/mlsd/__init__.py b/comparison_models/ControlNet/annotator/mlsd/__init__.py new file mode 100644 index 0000000..c186070 --- /dev/null +++ b/comparison_models/ControlNet/annotator/mlsd/__init__.py @@ -0,0 +1,43 @@ +# MLSD Line Detection +# From https://github.com/navervision/mlsd +# Apache-2.0 license + +import cv2 +import numpy as np +import torch +import os + +from einops import rearrange +from .models.mbv2_mlsd_tiny import MobileV2_MLSD_Tiny +from .models.mbv2_mlsd_large import MobileV2_MLSD_Large +from .utils import pred_lines + +from annotator.util import annotator_ckpts_path + + +remote_model_path = "https://huggingface.co/lllyasviel/ControlNet/resolve/main/annotator/ckpts/mlsd_large_512_fp32.pth" + + +class MLSDdetector: + def __init__(self): + model_path = os.path.join(annotator_ckpts_path, "mlsd_large_512_fp32.pth") + if not os.path.exists(model_path): + from basicsr.utils.download_util import load_file_from_url + load_file_from_url(remote_model_path, model_dir=annotator_ckpts_path) + model = MobileV2_MLSD_Large() + model.load_state_dict(torch.load(model_path), strict=True) + self.model = model.cuda().eval() + + def __call__(self, input_image, thr_v, thr_d): + assert input_image.ndim == 3 + img = input_image + img_output = np.zeros_like(img) + try: + with torch.no_grad(): + lines = pred_lines(img, self.model, [img.shape[0], img.shape[1]], thr_v, thr_d) + for line in lines: + x_start, y_start, x_end, y_end = [int(val) for val in line] + cv2.line(img_output, (x_start, y_start), (x_end, y_end), [255, 255, 255], 1) + except Exception as e: + pass + return img_output[:, :, 0] diff --git a/comparison_models/ControlNet/annotator/mlsd/models/mbv2_mlsd_large.py b/comparison_models/ControlNet/annotator/mlsd/models/mbv2_mlsd_large.py new file mode 100644 index 0000000..5b9799e --- /dev/null +++ b/comparison_models/ControlNet/annotator/mlsd/models/mbv2_mlsd_large.py @@ -0,0 +1,292 @@ +import os +import sys +import torch +import torch.nn as nn +import torch.utils.model_zoo as model_zoo +from torch.nn import functional as F + + +class BlockTypeA(nn.Module): + def __init__(self, in_c1, in_c2, out_c1, out_c2, upscale = True): + super(BlockTypeA, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c2, out_c2, kernel_size=1), + nn.BatchNorm2d(out_c2), + nn.ReLU(inplace=True) + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c1, out_c1, kernel_size=1), + nn.BatchNorm2d(out_c1), + nn.ReLU(inplace=True) + ) + self.upscale = upscale + + def forward(self, a, b): + b = self.conv1(b) + a = self.conv2(a) + if self.upscale: + b = F.interpolate(b, scale_factor=2.0, mode='bilinear', align_corners=True) + return torch.cat((a, b), dim=1) + + +class BlockTypeB(nn.Module): + def __init__(self, in_c, out_c): + super(BlockTypeB, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=1), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c, out_c, kernel_size=3, padding=1), + nn.BatchNorm2d(out_c), + nn.ReLU() + ) + + def forward(self, x): + x = self.conv1(x) + x + x = self.conv2(x) + return x + +class BlockTypeC(nn.Module): + def __init__(self, in_c, out_c): + super(BlockTypeC, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=5, dilation=5), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=1), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv3 = nn.Conv2d(in_c, out_c, kernel_size=1) + + def forward(self, x): + x = self.conv1(x) + x = self.conv2(x) + x = self.conv3(x) + return x + +def _make_divisible(v, divisor, min_value=None): + """ + This function is taken from the original tf repo. + It ensures that all layers have a channel number that is divisible by 8 + It can be seen here: + https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py + :param v: + :param divisor: + :param min_value: + :return: + """ + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class ConvBNReLU(nn.Sequential): + def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1): + self.channel_pad = out_planes - in_planes + self.stride = stride + #padding = (kernel_size - 1) // 2 + + # TFLite uses slightly different padding than PyTorch + if stride == 2: + padding = 0 + else: + padding = (kernel_size - 1) // 2 + + super(ConvBNReLU, self).__init__( + nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False), + nn.BatchNorm2d(out_planes), + nn.ReLU6(inplace=True) + ) + self.max_pool = nn.MaxPool2d(kernel_size=stride, stride=stride) + + + def forward(self, x): + # TFLite uses different padding + if self.stride == 2: + x = F.pad(x, (0, 1, 0, 1), "constant", 0) + #print(x.shape) + + for module in self: + if not isinstance(module, nn.MaxPool2d): + x = module(x) + return x + + +class InvertedResidual(nn.Module): + def __init__(self, inp, oup, stride, expand_ratio): + super(InvertedResidual, self).__init__() + self.stride = stride + assert stride in [1, 2] + + hidden_dim = int(round(inp * expand_ratio)) + self.use_res_connect = self.stride == 1 and inp == oup + + layers = [] + if expand_ratio != 1: + # pw + layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1)) + layers.extend([ + # dw + ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), + nn.BatchNorm2d(oup), + ]) + self.conv = nn.Sequential(*layers) + + def forward(self, x): + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + +class MobileNetV2(nn.Module): + def __init__(self, pretrained=True): + """ + MobileNet V2 main class + Args: + num_classes (int): Number of classes + width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount + inverted_residual_setting: Network structure + round_nearest (int): Round the number of channels in each layer to be a multiple of this number + Set to 1 to turn off rounding + block: Module specifying inverted residual building block for mobilenet + """ + super(MobileNetV2, self).__init__() + + block = InvertedResidual + input_channel = 32 + last_channel = 1280 + width_mult = 1.0 + round_nearest = 8 + + inverted_residual_setting = [ + # t, c, n, s + [1, 16, 1, 1], + [6, 24, 2, 2], + [6, 32, 3, 2], + [6, 64, 4, 2], + [6, 96, 3, 1], + #[6, 160, 3, 2], + #[6, 320, 1, 1], + ] + + # only check the first element, assuming user knows t,c,n,s are required + if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4: + raise ValueError("inverted_residual_setting should be non-empty " + "or a 4-element list, got {}".format(inverted_residual_setting)) + + # building first layer + input_channel = _make_divisible(input_channel * width_mult, round_nearest) + self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest) + features = [ConvBNReLU(4, input_channel, stride=2)] + # building inverted residual blocks + for t, c, n, s in inverted_residual_setting: + output_channel = _make_divisible(c * width_mult, round_nearest) + for i in range(n): + stride = s if i == 0 else 1 + features.append(block(input_channel, output_channel, stride, expand_ratio=t)) + input_channel = output_channel + + self.features = nn.Sequential(*features) + self.fpn_selected = [1, 3, 6, 10, 13] + # weight initialization + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out') + if m.bias is not None: + nn.init.zeros_(m.bias) + elif isinstance(m, nn.BatchNorm2d): + nn.init.ones_(m.weight) + nn.init.zeros_(m.bias) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.zeros_(m.bias) + if pretrained: + self._load_pretrained_model() + + def _forward_impl(self, x): + # This exists since TorchScript doesn't support inheritance, so the superclass method + # (this one) needs to have a name other than `forward` that can be accessed in a subclass + fpn_features = [] + for i, f in enumerate(self.features): + if i > self.fpn_selected[-1]: + break + x = f(x) + if i in self.fpn_selected: + fpn_features.append(x) + + c1, c2, c3, c4, c5 = fpn_features + return c1, c2, c3, c4, c5 + + + def forward(self, x): + return self._forward_impl(x) + + def _load_pretrained_model(self): + pretrain_dict = model_zoo.load_url('https://download.pytorch.org/models/mobilenet_v2-b0353104.pth') + model_dict = {} + state_dict = self.state_dict() + for k, v in pretrain_dict.items(): + if k in state_dict: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + + +class MobileV2_MLSD_Large(nn.Module): + def __init__(self): + super(MobileV2_MLSD_Large, self).__init__() + + self.backbone = MobileNetV2(pretrained=False) + ## A, B + self.block15 = BlockTypeA(in_c1= 64, in_c2= 96, + out_c1= 64, out_c2=64, + upscale=False) + self.block16 = BlockTypeB(128, 64) + + ## A, B + self.block17 = BlockTypeA(in_c1 = 32, in_c2 = 64, + out_c1= 64, out_c2= 64) + self.block18 = BlockTypeB(128, 64) + + ## A, B + self.block19 = BlockTypeA(in_c1=24, in_c2=64, + out_c1=64, out_c2=64) + self.block20 = BlockTypeB(128, 64) + + ## A, B, C + self.block21 = BlockTypeA(in_c1=16, in_c2=64, + out_c1=64, out_c2=64) + self.block22 = BlockTypeB(128, 64) + + self.block23 = BlockTypeC(64, 16) + + def forward(self, x): + c1, c2, c3, c4, c5 = self.backbone(x) + + x = self.block15(c4, c5) + x = self.block16(x) + + x = self.block17(c3, x) + x = self.block18(x) + + x = self.block19(c2, x) + x = self.block20(x) + + x = self.block21(c1, x) + x = self.block22(x) + x = self.block23(x) + x = x[:, 7:, :, :] + + return x \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/mlsd/models/mbv2_mlsd_tiny.py b/comparison_models/ControlNet/annotator/mlsd/models/mbv2_mlsd_tiny.py new file mode 100644 index 0000000..e3ed633 --- /dev/null +++ b/comparison_models/ControlNet/annotator/mlsd/models/mbv2_mlsd_tiny.py @@ -0,0 +1,275 @@ +import os +import sys +import torch +import torch.nn as nn +import torch.utils.model_zoo as model_zoo +from torch.nn import functional as F + + +class BlockTypeA(nn.Module): + def __init__(self, in_c1, in_c2, out_c1, out_c2, upscale = True): + super(BlockTypeA, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c2, out_c2, kernel_size=1), + nn.BatchNorm2d(out_c2), + nn.ReLU(inplace=True) + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c1, out_c1, kernel_size=1), + nn.BatchNorm2d(out_c1), + nn.ReLU(inplace=True) + ) + self.upscale = upscale + + def forward(self, a, b): + b = self.conv1(b) + a = self.conv2(a) + b = F.interpolate(b, scale_factor=2.0, mode='bilinear', align_corners=True) + return torch.cat((a, b), dim=1) + + +class BlockTypeB(nn.Module): + def __init__(self, in_c, out_c): + super(BlockTypeB, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=1), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c, out_c, kernel_size=3, padding=1), + nn.BatchNorm2d(out_c), + nn.ReLU() + ) + + def forward(self, x): + x = self.conv1(x) + x + x = self.conv2(x) + return x + +class BlockTypeC(nn.Module): + def __init__(self, in_c, out_c): + super(BlockTypeC, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=5, dilation=5), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=1), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv3 = nn.Conv2d(in_c, out_c, kernel_size=1) + + def forward(self, x): + x = self.conv1(x) + x = self.conv2(x) + x = self.conv3(x) + return x + +def _make_divisible(v, divisor, min_value=None): + """ + This function is taken from the original tf repo. + It ensures that all layers have a channel number that is divisible by 8 + It can be seen here: + https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py + :param v: + :param divisor: + :param min_value: + :return: + """ + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class ConvBNReLU(nn.Sequential): + def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1): + self.channel_pad = out_planes - in_planes + self.stride = stride + #padding = (kernel_size - 1) // 2 + + # TFLite uses slightly different padding than PyTorch + if stride == 2: + padding = 0 + else: + padding = (kernel_size - 1) // 2 + + super(ConvBNReLU, self).__init__( + nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False), + nn.BatchNorm2d(out_planes), + nn.ReLU6(inplace=True) + ) + self.max_pool = nn.MaxPool2d(kernel_size=stride, stride=stride) + + + def forward(self, x): + # TFLite uses different padding + if self.stride == 2: + x = F.pad(x, (0, 1, 0, 1), "constant", 0) + #print(x.shape) + + for module in self: + if not isinstance(module, nn.MaxPool2d): + x = module(x) + return x + + +class InvertedResidual(nn.Module): + def __init__(self, inp, oup, stride, expand_ratio): + super(InvertedResidual, self).__init__() + self.stride = stride + assert stride in [1, 2] + + hidden_dim = int(round(inp * expand_ratio)) + self.use_res_connect = self.stride == 1 and inp == oup + + layers = [] + if expand_ratio != 1: + # pw + layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1)) + layers.extend([ + # dw + ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), + nn.BatchNorm2d(oup), + ]) + self.conv = nn.Sequential(*layers) + + def forward(self, x): + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + +class MobileNetV2(nn.Module): + def __init__(self, pretrained=True): + """ + MobileNet V2 main class + Args: + num_classes (int): Number of classes + width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount + inverted_residual_setting: Network structure + round_nearest (int): Round the number of channels in each layer to be a multiple of this number + Set to 1 to turn off rounding + block: Module specifying inverted residual building block for mobilenet + """ + super(MobileNetV2, self).__init__() + + block = InvertedResidual + input_channel = 32 + last_channel = 1280 + width_mult = 1.0 + round_nearest = 8 + + inverted_residual_setting = [ + # t, c, n, s + [1, 16, 1, 1], + [6, 24, 2, 2], + [6, 32, 3, 2], + [6, 64, 4, 2], + #[6, 96, 3, 1], + #[6, 160, 3, 2], + #[6, 320, 1, 1], + ] + + # only check the first element, assuming user knows t,c,n,s are required + if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4: + raise ValueError("inverted_residual_setting should be non-empty " + "or a 4-element list, got {}".format(inverted_residual_setting)) + + # building first layer + input_channel = _make_divisible(input_channel * width_mult, round_nearest) + self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest) + features = [ConvBNReLU(4, input_channel, stride=2)] + # building inverted residual blocks + for t, c, n, s in inverted_residual_setting: + output_channel = _make_divisible(c * width_mult, round_nearest) + for i in range(n): + stride = s if i == 0 else 1 + features.append(block(input_channel, output_channel, stride, expand_ratio=t)) + input_channel = output_channel + self.features = nn.Sequential(*features) + + self.fpn_selected = [3, 6, 10] + # weight initialization + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out') + if m.bias is not None: + nn.init.zeros_(m.bias) + elif isinstance(m, nn.BatchNorm2d): + nn.init.ones_(m.weight) + nn.init.zeros_(m.bias) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.zeros_(m.bias) + + #if pretrained: + # self._load_pretrained_model() + + def _forward_impl(self, x): + # This exists since TorchScript doesn't support inheritance, so the superclass method + # (this one) needs to have a name other than `forward` that can be accessed in a subclass + fpn_features = [] + for i, f in enumerate(self.features): + if i > self.fpn_selected[-1]: + break + x = f(x) + if i in self.fpn_selected: + fpn_features.append(x) + + c2, c3, c4 = fpn_features + return c2, c3, c4 + + + def forward(self, x): + return self._forward_impl(x) + + def _load_pretrained_model(self): + pretrain_dict = model_zoo.load_url('https://download.pytorch.org/models/mobilenet_v2-b0353104.pth') + model_dict = {} + state_dict = self.state_dict() + for k, v in pretrain_dict.items(): + if k in state_dict: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + + +class MobileV2_MLSD_Tiny(nn.Module): + def __init__(self): + super(MobileV2_MLSD_Tiny, self).__init__() + + self.backbone = MobileNetV2(pretrained=True) + + self.block12 = BlockTypeA(in_c1= 32, in_c2= 64, + out_c1= 64, out_c2=64) + self.block13 = BlockTypeB(128, 64) + + self.block14 = BlockTypeA(in_c1 = 24, in_c2 = 64, + out_c1= 32, out_c2= 32) + self.block15 = BlockTypeB(64, 64) + + self.block16 = BlockTypeC(64, 16) + + def forward(self, x): + c2, c3, c4 = self.backbone(x) + + x = self.block12(c3, c4) + x = self.block13(x) + x = self.block14(c2, x) + x = self.block15(x) + x = self.block16(x) + x = x[:, 7:, :, :] + #print(x.shape) + x = F.interpolate(x, scale_factor=2.0, mode='bilinear', align_corners=True) + + return x \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/mlsd/utils.py b/comparison_models/ControlNet/annotator/mlsd/utils.py new file mode 100644 index 0000000..ae3cf94 --- /dev/null +++ b/comparison_models/ControlNet/annotator/mlsd/utils.py @@ -0,0 +1,580 @@ +''' +modified by lihaoweicv +pytorch version +''' + +''' +M-LSD +Copyright 2021-present NAVER Corp. +Apache License v2.0 +''' + +import os +import numpy as np +import cv2 +import torch +from torch.nn import functional as F + + +def deccode_output_score_and_ptss(tpMap, topk_n = 200, ksize = 5): + ''' + tpMap: + center: tpMap[1, 0, :, :] + displacement: tpMap[1, 1:5, :, :] + ''' + b, c, h, w = tpMap.shape + assert b==1, 'only support bsize==1' + displacement = tpMap[:, 1:5, :, :][0] + center = tpMap[:, 0, :, :] + heat = torch.sigmoid(center) + hmax = F.max_pool2d( heat, (ksize, ksize), stride=1, padding=(ksize-1)//2) + keep = (hmax == heat).float() + heat = heat * keep + heat = heat.reshape(-1, ) + + scores, indices = torch.topk(heat, topk_n, dim=-1, largest=True) + yy = torch.floor_divide(indices, w).unsqueeze(-1) + xx = torch.fmod(indices, w).unsqueeze(-1) + ptss = torch.cat((yy, xx),dim=-1) + + ptss = ptss.detach().cpu().numpy() + scores = scores.detach().cpu().numpy() + displacement = displacement.detach().cpu().numpy() + displacement = displacement.transpose((1,2,0)) + return ptss, scores, displacement + + +def pred_lines(image, model, + input_shape=[512, 512], + score_thr=0.10, + dist_thr=20.0): + h, w, _ = image.shape + h_ratio, w_ratio = [h / input_shape[0], w / input_shape[1]] + + resized_image = np.concatenate([cv2.resize(image, (input_shape[1], input_shape[0]), interpolation=cv2.INTER_AREA), + np.ones([input_shape[0], input_shape[1], 1])], axis=-1) + + resized_image = resized_image.transpose((2,0,1)) + batch_image = np.expand_dims(resized_image, axis=0).astype('float32') + batch_image = (batch_image / 127.5) - 1.0 + + batch_image = torch.from_numpy(batch_image).float().cuda() + outputs = model(batch_image) + pts, pts_score, vmap = deccode_output_score_and_ptss(outputs, 200, 3) + start = vmap[:, :, :2] + end = vmap[:, :, 2:] + dist_map = np.sqrt(np.sum((start - end) ** 2, axis=-1)) + + segments_list = [] + for center, score in zip(pts, pts_score): + y, x = center + distance = dist_map[y, x] + if score > score_thr and distance > dist_thr: + disp_x_start, disp_y_start, disp_x_end, disp_y_end = vmap[y, x, :] + x_start = x + disp_x_start + y_start = y + disp_y_start + x_end = x + disp_x_end + y_end = y + disp_y_end + segments_list.append([x_start, y_start, x_end, y_end]) + + lines = 2 * np.array(segments_list) # 256 > 512 + lines[:, 0] = lines[:, 0] * w_ratio + lines[:, 1] = lines[:, 1] * h_ratio + lines[:, 2] = lines[:, 2] * w_ratio + lines[:, 3] = lines[:, 3] * h_ratio + + return lines + + +def pred_squares(image, + model, + input_shape=[512, 512], + params={'score': 0.06, + 'outside_ratio': 0.28, + 'inside_ratio': 0.45, + 'w_overlap': 0.0, + 'w_degree': 1.95, + 'w_length': 0.0, + 'w_area': 1.86, + 'w_center': 0.14}): + ''' + shape = [height, width] + ''' + h, w, _ = image.shape + original_shape = [h, w] + + resized_image = np.concatenate([cv2.resize(image, (input_shape[0], input_shape[1]), interpolation=cv2.INTER_AREA), + np.ones([input_shape[0], input_shape[1], 1])], axis=-1) + resized_image = resized_image.transpose((2, 0, 1)) + batch_image = np.expand_dims(resized_image, axis=0).astype('float32') + batch_image = (batch_image / 127.5) - 1.0 + + batch_image = torch.from_numpy(batch_image).float().cuda() + outputs = model(batch_image) + + pts, pts_score, vmap = deccode_output_score_and_ptss(outputs, 200, 3) + start = vmap[:, :, :2] # (x, y) + end = vmap[:, :, 2:] # (x, y) + dist_map = np.sqrt(np.sum((start - end) ** 2, axis=-1)) + + junc_list = [] + segments_list = [] + for junc, score in zip(pts, pts_score): + y, x = junc + distance = dist_map[y, x] + if score > params['score'] and distance > 20.0: + junc_list.append([x, y]) + disp_x_start, disp_y_start, disp_x_end, disp_y_end = vmap[y, x, :] + d_arrow = 1.0 + x_start = x + d_arrow * disp_x_start + y_start = y + d_arrow * disp_y_start + x_end = x + d_arrow * disp_x_end + y_end = y + d_arrow * disp_y_end + segments_list.append([x_start, y_start, x_end, y_end]) + + segments = np.array(segments_list) + + ####### post processing for squares + # 1. get unique lines + point = np.array([[0, 0]]) + point = point[0] + start = segments[:, :2] + end = segments[:, 2:] + diff = start - end + a = diff[:, 1] + b = -diff[:, 0] + c = a * start[:, 0] + b * start[:, 1] + + d = np.abs(a * point[0] + b * point[1] - c) / np.sqrt(a ** 2 + b ** 2 + 1e-10) + theta = np.arctan2(diff[:, 0], diff[:, 1]) * 180 / np.pi + theta[theta < 0.0] += 180 + hough = np.concatenate([d[:, None], theta[:, None]], axis=-1) + + d_quant = 1 + theta_quant = 2 + hough[:, 0] //= d_quant + hough[:, 1] //= theta_quant + _, indices, counts = np.unique(hough, axis=0, return_index=True, return_counts=True) + + acc_map = np.zeros([512 // d_quant + 1, 360 // theta_quant + 1], dtype='float32') + idx_map = np.zeros([512 // d_quant + 1, 360 // theta_quant + 1], dtype='int32') - 1 + yx_indices = hough[indices, :].astype('int32') + acc_map[yx_indices[:, 0], yx_indices[:, 1]] = counts + idx_map[yx_indices[:, 0], yx_indices[:, 1]] = indices + + acc_map_np = acc_map + # acc_map = acc_map[None, :, :, None] + # + # ### fast suppression using tensorflow op + # acc_map = tf.constant(acc_map, dtype=tf.float32) + # max_acc_map = tf.keras.layers.MaxPool2D(pool_size=(5, 5), strides=1, padding='same')(acc_map) + # acc_map = acc_map * tf.cast(tf.math.equal(acc_map, max_acc_map), tf.float32) + # flatten_acc_map = tf.reshape(acc_map, [1, -1]) + # topk_values, topk_indices = tf.math.top_k(flatten_acc_map, k=len(pts)) + # _, h, w, _ = acc_map.shape + # y = tf.expand_dims(topk_indices // w, axis=-1) + # x = tf.expand_dims(topk_indices % w, axis=-1) + # yx = tf.concat([y, x], axis=-1) + + ### fast suppression using pytorch op + acc_map = torch.from_numpy(acc_map_np).unsqueeze(0).unsqueeze(0) + _,_, h, w = acc_map.shape + max_acc_map = F.max_pool2d(acc_map,kernel_size=5, stride=1, padding=2) + acc_map = acc_map * ( (acc_map == max_acc_map).float() ) + flatten_acc_map = acc_map.reshape([-1, ]) + + scores, indices = torch.topk(flatten_acc_map, len(pts), dim=-1, largest=True) + yy = torch.div(indices, w, rounding_mode='floor').unsqueeze(-1) + xx = torch.fmod(indices, w).unsqueeze(-1) + yx = torch.cat((yy, xx), dim=-1) + + yx = yx.detach().cpu().numpy() + + topk_values = scores.detach().cpu().numpy() + indices = idx_map[yx[:, 0], yx[:, 1]] + basis = 5 // 2 + + merged_segments = [] + for yx_pt, max_indice, value in zip(yx, indices, topk_values): + y, x = yx_pt + if max_indice == -1 or value == 0: + continue + segment_list = [] + for y_offset in range(-basis, basis + 1): + for x_offset in range(-basis, basis + 1): + indice = idx_map[y + y_offset, x + x_offset] + cnt = int(acc_map_np[y + y_offset, x + x_offset]) + if indice != -1: + segment_list.append(segments[indice]) + if cnt > 1: + check_cnt = 1 + current_hough = hough[indice] + for new_indice, new_hough in enumerate(hough): + if (current_hough == new_hough).all() and indice != new_indice: + segment_list.append(segments[new_indice]) + check_cnt += 1 + if check_cnt == cnt: + break + group_segments = np.array(segment_list).reshape([-1, 2]) + sorted_group_segments = np.sort(group_segments, axis=0) + x_min, y_min = sorted_group_segments[0, :] + x_max, y_max = sorted_group_segments[-1, :] + + deg = theta[max_indice] + if deg >= 90: + merged_segments.append([x_min, y_max, x_max, y_min]) + else: + merged_segments.append([x_min, y_min, x_max, y_max]) + + # 2. get intersections + new_segments = np.array(merged_segments) # (x1, y1, x2, y2) + start = new_segments[:, :2] # (x1, y1) + end = new_segments[:, 2:] # (x2, y2) + new_centers = (start + end) / 2.0 + diff = start - end + dist_segments = np.sqrt(np.sum(diff ** 2, axis=-1)) + + # ax + by = c + a = diff[:, 1] + b = -diff[:, 0] + c = a * start[:, 0] + b * start[:, 1] + pre_det = a[:, None] * b[None, :] + det = pre_det - np.transpose(pre_det) + + pre_inter_y = a[:, None] * c[None, :] + inter_y = (pre_inter_y - np.transpose(pre_inter_y)) / (det + 1e-10) + pre_inter_x = c[:, None] * b[None, :] + inter_x = (pre_inter_x - np.transpose(pre_inter_x)) / (det + 1e-10) + inter_pts = np.concatenate([inter_x[:, :, None], inter_y[:, :, None]], axis=-1).astype('int32') + + # 3. get corner information + # 3.1 get distance + ''' + dist_segments: + | dist(0), dist(1), dist(2), ...| + dist_inter_to_segment1: + | dist(inter,0), dist(inter,0), dist(inter,0), ... | + | dist(inter,1), dist(inter,1), dist(inter,1), ... | + ... + dist_inter_to_semgnet2: + | dist(inter,0), dist(inter,1), dist(inter,2), ... | + | dist(inter,0), dist(inter,1), dist(inter,2), ... | + ... + ''' + + dist_inter_to_segment1_start = np.sqrt( + np.sum(((inter_pts - start[:, None, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1] + dist_inter_to_segment1_end = np.sqrt( + np.sum(((inter_pts - end[:, None, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1] + dist_inter_to_segment2_start = np.sqrt( + np.sum(((inter_pts - start[None, :, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1] + dist_inter_to_segment2_end = np.sqrt( + np.sum(((inter_pts - end[None, :, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1] + + # sort ascending + dist_inter_to_segment1 = np.sort( + np.concatenate([dist_inter_to_segment1_start, dist_inter_to_segment1_end], axis=-1), + axis=-1) # [n_batch, n_batch, 2] + dist_inter_to_segment2 = np.sort( + np.concatenate([dist_inter_to_segment2_start, dist_inter_to_segment2_end], axis=-1), + axis=-1) # [n_batch, n_batch, 2] + + # 3.2 get degree + inter_to_start = new_centers[:, None, :] - inter_pts + deg_inter_to_start = np.arctan2(inter_to_start[:, :, 1], inter_to_start[:, :, 0]) * 180 / np.pi + deg_inter_to_start[deg_inter_to_start < 0.0] += 360 + inter_to_end = new_centers[None, :, :] - inter_pts + deg_inter_to_end = np.arctan2(inter_to_end[:, :, 1], inter_to_end[:, :, 0]) * 180 / np.pi + deg_inter_to_end[deg_inter_to_end < 0.0] += 360 + + ''' + B -- G + | | + C -- R + B : blue / G: green / C: cyan / R: red + + 0 -- 1 + | | + 3 -- 2 + ''' + # rename variables + deg1_map, deg2_map = deg_inter_to_start, deg_inter_to_end + # sort deg ascending + deg_sort = np.sort(np.concatenate([deg1_map[:, :, None], deg2_map[:, :, None]], axis=-1), axis=-1) + + deg_diff_map = np.abs(deg1_map - deg2_map) + # we only consider the smallest degree of intersect + deg_diff_map[deg_diff_map > 180] = 360 - deg_diff_map[deg_diff_map > 180] + + # define available degree range + deg_range = [60, 120] + + corner_dict = {corner_info: [] for corner_info in range(4)} + inter_points = [] + for i in range(inter_pts.shape[0]): + for j in range(i + 1, inter_pts.shape[1]): + # i, j > line index, always i < j + x, y = inter_pts[i, j, :] + deg1, deg2 = deg_sort[i, j, :] + deg_diff = deg_diff_map[i, j] + + check_degree = deg_diff > deg_range[0] and deg_diff < deg_range[1] + + outside_ratio = params['outside_ratio'] # over ratio >>> drop it! + inside_ratio = params['inside_ratio'] # over ratio >>> drop it! + check_distance = ((dist_inter_to_segment1[i, j, 1] >= dist_segments[i] and \ + dist_inter_to_segment1[i, j, 0] <= dist_segments[i] * outside_ratio) or \ + (dist_inter_to_segment1[i, j, 1] <= dist_segments[i] and \ + dist_inter_to_segment1[i, j, 0] <= dist_segments[i] * inside_ratio)) and \ + ((dist_inter_to_segment2[i, j, 1] >= dist_segments[j] and \ + dist_inter_to_segment2[i, j, 0] <= dist_segments[j] * outside_ratio) or \ + (dist_inter_to_segment2[i, j, 1] <= dist_segments[j] and \ + dist_inter_to_segment2[i, j, 0] <= dist_segments[j] * inside_ratio)) + + if check_degree and check_distance: + corner_info = None + + if (deg1 >= 0 and deg1 <= 45 and deg2 >= 45 and deg2 <= 120) or \ + (deg2 >= 315 and deg1 >= 45 and deg1 <= 120): + corner_info, color_info = 0, 'blue' + elif (deg1 >= 45 and deg1 <= 125 and deg2 >= 125 and deg2 <= 225): + corner_info, color_info = 1, 'green' + elif (deg1 >= 125 and deg1 <= 225 and deg2 >= 225 and deg2 <= 315): + corner_info, color_info = 2, 'black' + elif (deg1 >= 0 and deg1 <= 45 and deg2 >= 225 and deg2 <= 315) or \ + (deg2 >= 315 and deg1 >= 225 and deg1 <= 315): + corner_info, color_info = 3, 'cyan' + else: + corner_info, color_info = 4, 'red' # we don't use it + continue + + corner_dict[corner_info].append([x, y, i, j]) + inter_points.append([x, y]) + + square_list = [] + connect_list = [] + segments_list = [] + for corner0 in corner_dict[0]: + for corner1 in corner_dict[1]: + connect01 = False + for corner0_line in corner0[2:]: + if corner0_line in corner1[2:]: + connect01 = True + break + if connect01: + for corner2 in corner_dict[2]: + connect12 = False + for corner1_line in corner1[2:]: + if corner1_line in corner2[2:]: + connect12 = True + break + if connect12: + for corner3 in corner_dict[3]: + connect23 = False + for corner2_line in corner2[2:]: + if corner2_line in corner3[2:]: + connect23 = True + break + if connect23: + for corner3_line in corner3[2:]: + if corner3_line in corner0[2:]: + # SQUARE!!! + ''' + 0 -- 1 + | | + 3 -- 2 + square_list: + order: 0 > 1 > 2 > 3 + | x0, y0, x1, y1, x2, y2, x3, y3 | + | x0, y0, x1, y1, x2, y2, x3, y3 | + ... + connect_list: + order: 01 > 12 > 23 > 30 + | line_idx01, line_idx12, line_idx23, line_idx30 | + | line_idx01, line_idx12, line_idx23, line_idx30 | + ... + segments_list: + order: 0 > 1 > 2 > 3 + | line_idx0_i, line_idx0_j, line_idx1_i, line_idx1_j, line_idx2_i, line_idx2_j, line_idx3_i, line_idx3_j | + | line_idx0_i, line_idx0_j, line_idx1_i, line_idx1_j, line_idx2_i, line_idx2_j, line_idx3_i, line_idx3_j | + ... + ''' + square_list.append(corner0[:2] + corner1[:2] + corner2[:2] + corner3[:2]) + connect_list.append([corner0_line, corner1_line, corner2_line, corner3_line]) + segments_list.append(corner0[2:] + corner1[2:] + corner2[2:] + corner3[2:]) + + def check_outside_inside(segments_info, connect_idx): + # return 'outside or inside', min distance, cover_param, peri_param + if connect_idx == segments_info[0]: + check_dist_mat = dist_inter_to_segment1 + else: + check_dist_mat = dist_inter_to_segment2 + + i, j = segments_info + min_dist, max_dist = check_dist_mat[i, j, :] + connect_dist = dist_segments[connect_idx] + if max_dist > connect_dist: + return 'outside', min_dist, 0, 1 + else: + return 'inside', min_dist, -1, -1 + + top_square = None + + try: + map_size = input_shape[0] / 2 + squares = np.array(square_list).reshape([-1, 4, 2]) + score_array = [] + connect_array = np.array(connect_list) + segments_array = np.array(segments_list).reshape([-1, 4, 2]) + + # get degree of corners: + squares_rollup = np.roll(squares, 1, axis=1) + squares_rolldown = np.roll(squares, -1, axis=1) + vec1 = squares_rollup - squares + normalized_vec1 = vec1 / (np.linalg.norm(vec1, axis=-1, keepdims=True) + 1e-10) + vec2 = squares_rolldown - squares + normalized_vec2 = vec2 / (np.linalg.norm(vec2, axis=-1, keepdims=True) + 1e-10) + inner_products = np.sum(normalized_vec1 * normalized_vec2, axis=-1) # [n_squares, 4] + squares_degree = np.arccos(inner_products) * 180 / np.pi # [n_squares, 4] + + # get square score + overlap_scores = [] + degree_scores = [] + length_scores = [] + + for connects, segments, square, degree in zip(connect_array, segments_array, squares, squares_degree): + ''' + 0 -- 1 + | | + 3 -- 2 + + # segments: [4, 2] + # connects: [4] + ''' + + ###################################### OVERLAP SCORES + cover = 0 + perimeter = 0 + # check 0 > 1 > 2 > 3 + square_length = [] + + for start_idx in range(4): + end_idx = (start_idx + 1) % 4 + + connect_idx = connects[start_idx] # segment idx of segment01 + start_segments = segments[start_idx] + end_segments = segments[end_idx] + + start_point = square[start_idx] + end_point = square[end_idx] + + # check whether outside or inside + start_position, start_min, start_cover_param, start_peri_param = check_outside_inside(start_segments, + connect_idx) + end_position, end_min, end_cover_param, end_peri_param = check_outside_inside(end_segments, connect_idx) + + cover += dist_segments[connect_idx] + start_cover_param * start_min + end_cover_param * end_min + perimeter += dist_segments[connect_idx] + start_peri_param * start_min + end_peri_param * end_min + + square_length.append( + dist_segments[connect_idx] + start_peri_param * start_min + end_peri_param * end_min) + + overlap_scores.append(cover / perimeter) + ###################################### + ###################################### DEGREE SCORES + ''' + deg0 vs deg2 + deg1 vs deg3 + ''' + deg0, deg1, deg2, deg3 = degree + deg_ratio1 = deg0 / deg2 + if deg_ratio1 > 1.0: + deg_ratio1 = 1 / deg_ratio1 + deg_ratio2 = deg1 / deg3 + if deg_ratio2 > 1.0: + deg_ratio2 = 1 / deg_ratio2 + degree_scores.append((deg_ratio1 + deg_ratio2) / 2) + ###################################### + ###################################### LENGTH SCORES + ''' + len0 vs len2 + len1 vs len3 + ''' + len0, len1, len2, len3 = square_length + len_ratio1 = len0 / len2 if len2 > len0 else len2 / len0 + len_ratio2 = len1 / len3 if len3 > len1 else len3 / len1 + length_scores.append((len_ratio1 + len_ratio2) / 2) + + ###################################### + + overlap_scores = np.array(overlap_scores) + overlap_scores /= np.max(overlap_scores) + + degree_scores = np.array(degree_scores) + # degree_scores /= np.max(degree_scores) + + length_scores = np.array(length_scores) + + ###################################### AREA SCORES + area_scores = np.reshape(squares, [-1, 4, 2]) + area_x = area_scores[:, :, 0] + area_y = area_scores[:, :, 1] + correction = area_x[:, -1] * area_y[:, 0] - area_y[:, -1] * area_x[:, 0] + area_scores = np.sum(area_x[:, :-1] * area_y[:, 1:], axis=-1) - np.sum(area_y[:, :-1] * area_x[:, 1:], axis=-1) + area_scores = 0.5 * np.abs(area_scores + correction) + area_scores /= (map_size * map_size) # np.max(area_scores) + ###################################### + + ###################################### CENTER SCORES + centers = np.array([[256 // 2, 256 // 2]], dtype='float32') # [1, 2] + # squares: [n, 4, 2] + square_centers = np.mean(squares, axis=1) # [n, 2] + center2center = np.sqrt(np.sum((centers - square_centers) ** 2)) + center_scores = center2center / (map_size / np.sqrt(2.0)) + + ''' + score_w = [overlap, degree, area, center, length] + ''' + score_w = [0.0, 1.0, 10.0, 0.5, 1.0] + score_array = params['w_overlap'] * overlap_scores \ + + params['w_degree'] * degree_scores \ + + params['w_area'] * area_scores \ + - params['w_center'] * center_scores \ + + params['w_length'] * length_scores + + best_square = [] + + sorted_idx = np.argsort(score_array)[::-1] + score_array = score_array[sorted_idx] + squares = squares[sorted_idx] + + except Exception as e: + pass + + '''return list + merged_lines, squares, scores + ''' + + try: + new_segments[:, 0] = new_segments[:, 0] * 2 / input_shape[1] * original_shape[1] + new_segments[:, 1] = new_segments[:, 1] * 2 / input_shape[0] * original_shape[0] + new_segments[:, 2] = new_segments[:, 2] * 2 / input_shape[1] * original_shape[1] + new_segments[:, 3] = new_segments[:, 3] * 2 / input_shape[0] * original_shape[0] + except: + new_segments = [] + + try: + squares[:, :, 0] = squares[:, :, 0] * 2 / input_shape[1] * original_shape[1] + squares[:, :, 1] = squares[:, :, 1] * 2 / input_shape[0] * original_shape[0] + except: + squares = [] + score_array = [] + + try: + inter_points = np.array(inter_points) + inter_points[:, 0] = inter_points[:, 0] * 2 / input_shape[1] * original_shape[1] + inter_points[:, 1] = inter_points[:, 1] * 2 / input_shape[0] * original_shape[0] + except: + inter_points = [] + + return new_segments, squares, score_array, inter_points diff --git a/comparison_models/ControlNet/annotator/openpose/LICENSE b/comparison_models/ControlNet/annotator/openpose/LICENSE new file mode 100644 index 0000000..6f60b76 --- /dev/null +++ b/comparison_models/ControlNet/annotator/openpose/LICENSE @@ -0,0 +1,108 @@ +OPENPOSE: MULTIPERSON KEYPOINT DETECTION +SOFTWARE LICENSE AGREEMENT +ACADEMIC OR NON-PROFIT ORGANIZATION NONCOMMERCIAL RESEARCH USE ONLY + +BY USING OR DOWNLOADING THE SOFTWARE, YOU ARE AGREEING TO THE TERMS OF THIS LICENSE AGREEMENT. IF YOU DO NOT AGREE WITH THESE TERMS, YOU MAY NOT USE OR DOWNLOAD THE SOFTWARE. + +This is a license agreement ("Agreement") between your academic institution or non-profit organization or self (called "Licensee" or "You" in this Agreement) and Carnegie Mellon University (called "Licensor" in this Agreement). All rights not specifically granted to you in this Agreement are reserved for Licensor. + +RESERVATION OF OWNERSHIP AND GRANT OF LICENSE: +Licensor retains exclusive ownership of any copy of the Software (as defined below) licensed under this Agreement and hereby grants to Licensee a personal, non-exclusive, +non-transferable license to use the Software for noncommercial research purposes, without the right to sublicense, pursuant to the terms and conditions of this Agreement. As used in this Agreement, the term "Software" means (i) the actual copy of all or any portion of code for program routines made accessible to Licensee by Licensor pursuant to this Agreement, inclusive of backups, updates, and/or merged copies permitted hereunder or subsequently supplied by Licensor, including all or any file structures, programming instructions, user interfaces and screen formats and sequences as well as any and all documentation and instructions related to it, and (ii) all or any derivatives and/or modifications created or made by You to any of the items specified in (i). + +CONFIDENTIALITY: Licensee acknowledges that the Software is proprietary to Licensor, and as such, Licensee agrees to receive all such materials in confidence and use the Software only in accordance with the terms of this Agreement. Licensee agrees to use reasonable effort to protect the Software from unauthorized use, reproduction, distribution, or publication. + +COPYRIGHT: The Software is owned by Licensor and is protected by United +States copyright laws and applicable international treaties and/or conventions. + +PERMITTED USES: The Software may be used for your own noncommercial internal research purposes. You understand and agree that Licensor is not obligated to implement any suggestions and/or feedback you might provide regarding the Software, but to the extent Licensor does so, you are not entitled to any compensation related thereto. + +DERIVATIVES: You may create derivatives of or make modifications to the Software, however, You agree that all and any such derivatives and modifications will be owned by Licensor and become a part of the Software licensed to You under this Agreement. You may only use such derivatives and modifications for your own noncommercial internal research purposes, and you may not otherwise use, distribute or copy such derivatives and modifications in violation of this Agreement. + +BACKUPS: If Licensee is an organization, it may make that number of copies of the Software necessary for internal noncommercial use at a single site within its organization provided that all information appearing in or on the original labels, including the copyright and trademark notices are copied onto the labels of the copies. + +USES NOT PERMITTED: You may not distribute, copy or use the Software except as explicitly permitted herein. Licensee has not been granted any trademark license as part of this Agreement and may not use the name or mark “OpenPose", "Carnegie Mellon" or any renditions thereof without the prior written permission of Licensor. + +You may not sell, rent, lease, sublicense, lend, time-share or transfer, in whole or in part, or provide third parties access to prior or present versions (or any parts thereof) of the Software. + +ASSIGNMENT: You may not assign this Agreement or your rights hereunder without the prior written consent of Licensor. Any attempted assignment without such consent shall be null and void. + +TERM: The term of the license granted by this Agreement is from Licensee's acceptance of this Agreement by downloading the Software or by using the Software until terminated as provided below. + +The Agreement automatically terminates without notice if you fail to comply with any provision of this Agreement. Licensee may terminate this Agreement by ceasing using the Software. Upon any termination of this Agreement, Licensee will delete any and all copies of the Software. You agree that all provisions which operate to protect the proprietary rights of Licensor shall remain in force should breach occur and that the obligation of confidentiality described in this Agreement is binding in perpetuity and, as such, survives the term of the Agreement. + +FEE: Provided Licensee abides completely by the terms and conditions of this Agreement, there is no fee due to Licensor for Licensee's use of the Software in accordance with this Agreement. + +DISCLAIMER OF WARRANTIES: THE SOFTWARE IS PROVIDED "AS-IS" WITHOUT WARRANTY OF ANY KIND INCLUDING ANY WARRANTIES OF PERFORMANCE OR MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE OR PURPOSE OR OF NON-INFRINGEMENT. LICENSEE BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE SOFTWARE AND RELATED MATERIALS. + +SUPPORT AND MAINTENANCE: No Software support or training by the Licensor is provided as part of this Agreement. + +EXCLUSIVE REMEDY AND LIMITATION OF LIABILITY: To the maximum extent permitted under applicable law, Licensor shall not be liable for direct, indirect, special, incidental, or consequential damages or lost profits related to Licensee's use of and/or inability to use the Software, even if Licensor is advised of the possibility of such damage. + +EXPORT REGULATION: Licensee agrees to comply with any and all applicable +U.S. export control laws, regulations, and/or other laws related to embargoes and sanction programs administered by the Office of Foreign Assets Control. + +SEVERABILITY: If any provision(s) of this Agreement shall be held to be invalid, illegal, or unenforceable by a court or other tribunal of competent jurisdiction, the validity, legality and enforceability of the remaining provisions shall not in any way be affected or impaired thereby. + +NO IMPLIED WAIVERS: No failure or delay by Licensor in enforcing any right or remedy under this Agreement shall be construed as a waiver of any future or other exercise of such right or remedy by Licensor. + +GOVERNING LAW: This Agreement shall be construed and enforced in accordance with the laws of the Commonwealth of Pennsylvania without reference to conflict of laws principles. You consent to the personal jurisdiction of the courts of this County and waive their rights to venue outside of Allegheny County, Pennsylvania. + +ENTIRE AGREEMENT AND AMENDMENTS: This Agreement constitutes the sole and entire agreement between Licensee and Licensor as to the matter set forth herein and supersedes any previous agreements, understandings, and arrangements between the parties relating hereto. + + + +************************************************************************ + +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION + +This project incorporates material from the project(s) listed below (collectively, "Third Party Code"). This Third Party Code is licensed to you under their original license terms set forth below. We reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. + +1. Caffe, version 1.0.0, (https://github.com/BVLC/caffe/) + +COPYRIGHT + +All contributions by the University of California: +Copyright (c) 2014-2017 The Regents of the University of California (Regents) +All rights reserved. + +All other contributions: +Copyright (c) 2014-2017, the respective contributors +All rights reserved. + +Caffe uses a shared copyright model: each contributor holds copyright over +their contributions to Caffe. The project versioning records all such +contribution and copyright details. If a contributor wants to further mark +their specific copyright on a particular contribution, they should indicate +their copyright solely in the commit message of the change when it is +committed. + +LICENSE + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +CONTRIBUTION AGREEMENT + +By contributing to the BVLC/caffe repository through pull-request, comment, +or otherwise, the contributor releases their content to the +license and copyright terms herein. + +************END OF THIRD-PARTY SOFTWARE NOTICES AND INFORMATION********** \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/openpose/__init__.py b/comparison_models/ControlNet/annotator/openpose/__init__.py new file mode 100644 index 0000000..92e530f --- /dev/null +++ b/comparison_models/ControlNet/annotator/openpose/__init__.py @@ -0,0 +1,49 @@ +# Openpose +# Original from CMU https://github.com/CMU-Perceptual-Computing-Lab/openpose +# 2nd Edited by https://github.com/Hzzone/pytorch-openpose +# 3rd Edited by ControlNet + +import os +os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" + +import torch +import numpy as np +from . import util +from .body import Body +from .hand import Hand +from annotator.util import annotator_ckpts_path + + +body_model_path = "https://huggingface.co/lllyasviel/ControlNet/resolve/main/annotator/ckpts/body_pose_model.pth" +hand_model_path = "https://huggingface.co/lllyasviel/ControlNet/resolve/main/annotator/ckpts/hand_pose_model.pth" + + +class OpenposeDetector: + def __init__(self): + body_modelpath = os.path.join(annotator_ckpts_path, "body_pose_model.pth") + hand_modelpath = os.path.join(annotator_ckpts_path, "hand_pose_model.pth") + + if not os.path.exists(hand_modelpath): + from basicsr.utils.download_util import load_file_from_url + load_file_from_url(body_model_path, model_dir=annotator_ckpts_path) + load_file_from_url(hand_model_path, model_dir=annotator_ckpts_path) + + self.body_estimation = Body(body_modelpath) + self.hand_estimation = Hand(hand_modelpath) + + def __call__(self, oriImg, hand=False): + oriImg = oriImg[:, :, ::-1].copy() + with torch.no_grad(): + candidate, subset = self.body_estimation(oriImg) + canvas = np.zeros_like(oriImg) + canvas = util.draw_bodypose(canvas, candidate, subset) + if hand: + hands_list = util.handDetect(candidate, subset, oriImg) + all_hand_peaks = [] + for x, y, w, is_left in hands_list: + peaks = self.hand_estimation(oriImg[y:y+w, x:x+w, :]) + peaks[:, 0] = np.where(peaks[:, 0] == 0, peaks[:, 0], peaks[:, 0] + x) + peaks[:, 1] = np.where(peaks[:, 1] == 0, peaks[:, 1], peaks[:, 1] + y) + all_hand_peaks.append(peaks) + canvas = util.draw_handpose(canvas, all_hand_peaks) + return canvas, dict(candidate=candidate.tolist(), subset=subset.tolist()) diff --git a/comparison_models/ControlNet/annotator/openpose/body.py b/comparison_models/ControlNet/annotator/openpose/body.py new file mode 100644 index 0000000..7c3cf7a --- /dev/null +++ b/comparison_models/ControlNet/annotator/openpose/body.py @@ -0,0 +1,219 @@ +import cv2 +import numpy as np +import math +import time +from scipy.ndimage.filters import gaussian_filter +import matplotlib.pyplot as plt +import matplotlib +import torch +from torchvision import transforms + +from . import util +from .model import bodypose_model + +class Body(object): + def __init__(self, model_path): + self.model = bodypose_model() + if torch.cuda.is_available(): + self.model = self.model.cuda() + print('cuda') + model_dict = util.transfer(self.model, torch.load(model_path)) + self.model.load_state_dict(model_dict) + self.model.eval() + + def __call__(self, oriImg): + # scale_search = [0.5, 1.0, 1.5, 2.0] + scale_search = [0.5] + boxsize = 368 + stride = 8 + padValue = 128 + thre1 = 0.1 + thre2 = 0.05 + multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search] + heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 19)) + paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38)) + + for m in range(len(multiplier)): + scale = multiplier[m] + imageToTest = cv2.resize(oriImg, (0, 0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) + imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue) + im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5 + im = np.ascontiguousarray(im) + + data = torch.from_numpy(im).float() + if torch.cuda.is_available(): + data = data.cuda() + # data = data.permute([2, 0, 1]).unsqueeze(0).float() + with torch.no_grad(): + Mconv7_stage6_L1, Mconv7_stage6_L2 = self.model(data) + Mconv7_stage6_L1 = Mconv7_stage6_L1.cpu().numpy() + Mconv7_stage6_L2 = Mconv7_stage6_L2.cpu().numpy() + + # extract outputs, resize, and remove padding + # heatmap = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[1]].data), (1, 2, 0)) # output 1 is heatmaps + heatmap = np.transpose(np.squeeze(Mconv7_stage6_L2), (1, 2, 0)) # output 1 is heatmaps + heatmap = cv2.resize(heatmap, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC) + heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + heatmap = cv2.resize(heatmap, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC) + + # paf = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[0]].data), (1, 2, 0)) # output 0 is PAFs + paf = np.transpose(np.squeeze(Mconv7_stage6_L1), (1, 2, 0)) # output 0 is PAFs + paf = cv2.resize(paf, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC) + paf = paf[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + paf = cv2.resize(paf, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC) + + heatmap_avg += heatmap_avg + heatmap / len(multiplier) + paf_avg += + paf / len(multiplier) + + all_peaks = [] + peak_counter = 0 + + for part in range(18): + map_ori = heatmap_avg[:, :, part] + one_heatmap = gaussian_filter(map_ori, sigma=3) + + map_left = np.zeros(one_heatmap.shape) + map_left[1:, :] = one_heatmap[:-1, :] + map_right = np.zeros(one_heatmap.shape) + map_right[:-1, :] = one_heatmap[1:, :] + map_up = np.zeros(one_heatmap.shape) + map_up[:, 1:] = one_heatmap[:, :-1] + map_down = np.zeros(one_heatmap.shape) + map_down[:, :-1] = one_heatmap[:, 1:] + + peaks_binary = np.logical_and.reduce( + (one_heatmap >= map_left, one_heatmap >= map_right, one_heatmap >= map_up, one_heatmap >= map_down, one_heatmap > thre1)) + peaks = list(zip(np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0])) # note reverse + peaks_with_score = [x + (map_ori[x[1], x[0]],) for x in peaks] + peak_id = range(peak_counter, peak_counter + len(peaks)) + peaks_with_score_and_id = [peaks_with_score[i] + (peak_id[i],) for i in range(len(peak_id))] + + all_peaks.append(peaks_with_score_and_id) + peak_counter += len(peaks) + + # find connection in the specified sequence, center 29 is in the position 15 + limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \ + [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \ + [1, 16], [16, 18], [3, 17], [6, 18]] + # the middle joints heatmap correpondence + mapIdx = [[31, 32], [39, 40], [33, 34], [35, 36], [41, 42], [43, 44], [19, 20], [21, 22], \ + [23, 24], [25, 26], [27, 28], [29, 30], [47, 48], [49, 50], [53, 54], [51, 52], \ + [55, 56], [37, 38], [45, 46]] + + connection_all = [] + special_k = [] + mid_num = 10 + + for k in range(len(mapIdx)): + score_mid = paf_avg[:, :, [x - 19 for x in mapIdx[k]]] + candA = all_peaks[limbSeq[k][0] - 1] + candB = all_peaks[limbSeq[k][1] - 1] + nA = len(candA) + nB = len(candB) + indexA, indexB = limbSeq[k] + if (nA != 0 and nB != 0): + connection_candidate = [] + for i in range(nA): + for j in range(nB): + vec = np.subtract(candB[j][:2], candA[i][:2]) + norm = math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]) + norm = max(0.001, norm) + vec = np.divide(vec, norm) + + startend = list(zip(np.linspace(candA[i][0], candB[j][0], num=mid_num), \ + np.linspace(candA[i][1], candB[j][1], num=mid_num))) + + vec_x = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 0] \ + for I in range(len(startend))]) + vec_y = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 1] \ + for I in range(len(startend))]) + + score_midpts = np.multiply(vec_x, vec[0]) + np.multiply(vec_y, vec[1]) + score_with_dist_prior = sum(score_midpts) / len(score_midpts) + min( + 0.5 * oriImg.shape[0] / norm - 1, 0) + criterion1 = len(np.nonzero(score_midpts > thre2)[0]) > 0.8 * len(score_midpts) + criterion2 = score_with_dist_prior > 0 + if criterion1 and criterion2: + connection_candidate.append( + [i, j, score_with_dist_prior, score_with_dist_prior + candA[i][2] + candB[j][2]]) + + connection_candidate = sorted(connection_candidate, key=lambda x: x[2], reverse=True) + connection = np.zeros((0, 5)) + for c in range(len(connection_candidate)): + i, j, s = connection_candidate[c][0:3] + if (i not in connection[:, 3] and j not in connection[:, 4]): + connection = np.vstack([connection, [candA[i][3], candB[j][3], s, i, j]]) + if (len(connection) >= min(nA, nB)): + break + + connection_all.append(connection) + else: + special_k.append(k) + connection_all.append([]) + + # last number in each row is the total parts number of that person + # the second last number in each row is the score of the overall configuration + subset = -1 * np.ones((0, 20)) + candidate = np.array([item for sublist in all_peaks for item in sublist]) + + for k in range(len(mapIdx)): + if k not in special_k: + partAs = connection_all[k][:, 0] + partBs = connection_all[k][:, 1] + indexA, indexB = np.array(limbSeq[k]) - 1 + + for i in range(len(connection_all[k])): # = 1:size(temp,1) + found = 0 + subset_idx = [-1, -1] + for j in range(len(subset)): # 1:size(subset,1): + if subset[j][indexA] == partAs[i] or subset[j][indexB] == partBs[i]: + subset_idx[found] = j + found += 1 + + if found == 1: + j = subset_idx[0] + if subset[j][indexB] != partBs[i]: + subset[j][indexB] = partBs[i] + subset[j][-1] += 1 + subset[j][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2] + elif found == 2: # if found 2 and disjoint, merge them + j1, j2 = subset_idx + membership = ((subset[j1] >= 0).astype(int) + (subset[j2] >= 0).astype(int))[:-2] + if len(np.nonzero(membership == 2)[0]) == 0: # merge + subset[j1][:-2] += (subset[j2][:-2] + 1) + subset[j1][-2:] += subset[j2][-2:] + subset[j1][-2] += connection_all[k][i][2] + subset = np.delete(subset, j2, 0) + else: # as like found == 1 + subset[j1][indexB] = partBs[i] + subset[j1][-1] += 1 + subset[j1][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2] + + # if find no partA in the subset, create a new subset + elif not found and k < 17: + row = -1 * np.ones(20) + row[indexA] = partAs[i] + row[indexB] = partBs[i] + row[-1] = 2 + row[-2] = sum(candidate[connection_all[k][i, :2].astype(int), 2]) + connection_all[k][i][2] + subset = np.vstack([subset, row]) + # delete some rows of subset which has few parts occur + deleteIdx = [] + for i in range(len(subset)): + if subset[i][-1] < 4 or subset[i][-2] / subset[i][-1] < 0.4: + deleteIdx.append(i) + subset = np.delete(subset, deleteIdx, axis=0) + + # subset: n*20 array, 0-17 is the index in candidate, 18 is the total score, 19 is the total parts + # candidate: x, y, score, id + return candidate, subset + +if __name__ == "__main__": + body_estimation = Body('../model/body_pose_model.pth') + + test_image = '../images/ski.jpg' + oriImg = cv2.imread(test_image) # B,G,R order + candidate, subset = body_estimation(oriImg) + canvas = util.draw_bodypose(oriImg, candidate, subset) + plt.imshow(canvas[:, :, [2, 1, 0]]) + plt.show() diff --git a/comparison_models/ControlNet/annotator/openpose/hand.py b/comparison_models/ControlNet/annotator/openpose/hand.py new file mode 100644 index 0000000..3d0bf17 --- /dev/null +++ b/comparison_models/ControlNet/annotator/openpose/hand.py @@ -0,0 +1,86 @@ +import cv2 +import json +import numpy as np +import math +import time +from scipy.ndimage.filters import gaussian_filter +import matplotlib.pyplot as plt +import matplotlib +import torch +from skimage.measure import label + +from .model import handpose_model +from . import util + +class Hand(object): + def __init__(self, model_path): + self.model = handpose_model() + if torch.cuda.is_available(): + self.model = self.model.cuda() + print('cuda') + model_dict = util.transfer(self.model, torch.load(model_path)) + self.model.load_state_dict(model_dict) + self.model.eval() + + def __call__(self, oriImg): + scale_search = [0.5, 1.0, 1.5, 2.0] + # scale_search = [0.5] + boxsize = 368 + stride = 8 + padValue = 128 + thre = 0.05 + multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search] + heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 22)) + # paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38)) + + for m in range(len(multiplier)): + scale = multiplier[m] + imageToTest = cv2.resize(oriImg, (0, 0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) + imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue) + im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5 + im = np.ascontiguousarray(im) + + data = torch.from_numpy(im).float() + if torch.cuda.is_available(): + data = data.cuda() + # data = data.permute([2, 0, 1]).unsqueeze(0).float() + with torch.no_grad(): + output = self.model(data).cpu().numpy() + # output = self.model(data).numpy()q + + # extract outputs, resize, and remove padding + heatmap = np.transpose(np.squeeze(output), (1, 2, 0)) # output 1 is heatmaps + heatmap = cv2.resize(heatmap, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC) + heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + heatmap = cv2.resize(heatmap, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC) + + heatmap_avg += heatmap / len(multiplier) + + all_peaks = [] + for part in range(21): + map_ori = heatmap_avg[:, :, part] + one_heatmap = gaussian_filter(map_ori, sigma=3) + binary = np.ascontiguousarray(one_heatmap > thre, dtype=np.uint8) + # 全部小于阈值 + if np.sum(binary) == 0: + all_peaks.append([0, 0]) + continue + label_img, label_numbers = label(binary, return_num=True, connectivity=binary.ndim) + max_index = np.argmax([np.sum(map_ori[label_img == i]) for i in range(1, label_numbers + 1)]) + 1 + label_img[label_img != max_index] = 0 + map_ori[label_img == 0] = 0 + + y, x = util.npmax(map_ori) + all_peaks.append([x, y]) + return np.array(all_peaks) + +if __name__ == "__main__": + hand_estimation = Hand('../model/hand_pose_model.pth') + + # test_image = '../images/hand.jpg' + test_image = '../images/hand.jpg' + oriImg = cv2.imread(test_image) # B,G,R order + peaks = hand_estimation(oriImg) + canvas = util.draw_handpose(oriImg, peaks, True) + cv2.imshow('', canvas) + cv2.waitKey(0) \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/openpose/model.py b/comparison_models/ControlNet/annotator/openpose/model.py new file mode 100644 index 0000000..5dfc80d --- /dev/null +++ b/comparison_models/ControlNet/annotator/openpose/model.py @@ -0,0 +1,219 @@ +import torch +from collections import OrderedDict + +import torch +import torch.nn as nn + +def make_layers(block, no_relu_layers): + layers = [] + for layer_name, v in block.items(): + if 'pool' in layer_name: + layer = nn.MaxPool2d(kernel_size=v[0], stride=v[1], + padding=v[2]) + layers.append((layer_name, layer)) + else: + conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], + kernel_size=v[2], stride=v[3], + padding=v[4]) + layers.append((layer_name, conv2d)) + if layer_name not in no_relu_layers: + layers.append(('relu_'+layer_name, nn.ReLU(inplace=True))) + + return nn.Sequential(OrderedDict(layers)) + +class bodypose_model(nn.Module): + def __init__(self): + super(bodypose_model, self).__init__() + + # these layers have no relu layer + no_relu_layers = ['conv5_5_CPM_L1', 'conv5_5_CPM_L2', 'Mconv7_stage2_L1',\ + 'Mconv7_stage2_L2', 'Mconv7_stage3_L1', 'Mconv7_stage3_L2',\ + 'Mconv7_stage4_L1', 'Mconv7_stage4_L2', 'Mconv7_stage5_L1',\ + 'Mconv7_stage5_L2', 'Mconv7_stage6_L1', 'Mconv7_stage6_L1'] + blocks = {} + block0 = OrderedDict([ + ('conv1_1', [3, 64, 3, 1, 1]), + ('conv1_2', [64, 64, 3, 1, 1]), + ('pool1_stage1', [2, 2, 0]), + ('conv2_1', [64, 128, 3, 1, 1]), + ('conv2_2', [128, 128, 3, 1, 1]), + ('pool2_stage1', [2, 2, 0]), + ('conv3_1', [128, 256, 3, 1, 1]), + ('conv3_2', [256, 256, 3, 1, 1]), + ('conv3_3', [256, 256, 3, 1, 1]), + ('conv3_4', [256, 256, 3, 1, 1]), + ('pool3_stage1', [2, 2, 0]), + ('conv4_1', [256, 512, 3, 1, 1]), + ('conv4_2', [512, 512, 3, 1, 1]), + ('conv4_3_CPM', [512, 256, 3, 1, 1]), + ('conv4_4_CPM', [256, 128, 3, 1, 1]) + ]) + + + # Stage 1 + block1_1 = OrderedDict([ + ('conv5_1_CPM_L1', [128, 128, 3, 1, 1]), + ('conv5_2_CPM_L1', [128, 128, 3, 1, 1]), + ('conv5_3_CPM_L1', [128, 128, 3, 1, 1]), + ('conv5_4_CPM_L1', [128, 512, 1, 1, 0]), + ('conv5_5_CPM_L1', [512, 38, 1, 1, 0]) + ]) + + block1_2 = OrderedDict([ + ('conv5_1_CPM_L2', [128, 128, 3, 1, 1]), + ('conv5_2_CPM_L2', [128, 128, 3, 1, 1]), + ('conv5_3_CPM_L2', [128, 128, 3, 1, 1]), + ('conv5_4_CPM_L2', [128, 512, 1, 1, 0]), + ('conv5_5_CPM_L2', [512, 19, 1, 1, 0]) + ]) + blocks['block1_1'] = block1_1 + blocks['block1_2'] = block1_2 + + self.model0 = make_layers(block0, no_relu_layers) + + # Stages 2 - 6 + for i in range(2, 7): + blocks['block%d_1' % i] = OrderedDict([ + ('Mconv1_stage%d_L1' % i, [185, 128, 7, 1, 3]), + ('Mconv2_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d_L1' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d_L1' % i, [128, 38, 1, 1, 0]) + ]) + + blocks['block%d_2' % i] = OrderedDict([ + ('Mconv1_stage%d_L2' % i, [185, 128, 7, 1, 3]), + ('Mconv2_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d_L2' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d_L2' % i, [128, 19, 1, 1, 0]) + ]) + + for k in blocks.keys(): + blocks[k] = make_layers(blocks[k], no_relu_layers) + + self.model1_1 = blocks['block1_1'] + self.model2_1 = blocks['block2_1'] + self.model3_1 = blocks['block3_1'] + self.model4_1 = blocks['block4_1'] + self.model5_1 = blocks['block5_1'] + self.model6_1 = blocks['block6_1'] + + self.model1_2 = blocks['block1_2'] + self.model2_2 = blocks['block2_2'] + self.model3_2 = blocks['block3_2'] + self.model4_2 = blocks['block4_2'] + self.model5_2 = blocks['block5_2'] + self.model6_2 = blocks['block6_2'] + + + def forward(self, x): + + out1 = self.model0(x) + + out1_1 = self.model1_1(out1) + out1_2 = self.model1_2(out1) + out2 = torch.cat([out1_1, out1_2, out1], 1) + + out2_1 = self.model2_1(out2) + out2_2 = self.model2_2(out2) + out3 = torch.cat([out2_1, out2_2, out1], 1) + + out3_1 = self.model3_1(out3) + out3_2 = self.model3_2(out3) + out4 = torch.cat([out3_1, out3_2, out1], 1) + + out4_1 = self.model4_1(out4) + out4_2 = self.model4_2(out4) + out5 = torch.cat([out4_1, out4_2, out1], 1) + + out5_1 = self.model5_1(out5) + out5_2 = self.model5_2(out5) + out6 = torch.cat([out5_1, out5_2, out1], 1) + + out6_1 = self.model6_1(out6) + out6_2 = self.model6_2(out6) + + return out6_1, out6_2 + +class handpose_model(nn.Module): + def __init__(self): + super(handpose_model, self).__init__() + + # these layers have no relu layer + no_relu_layers = ['conv6_2_CPM', 'Mconv7_stage2', 'Mconv7_stage3',\ + 'Mconv7_stage4', 'Mconv7_stage5', 'Mconv7_stage6'] + # stage 1 + block1_0 = OrderedDict([ + ('conv1_1', [3, 64, 3, 1, 1]), + ('conv1_2', [64, 64, 3, 1, 1]), + ('pool1_stage1', [2, 2, 0]), + ('conv2_1', [64, 128, 3, 1, 1]), + ('conv2_2', [128, 128, 3, 1, 1]), + ('pool2_stage1', [2, 2, 0]), + ('conv3_1', [128, 256, 3, 1, 1]), + ('conv3_2', [256, 256, 3, 1, 1]), + ('conv3_3', [256, 256, 3, 1, 1]), + ('conv3_4', [256, 256, 3, 1, 1]), + ('pool3_stage1', [2, 2, 0]), + ('conv4_1', [256, 512, 3, 1, 1]), + ('conv4_2', [512, 512, 3, 1, 1]), + ('conv4_3', [512, 512, 3, 1, 1]), + ('conv4_4', [512, 512, 3, 1, 1]), + ('conv5_1', [512, 512, 3, 1, 1]), + ('conv5_2', [512, 512, 3, 1, 1]), + ('conv5_3_CPM', [512, 128, 3, 1, 1]) + ]) + + block1_1 = OrderedDict([ + ('conv6_1_CPM', [128, 512, 1, 1, 0]), + ('conv6_2_CPM', [512, 22, 1, 1, 0]) + ]) + + blocks = {} + blocks['block1_0'] = block1_0 + blocks['block1_1'] = block1_1 + + # stage 2-6 + for i in range(2, 7): + blocks['block%d' % i] = OrderedDict([ + ('Mconv1_stage%d' % i, [150, 128, 7, 1, 3]), + ('Mconv2_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d' % i, [128, 22, 1, 1, 0]) + ]) + + for k in blocks.keys(): + blocks[k] = make_layers(blocks[k], no_relu_layers) + + self.model1_0 = blocks['block1_0'] + self.model1_1 = blocks['block1_1'] + self.model2 = blocks['block2'] + self.model3 = blocks['block3'] + self.model4 = blocks['block4'] + self.model5 = blocks['block5'] + self.model6 = blocks['block6'] + + def forward(self, x): + out1_0 = self.model1_0(x) + out1_1 = self.model1_1(out1_0) + concat_stage2 = torch.cat([out1_1, out1_0], 1) + out_stage2 = self.model2(concat_stage2) + concat_stage3 = torch.cat([out_stage2, out1_0], 1) + out_stage3 = self.model3(concat_stage3) + concat_stage4 = torch.cat([out_stage3, out1_0], 1) + out_stage4 = self.model4(concat_stage4) + concat_stage5 = torch.cat([out_stage4, out1_0], 1) + out_stage5 = self.model5(concat_stage5) + concat_stage6 = torch.cat([out_stage5, out1_0], 1) + out_stage6 = self.model6(concat_stage6) + return out_stage6 + + diff --git a/comparison_models/ControlNet/annotator/openpose/util.py b/comparison_models/ControlNet/annotator/openpose/util.py new file mode 100644 index 0000000..6f91ae0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/openpose/util.py @@ -0,0 +1,164 @@ +import math +import numpy as np +import matplotlib +import cv2 + + +def padRightDownCorner(img, stride, padValue): + h = img.shape[0] + w = img.shape[1] + + pad = 4 * [None] + pad[0] = 0 # up + pad[1] = 0 # left + pad[2] = 0 if (h % stride == 0) else stride - (h % stride) # down + pad[3] = 0 if (w % stride == 0) else stride - (w % stride) # right + + img_padded = img + pad_up = np.tile(img_padded[0:1, :, :]*0 + padValue, (pad[0], 1, 1)) + img_padded = np.concatenate((pad_up, img_padded), axis=0) + pad_left = np.tile(img_padded[:, 0:1, :]*0 + padValue, (1, pad[1], 1)) + img_padded = np.concatenate((pad_left, img_padded), axis=1) + pad_down = np.tile(img_padded[-2:-1, :, :]*0 + padValue, (pad[2], 1, 1)) + img_padded = np.concatenate((img_padded, pad_down), axis=0) + pad_right = np.tile(img_padded[:, -2:-1, :]*0 + padValue, (1, pad[3], 1)) + img_padded = np.concatenate((img_padded, pad_right), axis=1) + + return img_padded, pad + +# transfer caffe model to pytorch which will match the layer name +def transfer(model, model_weights): + transfered_model_weights = {} + for weights_name in model.state_dict().keys(): + transfered_model_weights[weights_name] = model_weights['.'.join(weights_name.split('.')[1:])] + return transfered_model_weights + +# draw the body keypoint and lims +def draw_bodypose(canvas, candidate, subset): + stickwidth = 4 + limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \ + [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \ + [1, 16], [16, 18], [3, 17], [6, 18]] + + colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \ + [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \ + [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]] + for i in range(18): + for n in range(len(subset)): + index = int(subset[n][i]) + if index == -1: + continue + x, y = candidate[index][0:2] + cv2.circle(canvas, (int(x), int(y)), 4, colors[i], thickness=-1) + for i in range(17): + for n in range(len(subset)): + index = subset[n][np.array(limbSeq[i]) - 1] + if -1 in index: + continue + cur_canvas = canvas.copy() + Y = candidate[index.astype(int), 0] + X = candidate[index.astype(int), 1] + mX = np.mean(X) + mY = np.mean(Y) + length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5 + angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1])) + polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1) + cv2.fillConvexPoly(cur_canvas, polygon, colors[i]) + canvas = cv2.addWeighted(canvas, 0.4, cur_canvas, 0.6, 0) + # plt.imsave("preview.jpg", canvas[:, :, [2, 1, 0]]) + # plt.imshow(canvas[:, :, [2, 1, 0]]) + return canvas + + +# image drawed by opencv is not good. +def draw_handpose(canvas, all_hand_peaks, show_number=False): + edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \ + [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]] + + for peaks in all_hand_peaks: + for ie, e in enumerate(edges): + if np.sum(np.all(peaks[e], axis=1)==0)==0: + x1, y1 = peaks[e[0]] + x2, y2 = peaks[e[1]] + cv2.line(canvas, (x1, y1), (x2, y2), matplotlib.colors.hsv_to_rgb([ie/float(len(edges)), 1.0, 1.0])*255, thickness=2) + + for i, keyponit in enumerate(peaks): + x, y = keyponit + cv2.circle(canvas, (x, y), 4, (0, 0, 255), thickness=-1) + if show_number: + cv2.putText(canvas, str(i), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (0, 0, 0), lineType=cv2.LINE_AA) + return canvas + +# detect hand according to body pose keypoints +# please refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/src/openpose/hand/handDetector.cpp +def handDetect(candidate, subset, oriImg): + # right hand: wrist 4, elbow 3, shoulder 2 + # left hand: wrist 7, elbow 6, shoulder 5 + ratioWristElbow = 0.33 + detect_result = [] + image_height, image_width = oriImg.shape[0:2] + for person in subset.astype(int): + # if any of three not detected + has_left = np.sum(person[[5, 6, 7]] == -1) == 0 + has_right = np.sum(person[[2, 3, 4]] == -1) == 0 + if not (has_left or has_right): + continue + hands = [] + #left hand + if has_left: + left_shoulder_index, left_elbow_index, left_wrist_index = person[[5, 6, 7]] + x1, y1 = candidate[left_shoulder_index][:2] + x2, y2 = candidate[left_elbow_index][:2] + x3, y3 = candidate[left_wrist_index][:2] + hands.append([x1, y1, x2, y2, x3, y3, True]) + # right hand + if has_right: + right_shoulder_index, right_elbow_index, right_wrist_index = person[[2, 3, 4]] + x1, y1 = candidate[right_shoulder_index][:2] + x2, y2 = candidate[right_elbow_index][:2] + x3, y3 = candidate[right_wrist_index][:2] + hands.append([x1, y1, x2, y2, x3, y3, False]) + + for x1, y1, x2, y2, x3, y3, is_left in hands: + # pos_hand = pos_wrist + ratio * (pos_wrist - pos_elbox) = (1 + ratio) * pos_wrist - ratio * pos_elbox + # handRectangle.x = posePtr[wrist*3] + ratioWristElbow * (posePtr[wrist*3] - posePtr[elbow*3]); + # handRectangle.y = posePtr[wrist*3+1] + ratioWristElbow * (posePtr[wrist*3+1] - posePtr[elbow*3+1]); + # const auto distanceWristElbow = getDistance(poseKeypoints, person, wrist, elbow); + # const auto distanceElbowShoulder = getDistance(poseKeypoints, person, elbow, shoulder); + # handRectangle.width = 1.5f * fastMax(distanceWristElbow, 0.9f * distanceElbowShoulder); + x = x3 + ratioWristElbow * (x3 - x2) + y = y3 + ratioWristElbow * (y3 - y2) + distanceWristElbow = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2) + distanceElbowShoulder = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) + width = 1.5 * max(distanceWristElbow, 0.9 * distanceElbowShoulder) + # x-y refers to the center --> offset to topLeft point + # handRectangle.x -= handRectangle.width / 2.f; + # handRectangle.y -= handRectangle.height / 2.f; + x -= width / 2 + y -= width / 2 # width = height + # overflow the image + if x < 0: x = 0 + if y < 0: y = 0 + width1 = width + width2 = width + if x + width > image_width: width1 = image_width - x + if y + width > image_height: width2 = image_height - y + width = min(width1, width2) + # the max hand box value is 20 pixels + if width >= 20: + detect_result.append([int(x), int(y), int(width), is_left]) + + ''' + return value: [[x, y, w, True if left hand else False]]. + width=height since the network require squared input. + x, y is the coordinate of top left + ''' + return detect_result + +# get max index of 2d array +def npmax(array): + arrayindex = array.argmax(1) + arrayvalue = array.max(1) + i = arrayvalue.argmax() + j = arrayindex[i] + return i, j diff --git a/comparison_models/ControlNet/annotator/uniformer/LICENSE b/comparison_models/ControlNet/annotator/uniformer/LICENSE new file mode 100644 index 0000000..c38dc63 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/LICENSE @@ -0,0 +1,203 @@ +Copyright 2022 SenseTime X-Lab. 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 2022 SenseTime X-Lab. + + 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. \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/uniformer/__init__.py b/comparison_models/ControlNet/annotator/uniformer/__init__.py new file mode 100644 index 0000000..3364d40 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/__init__.py @@ -0,0 +1,27 @@ +# Uniformer +# From https://github.com/Sense-X/UniFormer +# # Apache-2.0 license + +import os + +from annotator.uniformer.mmseg.apis import init_segmentor, inference_segmentor, show_result_pyplot +from annotator.uniformer.mmseg.core.evaluation import get_palette +from annotator.util import annotator_ckpts_path + + +checkpoint_file = "https://huggingface.co/lllyasviel/ControlNet/resolve/main/annotator/ckpts/upernet_global_small.pth" + + +class UniformerDetector: + def __init__(self): + modelpath = os.path.join(annotator_ckpts_path, "upernet_global_small.pth") + if not os.path.exists(modelpath): + from basicsr.utils.download_util import load_file_from_url + load_file_from_url(checkpoint_file, model_dir=annotator_ckpts_path) + config_file = os.path.join(os.path.dirname(annotator_ckpts_path), "uniformer", "exp", "upernet_global_small", "config.py") + self.model = init_segmentor(config_file, modelpath).cuda() + + def __call__(self, img): + result = inference_segmentor(self.model, img) + res_img = show_result_pyplot(self.model, img, result, get_palette('ade'), opacity=1) + return res_img diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/ade20k.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/ade20k.py new file mode 100644 index 0000000..efc8b4b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/ade20k.py @@ -0,0 +1,54 @@ +# 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) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='Resize', img_scale=(2048, 512), 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']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(2048, 512), + # 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']), + ]) +] +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/training', + ann_dir='annotations/training', + pipeline=train_pipeline), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/chase_db1.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/chase_db1.py new file mode 100644 index 0000000..298594e --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/chase_db1.py @@ -0,0 +1,59 @@ +# dataset settings +dataset_type = 'ChaseDB1Dataset' +data_root = 'data/CHASE_DB1' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +img_scale = (960, 999) +crop_size = (128, 128) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', img_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='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']) +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=img_scale, + # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0], + 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']) + ]) +] + +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict( + type='RepeatDataset', + times=40000, + dataset=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/training', + ann_dir='annotations/training', + pipeline=train_pipeline)), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/cityscapes.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/cityscapes.py new file mode 100644 index 0000000..f21867c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/cityscapes.py @@ -0,0 +1,54 @@ +# dataset settings +dataset_type = 'CityscapesDataset' +data_root = 'data/cityscapes/' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', img_scale=(2048, 1024), 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']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(2048, 1024), + # 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']), + ]) +] +data = dict( + samples_per_gpu=2, + workers_per_gpu=2, + train=dict( + type=dataset_type, + data_root=data_root, + img_dir='leftImg8bit/train', + ann_dir='gtFine/train', + pipeline=train_pipeline), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='leftImg8bit/val', + ann_dir='gtFine/val', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='leftImg8bit/val', + ann_dir='gtFine/val', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/cityscapes_769x769.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/cityscapes_769x769.py new file mode 100644 index 0000000..336c7b2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/cityscapes_769x769.py @@ -0,0 +1,35 @@ +_base_ = './cityscapes.py' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +crop_size = (769, 769) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', img_scale=(2049, 1025), 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']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(2049, 1025), + # 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']), + ]) +] +data = dict( + train=dict(pipeline=train_pipeline), + val=dict(pipeline=test_pipeline), + test=dict(pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/drive.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/drive.py new file mode 100644 index 0000000..06e8ff6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/drive.py @@ -0,0 +1,59 @@ +# dataset settings +dataset_type = 'DRIVEDataset' +data_root = 'data/DRIVE' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +img_scale = (584, 565) +crop_size = (64, 64) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', img_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='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']) +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=img_scale, + # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0], + 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']) + ]) +] + +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict( + type='RepeatDataset', + times=40000, + dataset=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/training', + ann_dir='annotations/training', + pipeline=train_pipeline)), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/hrf.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/hrf.py new file mode 100644 index 0000000..242d790 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/hrf.py @@ -0,0 +1,59 @@ +# dataset settings +dataset_type = 'HRFDataset' +data_root = 'data/HRF' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +img_scale = (2336, 3504) +crop_size = (256, 256) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', img_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='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']) +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=img_scale, + # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0], + 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']) + ]) +] + +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict( + type='RepeatDataset', + times=40000, + dataset=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/training', + ann_dir='annotations/training', + pipeline=train_pipeline)), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_context.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_context.py new file mode 100644 index 0000000..ff65bad --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_context.py @@ -0,0 +1,60 @@ +# dataset settings +dataset_type = 'PascalContextDataset' +data_root = 'data/VOCdevkit/VOC2010/' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) + +img_scale = (520, 520) +crop_size = (480, 480) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', img_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='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']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=img_scale, + # 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']), + ]) +] +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClassContext', + split='ImageSets/SegmentationContext/train.txt', + pipeline=train_pipeline), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClassContext', + split='ImageSets/SegmentationContext/val.txt', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClassContext', + split='ImageSets/SegmentationContext/val.txt', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_context_59.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_context_59.py new file mode 100644 index 0000000..37585ab --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_context_59.py @@ -0,0 +1,60 @@ +# dataset settings +dataset_type = 'PascalContextDataset59' +data_root = 'data/VOCdevkit/VOC2010/' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) + +img_scale = (520, 520) +crop_size = (480, 480) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='Resize', img_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='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']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=img_scale, + # 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']), + ]) +] +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClassContext', + split='ImageSets/SegmentationContext/train.txt', + pipeline=train_pipeline), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClassContext', + split='ImageSets/SegmentationContext/val.txt', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClassContext', + split='ImageSets/SegmentationContext/val.txt', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_voc12.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_voc12.py new file mode 100644 index 0000000..ba1d42d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_voc12.py @@ -0,0 +1,57 @@ +# dataset settings +dataset_type = 'PascalVOCDataset' +data_root = 'data/VOCdevkit/VOC2012' +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) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', img_scale=(2048, 512), 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']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(2048, 512), + # 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']), + ]) +] +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClass', + split='ImageSets/Segmentation/train.txt', + pipeline=train_pipeline), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClass', + split='ImageSets/Segmentation/val.txt', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='JPEGImages', + ann_dir='SegmentationClass', + split='ImageSets/Segmentation/val.txt', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_voc12_aug.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_voc12_aug.py new file mode 100644 index 0000000..3f23b67 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/pascal_voc12_aug.py @@ -0,0 +1,9 @@ +_base_ = './pascal_voc12.py' +# dataset settings +data = dict( + train=dict( + ann_dir=['SegmentationClass', 'SegmentationClassAug'], + split=[ + 'ImageSets/Segmentation/train.txt', + 'ImageSets/Segmentation/aug.txt' + ])) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/stare.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/stare.py new file mode 100644 index 0000000..3f71b25 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/datasets/stare.py @@ -0,0 +1,59 @@ +# dataset settings +dataset_type = 'STAREDataset' +data_root = 'data/STARE' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +img_scale = (605, 700) +crop_size = (128, 128) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', img_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='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']) +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=img_scale, + # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0], + 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']) + ]) +] + +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict( + type='RepeatDataset', + times=40000, + dataset=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/training', + ann_dir='annotations/training', + pipeline=train_pipeline)), + val=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + data_root=data_root, + img_dir='images/validation', + ann_dir='annotations/validation', + pipeline=test_pipeline)) diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/default_runtime.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/default_runtime.py new file mode 100644 index 0000000..b564cc4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/default_runtime.py @@ -0,0 +1,14 @@ +# yapf:disable +log_config = dict( + interval=50, + hooks=[ + dict(type='TextLoggerHook', by_epoch=False), + # dict(type='TensorboardLoggerHook') + ]) +# yapf:enable +dist_params = dict(backend='nccl') +log_level = 'INFO' +load_from = None +resume_from = None +workflow = [('train', 1)] +cudnn_benchmark = True diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ann_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ann_r50-d8.py new file mode 100644 index 0000000..a2cb653 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ann_r50-d8.py @@ -0,0 +1,46 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/apcnet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/apcnet_r50-d8.py new file mode 100644 index 0000000..c8f5316 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/apcnet_r50-d8.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ccnet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ccnet_r50-d8.py new file mode 100644 index 0000000..794148f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ccnet_r50-d8.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/cgnet.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/cgnet.py new file mode 100644 index 0000000..eff8d94 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/cgnet.py @@ -0,0 +1,35 @@ +# model settings +norm_cfg = dict(type='SyncBN', eps=1e-03, requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/danet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/danet_r50-d8.py new file mode 100644 index 0000000..2c93493 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/danet_r50-d8.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3_r50-d8.py new file mode 100644 index 0000000..d7a43be --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3_r50-d8.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3_unet_s5-d16.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3_unet_s5-d16.py new file mode 100644 index 0000000..0cd2629 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3_unet_s5-d16.py @@ -0,0 +1,50 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3plus_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3plus_r50-d8.py new file mode 100644 index 0000000..050e39e --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/deeplabv3plus_r50-d8.py @@ -0,0 +1,46 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/dmnet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/dmnet_r50-d8.py new file mode 100644 index 0000000..d22ba52 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/dmnet_r50-d8.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/dnl_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/dnl_r50-d8.py new file mode 100644 index 0000000..edb4c17 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/dnl_r50-d8.py @@ -0,0 +1,46 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/emanet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/emanet_r50-d8.py new file mode 100644 index 0000000..26adcd4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/emanet_r50-d8.py @@ -0,0 +1,47 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/encnet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/encnet_r50-d8.py new file mode 100644 index 0000000..be77712 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/encnet_r50-d8.py @@ -0,0 +1,48 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fast_scnn.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fast_scnn.py new file mode 100644 index 0000000..32fdeb6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fast_scnn.py @@ -0,0 +1,57 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True, momentum=0.01) +model = dict( + type='EncoderDecoder', + 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=0.4)), + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_hr18.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_hr18.py new file mode 100644 index 0000000..c3e299b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_hr18.py @@ -0,0 +1,52 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_r50-d8.py new file mode 100644 index 0000000..5e98f6c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_r50-d8.py @@ -0,0 +1,45 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_unet_s5-d16.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_unet_s5-d16.py new file mode 100644 index 0000000..a33e797 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fcn_unet_s5-d16.py @@ -0,0 +1,51 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fpn_r50.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fpn_r50.py new file mode 100644 index 0000000..86ab327 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fpn_r50.py @@ -0,0 +1,36 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fpn_uniformer.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fpn_uniformer.py new file mode 100644 index 0000000..8aae98c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/fpn_uniformer.py @@ -0,0 +1,35 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + backbone=dict( + type='UniFormer', + embed_dim=[64, 128, 320, 512], + layers=[3, 4, 8, 3], + head_dim=64, + mlp_ratio=4., + qkv_bias=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.1), + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/gcnet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/gcnet_r50-d8.py new file mode 100644 index 0000000..3d2ad69 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/gcnet_r50-d8.py @@ -0,0 +1,46 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/lraspp_m-v3-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/lraspp_m-v3-d8.py new file mode 100644 index 0000000..9325824 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/lraspp_m-v3-d8.py @@ -0,0 +1,25 @@ +# model settings +norm_cfg = dict(type='SyncBN', eps=0.001, requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/nonlocal_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/nonlocal_r50-d8.py new file mode 100644 index 0000000..5674a39 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/nonlocal_r50-d8.py @@ -0,0 +1,46 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ocrnet_hr18.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ocrnet_hr18.py new file mode 100644 index 0000000..c60f62a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ocrnet_hr18.py @@ -0,0 +1,68 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='CascadeEncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ocrnet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ocrnet_r50-d8.py new file mode 100644 index 0000000..615aa3f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/ocrnet_r50-d8.py @@ -0,0 +1,47 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='CascadeEncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pointrend_r50.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pointrend_r50.py new file mode 100644 index 0000000..9d323db --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pointrend_r50.py @@ -0,0 +1,56 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='CascadeEncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/psanet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/psanet_r50-d8.py new file mode 100644 index 0000000..689513f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/psanet_r50-d8.py @@ -0,0 +1,49 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pspnet_r50-d8.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pspnet_r50-d8.py new file mode 100644 index 0000000..f451e08 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pspnet_r50-d8.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pspnet_unet_s5-d16.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pspnet_unet_s5-d16.py new file mode 100644 index 0000000..fcff9ec --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/pspnet_unet_s5-d16.py @@ -0,0 +1,50 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/upernet_r50.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/upernet_r50.py new file mode 100644 index 0000000..1097496 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/upernet_r50.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + 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/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/upernet_uniformer.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/upernet_uniformer.py new file mode 100644 index 0000000..41aa4db --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/models/upernet_uniformer.py @@ -0,0 +1,43 @@ +# model settings +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + type='EncoderDecoder', + pretrained=None, + backbone=dict( + type='UniFormer', + embed_dim=[64, 128, 320, 512], + layers=[3, 4, 8, 3], + head_dim=64, + mlp_ratio=4., + qkv_bias=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.1), + 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=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=320, + 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')) \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_160k.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_160k.py new file mode 100644 index 0000000..5260389 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_160k.py @@ -0,0 +1,9 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optimizer_config = dict() +# learning policy +lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False) +# runtime settings +runner = dict(type='IterBasedRunner', max_iters=160000) +checkpoint_config = dict(by_epoch=False, interval=16000) +evaluation = dict(interval=16000, metric='mIoU') diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_20k.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_20k.py new file mode 100644 index 0000000..bf780a1 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_20k.py @@ -0,0 +1,9 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optimizer_config = dict() +# learning policy +lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False) +# runtime settings +runner = dict(type='IterBasedRunner', max_iters=20000) +checkpoint_config = dict(by_epoch=False, interval=2000) +evaluation = dict(interval=2000, metric='mIoU') diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_40k.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_40k.py new file mode 100644 index 0000000..cdbf841 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_40k.py @@ -0,0 +1,9 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optimizer_config = dict() +# learning policy +lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False) +# runtime settings +runner = dict(type='IterBasedRunner', max_iters=40000) +checkpoint_config = dict(by_epoch=False, interval=4000) +evaluation = dict(interval=4000, metric='mIoU') diff --git a/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_80k.py b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_80k.py new file mode 100644 index 0000000..c190cee --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/configs/_base_/schedules/schedule_80k.py @@ -0,0 +1,9 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optimizer_config = dict() +# learning policy +lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False) +# runtime settings +runner = dict(type='IterBasedRunner', max_iters=80000) +checkpoint_config = dict(by_epoch=False, interval=8000) +evaluation = dict(interval=8000, metric='mIoU') diff --git a/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/config.py b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/config.py new file mode 100644 index 0000000..01db96b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/config.py @@ -0,0 +1,38 @@ +_base_ = [ + '../../configs/_base_/models/upernet_uniformer.py', + '../../configs/_base_/datasets/ade20k.py', + '../../configs/_base_/default_runtime.py', + '../../configs/_base_/schedules/schedule_160k.py' +] +model = dict( + backbone=dict( + type='UniFormer', + embed_dim=[64, 128, 320, 512], + layers=[3, 4, 8, 3], + head_dim=64, + drop_path_rate=0.25, + windows=False, + hybrid=False + ), + 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 +optimizer = dict(_delete_=True, 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.)})) + +lr_config = dict(_delete_=True, policy='poly', + warmup='linear', + warmup_iters=1500, + warmup_ratio=1e-6, + power=1.0, min_lr=0.0, by_epoch=False) + +data=dict(samples_per_gpu=2) \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/run.sh b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/run.sh new file mode 100644 index 0000000..9fb22ed --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/run.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +work_path=$(dirname $0) +PYTHONPATH="$(dirname $0)/../../":$PYTHONPATH \ +python -m torch.distributed.launch --nproc_per_node=8 \ + tools/train.py ${work_path}/config.py \ + --launcher pytorch \ + --options model.backbone.pretrained_path='your_model_path/uniformer_small_in1k.pth' \ + --work-dir ${work_path}/ckpt \ + 2>&1 | tee -a ${work_path}/log.txt diff --git a/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test.sh b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test.sh new file mode 100644 index 0000000..d9a85e7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +work_path=$(dirname $0) +PYTHONPATH="$(dirname $0)/../../":$PYTHONPATH \ +python -m torch.distributed.launch --nproc_per_node=8 \ + tools/test.py ${work_path}/test_config_h32.py \ + ${work_path}/ckpt/latest.pth \ + --launcher pytorch \ + --eval mIoU \ + 2>&1 | tee -a ${work_path}/log.txt diff --git a/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_g.py b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_g.py new file mode 100644 index 0000000..e43737a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_g.py @@ -0,0 +1,38 @@ +_base_ = [ + '../../configs/_base_/models/upernet_uniformer.py', + '../../configs/_base_/datasets/ade20k.py', + '../../configs/_base_/default_runtime.py', + '../../configs/_base_/schedules/schedule_160k.py' +] +model = dict( + backbone=dict( + type='UniFormer', + embed_dim=[64, 128, 320, 512], + layers=[3, 4, 8, 3], + head_dim=64, + drop_path_rate=0.25, + windows=False, + hybrid=False, + ), + 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 +optimizer = dict(_delete_=True, 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.)})) + +lr_config = dict(_delete_=True, policy='poly', + warmup='linear', + warmup_iters=1500, + warmup_ratio=1e-6, + power=1.0, min_lr=0.0, by_epoch=False) + +data=dict(samples_per_gpu=2) \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_h32.py b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_h32.py new file mode 100644 index 0000000..a31e387 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_h32.py @@ -0,0 +1,39 @@ +_base_ = [ + '../../configs/_base_/models/upernet_uniformer.py', + '../../configs/_base_/datasets/ade20k.py', + '../../configs/_base_/default_runtime.py', + '../../configs/_base_/schedules/schedule_160k.py' +] +model = dict( + backbone=dict( + type='UniFormer', + embed_dim=[64, 128, 320, 512], + layers=[3, 4, 8, 3], + head_dim=64, + drop_path_rate=0.25, + windows=False, + hybrid=True, + window_size=32 + ), + 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 +optimizer = dict(_delete_=True, 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.)})) + +lr_config = dict(_delete_=True, policy='poly', + warmup='linear', + warmup_iters=1500, + warmup_ratio=1e-6, + power=1.0, min_lr=0.0, by_epoch=False) + +data=dict(samples_per_gpu=2) \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_w32.py b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_w32.py new file mode 100644 index 0000000..3d9e06f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/exp/upernet_global_small/test_config_w32.py @@ -0,0 +1,39 @@ +_base_ = [ + '../../configs/_base_/models/upernet_uniformer.py', + '../../configs/_base_/datasets/ade20k.py', + '../../configs/_base_/default_runtime.py', + '../../configs/_base_/schedules/schedule_160k.py' +] +model = dict( + backbone=dict( + type='UniFormer', + embed_dim=[64, 128, 320, 512], + layers=[3, 4, 8, 3], + head_dim=64, + drop_path_rate=0.25, + windows=True, + hybrid=False, + window_size=32 + ), + 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 +optimizer = dict(_delete_=True, 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.)})) + +lr_config = dict(_delete_=True, policy='poly', + warmup='linear', + warmup_iters=1500, + warmup_ratio=1e-6, + power=1.0, min_lr=0.0, by_epoch=False) + +data=dict(samples_per_gpu=2) \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/__init__.py new file mode 100644 index 0000000..210a298 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# flake8: noqa +from .arraymisc import * +from .fileio import * +from .image import * +from .utils import * +from .version import * +from .video import * +from .visualization import * + +# The following modules are not imported to this level, so mmcv may be used +# without PyTorch. +# - runner +# - parallel +# - op diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/arraymisc/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/arraymisc/__init__.py new file mode 100644 index 0000000..4b4700d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/arraymisc/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .quantization import dequantize, quantize + +__all__ = ['quantize', 'dequantize'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/arraymisc/quantization.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/arraymisc/quantization.py new file mode 100644 index 0000000..8e47a35 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/arraymisc/quantization.py @@ -0,0 +1,55 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np + + +def quantize(arr, min_val, max_val, levels, dtype=np.int64): + """Quantize an array of (-inf, inf) to [0, levels-1]. + + Args: + arr (ndarray): Input array. + min_val (scalar): Minimum value to be clipped. + max_val (scalar): Maximum value to be clipped. + levels (int): Quantization levels. + dtype (np.type): The type of the quantized array. + + Returns: + tuple: Quantized array. + """ + if not (isinstance(levels, int) and levels > 1): + raise ValueError( + f'levels must be a positive integer, but got {levels}') + if min_val >= max_val: + raise ValueError( + f'min_val ({min_val}) must be smaller than max_val ({max_val})') + + arr = np.clip(arr, min_val, max_val) - min_val + quantized_arr = np.minimum( + np.floor(levels * arr / (max_val - min_val)).astype(dtype), levels - 1) + + return quantized_arr + + +def dequantize(arr, min_val, max_val, levels, dtype=np.float64): + """Dequantize an array. + + Args: + arr (ndarray): Input array. + min_val (scalar): Minimum value to be clipped. + max_val (scalar): Maximum value to be clipped. + levels (int): Quantization levels. + dtype (np.type): The type of the dequantized array. + + Returns: + tuple: Dequantized array. + """ + if not (isinstance(levels, int) and levels > 1): + raise ValueError( + f'levels must be a positive integer, but got {levels}') + if min_val >= max_val: + raise ValueError( + f'min_val ({min_val}) must be smaller than max_val ({max_val})') + + dequantized_arr = (arr + 0.5).astype(dtype) * (max_val - + min_val) / levels + min_val + + return dequantized_arr diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/__init__.py new file mode 100644 index 0000000..7246c89 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/__init__.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .alexnet import AlexNet +# yapf: disable +from .bricks import (ACTIVATION_LAYERS, CONV_LAYERS, NORM_LAYERS, + PADDING_LAYERS, PLUGIN_LAYERS, UPSAMPLE_LAYERS, + ContextBlock, Conv2d, Conv3d, ConvAWS2d, ConvModule, + ConvTranspose2d, ConvTranspose3d, ConvWS2d, + DepthwiseSeparableConvModule, GeneralizedAttention, + HSigmoid, HSwish, Linear, MaxPool2d, MaxPool3d, + NonLocal1d, NonLocal2d, NonLocal3d, Scale, Swish, + build_activation_layer, build_conv_layer, + build_norm_layer, build_padding_layer, build_plugin_layer, + build_upsample_layer, conv_ws_2d, is_norm) +from .builder import MODELS, build_model_from_cfg +# yapf: enable +from .resnet import ResNet, make_res_layer +from .utils import (INITIALIZERS, Caffe2XavierInit, ConstantInit, KaimingInit, + NormalInit, PretrainedInit, TruncNormalInit, UniformInit, + XavierInit, bias_init_with_prob, caffe2_xavier_init, + constant_init, fuse_conv_bn, get_model_complexity_info, + initialize, kaiming_init, normal_init, trunc_normal_init, + uniform_init, xavier_init) +from .vgg import VGG, make_vgg_layer + +__all__ = [ + 'AlexNet', 'VGG', 'make_vgg_layer', 'ResNet', 'make_res_layer', + 'constant_init', 'xavier_init', 'normal_init', 'trunc_normal_init', + 'uniform_init', 'kaiming_init', 'caffe2_xavier_init', + 'bias_init_with_prob', 'ConvModule', 'build_activation_layer', + 'build_conv_layer', 'build_norm_layer', 'build_padding_layer', + 'build_upsample_layer', 'build_plugin_layer', 'is_norm', 'NonLocal1d', + 'NonLocal2d', 'NonLocal3d', 'ContextBlock', 'HSigmoid', 'Swish', 'HSwish', + 'GeneralizedAttention', 'ACTIVATION_LAYERS', 'CONV_LAYERS', 'NORM_LAYERS', + 'PADDING_LAYERS', 'UPSAMPLE_LAYERS', 'PLUGIN_LAYERS', 'Scale', + 'get_model_complexity_info', 'conv_ws_2d', 'ConvAWS2d', 'ConvWS2d', + 'fuse_conv_bn', 'DepthwiseSeparableConvModule', 'Linear', 'Conv2d', + 'ConvTranspose2d', 'MaxPool2d', 'ConvTranspose3d', 'MaxPool3d', 'Conv3d', + 'initialize', 'INITIALIZERS', 'ConstantInit', 'XavierInit', 'NormalInit', + 'TruncNormalInit', 'UniformInit', 'KaimingInit', 'PretrainedInit', + 'Caffe2XavierInit', 'MODELS', 'build_model_from_cfg' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/alexnet.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/alexnet.py new file mode 100644 index 0000000..89e36b8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/alexnet.py @@ -0,0 +1,61 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging + +import torch.nn as nn + + +class AlexNet(nn.Module): + """AlexNet backbone. + + Args: + num_classes (int): number of classes for classification. + """ + + def __init__(self, num_classes=-1): + super(AlexNet, self).__init__() + self.num_classes = num_classes + self.features = nn.Sequential( + nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), + nn.ReLU(inplace=True), + nn.MaxPool2d(kernel_size=3, stride=2), + nn.Conv2d(64, 192, kernel_size=5, padding=2), + nn.ReLU(inplace=True), + nn.MaxPool2d(kernel_size=3, stride=2), + nn.Conv2d(192, 384, kernel_size=3, padding=1), + nn.ReLU(inplace=True), + nn.Conv2d(384, 256, kernel_size=3, padding=1), + nn.ReLU(inplace=True), + nn.Conv2d(256, 256, kernel_size=3, padding=1), + nn.ReLU(inplace=True), + nn.MaxPool2d(kernel_size=3, stride=2), + ) + if self.num_classes > 0: + self.classifier = nn.Sequential( + nn.Dropout(), + nn.Linear(256 * 6 * 6, 4096), + nn.ReLU(inplace=True), + nn.Dropout(), + nn.Linear(4096, 4096), + nn.ReLU(inplace=True), + nn.Linear(4096, num_classes), + ) + + def init_weights(self, pretrained=None): + if isinstance(pretrained, str): + logger = logging.getLogger() + from ..runner import load_checkpoint + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + # use default initializer + pass + else: + raise TypeError('pretrained must be a str or None') + + def forward(self, x): + + x = self.features(x) + if self.num_classes > 0: + x = x.view(x.size(0), 256 * 6 * 6) + x = self.classifier(x) + + return x diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/__init__.py new file mode 100644 index 0000000..0f33124 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/__init__.py @@ -0,0 +1,35 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .activation import build_activation_layer +from .context_block import ContextBlock +from .conv import build_conv_layer +from .conv2d_adaptive_padding import Conv2dAdaptivePadding +from .conv_module import ConvModule +from .conv_ws import ConvAWS2d, ConvWS2d, conv_ws_2d +from .depthwise_separable_conv_module import DepthwiseSeparableConvModule +from .drop import Dropout, DropPath +from .generalized_attention import GeneralizedAttention +from .hsigmoid import HSigmoid +from .hswish import HSwish +from .non_local import NonLocal1d, NonLocal2d, NonLocal3d +from .norm import build_norm_layer, is_norm +from .padding import build_padding_layer +from .plugin import build_plugin_layer +from .registry import (ACTIVATION_LAYERS, CONV_LAYERS, NORM_LAYERS, + PADDING_LAYERS, PLUGIN_LAYERS, UPSAMPLE_LAYERS) +from .scale import Scale +from .swish import Swish +from .upsample import build_upsample_layer +from .wrappers import (Conv2d, Conv3d, ConvTranspose2d, ConvTranspose3d, + Linear, MaxPool2d, MaxPool3d) + +__all__ = [ + 'ConvModule', 'build_activation_layer', 'build_conv_layer', + 'build_norm_layer', 'build_padding_layer', 'build_upsample_layer', + 'build_plugin_layer', 'is_norm', 'HSigmoid', 'HSwish', 'NonLocal1d', + 'NonLocal2d', 'NonLocal3d', 'ContextBlock', 'GeneralizedAttention', + 'ACTIVATION_LAYERS', 'CONV_LAYERS', 'NORM_LAYERS', 'PADDING_LAYERS', + 'UPSAMPLE_LAYERS', 'PLUGIN_LAYERS', 'Scale', 'ConvAWS2d', 'ConvWS2d', + 'conv_ws_2d', 'DepthwiseSeparableConvModule', 'Swish', 'Linear', + 'Conv2dAdaptivePadding', 'Conv2d', 'ConvTranspose2d', 'MaxPool2d', + 'ConvTranspose3d', 'MaxPool3d', 'Conv3d', 'Dropout', 'DropPath' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/activation.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/activation.py new file mode 100644 index 0000000..cab2712 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/activation.py @@ -0,0 +1,92 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from annotator.uniformer.mmcv.utils import TORCH_VERSION, build_from_cfg, digit_version +from .registry import ACTIVATION_LAYERS + +for module in [ + nn.ReLU, nn.LeakyReLU, nn.PReLU, nn.RReLU, nn.ReLU6, nn.ELU, + nn.Sigmoid, nn.Tanh +]: + ACTIVATION_LAYERS.register_module(module=module) + + +@ACTIVATION_LAYERS.register_module(name='Clip') +@ACTIVATION_LAYERS.register_module() +class Clamp(nn.Module): + """Clamp activation layer. + + This activation function is to clamp the feature map value within + :math:`[min, max]`. More details can be found in ``torch.clamp()``. + + Args: + min (Number | optional): Lower-bound of the range to be clamped to. + Default to -1. + max (Number | optional): Upper-bound of the range to be clamped to. + Default to 1. + """ + + def __init__(self, min=-1., max=1.): + super(Clamp, self).__init__() + self.min = min + self.max = max + + def forward(self, x): + """Forward function. + + Args: + x (torch.Tensor): The input tensor. + + Returns: + torch.Tensor: Clamped tensor. + """ + return torch.clamp(x, min=self.min, max=self.max) + + +class GELU(nn.Module): + r"""Applies the Gaussian Error Linear Units function: + + .. math:: + \text{GELU}(x) = x * \Phi(x) + where :math:`\Phi(x)` is the Cumulative Distribution Function for + Gaussian Distribution. + + Shape: + - Input: :math:`(N, *)` where `*` means, any number of additional + dimensions + - Output: :math:`(N, *)`, same shape as the input + + .. image:: scripts/activation_images/GELU.png + + Examples:: + + >>> m = nn.GELU() + >>> input = torch.randn(2) + >>> output = m(input) + """ + + def forward(self, input): + return F.gelu(input) + + +if (TORCH_VERSION == 'parrots' + or digit_version(TORCH_VERSION) < digit_version('1.4')): + ACTIVATION_LAYERS.register_module(module=GELU) +else: + ACTIVATION_LAYERS.register_module(module=nn.GELU) + + +def build_activation_layer(cfg): + """Build activation layer. + + Args: + cfg (dict): The activation layer config, which should contain: + - type (str): Layer type. + - layer args: Args needed to instantiate an activation layer. + + Returns: + nn.Module: Created activation layer. + """ + return build_from_cfg(cfg, ACTIVATION_LAYERS) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/context_block.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/context_block.py new file mode 100644 index 0000000..d60fdb9 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/context_block.py @@ -0,0 +1,125 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import nn + +from ..utils import constant_init, kaiming_init +from .registry import PLUGIN_LAYERS + + +def last_zero_init(m): + if isinstance(m, nn.Sequential): + constant_init(m[-1], val=0) + else: + constant_init(m, val=0) + + +@PLUGIN_LAYERS.register_module() +class ContextBlock(nn.Module): + """ContextBlock module in GCNet. + + See 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + (https://arxiv.org/abs/1904.11492) for details. + + Args: + in_channels (int): Channels of the input feature map. + ratio (float): Ratio of channels of transform bottleneck + pooling_type (str): Pooling method for context modeling. + Options are 'att' and 'avg', stand for attention pooling and + average pooling respectively. Default: 'att'. + fusion_types (Sequence[str]): Fusion method for feature fusion, + Options are 'channels_add', 'channel_mul', stand for channelwise + addition and multiplication respectively. Default: ('channel_add',) + """ + + _abbr_ = 'context_block' + + def __init__(self, + in_channels, + ratio, + pooling_type='att', + fusion_types=('channel_add', )): + super(ContextBlock, self).__init__() + assert pooling_type in ['avg', 'att'] + assert isinstance(fusion_types, (list, tuple)) + valid_fusion_types = ['channel_add', 'channel_mul'] + assert all([f in valid_fusion_types for f in fusion_types]) + assert len(fusion_types) > 0, 'at least one fusion should be used' + self.in_channels = in_channels + self.ratio = ratio + self.planes = int(in_channels * ratio) + self.pooling_type = pooling_type + self.fusion_types = fusion_types + if pooling_type == 'att': + self.conv_mask = nn.Conv2d(in_channels, 1, kernel_size=1) + self.softmax = nn.Softmax(dim=2) + else: + self.avg_pool = nn.AdaptiveAvgPool2d(1) + if 'channel_add' in fusion_types: + self.channel_add_conv = nn.Sequential( + nn.Conv2d(self.in_channels, self.planes, kernel_size=1), + nn.LayerNorm([self.planes, 1, 1]), + nn.ReLU(inplace=True), # yapf: disable + nn.Conv2d(self.planes, self.in_channels, kernel_size=1)) + else: + self.channel_add_conv = None + if 'channel_mul' in fusion_types: + self.channel_mul_conv = nn.Sequential( + nn.Conv2d(self.in_channels, self.planes, kernel_size=1), + nn.LayerNorm([self.planes, 1, 1]), + nn.ReLU(inplace=True), # yapf: disable + nn.Conv2d(self.planes, self.in_channels, kernel_size=1)) + else: + self.channel_mul_conv = None + self.reset_parameters() + + def reset_parameters(self): + if self.pooling_type == 'att': + kaiming_init(self.conv_mask, mode='fan_in') + self.conv_mask.inited = True + + if self.channel_add_conv is not None: + last_zero_init(self.channel_add_conv) + if self.channel_mul_conv is not None: + last_zero_init(self.channel_mul_conv) + + def spatial_pool(self, x): + batch, channel, height, width = x.size() + if self.pooling_type == 'att': + input_x = x + # [N, C, H * W] + input_x = input_x.view(batch, channel, height * width) + # [N, 1, C, H * W] + input_x = input_x.unsqueeze(1) + # [N, 1, H, W] + context_mask = self.conv_mask(x) + # [N, 1, H * W] + context_mask = context_mask.view(batch, 1, height * width) + # [N, 1, H * W] + context_mask = self.softmax(context_mask) + # [N, 1, H * W, 1] + context_mask = context_mask.unsqueeze(-1) + # [N, 1, C, 1] + context = torch.matmul(input_x, context_mask) + # [N, C, 1, 1] + context = context.view(batch, channel, 1, 1) + else: + # [N, C, 1, 1] + context = self.avg_pool(x) + + return context + + def forward(self, x): + # [N, C, 1, 1] + context = self.spatial_pool(x) + + out = x + if self.channel_mul_conv is not None: + # [N, C, 1, 1] + channel_mul_term = torch.sigmoid(self.channel_mul_conv(context)) + out = out * channel_mul_term + if self.channel_add_conv is not None: + # [N, C, 1, 1] + channel_add_term = self.channel_add_conv(context) + out = out + channel_add_term + + return out diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv.py new file mode 100644 index 0000000..cf54491 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv.py @@ -0,0 +1,44 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from torch import nn + +from .registry import CONV_LAYERS + +CONV_LAYERS.register_module('Conv1d', module=nn.Conv1d) +CONV_LAYERS.register_module('Conv2d', module=nn.Conv2d) +CONV_LAYERS.register_module('Conv3d', module=nn.Conv3d) +CONV_LAYERS.register_module('Conv', module=nn.Conv2d) + + +def build_conv_layer(cfg, *args, **kwargs): + """Build convolution layer. + + Args: + cfg (None or dict): The conv layer config, which should contain: + - type (str): Layer type. + - layer args: Args needed to instantiate an conv layer. + args (argument list): Arguments passed to the `__init__` + method of the corresponding conv layer. + kwargs (keyword arguments): Keyword arguments passed to the `__init__` + method of the corresponding conv layer. + + Returns: + nn.Module: Created conv layer. + """ + if cfg is None: + cfg_ = dict(type='Conv2d') + else: + if not isinstance(cfg, dict): + raise TypeError('cfg must be a dict') + if 'type' not in cfg: + raise KeyError('the cfg dict must contain the key "type"') + cfg_ = cfg.copy() + + layer_type = cfg_.pop('type') + if layer_type not in CONV_LAYERS: + raise KeyError(f'Unrecognized norm type {layer_type}') + else: + conv_layer = CONV_LAYERS.get(layer_type) + + layer = conv_layer(*args, **kwargs, **cfg_) + + return layer diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv2d_adaptive_padding.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv2d_adaptive_padding.py new file mode 100644 index 0000000..b45e758 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv2d_adaptive_padding.py @@ -0,0 +1,62 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +from torch import nn +from torch.nn import functional as F + +from .registry import CONV_LAYERS + + +@CONV_LAYERS.register_module() +class Conv2dAdaptivePadding(nn.Conv2d): + """Implementation of 2D convolution in tensorflow with `padding` as "same", + which applies padding to input (if needed) so that input image gets fully + covered by filter and stride you specified. For stride 1, this will ensure + that output image size is same as input. For stride of 2, output dimensions + will be half, for example. + + Args: + in_channels (int): Number of channels in the input image + out_channels (int): Number of channels produced by the convolution + kernel_size (int or tuple): Size of the convolving kernel + stride (int or tuple, optional): Stride of the convolution. Default: 1 + padding (int or tuple, optional): Zero-padding added to both sides of + the input. Default: 0 + dilation (int or tuple, optional): Spacing between kernel elements. + Default: 1 + groups (int, optional): Number of blocked connections from input + channels to output channels. Default: 1 + bias (bool, optional): If ``True``, adds a learnable bias to the + output. Default: ``True`` + """ + + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True): + super().__init__(in_channels, out_channels, kernel_size, stride, 0, + dilation, groups, bias) + + def forward(self, x): + img_h, img_w = x.size()[-2:] + kernel_h, kernel_w = self.weight.size()[-2:] + stride_h, stride_w = self.stride + output_h = math.ceil(img_h / stride_h) + output_w = math.ceil(img_w / stride_w) + pad_h = ( + max((output_h - 1) * self.stride[0] + + (kernel_h - 1) * self.dilation[0] + 1 - img_h, 0)) + pad_w = ( + max((output_w - 1) * self.stride[1] + + (kernel_w - 1) * self.dilation[1] + 1 - img_w, 0)) + if pad_h > 0 or pad_w > 0: + x = F.pad(x, [ + pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2 + ]) + return F.conv2d(x, self.weight, self.bias, self.stride, self.padding, + self.dilation, self.groups) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv_module.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv_module.py new file mode 100644 index 0000000..e60e7e6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv_module.py @@ -0,0 +1,206 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn + +from annotator.uniformer.mmcv.utils import _BatchNorm, _InstanceNorm +from ..utils import constant_init, kaiming_init +from .activation import build_activation_layer +from .conv import build_conv_layer +from .norm import build_norm_layer +from .padding import build_padding_layer +from .registry import PLUGIN_LAYERS + + +@PLUGIN_LAYERS.register_module() +class ConvModule(nn.Module): + """A conv block that bundles conv/norm/activation layers. + + This block simplifies the usage of convolution layers, which are commonly + used with a norm layer (e.g., BatchNorm) and activation layer (e.g., ReLU). + It is based upon three build methods: `build_conv_layer()`, + `build_norm_layer()` and `build_activation_layer()`. + + Besides, we add some additional features in this module. + 1. Automatically set `bias` of the conv layer. + 2. Spectral norm is supported. + 3. More padding modes are supported. Before PyTorch 1.5, nn.Conv2d only + supports zero and circular padding, and we add "reflect" padding mode. + + Args: + in_channels (int): Number of channels in the input feature map. + Same as that in ``nn._ConvNd``. + out_channels (int): Number of channels produced by the convolution. + Same as that in ``nn._ConvNd``. + kernel_size (int | tuple[int]): Size of the convolving kernel. + Same as that in ``nn._ConvNd``. + stride (int | tuple[int]): Stride of the convolution. + Same as that in ``nn._ConvNd``. + padding (int | tuple[int]): Zero-padding added to both sides of + the input. Same as that in ``nn._ConvNd``. + dilation (int | tuple[int]): Spacing between kernel elements. + Same as that in ``nn._ConvNd``. + groups (int): Number of blocked connections from input channels to + output channels. Same as that in ``nn._ConvNd``. + bias (bool | str): If specified as `auto`, it will be decided by the + norm_cfg. Bias will be set as True if `norm_cfg` is None, otherwise + False. Default: "auto". + conv_cfg (dict): Config dict for convolution layer. Default: None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. Default: None. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU'). + inplace (bool): Whether to use inplace mode for activation. + Default: True. + with_spectral_norm (bool): Whether use spectral norm in conv module. + Default: False. + padding_mode (str): If the `padding_mode` has not been supported by + current `Conv2d` in PyTorch, we will use our own padding layer + instead. Currently, we support ['zeros', 'circular'] with official + implementation and ['reflect'] with our own implementation. + Default: 'zeros'. + order (tuple[str]): The order of conv/norm/activation layers. It is a + sequence of "conv", "norm" and "act". Common examples are + ("conv", "norm", "act") and ("act", "conv", "norm"). + Default: ('conv', 'norm', 'act'). + """ + + _abbr_ = 'conv_block' + + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias='auto', + conv_cfg=None, + norm_cfg=None, + act_cfg=dict(type='ReLU'), + inplace=True, + with_spectral_norm=False, + padding_mode='zeros', + order=('conv', 'norm', 'act')): + super(ConvModule, self).__init__() + assert conv_cfg is None or isinstance(conv_cfg, dict) + assert norm_cfg is None or isinstance(norm_cfg, dict) + assert act_cfg is None or isinstance(act_cfg, dict) + official_padding_mode = ['zeros', 'circular'] + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.inplace = inplace + self.with_spectral_norm = with_spectral_norm + self.with_explicit_padding = padding_mode not in official_padding_mode + self.order = order + assert isinstance(self.order, tuple) and len(self.order) == 3 + assert set(order) == set(['conv', 'norm', 'act']) + + self.with_norm = norm_cfg is not None + self.with_activation = act_cfg is not None + # if the conv layer is before a norm layer, bias is unnecessary. + if bias == 'auto': + bias = not self.with_norm + self.with_bias = bias + + if self.with_explicit_padding: + pad_cfg = dict(type=padding_mode) + self.padding_layer = build_padding_layer(pad_cfg, padding) + + # reset padding to 0 for conv module + conv_padding = 0 if self.with_explicit_padding else padding + # build convolution layer + self.conv = build_conv_layer( + conv_cfg, + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=conv_padding, + dilation=dilation, + groups=groups, + bias=bias) + # export the attributes of self.conv to a higher level for convenience + self.in_channels = self.conv.in_channels + self.out_channels = self.conv.out_channels + self.kernel_size = self.conv.kernel_size + self.stride = self.conv.stride + self.padding = padding + self.dilation = self.conv.dilation + self.transposed = self.conv.transposed + self.output_padding = self.conv.output_padding + self.groups = self.conv.groups + + if self.with_spectral_norm: + self.conv = nn.utils.spectral_norm(self.conv) + + # build normalization layers + if self.with_norm: + # norm layer is after conv layer + if order.index('norm') > order.index('conv'): + norm_channels = out_channels + else: + norm_channels = in_channels + self.norm_name, norm = build_norm_layer(norm_cfg, norm_channels) + self.add_module(self.norm_name, norm) + if self.with_bias: + if isinstance(norm, (_BatchNorm, _InstanceNorm)): + warnings.warn( + 'Unnecessary conv bias before batch/instance norm') + else: + self.norm_name = None + + # build activation layer + if self.with_activation: + act_cfg_ = act_cfg.copy() + # nn.Tanh has no 'inplace' argument + if act_cfg_['type'] not in [ + 'Tanh', 'PReLU', 'Sigmoid', 'HSigmoid', 'Swish' + ]: + act_cfg_.setdefault('inplace', inplace) + self.activate = build_activation_layer(act_cfg_) + + # Use msra init by default + self.init_weights() + + @property + def norm(self): + if self.norm_name: + return getattr(self, self.norm_name) + else: + return None + + def init_weights(self): + # 1. It is mainly for customized conv layers with their own + # initialization manners by calling their own ``init_weights()``, + # and we do not want ConvModule to override the initialization. + # 2. For customized conv layers without their own initialization + # manners (that is, they don't have their own ``init_weights()``) + # and PyTorch's conv layers, they will be initialized by + # this method with default ``kaiming_init``. + # Note: For PyTorch's conv layers, they will be overwritten by our + # initialization implementation using default ``kaiming_init``. + if not hasattr(self.conv, 'init_weights'): + if self.with_activation and self.act_cfg['type'] == 'LeakyReLU': + nonlinearity = 'leaky_relu' + a = self.act_cfg.get('negative_slope', 0.01) + else: + nonlinearity = 'relu' + a = 0 + kaiming_init(self.conv, a=a, nonlinearity=nonlinearity) + if self.with_norm: + constant_init(self.norm, 1, bias=0) + + def forward(self, x, activate=True, norm=True): + for layer in self.order: + if layer == 'conv': + if self.with_explicit_padding: + x = self.padding_layer(x) + x = self.conv(x) + elif layer == 'norm' and norm and self.with_norm: + x = self.norm(x) + elif layer == 'act' and activate and self.with_activation: + x = self.activate(x) + return x diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv_ws.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv_ws.py new file mode 100644 index 0000000..a3941e2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/conv_ws.py @@ -0,0 +1,148 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from .registry import CONV_LAYERS + + +def conv_ws_2d(input, + weight, + bias=None, + stride=1, + padding=0, + dilation=1, + groups=1, + eps=1e-5): + c_in = weight.size(0) + weight_flat = weight.view(c_in, -1) + mean = weight_flat.mean(dim=1, keepdim=True).view(c_in, 1, 1, 1) + std = weight_flat.std(dim=1, keepdim=True).view(c_in, 1, 1, 1) + weight = (weight - mean) / (std + eps) + return F.conv2d(input, weight, bias, stride, padding, dilation, groups) + + +@CONV_LAYERS.register_module('ConvWS') +class ConvWS2d(nn.Conv2d): + + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True, + eps=1e-5): + super(ConvWS2d, self).__init__( + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias=bias) + self.eps = eps + + def forward(self, x): + return conv_ws_2d(x, self.weight, self.bias, self.stride, self.padding, + self.dilation, self.groups, self.eps) + + +@CONV_LAYERS.register_module(name='ConvAWS') +class ConvAWS2d(nn.Conv2d): + """AWS (Adaptive Weight Standardization) + + This is a variant of Weight Standardization + (https://arxiv.org/pdf/1903.10520.pdf) + It is used in DetectoRS to avoid NaN + (https://arxiv.org/pdf/2006.02334.pdf) + + Args: + in_channels (int): Number of channels in the input image + out_channels (int): Number of channels produced by the convolution + kernel_size (int or tuple): Size of the conv kernel + stride (int or tuple, optional): Stride of the convolution. Default: 1 + padding (int or tuple, optional): Zero-padding added to both sides of + the input. Default: 0 + dilation (int or tuple, optional): Spacing between kernel elements. + Default: 1 + groups (int, optional): Number of blocked connections from input + channels to output channels. Default: 1 + bias (bool, optional): If set True, adds a learnable bias to the + output. Default: True + """ + + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True): + super().__init__( + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias=bias) + self.register_buffer('weight_gamma', + torch.ones(self.out_channels, 1, 1, 1)) + self.register_buffer('weight_beta', + torch.zeros(self.out_channels, 1, 1, 1)) + + def _get_weight(self, weight): + weight_flat = weight.view(weight.size(0), -1) + mean = weight_flat.mean(dim=1).view(-1, 1, 1, 1) + std = torch.sqrt(weight_flat.var(dim=1) + 1e-5).view(-1, 1, 1, 1) + weight = (weight - mean) / std + weight = self.weight_gamma * weight + self.weight_beta + return weight + + def forward(self, x): + weight = self._get_weight(self.weight) + return F.conv2d(x, weight, self.bias, self.stride, self.padding, + self.dilation, self.groups) + + def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, + missing_keys, unexpected_keys, error_msgs): + """Override default load function. + + AWS overrides the function _load_from_state_dict to recover + weight_gamma and weight_beta if they are missing. If weight_gamma and + weight_beta are found in the checkpoint, this function will return + after super()._load_from_state_dict. Otherwise, it will compute the + mean and std of the pretrained weights and store them in weight_beta + and weight_gamma. + """ + + self.weight_gamma.data.fill_(-1) + local_missing_keys = [] + super()._load_from_state_dict(state_dict, prefix, local_metadata, + strict, local_missing_keys, + unexpected_keys, error_msgs) + if self.weight_gamma.data.mean() > 0: + for k in local_missing_keys: + missing_keys.append(k) + return + weight = self.weight.data + weight_flat = weight.view(weight.size(0), -1) + mean = weight_flat.mean(dim=1).view(-1, 1, 1, 1) + std = torch.sqrt(weight_flat.var(dim=1) + 1e-5).view(-1, 1, 1, 1) + self.weight_beta.data.copy_(mean) + self.weight_gamma.data.copy_(std) + missing_gamma_beta = [ + k for k in local_missing_keys + if k.endswith('weight_gamma') or k.endswith('weight_beta') + ] + for k in missing_gamma_beta: + local_missing_keys.remove(k) + for k in local_missing_keys: + missing_keys.append(k) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/depthwise_separable_conv_module.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/depthwise_separable_conv_module.py new file mode 100644 index 0000000..722d5d8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/depthwise_separable_conv_module.py @@ -0,0 +1,96 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn + +from .conv_module import ConvModule + + +class DepthwiseSeparableConvModule(nn.Module): + """Depthwise separable convolution module. + + See https://arxiv.org/pdf/1704.04861.pdf for details. + + This module can replace a ConvModule with the conv block replaced by two + conv block: depthwise conv block and pointwise conv block. The depthwise + conv block contains depthwise-conv/norm/activation layers. The pointwise + conv block contains pointwise-conv/norm/activation layers. It should be + noted that there will be norm/activation layer in the depthwise conv block + if `norm_cfg` and `act_cfg` are specified. + + Args: + in_channels (int): Number of channels in the input feature map. + Same as that in ``nn._ConvNd``. + out_channels (int): Number of channels produced by the convolution. + Same as that in ``nn._ConvNd``. + kernel_size (int | tuple[int]): Size of the convolving kernel. + Same as that in ``nn._ConvNd``. + stride (int | tuple[int]): Stride of the convolution. + Same as that in ``nn._ConvNd``. Default: 1. + padding (int | tuple[int]): Zero-padding added to both sides of + the input. Same as that in ``nn._ConvNd``. Default: 0. + dilation (int | tuple[int]): Spacing between kernel elements. + Same as that in ``nn._ConvNd``. Default: 1. + norm_cfg (dict): Default norm config for both depthwise ConvModule and + pointwise ConvModule. Default: None. + act_cfg (dict): Default activation config for both depthwise ConvModule + and pointwise ConvModule. Default: dict(type='ReLU'). + dw_norm_cfg (dict): Norm config of depthwise ConvModule. If it is + 'default', it will be the same as `norm_cfg`. Default: 'default'. + dw_act_cfg (dict): Activation config of depthwise ConvModule. If it is + 'default', it will be the same as `act_cfg`. Default: 'default'. + pw_norm_cfg (dict): Norm config of pointwise ConvModule. If it is + 'default', it will be the same as `norm_cfg`. Default: 'default'. + pw_act_cfg (dict): Activation config of pointwise ConvModule. If it is + 'default', it will be the same as `act_cfg`. Default: 'default'. + kwargs (optional): Other shared arguments for depthwise and pointwise + ConvModule. See ConvModule for ref. + """ + + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + norm_cfg=None, + act_cfg=dict(type='ReLU'), + dw_norm_cfg='default', + dw_act_cfg='default', + pw_norm_cfg='default', + pw_act_cfg='default', + **kwargs): + super(DepthwiseSeparableConvModule, self).__init__() + assert 'groups' not in kwargs, 'groups should not be specified' + + # if norm/activation config of depthwise/pointwise ConvModule is not + # specified, use default config. + dw_norm_cfg = dw_norm_cfg if dw_norm_cfg != 'default' else norm_cfg + dw_act_cfg = dw_act_cfg if dw_act_cfg != 'default' else act_cfg + pw_norm_cfg = pw_norm_cfg if pw_norm_cfg != 'default' else norm_cfg + pw_act_cfg = pw_act_cfg if pw_act_cfg != 'default' else act_cfg + + # depthwise convolution + self.depthwise_conv = ConvModule( + in_channels, + in_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=in_channels, + norm_cfg=dw_norm_cfg, + act_cfg=dw_act_cfg, + **kwargs) + + self.pointwise_conv = ConvModule( + in_channels, + out_channels, + 1, + norm_cfg=pw_norm_cfg, + act_cfg=pw_act_cfg, + **kwargs) + + def forward(self, x): + x = self.depthwise_conv(x) + x = self.pointwise_conv(x) + return x diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/drop.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/drop.py new file mode 100644 index 0000000..b7b4fcc --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/drop.py @@ -0,0 +1,65 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn + +from annotator.uniformer.mmcv import build_from_cfg +from .registry import DROPOUT_LAYERS + + +def drop_path(x, drop_prob=0., training=False): + """Drop paths (Stochastic Depth) per sample (when applied in main path of + residual blocks). + + We follow the implementation + https://github.com/rwightman/pytorch-image-models/blob/a2727c1bf78ba0d7b5727f5f95e37fb7f8866b1f/timm/models/layers/drop.py # noqa: E501 + """ + if drop_prob == 0. or not training: + return x + keep_prob = 1 - drop_prob + # handle tensors with different dimensions, not just 4D tensors. + shape = (x.shape[0], ) + (1, ) * (x.ndim - 1) + random_tensor = keep_prob + torch.rand( + shape, dtype=x.dtype, device=x.device) + output = x.div(keep_prob) * random_tensor.floor() + return output + + +@DROPOUT_LAYERS.register_module() +class DropPath(nn.Module): + """Drop paths (Stochastic Depth) per sample (when applied in main path of + residual blocks). + + We follow the implementation + https://github.com/rwightman/pytorch-image-models/blob/a2727c1bf78ba0d7b5727f5f95e37fb7f8866b1f/timm/models/layers/drop.py # noqa: E501 + + Args: + drop_prob (float): Probability of the path to be zeroed. Default: 0.1 + """ + + def __init__(self, drop_prob=0.1): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + + def forward(self, x): + return drop_path(x, self.drop_prob, self.training) + + +@DROPOUT_LAYERS.register_module() +class Dropout(nn.Dropout): + """A wrapper for ``torch.nn.Dropout``, We rename the ``p`` of + ``torch.nn.Dropout`` to ``drop_prob`` so as to be consistent with + ``DropPath`` + + Args: + drop_prob (float): Probability of the elements to be + zeroed. Default: 0.5. + inplace (bool): Do the operation inplace or not. Default: False. + """ + + def __init__(self, drop_prob=0.5, inplace=False): + super().__init__(p=drop_prob, inplace=inplace) + + +def build_dropout(cfg, default_args=None): + """Builder for drop out layers.""" + return build_from_cfg(cfg, DROPOUT_LAYERS, default_args) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/generalized_attention.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/generalized_attention.py new file mode 100644 index 0000000..988d9ad --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/generalized_attention.py @@ -0,0 +1,412 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + +from ..utils import kaiming_init +from .registry import PLUGIN_LAYERS + + +@PLUGIN_LAYERS.register_module() +class GeneralizedAttention(nn.Module): + """GeneralizedAttention module. + + See 'An Empirical Study of Spatial Attention Mechanisms in Deep Networks' + (https://arxiv.org/abs/1711.07971) for details. + + Args: + in_channels (int): Channels of the input feature map. + spatial_range (int): The spatial range. -1 indicates no spatial range + constraint. Default: -1. + num_heads (int): The head number of empirical_attention module. + Default: 9. + position_embedding_dim (int): The position embedding dimension. + Default: -1. + position_magnitude (int): A multiplier acting on coord difference. + Default: 1. + kv_stride (int): The feature stride acting on key/value feature map. + Default: 2. + q_stride (int): The feature stride acting on query feature map. + Default: 1. + attention_type (str): A binary indicator string for indicating which + items in generalized empirical_attention module are used. + Default: '1111'. + + - '1000' indicates 'query and key content' (appr - appr) item, + - '0100' indicates 'query content and relative position' + (appr - position) item, + - '0010' indicates 'key content only' (bias - appr) item, + - '0001' indicates 'relative position only' (bias - position) item. + """ + + _abbr_ = 'gen_attention_block' + + def __init__(self, + in_channels, + spatial_range=-1, + num_heads=9, + position_embedding_dim=-1, + position_magnitude=1, + kv_stride=2, + q_stride=1, + attention_type='1111'): + + super(GeneralizedAttention, self).__init__() + + # hard range means local range for non-local operation + self.position_embedding_dim = ( + position_embedding_dim + if position_embedding_dim > 0 else in_channels) + + self.position_magnitude = position_magnitude + self.num_heads = num_heads + self.in_channels = in_channels + self.spatial_range = spatial_range + self.kv_stride = kv_stride + self.q_stride = q_stride + self.attention_type = [bool(int(_)) for _ in attention_type] + self.qk_embed_dim = in_channels // num_heads + out_c = self.qk_embed_dim * num_heads + + if self.attention_type[0] or self.attention_type[1]: + self.query_conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_c, + kernel_size=1, + bias=False) + self.query_conv.kaiming_init = True + + if self.attention_type[0] or self.attention_type[2]: + self.key_conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_c, + kernel_size=1, + bias=False) + self.key_conv.kaiming_init = True + + self.v_dim = in_channels // num_heads + self.value_conv = nn.Conv2d( + in_channels=in_channels, + out_channels=self.v_dim * num_heads, + kernel_size=1, + bias=False) + self.value_conv.kaiming_init = True + + if self.attention_type[1] or self.attention_type[3]: + self.appr_geom_fc_x = nn.Linear( + self.position_embedding_dim // 2, out_c, bias=False) + self.appr_geom_fc_x.kaiming_init = True + + self.appr_geom_fc_y = nn.Linear( + self.position_embedding_dim // 2, out_c, bias=False) + self.appr_geom_fc_y.kaiming_init = True + + if self.attention_type[2]: + stdv = 1.0 / math.sqrt(self.qk_embed_dim * 2) + appr_bias_value = -2 * stdv * torch.rand(out_c) + stdv + self.appr_bias = nn.Parameter(appr_bias_value) + + if self.attention_type[3]: + stdv = 1.0 / math.sqrt(self.qk_embed_dim * 2) + geom_bias_value = -2 * stdv * torch.rand(out_c) + stdv + self.geom_bias = nn.Parameter(geom_bias_value) + + self.proj_conv = nn.Conv2d( + in_channels=self.v_dim * num_heads, + out_channels=in_channels, + kernel_size=1, + bias=True) + self.proj_conv.kaiming_init = True + self.gamma = nn.Parameter(torch.zeros(1)) + + if self.spatial_range >= 0: + # only works when non local is after 3*3 conv + if in_channels == 256: + max_len = 84 + elif in_channels == 512: + max_len = 42 + + max_len_kv = int((max_len - 1.0) / self.kv_stride + 1) + local_constraint_map = np.ones( + (max_len, max_len, max_len_kv, max_len_kv), dtype=np.int) + for iy in range(max_len): + for ix in range(max_len): + local_constraint_map[ + iy, ix, + max((iy - self.spatial_range) // + self.kv_stride, 0):min((iy + self.spatial_range + + 1) // self.kv_stride + + 1, max_len), + max((ix - self.spatial_range) // + self.kv_stride, 0):min((ix + self.spatial_range + + 1) // self.kv_stride + + 1, max_len)] = 0 + + self.local_constraint_map = nn.Parameter( + torch.from_numpy(local_constraint_map).byte(), + requires_grad=False) + + if self.q_stride > 1: + self.q_downsample = nn.AvgPool2d( + kernel_size=1, stride=self.q_stride) + else: + self.q_downsample = None + + if self.kv_stride > 1: + self.kv_downsample = nn.AvgPool2d( + kernel_size=1, stride=self.kv_stride) + else: + self.kv_downsample = None + + self.init_weights() + + def get_position_embedding(self, + h, + w, + h_kv, + w_kv, + q_stride, + kv_stride, + device, + dtype, + feat_dim, + wave_length=1000): + # the default type of Tensor is float32, leading to type mismatch + # in fp16 mode. Cast it to support fp16 mode. + h_idxs = torch.linspace(0, h - 1, h).to(device=device, dtype=dtype) + h_idxs = h_idxs.view((h, 1)) * q_stride + + w_idxs = torch.linspace(0, w - 1, w).to(device=device, dtype=dtype) + w_idxs = w_idxs.view((w, 1)) * q_stride + + h_kv_idxs = torch.linspace(0, h_kv - 1, h_kv).to( + device=device, dtype=dtype) + h_kv_idxs = h_kv_idxs.view((h_kv, 1)) * kv_stride + + w_kv_idxs = torch.linspace(0, w_kv - 1, w_kv).to( + device=device, dtype=dtype) + w_kv_idxs = w_kv_idxs.view((w_kv, 1)) * kv_stride + + # (h, h_kv, 1) + h_diff = h_idxs.unsqueeze(1) - h_kv_idxs.unsqueeze(0) + h_diff *= self.position_magnitude + + # (w, w_kv, 1) + w_diff = w_idxs.unsqueeze(1) - w_kv_idxs.unsqueeze(0) + w_diff *= self.position_magnitude + + feat_range = torch.arange(0, feat_dim / 4).to( + device=device, dtype=dtype) + + dim_mat = torch.Tensor([wave_length]).to(device=device, dtype=dtype) + dim_mat = dim_mat**((4. / feat_dim) * feat_range) + dim_mat = dim_mat.view((1, 1, -1)) + + embedding_x = torch.cat( + ((w_diff / dim_mat).sin(), (w_diff / dim_mat).cos()), dim=2) + + embedding_y = torch.cat( + ((h_diff / dim_mat).sin(), (h_diff / dim_mat).cos()), dim=2) + + return embedding_x, embedding_y + + def forward(self, x_input): + num_heads = self.num_heads + + # use empirical_attention + if self.q_downsample is not None: + x_q = self.q_downsample(x_input) + else: + x_q = x_input + n, _, h, w = x_q.shape + + if self.kv_downsample is not None: + x_kv = self.kv_downsample(x_input) + else: + x_kv = x_input + _, _, h_kv, w_kv = x_kv.shape + + if self.attention_type[0] or self.attention_type[1]: + proj_query = self.query_conv(x_q).view( + (n, num_heads, self.qk_embed_dim, h * w)) + proj_query = proj_query.permute(0, 1, 3, 2) + + if self.attention_type[0] or self.attention_type[2]: + proj_key = self.key_conv(x_kv).view( + (n, num_heads, self.qk_embed_dim, h_kv * w_kv)) + + if self.attention_type[1] or self.attention_type[3]: + position_embed_x, position_embed_y = self.get_position_embedding( + h, w, h_kv, w_kv, self.q_stride, self.kv_stride, + x_input.device, x_input.dtype, self.position_embedding_dim) + # (n, num_heads, w, w_kv, dim) + position_feat_x = self.appr_geom_fc_x(position_embed_x).\ + view(1, w, w_kv, num_heads, self.qk_embed_dim).\ + permute(0, 3, 1, 2, 4).\ + repeat(n, 1, 1, 1, 1) + + # (n, num_heads, h, h_kv, dim) + position_feat_y = self.appr_geom_fc_y(position_embed_y).\ + view(1, h, h_kv, num_heads, self.qk_embed_dim).\ + permute(0, 3, 1, 2, 4).\ + repeat(n, 1, 1, 1, 1) + + position_feat_x /= math.sqrt(2) + position_feat_y /= math.sqrt(2) + + # accelerate for saliency only + if (np.sum(self.attention_type) == 1) and self.attention_type[2]: + appr_bias = self.appr_bias.\ + view(1, num_heads, 1, self.qk_embed_dim).\ + repeat(n, 1, 1, 1) + + energy = torch.matmul(appr_bias, proj_key).\ + view(n, num_heads, 1, h_kv * w_kv) + + h = 1 + w = 1 + else: + # (n, num_heads, h*w, h_kv*w_kv), query before key, 540mb for + if not self.attention_type[0]: + energy = torch.zeros( + n, + num_heads, + h, + w, + h_kv, + w_kv, + dtype=x_input.dtype, + device=x_input.device) + + # attention_type[0]: appr - appr + # attention_type[1]: appr - position + # attention_type[2]: bias - appr + # attention_type[3]: bias - position + if self.attention_type[0] or self.attention_type[2]: + if self.attention_type[0] and self.attention_type[2]: + appr_bias = self.appr_bias.\ + view(1, num_heads, 1, self.qk_embed_dim) + energy = torch.matmul(proj_query + appr_bias, proj_key).\ + view(n, num_heads, h, w, h_kv, w_kv) + + elif self.attention_type[0]: + energy = torch.matmul(proj_query, proj_key).\ + view(n, num_heads, h, w, h_kv, w_kv) + + elif self.attention_type[2]: + appr_bias = self.appr_bias.\ + view(1, num_heads, 1, self.qk_embed_dim).\ + repeat(n, 1, 1, 1) + + energy += torch.matmul(appr_bias, proj_key).\ + view(n, num_heads, 1, 1, h_kv, w_kv) + + if self.attention_type[1] or self.attention_type[3]: + if self.attention_type[1] and self.attention_type[3]: + geom_bias = self.geom_bias.\ + view(1, num_heads, 1, self.qk_embed_dim) + + proj_query_reshape = (proj_query + geom_bias).\ + view(n, num_heads, h, w, self.qk_embed_dim) + + energy_x = torch.matmul( + proj_query_reshape.permute(0, 1, 3, 2, 4), + position_feat_x.permute(0, 1, 2, 4, 3)) + energy_x = energy_x.\ + permute(0, 1, 3, 2, 4).unsqueeze(4) + + energy_y = torch.matmul( + proj_query_reshape, + position_feat_y.permute(0, 1, 2, 4, 3)) + energy_y = energy_y.unsqueeze(5) + + energy += energy_x + energy_y + + elif self.attention_type[1]: + proj_query_reshape = proj_query.\ + view(n, num_heads, h, w, self.qk_embed_dim) + proj_query_reshape = proj_query_reshape.\ + permute(0, 1, 3, 2, 4) + position_feat_x_reshape = position_feat_x.\ + permute(0, 1, 2, 4, 3) + position_feat_y_reshape = position_feat_y.\ + permute(0, 1, 2, 4, 3) + + energy_x = torch.matmul(proj_query_reshape, + position_feat_x_reshape) + energy_x = energy_x.permute(0, 1, 3, 2, 4).unsqueeze(4) + + energy_y = torch.matmul(proj_query_reshape, + position_feat_y_reshape) + energy_y = energy_y.unsqueeze(5) + + energy += energy_x + energy_y + + elif self.attention_type[3]: + geom_bias = self.geom_bias.\ + view(1, num_heads, self.qk_embed_dim, 1).\ + repeat(n, 1, 1, 1) + + position_feat_x_reshape = position_feat_x.\ + view(n, num_heads, w*w_kv, self.qk_embed_dim) + + position_feat_y_reshape = position_feat_y.\ + view(n, num_heads, h * h_kv, self.qk_embed_dim) + + energy_x = torch.matmul(position_feat_x_reshape, geom_bias) + energy_x = energy_x.view(n, num_heads, 1, w, 1, w_kv) + + energy_y = torch.matmul(position_feat_y_reshape, geom_bias) + energy_y = energy_y.view(n, num_heads, h, 1, h_kv, 1) + + energy += energy_x + energy_y + + energy = energy.view(n, num_heads, h * w, h_kv * w_kv) + + if self.spatial_range >= 0: + cur_local_constraint_map = \ + self.local_constraint_map[:h, :w, :h_kv, :w_kv].\ + contiguous().\ + view(1, 1, h*w, h_kv*w_kv) + + energy = energy.masked_fill_(cur_local_constraint_map, + float('-inf')) + + attention = F.softmax(energy, 3) + + proj_value = self.value_conv(x_kv) + proj_value_reshape = proj_value.\ + view((n, num_heads, self.v_dim, h_kv * w_kv)).\ + permute(0, 1, 3, 2) + + out = torch.matmul(attention, proj_value_reshape).\ + permute(0, 1, 3, 2).\ + contiguous().\ + view(n, self.v_dim * self.num_heads, h, w) + + out = self.proj_conv(out) + + # output is downsampled, upsample back to input size + if self.q_downsample is not None: + out = F.interpolate( + out, + size=x_input.shape[2:], + mode='bilinear', + align_corners=False) + + out = self.gamma * out + x_input + return out + + def init_weights(self): + for m in self.modules(): + if hasattr(m, 'kaiming_init') and m.kaiming_init: + kaiming_init( + m, + mode='fan_in', + nonlinearity='leaky_relu', + bias=0, + distribution='uniform', + a=1) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/hsigmoid.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/hsigmoid.py new file mode 100644 index 0000000..30b1a3d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/hsigmoid.py @@ -0,0 +1,34 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn + +from .registry import ACTIVATION_LAYERS + + +@ACTIVATION_LAYERS.register_module() +class HSigmoid(nn.Module): + """Hard Sigmoid Module. Apply the hard sigmoid function: + Hsigmoid(x) = min(max((x + bias) / divisor, min_value), max_value) + Default: Hsigmoid(x) = min(max((x + 1) / 2, 0), 1) + + Args: + bias (float): Bias of the input feature map. Default: 1.0. + divisor (float): Divisor of the input feature map. Default: 2.0. + min_value (float): Lower bound value. Default: 0.0. + max_value (float): Upper bound value. Default: 1.0. + + Returns: + Tensor: The output tensor. + """ + + def __init__(self, bias=1.0, divisor=2.0, min_value=0.0, max_value=1.0): + super(HSigmoid, self).__init__() + self.bias = bias + self.divisor = divisor + assert self.divisor != 0 + self.min_value = min_value + self.max_value = max_value + + def forward(self, x): + x = (x + self.bias) / self.divisor + + return x.clamp_(self.min_value, self.max_value) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/hswish.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/hswish.py new file mode 100644 index 0000000..7e0c090 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/hswish.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn + +from .registry import ACTIVATION_LAYERS + + +@ACTIVATION_LAYERS.register_module() +class HSwish(nn.Module): + """Hard Swish Module. + + This module applies the hard swish function: + + .. math:: + Hswish(x) = x * ReLU6(x + 3) / 6 + + Args: + inplace (bool): can optionally do the operation in-place. + Default: False. + + Returns: + Tensor: The output tensor. + """ + + def __init__(self, inplace=False): + super(HSwish, self).__init__() + self.act = nn.ReLU6(inplace) + + def forward(self, x): + return x * self.act(x + 3) / 6 diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/non_local.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/non_local.py new file mode 100644 index 0000000..92d0015 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/non_local.py @@ -0,0 +1,306 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta + +import torch +import torch.nn as nn + +from ..utils import constant_init, normal_init +from .conv_module import ConvModule +from .registry import PLUGIN_LAYERS + + +class _NonLocalNd(nn.Module, metaclass=ABCMeta): + """Basic Non-local module. + + This module is proposed in + "Non-local Neural Networks" + Paper reference: https://arxiv.org/abs/1711.07971 + Code reference: https://github.com/AlexHex7/Non-local_pytorch + + Args: + in_channels (int): Channels of the input feature map. + reduction (int): Channel reduction ratio. Default: 2. + use_scale (bool): Whether to scale pairwise_weight by + `1/sqrt(inter_channels)` when the mode is `embedded_gaussian`. + Default: True. + conv_cfg (None | dict): The config dict for convolution layers. + If not specified, it will use `nn.Conv2d` for convolution layers. + Default: None. + norm_cfg (None | dict): The config dict for normalization layers. + Default: None. (This parameter is only applicable to conv_out.) + mode (str): Options are `gaussian`, `concatenation`, + `embedded_gaussian` and `dot_product`. Default: embedded_gaussian. + """ + + def __init__(self, + in_channels, + reduction=2, + use_scale=True, + conv_cfg=None, + norm_cfg=None, + mode='embedded_gaussian', + **kwargs): + super(_NonLocalNd, self).__init__() + self.in_channels = in_channels + self.reduction = reduction + self.use_scale = use_scale + self.inter_channels = max(in_channels // reduction, 1) + self.mode = mode + + if mode not in [ + 'gaussian', 'embedded_gaussian', 'dot_product', 'concatenation' + ]: + raise ValueError("Mode should be in 'gaussian', 'concatenation', " + f"'embedded_gaussian' or 'dot_product', but got " + f'{mode} instead.') + + # g, theta, phi are defaulted as `nn.ConvNd`. + # Here we use ConvModule for potential usage. + self.g = ConvModule( + self.in_channels, + self.inter_channels, + kernel_size=1, + conv_cfg=conv_cfg, + act_cfg=None) + self.conv_out = ConvModule( + self.inter_channels, + self.in_channels, + kernel_size=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + if self.mode != 'gaussian': + self.theta = ConvModule( + self.in_channels, + self.inter_channels, + kernel_size=1, + conv_cfg=conv_cfg, + act_cfg=None) + self.phi = ConvModule( + self.in_channels, + self.inter_channels, + kernel_size=1, + conv_cfg=conv_cfg, + act_cfg=None) + + if self.mode == 'concatenation': + self.concat_project = ConvModule( + self.inter_channels * 2, + 1, + kernel_size=1, + stride=1, + padding=0, + bias=False, + act_cfg=dict(type='ReLU')) + + self.init_weights(**kwargs) + + def init_weights(self, std=0.01, zeros_init=True): + if self.mode != 'gaussian': + for m in [self.g, self.theta, self.phi]: + normal_init(m.conv, std=std) + else: + normal_init(self.g.conv, std=std) + if zeros_init: + if self.conv_out.norm_cfg is None: + constant_init(self.conv_out.conv, 0) + else: + constant_init(self.conv_out.norm, 0) + else: + if self.conv_out.norm_cfg is None: + normal_init(self.conv_out.conv, std=std) + else: + normal_init(self.conv_out.norm, std=std) + + def gaussian(self, theta_x, phi_x): + # NonLocal1d pairwise_weight: [N, H, H] + # NonLocal2d pairwise_weight: [N, HxW, HxW] + # NonLocal3d pairwise_weight: [N, TxHxW, TxHxW] + pairwise_weight = torch.matmul(theta_x, phi_x) + pairwise_weight = pairwise_weight.softmax(dim=-1) + return pairwise_weight + + def embedded_gaussian(self, theta_x, phi_x): + # NonLocal1d pairwise_weight: [N, H, H] + # NonLocal2d pairwise_weight: [N, HxW, HxW] + # NonLocal3d pairwise_weight: [N, TxHxW, TxHxW] + pairwise_weight = torch.matmul(theta_x, phi_x) + if self.use_scale: + # theta_x.shape[-1] is `self.inter_channels` + pairwise_weight /= theta_x.shape[-1]**0.5 + pairwise_weight = pairwise_weight.softmax(dim=-1) + return pairwise_weight + + def dot_product(self, theta_x, phi_x): + # NonLocal1d pairwise_weight: [N, H, H] + # NonLocal2d pairwise_weight: [N, HxW, HxW] + # NonLocal3d pairwise_weight: [N, TxHxW, TxHxW] + pairwise_weight = torch.matmul(theta_x, phi_x) + pairwise_weight /= pairwise_weight.shape[-1] + return pairwise_weight + + def concatenation(self, theta_x, phi_x): + # NonLocal1d pairwise_weight: [N, H, H] + # NonLocal2d pairwise_weight: [N, HxW, HxW] + # NonLocal3d pairwise_weight: [N, TxHxW, TxHxW] + h = theta_x.size(2) + w = phi_x.size(3) + theta_x = theta_x.repeat(1, 1, 1, w) + phi_x = phi_x.repeat(1, 1, h, 1) + + concat_feature = torch.cat([theta_x, phi_x], dim=1) + pairwise_weight = self.concat_project(concat_feature) + n, _, h, w = pairwise_weight.size() + pairwise_weight = pairwise_weight.view(n, h, w) + pairwise_weight /= pairwise_weight.shape[-1] + + return pairwise_weight + + def forward(self, x): + # Assume `reduction = 1`, then `inter_channels = C` + # or `inter_channels = C` when `mode="gaussian"` + + # NonLocal1d x: [N, C, H] + # NonLocal2d x: [N, C, H, W] + # NonLocal3d x: [N, C, T, H, W] + n = x.size(0) + + # NonLocal1d g_x: [N, H, C] + # NonLocal2d g_x: [N, HxW, C] + # NonLocal3d g_x: [N, TxHxW, C] + g_x = self.g(x).view(n, self.inter_channels, -1) + g_x = g_x.permute(0, 2, 1) + + # NonLocal1d theta_x: [N, H, C], phi_x: [N, C, H] + # NonLocal2d theta_x: [N, HxW, C], phi_x: [N, C, HxW] + # NonLocal3d theta_x: [N, TxHxW, C], phi_x: [N, C, TxHxW] + 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) + + pairwise_func = getattr(self, self.mode) + # NonLocal1d pairwise_weight: [N, H, H] + # NonLocal2d pairwise_weight: [N, HxW, HxW] + # NonLocal3d pairwise_weight: [N, TxHxW, TxHxW] + pairwise_weight = pairwise_func(theta_x, phi_x) + + # NonLocal1d y: [N, H, C] + # NonLocal2d y: [N, HxW, C] + # NonLocal3d y: [N, TxHxW, C] + y = torch.matmul(pairwise_weight, g_x) + # NonLocal1d y: [N, C, H] + # NonLocal2d y: [N, C, H, W] + # NonLocal3d y: [N, C, T, H, W] + y = y.permute(0, 2, 1).contiguous().reshape(n, self.inter_channels, + *x.size()[2:]) + + output = x + self.conv_out(y) + + return output + + +class NonLocal1d(_NonLocalNd): + """1D Non-local module. + + Args: + in_channels (int): Same as `NonLocalND`. + sub_sample (bool): Whether to apply max pooling after pairwise + function (Note that the `sub_sample` is applied on spatial only). + Default: False. + conv_cfg (None | dict): Same as `NonLocalND`. + Default: dict(type='Conv1d'). + """ + + def __init__(self, + in_channels, + sub_sample=False, + conv_cfg=dict(type='Conv1d'), + **kwargs): + super(NonLocal1d, self).__init__( + in_channels, conv_cfg=conv_cfg, **kwargs) + + self.sub_sample = sub_sample + + if sub_sample: + max_pool_layer = nn.MaxPool1d(kernel_size=2) + self.g = nn.Sequential(self.g, max_pool_layer) + if self.mode != 'gaussian': + self.phi = nn.Sequential(self.phi, max_pool_layer) + else: + self.phi = max_pool_layer + + +@PLUGIN_LAYERS.register_module() +class NonLocal2d(_NonLocalNd): + """2D Non-local module. + + Args: + in_channels (int): Same as `NonLocalND`. + sub_sample (bool): Whether to apply max pooling after pairwise + function (Note that the `sub_sample` is applied on spatial only). + Default: False. + conv_cfg (None | dict): Same as `NonLocalND`. + Default: dict(type='Conv2d'). + """ + + _abbr_ = 'nonlocal_block' + + def __init__(self, + in_channels, + sub_sample=False, + conv_cfg=dict(type='Conv2d'), + **kwargs): + super(NonLocal2d, self).__init__( + in_channels, conv_cfg=conv_cfg, **kwargs) + + self.sub_sample = sub_sample + + if sub_sample: + max_pool_layer = nn.MaxPool2d(kernel_size=(2, 2)) + self.g = nn.Sequential(self.g, max_pool_layer) + if self.mode != 'gaussian': + self.phi = nn.Sequential(self.phi, max_pool_layer) + else: + self.phi = max_pool_layer + + +class NonLocal3d(_NonLocalNd): + """3D Non-local module. + + Args: + in_channels (int): Same as `NonLocalND`. + sub_sample (bool): Whether to apply max pooling after pairwise + function (Note that the `sub_sample` is applied on spatial only). + Default: False. + conv_cfg (None | dict): Same as `NonLocalND`. + Default: dict(type='Conv3d'). + """ + + def __init__(self, + in_channels, + sub_sample=False, + conv_cfg=dict(type='Conv3d'), + **kwargs): + super(NonLocal3d, self).__init__( + in_channels, conv_cfg=conv_cfg, **kwargs) + self.sub_sample = sub_sample + + if sub_sample: + max_pool_layer = nn.MaxPool3d(kernel_size=(1, 2, 2)) + self.g = nn.Sequential(self.g, max_pool_layer) + if self.mode != 'gaussian': + self.phi = nn.Sequential(self.phi, max_pool_layer) + else: + self.phi = max_pool_layer diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/norm.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/norm.py new file mode 100644 index 0000000..408f4b4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/norm.py @@ -0,0 +1,144 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import inspect + +import torch.nn as nn + +from annotator.uniformer.mmcv.utils import is_tuple_of +from annotator.uniformer.mmcv.utils.parrots_wrapper import SyncBatchNorm, _BatchNorm, _InstanceNorm +from .registry import NORM_LAYERS + +NORM_LAYERS.register_module('BN', module=nn.BatchNorm2d) +NORM_LAYERS.register_module('BN1d', module=nn.BatchNorm1d) +NORM_LAYERS.register_module('BN2d', module=nn.BatchNorm2d) +NORM_LAYERS.register_module('BN3d', module=nn.BatchNorm3d) +NORM_LAYERS.register_module('SyncBN', module=SyncBatchNorm) +NORM_LAYERS.register_module('GN', module=nn.GroupNorm) +NORM_LAYERS.register_module('LN', module=nn.LayerNorm) +NORM_LAYERS.register_module('IN', module=nn.InstanceNorm2d) +NORM_LAYERS.register_module('IN1d', module=nn.InstanceNorm1d) +NORM_LAYERS.register_module('IN2d', module=nn.InstanceNorm2d) +NORM_LAYERS.register_module('IN3d', module=nn.InstanceNorm3d) + + +def infer_abbr(class_type): + """Infer abbreviation from the class name. + + When we build a norm layer with `build_norm_layer()`, we want to preserve + the norm type in variable names, e.g, self.bn1, self.gn. This method will + infer the abbreviation to map class types to abbreviations. + + Rule 1: If the class has the property "_abbr_", return the property. + Rule 2: If the parent class is _BatchNorm, GroupNorm, LayerNorm or + InstanceNorm, the abbreviation of this layer will be "bn", "gn", "ln" and + "in" respectively. + Rule 3: If the class name contains "batch", "group", "layer" or "instance", + the abbreviation of this layer will be "bn", "gn", "ln" and "in" + respectively. + Rule 4: Otherwise, the abbreviation falls back to "norm". + + Args: + class_type (type): The norm layer type. + + Returns: + str: The inferred abbreviation. + """ + if not inspect.isclass(class_type): + raise TypeError( + f'class_type must be a type, but got {type(class_type)}') + if hasattr(class_type, '_abbr_'): + return class_type._abbr_ + if issubclass(class_type, _InstanceNorm): # IN is a subclass of BN + return 'in' + elif issubclass(class_type, _BatchNorm): + return 'bn' + elif issubclass(class_type, nn.GroupNorm): + return 'gn' + elif issubclass(class_type, nn.LayerNorm): + return 'ln' + else: + class_name = class_type.__name__.lower() + if 'batch' in class_name: + return 'bn' + elif 'group' in class_name: + return 'gn' + elif 'layer' in class_name: + return 'ln' + elif 'instance' in class_name: + return 'in' + else: + return 'norm_layer' + + +def build_norm_layer(cfg, num_features, postfix=''): + """Build normalization layer. + + Args: + cfg (dict): The norm layer config, which should contain: + + - type (str): Layer type. + - layer args: Args needed to instantiate a norm layer. + - requires_grad (bool, optional): Whether stop gradient updates. + num_features (int): Number of input channels. + postfix (int | str): The postfix to be appended into norm abbreviation + to create named layer. + + Returns: + (str, nn.Module): The first element is the layer name consisting of + abbreviation and postfix, e.g., bn1, gn. The second element is the + created norm layer. + """ + if not isinstance(cfg, dict): + raise TypeError('cfg must be a dict') + if 'type' not in cfg: + raise KeyError('the cfg dict must contain the key "type"') + cfg_ = cfg.copy() + + layer_type = cfg_.pop('type') + if layer_type not in NORM_LAYERS: + raise KeyError(f'Unrecognized norm type {layer_type}') + + norm_layer = NORM_LAYERS.get(layer_type) + abbr = infer_abbr(norm_layer) + + assert isinstance(postfix, (int, str)) + name = abbr + str(postfix) + + requires_grad = cfg_.pop('requires_grad', True) + cfg_.setdefault('eps', 1e-5) + if layer_type != 'GN': + layer = norm_layer(num_features, **cfg_) + if layer_type == 'SyncBN' and hasattr(layer, '_specify_ddp_gpu_num'): + layer._specify_ddp_gpu_num(1) + else: + assert 'num_groups' in cfg_ + layer = norm_layer(num_channels=num_features, **cfg_) + + for param in layer.parameters(): + param.requires_grad = requires_grad + + return name, layer + + +def is_norm(layer, exclude=None): + """Check if a layer is a normalization layer. + + Args: + layer (nn.Module): The layer to be checked. + exclude (type | tuple[type]): Types to be excluded. + + Returns: + bool: Whether the layer is a norm layer. + """ + if exclude is not None: + if not isinstance(exclude, tuple): + exclude = (exclude, ) + if not is_tuple_of(exclude, type): + raise TypeError( + f'"exclude" must be either None or type or a tuple of types, ' + f'but got {type(exclude)}: {exclude}') + + if exclude and isinstance(layer, exclude): + return False + + all_norm_bases = (_BatchNorm, _InstanceNorm, nn.GroupNorm, nn.LayerNorm) + return isinstance(layer, all_norm_bases) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/padding.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/padding.py new file mode 100644 index 0000000..e4ac6b2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/padding.py @@ -0,0 +1,36 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn + +from .registry import PADDING_LAYERS + +PADDING_LAYERS.register_module('zero', module=nn.ZeroPad2d) +PADDING_LAYERS.register_module('reflect', module=nn.ReflectionPad2d) +PADDING_LAYERS.register_module('replicate', module=nn.ReplicationPad2d) + + +def build_padding_layer(cfg, *args, **kwargs): + """Build padding layer. + + Args: + cfg (None or dict): The padding layer config, which should contain: + - type (str): Layer type. + - layer args: Args needed to instantiate a padding layer. + + Returns: + nn.Module: Created padding layer. + """ + if not isinstance(cfg, dict): + raise TypeError('cfg must be a dict') + if 'type' not in cfg: + raise KeyError('the cfg dict must contain the key "type"') + + cfg_ = cfg.copy() + padding_type = cfg_.pop('type') + if padding_type not in PADDING_LAYERS: + raise KeyError(f'Unrecognized padding type {padding_type}.') + else: + padding_layer = PADDING_LAYERS.get(padding_type) + + layer = padding_layer(*args, **kwargs, **cfg_) + + return layer diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/plugin.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/plugin.py new file mode 100644 index 0000000..07c010d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/plugin.py @@ -0,0 +1,88 @@ +import inspect +import platform + +from .registry import PLUGIN_LAYERS + +if platform.system() == 'Windows': + import regex as re +else: + import re + + +def infer_abbr(class_type): + """Infer abbreviation from the class name. + + This method will infer the abbreviation to map class types to + abbreviations. + + Rule 1: If the class has the property "abbr", return the property. + Rule 2: Otherwise, the abbreviation falls back to snake case of class + name, e.g. the abbreviation of ``FancyBlock`` will be ``fancy_block``. + + Args: + class_type (type): The norm layer type. + + Returns: + str: The inferred abbreviation. + """ + + def camel2snack(word): + """Convert camel case word into snack case. + + Modified from `inflection lib + `_. + + Example:: + + >>> camel2snack("FancyBlock") + 'fancy_block' + """ + + word = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', word) + word = re.sub(r'([a-z\d])([A-Z])', r'\1_\2', word) + word = word.replace('-', '_') + return word.lower() + + if not inspect.isclass(class_type): + raise TypeError( + f'class_type must be a type, but got {type(class_type)}') + if hasattr(class_type, '_abbr_'): + return class_type._abbr_ + else: + return camel2snack(class_type.__name__) + + +def build_plugin_layer(cfg, postfix='', **kwargs): + """Build plugin layer. + + Args: + cfg (None or dict): cfg should contain: + type (str): identify plugin layer type. + layer args: args needed to instantiate a plugin layer. + postfix (int, str): appended into norm abbreviation to + create named layer. Default: ''. + + Returns: + tuple[str, nn.Module]: + name (str): abbreviation + postfix + layer (nn.Module): created plugin layer + """ + if not isinstance(cfg, dict): + raise TypeError('cfg must be a dict') + if 'type' not in cfg: + raise KeyError('the cfg dict must contain the key "type"') + cfg_ = cfg.copy() + + layer_type = cfg_.pop('type') + if layer_type not in PLUGIN_LAYERS: + raise KeyError(f'Unrecognized plugin type {layer_type}') + + plugin_layer = PLUGIN_LAYERS.get(layer_type) + abbr = infer_abbr(plugin_layer) + + assert isinstance(postfix, (int, str)) + name = abbr + str(postfix) + + layer = plugin_layer(**kwargs, **cfg_) + + return name, layer diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/registry.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/registry.py new file mode 100644 index 0000000..39eabc5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/registry.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from annotator.uniformer.mmcv.utils import Registry + +CONV_LAYERS = Registry('conv layer') +NORM_LAYERS = Registry('norm layer') +ACTIVATION_LAYERS = Registry('activation layer') +PADDING_LAYERS = Registry('padding layer') +UPSAMPLE_LAYERS = Registry('upsample layer') +PLUGIN_LAYERS = Registry('plugin layer') + +DROPOUT_LAYERS = Registry('drop out layers') +POSITIONAL_ENCODING = Registry('position encoding') +ATTENTION = Registry('attention') +FEEDFORWARD_NETWORK = Registry('feed-forward Network') +TRANSFORMER_LAYER = Registry('transformerLayer') +TRANSFORMER_LAYER_SEQUENCE = Registry('transformer-layers sequence') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/scale.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/scale.py new file mode 100644 index 0000000..c905fff --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/scale.py @@ -0,0 +1,21 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn + + +class Scale(nn.Module): + """A learnable scale parameter. + + This layer scales the input by a learnable factor. It multiplies a + learnable scale parameter of shape (1,) with input of any shape. + + Args: + scale (float): Initial value of scale factor. Default: 1.0 + """ + + def __init__(self, scale=1.0): + super(Scale, self).__init__() + self.scale = nn.Parameter(torch.tensor(scale, dtype=torch.float)) + + def forward(self, x): + return x * self.scale diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/swish.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/swish.py new file mode 100644 index 0000000..e2ca8ed --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/swish.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn + +from .registry import ACTIVATION_LAYERS + + +@ACTIVATION_LAYERS.register_module() +class Swish(nn.Module): + """Swish Module. + + This module applies the swish function: + + .. math:: + Swish(x) = x * Sigmoid(x) + + Returns: + Tensor: The output tensor. + """ + + def __init__(self): + super(Swish, self).__init__() + + def forward(self, x): + return x * torch.sigmoid(x) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/transformer.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/transformer.py new file mode 100644 index 0000000..e61ae0d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/transformer.py @@ -0,0 +1,595 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings + +import torch +import torch.nn as nn + +from annotator.uniformer.mmcv import ConfigDict, deprecated_api_warning +from annotator.uniformer.mmcv.cnn import Linear, build_activation_layer, build_norm_layer +from annotator.uniformer.mmcv.runner.base_module import BaseModule, ModuleList, Sequential +from annotator.uniformer.mmcv.utils import build_from_cfg +from .drop import build_dropout +from .registry import (ATTENTION, FEEDFORWARD_NETWORK, POSITIONAL_ENCODING, + TRANSFORMER_LAYER, TRANSFORMER_LAYER_SEQUENCE) + +# Avoid BC-breaking of importing MultiScaleDeformableAttention from this file +try: + from annotator.uniformer.mmcv.ops.multi_scale_deform_attn import MultiScaleDeformableAttention # noqa F401 + warnings.warn( + ImportWarning( + '``MultiScaleDeformableAttention`` has been moved to ' + '``mmcv.ops.multi_scale_deform_attn``, please change original path ' # noqa E501 + '``from annotator.uniformer.mmcv.cnn.bricks.transformer import MultiScaleDeformableAttention`` ' # noqa E501 + 'to ``from annotator.uniformer.mmcv.ops.multi_scale_deform_attn import MultiScaleDeformableAttention`` ' # noqa E501 + )) + +except ImportError: + warnings.warn('Fail to import ``MultiScaleDeformableAttention`` from ' + '``mmcv.ops.multi_scale_deform_attn``, ' + 'You should install ``mmcv-full`` if you need this module. ') + + +def build_positional_encoding(cfg, default_args=None): + """Builder for Position Encoding.""" + return build_from_cfg(cfg, POSITIONAL_ENCODING, default_args) + + +def build_attention(cfg, default_args=None): + """Builder for attention.""" + return build_from_cfg(cfg, ATTENTION, default_args) + + +def build_feedforward_network(cfg, default_args=None): + """Builder for feed-forward network (FFN).""" + return build_from_cfg(cfg, FEEDFORWARD_NETWORK, default_args) + + +def build_transformer_layer(cfg, default_args=None): + """Builder for transformer layer.""" + return build_from_cfg(cfg, TRANSFORMER_LAYER, default_args) + + +def build_transformer_layer_sequence(cfg, default_args=None): + """Builder for transformer encoder and transformer decoder.""" + return build_from_cfg(cfg, TRANSFORMER_LAYER_SEQUENCE, default_args) + + +@ATTENTION.register_module() +class MultiheadAttention(BaseModule): + """A wrapper for ``torch.nn.MultiheadAttention``. + + This module implements MultiheadAttention with identity connection, + and positional encoding is also passed as input. + + 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. + init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization. + Default: None. + batch_first (bool): When it is True, Key, Query and Value are shape of + (batch, n, embed_dim), otherwise (n, batch, embed_dim). + Default to False. + """ + + def __init__(self, + embed_dims, + num_heads, + attn_drop=0., + proj_drop=0., + dropout_layer=dict(type='Dropout', drop_prob=0.), + init_cfg=None, + batch_first=False, + **kwargs): + super(MultiheadAttention, self).__init__(init_cfg) + if 'dropout' in kwargs: + warnings.warn('The arguments `dropout` in MultiheadAttention ' + 'has been deprecated, now you can separately ' + 'set `attn_drop`(float), proj_drop(float), ' + 'and `dropout_layer`(dict) ') + attn_drop = kwargs['dropout'] + dropout_layer['drop_prob'] = kwargs.pop('dropout') + + self.embed_dims = embed_dims + self.num_heads = num_heads + self.batch_first = batch_first + + self.attn = nn.MultiheadAttention(embed_dims, num_heads, attn_drop, + **kwargs) + + self.proj_drop = nn.Dropout(proj_drop) + self.dropout_layer = build_dropout( + dropout_layer) if dropout_layer else nn.Identity() + + @deprecated_api_warning({'residual': 'identity'}, + cls_name='MultiheadAttention') + def forward(self, + query, + key=None, + value=None, + identity=None, + query_pos=None, + key_pos=None, + attn_mask=None, + key_padding_mask=None, + **kwargs): + """Forward function for `MultiheadAttention`. + + **kwargs allow passing a more general data flow when combining + with other operations in `transformerlayer`. + + Args: + query (Tensor): The input query with shape [num_queries, bs, + embed_dims] if self.batch_first is False, else + [bs, num_queries embed_dims]. + key (Tensor): The key tensor with shape [num_keys, bs, + embed_dims] if self.batch_first is False, else + [bs, num_keys, embed_dims] . + If None, the ``query`` will be used. Defaults to None. + value (Tensor): The value tensor with same shape as `key`. + Same in `nn.MultiheadAttention.forward`. Defaults to None. + If None, the `key` will be used. + identity (Tensor): This tensor, with the same shape as x, + will be used for the identity link. + If None, `x` will be used. Defaults to None. + query_pos (Tensor): The positional encoding for query, with + the same shape as `x`. If not None, it will + be added to `x` before forward function. Defaults to None. + key_pos (Tensor): The positional encoding for `key`, with the + same shape as `key`. Defaults to None. If not None, it will + be added to `key` before forward function. If None, and + `query_pos` has the same shape as `key`, then `query_pos` + will be used for `key_pos`. Defaults to None. + attn_mask (Tensor): ByteTensor mask with shape [num_queries, + num_keys]. Same in `nn.MultiheadAttention.forward`. + Defaults to None. + key_padding_mask (Tensor): ByteTensor with shape [bs, num_keys]. + Defaults to None. + + Returns: + Tensor: forwarded results with shape + [num_queries, bs, embed_dims] + if self.batch_first is False, else + [bs, num_queries embed_dims]. + """ + + if key is None: + key = query + if value is None: + value = key + if identity is None: + identity = query + if key_pos is None: + if query_pos is not None: + # use query_pos if key_pos is not available + if query_pos.shape == key.shape: + key_pos = query_pos + else: + warnings.warn(f'position encoding of key is' + f'missing in {self.__class__.__name__}.') + if query_pos is not None: + query = query + query_pos + if key_pos is not None: + key = key + key_pos + + # 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: + query = query.transpose(0, 1) + key = key.transpose(0, 1) + value = value.transpose(0, 1) + + out = self.attn( + query=query, + key=key, + value=value, + attn_mask=attn_mask, + key_padding_mask=key_padding_mask)[0] + + if self.batch_first: + out = out.transpose(0, 1) + + return identity + self.dropout_layer(self.proj_drop(out)) + + +@FEEDFORWARD_NETWORK.register_module() +class FFN(BaseModule): + """Implements feed-forward networks (FFNs) with identity connection. + + Args: + embed_dims (int): The feature dimension. Same as + `MultiheadAttention`. Defaults: 256. + feedforward_channels (int): The hidden dimension of FFNs. + Defaults: 1024. + num_fcs (int, optional): The number of fully-connected layers in + FFNs. Default: 2. + 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. + add_identity (bool, optional): Whether to add the + identity connection. Default: `True`. + dropout_layer (obj:`ConfigDict`): The dropout_layer used + when adding the shortcut. + init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization. + Default: None. + """ + + @deprecated_api_warning( + { + 'dropout': 'ffn_drop', + 'add_residual': 'add_identity' + }, + cls_name='FFN') + def __init__(self, + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + act_cfg=dict(type='ReLU', inplace=True), + ffn_drop=0., + dropout_layer=None, + add_identity=True, + init_cfg=None, + **kwargs): + super(FFN, self).__init__(init_cfg) + assert num_fcs >= 2, 'num_fcs should be no less ' \ + f'than 2. got {num_fcs}.' + self.embed_dims = embed_dims + self.feedforward_channels = feedforward_channels + self.num_fcs = num_fcs + self.act_cfg = act_cfg + self.activate = build_activation_layer(act_cfg) + + layers = [] + in_channels = embed_dims + for _ in range(num_fcs - 1): + layers.append( + Sequential( + Linear(in_channels, feedforward_channels), self.activate, + nn.Dropout(ffn_drop))) + in_channels = feedforward_channels + layers.append(Linear(feedforward_channels, embed_dims)) + layers.append(nn.Dropout(ffn_drop)) + self.layers = Sequential(*layers) + self.dropout_layer = build_dropout( + dropout_layer) if dropout_layer else torch.nn.Identity() + self.add_identity = add_identity + + @deprecated_api_warning({'residual': 'identity'}, cls_name='FFN') + def forward(self, x, identity=None): + """Forward function for `FFN`. + + The function would add x to the output tensor if residue is None. + """ + out = self.layers(x) + if not self.add_identity: + return self.dropout_layer(out) + if identity is None: + identity = x + return identity + self.dropout_layer(out) + + +@TRANSFORMER_LAYER.register_module() +class BaseTransformerLayer(BaseModule): + """Base `TransformerLayer` for vision transformer. + + It can be built from `mmcv.ConfigDict` and support more flexible + customization, for example, using any number of `FFN or LN ` and + use different kinds of `attention` by specifying a list of `ConfigDict` + named `attn_cfgs`. It is worth mentioning that it supports `prenorm` + when you specifying `norm` as the first element of `operation_order`. + More details about the `prenorm`: `On Layer Normalization in the + Transformer Architecture `_ . + + Args: + attn_cfgs (list[`mmcv.ConfigDict`] | obj:`mmcv.ConfigDict` | None )): + Configs for `self_attention` or `cross_attention` modules, + The order of the configs in the list should be consistent with + corresponding attentions in operation_order. + If it is a dict, all of the attention modules in operation_order + will be built with this config. Default: None. + ffn_cfgs (list[`mmcv.ConfigDict`] | obj:`mmcv.ConfigDict` | None )): + Configs for FFN, The order of the configs in the list should be + consistent with corresponding ffn in operation_order. + If it is a dict, all of the attention modules in operation_order + will be built with this config. + operation_order (tuple[str]): The execution order of operation + in transformer. Such as ('self_attn', 'norm', 'ffn', 'norm'). + Support `prenorm` when you specifying first element as `norm`. + Default:None. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + 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 to False. + """ + + def __init__(self, + attn_cfgs=None, + ffn_cfgs=dict( + type='FFN', + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0., + act_cfg=dict(type='ReLU', inplace=True), + ), + operation_order=None, + norm_cfg=dict(type='LN'), + init_cfg=None, + batch_first=False, + **kwargs): + + deprecated_args = dict( + feedforward_channels='feedforward_channels', + ffn_dropout='ffn_drop', + ffn_num_fcs='num_fcs') + for ori_name, new_name in deprecated_args.items(): + if ori_name in kwargs: + warnings.warn( + f'The arguments `{ori_name}` in BaseTransformerLayer ' + f'has been deprecated, now you should set `{new_name}` ' + f'and other FFN related arguments ' + f'to a dict named `ffn_cfgs`. ') + ffn_cfgs[new_name] = kwargs[ori_name] + + super(BaseTransformerLayer, self).__init__(init_cfg) + + self.batch_first = batch_first + + assert set(operation_order) & set( + ['self_attn', 'norm', 'ffn', 'cross_attn']) == \ + set(operation_order), f'The operation_order of' \ + f' {self.__class__.__name__} should ' \ + f'contains all four operation type ' \ + f"{['self_attn', 'norm', 'ffn', 'cross_attn']}" + + num_attn = operation_order.count('self_attn') + operation_order.count( + 'cross_attn') + if isinstance(attn_cfgs, dict): + attn_cfgs = [copy.deepcopy(attn_cfgs) for _ in range(num_attn)] + else: + assert num_attn == len(attn_cfgs), f'The length ' \ + f'of attn_cfg {num_attn} is ' \ + f'not consistent with the number of attention' \ + f'in operation_order {operation_order}.' + + self.num_attn = num_attn + self.operation_order = operation_order + self.norm_cfg = norm_cfg + self.pre_norm = operation_order[0] == 'norm' + self.attentions = ModuleList() + + index = 0 + for operation_name in operation_order: + if operation_name in ['self_attn', 'cross_attn']: + if 'batch_first' in attn_cfgs[index]: + assert self.batch_first == attn_cfgs[index]['batch_first'] + else: + attn_cfgs[index]['batch_first'] = self.batch_first + attention = build_attention(attn_cfgs[index]) + # Some custom attentions used as `self_attn` + # or `cross_attn` can have different behavior. + attention.operation_name = operation_name + self.attentions.append(attention) + index += 1 + + self.embed_dims = self.attentions[0].embed_dims + + self.ffns = ModuleList() + num_ffns = operation_order.count('ffn') + if isinstance(ffn_cfgs, dict): + ffn_cfgs = ConfigDict(ffn_cfgs) + if isinstance(ffn_cfgs, dict): + ffn_cfgs = [copy.deepcopy(ffn_cfgs) for _ in range(num_ffns)] + assert len(ffn_cfgs) == num_ffns + for ffn_index in range(num_ffns): + if 'embed_dims' not in ffn_cfgs[ffn_index]: + ffn_cfgs['embed_dims'] = self.embed_dims + else: + assert ffn_cfgs[ffn_index]['embed_dims'] == self.embed_dims + self.ffns.append( + build_feedforward_network(ffn_cfgs[ffn_index], + dict(type='FFN'))) + + self.norms = ModuleList() + num_norms = operation_order.count('norm') + for _ in range(num_norms): + self.norms.append(build_norm_layer(norm_cfg, self.embed_dims)[1]) + + def forward(self, + query, + key=None, + value=None, + query_pos=None, + key_pos=None, + attn_masks=None, + query_key_padding_mask=None, + key_padding_mask=None, + **kwargs): + """Forward function for `TransformerDecoderLayer`. + + **kwargs contains some specific arguments of attentions. + + Args: + query (Tensor): The input query with shape + [num_queries, bs, embed_dims] if + self.batch_first is False, else + [bs, num_queries embed_dims]. + key (Tensor): The key tensor with shape [num_keys, bs, + embed_dims] if self.batch_first is False, else + [bs, num_keys, embed_dims] . + value (Tensor): The value tensor with same shape as `key`. + query_pos (Tensor): The positional encoding for `query`. + Default: None. + key_pos (Tensor): The positional encoding for `key`. + Default: None. + attn_masks (List[Tensor] | None): 2D Tensor used in + calculation of corresponding attention. The length of + it should equal to the number of `attention` in + `operation_order`. Default: None. + query_key_padding_mask (Tensor): ByteTensor for `query`, with + shape [bs, num_queries]. Only used in `self_attn` layer. + Defaults to None. + key_padding_mask (Tensor): ByteTensor for `query`, with + shape [bs, num_keys]. Default: None. + + Returns: + Tensor: forwarded results with shape [num_queries, bs, embed_dims]. + """ + + norm_index = 0 + attn_index = 0 + ffn_index = 0 + identity = query + if attn_masks is None: + attn_masks = [None for _ in range(self.num_attn)] + elif isinstance(attn_masks, torch.Tensor): + attn_masks = [ + copy.deepcopy(attn_masks) for _ in range(self.num_attn) + ] + warnings.warn(f'Use same attn_mask in all attentions in ' + f'{self.__class__.__name__} ') + else: + assert len(attn_masks) == self.num_attn, f'The length of ' \ + f'attn_masks {len(attn_masks)} must be equal ' \ + f'to the number of attention in ' \ + f'operation_order {self.num_attn}' + + for layer in self.operation_order: + if layer == 'self_attn': + temp_key = temp_value = query + query = self.attentions[attn_index]( + query, + temp_key, + temp_value, + identity if self.pre_norm else None, + query_pos=query_pos, + key_pos=query_pos, + attn_mask=attn_masks[attn_index], + key_padding_mask=query_key_padding_mask, + **kwargs) + attn_index += 1 + identity = query + + elif layer == 'norm': + query = self.norms[norm_index](query) + norm_index += 1 + + elif layer == 'cross_attn': + query = self.attentions[attn_index]( + query, + key, + value, + identity if self.pre_norm else None, + query_pos=query_pos, + key_pos=key_pos, + attn_mask=attn_masks[attn_index], + key_padding_mask=key_padding_mask, + **kwargs) + attn_index += 1 + identity = query + + elif layer == 'ffn': + query = self.ffns[ffn_index]( + query, identity if self.pre_norm else None) + ffn_index += 1 + + return query + + +@TRANSFORMER_LAYER_SEQUENCE.register_module() +class TransformerLayerSequence(BaseModule): + """Base class for TransformerEncoder and TransformerDecoder in vision + transformer. + + As base-class of Encoder and Decoder in vision transformer. + Support customization such as specifying different kind + of `transformer_layer` in `transformer_coder`. + + Args: + transformerlayer (list[obj:`mmcv.ConfigDict`] | + obj:`mmcv.ConfigDict`): Config of transformerlayer + in TransformerCoder. If it is obj:`mmcv.ConfigDict`, + it would be repeated `num_layer` times to a + list[`mmcv.ConfigDict`]. Default: None. + num_layers (int): The number of `TransformerLayer`. Default: None. + init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization. + Default: None. + """ + + def __init__(self, transformerlayers=None, num_layers=None, init_cfg=None): + super(TransformerLayerSequence, self).__init__(init_cfg) + if isinstance(transformerlayers, dict): + transformerlayers = [ + copy.deepcopy(transformerlayers) for _ in range(num_layers) + ] + else: + assert isinstance(transformerlayers, list) and \ + len(transformerlayers) == num_layers + self.num_layers = num_layers + self.layers = ModuleList() + for i in range(num_layers): + self.layers.append(build_transformer_layer(transformerlayers[i])) + self.embed_dims = self.layers[0].embed_dims + self.pre_norm = self.layers[0].pre_norm + + def forward(self, + query, + key, + value, + query_pos=None, + key_pos=None, + attn_masks=None, + query_key_padding_mask=None, + key_padding_mask=None, + **kwargs): + """Forward function for `TransformerCoder`. + + Args: + query (Tensor): Input query with shape + `(num_queries, bs, embed_dims)`. + key (Tensor): The key tensor with shape + `(num_keys, bs, embed_dims)`. + value (Tensor): The value tensor with shape + `(num_keys, bs, embed_dims)`. + query_pos (Tensor): The positional encoding for `query`. + Default: None. + key_pos (Tensor): The positional encoding for `key`. + Default: None. + attn_masks (List[Tensor], optional): Each element is 2D Tensor + which is used in calculation of corresponding attention in + operation_order. Default: None. + query_key_padding_mask (Tensor): ByteTensor for `query`, with + shape [bs, num_queries]. Only used in self-attention + Default: None. + key_padding_mask (Tensor): ByteTensor for `query`, with + shape [bs, num_keys]. Default: None. + + Returns: + Tensor: results with shape [num_queries, bs, embed_dims]. + """ + for layer in self.layers: + query = layer( + query, + key, + value, + query_pos=query_pos, + key_pos=key_pos, + attn_masks=attn_masks, + query_key_padding_mask=query_key_padding_mask, + key_padding_mask=key_padding_mask, + **kwargs) + return query diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/upsample.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/upsample.py new file mode 100644 index 0000000..a1a3537 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/upsample.py @@ -0,0 +1,84 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +import torch.nn.functional as F + +from ..utils import xavier_init +from .registry import UPSAMPLE_LAYERS + +UPSAMPLE_LAYERS.register_module('nearest', module=nn.Upsample) +UPSAMPLE_LAYERS.register_module('bilinear', module=nn.Upsample) + + +@UPSAMPLE_LAYERS.register_module(name='pixel_shuffle') +class PixelShufflePack(nn.Module): + """Pixel Shuffle upsample layer. + + This module packs `F.pixel_shuffle()` and a nn.Conv2d module together to + achieve a simple upsampling with pixel shuffle. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + scale_factor (int): Upsample ratio. + upsample_kernel (int): Kernel size of the conv layer to expand the + channels. + """ + + def __init__(self, in_channels, out_channels, scale_factor, + upsample_kernel): + super(PixelShufflePack, self).__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.scale_factor = scale_factor + self.upsample_kernel = upsample_kernel + self.upsample_conv = nn.Conv2d( + self.in_channels, + self.out_channels * scale_factor * scale_factor, + self.upsample_kernel, + padding=(self.upsample_kernel - 1) // 2) + self.init_weights() + + def init_weights(self): + xavier_init(self.upsample_conv, distribution='uniform') + + def forward(self, x): + x = self.upsample_conv(x) + x = F.pixel_shuffle(x, self.scale_factor) + return x + + +def build_upsample_layer(cfg, *args, **kwargs): + """Build upsample layer. + + Args: + cfg (dict): The upsample layer config, which should contain: + + - type (str): Layer type. + - scale_factor (int): Upsample ratio, which is not applicable to + deconv. + - layer args: Args needed to instantiate a upsample layer. + args (argument list): Arguments passed to the ``__init__`` + method of the corresponding conv layer. + kwargs (keyword arguments): Keyword arguments passed to the + ``__init__`` method of the corresponding conv layer. + + Returns: + nn.Module: Created upsample layer. + """ + if not isinstance(cfg, dict): + raise TypeError(f'cfg must be a dict, but got {type(cfg)}') + if 'type' not in cfg: + raise KeyError( + f'the cfg dict must contain the key "type", but got {cfg}') + cfg_ = cfg.copy() + + layer_type = cfg_.pop('type') + if layer_type not in UPSAMPLE_LAYERS: + raise KeyError(f'Unrecognized upsample type {layer_type}') + else: + upsample = UPSAMPLE_LAYERS.get(layer_type) + + if upsample is nn.Upsample: + cfg_['mode'] = layer_type + layer = upsample(*args, **kwargs, **cfg_) + return layer diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/wrappers.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/wrappers.py new file mode 100644 index 0000000..8aebf67 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/bricks/wrappers.py @@ -0,0 +1,180 @@ +# Copyright (c) OpenMMLab. All rights reserved. +r"""Modified from https://github.com/facebookresearch/detectron2/blob/master/detectron2/layers/wrappers.py # noqa: E501 + +Wrap some nn modules to support empty tensor input. Currently, these wrappers +are mainly used in mask heads like fcn_mask_head and maskiou_heads since mask +heads are trained on only positive RoIs. +""" +import math + +import torch +import torch.nn as nn +from torch.nn.modules.utils import _pair, _triple + +from .registry import CONV_LAYERS, UPSAMPLE_LAYERS + +if torch.__version__ == 'parrots': + TORCH_VERSION = torch.__version__ +else: + # torch.__version__ could be 1.3.1+cu92, we only need the first two + # for comparison + TORCH_VERSION = tuple(int(x) for x in torch.__version__.split('.')[:2]) + + +def obsolete_torch_version(torch_version, version_threshold): + return torch_version == 'parrots' or torch_version <= version_threshold + + +class NewEmptyTensorOp(torch.autograd.Function): + + @staticmethod + def forward(ctx, x, new_shape): + ctx.shape = x.shape + return x.new_empty(new_shape) + + @staticmethod + def backward(ctx, grad): + shape = ctx.shape + return NewEmptyTensorOp.apply(grad, shape), None + + +@CONV_LAYERS.register_module('Conv', force=True) +class Conv2d(nn.Conv2d): + + def forward(self, x): + if x.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 4)): + out_shape = [x.shape[0], self.out_channels] + for i, k, p, s, d in zip(x.shape[-2:], self.kernel_size, + self.padding, self.stride, self.dilation): + o = (i + 2 * p - (d * (k - 1) + 1)) // s + 1 + out_shape.append(o) + empty = NewEmptyTensorOp.apply(x, out_shape) + if self.training: + # produce dummy gradient to avoid DDP warning. + dummy = sum(x.view(-1)[0] for x in self.parameters()) * 0.0 + return empty + dummy + else: + return empty + + return super().forward(x) + + +@CONV_LAYERS.register_module('Conv3d', force=True) +class Conv3d(nn.Conv3d): + + def forward(self, x): + if x.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 4)): + out_shape = [x.shape[0], self.out_channels] + for i, k, p, s, d in zip(x.shape[-3:], self.kernel_size, + self.padding, self.stride, self.dilation): + o = (i + 2 * p - (d * (k - 1) + 1)) // s + 1 + out_shape.append(o) + empty = NewEmptyTensorOp.apply(x, out_shape) + if self.training: + # produce dummy gradient to avoid DDP warning. + dummy = sum(x.view(-1)[0] for x in self.parameters()) * 0.0 + return empty + dummy + else: + return empty + + return super().forward(x) + + +@CONV_LAYERS.register_module() +@CONV_LAYERS.register_module('deconv') +@UPSAMPLE_LAYERS.register_module('deconv', force=True) +class ConvTranspose2d(nn.ConvTranspose2d): + + def forward(self, x): + if x.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 4)): + out_shape = [x.shape[0], self.out_channels] + for i, k, p, s, d, op in zip(x.shape[-2:], self.kernel_size, + self.padding, self.stride, + self.dilation, self.output_padding): + out_shape.append((i - 1) * s - 2 * p + (d * (k - 1) + 1) + op) + empty = NewEmptyTensorOp.apply(x, out_shape) + if self.training: + # produce dummy gradient to avoid DDP warning. + dummy = sum(x.view(-1)[0] for x in self.parameters()) * 0.0 + return empty + dummy + else: + return empty + + return super().forward(x) + + +@CONV_LAYERS.register_module() +@CONV_LAYERS.register_module('deconv3d') +@UPSAMPLE_LAYERS.register_module('deconv3d', force=True) +class ConvTranspose3d(nn.ConvTranspose3d): + + def forward(self, x): + if x.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 4)): + out_shape = [x.shape[0], self.out_channels] + for i, k, p, s, d, op in zip(x.shape[-3:], self.kernel_size, + self.padding, self.stride, + self.dilation, self.output_padding): + out_shape.append((i - 1) * s - 2 * p + (d * (k - 1) + 1) + op) + empty = NewEmptyTensorOp.apply(x, out_shape) + if self.training: + # produce dummy gradient to avoid DDP warning. + dummy = sum(x.view(-1)[0] for x in self.parameters()) * 0.0 + return empty + dummy + else: + return empty + + return super().forward(x) + + +class MaxPool2d(nn.MaxPool2d): + + def forward(self, x): + # PyTorch 1.9 does not support empty tensor inference yet + if x.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 9)): + out_shape = list(x.shape[:2]) + for i, k, p, s, d in zip(x.shape[-2:], _pair(self.kernel_size), + _pair(self.padding), _pair(self.stride), + _pair(self.dilation)): + o = (i + 2 * p - (d * (k - 1) + 1)) / s + 1 + o = math.ceil(o) if self.ceil_mode else math.floor(o) + out_shape.append(o) + empty = NewEmptyTensorOp.apply(x, out_shape) + return empty + + return super().forward(x) + + +class MaxPool3d(nn.MaxPool3d): + + def forward(self, x): + # PyTorch 1.9 does not support empty tensor inference yet + if x.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 9)): + out_shape = list(x.shape[:2]) + for i, k, p, s, d in zip(x.shape[-3:], _triple(self.kernel_size), + _triple(self.padding), + _triple(self.stride), + _triple(self.dilation)): + o = (i + 2 * p - (d * (k - 1) + 1)) / s + 1 + o = math.ceil(o) if self.ceil_mode else math.floor(o) + out_shape.append(o) + empty = NewEmptyTensorOp.apply(x, out_shape) + return empty + + return super().forward(x) + + +class Linear(torch.nn.Linear): + + def forward(self, x): + # empty tensor forward of Linear layer is supported in Pytorch 1.6 + if x.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 5)): + out_shape = [x.shape[0], self.out_features] + empty = NewEmptyTensorOp.apply(x, out_shape) + if self.training: + # produce dummy gradient to avoid DDP warning. + dummy = sum(x.view(-1)[0] for x in self.parameters()) * 0.0 + return empty + dummy + else: + return empty + + return super().forward(x) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/builder.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/builder.py new file mode 100644 index 0000000..7567316 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/builder.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ..runner import Sequential +from ..utils import Registry, build_from_cfg + + +def build_model_from_cfg(cfg, registry, default_args=None): + """Build a PyTorch model from config dict(s). Different from + ``build_from_cfg``, if cfg is a list, a ``nn.Sequential`` will be built. + + Args: + cfg (dict, list[dict]): The config of modules, is is either a config + dict or a list of config dicts. If cfg is a list, a + the built modules will be wrapped with ``nn.Sequential``. + registry (:obj:`Registry`): A registry the module belongs to. + default_args (dict, optional): Default arguments to build the module. + Defaults to None. + + Returns: + nn.Module: A built nn module. + """ + if isinstance(cfg, list): + modules = [ + build_from_cfg(cfg_, registry, default_args) for cfg_ in cfg + ] + return Sequential(*modules) + else: + return build_from_cfg(cfg, registry, default_args) + + +MODELS = Registry('model', build_func=build_model_from_cfg) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/resnet.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/resnet.py new file mode 100644 index 0000000..1cb3ac0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/resnet.py @@ -0,0 +1,316 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging + +import torch.nn as nn +import torch.utils.checkpoint as cp + +from .utils import constant_init, kaiming_init + + +def conv3x3(in_planes, out_planes, stride=1, dilation=1): + """3x3 convolution with padding.""" + return nn.Conv2d( + in_planes, + out_planes, + kernel_size=3, + stride=stride, + padding=dilation, + dilation=dilation, + bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, + inplanes, + planes, + stride=1, + dilation=1, + downsample=None, + style='pytorch', + with_cp=False): + super(BasicBlock, self).__init__() + assert style in ['pytorch', 'caffe'] + self.conv1 = conv3x3(inplanes, planes, stride, dilation) + self.bn1 = nn.BatchNorm2d(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = nn.BatchNorm2d(planes) + self.downsample = downsample + self.stride = stride + self.dilation = dilation + assert not with_cp + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, + inplanes, + planes, + stride=1, + dilation=1, + downsample=None, + style='pytorch', + with_cp=False): + """Bottleneck block. + + 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. + """ + super(Bottleneck, self).__init__() + assert style in ['pytorch', 'caffe'] + if style == 'pytorch': + conv1_stride = 1 + conv2_stride = stride + else: + conv1_stride = stride + conv2_stride = 1 + self.conv1 = nn.Conv2d( + inplanes, planes, kernel_size=1, stride=conv1_stride, bias=False) + self.conv2 = nn.Conv2d( + planes, + planes, + kernel_size=3, + stride=conv2_stride, + padding=dilation, + dilation=dilation, + bias=False) + + self.bn1 = nn.BatchNorm2d(planes) + self.bn2 = nn.BatchNorm2d(planes) + self.conv3 = nn.Conv2d( + planes, planes * self.expansion, kernel_size=1, bias=False) + self.bn3 = nn.BatchNorm2d(planes * self.expansion) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + self.dilation = dilation + self.with_cp = with_cp + + def forward(self, x): + + def _inner_forward(x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + + 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 + + +def make_res_layer(block, + inplanes, + planes, + blocks, + stride=1, + dilation=1, + style='pytorch', + with_cp=False): + 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), + nn.BatchNorm2d(planes * block.expansion), + ) + + layers = [] + layers.append( + block( + inplanes, + planes, + stride, + dilation, + downsample, + style=style, + with_cp=with_cp)) + inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append( + block(inplanes, planes, 1, dilation, style=style, with_cp=with_cp)) + + return nn.Sequential(*layers) + + +class ResNet(nn.Module): + """ResNet backbone. + + Args: + depth (int): Depth of resnet, from {18, 34, 50, 101, 152}. + num_stages (int): Resnet stages, normally 4. + 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. + bn_eval (bool): Whether to set BN layers as eval mode, namely, freeze + running stats (mean and var). + bn_frozen (bool): Whether to freeze weight and bias of BN layers. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. + """ + + 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, + num_stages=4, + strides=(1, 2, 2, 2), + dilations=(1, 1, 1, 1), + out_indices=(0, 1, 2, 3), + style='pytorch', + frozen_stages=-1, + bn_eval=True, + bn_frozen=False, + with_cp=False): + super(ResNet, self).__init__() + if depth not in self.arch_settings: + raise KeyError(f'invalid depth {depth} for resnet') + assert num_stages >= 1 and num_stages <= 4 + block, stage_blocks = self.arch_settings[depth] + stage_blocks = stage_blocks[:num_stages] + assert len(strides) == len(dilations) == num_stages + assert max(out_indices) < num_stages + + self.out_indices = out_indices + self.style = style + self.frozen_stages = frozen_stages + self.bn_eval = bn_eval + self.bn_frozen = bn_frozen + self.with_cp = with_cp + + self.inplanes = 64 + self.conv1 = nn.Conv2d( + 3, 64, kernel_size=7, stride=2, padding=3, bias=False) + self.bn1 = nn.BatchNorm2d(64) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + + self.res_layers = [] + for i, num_blocks in enumerate(stage_blocks): + stride = strides[i] + dilation = dilations[i] + planes = 64 * 2**i + res_layer = make_res_layer( + block, + self.inplanes, + planes, + num_blocks, + stride=stride, + dilation=dilation, + style=self.style, + with_cp=with_cp) + self.inplanes = planes * block.expansion + layer_name = f'layer{i + 1}' + self.add_module(layer_name, res_layer) + self.res_layers.append(layer_name) + + self.feat_dim = block.expansion * 64 * 2**(len(stage_blocks) - 1) + + def init_weights(self, pretrained=None): + if isinstance(pretrained, str): + logger = logging.getLogger() + from ..runner import load_checkpoint + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, nn.BatchNorm2d): + constant_init(m, 1) + else: + raise TypeError('pretrained must be a str or None') + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(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) + if len(outs) == 1: + return outs[0] + else: + return tuple(outs) + + def train(self, mode=True): + super(ResNet, self).train(mode) + if self.bn_eval: + for m in self.modules(): + if isinstance(m, nn.BatchNorm2d): + m.eval() + if self.bn_frozen: + for params in m.parameters(): + params.requires_grad = False + if mode and self.frozen_stages >= 0: + for param in self.conv1.parameters(): + param.requires_grad = False + for param in self.bn1.parameters(): + param.requires_grad = False + self.bn1.eval() + self.bn1.weight.requires_grad = False + self.bn1.bias.requires_grad = False + for i in range(1, self.frozen_stages + 1): + mod = getattr(self, f'layer{i}') + mod.eval() + for param in mod.parameters(): + param.requires_grad = False diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/__init__.py new file mode 100644 index 0000000..a263e31 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .flops_counter import get_model_complexity_info +from .fuse_conv_bn import fuse_conv_bn +from .sync_bn import revert_sync_batchnorm +from .weight_init import (INITIALIZERS, Caffe2XavierInit, ConstantInit, + KaimingInit, NormalInit, PretrainedInit, + TruncNormalInit, UniformInit, XavierInit, + bias_init_with_prob, caffe2_xavier_init, + constant_init, initialize, kaiming_init, normal_init, + trunc_normal_init, uniform_init, xavier_init) + +__all__ = [ + 'get_model_complexity_info', 'bias_init_with_prob', 'caffe2_xavier_init', + 'constant_init', 'kaiming_init', 'normal_init', 'trunc_normal_init', + 'uniform_init', 'xavier_init', 'fuse_conv_bn', 'initialize', + 'INITIALIZERS', 'ConstantInit', 'XavierInit', 'NormalInit', + 'TruncNormalInit', 'UniformInit', 'KaimingInit', 'PretrainedInit', + 'Caffe2XavierInit', 'revert_sync_batchnorm' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/flops_counter.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/flops_counter.py new file mode 100644 index 0000000..d10af5f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/flops_counter.py @@ -0,0 +1,599 @@ +# Modified from flops-counter.pytorch by Vladislav Sovrasov +# original repo: https://github.com/sovrasov/flops-counter.pytorch + +# MIT License + +# Copyright (c) 2018 Vladislav Sovrasov + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys +from functools import partial + +import numpy as np +import torch +import torch.nn as nn + +import annotator.uniformer.mmcv as mmcv + + +def get_model_complexity_info(model, + input_shape, + print_per_layer_stat=True, + as_strings=True, + input_constructor=None, + flush=False, + ost=sys.stdout): + """Get complexity information of a model. + + This method can calculate FLOPs and parameter counts of a model with + corresponding input shape. It can also print complexity information for + each layer in a model. + + Supported layers are listed as below: + - Convolutions: ``nn.Conv1d``, ``nn.Conv2d``, ``nn.Conv3d``. + - Activations: ``nn.ReLU``, ``nn.PReLU``, ``nn.ELU``, ``nn.LeakyReLU``, + ``nn.ReLU6``. + - Poolings: ``nn.MaxPool1d``, ``nn.MaxPool2d``, ``nn.MaxPool3d``, + ``nn.AvgPool1d``, ``nn.AvgPool2d``, ``nn.AvgPool3d``, + ``nn.AdaptiveMaxPool1d``, ``nn.AdaptiveMaxPool2d``, + ``nn.AdaptiveMaxPool3d``, ``nn.AdaptiveAvgPool1d``, + ``nn.AdaptiveAvgPool2d``, ``nn.AdaptiveAvgPool3d``. + - BatchNorms: ``nn.BatchNorm1d``, ``nn.BatchNorm2d``, + ``nn.BatchNorm3d``, ``nn.GroupNorm``, ``nn.InstanceNorm1d``, + ``InstanceNorm2d``, ``InstanceNorm3d``, ``nn.LayerNorm``. + - Linear: ``nn.Linear``. + - Deconvolution: ``nn.ConvTranspose2d``. + - Upsample: ``nn.Upsample``. + + Args: + model (nn.Module): The model for complexity calculation. + input_shape (tuple): Input shape used for calculation. + print_per_layer_stat (bool): Whether to print complexity information + for each layer in a model. Default: True. + as_strings (bool): Output FLOPs and params counts in a string form. + Default: True. + input_constructor (None | callable): If specified, it takes a callable + method that generates input. otherwise, it will generate a random + tensor with input shape to calculate FLOPs. Default: None. + flush (bool): same as that in :func:`print`. Default: False. + ost (stream): same as ``file`` param in :func:`print`. + Default: sys.stdout. + + Returns: + tuple[float | str]: If ``as_strings`` is set to True, it will return + FLOPs and parameter counts in a string format. otherwise, it will + return those in a float number format. + """ + assert type(input_shape) is tuple + assert len(input_shape) >= 1 + assert isinstance(model, nn.Module) + flops_model = add_flops_counting_methods(model) + flops_model.eval() + flops_model.start_flops_count() + if input_constructor: + input = input_constructor(input_shape) + _ = flops_model(**input) + else: + try: + batch = torch.ones(()).new_empty( + (1, *input_shape), + dtype=next(flops_model.parameters()).dtype, + device=next(flops_model.parameters()).device) + except StopIteration: + # Avoid StopIteration for models which have no parameters, + # like `nn.Relu()`, `nn.AvgPool2d`, etc. + batch = torch.ones(()).new_empty((1, *input_shape)) + + _ = flops_model(batch) + + flops_count, params_count = flops_model.compute_average_flops_cost() + if print_per_layer_stat: + print_model_with_flops( + flops_model, flops_count, params_count, ost=ost, flush=flush) + flops_model.stop_flops_count() + + if as_strings: + return flops_to_string(flops_count), params_to_string(params_count) + + return flops_count, params_count + + +def flops_to_string(flops, units='GFLOPs', precision=2): + """Convert FLOPs number into a string. + + Note that Here we take a multiply-add counts as one FLOP. + + Args: + flops (float): FLOPs number to be converted. + units (str | None): Converted FLOPs units. Options are None, 'GFLOPs', + 'MFLOPs', 'KFLOPs', 'FLOPs'. If set to None, it will automatically + choose the most suitable unit for FLOPs. Default: 'GFLOPs'. + precision (int): Digit number after the decimal point. Default: 2. + + Returns: + str: The converted FLOPs number with units. + + Examples: + >>> flops_to_string(1e9) + '1.0 GFLOPs' + >>> flops_to_string(2e5, 'MFLOPs') + '0.2 MFLOPs' + >>> flops_to_string(3e-9, None) + '3e-09 FLOPs' + """ + if units is None: + if flops // 10**9 > 0: + return str(round(flops / 10.**9, precision)) + ' GFLOPs' + elif flops // 10**6 > 0: + return str(round(flops / 10.**6, precision)) + ' MFLOPs' + elif flops // 10**3 > 0: + return str(round(flops / 10.**3, precision)) + ' KFLOPs' + else: + return str(flops) + ' FLOPs' + else: + if units == 'GFLOPs': + return str(round(flops / 10.**9, precision)) + ' ' + units + elif units == 'MFLOPs': + return str(round(flops / 10.**6, precision)) + ' ' + units + elif units == 'KFLOPs': + return str(round(flops / 10.**3, precision)) + ' ' + units + else: + return str(flops) + ' FLOPs' + + +def params_to_string(num_params, units=None, precision=2): + """Convert parameter number into a string. + + Args: + num_params (float): Parameter number to be converted. + units (str | None): Converted FLOPs units. Options are None, 'M', + 'K' and ''. If set to None, it will automatically choose the most + suitable unit for Parameter number. Default: None. + precision (int): Digit number after the decimal point. Default: 2. + + Returns: + str: The converted parameter number with units. + + Examples: + >>> params_to_string(1e9) + '1000.0 M' + >>> params_to_string(2e5) + '200.0 k' + >>> params_to_string(3e-9) + '3e-09' + """ + if units is None: + if num_params // 10**6 > 0: + return str(round(num_params / 10**6, precision)) + ' M' + elif num_params // 10**3: + return str(round(num_params / 10**3, precision)) + ' k' + else: + return str(num_params) + else: + if units == 'M': + return str(round(num_params / 10.**6, precision)) + ' ' + units + elif units == 'K': + return str(round(num_params / 10.**3, precision)) + ' ' + units + else: + return str(num_params) + + +def print_model_with_flops(model, + total_flops, + total_params, + units='GFLOPs', + precision=3, + ost=sys.stdout, + flush=False): + """Print a model with FLOPs for each layer. + + Args: + model (nn.Module): The model to be printed. + total_flops (float): Total FLOPs of the model. + total_params (float): Total parameter counts of the model. + units (str | None): Converted FLOPs units. Default: 'GFLOPs'. + precision (int): Digit number after the decimal point. Default: 3. + ost (stream): same as `file` param in :func:`print`. + Default: sys.stdout. + flush (bool): same as that in :func:`print`. Default: False. + + Example: + >>> class ExampleModel(nn.Module): + + >>> def __init__(self): + >>> super().__init__() + >>> self.conv1 = nn.Conv2d(3, 8, 3) + >>> self.conv2 = nn.Conv2d(8, 256, 3) + >>> self.conv3 = nn.Conv2d(256, 8, 3) + >>> self.avg_pool = nn.AdaptiveAvgPool2d((1, 1)) + >>> self.flatten = nn.Flatten() + >>> self.fc = nn.Linear(8, 1) + + >>> def forward(self, x): + >>> x = self.conv1(x) + >>> x = self.conv2(x) + >>> x = self.conv3(x) + >>> x = self.avg_pool(x) + >>> x = self.flatten(x) + >>> x = self.fc(x) + >>> return x + + >>> model = ExampleModel() + >>> x = (3, 16, 16) + to print the complexity information state for each layer, you can use + >>> get_model_complexity_info(model, x) + or directly use + >>> print_model_with_flops(model, 4579784.0, 37361) + ExampleModel( + 0.037 M, 100.000% Params, 0.005 GFLOPs, 100.000% FLOPs, + (conv1): Conv2d(0.0 M, 0.600% Params, 0.0 GFLOPs, 0.959% FLOPs, 3, 8, kernel_size=(3, 3), stride=(1, 1)) # noqa: E501 + (conv2): Conv2d(0.019 M, 50.020% Params, 0.003 GFLOPs, 58.760% FLOPs, 8, 256, kernel_size=(3, 3), stride=(1, 1)) + (conv3): Conv2d(0.018 M, 49.356% Params, 0.002 GFLOPs, 40.264% FLOPs, 256, 8, kernel_size=(3, 3), stride=(1, 1)) + (avg_pool): AdaptiveAvgPool2d(0.0 M, 0.000% Params, 0.0 GFLOPs, 0.017% FLOPs, output_size=(1, 1)) + (flatten): Flatten(0.0 M, 0.000% Params, 0.0 GFLOPs, 0.000% FLOPs, ) + (fc): Linear(0.0 M, 0.024% Params, 0.0 GFLOPs, 0.000% FLOPs, in_features=8, out_features=1, bias=True) + ) + """ + + def accumulate_params(self): + if is_supported_instance(self): + return self.__params__ + else: + sum = 0 + for m in self.children(): + sum += m.accumulate_params() + return sum + + def accumulate_flops(self): + if is_supported_instance(self): + return self.__flops__ / model.__batch_counter__ + else: + sum = 0 + for m in self.children(): + sum += m.accumulate_flops() + return sum + + def flops_repr(self): + accumulated_num_params = self.accumulate_params() + accumulated_flops_cost = self.accumulate_flops() + return ', '.join([ + params_to_string( + accumulated_num_params, units='M', precision=precision), + '{:.3%} Params'.format(accumulated_num_params / total_params), + flops_to_string( + accumulated_flops_cost, units=units, precision=precision), + '{:.3%} FLOPs'.format(accumulated_flops_cost / total_flops), + self.original_extra_repr() + ]) + + def add_extra_repr(m): + m.accumulate_flops = accumulate_flops.__get__(m) + m.accumulate_params = accumulate_params.__get__(m) + flops_extra_repr = flops_repr.__get__(m) + if m.extra_repr != flops_extra_repr: + m.original_extra_repr = m.extra_repr + m.extra_repr = flops_extra_repr + assert m.extra_repr != m.original_extra_repr + + def del_extra_repr(m): + if hasattr(m, 'original_extra_repr'): + m.extra_repr = m.original_extra_repr + del m.original_extra_repr + if hasattr(m, 'accumulate_flops'): + del m.accumulate_flops + + model.apply(add_extra_repr) + print(model, file=ost, flush=flush) + model.apply(del_extra_repr) + + +def get_model_parameters_number(model): + """Calculate parameter number of a model. + + Args: + model (nn.module): The model for parameter number calculation. + + Returns: + float: Parameter number of the model. + """ + num_params = sum(p.numel() for p in model.parameters() if p.requires_grad) + return num_params + + +def add_flops_counting_methods(net_main_module): + # adding additional methods to the existing module object, + # this is done this way so that each function has access to self object + net_main_module.start_flops_count = start_flops_count.__get__( + net_main_module) + net_main_module.stop_flops_count = stop_flops_count.__get__( + net_main_module) + net_main_module.reset_flops_count = reset_flops_count.__get__( + net_main_module) + net_main_module.compute_average_flops_cost = compute_average_flops_cost.__get__( # noqa: E501 + net_main_module) + + net_main_module.reset_flops_count() + + return net_main_module + + +def compute_average_flops_cost(self): + """Compute average FLOPs cost. + + A method to compute average FLOPs cost, which will be available after + `add_flops_counting_methods()` is called on a desired net object. + + Returns: + float: Current mean flops consumption per image. + """ + batches_count = self.__batch_counter__ + flops_sum = 0 + for module in self.modules(): + if is_supported_instance(module): + flops_sum += module.__flops__ + params_sum = get_model_parameters_number(self) + return flops_sum / batches_count, params_sum + + +def start_flops_count(self): + """Activate the computation of mean flops consumption per image. + + A method to activate the computation of mean flops consumption per image. + which will be available after ``add_flops_counting_methods()`` is called on + a desired net object. It should be called before running the network. + """ + add_batch_counter_hook_function(self) + + def add_flops_counter_hook_function(module): + if is_supported_instance(module): + if hasattr(module, '__flops_handle__'): + return + + else: + handle = module.register_forward_hook( + get_modules_mapping()[type(module)]) + + module.__flops_handle__ = handle + + self.apply(partial(add_flops_counter_hook_function)) + + +def stop_flops_count(self): + """Stop computing the mean flops consumption per image. + + A method to stop computing the mean flops consumption per image, which will + be available after ``add_flops_counting_methods()`` is called on a desired + net object. It can be called to pause the computation whenever. + """ + remove_batch_counter_hook_function(self) + self.apply(remove_flops_counter_hook_function) + + +def reset_flops_count(self): + """Reset statistics computed so far. + + A method to Reset computed statistics, which will be available after + `add_flops_counting_methods()` is called on a desired net object. + """ + add_batch_counter_variables_or_reset(self) + self.apply(add_flops_counter_variable_or_reset) + + +# ---- Internal functions +def empty_flops_counter_hook(module, input, output): + module.__flops__ += 0 + + +def upsample_flops_counter_hook(module, input, output): + output_size = output[0] + batch_size = output_size.shape[0] + output_elements_count = batch_size + for val in output_size.shape[1:]: + output_elements_count *= val + module.__flops__ += int(output_elements_count) + + +def relu_flops_counter_hook(module, input, output): + active_elements_count = output.numel() + module.__flops__ += int(active_elements_count) + + +def linear_flops_counter_hook(module, input, output): + input = input[0] + output_last_dim = output.shape[ + -1] # pytorch checks dimensions, so here we don't care much + module.__flops__ += int(np.prod(input.shape) * output_last_dim) + + +def pool_flops_counter_hook(module, input, output): + input = input[0] + module.__flops__ += int(np.prod(input.shape)) + + +def norm_flops_counter_hook(module, input, output): + input = input[0] + + batch_flops = np.prod(input.shape) + if (getattr(module, 'affine', False) + or getattr(module, 'elementwise_affine', False)): + batch_flops *= 2 + module.__flops__ += int(batch_flops) + + +def deconv_flops_counter_hook(conv_module, input, output): + # Can have multiple inputs, getting the first one + input = input[0] + + batch_size = input.shape[0] + input_height, input_width = input.shape[2:] + + kernel_height, kernel_width = conv_module.kernel_size + in_channels = conv_module.in_channels + out_channels = conv_module.out_channels + groups = conv_module.groups + + filters_per_channel = out_channels // groups + conv_per_position_flops = ( + kernel_height * kernel_width * in_channels * filters_per_channel) + + active_elements_count = batch_size * input_height * input_width + overall_conv_flops = conv_per_position_flops * active_elements_count + bias_flops = 0 + if conv_module.bias is not None: + output_height, output_width = output.shape[2:] + bias_flops = out_channels * batch_size * output_height * output_height + overall_flops = overall_conv_flops + bias_flops + + conv_module.__flops__ += int(overall_flops) + + +def conv_flops_counter_hook(conv_module, input, output): + # Can have multiple inputs, getting the first one + input = input[0] + + batch_size = input.shape[0] + output_dims = list(output.shape[2:]) + + kernel_dims = list(conv_module.kernel_size) + in_channels = conv_module.in_channels + out_channels = conv_module.out_channels + groups = conv_module.groups + + filters_per_channel = out_channels // groups + conv_per_position_flops = int( + np.prod(kernel_dims)) * in_channels * filters_per_channel + + active_elements_count = batch_size * int(np.prod(output_dims)) + + overall_conv_flops = conv_per_position_flops * active_elements_count + + bias_flops = 0 + + if conv_module.bias is not None: + + bias_flops = out_channels * active_elements_count + + overall_flops = overall_conv_flops + bias_flops + + conv_module.__flops__ += int(overall_flops) + + +def batch_counter_hook(module, input, output): + batch_size = 1 + if len(input) > 0: + # Can have multiple inputs, getting the first one + input = input[0] + batch_size = len(input) + else: + pass + print('Warning! No positional inputs found for a module, ' + 'assuming batch size is 1.') + module.__batch_counter__ += batch_size + + +def add_batch_counter_variables_or_reset(module): + + module.__batch_counter__ = 0 + + +def add_batch_counter_hook_function(module): + if hasattr(module, '__batch_counter_handle__'): + return + + handle = module.register_forward_hook(batch_counter_hook) + module.__batch_counter_handle__ = handle + + +def remove_batch_counter_hook_function(module): + if hasattr(module, '__batch_counter_handle__'): + module.__batch_counter_handle__.remove() + del module.__batch_counter_handle__ + + +def add_flops_counter_variable_or_reset(module): + if is_supported_instance(module): + if hasattr(module, '__flops__') or hasattr(module, '__params__'): + print('Warning: variables __flops__ or __params__ are already ' + 'defined for the module' + type(module).__name__ + + ' ptflops can affect your code!') + module.__flops__ = 0 + module.__params__ = get_model_parameters_number(module) + + +def is_supported_instance(module): + if type(module) in get_modules_mapping(): + return True + return False + + +def remove_flops_counter_hook_function(module): + if is_supported_instance(module): + if hasattr(module, '__flops_handle__'): + module.__flops_handle__.remove() + del module.__flops_handle__ + + +def get_modules_mapping(): + return { + # convolutions + nn.Conv1d: conv_flops_counter_hook, + nn.Conv2d: conv_flops_counter_hook, + mmcv.cnn.bricks.Conv2d: conv_flops_counter_hook, + nn.Conv3d: conv_flops_counter_hook, + mmcv.cnn.bricks.Conv3d: conv_flops_counter_hook, + # activations + nn.ReLU: relu_flops_counter_hook, + nn.PReLU: relu_flops_counter_hook, + nn.ELU: relu_flops_counter_hook, + nn.LeakyReLU: relu_flops_counter_hook, + nn.ReLU6: relu_flops_counter_hook, + # poolings + nn.MaxPool1d: pool_flops_counter_hook, + nn.AvgPool1d: pool_flops_counter_hook, + nn.AvgPool2d: pool_flops_counter_hook, + nn.MaxPool2d: pool_flops_counter_hook, + mmcv.cnn.bricks.MaxPool2d: pool_flops_counter_hook, + nn.MaxPool3d: pool_flops_counter_hook, + mmcv.cnn.bricks.MaxPool3d: pool_flops_counter_hook, + nn.AvgPool3d: pool_flops_counter_hook, + nn.AdaptiveMaxPool1d: pool_flops_counter_hook, + nn.AdaptiveAvgPool1d: pool_flops_counter_hook, + nn.AdaptiveMaxPool2d: pool_flops_counter_hook, + nn.AdaptiveAvgPool2d: pool_flops_counter_hook, + nn.AdaptiveMaxPool3d: pool_flops_counter_hook, + nn.AdaptiveAvgPool3d: pool_flops_counter_hook, + # normalizations + nn.BatchNorm1d: norm_flops_counter_hook, + nn.BatchNorm2d: norm_flops_counter_hook, + nn.BatchNorm3d: norm_flops_counter_hook, + nn.GroupNorm: norm_flops_counter_hook, + nn.InstanceNorm1d: norm_flops_counter_hook, + nn.InstanceNorm2d: norm_flops_counter_hook, + nn.InstanceNorm3d: norm_flops_counter_hook, + nn.LayerNorm: norm_flops_counter_hook, + # FC + nn.Linear: linear_flops_counter_hook, + mmcv.cnn.bricks.Linear: linear_flops_counter_hook, + # Upscale + nn.Upsample: upsample_flops_counter_hook, + # Deconvolution + nn.ConvTranspose2d: deconv_flops_counter_hook, + mmcv.cnn.bricks.ConvTranspose2d: deconv_flops_counter_hook, + } diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/fuse_conv_bn.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/fuse_conv_bn.py new file mode 100644 index 0000000..cb7076f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/fuse_conv_bn.py @@ -0,0 +1,59 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn + + +def _fuse_conv_bn(conv, bn): + """Fuse conv and bn into one module. + + Args: + conv (nn.Module): Conv to be fused. + bn (nn.Module): BN to be fused. + + Returns: + nn.Module: Fused module. + """ + conv_w = conv.weight + conv_b = conv.bias if conv.bias is not None else torch.zeros_like( + bn.running_mean) + + factor = bn.weight / torch.sqrt(bn.running_var + bn.eps) + conv.weight = nn.Parameter(conv_w * + factor.reshape([conv.out_channels, 1, 1, 1])) + conv.bias = nn.Parameter((conv_b - bn.running_mean) * factor + bn.bias) + return conv + + +def fuse_conv_bn(module): + """Recursively fuse conv and bn in a module. + + During inference, the functionary of batch norm layers is turned off + but only the mean and var alone channels are used, which exposes the + chance to fuse it with the preceding conv layers to save computations and + simplify network structures. + + Args: + module (nn.Module): Module to be fused. + + Returns: + nn.Module: Fused module. + """ + last_conv = None + last_conv_name = None + + for name, child in module.named_children(): + if isinstance(child, + (nn.modules.batchnorm._BatchNorm, nn.SyncBatchNorm)): + if last_conv is None: # only fuse BN that is after Conv + continue + fused_conv = _fuse_conv_bn(last_conv, child) + module._modules[last_conv_name] = fused_conv + # To reduce changes, set BN as Identity instead of deleting it. + module._modules[name] = nn.Identity() + last_conv = None + elif isinstance(child, nn.Conv2d): + last_conv = child + last_conv_name = name + else: + fuse_conv_bn(child) + return module diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/sync_bn.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/sync_bn.py new file mode 100644 index 0000000..f78f391 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/sync_bn.py @@ -0,0 +1,59 @@ +import torch + +import annotator.uniformer.mmcv as mmcv + + +class _BatchNormXd(torch.nn.modules.batchnorm._BatchNorm): + """A general BatchNorm layer without input dimension check. + + Reproduced from @kapily's work: + (https://github.com/pytorch/pytorch/issues/41081#issuecomment-783961547) + The only difference between BatchNorm1d, BatchNorm2d, BatchNorm3d, etc + is `_check_input_dim` that is designed for tensor sanity checks. + The check has been bypassed in this class for the convenience of converting + SyncBatchNorm. + """ + + def _check_input_dim(self, input): + return + + +def revert_sync_batchnorm(module): + """Helper function to convert all `SyncBatchNorm` (SyncBN) and + `mmcv.ops.sync_bn.SyncBatchNorm`(MMSyncBN) layers in the model to + `BatchNormXd` layers. + + Adapted from @kapily's work: + (https://github.com/pytorch/pytorch/issues/41081#issuecomment-783961547) + + Args: + module (nn.Module): The module containing `SyncBatchNorm` layers. + + Returns: + module_output: The converted module with `BatchNormXd` layers. + """ + module_output = module + module_checklist = [torch.nn.modules.batchnorm.SyncBatchNorm] + if hasattr(mmcv, 'ops'): + module_checklist.append(mmcv.ops.SyncBatchNorm) + if isinstance(module, tuple(module_checklist)): + module_output = _BatchNormXd(module.num_features, module.eps, + module.momentum, module.affine, + module.track_running_stats) + if module.affine: + # no_grad() may not be needed here but + # just to be consistent with `convert_sync_batchnorm()` + with torch.no_grad(): + module_output.weight = module.weight + module_output.bias = module.bias + module_output.running_mean = module.running_mean + module_output.running_var = module.running_var + module_output.num_batches_tracked = module.num_batches_tracked + module_output.training = module.training + # qconfig exists in quantized models + if hasattr(module, 'qconfig'): + module_output.qconfig = module.qconfig + for name, child in module.named_children(): + module_output.add_module(name, revert_sync_batchnorm(child)) + del module + return module_output diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/weight_init.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/weight_init.py new file mode 100644 index 0000000..287a1d0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/utils/weight_init.py @@ -0,0 +1,684 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import math +import warnings + +import numpy as np +import torch +import torch.nn as nn +from torch import Tensor + +from annotator.uniformer.mmcv.utils import Registry, build_from_cfg, get_logger, print_log + +INITIALIZERS = Registry('initializer') + + +def update_init_info(module, init_info): + """Update the `_params_init_info` in the module if the value of parameters + are changed. + + Args: + module (obj:`nn.Module`): The module of PyTorch with a user-defined + attribute `_params_init_info` which records the initialization + information. + init_info (str): The string that describes the initialization. + """ + assert hasattr( + module, + '_params_init_info'), f'Can not find `_params_init_info` in {module}' + for name, param in module.named_parameters(): + + assert param in module._params_init_info, ( + f'Find a new :obj:`Parameter` ' + f'named `{name}` during executing the ' + f'`init_weights` of ' + f'`{module.__class__.__name__}`. ' + f'Please do not add or ' + f'replace parameters during executing ' + f'the `init_weights`. ') + + # The parameter has been changed during executing the + # `init_weights` of module + mean_value = param.data.mean() + if module._params_init_info[param]['tmp_mean_value'] != mean_value: + module._params_init_info[param]['init_info'] = init_info + module._params_init_info[param]['tmp_mean_value'] = mean_value + + +def constant_init(module, val, bias=0): + if hasattr(module, 'weight') and module.weight is not None: + nn.init.constant_(module.weight, val) + if hasattr(module, 'bias') and module.bias is not None: + nn.init.constant_(module.bias, bias) + + +def xavier_init(module, gain=1, bias=0, distribution='normal'): + assert distribution in ['uniform', 'normal'] + if hasattr(module, 'weight') and module.weight is not None: + if distribution == 'uniform': + nn.init.xavier_uniform_(module.weight, gain=gain) + else: + nn.init.xavier_normal_(module.weight, gain=gain) + if hasattr(module, 'bias') and module.bias is not None: + nn.init.constant_(module.bias, bias) + + +def normal_init(module, mean=0, std=1, bias=0): + if hasattr(module, 'weight') and module.weight is not None: + nn.init.normal_(module.weight, mean, std) + if hasattr(module, 'bias') and module.bias is not None: + nn.init.constant_(module.bias, bias) + + +def trunc_normal_init(module: nn.Module, + mean: float = 0, + std: float = 1, + a: float = -2, + b: float = 2, + bias: float = 0) -> None: + if hasattr(module, 'weight') and module.weight is not None: + trunc_normal_(module.weight, mean, std, a, b) # type: ignore + if hasattr(module, 'bias') and module.bias is not None: + nn.init.constant_(module.bias, bias) # type: ignore + + +def uniform_init(module, a=0, b=1, bias=0): + if hasattr(module, 'weight') and module.weight is not None: + nn.init.uniform_(module.weight, a, b) + if hasattr(module, 'bias') and module.bias is not None: + nn.init.constant_(module.bias, bias) + + +def kaiming_init(module, + a=0, + mode='fan_out', + nonlinearity='relu', + bias=0, + distribution='normal'): + assert distribution in ['uniform', 'normal'] + if hasattr(module, 'weight') and module.weight is not None: + if distribution == 'uniform': + nn.init.kaiming_uniform_( + module.weight, a=a, mode=mode, nonlinearity=nonlinearity) + else: + nn.init.kaiming_normal_( + module.weight, a=a, mode=mode, nonlinearity=nonlinearity) + if hasattr(module, 'bias') and module.bias is not None: + nn.init.constant_(module.bias, bias) + + +def caffe2_xavier_init(module, bias=0): + # `XavierFill` in Caffe2 corresponds to `kaiming_uniform_` in PyTorch + # Acknowledgment to FAIR's internal code + kaiming_init( + module, + a=1, + mode='fan_in', + nonlinearity='leaky_relu', + bias=bias, + distribution='uniform') + + +def bias_init_with_prob(prior_prob): + """initialize conv/fc bias value according to a given probability value.""" + bias_init = float(-np.log((1 - prior_prob) / prior_prob)) + return bias_init + + +def _get_bases_name(m): + return [b.__name__ for b in m.__class__.__bases__] + + +class BaseInit(object): + + def __init__(self, *, bias=0, bias_prob=None, layer=None): + self.wholemodule = False + if not isinstance(bias, (int, float)): + raise TypeError(f'bias must be a number, but got a {type(bias)}') + + if bias_prob is not None: + if not isinstance(bias_prob, float): + raise TypeError(f'bias_prob type must be float, \ + but got {type(bias_prob)}') + + if layer is not None: + if not isinstance(layer, (str, list)): + raise TypeError(f'layer must be a str or a list of str, \ + but got a {type(layer)}') + else: + layer = [] + + if bias_prob is not None: + self.bias = bias_init_with_prob(bias_prob) + else: + self.bias = bias + self.layer = [layer] if isinstance(layer, str) else layer + + def _get_init_info(self): + info = f'{self.__class__.__name__}, bias={self.bias}' + return info + + +@INITIALIZERS.register_module(name='Constant') +class ConstantInit(BaseInit): + """Initialize module parameters with constant values. + + Args: + val (int | float): the value to fill the weights in the module with + bias (int | float): the value to fill the bias. Defaults to 0. + bias_prob (float, optional): the probability for bias initialization. + Defaults to None. + layer (str | list[str], optional): the layer will be initialized. + Defaults to None. + """ + + def __init__(self, val, **kwargs): + super().__init__(**kwargs) + self.val = val + + def __call__(self, module): + + def init(m): + if self.wholemodule: + constant_init(m, self.val, self.bias) + else: + layername = m.__class__.__name__ + basesname = _get_bases_name(m) + if len(set(self.layer) & set([layername] + basesname)): + constant_init(m, self.val, self.bias) + + module.apply(init) + if hasattr(module, '_params_init_info'): + update_init_info(module, init_info=self._get_init_info()) + + def _get_init_info(self): + info = f'{self.__class__.__name__}: val={self.val}, bias={self.bias}' + return info + + +@INITIALIZERS.register_module(name='Xavier') +class XavierInit(BaseInit): + r"""Initialize module parameters with values according to the method + described in `Understanding the difficulty of training deep feedforward + neural networks - Glorot, X. & Bengio, Y. (2010). + `_ + + Args: + gain (int | float): an optional scaling factor. Defaults to 1. + bias (int | float): the value to fill the bias. Defaults to 0. + bias_prob (float, optional): the probability for bias initialization. + Defaults to None. + distribution (str): distribution either be ``'normal'`` + or ``'uniform'``. Defaults to ``'normal'``. + layer (str | list[str], optional): the layer will be initialized. + Defaults to None. + """ + + def __init__(self, gain=1, distribution='normal', **kwargs): + super().__init__(**kwargs) + self.gain = gain + self.distribution = distribution + + def __call__(self, module): + + def init(m): + if self.wholemodule: + xavier_init(m, self.gain, self.bias, self.distribution) + else: + layername = m.__class__.__name__ + basesname = _get_bases_name(m) + if len(set(self.layer) & set([layername] + basesname)): + xavier_init(m, self.gain, self.bias, self.distribution) + + module.apply(init) + if hasattr(module, '_params_init_info'): + update_init_info(module, init_info=self._get_init_info()) + + def _get_init_info(self): + info = f'{self.__class__.__name__}: gain={self.gain}, ' \ + f'distribution={self.distribution}, bias={self.bias}' + return info + + +@INITIALIZERS.register_module(name='Normal') +class NormalInit(BaseInit): + r"""Initialize module parameters with the values drawn from the normal + distribution :math:`\mathcal{N}(\text{mean}, \text{std}^2)`. + + Args: + mean (int | float):the mean of the normal distribution. Defaults to 0. + std (int | float): the standard deviation of the normal distribution. + Defaults to 1. + bias (int | float): the value to fill the bias. Defaults to 0. + bias_prob (float, optional): the probability for bias initialization. + Defaults to None. + layer (str | list[str], optional): the layer will be initialized. + Defaults to None. + + """ + + def __init__(self, mean=0, std=1, **kwargs): + super().__init__(**kwargs) + self.mean = mean + self.std = std + + def __call__(self, module): + + def init(m): + if self.wholemodule: + normal_init(m, self.mean, self.std, self.bias) + else: + layername = m.__class__.__name__ + basesname = _get_bases_name(m) + if len(set(self.layer) & set([layername] + basesname)): + normal_init(m, self.mean, self.std, self.bias) + + module.apply(init) + if hasattr(module, '_params_init_info'): + update_init_info(module, init_info=self._get_init_info()) + + def _get_init_info(self): + info = f'{self.__class__.__name__}: mean={self.mean},' \ + f' std={self.std}, bias={self.bias}' + return info + + +@INITIALIZERS.register_module(name='TruncNormal') +class TruncNormalInit(BaseInit): + r"""Initialize module parameters with the values drawn from the normal + distribution :math:`\mathcal{N}(\text{mean}, \text{std}^2)` with values + outside :math:`[a, b]`. + + Args: + mean (float): the mean of the normal distribution. Defaults to 0. + std (float): the standard deviation of the normal distribution. + Defaults to 1. + a (float): The minimum cutoff value. + b ( float): The maximum cutoff value. + bias (float): the value to fill the bias. Defaults to 0. + bias_prob (float, optional): the probability for bias initialization. + Defaults to None. + layer (str | list[str], optional): the layer will be initialized. + Defaults to None. + + """ + + def __init__(self, + mean: float = 0, + std: float = 1, + a: float = -2, + b: float = 2, + **kwargs) -> None: + super().__init__(**kwargs) + self.mean = mean + self.std = std + self.a = a + self.b = b + + def __call__(self, module: nn.Module) -> None: + + def init(m): + if self.wholemodule: + trunc_normal_init(m, self.mean, self.std, self.a, self.b, + self.bias) + else: + layername = m.__class__.__name__ + basesname = _get_bases_name(m) + if len(set(self.layer) & set([layername] + basesname)): + trunc_normal_init(m, self.mean, self.std, self.a, self.b, + self.bias) + + module.apply(init) + if hasattr(module, '_params_init_info'): + update_init_info(module, init_info=self._get_init_info()) + + def _get_init_info(self): + info = f'{self.__class__.__name__}: a={self.a}, b={self.b},' \ + f' mean={self.mean}, std={self.std}, bias={self.bias}' + return info + + +@INITIALIZERS.register_module(name='Uniform') +class UniformInit(BaseInit): + r"""Initialize module parameters with values drawn from the uniform + distribution :math:`\mathcal{U}(a, b)`. + + Args: + a (int | float): the lower bound of the uniform distribution. + Defaults to 0. + b (int | float): the upper bound of the uniform distribution. + Defaults to 1. + bias (int | float): the value to fill the bias. Defaults to 0. + bias_prob (float, optional): the probability for bias initialization. + Defaults to None. + layer (str | list[str], optional): the layer will be initialized. + Defaults to None. + """ + + def __init__(self, a=0, b=1, **kwargs): + super().__init__(**kwargs) + self.a = a + self.b = b + + def __call__(self, module): + + def init(m): + if self.wholemodule: + uniform_init(m, self.a, self.b, self.bias) + else: + layername = m.__class__.__name__ + basesname = _get_bases_name(m) + if len(set(self.layer) & set([layername] + basesname)): + uniform_init(m, self.a, self.b, self.bias) + + module.apply(init) + if hasattr(module, '_params_init_info'): + update_init_info(module, init_info=self._get_init_info()) + + def _get_init_info(self): + info = f'{self.__class__.__name__}: a={self.a},' \ + f' b={self.b}, bias={self.bias}' + return info + + +@INITIALIZERS.register_module(name='Kaiming') +class KaimingInit(BaseInit): + r"""Initialize module parameters with the values according to the method + described in `Delving deep into rectifiers: Surpassing human-level + performance on ImageNet classification - He, K. et al. (2015). + `_ + + Args: + a (int | float): the negative slope of the rectifier used after this + layer (only used with ``'leaky_relu'``). Defaults to 0. + mode (str): either ``'fan_in'`` or ``'fan_out'``. Choosing + ``'fan_in'`` preserves the magnitude of the variance of the weights + in the forward pass. Choosing ``'fan_out'`` preserves the + magnitudes in the backwards pass. Defaults to ``'fan_out'``. + nonlinearity (str): the non-linear function (`nn.functional` name), + recommended to use only with ``'relu'`` or ``'leaky_relu'`` . + Defaults to 'relu'. + bias (int | float): the value to fill the bias. Defaults to 0. + bias_prob (float, optional): the probability for bias initialization. + Defaults to None. + distribution (str): distribution either be ``'normal'`` or + ``'uniform'``. Defaults to ``'normal'``. + layer (str | list[str], optional): the layer will be initialized. + Defaults to None. + """ + + def __init__(self, + a=0, + mode='fan_out', + nonlinearity='relu', + distribution='normal', + **kwargs): + super().__init__(**kwargs) + self.a = a + self.mode = mode + self.nonlinearity = nonlinearity + self.distribution = distribution + + def __call__(self, module): + + def init(m): + if self.wholemodule: + kaiming_init(m, self.a, self.mode, self.nonlinearity, + self.bias, self.distribution) + else: + layername = m.__class__.__name__ + basesname = _get_bases_name(m) + if len(set(self.layer) & set([layername] + basesname)): + kaiming_init(m, self.a, self.mode, self.nonlinearity, + self.bias, self.distribution) + + module.apply(init) + if hasattr(module, '_params_init_info'): + update_init_info(module, init_info=self._get_init_info()) + + def _get_init_info(self): + info = f'{self.__class__.__name__}: a={self.a}, mode={self.mode}, ' \ + f'nonlinearity={self.nonlinearity}, ' \ + f'distribution ={self.distribution}, bias={self.bias}' + return info + + +@INITIALIZERS.register_module(name='Caffe2Xavier') +class Caffe2XavierInit(KaimingInit): + # `XavierFill` in Caffe2 corresponds to `kaiming_uniform_` in PyTorch + # Acknowledgment to FAIR's internal code + def __init__(self, **kwargs): + super().__init__( + a=1, + mode='fan_in', + nonlinearity='leaky_relu', + distribution='uniform', + **kwargs) + + def __call__(self, module): + super().__call__(module) + + +@INITIALIZERS.register_module(name='Pretrained') +class PretrainedInit(object): + """Initialize module by loading a pretrained model. + + Args: + checkpoint (str): the checkpoint file of the pretrained model should + be load. + prefix (str, optional): the prefix of a sub-module in the pretrained + model. it is for loading a part of the pretrained model to + initialize. For example, if we would like to only load the + backbone of a detector model, we can set ``prefix='backbone.'``. + Defaults to None. + map_location (str): map tensors into proper locations. + """ + + def __init__(self, checkpoint, prefix=None, map_location=None): + self.checkpoint = checkpoint + self.prefix = prefix + self.map_location = map_location + + def __call__(self, module): + from annotator.uniformer.mmcv.runner import (_load_checkpoint_with_prefix, load_checkpoint, + load_state_dict) + logger = get_logger('mmcv') + if self.prefix is None: + print_log(f'load model from: {self.checkpoint}', logger=logger) + load_checkpoint( + module, + self.checkpoint, + map_location=self.map_location, + strict=False, + logger=logger) + else: + print_log( + f'load {self.prefix} in model from: {self.checkpoint}', + logger=logger) + state_dict = _load_checkpoint_with_prefix( + self.prefix, self.checkpoint, map_location=self.map_location) + load_state_dict(module, state_dict, strict=False, logger=logger) + + if hasattr(module, '_params_init_info'): + update_init_info(module, init_info=self._get_init_info()) + + def _get_init_info(self): + info = f'{self.__class__.__name__}: load from {self.checkpoint}' + return info + + +def _initialize(module, cfg, wholemodule=False): + func = build_from_cfg(cfg, INITIALIZERS) + # wholemodule flag is for override mode, there is no layer key in override + # and initializer will give init values for the whole module with the name + # in override. + func.wholemodule = wholemodule + func(module) + + +def _initialize_override(module, override, cfg): + if not isinstance(override, (dict, list)): + raise TypeError(f'override must be a dict or a list of dict, \ + but got {type(override)}') + + override = [override] if isinstance(override, dict) else override + + for override_ in override: + + cp_override = copy.deepcopy(override_) + name = cp_override.pop('name', None) + if name is None: + raise ValueError('`override` must contain the key "name",' + f'but got {cp_override}') + # if override only has name key, it means use args in init_cfg + if not cp_override: + cp_override.update(cfg) + # if override has name key and other args except type key, it will + # raise error + elif 'type' not in cp_override.keys(): + raise ValueError( + f'`override` need "type" key, but got {cp_override}') + + if hasattr(module, name): + _initialize(getattr(module, name), cp_override, wholemodule=True) + else: + raise RuntimeError(f'module did not have attribute {name}, ' + f'but init_cfg is {cp_override}.') + + +def initialize(module, init_cfg): + """Initialize a module. + + Args: + module (``torch.nn.Module``): the module will be initialized. + init_cfg (dict | list[dict]): initialization configuration dict to + define initializer. OpenMMLab has implemented 6 initializers + including ``Constant``, ``Xavier``, ``Normal``, ``Uniform``, + ``Kaiming``, and ``Pretrained``. + Example: + >>> module = nn.Linear(2, 3, bias=True) + >>> init_cfg = dict(type='Constant', layer='Linear', val =1 , bias =2) + >>> initialize(module, init_cfg) + + >>> module = nn.Sequential(nn.Conv1d(3, 1, 3), nn.Linear(1,2)) + >>> # define key ``'layer'`` for initializing layer with different + >>> # configuration + >>> init_cfg = [dict(type='Constant', layer='Conv1d', val=1), + dict(type='Constant', layer='Linear', val=2)] + >>> initialize(module, init_cfg) + + >>> # define key``'override'`` to initialize some specific part in + >>> # module + >>> class FooNet(nn.Module): + >>> def __init__(self): + >>> super().__init__() + >>> self.feat = nn.Conv2d(3, 16, 3) + >>> self.reg = nn.Conv2d(16, 10, 3) + >>> self.cls = nn.Conv2d(16, 5, 3) + >>> model = FooNet() + >>> init_cfg = dict(type='Constant', val=1, bias=2, layer='Conv2d', + >>> override=dict(type='Constant', name='reg', val=3, bias=4)) + >>> initialize(model, init_cfg) + + >>> model = ResNet(depth=50) + >>> # Initialize weights with the pretrained model. + >>> init_cfg = dict(type='Pretrained', + checkpoint='torchvision://resnet50') + >>> initialize(model, init_cfg) + + >>> # Initialize weights of a sub-module with the specific part of + >>> # a pretrained model by using "prefix". + >>> url = 'http://download.openmmlab.com/mmdetection/v2.0/retinanet/'\ + >>> 'retinanet_r50_fpn_1x_coco/'\ + >>> 'retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth' + >>> init_cfg = dict(type='Pretrained', + checkpoint=url, prefix='backbone.') + """ + if not isinstance(init_cfg, (dict, list)): + raise TypeError(f'init_cfg must be a dict or a list of dict, \ + but got {type(init_cfg)}') + + if isinstance(init_cfg, dict): + init_cfg = [init_cfg] + + for cfg in init_cfg: + # should deeply copy the original config because cfg may be used by + # other modules, e.g., one init_cfg shared by multiple bottleneck + # blocks, the expected cfg will be changed after pop and will change + # the initialization behavior of other modules + cp_cfg = copy.deepcopy(cfg) + override = cp_cfg.pop('override', None) + _initialize(module, cp_cfg) + + if override is not None: + cp_cfg.pop('layer', None) + _initialize_override(module, override, cp_cfg) + else: + # All attributes in module have same initialization. + pass + + +def _no_grad_trunc_normal_(tensor: Tensor, mean: float, std: float, a: float, + b: float) -> Tensor: + # Method based on + # https://people.sc.fsu.edu/~jburkardt/presentations/truncated_normal.pdf + # Modified from + # https://github.com/pytorch/pytorch/blob/master/torch/nn/init.py + def norm_cdf(x): + # Computes standard normal cumulative distribution function + return (1. + math.erf(x / math.sqrt(2.))) / 2. + + if (mean < a - 2 * std) or (mean > b + 2 * std): + warnings.warn( + 'mean is more than 2 std from [a, b] in nn.init.trunc_normal_. ' + 'The distribution of values may be incorrect.', + stacklevel=2) + + with torch.no_grad(): + # Values are generated by using a truncated uniform distribution and + # then using the inverse CDF for the normal distribution. + # Get upper and lower cdf values + lower = norm_cdf((a - mean) / std) + upper = norm_cdf((b - mean) / std) + + # Uniformly fill tensor with values from [lower, upper], then translate + # to [2lower-1, 2upper-1]. + tensor.uniform_(2 * lower - 1, 2 * upper - 1) + + # Use inverse cdf transform for normal distribution to get truncated + # standard normal + tensor.erfinv_() + + # Transform to proper mean, std + tensor.mul_(std * math.sqrt(2.)) + tensor.add_(mean) + + # Clamp to ensure it's in the proper range + tensor.clamp_(min=a, max=b) + return tensor + + +def trunc_normal_(tensor: Tensor, + mean: float = 0., + std: float = 1., + a: float = -2., + b: float = 2.) -> Tensor: + r"""Fills the input Tensor with values drawn from a truncated + normal distribution. The values are effectively drawn from the + normal distribution :math:`\mathcal{N}(\text{mean}, \text{std}^2)` + with values outside :math:`[a, b]` redrawn until they are within + the bounds. The method used for generating the random values works + best when :math:`a \leq \text{mean} \leq b`. + + Modified from + https://github.com/pytorch/pytorch/blob/master/torch/nn/init.py + + Args: + tensor (``torch.Tensor``): an n-dimensional `torch.Tensor`. + mean (float): the mean of the normal distribution. + std (float): the standard deviation of the normal distribution. + a (float): the minimum cutoff value. + b (float): the maximum cutoff value. + """ + return _no_grad_trunc_normal_(tensor, mean, std, a, b) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/vgg.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/vgg.py new file mode 100644 index 0000000..8778b64 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/cnn/vgg.py @@ -0,0 +1,175 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging + +import torch.nn as nn + +from .utils import constant_init, kaiming_init, normal_init + + +def conv3x3(in_planes, out_planes, dilation=1): + """3x3 convolution with padding.""" + return nn.Conv2d( + in_planes, + out_planes, + kernel_size=3, + padding=dilation, + dilation=dilation) + + +def make_vgg_layer(inplanes, + planes, + num_blocks, + dilation=1, + with_bn=False, + ceil_mode=False): + layers = [] + for _ in range(num_blocks): + layers.append(conv3x3(inplanes, planes, dilation)) + if with_bn: + layers.append(nn.BatchNorm2d(planes)) + layers.append(nn.ReLU(inplace=True)) + inplanes = planes + layers.append(nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=ceil_mode)) + + return layers + + +class VGG(nn.Module): + """VGG backbone. + + Args: + depth (int): Depth of vgg, from {11, 13, 16, 19}. + with_bn (bool): Use BatchNorm or not. + num_classes (int): number of classes for classification. + num_stages (int): VGG stages, normally 5. + dilations (Sequence[int]): Dilation of each stage. + out_indices (Sequence[int]): Output from which stages. + frozen_stages (int): Stages to be frozen (all param fixed). -1 means + not freezing any parameters. + bn_eval (bool): Whether to set BN layers as eval mode, namely, freeze + running stats (mean and var). + bn_frozen (bool): Whether to freeze weight and bias of BN layers. + """ + + arch_settings = { + 11: (1, 1, 2, 2, 2), + 13: (2, 2, 2, 2, 2), + 16: (2, 2, 3, 3, 3), + 19: (2, 2, 4, 4, 4) + } + + def __init__(self, + depth, + with_bn=False, + num_classes=-1, + num_stages=5, + dilations=(1, 1, 1, 1, 1), + out_indices=(0, 1, 2, 3, 4), + frozen_stages=-1, + bn_eval=True, + bn_frozen=False, + ceil_mode=False, + with_last_pool=True): + super(VGG, self).__init__() + if depth not in self.arch_settings: + raise KeyError(f'invalid depth {depth} for vgg') + assert num_stages >= 1 and num_stages <= 5 + stage_blocks = self.arch_settings[depth] + self.stage_blocks = stage_blocks[:num_stages] + assert len(dilations) == num_stages + assert max(out_indices) <= num_stages + + self.num_classes = num_classes + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.bn_eval = bn_eval + self.bn_frozen = bn_frozen + + self.inplanes = 3 + start_idx = 0 + vgg_layers = [] + self.range_sub_modules = [] + for i, num_blocks in enumerate(self.stage_blocks): + num_modules = num_blocks * (2 + with_bn) + 1 + end_idx = start_idx + num_modules + dilation = dilations[i] + planes = 64 * 2**i if i < 4 else 512 + vgg_layer = make_vgg_layer( + self.inplanes, + planes, + num_blocks, + dilation=dilation, + with_bn=with_bn, + ceil_mode=ceil_mode) + vgg_layers.extend(vgg_layer) + self.inplanes = planes + self.range_sub_modules.append([start_idx, end_idx]) + start_idx = end_idx + if not with_last_pool: + vgg_layers.pop(-1) + self.range_sub_modules[-1][1] -= 1 + self.module_name = 'features' + self.add_module(self.module_name, nn.Sequential(*vgg_layers)) + + if self.num_classes > 0: + self.classifier = nn.Sequential( + nn.Linear(512 * 7 * 7, 4096), + nn.ReLU(True), + nn.Dropout(), + nn.Linear(4096, 4096), + nn.ReLU(True), + nn.Dropout(), + nn.Linear(4096, num_classes), + ) + + def init_weights(self, pretrained=None): + if isinstance(pretrained, str): + logger = logging.getLogger() + from ..runner import load_checkpoint + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, nn.BatchNorm2d): + constant_init(m, 1) + elif isinstance(m, nn.Linear): + normal_init(m, std=0.01) + else: + raise TypeError('pretrained must be a str or None') + + def forward(self, x): + outs = [] + vgg_layers = getattr(self, self.module_name) + for i in range(len(self.stage_blocks)): + for j in range(*self.range_sub_modules[i]): + vgg_layer = vgg_layers[j] + x = vgg_layer(x) + if i in self.out_indices: + outs.append(x) + if self.num_classes > 0: + x = x.view(x.size(0), -1) + x = self.classifier(x) + outs.append(x) + if len(outs) == 1: + return outs[0] + else: + return tuple(outs) + + def train(self, mode=True): + super(VGG, self).train(mode) + if self.bn_eval: + for m in self.modules(): + if isinstance(m, nn.BatchNorm2d): + m.eval() + if self.bn_frozen: + for params in m.parameters(): + params.requires_grad = False + vgg_layers = getattr(self, self.module_name) + if mode and self.frozen_stages >= 0: + for i in range(self.frozen_stages): + for j in range(*self.range_sub_modules[i]): + mod = vgg_layers[j] + mod.eval() + for param in mod.parameters(): + param.requires_grad = False diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/engine/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/engine/__init__.py new file mode 100644 index 0000000..3193b7f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/engine/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .test import (collect_results_cpu, collect_results_gpu, multi_gpu_test, + single_gpu_test) + +__all__ = [ + 'collect_results_cpu', 'collect_results_gpu', 'multi_gpu_test', + 'single_gpu_test' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/engine/test.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/engine/test.py new file mode 100644 index 0000000..8dbeef2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/engine/test.py @@ -0,0 +1,202 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import pickle +import shutil +import tempfile +import time + +import torch +import torch.distributed as dist + +import annotator.uniformer.mmcv as mmcv +from annotator.uniformer.mmcv.runner import get_dist_info + + +def single_gpu_test(model, data_loader): + """Test model with a single gpu. + + This method tests model with a single gpu and displays test progress bar. + + Args: + model (nn.Module): Model to be tested. + data_loader (nn.Dataloader): Pytorch data loader. + + Returns: + list: The prediction results. + """ + model.eval() + results = [] + dataset = data_loader.dataset + prog_bar = mmcv.ProgressBar(len(dataset)) + for data in data_loader: + with torch.no_grad(): + result = model(return_loss=False, **data) + results.extend(result) + + # Assume result has the same length of batch_size + # refer to https://github.com/open-mmlab/mmcv/issues/985 + batch_size = len(result) + for _ in range(batch_size): + prog_bar.update() + return results + + +def multi_gpu_test(model, data_loader, tmpdir=None, gpu_collect=False): + """Test model with multiple gpus. + + This method tests model with multiple gpus and collects the results + under two different modes: gpu and cpu modes. By setting + ``gpu_collect=True``, it encodes results to gpu tensors and use gpu + communication for results collection. On cpu mode it saves the results on + different gpus to ``tmpdir`` and collects them by the rank 0 worker. + + Args: + model (nn.Module): Model to be tested. + data_loader (nn.Dataloader): Pytorch data loader. + tmpdir (str): Path of directory to save the temporary results from + different gpus under cpu mode. + gpu_collect (bool): Option to use either gpu or cpu to collect results. + + Returns: + list: The prediction results. + """ + model.eval() + results = [] + dataset = data_loader.dataset + rank, world_size = get_dist_info() + if rank == 0: + prog_bar = mmcv.ProgressBar(len(dataset)) + time.sleep(2) # This line can prevent deadlock problem in some cases. + for i, data in enumerate(data_loader): + with torch.no_grad(): + result = model(return_loss=False, **data) + results.extend(result) + + if rank == 0: + batch_size = len(result) + batch_size_all = batch_size * world_size + if batch_size_all + prog_bar.completed > len(dataset): + batch_size_all = len(dataset) - prog_bar.completed + for _ in range(batch_size_all): + prog_bar.update() + + # collect results from all ranks + if gpu_collect: + results = collect_results_gpu(results, len(dataset)) + else: + results = collect_results_cpu(results, len(dataset), tmpdir) + return results + + +def collect_results_cpu(result_part, size, tmpdir=None): + """Collect results under cpu mode. + + On cpu mode, this function will save the results on different gpus to + ``tmpdir`` and collect them by the rank 0 worker. + + Args: + result_part (list): Result list containing result parts + to be collected. + size (int): Size of the results, commonly equal to length of + the results. + tmpdir (str | None): temporal directory for collected results to + store. If set to None, it will create a random temporal directory + for it. + + Returns: + list: The collected results. + """ + rank, world_size = get_dist_info() + # create a tmp dir if it is not specified + if tmpdir is None: + MAX_LEN = 512 + # 32 is whitespace + dir_tensor = torch.full((MAX_LEN, ), + 32, + dtype=torch.uint8, + device='cuda') + if rank == 0: + mmcv.mkdir_or_exist('.dist_test') + tmpdir = tempfile.mkdtemp(dir='.dist_test') + tmpdir = torch.tensor( + bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda') + dir_tensor[:len(tmpdir)] = tmpdir + dist.broadcast(dir_tensor, 0) + tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip() + else: + mmcv.mkdir_or_exist(tmpdir) + # dump the part result to the dir + mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl')) + dist.barrier() + # collect all parts + if rank != 0: + return None + else: + # load results of all parts from tmp dir + part_list = [] + for i in range(world_size): + part_file = osp.join(tmpdir, f'part_{i}.pkl') + part_result = mmcv.load(part_file) + # When data is severely insufficient, an empty part_result + # on a certain gpu could makes the overall outputs empty. + if part_result: + part_list.append(part_result) + # sort the results + ordered_results = [] + for res in zip(*part_list): + ordered_results.extend(list(res)) + # the dataloader may pad some samples + ordered_results = ordered_results[:size] + # remove tmp dir + shutil.rmtree(tmpdir) + return ordered_results + + +def collect_results_gpu(result_part, size): + """Collect results under gpu mode. + + On gpu mode, this function will encode results to gpu tensors and use gpu + communication for results collection. + + Args: + result_part (list): Result list containing result parts + to be collected. + size (int): Size of the results, commonly equal to length of + the results. + + Returns: + list: The collected results. + """ + rank, world_size = get_dist_info() + # dump result part to tensor with pickle + part_tensor = torch.tensor( + bytearray(pickle.dumps(result_part)), dtype=torch.uint8, device='cuda') + # gather all result part tensor shape + shape_tensor = torch.tensor(part_tensor.shape, device='cuda') + shape_list = [shape_tensor.clone() for _ in range(world_size)] + dist.all_gather(shape_list, shape_tensor) + # padding result part tensor to max length + shape_max = torch.tensor(shape_list).max() + part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda') + part_send[:shape_tensor[0]] = part_tensor + part_recv_list = [ + part_tensor.new_zeros(shape_max) for _ in range(world_size) + ] + # gather all result part + dist.all_gather(part_recv_list, part_send) + + if rank == 0: + part_list = [] + for recv, shape in zip(part_recv_list, shape_list): + part_result = pickle.loads(recv[:shape[0]].cpu().numpy().tobytes()) + # When data is severely insufficient, an empty part_result + # on a certain gpu could makes the overall outputs empty. + if part_result: + part_list.append(part_result) + # sort the results + ordered_results = [] + for res in zip(*part_list): + ordered_results.extend(list(res)) + # the dataloader may pad some samples + ordered_results = ordered_results[:size] + return ordered_results diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/__init__.py new file mode 100644 index 0000000..2051b85 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .file_client import BaseStorageBackend, FileClient +from .handlers import BaseFileHandler, JsonHandler, PickleHandler, YamlHandler +from .io import dump, load, register_handler +from .parse import dict_from_file, list_from_file + +__all__ = [ + 'BaseStorageBackend', 'FileClient', 'load', 'dump', 'register_handler', + 'BaseFileHandler', 'JsonHandler', 'PickleHandler', 'YamlHandler', + 'list_from_file', 'dict_from_file' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/file_client.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/file_client.py new file mode 100644 index 0000000..950f0c1 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/file_client.py @@ -0,0 +1,1148 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import inspect +import os +import os.path as osp +import re +import tempfile +import warnings +from abc import ABCMeta, abstractmethod +from contextlib import contextmanager +from pathlib import Path +from typing import Iterable, Iterator, Optional, Tuple, Union +from urllib.request import urlopen + +import annotator.uniformer.mmcv as mmcv +from annotator.uniformer.mmcv.utils.misc import has_method +from annotator.uniformer.mmcv.utils.path import is_filepath + + +class BaseStorageBackend(metaclass=ABCMeta): + """Abstract class of storage backends. + + All backends need to implement two apis: ``get()`` and ``get_text()``. + ``get()`` reads the file as a byte stream and ``get_text()`` reads the file + as texts. + """ + + # a flag to indicate whether the backend can create a symlink for a file + _allow_symlink = False + + @property + def name(self): + return self.__class__.__name__ + + @property + def allow_symlink(self): + return self._allow_symlink + + @abstractmethod + def get(self, filepath): + pass + + @abstractmethod + def get_text(self, filepath): + pass + + +class CephBackend(BaseStorageBackend): + """Ceph storage backend (for internal use). + + Args: + path_mapping (dict|None): path mapping dict from local path to Petrel + path. When ``path_mapping={'src': 'dst'}``, ``src`` in ``filepath`` + will be replaced by ``dst``. Default: None. + + .. warning:: + :class:`mmcv.fileio.file_client.CephBackend` will be deprecated, + please use :class:`mmcv.fileio.file_client.PetrelBackend` instead. + """ + + def __init__(self, path_mapping=None): + try: + import ceph + except ImportError: + raise ImportError('Please install ceph to enable CephBackend.') + + warnings.warn( + 'CephBackend will be deprecated, please use PetrelBackend instead') + self._client = ceph.S3Client() + assert isinstance(path_mapping, dict) or path_mapping is None + self.path_mapping = path_mapping + + def get(self, filepath): + filepath = str(filepath) + if self.path_mapping is not None: + for k, v in self.path_mapping.items(): + filepath = filepath.replace(k, v) + value = self._client.Get(filepath) + value_buf = memoryview(value) + return value_buf + + def get_text(self, filepath, encoding=None): + raise NotImplementedError + + +class PetrelBackend(BaseStorageBackend): + """Petrel storage backend (for internal use). + + PetrelBackend supports reading and writing data to multiple clusters. + If the file path contains the cluster name, PetrelBackend will read data + from specified cluster or write data to it. Otherwise, PetrelBackend will + access the default cluster. + + Args: + path_mapping (dict, optional): Path mapping dict from local path to + Petrel path. When ``path_mapping={'src': 'dst'}``, ``src`` in + ``filepath`` will be replaced by ``dst``. Default: None. + enable_mc (bool, optional): Whether to enable memcached support. + Default: True. + + Examples: + >>> filepath1 = 's3://path/of/file' + >>> filepath2 = 'cluster-name:s3://path/of/file' + >>> client = PetrelBackend() + >>> client.get(filepath1) # get data from default cluster + >>> client.get(filepath2) # get data from 'cluster-name' cluster + """ + + def __init__(self, + path_mapping: Optional[dict] = None, + enable_mc: bool = True): + try: + from petrel_client import client + except ImportError: + raise ImportError('Please install petrel_client to enable ' + 'PetrelBackend.') + + self._client = client.Client(enable_mc=enable_mc) + assert isinstance(path_mapping, dict) or path_mapping is None + self.path_mapping = path_mapping + + def _map_path(self, filepath: Union[str, Path]) -> str: + """Map ``filepath`` to a string path whose prefix will be replaced by + :attr:`self.path_mapping`. + + Args: + filepath (str): Path to be mapped. + """ + filepath = str(filepath) + if self.path_mapping is not None: + for k, v in self.path_mapping.items(): + filepath = filepath.replace(k, v) + return filepath + + def _format_path(self, filepath: str) -> str: + """Convert a ``filepath`` to standard format of petrel oss. + + If the ``filepath`` is concatenated by ``os.path.join``, in a Windows + environment, the ``filepath`` will be the format of + 's3://bucket_name\\image.jpg'. By invoking :meth:`_format_path`, the + above ``filepath`` will be converted to 's3://bucket_name/image.jpg'. + + Args: + filepath (str): Path to be formatted. + """ + return re.sub(r'\\+', '/', filepath) + + def get(self, filepath: Union[str, Path]) -> memoryview: + """Read data from a given ``filepath`` with 'rb' mode. + + Args: + filepath (str or Path): Path to read data. + + Returns: + memoryview: A memory view of expected bytes object to avoid + copying. The memoryview object can be converted to bytes by + ``value_buf.tobytes()``. + """ + filepath = self._map_path(filepath) + filepath = self._format_path(filepath) + value = self._client.Get(filepath) + value_buf = memoryview(value) + return value_buf + + def get_text(self, + filepath: Union[str, Path], + encoding: str = 'utf-8') -> str: + """Read data from a given ``filepath`` with 'r' mode. + + Args: + filepath (str or Path): Path to read data. + encoding (str): The encoding format used to open the ``filepath``. + Default: 'utf-8'. + + Returns: + str: Expected text reading from ``filepath``. + """ + return str(self.get(filepath), encoding=encoding) + + def put(self, obj: bytes, filepath: Union[str, Path]) -> None: + """Save data to a given ``filepath``. + + Args: + obj (bytes): Data to be saved. + filepath (str or Path): Path to write data. + """ + filepath = self._map_path(filepath) + filepath = self._format_path(filepath) + self._client.put(filepath, obj) + + def put_text(self, + obj: str, + filepath: Union[str, Path], + encoding: str = 'utf-8') -> None: + """Save data to a given ``filepath``. + + Args: + obj (str): Data to be written. + filepath (str or Path): Path to write data. + encoding (str): The encoding format used to encode the ``obj``. + Default: 'utf-8'. + """ + self.put(bytes(obj, encoding=encoding), filepath) + + def remove(self, filepath: Union[str, Path]) -> None: + """Remove a file. + + Args: + filepath (str or Path): Path to be removed. + """ + if not has_method(self._client, 'delete'): + raise NotImplementedError( + ('Current version of Petrel Python SDK has not supported ' + 'the `delete` method, please use a higher version or dev' + ' branch instead.')) + + filepath = self._map_path(filepath) + filepath = self._format_path(filepath) + self._client.delete(filepath) + + def exists(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path exists. + + Args: + filepath (str or Path): Path to be checked whether exists. + + Returns: + bool: Return ``True`` if ``filepath`` exists, ``False`` otherwise. + """ + if not (has_method(self._client, 'contains') + and has_method(self._client, 'isdir')): + raise NotImplementedError( + ('Current version of Petrel Python SDK has not supported ' + 'the `contains` and `isdir` methods, please use a higher' + 'version or dev branch instead.')) + + filepath = self._map_path(filepath) + filepath = self._format_path(filepath) + return self._client.contains(filepath) or self._client.isdir(filepath) + + def isdir(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path is a directory. + + Args: + filepath (str or Path): Path to be checked whether it is a + directory. + + Returns: + bool: Return ``True`` if ``filepath`` points to a directory, + ``False`` otherwise. + """ + if not has_method(self._client, 'isdir'): + raise NotImplementedError( + ('Current version of Petrel Python SDK has not supported ' + 'the `isdir` method, please use a higher version or dev' + ' branch instead.')) + + filepath = self._map_path(filepath) + filepath = self._format_path(filepath) + return self._client.isdir(filepath) + + def isfile(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path is a file. + + Args: + filepath (str or Path): Path to be checked whether it is a file. + + Returns: + bool: Return ``True`` if ``filepath`` points to a file, ``False`` + otherwise. + """ + if not has_method(self._client, 'contains'): + raise NotImplementedError( + ('Current version of Petrel Python SDK has not supported ' + 'the `contains` method, please use a higher version or ' + 'dev branch instead.')) + + filepath = self._map_path(filepath) + filepath = self._format_path(filepath) + return self._client.contains(filepath) + + def join_path(self, filepath: Union[str, Path], + *filepaths: Union[str, Path]) -> str: + """Concatenate all file paths. + + Args: + filepath (str or Path): Path to be concatenated. + + Returns: + str: The result after concatenation. + """ + filepath = self._format_path(self._map_path(filepath)) + if filepath.endswith('/'): + filepath = filepath[:-1] + formatted_paths = [filepath] + for path in filepaths: + formatted_paths.append(self._format_path(self._map_path(path))) + return '/'.join(formatted_paths) + + @contextmanager + def get_local_path(self, filepath: Union[str, Path]) -> Iterable[str]: + """Download a file from ``filepath`` and return a temporary path. + + ``get_local_path`` is decorated by :meth:`contxtlib.contextmanager`. It + can be called with ``with`` statement, and when exists from the + ``with`` statement, the temporary path will be released. + + Args: + filepath (str | Path): Download a file from ``filepath``. + + Examples: + >>> client = PetrelBackend() + >>> # After existing from the ``with`` clause, + >>> # the path will be removed + >>> with client.get_local_path('s3://path/of/your/file') as path: + ... # do something here + + Yields: + Iterable[str]: Only yield one temporary path. + """ + filepath = self._map_path(filepath) + filepath = self._format_path(filepath) + assert self.isfile(filepath) + try: + f = tempfile.NamedTemporaryFile(delete=False) + f.write(self.get(filepath)) + f.close() + yield f.name + finally: + os.remove(f.name) + + def list_dir_or_file(self, + dir_path: Union[str, Path], + list_dir: bool = True, + list_file: bool = True, + suffix: Optional[Union[str, Tuple[str]]] = None, + recursive: bool = False) -> Iterator[str]: + """Scan a directory to find the interested directories or files in + arbitrary order. + + Note: + Petrel has no concept of directories but it simulates the directory + hierarchy in the filesystem through public prefixes. In addition, + if the returned path ends with '/', it means the path is a public + prefix which is a logical directory. + + Note: + :meth:`list_dir_or_file` returns the path relative to ``dir_path``. + In addition, the returned path of directory will not contains the + suffix '/' which is consistent with other backends. + + Args: + dir_path (str | Path): Path of the directory. + list_dir (bool): List the directories. Default: True. + list_file (bool): List the path of files. Default: True. + suffix (str or tuple[str], optional): File suffix + that we are interested in. Default: None. + recursive (bool): If set to True, recursively scan the + directory. Default: False. + + Yields: + Iterable[str]: A relative path to ``dir_path``. + """ + if not has_method(self._client, 'list'): + raise NotImplementedError( + ('Current version of Petrel Python SDK has not supported ' + 'the `list` method, please use a higher version or dev' + ' branch instead.')) + + dir_path = self._map_path(dir_path) + dir_path = self._format_path(dir_path) + if list_dir and suffix is not None: + raise TypeError( + '`list_dir` should be False when `suffix` is not None') + + if (suffix is not None) and not isinstance(suffix, (str, tuple)): + raise TypeError('`suffix` must be a string or tuple of strings') + + # Petrel's simulated directory hierarchy assumes that directory paths + # should end with `/` + if not dir_path.endswith('/'): + dir_path += '/' + + root = dir_path + + def _list_dir_or_file(dir_path, list_dir, list_file, suffix, + recursive): + for path in self._client.list(dir_path): + # the `self.isdir` is not used here to determine whether path + # is a directory, because `self.isdir` relies on + # `self._client.list` + if path.endswith('/'): # a directory path + next_dir_path = self.join_path(dir_path, path) + if list_dir: + # get the relative path and exclude the last + # character '/' + rel_dir = next_dir_path[len(root):-1] + yield rel_dir + if recursive: + yield from _list_dir_or_file(next_dir_path, list_dir, + list_file, suffix, + recursive) + else: # a file path + absolute_path = self.join_path(dir_path, path) + rel_path = absolute_path[len(root):] + if (suffix is None + or rel_path.endswith(suffix)) and list_file: + yield rel_path + + return _list_dir_or_file(dir_path, list_dir, list_file, suffix, + recursive) + + +class MemcachedBackend(BaseStorageBackend): + """Memcached storage backend. + + Attributes: + server_list_cfg (str): Config file for memcached server list. + client_cfg (str): Config file for memcached client. + sys_path (str | None): Additional path to be appended to `sys.path`. + Default: None. + """ + + def __init__(self, server_list_cfg, client_cfg, sys_path=None): + if sys_path is not None: + import sys + sys.path.append(sys_path) + try: + import mc + except ImportError: + raise ImportError( + 'Please install memcached to enable MemcachedBackend.') + + self.server_list_cfg = server_list_cfg + self.client_cfg = client_cfg + self._client = mc.MemcachedClient.GetInstance(self.server_list_cfg, + self.client_cfg) + # mc.pyvector servers as a point which points to a memory cache + self._mc_buffer = mc.pyvector() + + def get(self, filepath): + filepath = str(filepath) + import mc + self._client.Get(filepath, self._mc_buffer) + value_buf = mc.ConvertBuffer(self._mc_buffer) + return value_buf + + def get_text(self, filepath, encoding=None): + raise NotImplementedError + + +class LmdbBackend(BaseStorageBackend): + """Lmdb storage backend. + + Args: + db_path (str): Lmdb database path. + readonly (bool, optional): Lmdb environment parameter. If True, + disallow any write operations. Default: True. + lock (bool, optional): Lmdb environment parameter. If False, when + concurrent access occurs, do not lock the database. Default: False. + readahead (bool, optional): Lmdb environment parameter. If False, + disable the OS filesystem readahead mechanism, which may improve + random read performance when a database is larger than RAM. + Default: False. + + Attributes: + db_path (str): Lmdb database path. + """ + + def __init__(self, + db_path, + readonly=True, + lock=False, + readahead=False, + **kwargs): + try: + import lmdb + except ImportError: + raise ImportError('Please install lmdb to enable LmdbBackend.') + + self.db_path = str(db_path) + self._client = lmdb.open( + self.db_path, + readonly=readonly, + lock=lock, + readahead=readahead, + **kwargs) + + def get(self, filepath): + """Get values according to the filepath. + + Args: + filepath (str | obj:`Path`): Here, filepath is the lmdb key. + """ + filepath = str(filepath) + with self._client.begin(write=False) as txn: + value_buf = txn.get(filepath.encode('ascii')) + return value_buf + + def get_text(self, filepath, encoding=None): + raise NotImplementedError + + +class HardDiskBackend(BaseStorageBackend): + """Raw hard disks storage backend.""" + + _allow_symlink = True + + def get(self, filepath: Union[str, Path]) -> bytes: + """Read data from a given ``filepath`` with 'rb' mode. + + Args: + filepath (str or Path): Path to read data. + + Returns: + bytes: Expected bytes object. + """ + with open(filepath, 'rb') as f: + value_buf = f.read() + return value_buf + + def get_text(self, + filepath: Union[str, Path], + encoding: str = 'utf-8') -> str: + """Read data from a given ``filepath`` with 'r' mode. + + Args: + filepath (str or Path): Path to read data. + encoding (str): The encoding format used to open the ``filepath``. + Default: 'utf-8'. + + Returns: + str: Expected text reading from ``filepath``. + """ + with open(filepath, 'r', encoding=encoding) as f: + value_buf = f.read() + return value_buf + + def put(self, obj: bytes, filepath: Union[str, Path]) -> None: + """Write data to a given ``filepath`` with 'wb' mode. + + Note: + ``put`` will create a directory if the directory of ``filepath`` + does not exist. + + Args: + obj (bytes): Data to be written. + filepath (str or Path): Path to write data. + """ + mmcv.mkdir_or_exist(osp.dirname(filepath)) + with open(filepath, 'wb') as f: + f.write(obj) + + def put_text(self, + obj: str, + filepath: Union[str, Path], + encoding: str = 'utf-8') -> None: + """Write data to a given ``filepath`` with 'w' mode. + + Note: + ``put_text`` will create a directory if the directory of + ``filepath`` does not exist. + + Args: + obj (str): Data to be written. + filepath (str or Path): Path to write data. + encoding (str): The encoding format used to open the ``filepath``. + Default: 'utf-8'. + """ + mmcv.mkdir_or_exist(osp.dirname(filepath)) + with open(filepath, 'w', encoding=encoding) as f: + f.write(obj) + + def remove(self, filepath: Union[str, Path]) -> None: + """Remove a file. + + Args: + filepath (str or Path): Path to be removed. + """ + os.remove(filepath) + + def exists(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path exists. + + Args: + filepath (str or Path): Path to be checked whether exists. + + Returns: + bool: Return ``True`` if ``filepath`` exists, ``False`` otherwise. + """ + return osp.exists(filepath) + + def isdir(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path is a directory. + + Args: + filepath (str or Path): Path to be checked whether it is a + directory. + + Returns: + bool: Return ``True`` if ``filepath`` points to a directory, + ``False`` otherwise. + """ + return osp.isdir(filepath) + + def isfile(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path is a file. + + Args: + filepath (str or Path): Path to be checked whether it is a file. + + Returns: + bool: Return ``True`` if ``filepath`` points to a file, ``False`` + otherwise. + """ + return osp.isfile(filepath) + + def join_path(self, filepath: Union[str, Path], + *filepaths: Union[str, Path]) -> str: + """Concatenate all file paths. + + Join one or more filepath components intelligently. The return value + is the concatenation of filepath and any members of *filepaths. + + Args: + filepath (str or Path): Path to be concatenated. + + Returns: + str: The result of concatenation. + """ + return osp.join(filepath, *filepaths) + + @contextmanager + def get_local_path( + self, filepath: Union[str, Path]) -> Iterable[Union[str, Path]]: + """Only for unified API and do nothing.""" + yield filepath + + def list_dir_or_file(self, + dir_path: Union[str, Path], + list_dir: bool = True, + list_file: bool = True, + suffix: Optional[Union[str, Tuple[str]]] = None, + recursive: bool = False) -> Iterator[str]: + """Scan a directory to find the interested directories or files in + arbitrary order. + + Note: + :meth:`list_dir_or_file` returns the path relative to ``dir_path``. + + Args: + dir_path (str | Path): Path of the directory. + list_dir (bool): List the directories. Default: True. + list_file (bool): List the path of files. Default: True. + suffix (str or tuple[str], optional): File suffix + that we are interested in. Default: None. + recursive (bool): If set to True, recursively scan the + directory. Default: False. + + Yields: + Iterable[str]: A relative path to ``dir_path``. + """ + if list_dir and suffix is not None: + raise TypeError('`suffix` should be None when `list_dir` is True') + + if (suffix is not None) and not isinstance(suffix, (str, tuple)): + raise TypeError('`suffix` must be a string or tuple of strings') + + root = dir_path + + def _list_dir_or_file(dir_path, list_dir, list_file, suffix, + recursive): + for entry in os.scandir(dir_path): + if not entry.name.startswith('.') and entry.is_file(): + rel_path = osp.relpath(entry.path, root) + if (suffix is None + or rel_path.endswith(suffix)) and list_file: + yield rel_path + elif osp.isdir(entry.path): + if list_dir: + rel_dir = osp.relpath(entry.path, root) + yield rel_dir + if recursive: + yield from _list_dir_or_file(entry.path, list_dir, + list_file, suffix, + recursive) + + return _list_dir_or_file(dir_path, list_dir, list_file, suffix, + recursive) + + +class HTTPBackend(BaseStorageBackend): + """HTTP and HTTPS storage bachend.""" + + def get(self, filepath): + value_buf = urlopen(filepath).read() + return value_buf + + def get_text(self, filepath, encoding='utf-8'): + value_buf = urlopen(filepath).read() + return value_buf.decode(encoding) + + @contextmanager + def get_local_path(self, filepath: str) -> Iterable[str]: + """Download a file from ``filepath``. + + ``get_local_path`` is decorated by :meth:`contxtlib.contextmanager`. It + can be called with ``with`` statement, and when exists from the + ``with`` statement, the temporary path will be released. + + Args: + filepath (str): Download a file from ``filepath``. + + Examples: + >>> client = HTTPBackend() + >>> # After existing from the ``with`` clause, + >>> # the path will be removed + >>> with client.get_local_path('http://path/of/your/file') as path: + ... # do something here + """ + try: + f = tempfile.NamedTemporaryFile(delete=False) + f.write(self.get(filepath)) + f.close() + yield f.name + finally: + os.remove(f.name) + + +class FileClient: + """A general file client to access files in different backends. + + The client loads a file or text in a specified backend from its path + and returns it as a binary or text file. There are two ways to choose a + backend, the name of backend and the prefix of path. Although both of them + can be used to choose a storage backend, ``backend`` has a higher priority + that is if they are all set, the storage backend will be chosen by the + backend argument. If they are all `None`, the disk backend will be chosen. + Note that It can also register other backend accessor with a given name, + prefixes, and backend class. In addition, We use the singleton pattern to + avoid repeated object creation. If the arguments are the same, the same + object will be returned. + + Args: + backend (str, optional): The storage backend type. Options are "disk", + "ceph", "memcached", "lmdb", "http" and "petrel". Default: None. + prefix (str, optional): The prefix of the registered storage backend. + Options are "s3", "http", "https". Default: None. + + Examples: + >>> # only set backend + >>> file_client = FileClient(backend='petrel') + >>> # only set prefix + >>> file_client = FileClient(prefix='s3') + >>> # set both backend and prefix but use backend to choose client + >>> file_client = FileClient(backend='petrel', prefix='s3') + >>> # if the arguments are the same, the same object is returned + >>> file_client1 = FileClient(backend='petrel') + >>> file_client1 is file_client + True + + Attributes: + client (:obj:`BaseStorageBackend`): The backend object. + """ + + _backends = { + 'disk': HardDiskBackend, + 'ceph': CephBackend, + 'memcached': MemcachedBackend, + 'lmdb': LmdbBackend, + 'petrel': PetrelBackend, + 'http': HTTPBackend, + } + # This collection is used to record the overridden backends, and when a + # backend appears in the collection, the singleton pattern is disabled for + # that backend, because if the singleton pattern is used, then the object + # returned will be the backend before overwriting + _overridden_backends = set() + _prefix_to_backends = { + 's3': PetrelBackend, + 'http': HTTPBackend, + 'https': HTTPBackend, + } + _overridden_prefixes = set() + + _instances = {} + + def __new__(cls, backend=None, prefix=None, **kwargs): + if backend is None and prefix is None: + backend = 'disk' + if backend is not None and backend not in cls._backends: + raise ValueError( + f'Backend {backend} is not supported. Currently supported ones' + f' are {list(cls._backends.keys())}') + if prefix is not None and prefix not in cls._prefix_to_backends: + raise ValueError( + f'prefix {prefix} is not supported. Currently supported ones ' + f'are {list(cls._prefix_to_backends.keys())}') + + # concatenate the arguments to a unique key for determining whether + # objects with the same arguments were created + arg_key = f'{backend}:{prefix}' + for key, value in kwargs.items(): + arg_key += f':{key}:{value}' + + # if a backend was overridden, it will create a new object + if (arg_key in cls._instances + and backend not in cls._overridden_backends + and prefix not in cls._overridden_prefixes): + _instance = cls._instances[arg_key] + else: + # create a new object and put it to _instance + _instance = super().__new__(cls) + if backend is not None: + _instance.client = cls._backends[backend](**kwargs) + else: + _instance.client = cls._prefix_to_backends[prefix](**kwargs) + + cls._instances[arg_key] = _instance + + return _instance + + @property + def name(self): + return self.client.name + + @property + def allow_symlink(self): + return self.client.allow_symlink + + @staticmethod + def parse_uri_prefix(uri: Union[str, Path]) -> Optional[str]: + """Parse the prefix of a uri. + + Args: + uri (str | Path): Uri to be parsed that contains the file prefix. + + Examples: + >>> FileClient.parse_uri_prefix('s3://path/of/your/file') + 's3' + + Returns: + str | None: Return the prefix of uri if the uri contains '://' + else ``None``. + """ + assert is_filepath(uri) + uri = str(uri) + if '://' not in uri: + return None + else: + prefix, _ = uri.split('://') + # In the case of PetrelBackend, the prefix may contains the cluster + # name like clusterName:s3 + if ':' in prefix: + _, prefix = prefix.split(':') + return prefix + + @classmethod + def infer_client(cls, + file_client_args: Optional[dict] = None, + uri: Optional[Union[str, Path]] = None) -> 'FileClient': + """Infer a suitable file client based on the URI and arguments. + + Args: + file_client_args (dict, optional): Arguments to instantiate a + FileClient. Default: None. + uri (str | Path, optional): Uri to be parsed that contains the file + prefix. Default: None. + + Examples: + >>> uri = 's3://path/of/your/file' + >>> file_client = FileClient.infer_client(uri=uri) + >>> file_client_args = {'backend': 'petrel'} + >>> file_client = FileClient.infer_client(file_client_args) + + Returns: + FileClient: Instantiated FileClient object. + """ + assert file_client_args is not None or uri is not None + if file_client_args is None: + file_prefix = cls.parse_uri_prefix(uri) # type: ignore + return cls(prefix=file_prefix) + else: + return cls(**file_client_args) + + @classmethod + def _register_backend(cls, name, backend, force=False, prefixes=None): + if not isinstance(name, str): + raise TypeError('the backend name should be a string, ' + f'but got {type(name)}') + if not inspect.isclass(backend): + raise TypeError( + f'backend should be a class but got {type(backend)}') + if not issubclass(backend, BaseStorageBackend): + raise TypeError( + f'backend {backend} is not a subclass of BaseStorageBackend') + if not force and name in cls._backends: + raise KeyError( + f'{name} is already registered as a storage backend, ' + 'add "force=True" if you want to override it') + + if name in cls._backends and force: + cls._overridden_backends.add(name) + cls._backends[name] = backend + + if prefixes is not None: + if isinstance(prefixes, str): + prefixes = [prefixes] + else: + assert isinstance(prefixes, (list, tuple)) + for prefix in prefixes: + if prefix not in cls._prefix_to_backends: + cls._prefix_to_backends[prefix] = backend + elif (prefix in cls._prefix_to_backends) and force: + cls._overridden_prefixes.add(prefix) + cls._prefix_to_backends[prefix] = backend + else: + raise KeyError( + f'{prefix} is already registered as a storage backend,' + ' add "force=True" if you want to override it') + + @classmethod + def register_backend(cls, name, backend=None, force=False, prefixes=None): + """Register a backend to FileClient. + + This method can be used as a normal class method or a decorator. + + .. code-block:: python + + class NewBackend(BaseStorageBackend): + + def get(self, filepath): + return filepath + + def get_text(self, filepath): + return filepath + + FileClient.register_backend('new', NewBackend) + + or + + .. code-block:: python + + @FileClient.register_backend('new') + class NewBackend(BaseStorageBackend): + + def get(self, filepath): + return filepath + + def get_text(self, filepath): + return filepath + + Args: + name (str): The name of the registered backend. + backend (class, optional): The backend class to be registered, + which must be a subclass of :class:`BaseStorageBackend`. + When this method is used as a decorator, backend is None. + Defaults to None. + force (bool, optional): Whether to override the backend if the name + has already been registered. Defaults to False. + prefixes (str or list[str] or tuple[str], optional): The prefixes + of the registered storage backend. Default: None. + `New in version 1.3.15.` + """ + if backend is not None: + cls._register_backend( + name, backend, force=force, prefixes=prefixes) + return + + def _register(backend_cls): + cls._register_backend( + name, backend_cls, force=force, prefixes=prefixes) + return backend_cls + + return _register + + def get(self, filepath: Union[str, Path]) -> Union[bytes, memoryview]: + """Read data from a given ``filepath`` with 'rb' mode. + + Note: + There are two types of return values for ``get``, one is ``bytes`` + and the other is ``memoryview``. The advantage of using memoryview + is that you can avoid copying, and if you want to convert it to + ``bytes``, you can use ``.tobytes()``. + + Args: + filepath (str or Path): Path to read data. + + Returns: + bytes | memoryview: Expected bytes object or a memory view of the + bytes object. + """ + return self.client.get(filepath) + + def get_text(self, filepath: Union[str, Path], encoding='utf-8') -> str: + """Read data from a given ``filepath`` with 'r' mode. + + Args: + filepath (str or Path): Path to read data. + encoding (str): The encoding format used to open the ``filepath``. + Default: 'utf-8'. + + Returns: + str: Expected text reading from ``filepath``. + """ + return self.client.get_text(filepath, encoding) + + def put(self, obj: bytes, filepath: Union[str, Path]) -> None: + """Write data to a given ``filepath`` with 'wb' mode. + + Note: + ``put`` should create a directory if the directory of ``filepath`` + does not exist. + + Args: + obj (bytes): Data to be written. + filepath (str or Path): Path to write data. + """ + self.client.put(obj, filepath) + + def put_text(self, obj: str, filepath: Union[str, Path]) -> None: + """Write data to a given ``filepath`` with 'w' mode. + + Note: + ``put_text`` should create a directory if the directory of + ``filepath`` does not exist. + + Args: + obj (str): Data to be written. + filepath (str or Path): Path to write data. + encoding (str, optional): The encoding format used to open the + `filepath`. Default: 'utf-8'. + """ + self.client.put_text(obj, filepath) + + def remove(self, filepath: Union[str, Path]) -> None: + """Remove a file. + + Args: + filepath (str, Path): Path to be removed. + """ + self.client.remove(filepath) + + def exists(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path exists. + + Args: + filepath (str or Path): Path to be checked whether exists. + + Returns: + bool: Return ``True`` if ``filepath`` exists, ``False`` otherwise. + """ + return self.client.exists(filepath) + + def isdir(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path is a directory. + + Args: + filepath (str or Path): Path to be checked whether it is a + directory. + + Returns: + bool: Return ``True`` if ``filepath`` points to a directory, + ``False`` otherwise. + """ + return self.client.isdir(filepath) + + def isfile(self, filepath: Union[str, Path]) -> bool: + """Check whether a file path is a file. + + Args: + filepath (str or Path): Path to be checked whether it is a file. + + Returns: + bool: Return ``True`` if ``filepath`` points to a file, ``False`` + otherwise. + """ + return self.client.isfile(filepath) + + def join_path(self, filepath: Union[str, Path], + *filepaths: Union[str, Path]) -> str: + """Concatenate all file paths. + + Join one or more filepath components intelligently. The return value + is the concatenation of filepath and any members of *filepaths. + + Args: + filepath (str or Path): Path to be concatenated. + + Returns: + str: The result of concatenation. + """ + return self.client.join_path(filepath, *filepaths) + + @contextmanager + def get_local_path(self, filepath: Union[str, Path]) -> Iterable[str]: + """Download data from ``filepath`` and write the data to local path. + + ``get_local_path`` is decorated by :meth:`contxtlib.contextmanager`. It + can be called with ``with`` statement, and when exists from the + ``with`` statement, the temporary path will be released. + + Note: + If the ``filepath`` is a local path, just return itself. + + .. warning:: + ``get_local_path`` is an experimental interface that may change in + the future. + + Args: + filepath (str or Path): Path to be read data. + + Examples: + >>> file_client = FileClient(prefix='s3') + >>> with file_client.get_local_path('s3://bucket/abc.jpg') as path: + ... # do something here + + Yields: + Iterable[str]: Only yield one path. + """ + with self.client.get_local_path(str(filepath)) as local_path: + yield local_path + + def list_dir_or_file(self, + dir_path: Union[str, Path], + list_dir: bool = True, + list_file: bool = True, + suffix: Optional[Union[str, Tuple[str]]] = None, + recursive: bool = False) -> Iterator[str]: + """Scan a directory to find the interested directories or files in + arbitrary order. + + Note: + :meth:`list_dir_or_file` returns the path relative to ``dir_path``. + + Args: + dir_path (str | Path): Path of the directory. + list_dir (bool): List the directories. Default: True. + list_file (bool): List the path of files. Default: True. + suffix (str or tuple[str], optional): File suffix + that we are interested in. Default: None. + recursive (bool): If set to True, recursively scan the + directory. Default: False. + + Yields: + Iterable[str]: A relative path to ``dir_path``. + """ + yield from self.client.list_dir_or_file(dir_path, list_dir, list_file, + suffix, recursive) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/__init__.py new file mode 100644 index 0000000..aa24d91 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base import BaseFileHandler +from .json_handler import JsonHandler +from .pickle_handler import PickleHandler +from .yaml_handler import YamlHandler + +__all__ = ['BaseFileHandler', 'JsonHandler', 'PickleHandler', 'YamlHandler'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/base.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/base.py new file mode 100644 index 0000000..288878b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/base.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod + + +class BaseFileHandler(metaclass=ABCMeta): + # `str_like` is a flag to indicate whether the type of file object is + # str-like object or bytes-like object. Pickle only processes bytes-like + # objects but json only processes str-like object. If it is str-like + # object, `StringIO` will be used to process the buffer. + str_like = True + + @abstractmethod + def load_from_fileobj(self, file, **kwargs): + pass + + @abstractmethod + def dump_to_fileobj(self, obj, file, **kwargs): + pass + + @abstractmethod + def dump_to_str(self, obj, **kwargs): + pass + + def load_from_path(self, filepath, mode='r', **kwargs): + with open(filepath, mode) as f: + return self.load_from_fileobj(f, **kwargs) + + def dump_to_path(self, obj, filepath, mode='w', **kwargs): + with open(filepath, mode) as f: + self.dump_to_fileobj(obj, f, **kwargs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/json_handler.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/json_handler.py new file mode 100644 index 0000000..18d4f15 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/json_handler.py @@ -0,0 +1,36 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json + +import numpy as np + +from .base import BaseFileHandler + + +def set_default(obj): + """Set default json values for non-serializable values. + + It helps convert ``set``, ``range`` and ``np.ndarray`` data types to list. + It also converts ``np.generic`` (including ``np.int32``, ``np.float32``, + etc.) into plain numbers of plain python built-in types. + """ + if isinstance(obj, (set, range)): + return list(obj) + elif isinstance(obj, np.ndarray): + return obj.tolist() + elif isinstance(obj, np.generic): + return obj.item() + raise TypeError(f'{type(obj)} is unsupported for json dump') + + +class JsonHandler(BaseFileHandler): + + def load_from_fileobj(self, file): + return json.load(file) + + def dump_to_fileobj(self, obj, file, **kwargs): + kwargs.setdefault('default', set_default) + json.dump(obj, file, **kwargs) + + def dump_to_str(self, obj, **kwargs): + kwargs.setdefault('default', set_default) + return json.dumps(obj, **kwargs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/pickle_handler.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/pickle_handler.py new file mode 100644 index 0000000..b37c79b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/pickle_handler.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pickle + +from .base import BaseFileHandler + + +class PickleHandler(BaseFileHandler): + + str_like = False + + def load_from_fileobj(self, file, **kwargs): + return pickle.load(file, **kwargs) + + def load_from_path(self, filepath, **kwargs): + return super(PickleHandler, self).load_from_path( + filepath, mode='rb', **kwargs) + + def dump_to_str(self, obj, **kwargs): + kwargs.setdefault('protocol', 2) + return pickle.dumps(obj, **kwargs) + + def dump_to_fileobj(self, obj, file, **kwargs): + kwargs.setdefault('protocol', 2) + pickle.dump(obj, file, **kwargs) + + def dump_to_path(self, obj, filepath, **kwargs): + super(PickleHandler, self).dump_to_path( + obj, filepath, mode='wb', **kwargs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/yaml_handler.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/yaml_handler.py new file mode 100644 index 0000000..c5aa2ee --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/handlers/yaml_handler.py @@ -0,0 +1,24 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import yaml + +try: + from yaml import CLoader as Loader, CDumper as Dumper +except ImportError: + from yaml import Loader, Dumper + +from .base import BaseFileHandler # isort:skip + + +class YamlHandler(BaseFileHandler): + + def load_from_fileobj(self, file, **kwargs): + kwargs.setdefault('Loader', Loader) + return yaml.load(file, **kwargs) + + def dump_to_fileobj(self, obj, file, **kwargs): + kwargs.setdefault('Dumper', Dumper) + yaml.dump(obj, file, **kwargs) + + def dump_to_str(self, obj, **kwargs): + kwargs.setdefault('Dumper', Dumper) + return yaml.dump(obj, **kwargs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/io.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/io.py new file mode 100644 index 0000000..aaefde5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/io.py @@ -0,0 +1,151 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from io import BytesIO, StringIO +from pathlib import Path + +from ..utils import is_list_of, is_str +from .file_client import FileClient +from .handlers import BaseFileHandler, JsonHandler, PickleHandler, YamlHandler + +file_handlers = { + 'json': JsonHandler(), + 'yaml': YamlHandler(), + 'yml': YamlHandler(), + 'pickle': PickleHandler(), + 'pkl': PickleHandler() +} + + +def load(file, file_format=None, file_client_args=None, **kwargs): + """Load data from json/yaml/pickle files. + + This method provides a unified api for loading data from serialized files. + + Note: + In v1.3.16 and later, ``load`` supports loading data from serialized + files those can be storaged in different backends. + + Args: + file (str or :obj:`Path` or file-like object): Filename or a file-like + object. + file_format (str, optional): If not specified, the file format will be + inferred from the file extension, otherwise use the specified one. + Currently supported formats include "json", "yaml/yml" and + "pickle/pkl". + file_client_args (dict, optional): Arguments to instantiate a + FileClient. See :class:`mmcv.fileio.FileClient` for details. + Default: None. + + Examples: + >>> load('/path/of/your/file') # file is storaged in disk + >>> load('https://path/of/your/file') # file is storaged in Internet + >>> load('s3://path/of/your/file') # file is storaged in petrel + + Returns: + The content from the file. + """ + if isinstance(file, Path): + file = str(file) + if file_format is None and is_str(file): + file_format = file.split('.')[-1] + if file_format not in file_handlers: + raise TypeError(f'Unsupported format: {file_format}') + + handler = file_handlers[file_format] + if is_str(file): + file_client = FileClient.infer_client(file_client_args, file) + if handler.str_like: + with StringIO(file_client.get_text(file)) as f: + obj = handler.load_from_fileobj(f, **kwargs) + else: + with BytesIO(file_client.get(file)) as f: + obj = handler.load_from_fileobj(f, **kwargs) + elif hasattr(file, 'read'): + obj = handler.load_from_fileobj(file, **kwargs) + else: + raise TypeError('"file" must be a filepath str or a file-object') + return obj + + +def dump(obj, file=None, file_format=None, file_client_args=None, **kwargs): + """Dump data to json/yaml/pickle strings or files. + + This method provides a unified api for dumping data as strings or to files, + and also supports custom arguments for each file format. + + Note: + In v1.3.16 and later, ``dump`` supports dumping data as strings or to + files which is saved to different backends. + + Args: + obj (any): The python object to be dumped. + file (str or :obj:`Path` or file-like object, optional): If not + specified, then the object is dumped to a str, otherwise to a file + specified by the filename or file-like object. + file_format (str, optional): Same as :func:`load`. + file_client_args (dict, optional): Arguments to instantiate a + FileClient. See :class:`mmcv.fileio.FileClient` for details. + Default: None. + + Examples: + >>> dump('hello world', '/path/of/your/file') # disk + >>> dump('hello world', 's3://path/of/your/file') # ceph or petrel + + Returns: + bool: True for success, False otherwise. + """ + if isinstance(file, Path): + file = str(file) + if file_format is None: + if is_str(file): + file_format = file.split('.')[-1] + elif file is None: + raise ValueError( + 'file_format must be specified since file is None') + if file_format not in file_handlers: + raise TypeError(f'Unsupported format: {file_format}') + + handler = file_handlers[file_format] + if file is None: + return handler.dump_to_str(obj, **kwargs) + elif is_str(file): + file_client = FileClient.infer_client(file_client_args, file) + if handler.str_like: + with StringIO() as f: + handler.dump_to_fileobj(obj, f, **kwargs) + file_client.put_text(f.getvalue(), file) + else: + with BytesIO() as f: + handler.dump_to_fileobj(obj, f, **kwargs) + file_client.put(f.getvalue(), file) + elif hasattr(file, 'write'): + handler.dump_to_fileobj(obj, file, **kwargs) + else: + raise TypeError('"file" must be a filename str or a file-object') + + +def _register_handler(handler, file_formats): + """Register a handler for some file extensions. + + Args: + handler (:obj:`BaseFileHandler`): Handler to be registered. + file_formats (str or list[str]): File formats to be handled by this + handler. + """ + if not isinstance(handler, BaseFileHandler): + raise TypeError( + f'handler must be a child of BaseFileHandler, not {type(handler)}') + if isinstance(file_formats, str): + file_formats = [file_formats] + if not is_list_of(file_formats, str): + raise TypeError('file_formats must be a str or a list of str') + for ext in file_formats: + file_handlers[ext] = handler + + +def register_handler(file_formats, **kwargs): + + def wrap(cls): + _register_handler(cls(**kwargs), file_formats) + return cls + + return wrap diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/parse.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/parse.py new file mode 100644 index 0000000..f60f0d6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/fileio/parse.py @@ -0,0 +1,97 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +from io import StringIO + +from .file_client import FileClient + + +def list_from_file(filename, + prefix='', + offset=0, + max_num=0, + encoding='utf-8', + file_client_args=None): + """Load a text file and parse the content as a list of strings. + + Note: + In v1.3.16 and later, ``list_from_file`` supports loading a text file + which can be storaged in different backends and parsing the content as + a list for strings. + + Args: + filename (str): Filename. + prefix (str): The prefix to be inserted to the beginning of each item. + offset (int): The offset of lines. + max_num (int): The maximum number of lines to be read, + zeros and negatives mean no limitation. + encoding (str): Encoding used to open the file. Default utf-8. + file_client_args (dict, optional): Arguments to instantiate a + FileClient. See :class:`mmcv.fileio.FileClient` for details. + Default: None. + + Examples: + >>> list_from_file('/path/of/your/file') # disk + ['hello', 'world'] + >>> list_from_file('s3://path/of/your/file') # ceph or petrel + ['hello', 'world'] + + Returns: + list[str]: A list of strings. + """ + cnt = 0 + item_list = [] + file_client = FileClient.infer_client(file_client_args, filename) + with StringIO(file_client.get_text(filename, encoding)) as f: + for _ in range(offset): + f.readline() + for line in f: + if 0 < max_num <= cnt: + break + item_list.append(prefix + line.rstrip('\n\r')) + cnt += 1 + return item_list + + +def dict_from_file(filename, + key_type=str, + encoding='utf-8', + file_client_args=None): + """Load a text file and parse the content as a dict. + + Each line of the text file will be two or more columns split by + whitespaces or tabs. The first column will be parsed as dict keys, and + the following columns will be parsed as dict values. + + Note: + In v1.3.16 and later, ``dict_from_file`` supports loading a text file + which can be storaged in different backends and parsing the content as + a dict. + + Args: + filename(str): Filename. + key_type(type): Type of the dict keys. str is user by default and + type conversion will be performed if specified. + encoding (str): Encoding used to open the file. Default utf-8. + file_client_args (dict, optional): Arguments to instantiate a + FileClient. See :class:`mmcv.fileio.FileClient` for details. + Default: None. + + Examples: + >>> dict_from_file('/path/of/your/file') # disk + {'key1': 'value1', 'key2': 'value2'} + >>> dict_from_file('s3://path/of/your/file') # ceph or petrel + {'key1': 'value1', 'key2': 'value2'} + + Returns: + dict: The parsed contents. + """ + mapping = {} + file_client = FileClient.infer_client(file_client_args, filename) + with StringIO(file_client.get_text(filename, encoding)) as f: + for line in f: + items = line.rstrip('\n').split() + assert len(items) >= 2 + key = key_type(items[0]) + val = items[1:] if len(items) > 2 else items[1] + mapping[key] = val + return mapping diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/image/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/__init__.py new file mode 100644 index 0000000..d0051d6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/__init__.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .colorspace import (bgr2gray, bgr2hls, bgr2hsv, bgr2rgb, bgr2ycbcr, + gray2bgr, gray2rgb, hls2bgr, hsv2bgr, imconvert, + rgb2bgr, rgb2gray, rgb2ycbcr, ycbcr2bgr, ycbcr2rgb) +from .geometric import (cutout, imcrop, imflip, imflip_, impad, + impad_to_multiple, imrescale, imresize, imresize_like, + imresize_to_multiple, imrotate, imshear, imtranslate, + rescale_size) +from .io import imfrombytes, imread, imwrite, supported_backends, use_backend +from .misc import tensor2imgs +from .photometric import (adjust_brightness, adjust_color, adjust_contrast, + adjust_lighting, adjust_sharpness, auto_contrast, + clahe, imdenormalize, imequalize, iminvert, + imnormalize, imnormalize_, lut_transform, posterize, + solarize) + +__all__ = [ + 'bgr2gray', 'bgr2hls', 'bgr2hsv', 'bgr2rgb', 'gray2bgr', 'gray2rgb', + 'hls2bgr', 'hsv2bgr', 'imconvert', 'rgb2bgr', 'rgb2gray', 'imrescale', + 'imresize', 'imresize_like', 'imresize_to_multiple', 'rescale_size', + 'imcrop', 'imflip', 'imflip_', 'impad', 'impad_to_multiple', 'imrotate', + 'imfrombytes', 'imread', 'imwrite', 'supported_backends', 'use_backend', + 'imdenormalize', 'imnormalize', 'imnormalize_', 'iminvert', 'posterize', + 'solarize', 'rgb2ycbcr', 'bgr2ycbcr', 'ycbcr2rgb', 'ycbcr2bgr', + 'tensor2imgs', 'imshear', 'imtranslate', 'adjust_color', 'imequalize', + 'adjust_brightness', 'adjust_contrast', 'lut_transform', 'clahe', + 'adjust_sharpness', 'auto_contrast', 'cutout', 'adjust_lighting' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/image/colorspace.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/colorspace.py new file mode 100644 index 0000000..8145339 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/colorspace.py @@ -0,0 +1,306 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import cv2 +import numpy as np + + +def imconvert(img, src, dst): + """Convert an image from the src colorspace to dst colorspace. + + Args: + img (ndarray): The input image. + src (str): The source colorspace, e.g., 'rgb', 'hsv'. + dst (str): The destination colorspace, e.g., 'rgb', 'hsv'. + + Returns: + ndarray: The converted image. + """ + code = getattr(cv2, f'COLOR_{src.upper()}2{dst.upper()}') + out_img = cv2.cvtColor(img, code) + return out_img + + +def bgr2gray(img, keepdim=False): + """Convert a BGR image to grayscale image. + + Args: + img (ndarray): The input image. + keepdim (bool): If False (by default), then return the grayscale image + with 2 dims, otherwise 3 dims. + + Returns: + ndarray: The converted grayscale image. + """ + out_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + if keepdim: + out_img = out_img[..., None] + return out_img + + +def rgb2gray(img, keepdim=False): + """Convert a RGB image to grayscale image. + + Args: + img (ndarray): The input image. + keepdim (bool): If False (by default), then return the grayscale image + with 2 dims, otherwise 3 dims. + + Returns: + ndarray: The converted grayscale image. + """ + out_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) + if keepdim: + out_img = out_img[..., None] + return out_img + + +def gray2bgr(img): + """Convert a grayscale image to BGR image. + + Args: + img (ndarray): The input image. + + Returns: + ndarray: The converted BGR image. + """ + img = img[..., None] if img.ndim == 2 else img + out_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + return out_img + + +def gray2rgb(img): + """Convert a grayscale image to RGB image. + + Args: + img (ndarray): The input image. + + Returns: + ndarray: The converted RGB image. + """ + img = img[..., None] if img.ndim == 2 else img + out_img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) + return out_img + + +def _convert_input_type_range(img): + """Convert the type and range of the input image. + + It converts the input image to np.float32 type and range of [0, 1]. + It is mainly used for pre-processing the input image in colorspace + conversion functions such as rgb2ycbcr and ycbcr2rgb. + + Args: + img (ndarray): The input image. It accepts: + 1. np.uint8 type with range [0, 255]; + 2. np.float32 type with range [0, 1]. + + Returns: + (ndarray): The converted image with type of np.float32 and range of + [0, 1]. + """ + img_type = img.dtype + img = img.astype(np.float32) + if img_type == np.float32: + pass + elif img_type == np.uint8: + img /= 255. + else: + raise TypeError('The img type should be np.float32 or np.uint8, ' + f'but got {img_type}') + return img + + +def _convert_output_type_range(img, dst_type): + """Convert the type and range of the image according to dst_type. + + It converts the image to desired type and range. If `dst_type` is np.uint8, + images will be converted to np.uint8 type with range [0, 255]. If + `dst_type` is np.float32, it converts the image to np.float32 type with + range [0, 1]. + It is mainly used for post-processing images in colorspace conversion + functions such as rgb2ycbcr and ycbcr2rgb. + + Args: + img (ndarray): The image to be converted with np.float32 type and + range [0, 255]. + dst_type (np.uint8 | np.float32): If dst_type is np.uint8, it + converts the image to np.uint8 type with range [0, 255]. If + dst_type is np.float32, it converts the image to np.float32 type + with range [0, 1]. + + Returns: + (ndarray): The converted image with desired type and range. + """ + if dst_type not in (np.uint8, np.float32): + raise TypeError('The dst_type should be np.float32 or np.uint8, ' + f'but got {dst_type}') + if dst_type == np.uint8: + img = img.round() + else: + img /= 255. + return img.astype(dst_type) + + +def rgb2ycbcr(img, y_only=False): + """Convert a RGB image to YCbCr image. + + This function produces the same results as Matlab's `rgb2ycbcr` function. + It implements the ITU-R BT.601 conversion for standard-definition + television. See more details in + https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion. + + It differs from a similar function in cv2.cvtColor: `RGB <-> YCrCb`. + In OpenCV, it implements a JPEG conversion. See more details in + https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion. + + Args: + img (ndarray): The input image. It accepts: + 1. np.uint8 type with range [0, 255]; + 2. np.float32 type with range [0, 1]. + y_only (bool): Whether to only return Y channel. Default: False. + + Returns: + ndarray: The converted YCbCr image. The output image has the same type + and range as input image. + """ + img_type = img.dtype + img = _convert_input_type_range(img) + if y_only: + out_img = np.dot(img, [65.481, 128.553, 24.966]) + 16.0 + else: + out_img = np.matmul( + img, [[65.481, -37.797, 112.0], [128.553, -74.203, -93.786], + [24.966, 112.0, -18.214]]) + [16, 128, 128] + out_img = _convert_output_type_range(out_img, img_type) + return out_img + + +def bgr2ycbcr(img, y_only=False): + """Convert a BGR image to YCbCr image. + + The bgr version of rgb2ycbcr. + It implements the ITU-R BT.601 conversion for standard-definition + television. See more details in + https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion. + + It differs from a similar function in cv2.cvtColor: `BGR <-> YCrCb`. + In OpenCV, it implements a JPEG conversion. See more details in + https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion. + + Args: + img (ndarray): The input image. It accepts: + 1. np.uint8 type with range [0, 255]; + 2. np.float32 type with range [0, 1]. + y_only (bool): Whether to only return Y channel. Default: False. + + Returns: + ndarray: The converted YCbCr image. The output image has the same type + and range as input image. + """ + img_type = img.dtype + img = _convert_input_type_range(img) + if y_only: + out_img = np.dot(img, [24.966, 128.553, 65.481]) + 16.0 + else: + out_img = np.matmul( + img, [[24.966, 112.0, -18.214], [128.553, -74.203, -93.786], + [65.481, -37.797, 112.0]]) + [16, 128, 128] + out_img = _convert_output_type_range(out_img, img_type) + return out_img + + +def ycbcr2rgb(img): + """Convert a YCbCr image to RGB image. + + This function produces the same results as Matlab's ycbcr2rgb function. + It implements the ITU-R BT.601 conversion for standard-definition + television. See more details in + https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion. + + It differs from a similar function in cv2.cvtColor: `YCrCb <-> RGB`. + In OpenCV, it implements a JPEG conversion. See more details in + https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion. + + Args: + img (ndarray): The input image. It accepts: + 1. np.uint8 type with range [0, 255]; + 2. np.float32 type with range [0, 1]. + + Returns: + ndarray: The converted RGB image. The output image has the same type + and range as input image. + """ + img_type = img.dtype + img = _convert_input_type_range(img) * 255 + out_img = np.matmul(img, [[0.00456621, 0.00456621, 0.00456621], + [0, -0.00153632, 0.00791071], + [0.00625893, -0.00318811, 0]]) * 255.0 + [ + -222.921, 135.576, -276.836 + ] + out_img = _convert_output_type_range(out_img, img_type) + return out_img + + +def ycbcr2bgr(img): + """Convert a YCbCr image to BGR image. + + The bgr version of ycbcr2rgb. + It implements the ITU-R BT.601 conversion for standard-definition + television. See more details in + https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion. + + It differs from a similar function in cv2.cvtColor: `YCrCb <-> BGR`. + In OpenCV, it implements a JPEG conversion. See more details in + https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion. + + Args: + img (ndarray): The input image. It accepts: + 1. np.uint8 type with range [0, 255]; + 2. np.float32 type with range [0, 1]. + + Returns: + ndarray: The converted BGR image. The output image has the same type + and range as input image. + """ + img_type = img.dtype + img = _convert_input_type_range(img) * 255 + out_img = np.matmul(img, [[0.00456621, 0.00456621, 0.00456621], + [0.00791071, -0.00153632, 0], + [0, -0.00318811, 0.00625893]]) * 255.0 + [ + -276.836, 135.576, -222.921 + ] + out_img = _convert_output_type_range(out_img, img_type) + return out_img + + +def convert_color_factory(src, dst): + + code = getattr(cv2, f'COLOR_{src.upper()}2{dst.upper()}') + + def convert_color(img): + out_img = cv2.cvtColor(img, code) + return out_img + + convert_color.__doc__ = f"""Convert a {src.upper()} image to {dst.upper()} + image. + + Args: + img (ndarray or str): The input image. + + Returns: + ndarray: The converted {dst.upper()} image. + """ + + return convert_color + + +bgr2rgb = convert_color_factory('bgr', 'rgb') + +rgb2bgr = convert_color_factory('rgb', 'bgr') + +bgr2hsv = convert_color_factory('bgr', 'hsv') + +hsv2bgr = convert_color_factory('hsv', 'bgr') + +bgr2hls = convert_color_factory('bgr', 'hls') + +hls2bgr = convert_color_factory('hls', 'bgr') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/image/geometric.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/geometric.py new file mode 100644 index 0000000..cf97c20 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/geometric.py @@ -0,0 +1,728 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numbers + +import cv2 +import numpy as np + +from ..utils import to_2tuple +from .io import imread_backend + +try: + from PIL import Image +except ImportError: + Image = None + + +def _scale_size(size, scale): + """Rescale a size by a ratio. + + Args: + size (tuple[int]): (w, h). + scale (float | tuple(float)): Scaling factor. + + Returns: + tuple[int]: scaled size. + """ + if isinstance(scale, (float, int)): + scale = (scale, scale) + w, h = size + return int(w * float(scale[0]) + 0.5), int(h * float(scale[1]) + 0.5) + + +cv2_interp_codes = { + 'nearest': cv2.INTER_NEAREST, + 'bilinear': cv2.INTER_LINEAR, + 'bicubic': cv2.INTER_CUBIC, + 'area': cv2.INTER_AREA, + 'lanczos': cv2.INTER_LANCZOS4 +} + +if Image is not None: + pillow_interp_codes = { + 'nearest': Image.NEAREST, + 'bilinear': Image.BILINEAR, + 'bicubic': Image.BICUBIC, + 'box': Image.BOX, + 'lanczos': Image.LANCZOS, + 'hamming': Image.HAMMING + } + + +def imresize(img, + size, + return_scale=False, + interpolation='bilinear', + out=None, + backend=None): + """Resize image to a given size. + + Args: + img (ndarray): The input image. + size (tuple[int]): Target size (w, h). + return_scale (bool): Whether to return `w_scale` and `h_scale`. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. + out (ndarray): The output destination. + backend (str | None): The image resize backend type. Options are `cv2`, + `pillow`, `None`. If backend is None, the global imread_backend + specified by ``mmcv.use_backend()`` will be used. Default: None. + + Returns: + tuple | ndarray: (`resized_img`, `w_scale`, `h_scale`) or + `resized_img`. + """ + h, w = img.shape[:2] + if backend is None: + backend = imread_backend + if backend not in ['cv2', 'pillow']: + raise ValueError(f'backend: {backend} is not supported for resize.' + f"Supported backends are 'cv2', 'pillow'") + + if backend == 'pillow': + assert img.dtype == np.uint8, 'Pillow backend only support uint8 type' + pil_image = Image.fromarray(img) + pil_image = pil_image.resize(size, pillow_interp_codes[interpolation]) + resized_img = np.array(pil_image) + else: + resized_img = cv2.resize( + img, size, dst=out, interpolation=cv2_interp_codes[interpolation]) + if not return_scale: + return resized_img + else: + w_scale = size[0] / w + h_scale = size[1] / h + return resized_img, w_scale, h_scale + + +def imresize_to_multiple(img, + divisor, + size=None, + scale_factor=None, + keep_ratio=False, + return_scale=False, + interpolation='bilinear', + out=None, + backend=None): + """Resize image according to a given size or scale factor and then rounds + up the the resized or rescaled image size to the nearest value that can be + divided by the divisor. + + Args: + img (ndarray): The input image. + divisor (int | tuple): Resized image size will be a multiple of + divisor. If divisor is a tuple, divisor should be + (w_divisor, h_divisor). + size (None | int | tuple[int]): Target size (w, h). Default: None. + scale_factor (None | float | tuple[float]): Multiplier for spatial + size. Should match input size if it is a tuple and the 2D style is + (w_scale_factor, h_scale_factor). Default: None. + keep_ratio (bool): Whether to keep the aspect ratio when resizing the + image. Default: False. + return_scale (bool): Whether to return `w_scale` and `h_scale`. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. + out (ndarray): The output destination. + backend (str | None): The image resize backend type. Options are `cv2`, + `pillow`, `None`. If backend is None, the global imread_backend + specified by ``mmcv.use_backend()`` will be used. Default: None. + + Returns: + tuple | ndarray: (`resized_img`, `w_scale`, `h_scale`) or + `resized_img`. + """ + h, w = img.shape[:2] + if size is not None and scale_factor is not None: + raise ValueError('only one of size or scale_factor should be defined') + elif size is None and scale_factor is None: + raise ValueError('one of size or scale_factor should be defined') + elif size is not None: + size = to_2tuple(size) + if keep_ratio: + size = rescale_size((w, h), size, return_scale=False) + else: + size = _scale_size((w, h), scale_factor) + + divisor = to_2tuple(divisor) + size = tuple([int(np.ceil(s / d)) * d for s, d in zip(size, divisor)]) + resized_img, w_scale, h_scale = imresize( + img, + size, + return_scale=True, + interpolation=interpolation, + out=out, + backend=backend) + if return_scale: + return resized_img, w_scale, h_scale + else: + return resized_img + + +def imresize_like(img, + dst_img, + return_scale=False, + interpolation='bilinear', + backend=None): + """Resize image to the same size of a given image. + + Args: + img (ndarray): The input image. + dst_img (ndarray): The target image. + return_scale (bool): Whether to return `w_scale` and `h_scale`. + interpolation (str): Same as :func:`resize`. + backend (str | None): Same as :func:`resize`. + + Returns: + tuple or ndarray: (`resized_img`, `w_scale`, `h_scale`) or + `resized_img`. + """ + h, w = dst_img.shape[:2] + return imresize(img, (w, h), return_scale, interpolation, backend=backend) + + +def rescale_size(old_size, scale, return_scale=False): + """Calculate the new size to be rescaled to. + + Args: + old_size (tuple[int]): The old size (w, h) of image. + scale (float | tuple[int]): The scaling factor or maximum size. + If it is a float number, then the image will be rescaled by this + factor, else if it is a tuple of 2 integers, then the image will + be rescaled as large as possible within the scale. + return_scale (bool): Whether to return the scaling factor besides the + rescaled image size. + + Returns: + tuple[int]: The new rescaled image size. + """ + w, h = old_size + if isinstance(scale, (float, int)): + if scale <= 0: + raise ValueError(f'Invalid scale {scale}, must be positive.') + scale_factor = scale + elif isinstance(scale, tuple): + max_long_edge = max(scale) + max_short_edge = min(scale) + scale_factor = min(max_long_edge / max(h, w), + max_short_edge / min(h, w)) + else: + raise TypeError( + f'Scale must be a number or tuple of int, but got {type(scale)}') + + new_size = _scale_size((w, h), scale_factor) + + if return_scale: + return new_size, scale_factor + else: + return new_size + + +def imrescale(img, + scale, + return_scale=False, + interpolation='bilinear', + backend=None): + """Resize image while keeping the aspect ratio. + + Args: + img (ndarray): The input image. + scale (float | tuple[int]): The scaling factor or maximum size. + If it is a float number, then the image will be rescaled by this + factor, else if it is a tuple of 2 integers, then the image will + be rescaled as large as possible within the scale. + return_scale (bool): Whether to return the scaling factor besides the + rescaled image. + interpolation (str): Same as :func:`resize`. + backend (str | None): Same as :func:`resize`. + + Returns: + ndarray: The rescaled image. + """ + h, w = img.shape[:2] + new_size, scale_factor = rescale_size((w, h), scale, return_scale=True) + rescaled_img = imresize( + img, new_size, interpolation=interpolation, backend=backend) + if return_scale: + return rescaled_img, scale_factor + else: + return rescaled_img + + +def imflip(img, direction='horizontal'): + """Flip an image horizontally or vertically. + + Args: + img (ndarray): Image to be flipped. + direction (str): The flip direction, either "horizontal" or + "vertical" or "diagonal". + + Returns: + ndarray: The flipped image. + """ + assert direction in ['horizontal', 'vertical', 'diagonal'] + if direction == 'horizontal': + return np.flip(img, axis=1) + elif direction == 'vertical': + return np.flip(img, axis=0) + else: + return np.flip(img, axis=(0, 1)) + + +def imflip_(img, direction='horizontal'): + """Inplace flip an image horizontally or vertically. + + Args: + img (ndarray): Image to be flipped. + direction (str): The flip direction, either "horizontal" or + "vertical" or "diagonal". + + Returns: + ndarray: The flipped image (inplace). + """ + assert direction in ['horizontal', 'vertical', 'diagonal'] + if direction == 'horizontal': + return cv2.flip(img, 1, img) + elif direction == 'vertical': + return cv2.flip(img, 0, img) + else: + return cv2.flip(img, -1, img) + + +def imrotate(img, + angle, + center=None, + scale=1.0, + border_value=0, + interpolation='bilinear', + auto_bound=False): + """Rotate an image. + + Args: + img (ndarray): Image to be rotated. + angle (float): Rotation angle in degrees, positive values mean + clockwise rotation. + 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. + scale (float): Isotropic scale factor. + border_value (int): Border value. + interpolation (str): Same as :func:`resize`. + auto_bound (bool): Whether to adjust the image size to cover the whole + rotated image. + + Returns: + ndarray: The rotated image. + """ + if center is not None and auto_bound: + raise ValueError('`auto_bound` conflicts with `center`') + h, w = img.shape[:2] + if center is None: + center = ((w - 1) * 0.5, (h - 1) * 0.5) + assert isinstance(center, tuple) + + matrix = cv2.getRotationMatrix2D(center, -angle, scale) + if auto_bound: + cos = np.abs(matrix[0, 0]) + sin = np.abs(matrix[0, 1]) + new_w = h * sin + w * cos + new_h = h * cos + w * sin + matrix[0, 2] += (new_w - w) * 0.5 + matrix[1, 2] += (new_h - h) * 0.5 + w = int(np.round(new_w)) + h = int(np.round(new_h)) + rotated = cv2.warpAffine( + img, + matrix, (w, h), + flags=cv2_interp_codes[interpolation], + borderValue=border_value) + return rotated + + +def bbox_clip(bboxes, img_shape): + """Clip bboxes to fit the image shape. + + Args: + bboxes (ndarray): Shape (..., 4*k) + img_shape (tuple[int]): (height, width) of the image. + + Returns: + ndarray: Clipped bboxes. + """ + assert bboxes.shape[-1] % 4 == 0 + cmin = np.empty(bboxes.shape[-1], dtype=bboxes.dtype) + cmin[0::2] = img_shape[1] - 1 + cmin[1::2] = img_shape[0] - 1 + clipped_bboxes = np.maximum(np.minimum(bboxes, cmin), 0) + return clipped_bboxes + + +def bbox_scaling(bboxes, scale, clip_shape=None): + """Scaling bboxes w.r.t the box center. + + Args: + bboxes (ndarray): Shape(..., 4). + scale (float): Scaling factor. + clip_shape (tuple[int], optional): If specified, bboxes that exceed the + boundary will be clipped according to the given shape (h, w). + + Returns: + ndarray: Scaled bboxes. + """ + if float(scale) == 1.0: + scaled_bboxes = bboxes.copy() + else: + w = bboxes[..., 2] - bboxes[..., 0] + 1 + h = bboxes[..., 3] - bboxes[..., 1] + 1 + dw = (w * (scale - 1)) * 0.5 + dh = (h * (scale - 1)) * 0.5 + scaled_bboxes = bboxes + np.stack((-dw, -dh, dw, dh), axis=-1) + if clip_shape is not None: + return bbox_clip(scaled_bboxes, clip_shape) + else: + return scaled_bboxes + + +def imcrop(img, bboxes, scale=1.0, pad_fill=None): + """Crop image patches. + + 3 steps: scale the bboxes -> clip bboxes -> crop and pad. + + Args: + img (ndarray): Image to be cropped. + bboxes (ndarray): Shape (k, 4) or (4, ), location of cropped bboxes. + scale (float, optional): Scale ratio of bboxes, the default value + 1.0 means no padding. + pad_fill (Number | list[Number]): Value to be filled for padding. + Default: None, which means no padding. + + Returns: + list[ndarray] | ndarray: The cropped image patches. + """ + chn = 1 if img.ndim == 2 else img.shape[2] + if pad_fill is not None: + if isinstance(pad_fill, (int, float)): + pad_fill = [pad_fill for _ in range(chn)] + assert len(pad_fill) == chn + + _bboxes = bboxes[None, ...] if bboxes.ndim == 1 else bboxes + scaled_bboxes = bbox_scaling(_bboxes, scale).astype(np.int32) + clipped_bbox = bbox_clip(scaled_bboxes, img.shape) + + patches = [] + for i in range(clipped_bbox.shape[0]): + x1, y1, x2, y2 = tuple(clipped_bbox[i, :]) + if pad_fill is None: + patch = img[y1:y2 + 1, x1:x2 + 1, ...] + else: + _x1, _y1, _x2, _y2 = tuple(scaled_bboxes[i, :]) + if chn == 1: + patch_shape = (_y2 - _y1 + 1, _x2 - _x1 + 1) + else: + patch_shape = (_y2 - _y1 + 1, _x2 - _x1 + 1, chn) + patch = np.array( + pad_fill, dtype=img.dtype) * np.ones( + patch_shape, dtype=img.dtype) + x_start = 0 if _x1 >= 0 else -_x1 + y_start = 0 if _y1 >= 0 else -_y1 + w = x2 - x1 + 1 + h = y2 - y1 + 1 + patch[y_start:y_start + h, x_start:x_start + w, + ...] = img[y1:y1 + h, x1:x1 + w, ...] + patches.append(patch) + + if bboxes.ndim == 1: + return patches[0] + else: + return patches + + +def impad(img, + *, + shape=None, + padding=None, + pad_val=0, + padding_mode='constant'): + """Pad the given image to a certain shape or pad on all sides with + specified padding mode and padding value. + + Args: + img (ndarray): Image to be padded. + shape (tuple[int]): Expected padding shape (h, w). Default: None. + padding (int or tuple[int]): Padding on each border. If a single int is + provided this is used to pad all borders. If tuple of length 2 is + provided this is the padding on left/right and top/bottom + respectively. If a tuple of length 4 is provided this is the + padding for the left, top, right and bottom borders respectively. + Default: None. Note that `shape` and `padding` can not be both + set. + pad_val (Number | Sequence[Number]): Values to be filled in padding + areas when padding_mode is 'constant'. Default: 0. + padding_mode (str): Type of padding. Should be: constant, edge, + reflect or symmetric. Default: constant. + + - constant: pads with a constant value, this value is specified + with pad_val. + - edge: pads with the last value at the edge of the image. + - reflect: pads with reflection of image without repeating the + last value on the edge. For example, padding [1, 2, 3, 4] + with 2 elements on both sides in reflect mode will result + in [3, 2, 1, 2, 3, 4, 3, 2]. + - symmetric: pads with reflection of image repeating the last + value on the edge. For example, padding [1, 2, 3, 4] with + 2 elements on both sides in symmetric mode will result in + [2, 1, 1, 2, 3, 4, 4, 3] + + Returns: + ndarray: The padded image. + """ + + assert (shape is not None) ^ (padding is not None) + if shape is not None: + padding = (0, 0, shape[1] - img.shape[1], shape[0] - img.shape[0]) + + # check pad_val + if isinstance(pad_val, tuple): + assert len(pad_val) == img.shape[-1] + elif not isinstance(pad_val, numbers.Number): + raise TypeError('pad_val must be a int or a tuple. ' + f'But received {type(pad_val)}') + + # check padding + if isinstance(padding, tuple) and len(padding) in [2, 4]: + if len(padding) == 2: + padding = (padding[0], padding[1], padding[0], padding[1]) + elif isinstance(padding, numbers.Number): + padding = (padding, padding, padding, padding) + else: + raise ValueError('Padding must be a int or a 2, or 4 element tuple.' + f'But received {padding}') + + # check padding mode + assert padding_mode in ['constant', 'edge', 'reflect', 'symmetric'] + + border_type = { + 'constant': cv2.BORDER_CONSTANT, + 'edge': cv2.BORDER_REPLICATE, + 'reflect': cv2.BORDER_REFLECT_101, + 'symmetric': cv2.BORDER_REFLECT + } + img = cv2.copyMakeBorder( + img, + padding[1], + padding[3], + padding[0], + padding[2], + border_type[padding_mode], + value=pad_val) + + return img + + +def impad_to_multiple(img, divisor, pad_val=0): + """Pad an image to ensure each edge to be multiple to some number. + + Args: + img (ndarray): Image to be padded. + divisor (int): Padded image edges will be multiple to divisor. + pad_val (Number | Sequence[Number]): Same as :func:`impad`. + + Returns: + ndarray: The padded image. + """ + pad_h = int(np.ceil(img.shape[0] / divisor)) * divisor + pad_w = int(np.ceil(img.shape[1] / divisor)) * divisor + return impad(img, shape=(pad_h, pad_w), pad_val=pad_val) + + +def cutout(img, shape, pad_val=0): + """Randomly cut out a rectangle from the original img. + + Args: + img (ndarray): Image to be cutout. + shape (int | tuple[int]): Expected cutout shape (h, w). If given as a + int, the value will be used for both h and w. + pad_val (int | float | tuple[int | float]): Values to be filled in the + cut area. Defaults to 0. + + Returns: + ndarray: The cutout image. + """ + + channels = 1 if img.ndim == 2 else img.shape[2] + if isinstance(shape, int): + cut_h, cut_w = shape, shape + else: + assert isinstance(shape, tuple) and len(shape) == 2, \ + f'shape must be a int or a tuple with length 2, but got type ' \ + f'{type(shape)} instead.' + cut_h, cut_w = shape + if isinstance(pad_val, (int, float)): + pad_val = tuple([pad_val] * channels) + elif isinstance(pad_val, tuple): + assert len(pad_val) == channels, \ + 'Expected the num of elements in tuple equals the channels' \ + 'of input image. Found {} vs {}'.format( + len(pad_val), channels) + else: + raise TypeError(f'Invalid type {type(pad_val)} for `pad_val`') + + img_h, img_w = img.shape[:2] + y0 = np.random.uniform(img_h) + x0 = np.random.uniform(img_w) + + y1 = int(max(0, y0 - cut_h / 2.)) + x1 = int(max(0, x0 - cut_w / 2.)) + y2 = min(img_h, y1 + cut_h) + x2 = min(img_w, x1 + cut_w) + + if img.ndim == 2: + patch_shape = (y2 - y1, x2 - x1) + else: + patch_shape = (y2 - y1, x2 - x1, channels) + + img_cutout = img.copy() + patch = np.array( + pad_val, dtype=img.dtype) * np.ones( + patch_shape, dtype=img.dtype) + img_cutout[y1:y2, x1:x2, ...] = patch + + return img_cutout + + +def _get_shear_matrix(magnitude, direction='horizontal'): + """Generate the shear matrix for transformation. + + Args: + magnitude (int | float): The magnitude used for shear. + direction (str): The flip direction, either "horizontal" + or "vertical". + + Returns: + ndarray: The shear matrix with dtype float32. + """ + if direction == 'horizontal': + shear_matrix = np.float32([[1, magnitude, 0], [0, 1, 0]]) + elif direction == 'vertical': + shear_matrix = np.float32([[1, 0, 0], [magnitude, 1, 0]]) + return shear_matrix + + +def imshear(img, + magnitude, + direction='horizontal', + border_value=0, + interpolation='bilinear'): + """Shear an image. + + Args: + img (ndarray): Image to be sheared with format (h, w) + or (h, w, c). + magnitude (int | float): The magnitude used for shear. + direction (str): The flip direction, either "horizontal" + or "vertical". + border_value (int | tuple[int]): Value used in case of a + constant border. + interpolation (str): Same as :func:`resize`. + + Returns: + ndarray: The sheared image. + """ + assert direction in ['horizontal', + 'vertical'], f'Invalid direction: {direction}' + height, width = img.shape[:2] + if img.ndim == 2: + channels = 1 + elif img.ndim == 3: + channels = img.shape[-1] + if isinstance(border_value, int): + border_value = tuple([border_value] * channels) + elif isinstance(border_value, tuple): + assert len(border_value) == channels, \ + 'Expected the num of elements in tuple equals the channels' \ + 'of input image. Found {} vs {}'.format( + len(border_value), channels) + else: + raise ValueError( + f'Invalid type {type(border_value)} for `border_value`') + shear_matrix = _get_shear_matrix(magnitude, direction) + sheared = cv2.warpAffine( + img, + shear_matrix, + (width, height), + # Note case when the number elements in `border_value` + # greater than 3 (e.g. shearing masks whose channels large + # than 3) will raise TypeError in `cv2.warpAffine`. + # Here simply slice the first 3 values in `border_value`. + borderValue=border_value[:3], + flags=cv2_interp_codes[interpolation]) + return sheared + + +def _get_translate_matrix(offset, direction='horizontal'): + """Generate the translate matrix. + + Args: + offset (int | float): The offset used for translate. + direction (str): The translate direction, either + "horizontal" or "vertical". + + Returns: + ndarray: The translate matrix with dtype float32. + """ + if direction == 'horizontal': + translate_matrix = np.float32([[1, 0, offset], [0, 1, 0]]) + elif direction == 'vertical': + translate_matrix = np.float32([[1, 0, 0], [0, 1, offset]]) + return translate_matrix + + +def imtranslate(img, + offset, + direction='horizontal', + border_value=0, + interpolation='bilinear'): + """Translate an image. + + Args: + img (ndarray): Image to be translated with format + (h, w) or (h, w, c). + offset (int | float): The offset used for translate. + direction (str): The translate direction, either "horizontal" + or "vertical". + border_value (int | tuple[int]): Value used in case of a + constant border. + interpolation (str): Same as :func:`resize`. + + Returns: + ndarray: The translated image. + """ + assert direction in ['horizontal', + 'vertical'], f'Invalid direction: {direction}' + height, width = img.shape[:2] + if img.ndim == 2: + channels = 1 + elif img.ndim == 3: + channels = img.shape[-1] + if isinstance(border_value, int): + border_value = tuple([border_value] * channels) + elif isinstance(border_value, tuple): + assert len(border_value) == channels, \ + 'Expected the num of elements in tuple equals the channels' \ + 'of input image. Found {} vs {}'.format( + len(border_value), channels) + else: + raise ValueError( + f'Invalid type {type(border_value)} for `border_value`.') + translate_matrix = _get_translate_matrix(offset, direction) + translated = cv2.warpAffine( + img, + translate_matrix, + (width, height), + # Note case when the number elements in `border_value` + # greater than 3 (e.g. translating masks whose channels + # large than 3) will raise TypeError in `cv2.warpAffine`. + # Here simply slice the first 3 values in `border_value`. + borderValue=border_value[:3], + flags=cv2_interp_codes[interpolation]) + return translated diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/image/io.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/io.py new file mode 100644 index 0000000..d3fa2e8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/io.py @@ -0,0 +1,258 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import io +import os.path as osp +from pathlib import Path + +import cv2 +import numpy as np +from cv2 import (IMREAD_COLOR, IMREAD_GRAYSCALE, IMREAD_IGNORE_ORIENTATION, + IMREAD_UNCHANGED) + +from annotator.uniformer.mmcv.utils import check_file_exist, is_str, mkdir_or_exist + +try: + from turbojpeg import TJCS_RGB, TJPF_BGR, TJPF_GRAY, TurboJPEG +except ImportError: + TJCS_RGB = TJPF_GRAY = TJPF_BGR = TurboJPEG = None + +try: + from PIL import Image, ImageOps +except ImportError: + Image = None + +try: + import tifffile +except ImportError: + tifffile = None + +jpeg = None +supported_backends = ['cv2', 'turbojpeg', 'pillow', 'tifffile'] + +imread_flags = { + 'color': IMREAD_COLOR, + 'grayscale': IMREAD_GRAYSCALE, + 'unchanged': IMREAD_UNCHANGED, + 'color_ignore_orientation': IMREAD_IGNORE_ORIENTATION | IMREAD_COLOR, + 'grayscale_ignore_orientation': + IMREAD_IGNORE_ORIENTATION | IMREAD_GRAYSCALE +} + +imread_backend = 'cv2' + + +def use_backend(backend): + """Select a backend for image decoding. + + Args: + backend (str): The image decoding backend type. Options are `cv2`, + `pillow`, `turbojpeg` (see https://github.com/lilohuang/PyTurboJPEG) + and `tifffile`. `turbojpeg` is faster but it only supports `.jpeg` + file format. + """ + assert backend in supported_backends + global imread_backend + imread_backend = backend + if imread_backend == 'turbojpeg': + if TurboJPEG is None: + raise ImportError('`PyTurboJPEG` is not installed') + global jpeg + if jpeg is None: + jpeg = TurboJPEG() + elif imread_backend == 'pillow': + if Image is None: + raise ImportError('`Pillow` is not installed') + elif imread_backend == 'tifffile': + if tifffile is None: + raise ImportError('`tifffile` is not installed') + + +def _jpegflag(flag='color', channel_order='bgr'): + channel_order = channel_order.lower() + if channel_order not in ['rgb', 'bgr']: + raise ValueError('channel order must be either "rgb" or "bgr"') + + if flag == 'color': + if channel_order == 'bgr': + return TJPF_BGR + elif channel_order == 'rgb': + return TJCS_RGB + elif flag == 'grayscale': + return TJPF_GRAY + else: + raise ValueError('flag must be "color" or "grayscale"') + + +def _pillow2array(img, flag='color', channel_order='bgr'): + """Convert a pillow image to numpy array. + + Args: + img (:obj:`PIL.Image.Image`): The image loaded using PIL + flag (str): Flags specifying the color type of a loaded image, + candidates are 'color', 'grayscale' and 'unchanged'. + Default to 'color'. + channel_order (str): The channel order of the output image array, + candidates are 'bgr' and 'rgb'. Default to 'bgr'. + + Returns: + np.ndarray: The converted numpy array + """ + channel_order = channel_order.lower() + if channel_order not in ['rgb', 'bgr']: + raise ValueError('channel order must be either "rgb" or "bgr"') + + if flag == 'unchanged': + array = np.array(img) + if array.ndim >= 3 and array.shape[2] >= 3: # color image + array[:, :, :3] = array[:, :, (2, 1, 0)] # RGB to BGR + else: + # Handle exif orientation tag + if flag in ['color', 'grayscale']: + img = ImageOps.exif_transpose(img) + # If the image mode is not 'RGB', convert it to 'RGB' first. + if img.mode != 'RGB': + if img.mode != 'LA': + # Most formats except 'LA' can be directly converted to RGB + img = img.convert('RGB') + else: + # When the mode is 'LA', the default conversion will fill in + # the canvas with black, which sometimes shadows black objects + # in the foreground. + # + # Therefore, a random color (124, 117, 104) is used for canvas + img_rgba = img.convert('RGBA') + img = Image.new('RGB', img_rgba.size, (124, 117, 104)) + img.paste(img_rgba, mask=img_rgba.split()[3]) # 3 is alpha + if flag in ['color', 'color_ignore_orientation']: + array = np.array(img) + if channel_order != 'rgb': + array = array[:, :, ::-1] # RGB to BGR + elif flag in ['grayscale', 'grayscale_ignore_orientation']: + img = img.convert('L') + array = np.array(img) + else: + raise ValueError( + 'flag must be "color", "grayscale", "unchanged", ' + f'"color_ignore_orientation" or "grayscale_ignore_orientation"' + f' but got {flag}') + return array + + +def imread(img_or_path, flag='color', channel_order='bgr', backend=None): + """Read an image. + + Args: + img_or_path (ndarray or str or Path): Either a numpy array or str or + pathlib.Path. If it is a numpy array (loaded image), then + it will be returned as is. + flag (str): Flags specifying the color type of a loaded image, + candidates are `color`, `grayscale`, `unchanged`, + `color_ignore_orientation` and `grayscale_ignore_orientation`. + By default, `cv2` and `pillow` backend would rotate the image + according to its EXIF info unless called with `unchanged` or + `*_ignore_orientation` flags. `turbojpeg` and `tifffile` backend + always ignore image's EXIF info regardless of the flag. + The `turbojpeg` backend only supports `color` and `grayscale`. + channel_order (str): Order of channel, candidates are `bgr` and `rgb`. + backend (str | None): The image decoding backend type. Options are + `cv2`, `pillow`, `turbojpeg`, `tifffile`, `None`. + If backend is None, the global imread_backend specified by + ``mmcv.use_backend()`` will be used. Default: None. + + Returns: + ndarray: Loaded image array. + """ + + if backend is None: + backend = imread_backend + if backend not in supported_backends: + raise ValueError(f'backend: {backend} is not supported. Supported ' + "backends are 'cv2', 'turbojpeg', 'pillow'") + if isinstance(img_or_path, Path): + img_or_path = str(img_or_path) + + if isinstance(img_or_path, np.ndarray): + return img_or_path + elif is_str(img_or_path): + check_file_exist(img_or_path, + f'img file does not exist: {img_or_path}') + if backend == 'turbojpeg': + with open(img_or_path, 'rb') as in_file: + img = jpeg.decode(in_file.read(), + _jpegflag(flag, channel_order)) + if img.shape[-1] == 1: + img = img[:, :, 0] + return img + elif backend == 'pillow': + img = Image.open(img_or_path) + img = _pillow2array(img, flag, channel_order) + return img + elif backend == 'tifffile': + img = tifffile.imread(img_or_path) + return img + else: + flag = imread_flags[flag] if is_str(flag) else flag + img = cv2.imread(img_or_path, flag) + if flag == IMREAD_COLOR and channel_order == 'rgb': + cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) + return img + else: + raise TypeError('"img" must be a numpy array or a str or ' + 'a pathlib.Path object') + + +def imfrombytes(content, flag='color', channel_order='bgr', backend=None): + """Read an image from bytes. + + Args: + content (bytes): Image bytes got from files or other streams. + flag (str): Same as :func:`imread`. + backend (str | None): The image decoding backend type. Options are + `cv2`, `pillow`, `turbojpeg`, `None`. If backend is None, the + global imread_backend specified by ``mmcv.use_backend()`` will be + used. Default: None. + + Returns: + ndarray: Loaded image array. + """ + + if backend is None: + backend = imread_backend + if backend not in supported_backends: + raise ValueError(f'backend: {backend} is not supported. Supported ' + "backends are 'cv2', 'turbojpeg', 'pillow'") + if backend == 'turbojpeg': + img = jpeg.decode(content, _jpegflag(flag, channel_order)) + if img.shape[-1] == 1: + img = img[:, :, 0] + return img + elif backend == 'pillow': + buff = io.BytesIO(content) + img = Image.open(buff) + img = _pillow2array(img, flag, channel_order) + return img + else: + img_np = np.frombuffer(content, np.uint8) + flag = imread_flags[flag] if is_str(flag) else flag + img = cv2.imdecode(img_np, flag) + if flag == IMREAD_COLOR and channel_order == 'rgb': + cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) + return img + + +def imwrite(img, file_path, params=None, auto_mkdir=True): + """Write image to file. + + Args: + img (ndarray): Image array to be written. + file_path (str): Image file path. + params (None or list): Same as opencv :func:`imwrite` interface. + auto_mkdir (bool): If the parent folder of `file_path` does not exist, + whether to create it automatically. + + Returns: + bool: Successful or not. + """ + if auto_mkdir: + dir_name = osp.abspath(osp.dirname(file_path)) + mkdir_or_exist(dir_name) + return cv2.imwrite(file_path, img, params) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/image/misc.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/misc.py new file mode 100644 index 0000000..3e61f05 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/misc.py @@ -0,0 +1,44 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np + +import annotator.uniformer.mmcv as mmcv + +try: + import torch +except ImportError: + torch = None + + +def tensor2imgs(tensor, mean=(0, 0, 0), std=(1, 1, 1), to_rgb=True): + """Convert tensor to 3-channel images. + + Args: + tensor (torch.Tensor): Tensor that contains multiple images, shape ( + N, C, H, W). + mean (tuple[float], optional): Mean of images. Defaults to (0, 0, 0). + std (tuple[float], optional): Standard deviation of images. + Defaults to (1, 1, 1). + to_rgb (bool, optional): Whether the tensor was converted to RGB + format in the first place. If so, convert it back to BGR. + Defaults to True. + + Returns: + list[np.ndarray]: A list that contains multiple images. + """ + + if torch is None: + raise RuntimeError('pytorch is not installed') + assert torch.is_tensor(tensor) and tensor.ndim == 4 + assert len(mean) == 3 + assert len(std) == 3 + + num_imgs = tensor.size(0) + mean = np.array(mean, dtype=np.float32) + std = np.array(std, dtype=np.float32) + imgs = [] + for img_id in range(num_imgs): + img = tensor[img_id, ...].cpu().numpy().transpose(1, 2, 0) + img = mmcv.imdenormalize( + img, mean, std, to_bgr=to_rgb).astype(np.uint8) + imgs.append(np.ascontiguousarray(img)) + return imgs diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/image/photometric.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/photometric.py new file mode 100644 index 0000000..5085d01 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/image/photometric.py @@ -0,0 +1,428 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import cv2 +import numpy as np + +from ..utils import is_tuple_of +from .colorspace import bgr2gray, gray2bgr + + +def imnormalize(img, mean, std, to_rgb=True): + """Normalize an image with mean and std. + + Args: + img (ndarray): Image to be normalized. + mean (ndarray): The mean to be used for normalize. + std (ndarray): The std to be used for normalize. + to_rgb (bool): Whether to convert to rgb. + + Returns: + ndarray: The normalized image. + """ + img = img.copy().astype(np.float32) + return imnormalize_(img, mean, std, to_rgb) + + +def imnormalize_(img, mean, std, to_rgb=True): + """Inplace normalize an image with mean and std. + + Args: + img (ndarray): Image to be normalized. + mean (ndarray): The mean to be used for normalize. + std (ndarray): The std to be used for normalize. + to_rgb (bool): Whether to convert to rgb. + + Returns: + ndarray: The normalized image. + """ + # cv2 inplace normalization does not accept uint8 + assert img.dtype != np.uint8 + mean = np.float64(mean.reshape(1, -1)) + stdinv = 1 / np.float64(std.reshape(1, -1)) + if to_rgb: + cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) # inplace + cv2.subtract(img, mean, img) # inplace + cv2.multiply(img, stdinv, img) # inplace + return img + + +def imdenormalize(img, mean, std, to_bgr=True): + assert img.dtype != np.uint8 + mean = mean.reshape(1, -1).astype(np.float64) + std = std.reshape(1, -1).astype(np.float64) + img = cv2.multiply(img, std) # make a copy + cv2.add(img, mean, img) # inplace + if to_bgr: + cv2.cvtColor(img, cv2.COLOR_RGB2BGR, img) # inplace + return img + + +def iminvert(img): + """Invert (negate) an image. + + Args: + img (ndarray): Image to be inverted. + + Returns: + ndarray: The inverted image. + """ + return np.full_like(img, 255) - img + + +def solarize(img, thr=128): + """Solarize an image (invert all pixel values above a threshold) + + Args: + img (ndarray): Image to be solarized. + thr (int): Threshold for solarizing (0 - 255). + + Returns: + ndarray: The solarized image. + """ + img = np.where(img < thr, img, 255 - img) + return img + + +def posterize(img, bits): + """Posterize an image (reduce the number of bits for each color channel) + + Args: + img (ndarray): Image to be posterized. + bits (int): Number of bits (1 to 8) to use for posterizing. + + Returns: + ndarray: The posterized image. + """ + shift = 8 - bits + img = np.left_shift(np.right_shift(img, shift), shift) + return img + + +def adjust_color(img, alpha=1, beta=None, gamma=0): + r"""It blends the source image and its gray image: + + .. math:: + output = img * alpha + gray\_img * beta + gamma + + Args: + img (ndarray): The input source image. + alpha (int | float): Weight for the source image. Default 1. + beta (int | float): Weight for the converted gray image. + If None, it's assigned the value (1 - `alpha`). + gamma (int | float): Scalar added to each sum. + Same as :func:`cv2.addWeighted`. Default 0. + + Returns: + ndarray: Colored image which has the same size and dtype as input. + """ + gray_img = bgr2gray(img) + gray_img = np.tile(gray_img[..., None], [1, 1, 3]) + if beta is None: + beta = 1 - alpha + colored_img = cv2.addWeighted(img, alpha, gray_img, beta, gamma) + if not colored_img.dtype == np.uint8: + # Note when the dtype of `img` is not the default `np.uint8` + # (e.g. np.float32), the value in `colored_img` got from cv2 + # is not guaranteed to be in range [0, 255], so here clip + # is needed. + colored_img = np.clip(colored_img, 0, 255) + return colored_img + + +def imequalize(img): + """Equalize the image histogram. + + This function applies a non-linear mapping to the input image, + in order to create a uniform distribution of grayscale values + in the output image. + + Args: + img (ndarray): Image to be equalized. + + Returns: + ndarray: The equalized image. + """ + + def _scale_channel(im, c): + """Scale the data in the corresponding channel.""" + im = im[:, :, c] + # Compute the histogram of the image channel. + histo = np.histogram(im, 256, (0, 255))[0] + # For computing the step, filter out the nonzeros. + nonzero_histo = histo[histo > 0] + step = (np.sum(nonzero_histo) - nonzero_histo[-1]) // 255 + if not step: + lut = np.array(range(256)) + else: + # Compute the cumulative sum, shifted by step // 2 + # and then normalized by step. + lut = (np.cumsum(histo) + (step // 2)) // step + # Shift lut, prepending with 0. + lut = np.concatenate([[0], lut[:-1]], 0) + # handle potential integer overflow + lut[lut > 255] = 255 + # If step is zero, return the original image. + # Otherwise, index from lut. + return np.where(np.equal(step, 0), im, lut[im]) + + # Scales each channel independently and then stacks + # the result. + s1 = _scale_channel(img, 0) + s2 = _scale_channel(img, 1) + s3 = _scale_channel(img, 2) + equalized_img = np.stack([s1, s2, s3], axis=-1) + return equalized_img.astype(img.dtype) + + +def adjust_brightness(img, factor=1.): + """Adjust image brightness. + + This function controls the brightness of an image. An + enhancement factor of 0.0 gives a black image. + A factor of 1.0 gives the original image. This function + blends the source image and the degenerated black image: + + .. math:: + output = img * factor + degenerated * (1 - factor) + + Args: + img (ndarray): Image to be brightened. + factor (float): A value controls the enhancement. + Factor 1.0 returns the original image, lower + factors mean less color (brightness, contrast, + etc), and higher values more. Default 1. + + Returns: + ndarray: The brightened image. + """ + degenerated = np.zeros_like(img) + # Note manually convert the dtype to np.float32, to + # achieve as close results as PIL.ImageEnhance.Brightness. + # Set beta=1-factor, and gamma=0 + brightened_img = cv2.addWeighted( + img.astype(np.float32), factor, degenerated.astype(np.float32), + 1 - factor, 0) + brightened_img = np.clip(brightened_img, 0, 255) + return brightened_img.astype(img.dtype) + + +def adjust_contrast(img, factor=1.): + """Adjust image contrast. + + This function controls the contrast of an image. An + enhancement factor of 0.0 gives a solid grey + image. A factor of 1.0 gives the original image. It + blends the source image and the degenerated mean image: + + .. math:: + output = img * factor + degenerated * (1 - factor) + + Args: + img (ndarray): Image to be contrasted. BGR order. + factor (float): Same as :func:`mmcv.adjust_brightness`. + + Returns: + ndarray: The contrasted image. + """ + gray_img = bgr2gray(img) + hist = np.histogram(gray_img, 256, (0, 255))[0] + mean = round(np.sum(gray_img) / np.sum(hist)) + degenerated = (np.ones_like(img[..., 0]) * mean).astype(img.dtype) + degenerated = gray2bgr(degenerated) + contrasted_img = cv2.addWeighted( + img.astype(np.float32), factor, degenerated.astype(np.float32), + 1 - factor, 0) + contrasted_img = np.clip(contrasted_img, 0, 255) + return contrasted_img.astype(img.dtype) + + +def auto_contrast(img, cutoff=0): + """Auto adjust image contrast. + + This function maximize (normalize) image contrast by first removing cutoff + percent of the lightest and darkest pixels from the histogram and remapping + the image so that the darkest pixel becomes black (0), and the lightest + becomes white (255). + + Args: + img (ndarray): Image to be contrasted. BGR order. + cutoff (int | float | tuple): The cutoff percent of the lightest and + darkest pixels to be removed. If given as tuple, it shall be + (low, high). Otherwise, the single value will be used for both. + Defaults to 0. + + Returns: + ndarray: The contrasted image. + """ + + def _auto_contrast_channel(im, c, cutoff): + im = im[:, :, c] + # Compute the histogram of the image channel. + histo = np.histogram(im, 256, (0, 255))[0] + # Remove cut-off percent pixels from histo + histo_sum = np.cumsum(histo) + cut_low = histo_sum[-1] * cutoff[0] // 100 + cut_high = histo_sum[-1] - histo_sum[-1] * cutoff[1] // 100 + histo_sum = np.clip(histo_sum, cut_low, cut_high) - cut_low + histo = np.concatenate([[histo_sum[0]], np.diff(histo_sum)], 0) + + # Compute mapping + low, high = np.nonzero(histo)[0][0], np.nonzero(histo)[0][-1] + # If all the values have been cut off, return the origin img + if low >= high: + return im + scale = 255.0 / (high - low) + offset = -low * scale + lut = np.array(range(256)) + lut = lut * scale + offset + lut = np.clip(lut, 0, 255) + return lut[im] + + if isinstance(cutoff, (int, float)): + cutoff = (cutoff, cutoff) + else: + assert isinstance(cutoff, tuple), 'cutoff must be of type int, ' \ + f'float or tuple, but got {type(cutoff)} instead.' + # Auto adjusts contrast for each channel independently and then stacks + # the result. + s1 = _auto_contrast_channel(img, 0, cutoff) + s2 = _auto_contrast_channel(img, 1, cutoff) + s3 = _auto_contrast_channel(img, 2, cutoff) + contrasted_img = np.stack([s1, s2, s3], axis=-1) + return contrasted_img.astype(img.dtype) + + +def adjust_sharpness(img, factor=1., kernel=None): + """Adjust image sharpness. + + This function controls the sharpness of an image. An + enhancement factor of 0.0 gives a blurred image. A + factor of 1.0 gives the original image. And a factor + of 2.0 gives a sharpened image. It blends the source + image and the degenerated mean image: + + .. math:: + output = img * factor + degenerated * (1 - factor) + + Args: + img (ndarray): Image to be sharpened. BGR order. + factor (float): Same as :func:`mmcv.adjust_brightness`. + kernel (np.ndarray, optional): Filter kernel to be applied on the img + to obtain the degenerated img. Defaults to None. + + Note: + No value sanity check is enforced on the kernel set by users. So with + an inappropriate kernel, the ``adjust_sharpness`` may fail to perform + the function its name indicates but end up performing whatever + transform determined by the kernel. + + Returns: + ndarray: The sharpened image. + """ + + if kernel is None: + # adopted from PIL.ImageFilter.SMOOTH + kernel = np.array([[1., 1., 1.], [1., 5., 1.], [1., 1., 1.]]) / 13 + assert isinstance(kernel, np.ndarray), \ + f'kernel must be of type np.ndarray, but got {type(kernel)} instead.' + assert kernel.ndim == 2, \ + f'kernel must have a dimension of 2, but got {kernel.ndim} instead.' + + degenerated = cv2.filter2D(img, -1, kernel) + sharpened_img = cv2.addWeighted( + img.astype(np.float32), factor, degenerated.astype(np.float32), + 1 - factor, 0) + sharpened_img = np.clip(sharpened_img, 0, 255) + return sharpened_img.astype(img.dtype) + + +def adjust_lighting(img, eigval, eigvec, alphastd=0.1, to_rgb=True): + """AlexNet-style PCA jitter. + + This data augmentation is proposed in `ImageNet Classification with Deep + Convolutional Neural Networks + `_. + + Args: + img (ndarray): Image to be adjusted lighting. BGR order. + eigval (ndarray): the eigenvalue of the convariance matrix of pixel + values, respectively. + eigvec (ndarray): the eigenvector of the convariance matrix of pixel + values, respectively. + alphastd (float): The standard deviation for distribution of alpha. + Defaults to 0.1 + to_rgb (bool): Whether to convert img to rgb. + + Returns: + ndarray: The adjusted image. + """ + assert isinstance(eigval, np.ndarray) and isinstance(eigvec, np.ndarray), \ + f'eigval and eigvec should both be of type np.ndarray, got ' \ + f'{type(eigval)} and {type(eigvec)} instead.' + + assert eigval.ndim == 1 and eigvec.ndim == 2 + assert eigvec.shape == (3, eigval.shape[0]) + n_eigval = eigval.shape[0] + assert isinstance(alphastd, float), 'alphastd should be of type float, ' \ + f'got {type(alphastd)} instead.' + + img = img.copy().astype(np.float32) + if to_rgb: + cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) # inplace + + alpha = np.random.normal(0, alphastd, n_eigval) + alter = eigvec \ + * np.broadcast_to(alpha.reshape(1, n_eigval), (3, n_eigval)) \ + * np.broadcast_to(eigval.reshape(1, n_eigval), (3, n_eigval)) + alter = np.broadcast_to(alter.sum(axis=1).reshape(1, 1, 3), img.shape) + img_adjusted = img + alter + return img_adjusted + + +def lut_transform(img, lut_table): + """Transform array by look-up table. + + The function lut_transform fills the output array with values from the + look-up table. Indices of the entries are taken from the input array. + + Args: + img (ndarray): Image to be transformed. + lut_table (ndarray): look-up table of 256 elements; in case of + multi-channel input array, the table should either have a single + channel (in this case the same table is used for all channels) or + the same number of channels as in the input array. + + Returns: + ndarray: The transformed image. + """ + assert isinstance(img, np.ndarray) + assert 0 <= np.min(img) and np.max(img) <= 255 + assert isinstance(lut_table, np.ndarray) + assert lut_table.shape == (256, ) + + return cv2.LUT(np.array(img, dtype=np.uint8), lut_table) + + +def clahe(img, clip_limit=40.0, tile_grid_size=(8, 8)): + """Use CLAHE method to process the image. + + See `ZUIDERVELD,K. Contrast Limited Adaptive Histogram Equalization[J]. + Graphics Gems, 1994:474-485.` for more information. + + Args: + img (ndarray): Image to be processed. + 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). + + Returns: + ndarray: The processed image. + """ + assert isinstance(img, np.ndarray) + assert img.ndim == 2 + assert isinstance(clip_limit, (float, int)) + assert is_tuple_of(tile_grid_size, int) + assert len(tile_grid_size) == 2 + + clahe = cv2.createCLAHE(clip_limit, tile_grid_size) + return clahe.apply(np.array(img, dtype=np.uint8)) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/deprecated.json b/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/deprecated.json new file mode 100644 index 0000000..25cf6f2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/deprecated.json @@ -0,0 +1,6 @@ +{ + "resnet50_caffe": "detectron/resnet50_caffe", + "resnet50_caffe_bgr": "detectron2/resnet50_caffe_bgr", + "resnet101_caffe": "detectron/resnet101_caffe", + "resnet101_caffe_bgr": "detectron2/resnet101_caffe_bgr" +} diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/mmcls.json b/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/mmcls.json new file mode 100644 index 0000000..bdb311d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/mmcls.json @@ -0,0 +1,31 @@ +{ + "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_batch256_imagenet_20200708-34ab8f90.pth", + "resnet34": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet34_batch256_imagenet_20200708-32ffb4f7.pth", + "resnet50": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_batch256_imagenet_20200708-cfb998bf.pth", + "resnet101": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet101_batch256_imagenet_20200708-753f3608.pth", + "resnet152": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet152_batch256_imagenet_20200708-ec25b1f9.pth", + "resnet50_v1d": "https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d50_batch256_imagenet_20200708-1ad0ce94.pth", + "resnet101_v1d": "https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d101_batch256_imagenet_20200708-9cb302ef.pth", + "resnet152_v1d": "https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d152_batch256_imagenet_20200708-e79cb6a2.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" +} diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/open_mmlab.json b/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/open_mmlab.json new file mode 100644 index 0000000..8311db4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/model_zoo/open_mmlab.json @@ -0,0 +1,50 @@ +{ + "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" +} diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/__init__.py new file mode 100644 index 0000000..999e090 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/__init__.py @@ -0,0 +1,81 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .assign_score_withk import assign_score_withk +from .ball_query import ball_query +from .bbox import bbox_overlaps +from .border_align import BorderAlign, border_align +from .box_iou_rotated import box_iou_rotated +from .carafe import CARAFE, CARAFENaive, CARAFEPack, carafe, carafe_naive +from .cc_attention import CrissCrossAttention +from .contour_expand import contour_expand +from .corner_pool import CornerPool +from .correlation import Correlation +from .deform_conv import DeformConv2d, DeformConv2dPack, deform_conv2d +from .deform_roi_pool import (DeformRoIPool, DeformRoIPoolPack, + ModulatedDeformRoIPoolPack, deform_roi_pool) +from .deprecated_wrappers import Conv2d_deprecated as Conv2d +from .deprecated_wrappers import ConvTranspose2d_deprecated as ConvTranspose2d +from .deprecated_wrappers import Linear_deprecated as Linear +from .deprecated_wrappers import MaxPool2d_deprecated as MaxPool2d +from .focal_loss import (SigmoidFocalLoss, SoftmaxFocalLoss, + sigmoid_focal_loss, softmax_focal_loss) +from .furthest_point_sample import (furthest_point_sample, + furthest_point_sample_with_dist) +from .fused_bias_leakyrelu import FusedBiasLeakyReLU, fused_bias_leakyrelu +from .gather_points import gather_points +from .group_points import GroupAll, QueryAndGroup, grouping_operation +from .info import (get_compiler_version, get_compiling_cuda_version, + get_onnxruntime_op_path) +from .iou3d import boxes_iou_bev, nms_bev, nms_normal_bev +from .knn import knn +from .masked_conv import MaskedConv2d, masked_conv2d +from .modulated_deform_conv import (ModulatedDeformConv2d, + ModulatedDeformConv2dPack, + modulated_deform_conv2d) +from .multi_scale_deform_attn import MultiScaleDeformableAttention +from .nms import batched_nms, nms, nms_match, nms_rotated, soft_nms +from .pixel_group import pixel_group +from .point_sample import (SimpleRoIAlign, point_sample, + rel_roi_point_to_rel_img_point) +from .points_in_boxes import (points_in_boxes_all, points_in_boxes_cpu, + points_in_boxes_part) +from .points_sampler import PointsSampler +from .psa_mask import PSAMask +from .roi_align import RoIAlign, roi_align +from .roi_align_rotated import RoIAlignRotated, roi_align_rotated +from .roi_pool import RoIPool, roi_pool +from .roiaware_pool3d import RoIAwarePool3d +from .roipoint_pool3d import RoIPointPool3d +from .saconv import SAConv2d +from .scatter_points import DynamicScatter, dynamic_scatter +from .sync_bn import SyncBatchNorm +from .three_interpolate import three_interpolate +from .three_nn import three_nn +from .tin_shift import TINShift, tin_shift +from .upfirdn2d import upfirdn2d +from .voxelize import Voxelization, voxelization + +__all__ = [ + 'bbox_overlaps', 'CARAFE', 'CARAFENaive', 'CARAFEPack', 'carafe', + 'carafe_naive', 'CornerPool', 'DeformConv2d', 'DeformConv2dPack', + 'deform_conv2d', 'DeformRoIPool', 'DeformRoIPoolPack', + 'ModulatedDeformRoIPoolPack', 'deform_roi_pool', 'SigmoidFocalLoss', + 'SoftmaxFocalLoss', 'sigmoid_focal_loss', 'softmax_focal_loss', + 'get_compiler_version', 'get_compiling_cuda_version', + 'get_onnxruntime_op_path', 'MaskedConv2d', 'masked_conv2d', + 'ModulatedDeformConv2d', 'ModulatedDeformConv2dPack', + 'modulated_deform_conv2d', 'batched_nms', 'nms', 'soft_nms', 'nms_match', + 'RoIAlign', 'roi_align', 'RoIPool', 'roi_pool', 'SyncBatchNorm', 'Conv2d', + 'ConvTranspose2d', 'Linear', 'MaxPool2d', 'CrissCrossAttention', 'PSAMask', + 'point_sample', 'rel_roi_point_to_rel_img_point', 'SimpleRoIAlign', + 'SAConv2d', 'TINShift', 'tin_shift', 'assign_score_withk', + 'box_iou_rotated', 'RoIPointPool3d', 'nms_rotated', 'knn', 'ball_query', + 'upfirdn2d', 'FusedBiasLeakyReLU', 'fused_bias_leakyrelu', + 'RoIAlignRotated', 'roi_align_rotated', 'pixel_group', 'QueryAndGroup', + 'GroupAll', 'grouping_operation', 'contour_expand', 'three_nn', + 'three_interpolate', 'MultiScaleDeformableAttention', 'BorderAlign', + 'border_align', 'gather_points', 'furthest_point_sample', + 'furthest_point_sample_with_dist', 'PointsSampler', 'Correlation', + 'boxes_iou_bev', 'nms_bev', 'nms_normal_bev', 'Voxelization', + 'voxelization', 'dynamic_scatter', 'DynamicScatter', 'RoIAwarePool3d', + 'points_in_boxes_part', 'points_in_boxes_cpu', 'points_in_boxes_all' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/assign_score_withk.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/assign_score_withk.py new file mode 100644 index 0000000..4906ada --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/assign_score_withk.py @@ -0,0 +1,123 @@ +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['assign_score_withk_forward', 'assign_score_withk_backward']) + + +class AssignScoreWithK(Function): + r"""Perform weighted sum to generate output features according to scores. + Modified from `PAConv `_. + + This is a memory-efficient CUDA implementation of assign_scores operation, + which first transform all point features with weight bank, then assemble + neighbor features with ``knn_idx`` and perform weighted sum of ``scores``. + + See the `paper `_ appendix Sec. D for + more detailed descriptions. + + Note: + This implementation assumes using ``neighbor`` kernel input, which is + (point_features - center_features, point_features). + See https://github.com/CVMI-Lab/PAConv/blob/main/scene_seg/model/ + pointnet2/paconv.py#L128 for more details. + """ + + @staticmethod + def forward(ctx, + scores, + point_features, + center_features, + knn_idx, + aggregate='sum'): + """ + Args: + scores (torch.Tensor): (B, npoint, K, M), predicted scores to + aggregate weight matrices in the weight bank. + ``npoint`` is the number of sampled centers. + ``K`` is the number of queried neighbors. + ``M`` is the number of weight matrices in the weight bank. + point_features (torch.Tensor): (B, N, M, out_dim) + Pre-computed point features to be aggregated. + center_features (torch.Tensor): (B, N, M, out_dim) + Pre-computed center features to be aggregated. + knn_idx (torch.Tensor): (B, npoint, K), index of sampled kNN. + We assume the first idx in each row is the idx of the center. + aggregate (str, optional): Aggregation method. + Can be 'sum', 'avg' or 'max'. Defaults: 'sum'. + + Returns: + torch.Tensor: (B, out_dim, npoint, K), the aggregated features. + """ + agg = {'sum': 0, 'avg': 1, 'max': 2} + + B, N, M, out_dim = point_features.size() + _, npoint, K, _ = scores.size() + + output = point_features.new_zeros((B, out_dim, npoint, K)) + ext_module.assign_score_withk_forward( + point_features.contiguous(), + center_features.contiguous(), + scores.contiguous(), + knn_idx.contiguous(), + output, + B=B, + N0=N, + N1=npoint, + M=M, + K=K, + O=out_dim, + aggregate=agg[aggregate]) + + ctx.save_for_backward(output, point_features, center_features, scores, + knn_idx) + ctx.agg = agg[aggregate] + + return output + + @staticmethod + def backward(ctx, grad_out): + """ + Args: + grad_out (torch.Tensor): (B, out_dim, npoint, K) + + Returns: + grad_scores (torch.Tensor): (B, npoint, K, M) + grad_point_features (torch.Tensor): (B, N, M, out_dim) + grad_center_features (torch.Tensor): (B, N, M, out_dim) + """ + _, point_features, center_features, scores, knn_idx = ctx.saved_tensors + + agg = ctx.agg + + B, N, M, out_dim = point_features.size() + _, npoint, K, _ = scores.size() + + grad_point_features = point_features.new_zeros(point_features.shape) + grad_center_features = center_features.new_zeros(center_features.shape) + grad_scores = scores.new_zeros(scores.shape) + + ext_module.assign_score_withk_backward( + grad_out.contiguous(), + point_features.contiguous(), + center_features.contiguous(), + scores.contiguous(), + knn_idx.contiguous(), + grad_point_features, + grad_center_features, + grad_scores, + B=B, + N0=N, + N1=npoint, + M=M, + K=K, + O=out_dim, + aggregate=agg) + + return grad_scores, grad_point_features, \ + grad_center_features, None, None + + +assign_score_withk = AssignScoreWithK.apply diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/ball_query.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/ball_query.py new file mode 100644 index 0000000..d046684 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/ball_query.py @@ -0,0 +1,55 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['ball_query_forward']) + + +class BallQuery(Function): + """Find nearby points in spherical space.""" + + @staticmethod + def forward(ctx, min_radius: float, max_radius: float, sample_num: int, + xyz: torch.Tensor, center_xyz: torch.Tensor) -> torch.Tensor: + """ + Args: + min_radius (float): minimum radius of the balls. + max_radius (float): maximum radius of the balls. + sample_num (int): maximum number of features in the balls. + xyz (Tensor): (B, N, 3) xyz coordinates of the features. + center_xyz (Tensor): (B, npoint, 3) centers of the ball query. + + Returns: + Tensor: (B, npoint, nsample) tensor with the indices of + the features that form the query balls. + """ + assert center_xyz.is_contiguous() + assert xyz.is_contiguous() + assert min_radius < max_radius + + B, N, _ = xyz.size() + npoint = center_xyz.size(1) + idx = xyz.new_zeros(B, npoint, sample_num, dtype=torch.int) + + ext_module.ball_query_forward( + center_xyz, + xyz, + idx, + b=B, + n=N, + m=npoint, + min_radius=min_radius, + max_radius=max_radius, + nsample=sample_num) + if torch.__version__ != 'parrots': + ctx.mark_non_differentiable(idx) + return idx + + @staticmethod + def backward(ctx, a=None): + return None, None, None, None + + +ball_query = BallQuery.apply diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/bbox.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/bbox.py new file mode 100644 index 0000000..0c4d58b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/bbox.py @@ -0,0 +1,72 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['bbox_overlaps']) + + +def bbox_overlaps(bboxes1, bboxes2, mode='iou', aligned=False, offset=0): + """Calculate overlap between two set of bboxes. + + If ``aligned`` is ``False``, then calculate the ious between each bbox + of bboxes1 and bboxes2, otherwise the ious between each aligned pair of + bboxes1 and bboxes2. + + Args: + bboxes1 (Tensor): shape (m, 4) in format or empty. + bboxes2 (Tensor): shape (n, 4) in format or empty. + If aligned is ``True``, then m and n must be equal. + mode (str): "iou" (intersection over union) or iof (intersection over + foreground). + + Returns: + ious(Tensor): shape (m, n) if aligned == False else shape (m, 1) + + Example: + >>> bboxes1 = torch.FloatTensor([ + >>> [0, 0, 10, 10], + >>> [10, 10, 20, 20], + >>> [32, 32, 38, 42], + >>> ]) + >>> bboxes2 = torch.FloatTensor([ + >>> [0, 0, 10, 20], + >>> [0, 10, 10, 19], + >>> [10, 10, 20, 20], + >>> ]) + >>> bbox_overlaps(bboxes1, bboxes2) + tensor([[0.5000, 0.0000, 0.0000], + [0.0000, 0.0000, 1.0000], + [0.0000, 0.0000, 0.0000]]) + + Example: + >>> empty = torch.FloatTensor([]) + >>> nonempty = torch.FloatTensor([ + >>> [0, 0, 10, 9], + >>> ]) + >>> assert tuple(bbox_overlaps(empty, nonempty).shape) == (0, 1) + >>> assert tuple(bbox_overlaps(nonempty, empty).shape) == (1, 0) + >>> assert tuple(bbox_overlaps(empty, empty).shape) == (0, 0) + """ + + mode_dict = {'iou': 0, 'iof': 1} + assert mode in mode_dict.keys() + mode_flag = mode_dict[mode] + # Either the boxes are empty or the length of boxes' last dimension is 4 + assert (bboxes1.size(-1) == 4 or bboxes1.size(0) == 0) + assert (bboxes2.size(-1) == 4 or bboxes2.size(0) == 0) + assert offset == 1 or offset == 0 + + rows = bboxes1.size(0) + cols = bboxes2.size(0) + if aligned: + assert rows == cols + + if rows * cols == 0: + return bboxes1.new(rows, 1) if aligned else bboxes1.new(rows, cols) + + if aligned: + ious = bboxes1.new_zeros(rows) + else: + ious = bboxes1.new_zeros((rows, cols)) + ext_module.bbox_overlaps( + bboxes1, bboxes2, ious, mode=mode_flag, aligned=aligned, offset=offset) + return ious diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/border_align.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/border_align.py new file mode 100644 index 0000000..ff305be --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/border_align.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# modified from +# https://github.com/Megvii-BaseDetection/cvpods/blob/master/cvpods/layers/border_align.py + +import torch +import torch.nn as nn +from torch.autograd import Function +from torch.autograd.function import once_differentiable + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['border_align_forward', 'border_align_backward']) + + +class BorderAlignFunction(Function): + + @staticmethod + def symbolic(g, input, boxes, pool_size): + return g.op( + 'mmcv::MMCVBorderAlign', input, boxes, pool_size_i=pool_size) + + @staticmethod + def forward(ctx, input, boxes, pool_size): + ctx.pool_size = pool_size + ctx.input_shape = input.size() + + assert boxes.ndim == 3, 'boxes must be with shape [B, H*W, 4]' + assert boxes.size(2) == 4, \ + 'the last dimension of boxes must be (x1, y1, x2, y2)' + assert input.size(1) % 4 == 0, \ + 'the channel for input feature must be divisible by factor 4' + + # [B, C//4, H*W, 4] + output_shape = (input.size(0), input.size(1) // 4, boxes.size(1), 4) + output = input.new_zeros(output_shape) + # `argmax_idx` only used for backward + argmax_idx = input.new_zeros(output_shape).to(torch.int) + + ext_module.border_align_forward( + input, boxes, output, argmax_idx, pool_size=ctx.pool_size) + + ctx.save_for_backward(boxes, argmax_idx) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + boxes, argmax_idx = ctx.saved_tensors + grad_input = grad_output.new_zeros(ctx.input_shape) + # complex head architecture may cause grad_output uncontiguous + grad_output = grad_output.contiguous() + ext_module.border_align_backward( + grad_output, + boxes, + argmax_idx, + grad_input, + pool_size=ctx.pool_size) + return grad_input, None, None + + +border_align = BorderAlignFunction.apply + + +class BorderAlign(nn.Module): + r"""Border align pooling layer. + + Applies border_align over the input feature based on predicted bboxes. + The details were described in the paper + `BorderDet: Border Feature for Dense Object Detection + `_. + + For each border line (e.g. top, left, bottom or right) of each box, + border_align does the following: + 1. uniformly samples `pool_size`+1 positions on this line, involving \ + the start and end points. + 2. the corresponding features on these points are computed by \ + bilinear interpolation. + 3. max pooling over all the `pool_size`+1 positions are used for \ + computing pooled feature. + + Args: + pool_size (int): number of positions sampled over the boxes' borders + (e.g. top, bottom, left, right). + + """ + + def __init__(self, pool_size): + super(BorderAlign, self).__init__() + self.pool_size = pool_size + + def forward(self, input, boxes): + """ + Args: + input: Features with shape [N,4C,H,W]. Channels ranged in [0,C), + [C,2C), [2C,3C), [3C,4C) represent the top, left, bottom, + right features respectively. + boxes: Boxes with shape [N,H*W,4]. Coordinate format (x1,y1,x2,y2). + + Returns: + Tensor: Pooled features with shape [N,C,H*W,4]. The order is + (top,left,bottom,right) for the last dimension. + """ + return border_align(input, boxes, self.pool_size) + + def __repr__(self): + s = self.__class__.__name__ + s += f'(pool_size={self.pool_size})' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/box_iou_rotated.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/box_iou_rotated.py new file mode 100644 index 0000000..2d78015 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/box_iou_rotated.py @@ -0,0 +1,45 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['box_iou_rotated']) + + +def box_iou_rotated(bboxes1, bboxes2, mode='iou', aligned=False): + """Return intersection-over-union (Jaccard index) of boxes. + + Both sets of boxes are expected to be in + (x_center, y_center, width, height, angle) format. + + If ``aligned`` is ``False``, then calculate the ious between each bbox + of bboxes1 and bboxes2, otherwise the ious between each aligned pair of + bboxes1 and bboxes2. + + Arguments: + boxes1 (Tensor): rotated bboxes 1. \ + It has shape (N, 5), indicating (x, y, w, h, theta) for each row. + Note that theta is in radian. + boxes2 (Tensor): rotated bboxes 2. \ + It has shape (M, 5), indicating (x, y, w, h, theta) for each row. + Note that theta is in radian. + mode (str): "iou" (intersection over union) or iof (intersection over + foreground). + + Returns: + ious(Tensor): shape (N, M) if aligned == False else shape (N,) + """ + assert mode in ['iou', 'iof'] + mode_dict = {'iou': 0, 'iof': 1} + mode_flag = mode_dict[mode] + rows = bboxes1.size(0) + cols = bboxes2.size(0) + if aligned: + ious = bboxes1.new_zeros(rows) + else: + ious = bboxes1.new_zeros((rows * cols)) + bboxes1 = bboxes1.contiguous() + bboxes2 = bboxes2.contiguous() + ext_module.box_iou_rotated( + bboxes1, bboxes2, ious, mode_flag=mode_flag, aligned=aligned) + if not aligned: + ious = ious.view(rows, cols) + return ious diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/carafe.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/carafe.py new file mode 100644 index 0000000..5154cb3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/carafe.py @@ -0,0 +1,287 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.autograd import Function +from torch.nn.modules.module import Module + +from ..cnn import UPSAMPLE_LAYERS, normal_init, xavier_init +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', [ + 'carafe_naive_forward', 'carafe_naive_backward', 'carafe_forward', + 'carafe_backward' +]) + + +class CARAFENaiveFunction(Function): + + @staticmethod + def symbolic(g, features, masks, kernel_size, group_size, scale_factor): + return g.op( + 'mmcv::MMCVCARAFENaive', + features, + masks, + kernel_size_i=kernel_size, + group_size_i=group_size, + scale_factor_f=scale_factor) + + @staticmethod + def forward(ctx, features, masks, kernel_size, group_size, scale_factor): + assert scale_factor >= 1 + assert masks.size(1) == kernel_size * kernel_size * group_size + assert masks.size(-1) == features.size(-1) * scale_factor + assert masks.size(-2) == features.size(-2) * scale_factor + assert features.size(1) % group_size == 0 + assert (kernel_size - 1) % 2 == 0 and kernel_size >= 1 + ctx.kernel_size = kernel_size + ctx.group_size = group_size + ctx.scale_factor = scale_factor + ctx.feature_size = features.size() + ctx.mask_size = masks.size() + + n, c, h, w = features.size() + output = features.new_zeros((n, c, h * scale_factor, w * scale_factor)) + ext_module.carafe_naive_forward( + features, + masks, + output, + kernel_size=kernel_size, + group_size=group_size, + scale_factor=scale_factor) + + if features.requires_grad or masks.requires_grad: + ctx.save_for_backward(features, masks) + return output + + @staticmethod + def backward(ctx, grad_output): + assert grad_output.is_cuda + + features, masks = ctx.saved_tensors + kernel_size = ctx.kernel_size + group_size = ctx.group_size + scale_factor = ctx.scale_factor + + grad_input = torch.zeros_like(features) + grad_masks = torch.zeros_like(masks) + ext_module.carafe_naive_backward( + grad_output.contiguous(), + features, + masks, + grad_input, + grad_masks, + kernel_size=kernel_size, + group_size=group_size, + scale_factor=scale_factor) + + return grad_input, grad_masks, None, None, None + + +carafe_naive = CARAFENaiveFunction.apply + + +class CARAFENaive(Module): + + def __init__(self, kernel_size, group_size, scale_factor): + super(CARAFENaive, self).__init__() + + assert isinstance(kernel_size, int) and isinstance( + group_size, int) and isinstance(scale_factor, int) + self.kernel_size = kernel_size + self.group_size = group_size + self.scale_factor = scale_factor + + def forward(self, features, masks): + return carafe_naive(features, masks, self.kernel_size, self.group_size, + self.scale_factor) + + +class CARAFEFunction(Function): + + @staticmethod + def symbolic(g, features, masks, kernel_size, group_size, scale_factor): + return g.op( + 'mmcv::MMCVCARAFE', + features, + masks, + kernel_size_i=kernel_size, + group_size_i=group_size, + scale_factor_f=scale_factor) + + @staticmethod + def forward(ctx, features, masks, kernel_size, group_size, scale_factor): + assert scale_factor >= 1 + assert masks.size(1) == kernel_size * kernel_size * group_size + assert masks.size(-1) == features.size(-1) * scale_factor + assert masks.size(-2) == features.size(-2) * scale_factor + assert features.size(1) % group_size == 0 + assert (kernel_size - 1) % 2 == 0 and kernel_size >= 1 + ctx.kernel_size = kernel_size + ctx.group_size = group_size + ctx.scale_factor = scale_factor + ctx.feature_size = features.size() + ctx.mask_size = masks.size() + + n, c, h, w = features.size() + output = features.new_zeros((n, c, h * scale_factor, w * scale_factor)) + routput = features.new_zeros(output.size(), requires_grad=False) + rfeatures = features.new_zeros(features.size(), requires_grad=False) + rmasks = masks.new_zeros(masks.size(), requires_grad=False) + ext_module.carafe_forward( + features, + masks, + rfeatures, + routput, + rmasks, + output, + kernel_size=kernel_size, + group_size=group_size, + scale_factor=scale_factor) + + if features.requires_grad or masks.requires_grad: + ctx.save_for_backward(features, masks, rfeatures) + return output + + @staticmethod + def backward(ctx, grad_output): + assert grad_output.is_cuda + + features, masks, rfeatures = ctx.saved_tensors + kernel_size = ctx.kernel_size + group_size = ctx.group_size + scale_factor = ctx.scale_factor + + rgrad_output = torch.zeros_like(grad_output, requires_grad=False) + rgrad_input_hs = torch.zeros_like(grad_output, requires_grad=False) + rgrad_input = torch.zeros_like(features, requires_grad=False) + rgrad_masks = torch.zeros_like(masks, requires_grad=False) + grad_input = torch.zeros_like(features, requires_grad=False) + grad_masks = torch.zeros_like(masks, requires_grad=False) + ext_module.carafe_backward( + grad_output.contiguous(), + rfeatures, + masks, + rgrad_output, + rgrad_input_hs, + rgrad_input, + rgrad_masks, + grad_input, + grad_masks, + kernel_size=kernel_size, + group_size=group_size, + scale_factor=scale_factor) + return grad_input, grad_masks, None, None, None + + +carafe = CARAFEFunction.apply + + +class CARAFE(Module): + """ CARAFE: Content-Aware ReAssembly of FEatures + + Please refer to https://arxiv.org/abs/1905.02188 for more details. + + Args: + kernel_size (int): reassemble kernel size + group_size (int): reassemble group size + scale_factor (int): upsample ratio + + Returns: + upsampled feature map + """ + + def __init__(self, kernel_size, group_size, scale_factor): + super(CARAFE, self).__init__() + + assert isinstance(kernel_size, int) and isinstance( + group_size, int) and isinstance(scale_factor, int) + self.kernel_size = kernel_size + self.group_size = group_size + self.scale_factor = scale_factor + + def forward(self, features, masks): + return carafe(features, masks, self.kernel_size, self.group_size, + self.scale_factor) + + +@UPSAMPLE_LAYERS.register_module(name='carafe') +class CARAFEPack(nn.Module): + """A unified package of CARAFE upsampler that contains: 1) channel + compressor 2) content encoder 3) CARAFE op. + + Official implementation of ICCV 2019 paper + CARAFE: Content-Aware ReAssembly of FEatures + Please refer to https://arxiv.org/abs/1905.02188 for more details. + + Args: + channels (int): input feature channels + scale_factor (int): upsample ratio + up_kernel (int): kernel size of CARAFE op + up_group (int): group size of CARAFE op + encoder_kernel (int): kernel size of content encoder + encoder_dilation (int): dilation of content encoder + compressed_channels (int): output channels of channels compressor + + Returns: + upsampled feature map + """ + + def __init__(self, + channels, + scale_factor, + up_kernel=5, + up_group=1, + encoder_kernel=3, + encoder_dilation=1, + compressed_channels=64): + super(CARAFEPack, self).__init__() + self.channels = channels + self.scale_factor = scale_factor + self.up_kernel = up_kernel + self.up_group = up_group + self.encoder_kernel = encoder_kernel + self.encoder_dilation = encoder_dilation + self.compressed_channels = compressed_channels + self.channel_compressor = nn.Conv2d(channels, self.compressed_channels, + 1) + self.content_encoder = nn.Conv2d( + self.compressed_channels, + self.up_kernel * self.up_kernel * self.up_group * + self.scale_factor * self.scale_factor, + self.encoder_kernel, + padding=int((self.encoder_kernel - 1) * self.encoder_dilation / 2), + dilation=self.encoder_dilation, + groups=1) + self.init_weights() + + def init_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + xavier_init(m, distribution='uniform') + normal_init(self.content_encoder, std=0.001) + + def kernel_normalizer(self, mask): + mask = F.pixel_shuffle(mask, self.scale_factor) + n, mask_c, h, w = mask.size() + # use float division explicitly, + # to void inconsistency while exporting to onnx + mask_channel = int(mask_c / float(self.up_kernel**2)) + mask = mask.view(n, mask_channel, -1, h, w) + + mask = F.softmax(mask, dim=2, dtype=mask.dtype) + mask = mask.view(n, mask_c, h, w).contiguous() + + return mask + + def feature_reassemble(self, x, mask): + x = carafe(x, mask, self.up_kernel, self.up_group, self.scale_factor) + return x + + def forward(self, x): + compressed_x = self.channel_compressor(x) + mask = self.content_encoder(compressed_x) + mask = self.kernel_normalizer(mask) + + x = self.feature_reassemble(x, mask) + return x diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/cc_attention.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/cc_attention.py new file mode 100644 index 0000000..9207aa9 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/cc_attention.py @@ -0,0 +1,83 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from annotator.uniformer.mmcv.cnn import PLUGIN_LAYERS, Scale + + +def NEG_INF_DIAG(n, device): + """Returns a diagonal matrix of size [n, n]. + + The diagonal are all "-inf". This is for avoiding calculating the + overlapped element in the Criss-Cross twice. + """ + return torch.diag(torch.tensor(float('-inf')).to(device).repeat(n), 0) + + +@PLUGIN_LAYERS.register_module() +class CrissCrossAttention(nn.Module): + """Criss-Cross Attention Module. + + .. note:: + Before v1.3.13, we use a CUDA op. Since v1.3.13, we switch + to a pure PyTorch and equivalent implementation. For more + details, please refer to https://github.com/open-mmlab/mmcv/pull/1201. + + Speed comparison for one forward pass + + - Input size: [2,512,97,97] + - Device: 1 NVIDIA GeForce RTX 2080 Ti + + +-----------------------+---------------+------------+---------------+ + | |PyTorch version|CUDA version|Relative speed | + +=======================+===============+============+===============+ + |with torch.no_grad() |0.00554402 s |0.0299619 s |5.4x | + +-----------------------+---------------+------------+---------------+ + |no with torch.no_grad()|0.00562803 s |0.0301349 s |5.4x | + +-----------------------+---------------+------------+---------------+ + + Args: + in_channels (int): Channels of the input feature map. + """ + + def __init__(self, in_channels): + super().__init__() + self.query_conv = nn.Conv2d(in_channels, in_channels // 8, 1) + self.key_conv = nn.Conv2d(in_channels, in_channels // 8, 1) + self.value_conv = nn.Conv2d(in_channels, in_channels, 1) + self.gamma = Scale(0.) + self.in_channels = in_channels + + def forward(self, x): + """forward function of Criss-Cross Attention. + + Args: + x (Tensor): Input feature. \ + shape (batch_size, in_channels, height, width) + Returns: + Tensor: Output of the layer, with shape of \ + (batch_size, in_channels, height, width) + """ + B, C, H, W = x.size() + query = self.query_conv(x) + key = self.key_conv(x) + value = self.value_conv(x) + energy_H = torch.einsum('bchw,bciw->bwhi', query, key) + NEG_INF_DIAG( + H, query.device) + energy_H = energy_H.transpose(1, 2) + energy_W = torch.einsum('bchw,bchj->bhwj', query, key) + attn = F.softmax( + torch.cat([energy_H, energy_W], dim=-1), dim=-1) # [B,H,W,(H+W)] + out = torch.einsum('bciw,bhwi->bchw', value, attn[..., :H]) + out += torch.einsum('bchj,bhwj->bchw', value, attn[..., H:]) + + out = self.gamma(out) + x + out = out.contiguous() + + return out + + def __repr__(self): + s = self.__class__.__name__ + s += f'(in_channels={self.in_channels})' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/contour_expand.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/contour_expand.py new file mode 100644 index 0000000..ea1111e --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/contour_expand.py @@ -0,0 +1,49 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import torch + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['contour_expand']) + + +def contour_expand(kernel_mask, internal_kernel_label, min_kernel_area, + kernel_num): + """Expand kernel contours so that foreground pixels are assigned into + instances. + + Arguments: + kernel_mask (np.array or Tensor): The instance kernel mask with + size hxw. + internal_kernel_label (np.array or Tensor): The instance internal + kernel label with size hxw. + min_kernel_area (int): The minimum kernel area. + kernel_num (int): The instance kernel number. + + Returns: + label (list): The instance index map with size hxw. + """ + assert isinstance(kernel_mask, (torch.Tensor, np.ndarray)) + assert isinstance(internal_kernel_label, (torch.Tensor, np.ndarray)) + assert isinstance(min_kernel_area, int) + assert isinstance(kernel_num, int) + + if isinstance(kernel_mask, np.ndarray): + kernel_mask = torch.from_numpy(kernel_mask) + if isinstance(internal_kernel_label, np.ndarray): + internal_kernel_label = torch.from_numpy(internal_kernel_label) + + if torch.__version__ == 'parrots': + if kernel_mask.shape[0] == 0 or internal_kernel_label.shape[0] == 0: + label = [] + else: + label = ext_module.contour_expand( + kernel_mask, + internal_kernel_label, + min_kernel_area=min_kernel_area, + kernel_num=kernel_num) + label = label.tolist() + else: + label = ext_module.contour_expand(kernel_mask, internal_kernel_label, + min_kernel_area, kernel_num) + return label diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/corner_pool.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/corner_pool.py new file mode 100644 index 0000000..a33d798 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/corner_pool.py @@ -0,0 +1,161 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import nn +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', [ + 'top_pool_forward', 'top_pool_backward', 'bottom_pool_forward', + 'bottom_pool_backward', 'left_pool_forward', 'left_pool_backward', + 'right_pool_forward', 'right_pool_backward' +]) + +_mode_dict = {'top': 0, 'bottom': 1, 'left': 2, 'right': 3} + + +class TopPoolFunction(Function): + + @staticmethod + def symbolic(g, input): + output = g.op( + 'mmcv::MMCVCornerPool', input, mode_i=int(_mode_dict['top'])) + return output + + @staticmethod + def forward(ctx, input): + output = ext_module.top_pool_forward(input) + ctx.save_for_backward(input) + return output + + @staticmethod + def backward(ctx, grad_output): + input, = ctx.saved_tensors + output = ext_module.top_pool_backward(input, grad_output) + return output + + +class BottomPoolFunction(Function): + + @staticmethod + def symbolic(g, input): + output = g.op( + 'mmcv::MMCVCornerPool', input, mode_i=int(_mode_dict['bottom'])) + return output + + @staticmethod + def forward(ctx, input): + output = ext_module.bottom_pool_forward(input) + ctx.save_for_backward(input) + return output + + @staticmethod + def backward(ctx, grad_output): + input, = ctx.saved_tensors + output = ext_module.bottom_pool_backward(input, grad_output) + return output + + +class LeftPoolFunction(Function): + + @staticmethod + def symbolic(g, input): + output = g.op( + 'mmcv::MMCVCornerPool', input, mode_i=int(_mode_dict['left'])) + return output + + @staticmethod + def forward(ctx, input): + output = ext_module.left_pool_forward(input) + ctx.save_for_backward(input) + return output + + @staticmethod + def backward(ctx, grad_output): + input, = ctx.saved_tensors + output = ext_module.left_pool_backward(input, grad_output) + return output + + +class RightPoolFunction(Function): + + @staticmethod + def symbolic(g, input): + output = g.op( + 'mmcv::MMCVCornerPool', input, mode_i=int(_mode_dict['right'])) + return output + + @staticmethod + def forward(ctx, input): + output = ext_module.right_pool_forward(input) + ctx.save_for_backward(input) + return output + + @staticmethod + def backward(ctx, grad_output): + input, = ctx.saved_tensors + output = ext_module.right_pool_backward(input, grad_output) + return output + + +class CornerPool(nn.Module): + """Corner Pooling. + + Corner Pooling is a new type of pooling layer that helps a + convolutional network better localize corners of bounding boxes. + + Please refer to https://arxiv.org/abs/1808.01244 for more details. + Code is modified from https://github.com/princeton-vl/CornerNet-Lite. + + Args: + mode(str): Pooling orientation for the pooling layer + + - 'bottom': Bottom Pooling + - 'left': Left Pooling + - 'right': Right Pooling + - 'top': Top Pooling + + Returns: + Feature map after pooling. + """ + + pool_functions = { + 'bottom': BottomPoolFunction, + 'left': LeftPoolFunction, + 'right': RightPoolFunction, + 'top': TopPoolFunction, + } + + cummax_dim_flip = { + 'bottom': (2, False), + 'left': (3, True), + 'right': (3, False), + 'top': (2, True), + } + + def __init__(self, mode): + super(CornerPool, self).__init__() + assert mode in self.pool_functions + self.mode = mode + self.corner_pool = self.pool_functions[mode] + + def forward(self, x): + if torch.__version__ != 'parrots' and torch.__version__ >= '1.5.0': + if torch.onnx.is_in_onnx_export(): + assert torch.__version__ >= '1.7.0', \ + 'When `cummax` serves as an intermediate component whose '\ + 'outputs is used as inputs for another modules, it\'s '\ + 'expected that pytorch version must be >= 1.7.0, '\ + 'otherwise Error appears like: `RuntimeError: tuple '\ + 'appears in op that does not forward tuples, unsupported '\ + 'kind: prim::PythonOp`.' + + dim, flip = self.cummax_dim_flip[self.mode] + if flip: + x = x.flip(dim) + pool_tensor, _ = torch.cummax(x, dim=dim) + if flip: + pool_tensor = pool_tensor.flip(dim) + return pool_tensor + else: + return self.corner_pool.apply(x) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/correlation.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/correlation.py new file mode 100644 index 0000000..3d0b79c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/correlation.py @@ -0,0 +1,196 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import Tensor, nn +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.modules.utils import _pair + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['correlation_forward', 'correlation_backward']) + + +class CorrelationFunction(Function): + + @staticmethod + def forward(ctx, + input1, + input2, + kernel_size=1, + max_displacement=1, + stride=1, + padding=1, + dilation=1, + dilation_patch=1): + + ctx.save_for_backward(input1, input2) + + kH, kW = ctx.kernel_size = _pair(kernel_size) + patch_size = max_displacement * 2 + 1 + ctx.patch_size = patch_size + dH, dW = ctx.stride = _pair(stride) + padH, padW = ctx.padding = _pair(padding) + dilationH, dilationW = ctx.dilation = _pair(dilation) + dilation_patchH, dilation_patchW = ctx.dilation_patch = _pair( + dilation_patch) + + output_size = CorrelationFunction._output_size(ctx, input1) + + output = input1.new_zeros(output_size) + + ext_module.correlation_forward( + input1, + input2, + output, + kH=kH, + kW=kW, + patchH=patch_size, + patchW=patch_size, + padH=padH, + padW=padW, + dilationH=dilationH, + dilationW=dilationW, + dilation_patchH=dilation_patchH, + dilation_patchW=dilation_patchW, + dH=dH, + dW=dW) + + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + input1, input2 = ctx.saved_tensors + + kH, kW = ctx.kernel_size + patch_size = ctx.patch_size + padH, padW = ctx.padding + dilationH, dilationW = ctx.dilation + dilation_patchH, dilation_patchW = ctx.dilation_patch + dH, dW = ctx.stride + grad_input1 = torch.zeros_like(input1) + grad_input2 = torch.zeros_like(input2) + + ext_module.correlation_backward( + grad_output, + input1, + input2, + grad_input1, + grad_input2, + kH=kH, + kW=kW, + patchH=patch_size, + patchW=patch_size, + padH=padH, + padW=padW, + dilationH=dilationH, + dilationW=dilationW, + dilation_patchH=dilation_patchH, + dilation_patchW=dilation_patchW, + dH=dH, + dW=dW) + return grad_input1, grad_input2, None, None, None, None, None, None + + @staticmethod + def _output_size(ctx, input1): + iH, iW = input1.size(2), input1.size(3) + batch_size = input1.size(0) + kH, kW = ctx.kernel_size + patch_size = ctx.patch_size + dH, dW = ctx.stride + padH, padW = ctx.padding + dilationH, dilationW = ctx.dilation + dilatedKH = (kH - 1) * dilationH + 1 + dilatedKW = (kW - 1) * dilationW + 1 + + oH = int((iH + 2 * padH - dilatedKH) / dH + 1) + oW = int((iW + 2 * padW - dilatedKW) / dW + 1) + + output_size = (batch_size, patch_size, patch_size, oH, oW) + return output_size + + +class Correlation(nn.Module): + r"""Correlation operator + + This correlation operator works for optical flow correlation computation. + + There are two batched tensors with shape :math:`(N, C, H, W)`, + and the correlation output's shape is :math:`(N, max\_displacement \times + 2 + 1, max\_displacement * 2 + 1, H_{out}, W_{out})` + + where + + .. math:: + H_{out} = \left\lfloor\frac{H_{in} + 2 \times padding - + dilation \times (kernel\_size - 1) - 1} + {stride} + 1\right\rfloor + + .. math:: + W_{out} = \left\lfloor\frac{W_{in} + 2 \times padding - dilation + \times (kernel\_size - 1) - 1} + {stride} + 1\right\rfloor + + the correlation item :math:`(N_i, dy, dx)` is formed by taking the sliding + window convolution between input1 and shifted input2, + + .. math:: + Corr(N_i, dx, dy) = + \sum_{c=0}^{C-1} + input1(N_i, c) \star + \mathcal{S}(input2(N_i, c), dy, dx) + + where :math:`\star` is the valid 2d sliding window convolution operator, + and :math:`\mathcal{S}` means shifting the input features (auto-complete + zero marginal), and :math:`dx, dy` are shifting distance, :math:`dx, dy \in + [-max\_displacement \times dilation\_patch, max\_displacement \times + dilation\_patch]`. + + Args: + kernel_size (int): The size of sliding window i.e. local neighborhood + representing the center points and involved in correlation + computation. Defaults to 1. + max_displacement (int): The radius for computing correlation volume, + but the actual working space can be dilated by dilation_patch. + Defaults to 1. + stride (int): The stride of the sliding blocks in the input spatial + dimensions. Defaults to 1. + padding (int): Zero padding added to all four sides of the input1. + Defaults to 0. + dilation (int): The spacing of local neighborhood that will involved + in correlation. Defaults to 1. + dilation_patch (int): The spacing between position need to compute + correlation. Defaults to 1. + """ + + def __init__(self, + kernel_size: int = 1, + max_displacement: int = 1, + stride: int = 1, + padding: int = 0, + dilation: int = 1, + dilation_patch: int = 1) -> None: + super().__init__() + self.kernel_size = kernel_size + self.max_displacement = max_displacement + self.stride = stride + self.padding = padding + self.dilation = dilation + self.dilation_patch = dilation_patch + + def forward(self, input1: Tensor, input2: Tensor) -> Tensor: + return CorrelationFunction.apply(input1, input2, self.kernel_size, + self.max_displacement, self.stride, + self.padding, self.dilation, + self.dilation_patch) + + def __repr__(self) -> str: + s = self.__class__.__name__ + s += f'(kernel_size={self.kernel_size}, ' + s += f'max_displacement={self.max_displacement}, ' + s += f'stride={self.stride}, ' + s += f'padding={self.padding}, ' + s += f'dilation={self.dilation}, ' + s += f'dilation_patch={self.dilation_patch})' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deform_conv.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deform_conv.py new file mode 100644 index 0000000..a3f8c75 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deform_conv.py @@ -0,0 +1,405 @@ +# 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 torch import Tensor +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.modules.utils import _pair, _single + +from annotator.uniformer.mmcv.utils import deprecated_api_warning +from ..cnn import CONV_LAYERS +from ..utils import ext_loader, print_log + +ext_module = ext_loader.load_ext('_ext', [ + 'deform_conv_forward', 'deform_conv_backward_input', + 'deform_conv_backward_parameters' +]) + + +class DeformConv2dFunction(Function): + + @staticmethod + def symbolic(g, + input, + offset, + weight, + stride, + padding, + dilation, + groups, + deform_groups, + bias=False, + im2col_step=32): + return g.op( + 'mmcv::MMCVDeformConv2d', + input, + offset, + weight, + stride_i=stride, + padding_i=padding, + dilation_i=dilation, + groups_i=groups, + deform_groups_i=deform_groups, + bias_i=bias, + im2col_step_i=im2col_step) + + @staticmethod + def forward(ctx, + input, + offset, + weight, + stride=1, + padding=0, + dilation=1, + groups=1, + deform_groups=1, + bias=False, + im2col_step=32): + if input is not None and input.dim() != 4: + raise ValueError( + f'Expected 4D tensor as input, got {input.dim()}D tensor \ + instead.') + assert bias is False, 'Only support bias is False.' + ctx.stride = _pair(stride) + ctx.padding = _pair(padding) + ctx.dilation = _pair(dilation) + ctx.groups = groups + ctx.deform_groups = deform_groups + ctx.im2col_step = im2col_step + + # When pytorch version >= 1.6.0, amp is adopted for fp16 mode; + # amp won't cast the type of model (float32), but "offset" is cast + # to float16 by nn.Conv2d automatically, leading to the type + # mismatch with input (when it is float32) or weight. + # The flag for whether to use fp16 or amp is the type of "offset", + # we cast weight and input to temporarily support fp16 and amp + # whatever the pytorch version is. + input = input.type_as(offset) + weight = weight.type_as(input) + ctx.save_for_backward(input, offset, weight) + + output = input.new_empty( + DeformConv2dFunction._output_size(ctx, input, weight)) + + ctx.bufs_ = [input.new_empty(0), input.new_empty(0)] # columns, ones + + cur_im2col_step = min(ctx.im2col_step, input.size(0)) + assert (input.size(0) % + cur_im2col_step) == 0, 'im2col step must divide batchsize' + ext_module.deform_conv_forward( + input, + weight, + offset, + output, + ctx.bufs_[0], + ctx.bufs_[1], + kW=weight.size(3), + kH=weight.size(2), + dW=ctx.stride[1], + dH=ctx.stride[0], + padW=ctx.padding[1], + padH=ctx.padding[0], + dilationW=ctx.dilation[1], + dilationH=ctx.dilation[0], + group=ctx.groups, + deformable_group=ctx.deform_groups, + im2col_step=cur_im2col_step) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + input, offset, weight = ctx.saved_tensors + + grad_input = grad_offset = grad_weight = None + + cur_im2col_step = min(ctx.im2col_step, input.size(0)) + assert (input.size(0) % cur_im2col_step + ) == 0, 'batch size must be divisible by im2col_step' + + grad_output = grad_output.contiguous() + if ctx.needs_input_grad[0] or ctx.needs_input_grad[1]: + grad_input = torch.zeros_like(input) + grad_offset = torch.zeros_like(offset) + ext_module.deform_conv_backward_input( + input, + offset, + grad_output, + grad_input, + grad_offset, + weight, + ctx.bufs_[0], + kW=weight.size(3), + kH=weight.size(2), + dW=ctx.stride[1], + dH=ctx.stride[0], + padW=ctx.padding[1], + padH=ctx.padding[0], + dilationW=ctx.dilation[1], + dilationH=ctx.dilation[0], + group=ctx.groups, + deformable_group=ctx.deform_groups, + im2col_step=cur_im2col_step) + + if ctx.needs_input_grad[2]: + grad_weight = torch.zeros_like(weight) + ext_module.deform_conv_backward_parameters( + input, + offset, + grad_output, + grad_weight, + ctx.bufs_[0], + ctx.bufs_[1], + kW=weight.size(3), + kH=weight.size(2), + dW=ctx.stride[1], + dH=ctx.stride[0], + padW=ctx.padding[1], + padH=ctx.padding[0], + dilationW=ctx.dilation[1], + dilationH=ctx.dilation[0], + group=ctx.groups, + deformable_group=ctx.deform_groups, + scale=1, + im2col_step=cur_im2col_step) + + return grad_input, grad_offset, grad_weight, \ + None, None, None, None, None, None, None + + @staticmethod + def _output_size(ctx, input, weight): + channels = weight.size(0) + output_size = (input.size(0), channels) + for d in range(input.dim() - 2): + in_size = input.size(d + 2) + pad = ctx.padding[d] + kernel = ctx.dilation[d] * (weight.size(d + 2) - 1) + 1 + stride_ = ctx.stride[d] + output_size += ((in_size + (2 * pad) - kernel) // stride_ + 1, ) + if not all(map(lambda s: s > 0, output_size)): + raise ValueError( + 'convolution input is too small (output would be ' + + 'x'.join(map(str, output_size)) + ')') + return output_size + + +deform_conv2d = DeformConv2dFunction.apply + + +class DeformConv2d(nn.Module): + r"""Deformable 2D convolution. + + Applies a deformable 2D convolution over an input signal composed of + several input planes. DeformConv2d was described in the paper + `Deformable Convolutional Networks + `_ + + Note: + The argument ``im2col_step`` was added in version 1.3.17, which means + number of samples processed by the ``im2col_cuda_kernel`` per call. + It enables users to define ``batch_size`` and ``im2col_step`` more + flexibly and solved `issue mmcv#1440 + `_. + + Args: + in_channels (int): Number of channels in the input image. + out_channels (int): Number of channels produced by the convolution. + kernel_size(int, tuple): Size of the convolving kernel. + stride(int, tuple): Stride of the convolution. Default: 1. + padding (int or tuple): Zero-padding added to both sides of the input. + Default: 0. + dilation (int or tuple): Spacing between kernel elements. Default: 1. + groups (int): Number of blocked connections from input. + channels to output channels. Default: 1. + deform_groups (int): Number of deformable group partitions. + bias (bool): If True, adds a learnable bias to the output. + Default: False. + im2col_step (int): Number of samples processed by im2col_cuda_kernel + per call. It will work when ``batch_size`` > ``im2col_step``, but + ``batch_size`` must be divisible by ``im2col_step``. Default: 32. + `New in version 1.3.17.` + """ + + @deprecated_api_warning({'deformable_groups': 'deform_groups'}, + cls_name='DeformConv2d') + def __init__(self, + in_channels: int, + out_channels: int, + kernel_size: Union[int, Tuple[int, ...]], + stride: Union[int, Tuple[int, ...]] = 1, + padding: Union[int, Tuple[int, ...]] = 0, + dilation: Union[int, Tuple[int, ...]] = 1, + groups: int = 1, + deform_groups: int = 1, + bias: bool = False, + im2col_step: int = 32) -> None: + super(DeformConv2d, self).__init__() + + assert not bias, \ + f'bias={bias} is not supported in DeformConv2d.' + assert in_channels % groups == 0, \ + f'in_channels {in_channels} cannot be divisible by groups {groups}' + assert out_channels % groups == 0, \ + f'out_channels {out_channels} cannot be divisible by groups \ + {groups}' + + self.in_channels = in_channels + self.out_channels = out_channels + self.kernel_size = _pair(kernel_size) + self.stride = _pair(stride) + self.padding = _pair(padding) + self.dilation = _pair(dilation) + self.groups = groups + self.deform_groups = deform_groups + self.im2col_step = im2col_step + # enable compatibility with nn.Conv2d + self.transposed = False + self.output_padding = _single(0) + + # only weight, no bias + self.weight = nn.Parameter( + torch.Tensor(out_channels, in_channels // self.groups, + *self.kernel_size)) + + self.reset_parameters() + + def reset_parameters(self): + # switch the initialization of `self.weight` to the standard kaiming + # method described in `Delving deep into rectifiers: Surpassing + # human-level performance on ImageNet classification` - He, K. et al. + # (2015), using a uniform distribution + nn.init.kaiming_uniform_(self.weight, nonlinearity='relu') + + def forward(self, x: Tensor, offset: Tensor) -> Tensor: + """Deformable Convolutional forward function. + + Args: + x (Tensor): Input feature, shape (B, C_in, H_in, W_in) + offset (Tensor): Offset for deformable convolution, shape + (B, deform_groups*kernel_size[0]*kernel_size[1]*2, + H_out, W_out), H_out, W_out are equal to the output's. + + An offset is like `[y0, x0, y1, x1, y2, x2, ..., y8, x8]`. + The spatial arrangement is like: + + .. code:: text + + (x0, y0) (x1, y1) (x2, y2) + (x3, y3) (x4, y4) (x5, y5) + (x6, y6) (x7, y7) (x8, y8) + + Returns: + Tensor: Output of the layer. + """ + # To fix an assert error in deform_conv_cuda.cpp:128 + # input image is smaller than kernel + input_pad = (x.size(2) < self.kernel_size[0]) or (x.size(3) < + self.kernel_size[1]) + if input_pad: + pad_h = max(self.kernel_size[0] - x.size(2), 0) + pad_w = max(self.kernel_size[1] - x.size(3), 0) + x = F.pad(x, (0, pad_w, 0, pad_h), 'constant', 0).contiguous() + offset = F.pad(offset, (0, pad_w, 0, pad_h), 'constant', 0) + offset = offset.contiguous() + out = deform_conv2d(x, offset, self.weight, self.stride, self.padding, + self.dilation, self.groups, self.deform_groups, + False, self.im2col_step) + if input_pad: + out = out[:, :, :out.size(2) - pad_h, :out.size(3) - + pad_w].contiguous() + return out + + def __repr__(self): + s = self.__class__.__name__ + s += f'(in_channels={self.in_channels},\n' + s += f'out_channels={self.out_channels},\n' + s += f'kernel_size={self.kernel_size},\n' + s += f'stride={self.stride},\n' + s += f'padding={self.padding},\n' + s += f'dilation={self.dilation},\n' + s += f'groups={self.groups},\n' + s += f'deform_groups={self.deform_groups},\n' + # bias is not supported in DeformConv2d. + s += 'bias=False)' + return s + + +@CONV_LAYERS.register_module('DCN') +class DeformConv2dPack(DeformConv2d): + """A Deformable Conv Encapsulation that acts as normal Conv layers. + + The offset tensor is like `[y0, x0, y1, x1, y2, x2, ..., y8, x8]`. + The spatial arrangement is like: + + .. code:: text + + (x0, y0) (x1, y1) (x2, y2) + (x3, y3) (x4, y4) (x5, y5) + (x6, y6) (x7, y7) (x8, y8) + + Args: + in_channels (int): Same as nn.Conv2d. + out_channels (int): Same as nn.Conv2d. + kernel_size (int or tuple[int]): Same as nn.Conv2d. + stride (int or tuple[int]): Same as nn.Conv2d. + padding (int or tuple[int]): Same as nn.Conv2d. + dilation (int or tuple[int]): Same as nn.Conv2d. + groups (int): Same as nn.Conv2d. + bias (bool or str): If specified as `auto`, it will be decided by the + norm_cfg. Bias will be set as True if norm_cfg is None, otherwise + False. + """ + + _version = 2 + + def __init__(self, *args, **kwargs): + super(DeformConv2dPack, self).__init__(*args, **kwargs) + self.conv_offset = nn.Conv2d( + self.in_channels, + self.deform_groups * 2 * self.kernel_size[0] * self.kernel_size[1], + kernel_size=self.kernel_size, + stride=_pair(self.stride), + padding=_pair(self.padding), + dilation=_pair(self.dilation), + bias=True) + self.init_offset() + + def init_offset(self): + self.conv_offset.weight.data.zero_() + self.conv_offset.bias.data.zero_() + + def forward(self, x): + offset = self.conv_offset(x) + return deform_conv2d(x, offset, self.weight, self.stride, self.padding, + self.dilation, self.groups, self.deform_groups, + False, self.im2col_step) + + def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, + missing_keys, unexpected_keys, error_msgs): + version = local_metadata.get('version', None) + + if version is None or version < 2: + # the key is different in early versions + # In version < 2, DeformConvPack loads previous benchmark models. + if (prefix + 'conv_offset.weight' not in state_dict + and prefix[:-1] + '_offset.weight' in state_dict): + state_dict[prefix + 'conv_offset.weight'] = state_dict.pop( + prefix[:-1] + '_offset.weight') + if (prefix + 'conv_offset.bias' not in state_dict + and prefix[:-1] + '_offset.bias' in state_dict): + state_dict[prefix + + 'conv_offset.bias'] = state_dict.pop(prefix[:-1] + + '_offset.bias') + + if version is not None and version > 1: + print_log( + f'DeformConv2dPack {prefix.rstrip(".")} is upgraded to ' + 'version 2.', + logger='root') + + super()._load_from_state_dict(state_dict, prefix, local_metadata, + strict, missing_keys, unexpected_keys, + error_msgs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deform_roi_pool.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deform_roi_pool.py new file mode 100644 index 0000000..cc245ba --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deform_roi_pool.py @@ -0,0 +1,204 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from torch import nn +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.modules.utils import _pair + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['deform_roi_pool_forward', 'deform_roi_pool_backward']) + + +class DeformRoIPoolFunction(Function): + + @staticmethod + def symbolic(g, input, rois, offset, output_size, spatial_scale, + sampling_ratio, gamma): + return g.op( + 'mmcv::MMCVDeformRoIPool', + input, + rois, + offset, + pooled_height_i=output_size[0], + pooled_width_i=output_size[1], + spatial_scale_f=spatial_scale, + sampling_ratio_f=sampling_ratio, + gamma_f=gamma) + + @staticmethod + def forward(ctx, + input, + rois, + offset, + output_size, + spatial_scale=1.0, + sampling_ratio=0, + gamma=0.1): + if offset is None: + offset = input.new_zeros(0) + ctx.output_size = _pair(output_size) + ctx.spatial_scale = float(spatial_scale) + ctx.sampling_ratio = int(sampling_ratio) + ctx.gamma = float(gamma) + + assert rois.size(1) == 5, 'RoI must be (idx, x1, y1, x2, y2)!' + + output_shape = (rois.size(0), input.size(1), ctx.output_size[0], + ctx.output_size[1]) + output = input.new_zeros(output_shape) + + ext_module.deform_roi_pool_forward( + input, + rois, + offset, + output, + pooled_height=ctx.output_size[0], + pooled_width=ctx.output_size[1], + spatial_scale=ctx.spatial_scale, + sampling_ratio=ctx.sampling_ratio, + gamma=ctx.gamma) + + ctx.save_for_backward(input, rois, offset) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + input, rois, offset = ctx.saved_tensors + grad_input = grad_output.new_zeros(input.shape) + grad_offset = grad_output.new_zeros(offset.shape) + + ext_module.deform_roi_pool_backward( + grad_output, + input, + rois, + offset, + grad_input, + grad_offset, + pooled_height=ctx.output_size[0], + pooled_width=ctx.output_size[1], + spatial_scale=ctx.spatial_scale, + sampling_ratio=ctx.sampling_ratio, + gamma=ctx.gamma) + if grad_offset.numel() == 0: + grad_offset = None + return grad_input, None, grad_offset, None, None, None, None + + +deform_roi_pool = DeformRoIPoolFunction.apply + + +class DeformRoIPool(nn.Module): + + def __init__(self, + output_size, + spatial_scale=1.0, + sampling_ratio=0, + gamma=0.1): + super(DeformRoIPool, self).__init__() + self.output_size = _pair(output_size) + self.spatial_scale = float(spatial_scale) + self.sampling_ratio = int(sampling_ratio) + self.gamma = float(gamma) + + def forward(self, input, rois, offset=None): + return deform_roi_pool(input, rois, offset, self.output_size, + self.spatial_scale, self.sampling_ratio, + self.gamma) + + +class DeformRoIPoolPack(DeformRoIPool): + + def __init__(self, + output_size, + output_channels, + deform_fc_channels=1024, + spatial_scale=1.0, + sampling_ratio=0, + gamma=0.1): + super(DeformRoIPoolPack, self).__init__(output_size, spatial_scale, + sampling_ratio, gamma) + + self.output_channels = output_channels + self.deform_fc_channels = deform_fc_channels + + self.offset_fc = nn.Sequential( + nn.Linear( + self.output_size[0] * self.output_size[1] * + self.output_channels, self.deform_fc_channels), + nn.ReLU(inplace=True), + nn.Linear(self.deform_fc_channels, self.deform_fc_channels), + nn.ReLU(inplace=True), + nn.Linear(self.deform_fc_channels, + self.output_size[0] * self.output_size[1] * 2)) + self.offset_fc[-1].weight.data.zero_() + self.offset_fc[-1].bias.data.zero_() + + def forward(self, input, rois): + assert input.size(1) == self.output_channels + x = deform_roi_pool(input, rois, None, self.output_size, + self.spatial_scale, self.sampling_ratio, + self.gamma) + rois_num = rois.size(0) + offset = self.offset_fc(x.view(rois_num, -1)) + offset = offset.view(rois_num, 2, self.output_size[0], + self.output_size[1]) + return deform_roi_pool(input, rois, offset, self.output_size, + self.spatial_scale, self.sampling_ratio, + self.gamma) + + +class ModulatedDeformRoIPoolPack(DeformRoIPool): + + def __init__(self, + output_size, + output_channels, + deform_fc_channels=1024, + spatial_scale=1.0, + sampling_ratio=0, + gamma=0.1): + super(ModulatedDeformRoIPoolPack, + self).__init__(output_size, spatial_scale, sampling_ratio, gamma) + + self.output_channels = output_channels + self.deform_fc_channels = deform_fc_channels + + self.offset_fc = nn.Sequential( + nn.Linear( + self.output_size[0] * self.output_size[1] * + self.output_channels, self.deform_fc_channels), + nn.ReLU(inplace=True), + nn.Linear(self.deform_fc_channels, self.deform_fc_channels), + nn.ReLU(inplace=True), + nn.Linear(self.deform_fc_channels, + self.output_size[0] * self.output_size[1] * 2)) + self.offset_fc[-1].weight.data.zero_() + self.offset_fc[-1].bias.data.zero_() + + self.mask_fc = nn.Sequential( + nn.Linear( + self.output_size[0] * self.output_size[1] * + self.output_channels, self.deform_fc_channels), + nn.ReLU(inplace=True), + nn.Linear(self.deform_fc_channels, + self.output_size[0] * self.output_size[1] * 1), + nn.Sigmoid()) + self.mask_fc[2].weight.data.zero_() + self.mask_fc[2].bias.data.zero_() + + def forward(self, input, rois): + assert input.size(1) == self.output_channels + x = deform_roi_pool(input, rois, None, self.output_size, + self.spatial_scale, self.sampling_ratio, + self.gamma) + rois_num = rois.size(0) + offset = self.offset_fc(x.view(rois_num, -1)) + offset = offset.view(rois_num, 2, self.output_size[0], + self.output_size[1]) + mask = self.mask_fc(x.view(rois_num, -1)) + mask = mask.view(rois_num, 1, self.output_size[0], self.output_size[1]) + d = deform_roi_pool(input, rois, offset, self.output_size, + self.spatial_scale, self.sampling_ratio, + self.gamma) + return d * mask diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deprecated_wrappers.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deprecated_wrappers.py new file mode 100644 index 0000000..a2e593d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/deprecated_wrappers.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# This file is for backward compatibility. +# Module wrappers for empty tensor have been moved to mmcv.cnn.bricks. +import warnings + +from ..cnn.bricks.wrappers import Conv2d, ConvTranspose2d, Linear, MaxPool2d + + +class Conv2d_deprecated(Conv2d): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warnings.warn( + 'Importing Conv2d wrapper from "mmcv.ops" will be deprecated in' + ' the future. Please import them from "mmcv.cnn" instead') + + +class ConvTranspose2d_deprecated(ConvTranspose2d): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warnings.warn( + 'Importing ConvTranspose2d wrapper from "mmcv.ops" will be ' + 'deprecated in the future. Please import them from "mmcv.cnn" ' + 'instead') + + +class MaxPool2d_deprecated(MaxPool2d): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warnings.warn( + 'Importing MaxPool2d wrapper from "mmcv.ops" will be deprecated in' + ' the future. Please import them from "mmcv.cnn" instead') + + +class Linear_deprecated(Linear): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warnings.warn( + 'Importing Linear wrapper from "mmcv.ops" will be deprecated in' + ' the future. Please import them from "mmcv.cnn" instead') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/focal_loss.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/focal_loss.py new file mode 100644 index 0000000..763bc93 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/focal_loss.py @@ -0,0 +1,212 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from torch.autograd import Function +from torch.autograd.function import once_differentiable + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', [ + 'sigmoid_focal_loss_forward', 'sigmoid_focal_loss_backward', + 'softmax_focal_loss_forward', 'softmax_focal_loss_backward' +]) + + +class SigmoidFocalLossFunction(Function): + + @staticmethod + def symbolic(g, input, target, gamma, alpha, weight, reduction): + return g.op( + 'mmcv::MMCVSigmoidFocalLoss', + input, + target, + gamma_f=gamma, + alpha_f=alpha, + weight_f=weight, + reduction_s=reduction) + + @staticmethod + def forward(ctx, + input, + target, + gamma=2.0, + alpha=0.25, + weight=None, + reduction='mean'): + + assert isinstance(target, (torch.LongTensor, torch.cuda.LongTensor)) + assert input.dim() == 2 + assert target.dim() == 1 + assert input.size(0) == target.size(0) + if weight is None: + weight = input.new_empty(0) + else: + assert weight.dim() == 1 + assert input.size(1) == weight.size(0) + ctx.reduction_dict = {'none': 0, 'mean': 1, 'sum': 2} + assert reduction in ctx.reduction_dict.keys() + + ctx.gamma = float(gamma) + ctx.alpha = float(alpha) + ctx.reduction = ctx.reduction_dict[reduction] + + output = input.new_zeros(input.size()) + + ext_module.sigmoid_focal_loss_forward( + input, target, weight, output, gamma=ctx.gamma, alpha=ctx.alpha) + if ctx.reduction == ctx.reduction_dict['mean']: + output = output.sum() / input.size(0) + elif ctx.reduction == ctx.reduction_dict['sum']: + output = output.sum() + ctx.save_for_backward(input, target, weight) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + input, target, weight = ctx.saved_tensors + + grad_input = input.new_zeros(input.size()) + + ext_module.sigmoid_focal_loss_backward( + input, + target, + weight, + grad_input, + gamma=ctx.gamma, + alpha=ctx.alpha) + + grad_input *= grad_output + if ctx.reduction == ctx.reduction_dict['mean']: + grad_input /= input.size(0) + return grad_input, None, None, None, None, None + + +sigmoid_focal_loss = SigmoidFocalLossFunction.apply + + +class SigmoidFocalLoss(nn.Module): + + def __init__(self, gamma, alpha, weight=None, reduction='mean'): + super(SigmoidFocalLoss, self).__init__() + self.gamma = gamma + self.alpha = alpha + self.register_buffer('weight', weight) + self.reduction = reduction + + def forward(self, input, target): + return sigmoid_focal_loss(input, target, self.gamma, self.alpha, + self.weight, self.reduction) + + def __repr__(self): + s = self.__class__.__name__ + s += f'(gamma={self.gamma}, ' + s += f'alpha={self.alpha}, ' + s += f'reduction={self.reduction})' + return s + + +class SoftmaxFocalLossFunction(Function): + + @staticmethod + def symbolic(g, input, target, gamma, alpha, weight, reduction): + return g.op( + 'mmcv::MMCVSoftmaxFocalLoss', + input, + target, + gamma_f=gamma, + alpha_f=alpha, + weight_f=weight, + reduction_s=reduction) + + @staticmethod + def forward(ctx, + input, + target, + gamma=2.0, + alpha=0.25, + weight=None, + reduction='mean'): + + assert isinstance(target, (torch.LongTensor, torch.cuda.LongTensor)) + assert input.dim() == 2 + assert target.dim() == 1 + assert input.size(0) == target.size(0) + if weight is None: + weight = input.new_empty(0) + else: + assert weight.dim() == 1 + assert input.size(1) == weight.size(0) + ctx.reduction_dict = {'none': 0, 'mean': 1, 'sum': 2} + assert reduction in ctx.reduction_dict.keys() + + ctx.gamma = float(gamma) + ctx.alpha = float(alpha) + ctx.reduction = ctx.reduction_dict[reduction] + + channel_stats, _ = torch.max(input, dim=1) + input_softmax = input - channel_stats.unsqueeze(1).expand_as(input) + input_softmax.exp_() + + channel_stats = input_softmax.sum(dim=1) + input_softmax /= channel_stats.unsqueeze(1).expand_as(input) + + output = input.new_zeros(input.size(0)) + ext_module.softmax_focal_loss_forward( + input_softmax, + target, + weight, + output, + gamma=ctx.gamma, + alpha=ctx.alpha) + + if ctx.reduction == ctx.reduction_dict['mean']: + output = output.sum() / input.size(0) + elif ctx.reduction == ctx.reduction_dict['sum']: + output = output.sum() + ctx.save_for_backward(input_softmax, target, weight) + return output + + @staticmethod + def backward(ctx, grad_output): + input_softmax, target, weight = ctx.saved_tensors + buff = input_softmax.new_zeros(input_softmax.size(0)) + grad_input = input_softmax.new_zeros(input_softmax.size()) + + ext_module.softmax_focal_loss_backward( + input_softmax, + target, + weight, + buff, + grad_input, + gamma=ctx.gamma, + alpha=ctx.alpha) + + grad_input *= grad_output + if ctx.reduction == ctx.reduction_dict['mean']: + grad_input /= input_softmax.size(0) + return grad_input, None, None, None, None, None + + +softmax_focal_loss = SoftmaxFocalLossFunction.apply + + +class SoftmaxFocalLoss(nn.Module): + + def __init__(self, gamma, alpha, weight=None, reduction='mean'): + super(SoftmaxFocalLoss, self).__init__() + self.gamma = gamma + self.alpha = alpha + self.register_buffer('weight', weight) + self.reduction = reduction + + def forward(self, input, target): + return softmax_focal_loss(input, target, self.gamma, self.alpha, + self.weight, self.reduction) + + def __repr__(self): + s = self.__class__.__name__ + s += f'(gamma={self.gamma}, ' + s += f'alpha={self.alpha}, ' + s += f'reduction={self.reduction})' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/furthest_point_sample.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/furthest_point_sample.py new file mode 100644 index 0000000..374b7a8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/furthest_point_sample.py @@ -0,0 +1,83 @@ +import torch +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', [ + 'furthest_point_sampling_forward', + 'furthest_point_sampling_with_dist_forward' +]) + + +class FurthestPointSampling(Function): + """Uses iterative furthest point sampling to select a set of features whose + corresponding points have the furthest distance.""" + + @staticmethod + def forward(ctx, points_xyz: torch.Tensor, + num_points: int) -> torch.Tensor: + """ + Args: + points_xyz (Tensor): (B, N, 3) where N > num_points. + num_points (int): Number of points in the sampled set. + + Returns: + Tensor: (B, num_points) indices of the sampled points. + """ + assert points_xyz.is_contiguous() + + B, N = points_xyz.size()[:2] + output = torch.cuda.IntTensor(B, num_points) + temp = torch.cuda.FloatTensor(B, N).fill_(1e10) + + ext_module.furthest_point_sampling_forward( + points_xyz, + temp, + output, + b=B, + n=N, + m=num_points, + ) + if torch.__version__ != 'parrots': + ctx.mark_non_differentiable(output) + return output + + @staticmethod + def backward(xyz, a=None): + return None, None + + +class FurthestPointSamplingWithDist(Function): + """Uses iterative furthest point sampling to select a set of features whose + corresponding points have the furthest distance.""" + + @staticmethod + def forward(ctx, points_dist: torch.Tensor, + num_points: int) -> torch.Tensor: + """ + Args: + points_dist (Tensor): (B, N, N) Distance between each point pair. + num_points (int): Number of points in the sampled set. + + Returns: + Tensor: (B, num_points) indices of the sampled points. + """ + assert points_dist.is_contiguous() + + B, N, _ = points_dist.size() + output = points_dist.new_zeros([B, num_points], dtype=torch.int32) + temp = points_dist.new_zeros([B, N]).fill_(1e10) + + ext_module.furthest_point_sampling_with_dist_forward( + points_dist, temp, output, b=B, n=N, m=num_points) + if torch.__version__ != 'parrots': + ctx.mark_non_differentiable(output) + return output + + @staticmethod + def backward(xyz, a=None): + return None, None + + +furthest_point_sample = FurthestPointSampling.apply +furthest_point_sample_with_dist = FurthestPointSamplingWithDist.apply diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/fused_bias_leakyrelu.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/fused_bias_leakyrelu.py new file mode 100644 index 0000000..6d12508 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/fused_bias_leakyrelu.py @@ -0,0 +1,268 @@ +# modified from https://github.com/rosinality/stylegan2-pytorch/blob/master/op/fused_act.py # noqa:E501 + +# Copyright (c) 2021, NVIDIA Corporation. All rights reserved. +# NVIDIA Source Code License for StyleGAN2 with Adaptive Discriminator +# Augmentation (ADA) +# ======================================================================= + +# 1. Definitions + +# "Licensor" means any person or entity that distributes its Work. + +# "Software" means the original work of authorship made available under +# this License. + +# "Work" means the Software and any additions to or derivative works of +# the Software that are made available under this License. + +# The terms "reproduce," "reproduction," "derivative works," and +# "distribution" have the meaning as provided under U.S. copyright law; +# provided, however, that 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. + +# Works, including the Software, are "made available" under this License +# by including in or with the Work either (a) a copyright notice +# referencing the applicability of this License to the Work, or (b) a +# copy of this License. + +# 2. License Grants + +# 2.1 Copyright Grant. Subject to the terms and conditions of this +# License, each Licensor grants to you a perpetual, worldwide, +# non-exclusive, royalty-free, copyright license to reproduce, +# prepare derivative works of, publicly display, publicly perform, +# sublicense and distribute its Work and any resulting derivative +# works in any form. + +# 3. Limitations + +# 3.1 Redistribution. You may reproduce or distribute the Work only +# if (a) you do so under this License, (b) you include a complete +# copy of this License with your distribution, and (c) you retain +# without modification any copyright, patent, trademark, or +# attribution notices that are present in the Work. + +# 3.2 Derivative Works. You may specify that additional or different +# terms apply to the use, reproduction, and distribution of your +# derivative works of the Work ("Your Terms") only if (a) Your Terms +# provide that the use limitation in Section 3.3 applies to your +# derivative works, and (b) you identify the specific derivative +# works that are subject to Your Terms. Notwithstanding Your Terms, +# this License (including the redistribution requirements in Section +# 3.1) will continue to apply to the Work itself. + +# 3.3 Use Limitation. The Work and any derivative works thereof only +# may be used or intended for use non-commercially. Notwithstanding +# the foregoing, NVIDIA and its affiliates may use the Work and any +# derivative works commercially. As used herein, "non-commercially" +# means for research or evaluation purposes only. + +# 3.4 Patent Claims. If you bring or threaten to bring a patent claim +# against any Licensor (including any claim, cross-claim or +# counterclaim in a lawsuit) to enforce any patents that you allege +# are infringed by any Work, then your rights under this License from +# such Licensor (including the grant in Section 2.1) will terminate +# immediately. + +# 3.5 Trademarks. This License does not grant any rights to use any +# Licensor’s or its affiliates’ names, logos, or trademarks, except +# as necessary to reproduce the notices described in this License. + +# 3.6 Termination. If you violate any term of this License, then your +# rights under this License (including the grant in Section 2.1) will +# terminate immediately. + +# 4. Disclaimer of Warranty. + +# THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR +# NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER +# THIS LICENSE. + +# 5. Limitation of Liability. + +# EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL +# THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE +# SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, +# INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF +# OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK +# (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, +# LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER +# COMMERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGES. + +# ======================================================================= + +import torch +import torch.nn.functional as F +from torch import nn +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['fused_bias_leakyrelu']) + + +class FusedBiasLeakyReLUFunctionBackward(Function): + """Calculate second order deviation. + + This function is to compute the second order deviation for the fused leaky + relu operation. + """ + + @staticmethod + def forward(ctx, grad_output, out, negative_slope, scale): + ctx.save_for_backward(out) + ctx.negative_slope = negative_slope + ctx.scale = scale + + empty = grad_output.new_empty(0) + + grad_input = ext_module.fused_bias_leakyrelu( + grad_output, + empty, + out, + act=3, + grad=1, + alpha=negative_slope, + scale=scale) + + dim = [0] + + if grad_input.ndim > 2: + dim += list(range(2, grad_input.ndim)) + + grad_bias = grad_input.sum(dim).detach() + + return grad_input, grad_bias + + @staticmethod + def backward(ctx, gradgrad_input, gradgrad_bias): + out, = ctx.saved_tensors + + # The second order deviation, in fact, contains two parts, while the + # the first part is zero. Thus, we direct consider the second part + # which is similar with the first order deviation in implementation. + gradgrad_out = ext_module.fused_bias_leakyrelu( + gradgrad_input, + gradgrad_bias.to(out.dtype), + out, + act=3, + grad=1, + alpha=ctx.negative_slope, + scale=ctx.scale) + + return gradgrad_out, None, None, None + + +class FusedBiasLeakyReLUFunction(Function): + + @staticmethod + def forward(ctx, input, bias, negative_slope, scale): + empty = input.new_empty(0) + + out = ext_module.fused_bias_leakyrelu( + input, + bias, + empty, + act=3, + grad=0, + alpha=negative_slope, + scale=scale) + ctx.save_for_backward(out) + ctx.negative_slope = negative_slope + ctx.scale = scale + + return out + + @staticmethod + def backward(ctx, grad_output): + out, = ctx.saved_tensors + + grad_input, grad_bias = FusedBiasLeakyReLUFunctionBackward.apply( + grad_output, out, ctx.negative_slope, ctx.scale) + + return grad_input, grad_bias, None, None + + +class FusedBiasLeakyReLU(nn.Module): + """Fused bias leaky ReLU. + + This function is introduced in the StyleGAN2: + http://arxiv.org/abs/1912.04958 + + The bias term comes from the convolution operation. In addition, to keep + the variance of the feature map or gradients unchanged, they also adopt a + scale similarly with Kaiming initialization. However, since the + :math:`1+{alpha}^2` : is too small, we can just ignore it. Therefore, the + final scale is just :math:`\sqrt{2}`:. Of course, you may change it with # noqa: W605, E501 + your own scale. + + TODO: Implement the CPU version. + + Args: + channel (int): The channel number of the feature map. + negative_slope (float, optional): Same as nn.LeakyRelu. + Defaults to 0.2. + scale (float, optional): A scalar to adjust the variance of the feature + map. Defaults to 2**0.5. + """ + + def __init__(self, num_channels, negative_slope=0.2, scale=2**0.5): + super(FusedBiasLeakyReLU, self).__init__() + + self.bias = nn.Parameter(torch.zeros(num_channels)) + self.negative_slope = negative_slope + self.scale = scale + + def forward(self, input): + return fused_bias_leakyrelu(input, self.bias, self.negative_slope, + self.scale) + + +def fused_bias_leakyrelu(input, bias, negative_slope=0.2, scale=2**0.5): + """Fused bias leaky ReLU function. + + This function is introduced in the StyleGAN2: + http://arxiv.org/abs/1912.04958 + + The bias term comes from the convolution operation. In addition, to keep + the variance of the feature map or gradients unchanged, they also adopt a + scale similarly with Kaiming initialization. However, since the + :math:`1+{alpha}^2` : is too small, we can just ignore it. Therefore, the + final scale is just :math:`\sqrt{2}`:. Of course, you may change it with # noqa: W605, E501 + your own scale. + + Args: + input (torch.Tensor): Input feature map. + bias (nn.Parameter): The bias from convolution operation. + negative_slope (float, optional): Same as nn.LeakyRelu. + Defaults to 0.2. + scale (float, optional): A scalar to adjust the variance of the feature + map. Defaults to 2**0.5. + + Returns: + torch.Tensor: Feature map after non-linear activation. + """ + + if not input.is_cuda: + return bias_leakyrelu_ref(input, bias, negative_slope, scale) + + return FusedBiasLeakyReLUFunction.apply(input, bias.to(input.dtype), + negative_slope, scale) + + +def bias_leakyrelu_ref(x, bias, negative_slope=0.2, scale=2**0.5): + + if bias is not None: + assert bias.ndim == 1 + assert bias.shape[0] == x.shape[1] + x = x + bias.reshape([-1 if i == 1 else 1 for i in range(x.ndim)]) + + x = F.leaky_relu(x, negative_slope) + if scale != 1: + x = x * scale + + return x diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/gather_points.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/gather_points.py new file mode 100644 index 0000000..f52f167 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/gather_points.py @@ -0,0 +1,57 @@ +import torch +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['gather_points_forward', 'gather_points_backward']) + + +class GatherPoints(Function): + """Gather points with given index.""" + + @staticmethod + def forward(ctx, features: torch.Tensor, + indices: torch.Tensor) -> torch.Tensor: + """ + Args: + features (Tensor): (B, C, N) features to gather. + indices (Tensor): (B, M) where M is the number of points. + + Returns: + Tensor: (B, C, M) where M is the number of points. + """ + assert features.is_contiguous() + assert indices.is_contiguous() + + B, npoint = indices.size() + _, C, N = features.size() + output = torch.cuda.FloatTensor(B, C, npoint) + + ext_module.gather_points_forward( + features, indices, output, b=B, c=C, n=N, npoints=npoint) + + ctx.for_backwards = (indices, C, N) + if torch.__version__ != 'parrots': + ctx.mark_non_differentiable(indices) + return output + + @staticmethod + def backward(ctx, grad_out): + idx, C, N = ctx.for_backwards + B, npoint = idx.size() + + grad_features = torch.cuda.FloatTensor(B, C, N).zero_() + grad_out_data = grad_out.data.contiguous() + ext_module.gather_points_backward( + grad_out_data, + idx, + grad_features.data, + b=B, + c=C, + n=N, + npoints=npoint) + return grad_features, None + + +gather_points = GatherPoints.apply diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/group_points.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/group_points.py new file mode 100644 index 0000000..6c3ec9d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/group_points.py @@ -0,0 +1,224 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch +from torch import nn as nn +from torch.autograd import Function + +from ..utils import ext_loader +from .ball_query import ball_query +from .knn import knn + +ext_module = ext_loader.load_ext( + '_ext', ['group_points_forward', 'group_points_backward']) + + +class QueryAndGroup(nn.Module): + """Groups points with a ball query of radius. + + Args: + max_radius (float): The maximum radius of the balls. + If None is given, we will use kNN sampling instead of ball query. + sample_num (int): Maximum number of features to gather in the ball. + min_radius (float, optional): The minimum radius of the balls. + Default: 0. + use_xyz (bool, optional): Whether to use xyz. + Default: True. + return_grouped_xyz (bool, optional): Whether to return grouped xyz. + Default: False. + normalize_xyz (bool, optional): Whether to normalize xyz. + Default: False. + uniform_sample (bool, optional): Whether to sample uniformly. + Default: False + return_unique_cnt (bool, optional): Whether to return the count of + unique samples. Default: False. + return_grouped_idx (bool, optional): Whether to return grouped idx. + Default: False. + """ + + def __init__(self, + max_radius, + sample_num, + min_radius=0, + use_xyz=True, + return_grouped_xyz=False, + normalize_xyz=False, + uniform_sample=False, + return_unique_cnt=False, + return_grouped_idx=False): + super().__init__() + self.max_radius = max_radius + self.min_radius = min_radius + self.sample_num = sample_num + self.use_xyz = use_xyz + self.return_grouped_xyz = return_grouped_xyz + self.normalize_xyz = normalize_xyz + self.uniform_sample = uniform_sample + self.return_unique_cnt = return_unique_cnt + self.return_grouped_idx = return_grouped_idx + if self.return_unique_cnt: + assert self.uniform_sample, \ + 'uniform_sample should be True when ' \ + 'returning the count of unique samples' + if self.max_radius is None: + assert not self.normalize_xyz, \ + 'can not normalize grouped xyz when max_radius is None' + + def forward(self, points_xyz, center_xyz, features=None): + """ + Args: + points_xyz (Tensor): (B, N, 3) xyz coordinates of the features. + center_xyz (Tensor): (B, npoint, 3) coordinates of the centriods. + features (Tensor): (B, C, N) Descriptors of the features. + + Returns: + Tensor: (B, 3 + C, npoint, sample_num) Grouped feature. + """ + # if self.max_radius is None, we will perform kNN instead of ball query + # idx is of shape [B, npoint, sample_num] + if self.max_radius is None: + idx = knn(self.sample_num, points_xyz, center_xyz, False) + idx = idx.transpose(1, 2).contiguous() + else: + idx = ball_query(self.min_radius, self.max_radius, self.sample_num, + points_xyz, center_xyz) + + if self.uniform_sample: + unique_cnt = torch.zeros((idx.shape[0], idx.shape[1])) + for i_batch in range(idx.shape[0]): + for i_region in range(idx.shape[1]): + unique_ind = torch.unique(idx[i_batch, i_region, :]) + num_unique = unique_ind.shape[0] + unique_cnt[i_batch, i_region] = num_unique + sample_ind = torch.randint( + 0, + num_unique, (self.sample_num - num_unique, ), + dtype=torch.long) + all_ind = torch.cat((unique_ind, unique_ind[sample_ind])) + idx[i_batch, i_region, :] = all_ind + + xyz_trans = points_xyz.transpose(1, 2).contiguous() + # (B, 3, npoint, sample_num) + grouped_xyz = grouping_operation(xyz_trans, idx) + grouped_xyz_diff = grouped_xyz - \ + center_xyz.transpose(1, 2).unsqueeze(-1) # relative offsets + if self.normalize_xyz: + grouped_xyz_diff /= self.max_radius + + if features is not None: + grouped_features = grouping_operation(features, idx) + if self.use_xyz: + # (B, C + 3, npoint, sample_num) + new_features = torch.cat([grouped_xyz_diff, grouped_features], + dim=1) + else: + new_features = grouped_features + else: + assert (self.use_xyz + ), 'Cannot have not features and not use xyz as a feature!' + new_features = grouped_xyz_diff + + ret = [new_features] + if self.return_grouped_xyz: + ret.append(grouped_xyz) + if self.return_unique_cnt: + ret.append(unique_cnt) + if self.return_grouped_idx: + ret.append(idx) + if len(ret) == 1: + return ret[0] + else: + return tuple(ret) + + +class GroupAll(nn.Module): + """Group xyz with feature. + + Args: + use_xyz (bool): Whether to use xyz. + """ + + def __init__(self, use_xyz: bool = True): + super().__init__() + self.use_xyz = use_xyz + + def forward(self, + xyz: torch.Tensor, + new_xyz: torch.Tensor, + features: torch.Tensor = None): + """ + Args: + xyz (Tensor): (B, N, 3) xyz coordinates of the features. + new_xyz (Tensor): new xyz coordinates of the features. + features (Tensor): (B, C, N) features to group. + + Returns: + Tensor: (B, C + 3, 1, N) Grouped feature. + """ + grouped_xyz = xyz.transpose(1, 2).unsqueeze(2) + if features is not None: + grouped_features = features.unsqueeze(2) + if self.use_xyz: + # (B, 3 + C, 1, N) + new_features = torch.cat([grouped_xyz, grouped_features], + dim=1) + else: + new_features = grouped_features + else: + new_features = grouped_xyz + + return new_features + + +class GroupingOperation(Function): + """Group feature with given index.""" + + @staticmethod + def forward(ctx, features: torch.Tensor, + indices: torch.Tensor) -> torch.Tensor: + """ + Args: + features (Tensor): (B, C, N) tensor of features to group. + indices (Tensor): (B, npoint, nsample) the indices of + features to group with. + + Returns: + Tensor: (B, C, npoint, nsample) Grouped features. + """ + features = features.contiguous() + indices = indices.contiguous() + + B, nfeatures, nsample = indices.size() + _, C, N = features.size() + output = torch.cuda.FloatTensor(B, C, nfeatures, nsample) + + ext_module.group_points_forward(B, C, N, nfeatures, nsample, features, + indices, output) + + ctx.for_backwards = (indices, N) + return output + + @staticmethod + def backward(ctx, + grad_out: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Args: + grad_out (Tensor): (B, C, npoint, nsample) tensor of the gradients + of the output from forward. + + Returns: + Tensor: (B, C, N) gradient of the features. + """ + idx, N = ctx.for_backwards + + B, C, npoint, nsample = grad_out.size() + grad_features = torch.cuda.FloatTensor(B, C, N).zero_() + + grad_out_data = grad_out.data.contiguous() + ext_module.group_points_backward(B, C, N, npoint, nsample, + grad_out_data, idx, + grad_features.data) + return grad_features, None + + +grouping_operation = GroupingOperation.apply diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/info.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/info.py new file mode 100644 index 0000000..29f2e55 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/info.py @@ -0,0 +1,36 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import glob +import os + +import torch + +if torch.__version__ == 'parrots': + import parrots + + def get_compiler_version(): + return 'GCC ' + parrots.version.compiler + + def get_compiling_cuda_version(): + return parrots.version.cuda +else: + from ..utils import ext_loader + ext_module = ext_loader.load_ext( + '_ext', ['get_compiler_version', 'get_compiling_cuda_version']) + + def get_compiler_version(): + return ext_module.get_compiler_version() + + def get_compiling_cuda_version(): + return ext_module.get_compiling_cuda_version() + + +def get_onnxruntime_op_path(): + wildcard = os.path.join( + os.path.abspath(os.path.dirname(os.path.dirname(__file__))), + '_ext_ort.*.so') + + paths = glob.glob(wildcard) + if len(paths) > 0: + return paths[0] + else: + return '' diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/iou3d.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/iou3d.py new file mode 100644 index 0000000..6fc7197 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/iou3d.py @@ -0,0 +1,85 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', [ + 'iou3d_boxes_iou_bev_forward', 'iou3d_nms_forward', + 'iou3d_nms_normal_forward' +]) + + +def boxes_iou_bev(boxes_a, boxes_b): + """Calculate boxes IoU in the Bird's Eye View. + + Args: + boxes_a (torch.Tensor): Input boxes a with shape (M, 5). + boxes_b (torch.Tensor): Input boxes b with shape (N, 5). + + Returns: + ans_iou (torch.Tensor): IoU result with shape (M, N). + """ + ans_iou = boxes_a.new_zeros( + torch.Size((boxes_a.shape[0], boxes_b.shape[0]))) + + ext_module.iou3d_boxes_iou_bev_forward(boxes_a.contiguous(), + boxes_b.contiguous(), ans_iou) + + return ans_iou + + +def nms_bev(boxes, scores, thresh, pre_max_size=None, post_max_size=None): + """NMS function GPU implementation (for BEV boxes). The overlap of two + boxes for IoU calculation is defined as the exact overlapping area of the + two boxes. In this function, one can also set ``pre_max_size`` and + ``post_max_size``. + + Args: + boxes (torch.Tensor): Input boxes with the shape of [N, 5] + ([x1, y1, x2, y2, ry]). + scores (torch.Tensor): Scores of boxes with the shape of [N]. + thresh (float): Overlap threshold of NMS. + pre_max_size (int, optional): Max size of boxes before NMS. + Default: None. + post_max_size (int, optional): Max size of boxes after NMS. + Default: None. + + Returns: + torch.Tensor: Indexes after NMS. + """ + assert boxes.size(1) == 5, 'Input boxes shape should be [N, 5]' + order = scores.sort(0, descending=True)[1] + + if pre_max_size is not None: + order = order[:pre_max_size] + boxes = boxes[order].contiguous() + + keep = torch.zeros(boxes.size(0), dtype=torch.long) + num_out = ext_module.iou3d_nms_forward(boxes, keep, thresh) + keep = order[keep[:num_out].cuda(boxes.device)].contiguous() + if post_max_size is not None: + keep = keep[:post_max_size] + return keep + + +def nms_normal_bev(boxes, scores, thresh): + """Normal NMS function GPU implementation (for BEV boxes). The overlap of + two boxes for IoU calculation is defined as the exact overlapping area of + the two boxes WITH their yaw angle set to 0. + + Args: + boxes (torch.Tensor): Input boxes with shape (N, 5). + scores (torch.Tensor): Scores of predicted boxes with shape (N). + thresh (float): Overlap threshold of NMS. + + Returns: + torch.Tensor: Remaining indices with scores in descending order. + """ + assert boxes.shape[1] == 5, 'Input boxes shape should be [N, 5]' + order = scores.sort(0, descending=True)[1] + + boxes = boxes[order].contiguous() + + keep = torch.zeros(boxes.size(0), dtype=torch.long) + num_out = ext_module.iou3d_nms_normal_forward(boxes, keep, thresh) + return order[keep[:num_out].cuda(boxes.device)].contiguous() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/knn.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/knn.py new file mode 100644 index 0000000..f335785 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/knn.py @@ -0,0 +1,77 @@ +import torch +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['knn_forward']) + + +class KNN(Function): + r"""KNN (CUDA) based on heap data structure. + Modified from `PAConv `_. + + Find k-nearest points. + """ + + @staticmethod + def forward(ctx, + k: int, + xyz: torch.Tensor, + center_xyz: torch.Tensor = None, + transposed: bool = False) -> torch.Tensor: + """ + Args: + k (int): number of nearest neighbors. + xyz (Tensor): (B, N, 3) if transposed == False, else (B, 3, N). + xyz coordinates of the features. + center_xyz (Tensor, optional): (B, npoint, 3) if transposed == + False, else (B, 3, npoint). centers of the knn query. + Default: None. + transposed (bool, optional): whether the input tensors are + transposed. Should not explicitly use this keyword when + calling knn (=KNN.apply), just add the fourth param. + Default: False. + + Returns: + Tensor: (B, k, npoint) tensor with the indices of + the features that form k-nearest neighbours. + """ + assert (k > 0) & (k < 100), 'k should be in range(0, 100)' + + if center_xyz is None: + center_xyz = xyz + + if transposed: + xyz = xyz.transpose(2, 1).contiguous() + center_xyz = center_xyz.transpose(2, 1).contiguous() + + assert xyz.is_contiguous() # [B, N, 3] + assert center_xyz.is_contiguous() # [B, npoint, 3] + + center_xyz_device = center_xyz.get_device() + assert center_xyz_device == xyz.get_device(), \ + 'center_xyz and xyz should be put on the same device' + if torch.cuda.current_device() != center_xyz_device: + torch.cuda.set_device(center_xyz_device) + + B, npoint, _ = center_xyz.shape + N = xyz.shape[1] + + idx = center_xyz.new_zeros((B, npoint, k)).int() + dist2 = center_xyz.new_zeros((B, npoint, k)).float() + + ext_module.knn_forward( + xyz, center_xyz, idx, dist2, b=B, n=N, m=npoint, nsample=k) + # idx shape to [B, k, npoint] + idx = idx.transpose(2, 1).contiguous() + if torch.__version__ != 'parrots': + ctx.mark_non_differentiable(idx) + return idx + + @staticmethod + def backward(ctx, a=None): + return None, None, None + + +knn = KNN.apply diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/masked_conv.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/masked_conv.py new file mode 100644 index 0000000..cd514cc --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/masked_conv.py @@ -0,0 +1,111 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.modules.utils import _pair + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['masked_im2col_forward', 'masked_col2im_forward']) + + +class MaskedConv2dFunction(Function): + + @staticmethod + def symbolic(g, features, mask, weight, bias, padding, stride): + return g.op( + 'mmcv::MMCVMaskedConv2d', + features, + mask, + weight, + bias, + padding_i=padding, + stride_i=stride) + + @staticmethod + def forward(ctx, features, mask, weight, bias, padding=0, stride=1): + assert mask.dim() == 3 and mask.size(0) == 1 + assert features.dim() == 4 and features.size(0) == 1 + assert features.size()[2:] == mask.size()[1:] + pad_h, pad_w = _pair(padding) + stride_h, stride_w = _pair(stride) + if stride_h != 1 or stride_w != 1: + raise ValueError( + 'Stride could not only be 1 in masked_conv2d currently.') + out_channel, in_channel, kernel_h, kernel_w = weight.size() + + batch_size = features.size(0) + out_h = int( + math.floor((features.size(2) + 2 * pad_h - + (kernel_h - 1) - 1) / stride_h + 1)) + out_w = int( + math.floor((features.size(3) + 2 * pad_w - + (kernel_h - 1) - 1) / stride_w + 1)) + mask_inds = torch.nonzero(mask[0] > 0, as_tuple=False) + output = features.new_zeros(batch_size, out_channel, out_h, out_w) + if mask_inds.numel() > 0: + mask_h_idx = mask_inds[:, 0].contiguous() + mask_w_idx = mask_inds[:, 1].contiguous() + data_col = features.new_zeros(in_channel * kernel_h * kernel_w, + mask_inds.size(0)) + ext_module.masked_im2col_forward( + features, + mask_h_idx, + mask_w_idx, + data_col, + kernel_h=kernel_h, + kernel_w=kernel_w, + pad_h=pad_h, + pad_w=pad_w) + + masked_output = torch.addmm(1, bias[:, None], 1, + weight.view(out_channel, -1), data_col) + ext_module.masked_col2im_forward( + masked_output, + mask_h_idx, + mask_w_idx, + output, + height=out_h, + width=out_w, + channels=out_channel) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + return (None, ) * 5 + + +masked_conv2d = MaskedConv2dFunction.apply + + +class MaskedConv2d(nn.Conv2d): + """A MaskedConv2d which inherits the official Conv2d. + + The masked forward doesn't implement the backward function and only + supports the stride parameter to be 1 currently. + """ + + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True): + super(MaskedConv2d, + self).__init__(in_channels, out_channels, kernel_size, stride, + padding, dilation, groups, bias) + + def forward(self, input, mask=None): + if mask is None: # fallback to the normal Conv2d + return super(MaskedConv2d, self).forward(input) + else: + return masked_conv2d(input, mask, self.weight, self.bias, + self.padding) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/merge_cells.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/merge_cells.py new file mode 100644 index 0000000..48ca8cc --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/merge_cells.py @@ -0,0 +1,149 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import abstractmethod + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from ..cnn import ConvModule + + +class BaseMergeCell(nn.Module): + """The basic class for cells used in NAS-FPN and NAS-FCOS. + + BaseMergeCell takes 2 inputs. After applying convolution + on them, they are resized to the target size. Then, + they go through binary_op, which depends on the type of cell. + If with_out_conv is True, the result of output will go through + another convolution layer. + + Args: + in_channels (int): number of input channels in out_conv layer. + out_channels (int): number of output channels in out_conv layer. + with_out_conv (bool): Whether to use out_conv layer + out_conv_cfg (dict): Config dict for convolution layer, which should + contain "groups", "kernel_size", "padding", "bias" to build + out_conv layer. + out_norm_cfg (dict): Config dict for normalization layer in out_conv. + out_conv_order (tuple): The order of conv/norm/activation layers in + out_conv. + with_input1_conv (bool): Whether to use convolution on input1. + with_input2_conv (bool): Whether to use convolution on input2. + input_conv_cfg (dict): Config dict for building input1_conv layer and + input2_conv layer, which is expected to contain the type of + convolution. + Default: None, which means using conv2d. + input_norm_cfg (dict): Config dict for normalization layer in + input1_conv and input2_conv layer. Default: None. + upsample_mode (str): Interpolation method used to resize the output + of input1_conv and input2_conv to target size. Currently, we + support ['nearest', 'bilinear']. Default: 'nearest'. + """ + + def __init__(self, + fused_channels=256, + out_channels=256, + with_out_conv=True, + out_conv_cfg=dict( + groups=1, kernel_size=3, padding=1, bias=True), + out_norm_cfg=None, + out_conv_order=('act', 'conv', 'norm'), + with_input1_conv=False, + with_input2_conv=False, + input_conv_cfg=None, + input_norm_cfg=None, + upsample_mode='nearest'): + super(BaseMergeCell, self).__init__() + assert upsample_mode in ['nearest', 'bilinear'] + self.with_out_conv = with_out_conv + self.with_input1_conv = with_input1_conv + self.with_input2_conv = with_input2_conv + self.upsample_mode = upsample_mode + + if self.with_out_conv: + self.out_conv = ConvModule( + fused_channels, + out_channels, + **out_conv_cfg, + norm_cfg=out_norm_cfg, + order=out_conv_order) + + self.input1_conv = self._build_input_conv( + out_channels, input_conv_cfg, + input_norm_cfg) if with_input1_conv else nn.Sequential() + self.input2_conv = self._build_input_conv( + out_channels, input_conv_cfg, + input_norm_cfg) if with_input2_conv else nn.Sequential() + + def _build_input_conv(self, channel, conv_cfg, norm_cfg): + return ConvModule( + channel, + channel, + 3, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + bias=True) + + @abstractmethod + def _binary_op(self, x1, x2): + pass + + def _resize(self, x, size): + if x.shape[-2:] == size: + return x + elif x.shape[-2:] < size: + return F.interpolate(x, size=size, mode=self.upsample_mode) + else: + assert x.shape[-2] % size[-2] == 0 and x.shape[-1] % size[-1] == 0 + kernel_size = x.shape[-1] // size[-1] + x = F.max_pool2d(x, kernel_size=kernel_size, stride=kernel_size) + return x + + def forward(self, x1, x2, out_size=None): + assert x1.shape[:2] == x2.shape[:2] + assert out_size is None or len(out_size) == 2 + if out_size is None: # resize to larger one + out_size = max(x1.size()[2:], x2.size()[2:]) + + x1 = self.input1_conv(x1) + x2 = self.input2_conv(x2) + + x1 = self._resize(x1, out_size) + x2 = self._resize(x2, out_size) + + x = self._binary_op(x1, x2) + if self.with_out_conv: + x = self.out_conv(x) + return x + + +class SumCell(BaseMergeCell): + + def __init__(self, in_channels, out_channels, **kwargs): + super(SumCell, self).__init__(in_channels, out_channels, **kwargs) + + def _binary_op(self, x1, x2): + return x1 + x2 + + +class ConcatCell(BaseMergeCell): + + def __init__(self, in_channels, out_channels, **kwargs): + super(ConcatCell, self).__init__(in_channels * 2, out_channels, + **kwargs) + + def _binary_op(self, x1, x2): + ret = torch.cat([x1, x2], dim=1) + return ret + + +class GlobalPoolingCell(BaseMergeCell): + + def __init__(self, in_channels=None, out_channels=None, **kwargs): + super().__init__(in_channels, out_channels, **kwargs) + self.global_pool = nn.AdaptiveAvgPool2d((1, 1)) + + def _binary_op(self, x1, x2): + x2_att = self.global_pool(x2).sigmoid() + return x2 + x2_att * x1 diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/modulated_deform_conv.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/modulated_deform_conv.py new file mode 100644 index 0000000..7555957 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/modulated_deform_conv.py @@ -0,0 +1,282 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.modules.utils import _pair, _single + +from annotator.uniformer.mmcv.utils import deprecated_api_warning +from ..cnn import CONV_LAYERS +from ..utils import ext_loader, print_log + +ext_module = ext_loader.load_ext( + '_ext', + ['modulated_deform_conv_forward', 'modulated_deform_conv_backward']) + + +class ModulatedDeformConv2dFunction(Function): + + @staticmethod + def symbolic(g, input, offset, mask, weight, bias, stride, padding, + dilation, groups, deform_groups): + input_tensors = [input, offset, mask, weight] + if bias is not None: + input_tensors.append(bias) + return g.op( + 'mmcv::MMCVModulatedDeformConv2d', + *input_tensors, + stride_i=stride, + padding_i=padding, + dilation_i=dilation, + groups_i=groups, + deform_groups_i=deform_groups) + + @staticmethod + def forward(ctx, + input, + offset, + mask, + weight, + bias=None, + stride=1, + padding=0, + dilation=1, + groups=1, + deform_groups=1): + if input is not None and input.dim() != 4: + raise ValueError( + f'Expected 4D tensor as input, got {input.dim()}D tensor \ + instead.') + ctx.stride = _pair(stride) + ctx.padding = _pair(padding) + ctx.dilation = _pair(dilation) + ctx.groups = groups + ctx.deform_groups = deform_groups + ctx.with_bias = bias is not None + if not ctx.with_bias: + bias = input.new_empty(0) # fake tensor + # When pytorch version >= 1.6.0, amp is adopted for fp16 mode; + # amp won't cast the type of model (float32), but "offset" is cast + # to float16 by nn.Conv2d automatically, leading to the type + # mismatch with input (when it is float32) or weight. + # The flag for whether to use fp16 or amp is the type of "offset", + # we cast weight and input to temporarily support fp16 and amp + # whatever the pytorch version is. + input = input.type_as(offset) + weight = weight.type_as(input) + ctx.save_for_backward(input, offset, mask, weight, bias) + output = input.new_empty( + ModulatedDeformConv2dFunction._output_size(ctx, input, weight)) + ctx._bufs = [input.new_empty(0), input.new_empty(0)] + ext_module.modulated_deform_conv_forward( + input, + weight, + bias, + ctx._bufs[0], + offset, + mask, + output, + ctx._bufs[1], + kernel_h=weight.size(2), + kernel_w=weight.size(3), + stride_h=ctx.stride[0], + stride_w=ctx.stride[1], + pad_h=ctx.padding[0], + pad_w=ctx.padding[1], + dilation_h=ctx.dilation[0], + dilation_w=ctx.dilation[1], + group=ctx.groups, + deformable_group=ctx.deform_groups, + with_bias=ctx.with_bias) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + input, offset, mask, weight, bias = ctx.saved_tensors + grad_input = torch.zeros_like(input) + grad_offset = torch.zeros_like(offset) + grad_mask = torch.zeros_like(mask) + grad_weight = torch.zeros_like(weight) + grad_bias = torch.zeros_like(bias) + grad_output = grad_output.contiguous() + ext_module.modulated_deform_conv_backward( + input, + weight, + bias, + ctx._bufs[0], + offset, + mask, + ctx._bufs[1], + grad_input, + grad_weight, + grad_bias, + grad_offset, + grad_mask, + grad_output, + kernel_h=weight.size(2), + kernel_w=weight.size(3), + stride_h=ctx.stride[0], + stride_w=ctx.stride[1], + pad_h=ctx.padding[0], + pad_w=ctx.padding[1], + dilation_h=ctx.dilation[0], + dilation_w=ctx.dilation[1], + group=ctx.groups, + deformable_group=ctx.deform_groups, + with_bias=ctx.with_bias) + if not ctx.with_bias: + grad_bias = None + + return (grad_input, grad_offset, grad_mask, grad_weight, grad_bias, + None, None, None, None, None) + + @staticmethod + def _output_size(ctx, input, weight): + channels = weight.size(0) + output_size = (input.size(0), channels) + for d in range(input.dim() - 2): + in_size = input.size(d + 2) + pad = ctx.padding[d] + kernel = ctx.dilation[d] * (weight.size(d + 2) - 1) + 1 + stride_ = ctx.stride[d] + output_size += ((in_size + (2 * pad) - kernel) // stride_ + 1, ) + if not all(map(lambda s: s > 0, output_size)): + raise ValueError( + 'convolution input is too small (output would be ' + + 'x'.join(map(str, output_size)) + ')') + return output_size + + +modulated_deform_conv2d = ModulatedDeformConv2dFunction.apply + + +class ModulatedDeformConv2d(nn.Module): + + @deprecated_api_warning({'deformable_groups': 'deform_groups'}, + cls_name='ModulatedDeformConv2d') + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + deform_groups=1, + bias=True): + super(ModulatedDeformConv2d, self).__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.kernel_size = _pair(kernel_size) + self.stride = _pair(stride) + self.padding = _pair(padding) + self.dilation = _pair(dilation) + self.groups = groups + self.deform_groups = deform_groups + # enable compatibility with nn.Conv2d + self.transposed = False + self.output_padding = _single(0) + + self.weight = nn.Parameter( + torch.Tensor(out_channels, in_channels // groups, + *self.kernel_size)) + if bias: + self.bias = nn.Parameter(torch.Tensor(out_channels)) + else: + self.register_parameter('bias', None) + self.init_weights() + + def init_weights(self): + n = self.in_channels + for k in self.kernel_size: + n *= k + stdv = 1. / math.sqrt(n) + self.weight.data.uniform_(-stdv, stdv) + if self.bias is not None: + self.bias.data.zero_() + + def forward(self, x, offset, mask): + return modulated_deform_conv2d(x, offset, mask, self.weight, self.bias, + self.stride, self.padding, + self.dilation, self.groups, + self.deform_groups) + + +@CONV_LAYERS.register_module('DCNv2') +class ModulatedDeformConv2dPack(ModulatedDeformConv2d): + """A ModulatedDeformable Conv Encapsulation that acts as normal Conv + layers. + + Args: + in_channels (int): Same as nn.Conv2d. + out_channels (int): Same as nn.Conv2d. + kernel_size (int or tuple[int]): Same as nn.Conv2d. + stride (int): Same as nn.Conv2d, while tuple is not supported. + padding (int): Same as nn.Conv2d, while tuple is not supported. + dilation (int): Same as nn.Conv2d, while tuple is not supported. + groups (int): Same as nn.Conv2d. + bias (bool or str): If specified as `auto`, it will be decided by the + norm_cfg. Bias will be set as True if norm_cfg is None, otherwise + False. + """ + + _version = 2 + + def __init__(self, *args, **kwargs): + super(ModulatedDeformConv2dPack, self).__init__(*args, **kwargs) + self.conv_offset = nn.Conv2d( + self.in_channels, + self.deform_groups * 3 * self.kernel_size[0] * self.kernel_size[1], + kernel_size=self.kernel_size, + stride=self.stride, + padding=self.padding, + dilation=self.dilation, + bias=True) + self.init_weights() + + def init_weights(self): + super(ModulatedDeformConv2dPack, self).init_weights() + if hasattr(self, 'conv_offset'): + self.conv_offset.weight.data.zero_() + self.conv_offset.bias.data.zero_() + + def forward(self, x): + out = self.conv_offset(x) + o1, o2, mask = torch.chunk(out, 3, dim=1) + offset = torch.cat((o1, o2), dim=1) + mask = torch.sigmoid(mask) + return modulated_deform_conv2d(x, offset, mask, self.weight, self.bias, + self.stride, self.padding, + self.dilation, self.groups, + self.deform_groups) + + def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, + missing_keys, unexpected_keys, error_msgs): + version = local_metadata.get('version', None) + + if version is None or version < 2: + # the key is different in early versions + # In version < 2, ModulatedDeformConvPack + # loads previous benchmark models. + if (prefix + 'conv_offset.weight' not in state_dict + and prefix[:-1] + '_offset.weight' in state_dict): + state_dict[prefix + 'conv_offset.weight'] = state_dict.pop( + prefix[:-1] + '_offset.weight') + if (prefix + 'conv_offset.bias' not in state_dict + and prefix[:-1] + '_offset.bias' in state_dict): + state_dict[prefix + + 'conv_offset.bias'] = state_dict.pop(prefix[:-1] + + '_offset.bias') + + if version is not None and version > 1: + print_log( + f'ModulatedDeformConvPack {prefix.rstrip(".")} is upgraded to ' + 'version 2.', + logger='root') + + super()._load_from_state_dict(state_dict, prefix, local_metadata, + strict, missing_keys, unexpected_keys, + error_msgs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/multi_scale_deform_attn.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/multi_scale_deform_attn.py new file mode 100644 index 0000000..c52dda1 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/multi_scale_deform_attn.py @@ -0,0 +1,358 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import warnings + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.autograd.function import Function, once_differentiable + +from annotator.uniformer.mmcv import deprecated_api_warning +from annotator.uniformer.mmcv.cnn import constant_init, xavier_init +from annotator.uniformer.mmcv.cnn.bricks.registry import ATTENTION +from annotator.uniformer.mmcv.runner import BaseModule +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['ms_deform_attn_backward', 'ms_deform_attn_forward']) + + +class MultiScaleDeformableAttnFunction(Function): + + @staticmethod + def forward(ctx, value, value_spatial_shapes, value_level_start_index, + sampling_locations, attention_weights, im2col_step): + """GPU version of multi-scale deformable attention. + + Args: + value (Tensor): The value has shape + (bs, num_keys, mum_heads, embed_dims//num_heads) + value_spatial_shapes (Tensor): Spatial shape of + each feature map, has shape (num_levels, 2), + last dimension 2 represent (h, w) + sampling_locations (Tensor): The location of sampling points, + has shape + (bs ,num_queries, num_heads, num_levels, num_points, 2), + the last dimension 2 represent (x, y). + attention_weights (Tensor): The weight of sampling points used + when calculate the attention, has shape + (bs ,num_queries, num_heads, num_levels, num_points), + im2col_step (Tensor): The step used in image to column. + + Returns: + Tensor: has shape (bs, num_queries, embed_dims) + """ + + ctx.im2col_step = im2col_step + output = ext_module.ms_deform_attn_forward( + value, + value_spatial_shapes, + value_level_start_index, + sampling_locations, + attention_weights, + im2col_step=ctx.im2col_step) + ctx.save_for_backward(value, value_spatial_shapes, + value_level_start_index, sampling_locations, + attention_weights) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + """GPU version of backward function. + + Args: + grad_output (Tensor): Gradient + of output tensor of forward. + + Returns: + Tuple[Tensor]: Gradient + of input tensors in forward. + """ + value, value_spatial_shapes, value_level_start_index,\ + sampling_locations, attention_weights = ctx.saved_tensors + grad_value = torch.zeros_like(value) + grad_sampling_loc = torch.zeros_like(sampling_locations) + grad_attn_weight = torch.zeros_like(attention_weights) + + ext_module.ms_deform_attn_backward( + value, + value_spatial_shapes, + value_level_start_index, + sampling_locations, + attention_weights, + grad_output.contiguous(), + grad_value, + grad_sampling_loc, + grad_attn_weight, + im2col_step=ctx.im2col_step) + + return grad_value, None, None, \ + grad_sampling_loc, grad_attn_weight, None + + +def multi_scale_deformable_attn_pytorch(value, value_spatial_shapes, + sampling_locations, attention_weights): + """CPU version of multi-scale deformable attention. + + Args: + value (Tensor): The value has shape + (bs, num_keys, mum_heads, embed_dims//num_heads) + value_spatial_shapes (Tensor): Spatial shape of + each feature map, has shape (num_levels, 2), + last dimension 2 represent (h, w) + sampling_locations (Tensor): The location of sampling points, + has shape + (bs ,num_queries, num_heads, num_levels, num_points, 2), + the last dimension 2 represent (x, y). + attention_weights (Tensor): The weight of sampling points used + when calculate the attention, has shape + (bs ,num_queries, num_heads, num_levels, num_points), + + Returns: + Tensor: has shape (bs, num_queries, embed_dims) + """ + + bs, _, num_heads, embed_dims = value.shape + _, num_queries, num_heads, num_levels, num_points, _ =\ + sampling_locations.shape + value_list = value.split([H_ * W_ for H_, W_ in value_spatial_shapes], + dim=1) + sampling_grids = 2 * sampling_locations - 1 + sampling_value_list = [] + for level, (H_, W_) in enumerate(value_spatial_shapes): + # bs, H_*W_, num_heads, embed_dims -> + # bs, H_*W_, num_heads*embed_dims -> + # bs, num_heads*embed_dims, H_*W_ -> + # bs*num_heads, embed_dims, H_, W_ + value_l_ = value_list[level].flatten(2).transpose(1, 2).reshape( + bs * num_heads, embed_dims, H_, W_) + # bs, num_queries, num_heads, num_points, 2 -> + # bs, num_heads, num_queries, num_points, 2 -> + # bs*num_heads, num_queries, num_points, 2 + sampling_grid_l_ = sampling_grids[:, :, :, + level].transpose(1, 2).flatten(0, 1) + # bs*num_heads, embed_dims, num_queries, num_points + sampling_value_l_ = F.grid_sample( + value_l_, + sampling_grid_l_, + mode='bilinear', + padding_mode='zeros', + align_corners=False) + sampling_value_list.append(sampling_value_l_) + # (bs, num_queries, num_heads, num_levels, num_points) -> + # (bs, num_heads, num_queries, num_levels, num_points) -> + # (bs, num_heads, 1, num_queries, num_levels*num_points) + attention_weights = attention_weights.transpose(1, 2).reshape( + bs * num_heads, 1, num_queries, num_levels * num_points) + output = (torch.stack(sampling_value_list, dim=-2).flatten(-2) * + attention_weights).sum(-1).view(bs, num_heads * embed_dims, + num_queries) + return output.transpose(1, 2).contiguous() + + +@ATTENTION.register_module() +class MultiScaleDeformableAttention(BaseModule): + """An attention module used in Deformable-Detr. + + `Deformable DETR: Deformable Transformers for End-to-End Object Detection. + `_. + + Args: + embed_dims (int): The embedding dimension of Attention. + Default: 256. + num_heads (int): Parallel attention heads. Default: 64. + num_levels (int): The number of feature map used in + Attention. Default: 4. + num_points (int): The number of sampling points for + each query in each head. Default: 4. + im2col_step (int): The step used in image_to_column. + Default: 64. + dropout (float): A Dropout layer on `inp_identity`. + Default: 0.1. + batch_first (bool): Key, Query and Value are shape of + (batch, n, embed_dim) + or (n, batch, embed_dim). Default to False. + norm_cfg (dict): Config dict for normalization layer. + Default: None. + init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims=256, + num_heads=8, + num_levels=4, + num_points=4, + im2col_step=64, + dropout=0.1, + batch_first=False, + norm_cfg=None, + init_cfg=None): + super().__init__(init_cfg) + if embed_dims % num_heads != 0: + raise ValueError(f'embed_dims must be divisible by num_heads, ' + f'but got {embed_dims} and {num_heads}') + dim_per_head = embed_dims // num_heads + self.norm_cfg = norm_cfg + self.dropout = nn.Dropout(dropout) + self.batch_first = batch_first + + # you'd better set dim_per_head to a power of 2 + # which is more efficient in the CUDA implementation + def _is_power_of_2(n): + if (not isinstance(n, int)) or (n < 0): + raise ValueError( + 'invalid input for _is_power_of_2: {} (type: {})'.format( + n, type(n))) + return (n & (n - 1) == 0) and n != 0 + + if not _is_power_of_2(dim_per_head): + warnings.warn( + "You'd better set embed_dims in " + 'MultiScaleDeformAttention to make ' + 'the dimension of each attention head a power of 2 ' + 'which is more efficient in our CUDA implementation.') + + self.im2col_step = im2col_step + self.embed_dims = embed_dims + self.num_levels = num_levels + self.num_heads = num_heads + self.num_points = num_points + self.sampling_offsets = nn.Linear( + embed_dims, num_heads * num_levels * num_points * 2) + self.attention_weights = nn.Linear(embed_dims, + num_heads * num_levels * num_points) + self.value_proj = nn.Linear(embed_dims, embed_dims) + self.output_proj = nn.Linear(embed_dims, embed_dims) + self.init_weights() + + def init_weights(self): + """Default initialization for Parameters of Module.""" + constant_init(self.sampling_offsets, 0.) + thetas = torch.arange( + self.num_heads, + dtype=torch.float32) * (2.0 * math.pi / self.num_heads) + grid_init = torch.stack([thetas.cos(), thetas.sin()], -1) + grid_init = (grid_init / + grid_init.abs().max(-1, keepdim=True)[0]).view( + self.num_heads, 1, 1, + 2).repeat(1, self.num_levels, self.num_points, 1) + for i in range(self.num_points): + grid_init[:, :, i, :] *= i + 1 + + self.sampling_offsets.bias.data = grid_init.view(-1) + constant_init(self.attention_weights, val=0., bias=0.) + xavier_init(self.value_proj, distribution='uniform', bias=0.) + xavier_init(self.output_proj, distribution='uniform', bias=0.) + self._is_init = True + + @deprecated_api_warning({'residual': 'identity'}, + cls_name='MultiScaleDeformableAttention') + def forward(self, + query, + key=None, + value=None, + identity=None, + query_pos=None, + key_padding_mask=None, + reference_points=None, + spatial_shapes=None, + level_start_index=None, + **kwargs): + """Forward Function of MultiScaleDeformAttention. + + Args: + query (Tensor): Query of Transformer with shape + (num_query, bs, embed_dims). + key (Tensor): The key tensor with shape + `(num_key, bs, embed_dims)`. + value (Tensor): The value tensor with shape + `(num_key, bs, embed_dims)`. + identity (Tensor): The tensor used for addition, with the + same shape as `query`. Default None. If None, + `query` will be used. + query_pos (Tensor): The positional encoding for `query`. + Default: None. + key_pos (Tensor): The positional encoding for `key`. Default + None. + reference_points (Tensor): The normalized reference + points with shape (bs, num_query, num_levels, 2), + all elements is range in [0, 1], top-left (0,0), + bottom-right (1, 1), including padding area. + or (N, Length_{query}, num_levels, 4), add + additional two dimensions is (w, h) to + form reference boxes. + key_padding_mask (Tensor): ByteTensor for `query`, with + shape [bs, num_key]. + spatial_shapes (Tensor): Spatial shape of features in + different levels. With shape (num_levels, 2), + last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape ``(num_levels, )`` and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + + Returns: + Tensor: forwarded results with shape [num_query, bs, embed_dims]. + """ + + if value is None: + value = query + + if identity is None: + identity = query + if query_pos is not None: + query = query + query_pos + if not self.batch_first: + # change to (bs, num_query ,embed_dims) + query = query.permute(1, 0, 2) + value = value.permute(1, 0, 2) + + bs, num_query, _ = query.shape + bs, num_value, _ = value.shape + assert (spatial_shapes[:, 0] * spatial_shapes[:, 1]).sum() == num_value + + value = self.value_proj(value) + if key_padding_mask is not None: + value = value.masked_fill(key_padding_mask[..., None], 0.0) + value = value.view(bs, num_value, self.num_heads, -1) + sampling_offsets = self.sampling_offsets(query).view( + bs, num_query, self.num_heads, self.num_levels, self.num_points, 2) + attention_weights = self.attention_weights(query).view( + bs, num_query, self.num_heads, self.num_levels * self.num_points) + attention_weights = attention_weights.softmax(-1) + + attention_weights = attention_weights.view(bs, num_query, + self.num_heads, + self.num_levels, + self.num_points) + if reference_points.shape[-1] == 2: + offset_normalizer = torch.stack( + [spatial_shapes[..., 1], spatial_shapes[..., 0]], -1) + sampling_locations = reference_points[:, :, None, :, None, :] \ + + sampling_offsets \ + / offset_normalizer[None, None, None, :, None, :] + elif reference_points.shape[-1] == 4: + sampling_locations = reference_points[:, :, None, :, None, :2] \ + + sampling_offsets / self.num_points \ + * reference_points[:, :, None, :, None, 2:] \ + * 0.5 + else: + raise ValueError( + f'Last dim of reference_points must be' + f' 2 or 4, but get {reference_points.shape[-1]} instead.') + if torch.cuda.is_available() and value.is_cuda: + output = MultiScaleDeformableAttnFunction.apply( + value, spatial_shapes, level_start_index, sampling_locations, + attention_weights, self.im2col_step) + else: + output = multi_scale_deformable_attn_pytorch( + value, spatial_shapes, sampling_locations, attention_weights) + + output = self.output_proj(output) + + if not self.batch_first: + # (num_query, bs ,embed_dims) + output = output.permute(1, 0, 2) + + return self.dropout(output) + identity diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/nms.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/nms.py new file mode 100644 index 0000000..6d96342 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/nms.py @@ -0,0 +1,417 @@ +import os + +import numpy as np +import torch + +from annotator.uniformer.mmcv.utils import deprecated_api_warning +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['nms', 'softnms', 'nms_match', 'nms_rotated']) + + +# This function is modified from: https://github.com/pytorch/vision/ +class NMSop(torch.autograd.Function): + + @staticmethod + def forward(ctx, bboxes, scores, iou_threshold, offset, score_threshold, + max_num): + is_filtering_by_score = score_threshold > 0 + if is_filtering_by_score: + valid_mask = scores > score_threshold + bboxes, scores = bboxes[valid_mask], scores[valid_mask] + valid_inds = torch.nonzero( + valid_mask, as_tuple=False).squeeze(dim=1) + + inds = ext_module.nms( + bboxes, scores, iou_threshold=float(iou_threshold), offset=offset) + + if max_num > 0: + inds = inds[:max_num] + if is_filtering_by_score: + inds = valid_inds[inds] + return inds + + @staticmethod + def symbolic(g, bboxes, scores, iou_threshold, offset, score_threshold, + max_num): + from ..onnx import is_custom_op_loaded + has_custom_op = is_custom_op_loaded() + # TensorRT nms plugin is aligned with original nms in ONNXRuntime + is_trt_backend = os.environ.get('ONNX_BACKEND') == 'MMCVTensorRT' + if has_custom_op and (not is_trt_backend): + return g.op( + 'mmcv::NonMaxSuppression', + bboxes, + scores, + iou_threshold_f=float(iou_threshold), + offset_i=int(offset)) + else: + from torch.onnx.symbolic_opset9 import select, squeeze, unsqueeze + from ..onnx.onnx_utils.symbolic_helper import _size_helper + + boxes = unsqueeze(g, bboxes, 0) + scores = unsqueeze(g, unsqueeze(g, scores, 0), 0) + + if max_num > 0: + max_num = g.op( + 'Constant', + value_t=torch.tensor(max_num, dtype=torch.long)) + else: + dim = g.op('Constant', value_t=torch.tensor(0)) + max_num = _size_helper(g, bboxes, dim) + max_output_per_class = max_num + iou_threshold = g.op( + 'Constant', + value_t=torch.tensor([iou_threshold], dtype=torch.float)) + score_threshold = g.op( + 'Constant', + value_t=torch.tensor([score_threshold], dtype=torch.float)) + nms_out = g.op('NonMaxSuppression', boxes, scores, + max_output_per_class, iou_threshold, + score_threshold) + return squeeze( + g, + select( + g, nms_out, 1, + g.op( + 'Constant', + value_t=torch.tensor([2], dtype=torch.long))), 1) + + +class SoftNMSop(torch.autograd.Function): + + @staticmethod + def forward(ctx, boxes, scores, iou_threshold, sigma, min_score, method, + offset): + dets = boxes.new_empty((boxes.size(0), 5), device='cpu') + inds = ext_module.softnms( + boxes.cpu(), + scores.cpu(), + dets.cpu(), + iou_threshold=float(iou_threshold), + sigma=float(sigma), + min_score=float(min_score), + method=int(method), + offset=int(offset)) + return dets, inds + + @staticmethod + def symbolic(g, boxes, scores, iou_threshold, sigma, min_score, method, + offset): + from packaging import version + assert version.parse(torch.__version__) >= version.parse('1.7.0') + nms_out = g.op( + 'mmcv::SoftNonMaxSuppression', + boxes, + scores, + iou_threshold_f=float(iou_threshold), + sigma_f=float(sigma), + min_score_f=float(min_score), + method_i=int(method), + offset_i=int(offset), + outputs=2) + return nms_out + + +@deprecated_api_warning({'iou_thr': 'iou_threshold'}) +def nms(boxes, scores, iou_threshold, offset=0, score_threshold=0, max_num=-1): + """Dispatch to either CPU or GPU NMS implementations. + + The input can be either torch tensor or numpy array. GPU NMS will be used + if the input is gpu tensor, otherwise CPU NMS + will be used. The returned type will always be the same as inputs. + + Arguments: + boxes (torch.Tensor or np.ndarray): boxes in shape (N, 4). + scores (torch.Tensor or np.ndarray): scores in shape (N, ). + iou_threshold (float): IoU threshold for NMS. + offset (int, 0 or 1): boxes' width or height is (x2 - x1 + offset). + score_threshold (float): score threshold for NMS. + max_num (int): maximum number of boxes after NMS. + + Returns: + tuple: kept dets(boxes and scores) and indice, which is always the \ + same data type as the input. + + Example: + >>> boxes = np.array([[49.1, 32.4, 51.0, 35.9], + >>> [49.3, 32.9, 51.0, 35.3], + >>> [49.2, 31.8, 51.0, 35.4], + >>> [35.1, 11.5, 39.1, 15.7], + >>> [35.6, 11.8, 39.3, 14.2], + >>> [35.3, 11.5, 39.9, 14.5], + >>> [35.2, 11.7, 39.7, 15.7]], dtype=np.float32) + >>> scores = np.array([0.9, 0.9, 0.5, 0.5, 0.5, 0.4, 0.3],\ + dtype=np.float32) + >>> iou_threshold = 0.6 + >>> dets, inds = nms(boxes, scores, iou_threshold) + >>> assert len(inds) == len(dets) == 3 + """ + assert isinstance(boxes, (torch.Tensor, np.ndarray)) + assert isinstance(scores, (torch.Tensor, np.ndarray)) + is_numpy = False + if isinstance(boxes, np.ndarray): + is_numpy = True + boxes = torch.from_numpy(boxes) + if isinstance(scores, np.ndarray): + scores = torch.from_numpy(scores) + assert boxes.size(1) == 4 + assert boxes.size(0) == scores.size(0) + assert offset in (0, 1) + + if torch.__version__ == 'parrots': + indata_list = [boxes, scores] + indata_dict = { + 'iou_threshold': float(iou_threshold), + 'offset': int(offset) + } + inds = ext_module.nms(*indata_list, **indata_dict) + else: + inds = NMSop.apply(boxes, scores, iou_threshold, offset, + score_threshold, max_num) + dets = torch.cat((boxes[inds], scores[inds].reshape(-1, 1)), dim=1) + if is_numpy: + dets = dets.cpu().numpy() + inds = inds.cpu().numpy() + return dets, inds + + +@deprecated_api_warning({'iou_thr': 'iou_threshold'}) +def soft_nms(boxes, + scores, + iou_threshold=0.3, + sigma=0.5, + min_score=1e-3, + method='linear', + offset=0): + """Dispatch to only CPU Soft NMS implementations. + + The input can be either a torch tensor or numpy array. + The returned type will always be the same as inputs. + + Arguments: + boxes (torch.Tensor or np.ndarray): boxes in shape (N, 4). + scores (torch.Tensor or np.ndarray): scores in shape (N, ). + iou_threshold (float): IoU threshold for NMS. + sigma (float): hyperparameter for gaussian method + min_score (float): score filter threshold + method (str): either 'linear' or 'gaussian' + offset (int, 0 or 1): boxes' width or height is (x2 - x1 + offset). + + Returns: + tuple: kept dets(boxes and scores) and indice, which is always the \ + same data type as the input. + + Example: + >>> boxes = np.array([[4., 3., 5., 3.], + >>> [4., 3., 5., 4.], + >>> [3., 1., 3., 1.], + >>> [3., 1., 3., 1.], + >>> [3., 1., 3., 1.], + >>> [3., 1., 3., 1.]], dtype=np.float32) + >>> scores = np.array([0.9, 0.9, 0.5, 0.5, 0.4, 0.0], dtype=np.float32) + >>> iou_threshold = 0.6 + >>> dets, inds = soft_nms(boxes, scores, iou_threshold, sigma=0.5) + >>> assert len(inds) == len(dets) == 5 + """ + + assert isinstance(boxes, (torch.Tensor, np.ndarray)) + assert isinstance(scores, (torch.Tensor, np.ndarray)) + is_numpy = False + if isinstance(boxes, np.ndarray): + is_numpy = True + boxes = torch.from_numpy(boxes) + if isinstance(scores, np.ndarray): + scores = torch.from_numpy(scores) + assert boxes.size(1) == 4 + assert boxes.size(0) == scores.size(0) + assert offset in (0, 1) + method_dict = {'naive': 0, 'linear': 1, 'gaussian': 2} + assert method in method_dict.keys() + + if torch.__version__ == 'parrots': + dets = boxes.new_empty((boxes.size(0), 5), device='cpu') + indata_list = [boxes.cpu(), scores.cpu(), dets.cpu()] + indata_dict = { + 'iou_threshold': float(iou_threshold), + 'sigma': float(sigma), + 'min_score': min_score, + 'method': method_dict[method], + 'offset': int(offset) + } + inds = ext_module.softnms(*indata_list, **indata_dict) + else: + dets, inds = SoftNMSop.apply(boxes.cpu(), scores.cpu(), + float(iou_threshold), float(sigma), + float(min_score), method_dict[method], + int(offset)) + + dets = dets[:inds.size(0)] + + if is_numpy: + dets = dets.cpu().numpy() + inds = inds.cpu().numpy() + return dets, inds + else: + return dets.to(device=boxes.device), inds.to(device=boxes.device) + + +def batched_nms(boxes, scores, idxs, nms_cfg, class_agnostic=False): + """Performs non-maximum suppression in a batched fashion. + + Modified from https://github.com/pytorch/vision/blob + /505cd6957711af790211896d32b40291bea1bc21/torchvision/ops/boxes.py#L39. + In order to perform NMS independently per class, we add an offset to all + the boxes. The offset is dependent only on the class idx, and is large + enough so that boxes from different classes do not overlap. + + Arguments: + boxes (torch.Tensor): boxes in shape (N, 4). + scores (torch.Tensor): scores in shape (N, ). + idxs (torch.Tensor): each index value correspond to a bbox cluster, + and NMS will not be applied between elements of different idxs, + shape (N, ). + nms_cfg (dict): specify nms type and other parameters like iou_thr. + Possible keys includes the following. + + - iou_thr (float): IoU threshold used for NMS. + - split_thr (float): threshold number of boxes. In some cases the + number of boxes is large (e.g., 200k). To avoid OOM during + training, the users could set `split_thr` to a small value. + If the number of boxes is greater than the threshold, it will + perform NMS on each group of boxes separately and sequentially. + Defaults to 10000. + class_agnostic (bool): if true, nms is class agnostic, + i.e. IoU thresholding happens over all boxes, + regardless of the predicted class. + + Returns: + tuple: kept dets and indice. + """ + nms_cfg_ = nms_cfg.copy() + class_agnostic = nms_cfg_.pop('class_agnostic', class_agnostic) + if class_agnostic: + boxes_for_nms = boxes + else: + max_coordinate = boxes.max() + offsets = idxs.to(boxes) * (max_coordinate + torch.tensor(1).to(boxes)) + boxes_for_nms = boxes + offsets[:, None] + + nms_type = nms_cfg_.pop('type', 'nms') + nms_op = eval(nms_type) + + split_thr = nms_cfg_.pop('split_thr', 10000) + # Won't split to multiple nms nodes when exporting to onnx + if boxes_for_nms.shape[0] < split_thr or torch.onnx.is_in_onnx_export(): + dets, keep = nms_op(boxes_for_nms, scores, **nms_cfg_) + boxes = boxes[keep] + # -1 indexing works abnormal in TensorRT + # This assumes `dets` has 5 dimensions where + # the last dimension is score. + # TODO: more elegant way to handle the dimension issue. + # Some type of nms would reweight the score, such as SoftNMS + scores = dets[:, 4] + else: + max_num = nms_cfg_.pop('max_num', -1) + total_mask = scores.new_zeros(scores.size(), dtype=torch.bool) + # Some type of nms would reweight the score, such as SoftNMS + scores_after_nms = scores.new_zeros(scores.size()) + for id in torch.unique(idxs): + mask = (idxs == id).nonzero(as_tuple=False).view(-1) + dets, keep = nms_op(boxes_for_nms[mask], scores[mask], **nms_cfg_) + total_mask[mask[keep]] = True + scores_after_nms[mask[keep]] = dets[:, -1] + keep = total_mask.nonzero(as_tuple=False).view(-1) + + scores, inds = scores_after_nms[keep].sort(descending=True) + keep = keep[inds] + boxes = boxes[keep] + + if max_num > 0: + keep = keep[:max_num] + boxes = boxes[:max_num] + scores = scores[:max_num] + + return torch.cat([boxes, scores[:, None]], -1), keep + + +def nms_match(dets, iou_threshold): + """Matched dets into different groups by NMS. + + NMS match is Similar to NMS but when a bbox is suppressed, nms match will + record the indice of suppressed bbox and form a group with the indice of + kept bbox. In each group, indice is sorted as score order. + + Arguments: + dets (torch.Tensor | np.ndarray): Det boxes with scores, shape (N, 5). + iou_thr (float): IoU thresh for NMS. + + Returns: + List[torch.Tensor | np.ndarray]: The outer list corresponds different + matched group, the inner Tensor corresponds the indices for a group + in score order. + """ + if dets.shape[0] == 0: + matched = [] + else: + assert dets.shape[-1] == 5, 'inputs dets.shape should be (N, 5), ' \ + f'but get {dets.shape}' + if isinstance(dets, torch.Tensor): + dets_t = dets.detach().cpu() + else: + dets_t = torch.from_numpy(dets) + indata_list = [dets_t] + indata_dict = {'iou_threshold': float(iou_threshold)} + matched = ext_module.nms_match(*indata_list, **indata_dict) + if torch.__version__ == 'parrots': + matched = matched.tolist() + + if isinstance(dets, torch.Tensor): + return [dets.new_tensor(m, dtype=torch.long) for m in matched] + else: + return [np.array(m, dtype=np.int) for m in matched] + + +def nms_rotated(dets, scores, iou_threshold, labels=None): + """Performs non-maximum suppression (NMS) on the rotated boxes according to + their intersection-over-union (IoU). + + Rotated NMS iteratively removes lower scoring rotated boxes which have an + IoU greater than iou_threshold with another (higher scoring) rotated box. + + Args: + boxes (Tensor): Rotated boxes in shape (N, 5). They are expected to \ + be in (x_ctr, y_ctr, width, height, angle_radian) format. + scores (Tensor): scores in shape (N, ). + iou_threshold (float): IoU thresh for NMS. + labels (Tensor): boxes' label in shape (N,). + + Returns: + tuple: kept dets(boxes and scores) and indice, which is always the \ + same data type as the input. + """ + if dets.shape[0] == 0: + return dets, None + multi_label = labels is not None + if multi_label: + dets_wl = torch.cat((dets, labels.unsqueeze(1)), 1) + else: + dets_wl = dets + _, order = scores.sort(0, descending=True) + dets_sorted = dets_wl.index_select(0, order) + + if torch.__version__ == 'parrots': + keep_inds = ext_module.nms_rotated( + dets_wl, + scores, + order, + dets_sorted, + iou_threshold=iou_threshold, + multi_label=multi_label) + else: + keep_inds = ext_module.nms_rotated(dets_wl, scores, order, dets_sorted, + iou_threshold, multi_label) + dets = torch.cat((dets[keep_inds], scores[keep_inds].reshape(-1, 1)), + dim=1) + return dets, keep_inds diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/pixel_group.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/pixel_group.py new file mode 100644 index 0000000..2143c75 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/pixel_group.py @@ -0,0 +1,75 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import torch + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['pixel_group']) + + +def pixel_group(score, mask, embedding, kernel_label, kernel_contour, + kernel_region_num, distance_threshold): + """Group pixels into text instances, which is widely used text detection + methods. + + Arguments: + score (np.array or Tensor): The foreground score with size hxw. + mask (np.array or Tensor): The foreground mask with size hxw. + embedding (np.array or Tensor): The embedding with size hxwxc to + distinguish instances. + kernel_label (np.array or Tensor): The instance kernel index with + size hxw. + kernel_contour (np.array or Tensor): The kernel contour with size hxw. + kernel_region_num (int): The instance kernel region number. + distance_threshold (float): The embedding distance threshold between + kernel and pixel in one instance. + + Returns: + pixel_assignment (List[List[float]]): The instance coordinate list. + Each element consists of averaged confidence, pixel number, and + coordinates (x_i, y_i for all pixels) in order. + """ + assert isinstance(score, (torch.Tensor, np.ndarray)) + assert isinstance(mask, (torch.Tensor, np.ndarray)) + assert isinstance(embedding, (torch.Tensor, np.ndarray)) + assert isinstance(kernel_label, (torch.Tensor, np.ndarray)) + assert isinstance(kernel_contour, (torch.Tensor, np.ndarray)) + assert isinstance(kernel_region_num, int) + assert isinstance(distance_threshold, float) + + if isinstance(score, np.ndarray): + score = torch.from_numpy(score) + if isinstance(mask, np.ndarray): + mask = torch.from_numpy(mask) + if isinstance(embedding, np.ndarray): + embedding = torch.from_numpy(embedding) + if isinstance(kernel_label, np.ndarray): + kernel_label = torch.from_numpy(kernel_label) + if isinstance(kernel_contour, np.ndarray): + kernel_contour = torch.from_numpy(kernel_contour) + + if torch.__version__ == 'parrots': + label = ext_module.pixel_group( + score, + mask, + embedding, + kernel_label, + kernel_contour, + kernel_region_num=kernel_region_num, + distance_threshold=distance_threshold) + label = label.tolist() + label = label[0] + list_index = kernel_region_num + pixel_assignment = [] + for x in range(kernel_region_num): + pixel_assignment.append( + np.array( + label[list_index:list_index + int(label[x])], + dtype=np.float)) + list_index = list_index + int(label[x]) + else: + pixel_assignment = ext_module.pixel_group(score, mask, embedding, + kernel_label, kernel_contour, + kernel_region_num, + distance_threshold) + return pixel_assignment diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/point_sample.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/point_sample.py new file mode 100644 index 0000000..267f4b3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/point_sample.py @@ -0,0 +1,336 @@ +# Modified from https://github.com/facebookresearch/detectron2/tree/master/projects/PointRend # noqa + +from os import path as osp + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.nn.modules.utils import _pair +from torch.onnx.operators import shape_as_tensor + + +def bilinear_grid_sample(im, grid, align_corners=False): + """Given an input and a flow-field grid, computes the output using input + values and pixel locations from grid. Supported only bilinear interpolation + method to sample the input pixels. + + Args: + im (torch.Tensor): Input feature map, shape (N, C, H, W) + grid (torch.Tensor): Point coordinates, shape (N, Hg, Wg, 2) + align_corners {bool}: If set to True, the extrema (-1 and 1) are + considered as referring to the center points of the input’s + corner pixels. If set to False, they are instead considered as + referring to the corner points of the input’s corner pixels, + making the sampling more resolution agnostic. + Returns: + torch.Tensor: A tensor with sampled points, shape (N, C, Hg, Wg) + """ + n, c, h, w = im.shape + gn, gh, gw, _ = grid.shape + assert n == gn + + x = grid[:, :, :, 0] + y = grid[:, :, :, 1] + + if align_corners: + x = ((x + 1) / 2) * (w - 1) + y = ((y + 1) / 2) * (h - 1) + else: + x = ((x + 1) * w - 1) / 2 + y = ((y + 1) * h - 1) / 2 + + x = x.view(n, -1) + y = y.view(n, -1) + + x0 = torch.floor(x).long() + y0 = torch.floor(y).long() + x1 = x0 + 1 + y1 = y0 + 1 + + wa = ((x1 - x) * (y1 - y)).unsqueeze(1) + wb = ((x1 - x) * (y - y0)).unsqueeze(1) + wc = ((x - x0) * (y1 - y)).unsqueeze(1) + wd = ((x - x0) * (y - y0)).unsqueeze(1) + + # Apply default for grid_sample function zero padding + im_padded = F.pad(im, pad=[1, 1, 1, 1], mode='constant', value=0) + padded_h = h + 2 + padded_w = w + 2 + # save points positions after padding + x0, x1, y0, y1 = x0 + 1, x1 + 1, y0 + 1, y1 + 1 + + # Clip coordinates to padded image size + x0 = torch.where(x0 < 0, torch.tensor(0), x0) + x0 = torch.where(x0 > padded_w - 1, torch.tensor(padded_w - 1), x0) + x1 = torch.where(x1 < 0, torch.tensor(0), x1) + x1 = torch.where(x1 > padded_w - 1, torch.tensor(padded_w - 1), x1) + y0 = torch.where(y0 < 0, torch.tensor(0), y0) + y0 = torch.where(y0 > padded_h - 1, torch.tensor(padded_h - 1), y0) + y1 = torch.where(y1 < 0, torch.tensor(0), y1) + y1 = torch.where(y1 > padded_h - 1, torch.tensor(padded_h - 1), y1) + + im_padded = im_padded.view(n, c, -1) + + x0_y0 = (x0 + y0 * padded_w).unsqueeze(1).expand(-1, c, -1) + x0_y1 = (x0 + y1 * padded_w).unsqueeze(1).expand(-1, c, -1) + x1_y0 = (x1 + y0 * padded_w).unsqueeze(1).expand(-1, c, -1) + x1_y1 = (x1 + y1 * padded_w).unsqueeze(1).expand(-1, c, -1) + + Ia = torch.gather(im_padded, 2, x0_y0) + Ib = torch.gather(im_padded, 2, x0_y1) + Ic = torch.gather(im_padded, 2, x1_y0) + Id = torch.gather(im_padded, 2, x1_y1) + + return (Ia * wa + Ib * wb + Ic * wc + Id * wd).reshape(n, c, gh, gw) + + +def is_in_onnx_export_without_custom_ops(): + from annotator.uniformer.mmcv.ops import get_onnxruntime_op_path + ort_custom_op_path = get_onnxruntime_op_path() + return torch.onnx.is_in_onnx_export( + ) and not osp.exists(ort_custom_op_path) + + +def normalize(grid): + """Normalize input grid from [-1, 1] to [0, 1] + Args: + grid (Tensor): The grid to be normalize, range [-1, 1]. + Returns: + Tensor: Normalized grid, range [0, 1]. + """ + + return (grid + 1.0) / 2.0 + + +def denormalize(grid): + """Denormalize input grid from range [0, 1] to [-1, 1] + Args: + grid (Tensor): The grid to be denormalize, range [0, 1]. + Returns: + Tensor: Denormalized grid, range [-1, 1]. + """ + + return grid * 2.0 - 1.0 + + +def generate_grid(num_grid, size, device): + """Generate regular square grid of points in [0, 1] x [0, 1] coordinate + space. + + Args: + num_grid (int): The number of grids to sample, one for each region. + size (tuple(int, int)): The side size of the regular grid. + device (torch.device): Desired device of returned tensor. + + Returns: + (torch.Tensor): A tensor of shape (num_grid, size[0]*size[1], 2) that + contains coordinates for the regular grids. + """ + + affine_trans = torch.tensor([[[1., 0., 0.], [0., 1., 0.]]], device=device) + grid = F.affine_grid( + affine_trans, torch.Size((1, 1, *size)), align_corners=False) + grid = normalize(grid) + return grid.view(1, -1, 2).expand(num_grid, -1, -1) + + +def rel_roi_point_to_abs_img_point(rois, rel_roi_points): + """Convert roi based relative point coordinates to image based absolute + point coordinates. + + Args: + rois (Tensor): RoIs or BBoxes, shape (N, 4) or (N, 5) + rel_roi_points (Tensor): Point coordinates inside RoI, relative to + RoI, location, range (0, 1), shape (N, P, 2) + Returns: + Tensor: Image based absolute point coordinates, shape (N, P, 2) + """ + + with torch.no_grad(): + assert rel_roi_points.size(0) == rois.size(0) + assert rois.dim() == 2 + assert rel_roi_points.dim() == 3 + assert rel_roi_points.size(2) == 2 + # remove batch idx + if rois.size(1) == 5: + rois = rois[:, 1:] + abs_img_points = rel_roi_points.clone() + # To avoid an error during exporting to onnx use independent + # variables instead inplace computation + xs = abs_img_points[:, :, 0] * (rois[:, None, 2] - rois[:, None, 0]) + ys = abs_img_points[:, :, 1] * (rois[:, None, 3] - rois[:, None, 1]) + xs += rois[:, None, 0] + ys += rois[:, None, 1] + abs_img_points = torch.stack([xs, ys], dim=2) + return abs_img_points + + +def get_shape_from_feature_map(x): + """Get spatial resolution of input feature map considering exporting to + onnx mode. + + Args: + x (torch.Tensor): Input tensor, shape (N, C, H, W) + Returns: + torch.Tensor: Spatial resolution (width, height), shape (1, 1, 2) + """ + if torch.onnx.is_in_onnx_export(): + img_shape = shape_as_tensor(x)[2:].flip(0).view(1, 1, 2).to( + x.device).float() + else: + img_shape = torch.tensor(x.shape[2:]).flip(0).view(1, 1, 2).to( + x.device).float() + return img_shape + + +def abs_img_point_to_rel_img_point(abs_img_points, img, spatial_scale=1.): + """Convert image based absolute point coordinates to image based relative + coordinates for sampling. + + Args: + abs_img_points (Tensor): Image based absolute point coordinates, + shape (N, P, 2) + img (tuple/Tensor): (height, width) of image or feature map. + spatial_scale (float): Scale points by this factor. Default: 1. + + Returns: + Tensor: Image based relative point coordinates for sampling, + shape (N, P, 2) + """ + + assert (isinstance(img, tuple) and len(img) == 2) or \ + (isinstance(img, torch.Tensor) and len(img.shape) == 4) + + if isinstance(img, tuple): + h, w = img + scale = torch.tensor([w, h], + dtype=torch.float, + device=abs_img_points.device) + scale = scale.view(1, 1, 2) + else: + scale = get_shape_from_feature_map(img) + + return abs_img_points / scale * spatial_scale + + +def rel_roi_point_to_rel_img_point(rois, + rel_roi_points, + img, + spatial_scale=1.): + """Convert roi based relative point coordinates to image based absolute + point coordinates. + + Args: + rois (Tensor): RoIs or BBoxes, shape (N, 4) or (N, 5) + rel_roi_points (Tensor): Point coordinates inside RoI, relative to + RoI, location, range (0, 1), shape (N, P, 2) + img (tuple/Tensor): (height, width) of image or feature map. + spatial_scale (float): Scale points by this factor. Default: 1. + + Returns: + Tensor: Image based relative point coordinates for sampling, + shape (N, P, 2) + """ + + abs_img_point = rel_roi_point_to_abs_img_point(rois, rel_roi_points) + rel_img_point = abs_img_point_to_rel_img_point(abs_img_point, img, + spatial_scale) + + return rel_img_point + + +def point_sample(input, points, align_corners=False, **kwargs): + """A wrapper around :func:`grid_sample` to support 3D point_coords tensors + Unlike :func:`torch.nn.functional.grid_sample` it assumes point_coords to + lie inside ``[0, 1] x [0, 1]`` square. + + Args: + input (Tensor): Feature map, shape (N, C, H, W). + points (Tensor): Image based absolute point coordinates (normalized), + range [0, 1] x [0, 1], shape (N, P, 2) or (N, Hgrid, Wgrid, 2). + align_corners (bool): Whether align_corners. Default: False + + Returns: + Tensor: Features of `point` on `input`, shape (N, C, P) or + (N, C, Hgrid, Wgrid). + """ + + add_dim = False + if points.dim() == 3: + add_dim = True + points = points.unsqueeze(2) + if is_in_onnx_export_without_custom_ops(): + # If custom ops for onnx runtime not compiled use python + # implementation of grid_sample function to make onnx graph + # with supported nodes + output = bilinear_grid_sample( + input, denormalize(points), align_corners=align_corners) + else: + output = F.grid_sample( + input, denormalize(points), align_corners=align_corners, **kwargs) + if add_dim: + output = output.squeeze(3) + return output + + +class SimpleRoIAlign(nn.Module): + + def __init__(self, output_size, spatial_scale, aligned=True): + """Simple RoI align in PointRend, faster than standard RoIAlign. + + Args: + output_size (tuple[int]): h, w + spatial_scale (float): scale the input boxes by this number + aligned (bool): if False, use the legacy implementation in + MMDetection, align_corners=True will be used in F.grid_sample. + If True, align the results more perfectly. + """ + + super(SimpleRoIAlign, self).__init__() + self.output_size = _pair(output_size) + self.spatial_scale = float(spatial_scale) + # to be consistent with other RoI ops + self.use_torchvision = False + self.aligned = aligned + + def forward(self, features, rois): + num_imgs = features.size(0) + num_rois = rois.size(0) + rel_roi_points = generate_grid( + num_rois, self.output_size, device=rois.device) + + if torch.onnx.is_in_onnx_export(): + rel_img_points = rel_roi_point_to_rel_img_point( + rois, rel_roi_points, features, self.spatial_scale) + rel_img_points = rel_img_points.reshape(num_imgs, -1, + *rel_img_points.shape[1:]) + point_feats = point_sample( + features, rel_img_points, align_corners=not self.aligned) + point_feats = point_feats.transpose(1, 2) + else: + point_feats = [] + for batch_ind in range(num_imgs): + # unravel batch dim + feat = features[batch_ind].unsqueeze(0) + inds = (rois[:, 0].long() == batch_ind) + if inds.any(): + rel_img_points = rel_roi_point_to_rel_img_point( + rois[inds], rel_roi_points[inds], feat, + self.spatial_scale).unsqueeze(0) + point_feat = point_sample( + feat, rel_img_points, align_corners=not self.aligned) + point_feat = point_feat.squeeze(0).transpose(0, 1) + point_feats.append(point_feat) + + point_feats = torch.cat(point_feats, dim=0) + + channels = features.size(1) + roi_feats = point_feats.reshape(num_rois, channels, *self.output_size) + + return roi_feats + + def __repr__(self): + format_str = self.__class__.__name__ + format_str += '(output_size={}, spatial_scale={}'.format( + self.output_size, self.spatial_scale) + return format_str diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/points_in_boxes.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/points_in_boxes.py new file mode 100644 index 0000000..4003173 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/points_in_boxes.py @@ -0,0 +1,133 @@ +import torch + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', [ + 'points_in_boxes_part_forward', 'points_in_boxes_cpu_forward', + 'points_in_boxes_all_forward' +]) + + +def points_in_boxes_part(points, boxes): + """Find the box in which each point is (CUDA). + + Args: + points (torch.Tensor): [B, M, 3], [x, y, z] in LiDAR/DEPTH coordinate + boxes (torch.Tensor): [B, T, 7], + num_valid_boxes <= T, [x, y, z, x_size, y_size, z_size, rz] in + LiDAR/DEPTH coordinate, (x, y, z) is the bottom center + + Returns: + box_idxs_of_pts (torch.Tensor): (B, M), default background = -1 + """ + assert points.shape[0] == boxes.shape[0], \ + 'Points and boxes should have the same batch size, ' \ + f'but got {points.shape[0]} and {boxes.shape[0]}' + assert boxes.shape[2] == 7, \ + 'boxes dimension should be 7, ' \ + f'but got unexpected shape {boxes.shape[2]}' + assert points.shape[2] == 3, \ + 'points dimension should be 3, ' \ + f'but got unexpected shape {points.shape[2]}' + batch_size, num_points, _ = points.shape + + box_idxs_of_pts = points.new_zeros((batch_size, num_points), + dtype=torch.int).fill_(-1) + + # If manually put the tensor 'points' or 'boxes' on a device + # which is not the current device, some temporary variables + # will be created on the current device in the cuda op, + # and the output will be incorrect. + # Therefore, we force the current device to be the same + # as the device of the tensors if it was not. + # Please refer to https://github.com/open-mmlab/mmdetection3d/issues/305 + # for the incorrect output before the fix. + points_device = points.get_device() + assert points_device == boxes.get_device(), \ + 'Points and boxes should be put on the same device' + if torch.cuda.current_device() != points_device: + torch.cuda.set_device(points_device) + + ext_module.points_in_boxes_part_forward(boxes.contiguous(), + points.contiguous(), + box_idxs_of_pts) + + return box_idxs_of_pts + + +def points_in_boxes_cpu(points, boxes): + """Find all boxes in which each point is (CPU). The CPU version of + :meth:`points_in_boxes_all`. + + Args: + points (torch.Tensor): [B, M, 3], [x, y, z] in + LiDAR/DEPTH coordinate + boxes (torch.Tensor): [B, T, 7], + num_valid_boxes <= T, [x, y, z, x_size, y_size, z_size, rz], + (x, y, z) is the bottom center. + + Returns: + box_idxs_of_pts (torch.Tensor): (B, M, T), default background = 0. + """ + assert points.shape[0] == boxes.shape[0], \ + 'Points and boxes should have the same batch size, ' \ + f'but got {points.shape[0]} and {boxes.shape[0]}' + assert boxes.shape[2] == 7, \ + 'boxes dimension should be 7, ' \ + f'but got unexpected shape {boxes.shape[2]}' + assert points.shape[2] == 3, \ + 'points dimension should be 3, ' \ + f'but got unexpected shape {points.shape[2]}' + batch_size, num_points, _ = points.shape + num_boxes = boxes.shape[1] + + point_indices = points.new_zeros((batch_size, num_boxes, num_points), + dtype=torch.int) + for b in range(batch_size): + ext_module.points_in_boxes_cpu_forward(boxes[b].float().contiguous(), + points[b].float().contiguous(), + point_indices[b]) + point_indices = point_indices.transpose(1, 2) + + return point_indices + + +def points_in_boxes_all(points, boxes): + """Find all boxes in which each point is (CUDA). + + Args: + points (torch.Tensor): [B, M, 3], [x, y, z] in LiDAR/DEPTH coordinate + boxes (torch.Tensor): [B, T, 7], + num_valid_boxes <= T, [x, y, z, x_size, y_size, z_size, rz], + (x, y, z) is the bottom center. + + Returns: + box_idxs_of_pts (torch.Tensor): (B, M, T), default background = 0. + """ + assert boxes.shape[0] == points.shape[0], \ + 'Points and boxes should have the same batch size, ' \ + f'but got {boxes.shape[0]} and {boxes.shape[0]}' + assert boxes.shape[2] == 7, \ + 'boxes dimension should be 7, ' \ + f'but got unexpected shape {boxes.shape[2]}' + assert points.shape[2] == 3, \ + 'points dimension should be 3, ' \ + f'but got unexpected shape {points.shape[2]}' + batch_size, num_points, _ = points.shape + num_boxes = boxes.shape[1] + + box_idxs_of_pts = points.new_zeros((batch_size, num_points, num_boxes), + dtype=torch.int).fill_(0) + + # Same reason as line 25-32 + points_device = points.get_device() + assert points_device == boxes.get_device(), \ + 'Points and boxes should be put on the same device' + if torch.cuda.current_device() != points_device: + torch.cuda.set_device(points_device) + + ext_module.points_in_boxes_all_forward(boxes.contiguous(), + points.contiguous(), + box_idxs_of_pts) + + return box_idxs_of_pts diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/points_sampler.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/points_sampler.py new file mode 100644 index 0000000..a802a74 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/points_sampler.py @@ -0,0 +1,177 @@ +from typing import List + +import torch +from torch import nn as nn + +from annotator.uniformer.mmcv.runner import force_fp32 +from .furthest_point_sample import (furthest_point_sample, + furthest_point_sample_with_dist) + + +def calc_square_dist(point_feat_a, point_feat_b, norm=True): + """Calculating square distance between a and b. + + Args: + point_feat_a (Tensor): (B, N, C) Feature vector of each point. + point_feat_b (Tensor): (B, M, C) Feature vector of each point. + norm (Bool, optional): Whether to normalize the distance. + Default: True. + + Returns: + Tensor: (B, N, M) Distance between each pair points. + """ + num_channel = point_feat_a.shape[-1] + # [bs, n, 1] + a_square = torch.sum(point_feat_a.unsqueeze(dim=2).pow(2), dim=-1) + # [bs, 1, m] + b_square = torch.sum(point_feat_b.unsqueeze(dim=1).pow(2), dim=-1) + + corr_matrix = torch.matmul(point_feat_a, point_feat_b.transpose(1, 2)) + + dist = a_square + b_square - 2 * corr_matrix + if norm: + dist = torch.sqrt(dist) / num_channel + return dist + + +def get_sampler_cls(sampler_type): + """Get the type and mode of points sampler. + + Args: + sampler_type (str): The type of points sampler. + The valid value are "D-FPS", "F-FPS", or "FS". + + Returns: + class: Points sampler type. + """ + sampler_mappings = { + 'D-FPS': DFPSSampler, + 'F-FPS': FFPSSampler, + 'FS': FSSampler, + } + try: + return sampler_mappings[sampler_type] + except KeyError: + raise KeyError( + f'Supported `sampler_type` are {sampler_mappings.keys()}, but got \ + {sampler_type}') + + +class PointsSampler(nn.Module): + """Points sampling. + + Args: + num_point (list[int]): Number of sample points. + fps_mod_list (list[str], optional): Type of FPS method, valid mod + ['F-FPS', 'D-FPS', 'FS'], Default: ['D-FPS']. + F-FPS: using feature distances for FPS. + D-FPS: using Euclidean distances of points for FPS. + FS: using F-FPS and D-FPS simultaneously. + fps_sample_range_list (list[int], optional): + Range of points to apply FPS. Default: [-1]. + """ + + def __init__(self, + num_point: List[int], + fps_mod_list: List[str] = ['D-FPS'], + fps_sample_range_list: List[int] = [-1]): + super().__init__() + # FPS would be applied to different fps_mod in the list, + # so the length of the num_point should be equal to + # fps_mod_list and fps_sample_range_list. + assert len(num_point) == len(fps_mod_list) == len( + fps_sample_range_list) + self.num_point = num_point + self.fps_sample_range_list = fps_sample_range_list + self.samplers = nn.ModuleList() + for fps_mod in fps_mod_list: + self.samplers.append(get_sampler_cls(fps_mod)()) + self.fp16_enabled = False + + @force_fp32() + def forward(self, points_xyz, features): + """ + Args: + points_xyz (Tensor): (B, N, 3) xyz coordinates of the features. + features (Tensor): (B, C, N) Descriptors of the features. + + Returns: + Tensor: (B, npoint, sample_num) Indices of sampled points. + """ + indices = [] + last_fps_end_index = 0 + + for fps_sample_range, sampler, npoint in zip( + self.fps_sample_range_list, self.samplers, self.num_point): + assert fps_sample_range < points_xyz.shape[1] + + if fps_sample_range == -1: + sample_points_xyz = points_xyz[:, last_fps_end_index:] + if features is not None: + sample_features = features[:, :, last_fps_end_index:] + else: + sample_features = None + else: + sample_points_xyz = \ + points_xyz[:, last_fps_end_index:fps_sample_range] + if features is not None: + sample_features = features[:, :, last_fps_end_index: + fps_sample_range] + else: + sample_features = None + + fps_idx = sampler(sample_points_xyz.contiguous(), sample_features, + npoint) + + indices.append(fps_idx + last_fps_end_index) + last_fps_end_index += fps_sample_range + indices = torch.cat(indices, dim=1) + + return indices + + +class DFPSSampler(nn.Module): + """Using Euclidean distances of points for FPS.""" + + def __init__(self): + super().__init__() + + def forward(self, points, features, npoint): + """Sampling points with D-FPS.""" + fps_idx = furthest_point_sample(points.contiguous(), npoint) + return fps_idx + + +class FFPSSampler(nn.Module): + """Using feature distances for FPS.""" + + def __init__(self): + super().__init__() + + def forward(self, points, features, npoint): + """Sampling points with F-FPS.""" + assert features is not None, \ + 'feature input to FFPS_Sampler should not be None' + features_for_fps = torch.cat([points, features.transpose(1, 2)], dim=2) + features_dist = calc_square_dist( + features_for_fps, features_for_fps, norm=False) + fps_idx = furthest_point_sample_with_dist(features_dist, npoint) + return fps_idx + + +class FSSampler(nn.Module): + """Using F-FPS and D-FPS simultaneously.""" + + def __init__(self): + super().__init__() + + def forward(self, points, features, npoint): + """Sampling points with FS_Sampling.""" + assert features is not None, \ + 'feature input to FS_Sampler should not be None' + ffps_sampler = FFPSSampler() + dfps_sampler = DFPSSampler() + fps_idx_ffps = ffps_sampler(points, features, npoint) + fps_idx_dfps = dfps_sampler(points, features, npoint) + fps_idx = torch.cat([fps_idx_ffps, fps_idx_dfps], dim=1) + return fps_idx diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/psa_mask.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/psa_mask.py new file mode 100644 index 0000000..cdf14e6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/psa_mask.py @@ -0,0 +1,92 @@ +# Modified from https://github.com/hszhao/semseg/blob/master/lib/psa +from torch import nn +from torch.autograd import Function +from torch.nn.modules.utils import _pair + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', + ['psamask_forward', 'psamask_backward']) + + +class PSAMaskFunction(Function): + + @staticmethod + def symbolic(g, input, psa_type, mask_size): + return g.op( + 'mmcv::MMCVPSAMask', + input, + psa_type_i=psa_type, + mask_size_i=mask_size) + + @staticmethod + def forward(ctx, input, psa_type, mask_size): + ctx.psa_type = psa_type + ctx.mask_size = _pair(mask_size) + ctx.save_for_backward(input) + + h_mask, w_mask = ctx.mask_size + batch_size, channels, h_feature, w_feature = input.size() + assert channels == h_mask * w_mask + output = input.new_zeros( + (batch_size, h_feature * w_feature, h_feature, w_feature)) + + ext_module.psamask_forward( + input, + output, + psa_type=psa_type, + num_=batch_size, + h_feature=h_feature, + w_feature=w_feature, + h_mask=h_mask, + w_mask=w_mask, + half_h_mask=(h_mask - 1) // 2, + half_w_mask=(w_mask - 1) // 2) + return output + + @staticmethod + def backward(ctx, grad_output): + input = ctx.saved_tensors[0] + psa_type = ctx.psa_type + h_mask, w_mask = ctx.mask_size + batch_size, channels, h_feature, w_feature = input.size() + grad_input = grad_output.new_zeros( + (batch_size, channels, h_feature, w_feature)) + ext_module.psamask_backward( + grad_output, + grad_input, + psa_type=psa_type, + num_=batch_size, + h_feature=h_feature, + w_feature=w_feature, + h_mask=h_mask, + w_mask=w_mask, + half_h_mask=(h_mask - 1) // 2, + half_w_mask=(w_mask - 1) // 2) + return grad_input, None, None, None + + +psa_mask = PSAMaskFunction.apply + + +class PSAMask(nn.Module): + + def __init__(self, psa_type, mask_size=None): + super(PSAMask, self).__init__() + assert psa_type in ['collect', 'distribute'] + if psa_type == 'collect': + psa_type_enum = 0 + else: + psa_type_enum = 1 + self.psa_type_enum = psa_type_enum + self.mask_size = mask_size + self.psa_type = psa_type + + def forward(self, input): + return psa_mask(input, self.psa_type_enum, self.mask_size) + + def __repr__(self): + s = self.__class__.__name__ + s += f'(psa_type={self.psa_type}, ' + s += f'mask_size={self.mask_size})' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_align.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_align.py new file mode 100644 index 0000000..0755aef --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_align.py @@ -0,0 +1,223 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.modules.utils import _pair + +from ..utils import deprecated_api_warning, ext_loader + +ext_module = ext_loader.load_ext('_ext', + ['roi_align_forward', 'roi_align_backward']) + + +class RoIAlignFunction(Function): + + @staticmethod + def symbolic(g, input, rois, output_size, spatial_scale, sampling_ratio, + pool_mode, aligned): + from ..onnx import is_custom_op_loaded + has_custom_op = is_custom_op_loaded() + if has_custom_op: + return g.op( + 'mmcv::MMCVRoiAlign', + input, + rois, + output_height_i=output_size[0], + output_width_i=output_size[1], + spatial_scale_f=spatial_scale, + sampling_ratio_i=sampling_ratio, + mode_s=pool_mode, + aligned_i=aligned) + else: + from torch.onnx.symbolic_opset9 import sub, squeeze + from torch.onnx.symbolic_helper import _slice_helper + from torch.onnx import TensorProtoDataType + # batch_indices = rois[:, 0].long() + batch_indices = _slice_helper( + g, rois, axes=[1], starts=[0], ends=[1]) + batch_indices = squeeze(g, batch_indices, 1) + batch_indices = g.op( + 'Cast', batch_indices, to_i=TensorProtoDataType.INT64) + # rois = rois[:, 1:] + rois = _slice_helper(g, rois, axes=[1], starts=[1], ends=[5]) + if aligned: + # rois -= 0.5/spatial_scale + aligned_offset = g.op( + 'Constant', + value_t=torch.tensor([0.5 / spatial_scale], + dtype=torch.float32)) + rois = sub(g, rois, aligned_offset) + # roi align + return g.op( + 'RoiAlign', + input, + rois, + batch_indices, + output_height_i=output_size[0], + output_width_i=output_size[1], + spatial_scale_f=spatial_scale, + sampling_ratio_i=max(0, sampling_ratio), + mode_s=pool_mode) + + @staticmethod + def forward(ctx, + input, + rois, + output_size, + spatial_scale=1.0, + sampling_ratio=0, + pool_mode='avg', + aligned=True): + ctx.output_size = _pair(output_size) + ctx.spatial_scale = spatial_scale + ctx.sampling_ratio = sampling_ratio + assert pool_mode in ('max', 'avg') + ctx.pool_mode = 0 if pool_mode == 'max' else 1 + ctx.aligned = aligned + ctx.input_shape = input.size() + + assert rois.size(1) == 5, 'RoI must be (idx, x1, y1, x2, y2)!' + + output_shape = (rois.size(0), input.size(1), ctx.output_size[0], + ctx.output_size[1]) + output = input.new_zeros(output_shape) + if ctx.pool_mode == 0: + argmax_y = input.new_zeros(output_shape) + argmax_x = input.new_zeros(output_shape) + else: + argmax_y = input.new_zeros(0) + argmax_x = input.new_zeros(0) + + ext_module.roi_align_forward( + input, + rois, + output, + argmax_y, + argmax_x, + aligned_height=ctx.output_size[0], + aligned_width=ctx.output_size[1], + spatial_scale=ctx.spatial_scale, + sampling_ratio=ctx.sampling_ratio, + pool_mode=ctx.pool_mode, + aligned=ctx.aligned) + + ctx.save_for_backward(rois, argmax_y, argmax_x) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + rois, argmax_y, argmax_x = ctx.saved_tensors + grad_input = grad_output.new_zeros(ctx.input_shape) + # complex head architecture may cause grad_output uncontiguous. + grad_output = grad_output.contiguous() + ext_module.roi_align_backward( + grad_output, + rois, + argmax_y, + argmax_x, + grad_input, + aligned_height=ctx.output_size[0], + aligned_width=ctx.output_size[1], + spatial_scale=ctx.spatial_scale, + sampling_ratio=ctx.sampling_ratio, + pool_mode=ctx.pool_mode, + aligned=ctx.aligned) + return grad_input, None, None, None, None, None, None + + +roi_align = RoIAlignFunction.apply + + +class RoIAlign(nn.Module): + """RoI align pooling layer. + + Args: + output_size (tuple): h, w + spatial_scale (float): scale the input boxes by this number + sampling_ratio (int): number of inputs samples to take for each + output sample. 0 to take samples densely for current models. + pool_mode (str, 'avg' or 'max'): pooling mode in each bin. + aligned (bool): if False, use the legacy implementation in + MMDetection. If True, align the results more perfectly. + use_torchvision (bool): whether to use roi_align from torchvision. + + Note: + The implementation of RoIAlign when aligned=True is modified from + https://github.com/facebookresearch/detectron2/ + + The meaning of aligned=True: + + Given a continuous coordinate c, its two neighboring pixel + indices (in our pixel model) are computed by floor(c - 0.5) and + ceil(c - 0.5). For example, c=1.3 has pixel neighbors with discrete + indices [0] and [1] (which are sampled from the underlying signal + at continuous coordinates 0.5 and 1.5). But the original roi_align + (aligned=False) does not subtract the 0.5 when computing + neighboring pixel indices and therefore it uses pixels with a + slightly incorrect alignment (relative to our pixel model) when + performing bilinear interpolation. + + With `aligned=True`, + we first appropriately scale the ROI and then shift it by -0.5 + prior to calling roi_align. This produces the correct neighbors; + + The difference does not make a difference to the model's + performance if ROIAlign is used together with conv layers. + """ + + @deprecated_api_warning( + { + 'out_size': 'output_size', + 'sample_num': 'sampling_ratio' + }, + cls_name='RoIAlign') + def __init__(self, + output_size, + spatial_scale=1.0, + sampling_ratio=0, + pool_mode='avg', + aligned=True, + use_torchvision=False): + super(RoIAlign, self).__init__() + + self.output_size = _pair(output_size) + self.spatial_scale = float(spatial_scale) + self.sampling_ratio = int(sampling_ratio) + self.pool_mode = pool_mode + self.aligned = aligned + self.use_torchvision = use_torchvision + + def forward(self, input, rois): + """ + Args: + input: NCHW images + rois: Bx5 boxes. First column is the index into N.\ + The other 4 columns are xyxy. + """ + if self.use_torchvision: + from torchvision.ops import roi_align as tv_roi_align + if 'aligned' in tv_roi_align.__code__.co_varnames: + return tv_roi_align(input, rois, self.output_size, + self.spatial_scale, self.sampling_ratio, + self.aligned) + else: + if self.aligned: + rois -= rois.new_tensor([0.] + + [0.5 / self.spatial_scale] * 4) + return tv_roi_align(input, rois, self.output_size, + self.spatial_scale, self.sampling_ratio) + else: + return roi_align(input, rois, self.output_size, self.spatial_scale, + self.sampling_ratio, self.pool_mode, self.aligned) + + def __repr__(self): + s = self.__class__.__name__ + s += f'(output_size={self.output_size}, ' + s += f'spatial_scale={self.spatial_scale}, ' + s += f'sampling_ratio={self.sampling_ratio}, ' + s += f'pool_mode={self.pool_mode}, ' + s += f'aligned={self.aligned}, ' + s += f'use_torchvision={self.use_torchvision})' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_align_rotated.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_align_rotated.py new file mode 100644 index 0000000..0ce4961 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_align_rotated.py @@ -0,0 +1,177 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['roi_align_rotated_forward', 'roi_align_rotated_backward']) + + +class RoIAlignRotatedFunction(Function): + + @staticmethod + def symbolic(g, features, rois, out_size, spatial_scale, sample_num, + aligned, clockwise): + if isinstance(out_size, int): + out_h = out_size + out_w = out_size + elif isinstance(out_size, tuple): + assert len(out_size) == 2 + assert isinstance(out_size[0], int) + assert isinstance(out_size[1], int) + out_h, out_w = out_size + else: + raise TypeError( + '"out_size" must be an integer or tuple of integers') + return g.op( + 'mmcv::MMCVRoIAlignRotated', + features, + rois, + output_height_i=out_h, + output_width_i=out_h, + spatial_scale_f=spatial_scale, + sampling_ratio_i=sample_num, + aligned_i=aligned, + clockwise_i=clockwise) + + @staticmethod + def forward(ctx, + features, + rois, + out_size, + spatial_scale, + sample_num=0, + aligned=True, + clockwise=False): + if isinstance(out_size, int): + out_h = out_size + out_w = out_size + elif isinstance(out_size, tuple): + assert len(out_size) == 2 + assert isinstance(out_size[0], int) + assert isinstance(out_size[1], int) + out_h, out_w = out_size + else: + raise TypeError( + '"out_size" must be an integer or tuple of integers') + ctx.spatial_scale = spatial_scale + ctx.sample_num = sample_num + ctx.aligned = aligned + ctx.clockwise = clockwise + ctx.save_for_backward(rois) + ctx.feature_size = features.size() + + batch_size, num_channels, data_height, data_width = features.size() + num_rois = rois.size(0) + + output = features.new_zeros(num_rois, num_channels, out_h, out_w) + ext_module.roi_align_rotated_forward( + features, + rois, + output, + pooled_height=out_h, + pooled_width=out_w, + spatial_scale=spatial_scale, + sample_num=sample_num, + aligned=aligned, + clockwise=clockwise) + return output + + @staticmethod + def backward(ctx, grad_output): + feature_size = ctx.feature_size + spatial_scale = ctx.spatial_scale + aligned = ctx.aligned + clockwise = ctx.clockwise + sample_num = ctx.sample_num + rois = ctx.saved_tensors[0] + assert feature_size is not None + batch_size, num_channels, data_height, data_width = feature_size + + out_w = grad_output.size(3) + out_h = grad_output.size(2) + + grad_input = grad_rois = None + + if ctx.needs_input_grad[0]: + grad_input = rois.new_zeros(batch_size, num_channels, data_height, + data_width) + ext_module.roi_align_rotated_backward( + grad_output.contiguous(), + rois, + grad_input, + pooled_height=out_h, + pooled_width=out_w, + spatial_scale=spatial_scale, + sample_num=sample_num, + aligned=aligned, + clockwise=clockwise) + return grad_input, grad_rois, None, None, None, None, None + + +roi_align_rotated = RoIAlignRotatedFunction.apply + + +class RoIAlignRotated(nn.Module): + """RoI align pooling layer for rotated proposals. + + It accepts a feature map of shape (N, C, H, W) and rois with shape + (n, 6) with each roi decoded as (batch_index, center_x, center_y, + w, h, angle). The angle is in radian. + + Args: + out_size (tuple): h, w + spatial_scale (float): scale the input boxes by this number + sample_num (int): number of inputs samples to take for each + output sample. 0 to take samples densely for current models. + aligned (bool): if False, use the legacy implementation in + MMDetection. If True, align the results more perfectly. + Default: True. + clockwise (bool): If True, the angle in each proposal follows a + clockwise fashion in image space, otherwise, the angle is + counterclockwise. Default: False. + + Note: + The implementation of RoIAlign when aligned=True is modified from + https://github.com/facebookresearch/detectron2/ + + The meaning of aligned=True: + + Given a continuous coordinate c, its two neighboring pixel + indices (in our pixel model) are computed by floor(c - 0.5) and + ceil(c - 0.5). For example, c=1.3 has pixel neighbors with discrete + indices [0] and [1] (which are sampled from the underlying signal + at continuous coordinates 0.5 and 1.5). But the original roi_align + (aligned=False) does not subtract the 0.5 when computing + neighboring pixel indices and therefore it uses pixels with a + slightly incorrect alignment (relative to our pixel model) when + performing bilinear interpolation. + + With `aligned=True`, + we first appropriately scale the ROI and then shift it by -0.5 + prior to calling roi_align. This produces the correct neighbors; + + The difference does not make a difference to the model's + performance if ROIAlign is used together with conv layers. + """ + + def __init__(self, + out_size, + spatial_scale, + sample_num=0, + aligned=True, + clockwise=False): + super(RoIAlignRotated, self).__init__() + + self.out_size = out_size + self.spatial_scale = float(spatial_scale) + self.sample_num = int(sample_num) + self.aligned = aligned + self.clockwise = clockwise + + def forward(self, features, rois): + return RoIAlignRotatedFunction.apply(features, rois, self.out_size, + self.spatial_scale, + self.sample_num, self.aligned, + self.clockwise) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_pool.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_pool.py new file mode 100644 index 0000000..d339d8f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roi_pool.py @@ -0,0 +1,86 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.modules.utils import _pair + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', + ['roi_pool_forward', 'roi_pool_backward']) + + +class RoIPoolFunction(Function): + + @staticmethod + def symbolic(g, input, rois, output_size, spatial_scale): + return g.op( + 'MaxRoiPool', + input, + rois, + pooled_shape_i=output_size, + spatial_scale_f=spatial_scale) + + @staticmethod + def forward(ctx, input, rois, output_size, spatial_scale=1.0): + ctx.output_size = _pair(output_size) + ctx.spatial_scale = spatial_scale + ctx.input_shape = input.size() + + assert rois.size(1) == 5, 'RoI must be (idx, x1, y1, x2, y2)!' + + output_shape = (rois.size(0), input.size(1), ctx.output_size[0], + ctx.output_size[1]) + output = input.new_zeros(output_shape) + argmax = input.new_zeros(output_shape, dtype=torch.int) + + ext_module.roi_pool_forward( + input, + rois, + output, + argmax, + pooled_height=ctx.output_size[0], + pooled_width=ctx.output_size[1], + spatial_scale=ctx.spatial_scale) + + ctx.save_for_backward(rois, argmax) + return output + + @staticmethod + @once_differentiable + def backward(ctx, grad_output): + rois, argmax = ctx.saved_tensors + grad_input = grad_output.new_zeros(ctx.input_shape) + + ext_module.roi_pool_backward( + grad_output, + rois, + argmax, + grad_input, + pooled_height=ctx.output_size[0], + pooled_width=ctx.output_size[1], + spatial_scale=ctx.spatial_scale) + + return grad_input, None, None, None + + +roi_pool = RoIPoolFunction.apply + + +class RoIPool(nn.Module): + + def __init__(self, output_size, spatial_scale=1.0): + super(RoIPool, self).__init__() + + self.output_size = _pair(output_size) + self.spatial_scale = float(spatial_scale) + + def forward(self, input, rois): + return roi_pool(input, rois, self.output_size, self.spatial_scale) + + def __repr__(self): + s = self.__class__.__name__ + s += f'(output_size={self.output_size}, ' + s += f'spatial_scale={self.spatial_scale})' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roiaware_pool3d.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roiaware_pool3d.py new file mode 100644 index 0000000..291b0e5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roiaware_pool3d.py @@ -0,0 +1,114 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import nn as nn +from torch.autograd import Function + +import annotator.uniformer.mmcv as mmcv +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['roiaware_pool3d_forward', 'roiaware_pool3d_backward']) + + +class RoIAwarePool3d(nn.Module): + """Encode the geometry-specific features of each 3D proposal. + + Please refer to `PartA2 `_ for more + details. + + Args: + out_size (int or tuple): The size of output features. n or + [n1, n2, n3]. + max_pts_per_voxel (int, optional): The maximum number of points per + voxel. Default: 128. + mode (str, optional): Pooling method of RoIAware, 'max' or 'avg'. + Default: 'max'. + """ + + def __init__(self, out_size, max_pts_per_voxel=128, mode='max'): + super().__init__() + + self.out_size = out_size + self.max_pts_per_voxel = max_pts_per_voxel + assert mode in ['max', 'avg'] + pool_mapping = {'max': 0, 'avg': 1} + self.mode = pool_mapping[mode] + + def forward(self, rois, pts, pts_feature): + """ + Args: + rois (torch.Tensor): [N, 7], in LiDAR coordinate, + (x, y, z) is the bottom center of rois. + pts (torch.Tensor): [npoints, 3], coordinates of input points. + pts_feature (torch.Tensor): [npoints, C], features of input points. + + Returns: + pooled_features (torch.Tensor): [N, out_x, out_y, out_z, C] + """ + + return RoIAwarePool3dFunction.apply(rois, pts, pts_feature, + self.out_size, + self.max_pts_per_voxel, self.mode) + + +class RoIAwarePool3dFunction(Function): + + @staticmethod + def forward(ctx, rois, pts, pts_feature, out_size, max_pts_per_voxel, + mode): + """ + Args: + rois (torch.Tensor): [N, 7], in LiDAR coordinate, + (x, y, z) is the bottom center of rois. + pts (torch.Tensor): [npoints, 3], coordinates of input points. + pts_feature (torch.Tensor): [npoints, C], features of input points. + out_size (int or tuple): The size of output features. n or + [n1, n2, n3]. + max_pts_per_voxel (int): The maximum number of points per voxel. + Default: 128. + mode (int): Pooling method of RoIAware, 0 (max pool) or 1 (average + pool). + + Returns: + pooled_features (torch.Tensor): [N, out_x, out_y, out_z, C], output + pooled features. + """ + + if isinstance(out_size, int): + out_x = out_y = out_z = out_size + else: + assert len(out_size) == 3 + assert mmcv.is_tuple_of(out_size, int) + out_x, out_y, out_z = out_size + + num_rois = rois.shape[0] + num_channels = pts_feature.shape[-1] + num_pts = pts.shape[0] + + pooled_features = pts_feature.new_zeros( + (num_rois, out_x, out_y, out_z, num_channels)) + argmax = pts_feature.new_zeros( + (num_rois, out_x, out_y, out_z, num_channels), dtype=torch.int) + pts_idx_of_voxels = pts_feature.new_zeros( + (num_rois, out_x, out_y, out_z, max_pts_per_voxel), + dtype=torch.int) + + ext_module.roiaware_pool3d_forward(rois, pts, pts_feature, argmax, + pts_idx_of_voxels, pooled_features, + mode) + + ctx.roiaware_pool3d_for_backward = (pts_idx_of_voxels, argmax, mode, + num_pts, num_channels) + return pooled_features + + @staticmethod + def backward(ctx, grad_out): + ret = ctx.roiaware_pool3d_for_backward + pts_idx_of_voxels, argmax, mode, num_pts, num_channels = ret + + grad_in = grad_out.new_zeros((num_pts, num_channels)) + ext_module.roiaware_pool3d_backward(pts_idx_of_voxels, argmax, + grad_out.contiguous(), grad_in, + mode) + + return None, None, grad_in, None, None, None diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roipoint_pool3d.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roipoint_pool3d.py new file mode 100644 index 0000000..0a21412 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/roipoint_pool3d.py @@ -0,0 +1,77 @@ +from torch import nn as nn +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['roipoint_pool3d_forward']) + + +class RoIPointPool3d(nn.Module): + """Encode the geometry-specific features of each 3D proposal. + + Please refer to `Paper of PartA2 `_ + for more details. + + Args: + num_sampled_points (int, optional): Number of samples in each roi. + Default: 512. + """ + + def __init__(self, num_sampled_points=512): + super().__init__() + self.num_sampled_points = num_sampled_points + + def forward(self, points, point_features, boxes3d): + """ + Args: + points (torch.Tensor): Input points whose shape is (B, N, C). + point_features (torch.Tensor): Features of input points whose shape + is (B, N, C). + boxes3d (B, M, 7), Input bounding boxes whose shape is (B, M, 7). + + Returns: + pooled_features (torch.Tensor): The output pooled features whose + shape is (B, M, 512, 3 + C). + pooled_empty_flag (torch.Tensor): Empty flag whose shape is (B, M). + """ + return RoIPointPool3dFunction.apply(points, point_features, boxes3d, + self.num_sampled_points) + + +class RoIPointPool3dFunction(Function): + + @staticmethod + def forward(ctx, points, point_features, boxes3d, num_sampled_points=512): + """ + Args: + points (torch.Tensor): Input points whose shape is (B, N, C). + point_features (torch.Tensor): Features of input points whose shape + is (B, N, C). + boxes3d (B, M, 7), Input bounding boxes whose shape is (B, M, 7). + num_sampled_points (int, optional): The num of sampled points. + Default: 512. + + Returns: + pooled_features (torch.Tensor): The output pooled features whose + shape is (B, M, 512, 3 + C). + pooled_empty_flag (torch.Tensor): Empty flag whose shape is (B, M). + """ + assert len(points.shape) == 3 and points.shape[2] == 3 + batch_size, boxes_num, feature_len = points.shape[0], boxes3d.shape[ + 1], point_features.shape[2] + pooled_boxes3d = boxes3d.view(batch_size, -1, 7) + pooled_features = point_features.new_zeros( + (batch_size, boxes_num, num_sampled_points, 3 + feature_len)) + pooled_empty_flag = point_features.new_zeros( + (batch_size, boxes_num)).int() + + ext_module.roipoint_pool3d_forward(points.contiguous(), + pooled_boxes3d.contiguous(), + point_features.contiguous(), + pooled_features, pooled_empty_flag) + + return pooled_features, pooled_empty_flag + + @staticmethod + def backward(ctx, grad_out): + raise NotImplementedError diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/saconv.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/saconv.py new file mode 100644 index 0000000..b4ee397 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/saconv.py @@ -0,0 +1,145 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from annotator.uniformer.mmcv.cnn import CONV_LAYERS, ConvAWS2d, constant_init +from annotator.uniformer.mmcv.ops.deform_conv import deform_conv2d +from annotator.uniformer.mmcv.utils import TORCH_VERSION, digit_version + + +@CONV_LAYERS.register_module(name='SAC') +class SAConv2d(ConvAWS2d): + """SAC (Switchable Atrous Convolution) + + This is an implementation of SAC in DetectoRS + (https://arxiv.org/pdf/2006.02334.pdf). + + Args: + in_channels (int): Number of channels in the input image + out_channels (int): Number of channels produced by the convolution + kernel_size (int or tuple): Size of the convolving kernel + stride (int or tuple, optional): Stride of the convolution. Default: 1 + padding (int or tuple, optional): Zero-padding added to both sides of + the input. Default: 0 + padding_mode (string, optional): ``'zeros'``, ``'reflect'``, + ``'replicate'`` or ``'circular'``. Default: ``'zeros'`` + dilation (int or tuple, optional): Spacing between kernel elements. + Default: 1 + groups (int, optional): Number of blocked connections from input + channels to output channels. Default: 1 + bias (bool, optional): If ``True``, adds a learnable bias to the + output. Default: ``True`` + use_deform: If ``True``, replace convolution with deformable + convolution. Default: ``False``. + """ + + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True, + use_deform=False): + super().__init__( + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias=bias) + self.use_deform = use_deform + self.switch = nn.Conv2d( + self.in_channels, 1, kernel_size=1, stride=stride, bias=True) + self.weight_diff = nn.Parameter(torch.Tensor(self.weight.size())) + self.pre_context = nn.Conv2d( + self.in_channels, self.in_channels, kernel_size=1, bias=True) + self.post_context = nn.Conv2d( + self.out_channels, self.out_channels, kernel_size=1, bias=True) + if self.use_deform: + self.offset_s = nn.Conv2d( + self.in_channels, + 18, + kernel_size=3, + padding=1, + stride=stride, + bias=True) + self.offset_l = nn.Conv2d( + self.in_channels, + 18, + kernel_size=3, + padding=1, + stride=stride, + bias=True) + self.init_weights() + + def init_weights(self): + constant_init(self.switch, 0, bias=1) + self.weight_diff.data.zero_() + constant_init(self.pre_context, 0) + constant_init(self.post_context, 0) + if self.use_deform: + constant_init(self.offset_s, 0) + constant_init(self.offset_l, 0) + + def forward(self, x): + # pre-context + avg_x = F.adaptive_avg_pool2d(x, output_size=1) + avg_x = self.pre_context(avg_x) + avg_x = avg_x.expand_as(x) + x = x + avg_x + # switch + avg_x = F.pad(x, pad=(2, 2, 2, 2), mode='reflect') + avg_x = F.avg_pool2d(avg_x, kernel_size=5, stride=1, padding=0) + switch = self.switch(avg_x) + # sac + weight = self._get_weight(self.weight) + zero_bias = torch.zeros( + self.out_channels, device=weight.device, dtype=weight.dtype) + + if self.use_deform: + offset = self.offset_s(avg_x) + out_s = deform_conv2d(x, offset, weight, self.stride, self.padding, + self.dilation, self.groups, 1) + else: + if (TORCH_VERSION == 'parrots' + or digit_version(TORCH_VERSION) < digit_version('1.5.0')): + out_s = super().conv2d_forward(x, weight) + elif digit_version(TORCH_VERSION) >= digit_version('1.8.0'): + # bias is a required argument of _conv_forward in torch 1.8.0 + out_s = super()._conv_forward(x, weight, zero_bias) + else: + out_s = super()._conv_forward(x, weight) + ori_p = self.padding + ori_d = self.dilation + self.padding = tuple(3 * p for p in self.padding) + self.dilation = tuple(3 * d for d in self.dilation) + weight = weight + self.weight_diff + if self.use_deform: + offset = self.offset_l(avg_x) + out_l = deform_conv2d(x, offset, weight, self.stride, self.padding, + self.dilation, self.groups, 1) + else: + if (TORCH_VERSION == 'parrots' + or digit_version(TORCH_VERSION) < digit_version('1.5.0')): + out_l = super().conv2d_forward(x, weight) + elif digit_version(TORCH_VERSION) >= digit_version('1.8.0'): + # bias is a required argument of _conv_forward in torch 1.8.0 + out_l = super()._conv_forward(x, weight, zero_bias) + else: + out_l = super()._conv_forward(x, weight) + + out = switch * out_s + (1 - switch) * out_l + self.padding = ori_p + self.dilation = ori_d + # post-context + avg_x = F.adaptive_avg_pool2d(out, output_size=1) + avg_x = self.post_context(avg_x) + avg_x = avg_x.expand_as(out) + out = out + avg_x + return out diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/scatter_points.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/scatter_points.py new file mode 100644 index 0000000..2b8aa41 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/scatter_points.py @@ -0,0 +1,135 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import nn +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', + ['dynamic_point_to_voxel_forward', 'dynamic_point_to_voxel_backward']) + + +class _DynamicScatter(Function): + + @staticmethod + def forward(ctx, feats, coors, reduce_type='max'): + """convert kitti points(N, >=3) to voxels. + + Args: + feats (torch.Tensor): [N, C]. Points features to be reduced + into voxels. + coors (torch.Tensor): [N, ndim]. Corresponding voxel coordinates + (specifically multi-dim voxel index) of each points. + reduce_type (str, optional): Reduce op. support 'max', 'sum' and + 'mean'. Default: 'max'. + + Returns: + voxel_feats (torch.Tensor): [M, C]. Reduced features, input + features that shares the same voxel coordinates are reduced to + one row. + voxel_coors (torch.Tensor): [M, ndim]. Voxel coordinates. + """ + results = ext_module.dynamic_point_to_voxel_forward( + feats, coors, reduce_type) + (voxel_feats, voxel_coors, point2voxel_map, + voxel_points_count) = results + ctx.reduce_type = reduce_type + ctx.save_for_backward(feats, voxel_feats, point2voxel_map, + voxel_points_count) + ctx.mark_non_differentiable(voxel_coors) + return voxel_feats, voxel_coors + + @staticmethod + def backward(ctx, grad_voxel_feats, grad_voxel_coors=None): + (feats, voxel_feats, point2voxel_map, + voxel_points_count) = ctx.saved_tensors + grad_feats = torch.zeros_like(feats) + # TODO: whether to use index put or use cuda_backward + # To use index put, need point to voxel index + ext_module.dynamic_point_to_voxel_backward( + grad_feats, grad_voxel_feats.contiguous(), feats, voxel_feats, + point2voxel_map, voxel_points_count, ctx.reduce_type) + return grad_feats, None, None + + +dynamic_scatter = _DynamicScatter.apply + + +class DynamicScatter(nn.Module): + """Scatters points into voxels, used in the voxel encoder with dynamic + voxelization. + + Note: + The CPU and GPU implementation get the same output, but have numerical + difference after summation and division (e.g., 5e-7). + + Args: + voxel_size (list): list [x, y, z] size of three dimension. + point_cloud_range (list): The coordinate range of points, [x_min, + y_min, z_min, x_max, y_max, z_max]. + average_points (bool): whether to use avg pooling to scatter points + into voxel. + """ + + def __init__(self, voxel_size, point_cloud_range, average_points: bool): + super().__init__() + + self.voxel_size = voxel_size + self.point_cloud_range = point_cloud_range + self.average_points = average_points + + def forward_single(self, points, coors): + """Scatters points into voxels. + + Args: + points (torch.Tensor): Points to be reduced into voxels. + coors (torch.Tensor): Corresponding voxel coordinates (specifically + multi-dim voxel index) of each points. + + Returns: + voxel_feats (torch.Tensor): Reduced features, input features that + shares the same voxel coordinates are reduced to one row. + voxel_coors (torch.Tensor): Voxel coordinates. + """ + reduce = 'mean' if self.average_points else 'max' + return dynamic_scatter(points.contiguous(), coors.contiguous(), reduce) + + def forward(self, points, coors): + """Scatters points/features into voxels. + + Args: + points (torch.Tensor): Points to be reduced into voxels. + coors (torch.Tensor): Corresponding voxel coordinates (specifically + multi-dim voxel index) of each points. + + Returns: + voxel_feats (torch.Tensor): Reduced features, input features that + shares the same voxel coordinates are reduced to one row. + voxel_coors (torch.Tensor): Voxel coordinates. + """ + if coors.size(-1) == 3: + return self.forward_single(points, coors) + else: + batch_size = coors[-1, 0] + 1 + voxels, voxel_coors = [], [] + for i in range(batch_size): + inds = torch.where(coors[:, 0] == i) + voxel, voxel_coor = self.forward_single( + points[inds], coors[inds][:, 1:]) + coor_pad = nn.functional.pad( + voxel_coor, (1, 0), mode='constant', value=i) + voxel_coors.append(coor_pad) + voxels.append(voxel) + features = torch.cat(voxels, dim=0) + feature_coors = torch.cat(voxel_coors, dim=0) + + return features, feature_coors + + def __repr__(self): + s = self.__class__.__name__ + '(' + s += 'voxel_size=' + str(self.voxel_size) + s += ', point_cloud_range=' + str(self.point_cloud_range) + s += ', average_points=' + str(self.average_points) + s += ')' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/sync_bn.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/sync_bn.py new file mode 100644 index 0000000..c9b016f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/sync_bn.py @@ -0,0 +1,279 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.distributed as dist +import torch.nn.functional as F +from torch.autograd import Function +from torch.autograd.function import once_differentiable +from torch.nn.modules.module import Module +from torch.nn.parameter import Parameter + +from annotator.uniformer.mmcv.cnn import NORM_LAYERS +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', [ + 'sync_bn_forward_mean', 'sync_bn_forward_var', 'sync_bn_forward_output', + 'sync_bn_backward_param', 'sync_bn_backward_data' +]) + + +class SyncBatchNormFunction(Function): + + @staticmethod + def symbolic(g, input, running_mean, running_var, weight, bias, momentum, + eps, group, group_size, stats_mode): + return g.op( + 'mmcv::MMCVSyncBatchNorm', + input, + running_mean, + running_var, + weight, + bias, + momentum_f=momentum, + eps_f=eps, + group_i=group, + group_size_i=group_size, + stats_mode=stats_mode) + + @staticmethod + def forward(self, input, running_mean, running_var, weight, bias, momentum, + eps, group, group_size, stats_mode): + self.momentum = momentum + self.eps = eps + self.group = group + self.group_size = group_size + self.stats_mode = stats_mode + + assert isinstance( + input, (torch.HalfTensor, torch.FloatTensor, + torch.cuda.HalfTensor, torch.cuda.FloatTensor)), \ + f'only support Half or Float Tensor, but {input.type()}' + output = torch.zeros_like(input) + input3d = input.flatten(start_dim=2) + output3d = output.view_as(input3d) + num_channels = input3d.size(1) + + # ensure mean/var/norm/std are initialized as zeros + # ``torch.empty()`` does not guarantee that + mean = torch.zeros( + num_channels, dtype=torch.float, device=input3d.device) + var = torch.zeros( + num_channels, dtype=torch.float, device=input3d.device) + norm = torch.zeros_like( + input3d, dtype=torch.float, device=input3d.device) + std = torch.zeros( + num_channels, dtype=torch.float, device=input3d.device) + + batch_size = input3d.size(0) + if batch_size > 0: + ext_module.sync_bn_forward_mean(input3d, mean) + batch_flag = torch.ones([1], device=mean.device, dtype=mean.dtype) + else: + # skip updating mean and leave it as zeros when the input is empty + batch_flag = torch.zeros([1], device=mean.device, dtype=mean.dtype) + + # synchronize mean and the batch flag + vec = torch.cat([mean, batch_flag]) + if self.stats_mode == 'N': + vec *= batch_size + if self.group_size > 1: + dist.all_reduce(vec, group=self.group) + total_batch = vec[-1].detach() + mean = vec[:num_channels] + + if self.stats_mode == 'default': + mean = mean / self.group_size + elif self.stats_mode == 'N': + mean = mean / total_batch.clamp(min=1) + else: + raise NotImplementedError + + # leave var as zeros when the input is empty + if batch_size > 0: + ext_module.sync_bn_forward_var(input3d, mean, var) + + if self.stats_mode == 'N': + var *= batch_size + if self.group_size > 1: + dist.all_reduce(var, group=self.group) + + if self.stats_mode == 'default': + var /= self.group_size + elif self.stats_mode == 'N': + var /= total_batch.clamp(min=1) + else: + raise NotImplementedError + + # if the total batch size over all the ranks is zero, + # we should not update the statistics in the current batch + update_flag = total_batch.clamp(max=1) + momentum = update_flag * self.momentum + ext_module.sync_bn_forward_output( + input3d, + mean, + var, + weight, + bias, + running_mean, + running_var, + norm, + std, + output3d, + eps=self.eps, + momentum=momentum, + group_size=self.group_size) + self.save_for_backward(norm, std, weight) + return output + + @staticmethod + @once_differentiable + def backward(self, grad_output): + norm, std, weight = self.saved_tensors + grad_weight = torch.zeros_like(weight) + grad_bias = torch.zeros_like(weight) + grad_input = torch.zeros_like(grad_output) + grad_output3d = grad_output.flatten(start_dim=2) + grad_input3d = grad_input.view_as(grad_output3d) + + batch_size = grad_input3d.size(0) + if batch_size > 0: + ext_module.sync_bn_backward_param(grad_output3d, norm, grad_weight, + grad_bias) + + # all reduce + if self.group_size > 1: + dist.all_reduce(grad_weight, group=self.group) + dist.all_reduce(grad_bias, group=self.group) + grad_weight /= self.group_size + grad_bias /= self.group_size + + if batch_size > 0: + ext_module.sync_bn_backward_data(grad_output3d, weight, + grad_weight, grad_bias, norm, std, + grad_input3d) + + return grad_input, None, None, grad_weight, grad_bias, \ + None, None, None, None, None + + +@NORM_LAYERS.register_module(name='MMSyncBN') +class SyncBatchNorm(Module): + """Synchronized Batch Normalization. + + Args: + num_features (int): number of features/chennels in input tensor + eps (float, optional): a value added to the denominator for numerical + stability. Defaults to 1e-5. + momentum (float, optional): the value used for the running_mean and + running_var computation. Defaults to 0.1. + affine (bool, optional): whether to use learnable affine parameters. + Defaults to True. + track_running_stats (bool, optional): whether to track the running + mean and variance during training. When set to False, this + module does not track such statistics, and initializes statistics + buffers ``running_mean`` and ``running_var`` as ``None``. When + these buffers are ``None``, this module always uses batch + statistics in both training and eval modes. Defaults to True. + group (int, optional): synchronization of stats happen within + each process group individually. By default it is synchronization + across the whole world. Defaults to None. + stats_mode (str, optional): The statistical mode. Available options + includes ``'default'`` and ``'N'``. Defaults to 'default'. + When ``stats_mode=='default'``, it computes the overall statistics + using those from each worker with equal weight, i.e., the + statistics are synchronized and simply divied by ``group``. This + mode will produce inaccurate statistics when empty tensors occur. + When ``stats_mode=='N'``, it compute the overall statistics using + the total number of batches in each worker ignoring the number of + group, i.e., the statistics are synchronized and then divied by + the total batch ``N``. This mode is beneficial when empty tensors + occur during training, as it average the total mean by the real + number of batch. + """ + + def __init__(self, + num_features, + eps=1e-5, + momentum=0.1, + affine=True, + track_running_stats=True, + group=None, + stats_mode='default'): + super(SyncBatchNorm, self).__init__() + self.num_features = num_features + self.eps = eps + self.momentum = momentum + self.affine = affine + self.track_running_stats = track_running_stats + group = dist.group.WORLD if group is None else group + self.group = group + self.group_size = dist.get_world_size(group) + assert stats_mode in ['default', 'N'], \ + f'"stats_mode" only accepts "default" and "N", got "{stats_mode}"' + self.stats_mode = stats_mode + if self.affine: + self.weight = Parameter(torch.Tensor(num_features)) + self.bias = Parameter(torch.Tensor(num_features)) + else: + self.register_parameter('weight', None) + self.register_parameter('bias', None) + if self.track_running_stats: + self.register_buffer('running_mean', torch.zeros(num_features)) + self.register_buffer('running_var', torch.ones(num_features)) + self.register_buffer('num_batches_tracked', + torch.tensor(0, dtype=torch.long)) + else: + self.register_buffer('running_mean', None) + self.register_buffer('running_var', None) + self.register_buffer('num_batches_tracked', None) + self.reset_parameters() + + def reset_running_stats(self): + if self.track_running_stats: + self.running_mean.zero_() + self.running_var.fill_(1) + self.num_batches_tracked.zero_() + + def reset_parameters(self): + self.reset_running_stats() + if self.affine: + self.weight.data.uniform_() # pytorch use ones_() + self.bias.data.zero_() + + def forward(self, input): + if input.dim() < 2: + raise ValueError( + f'expected at least 2D input, got {input.dim()}D input') + if self.momentum is None: + exponential_average_factor = 0.0 + else: + exponential_average_factor = self.momentum + + if self.training and self.track_running_stats: + if self.num_batches_tracked is not None: + self.num_batches_tracked += 1 + if self.momentum is None: # use cumulative moving average + exponential_average_factor = 1.0 / float( + self.num_batches_tracked) + else: # use exponential moving average + exponential_average_factor = self.momentum + + if self.training or not self.track_running_stats: + return SyncBatchNormFunction.apply( + input, self.running_mean, self.running_var, self.weight, + self.bias, exponential_average_factor, self.eps, self.group, + self.group_size, self.stats_mode) + else: + return F.batch_norm(input, self.running_mean, self.running_var, + self.weight, self.bias, False, + exponential_average_factor, self.eps) + + def __repr__(self): + s = self.__class__.__name__ + s += f'({self.num_features}, ' + s += f'eps={self.eps}, ' + s += f'momentum={self.momentum}, ' + s += f'affine={self.affine}, ' + s += f'track_running_stats={self.track_running_stats}, ' + s += f'group_size={self.group_size},' + s += f'stats_mode={self.stats_mode})' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/three_interpolate.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/three_interpolate.py new file mode 100644 index 0000000..203f47f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/three_interpolate.py @@ -0,0 +1,68 @@ +from typing import Tuple + +import torch +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['three_interpolate_forward', 'three_interpolate_backward']) + + +class ThreeInterpolate(Function): + """Performs weighted linear interpolation on 3 features. + + Please refer to `Paper of PointNet++ `_ + for more details. + """ + + @staticmethod + def forward(ctx, features: torch.Tensor, indices: torch.Tensor, + weight: torch.Tensor) -> torch.Tensor: + """ + Args: + features (Tensor): (B, C, M) Features descriptors to be + interpolated + indices (Tensor): (B, n, 3) index three nearest neighbors + of the target features in features + weight (Tensor): (B, n, 3) weights of interpolation + + Returns: + Tensor: (B, C, N) tensor of the interpolated features + """ + assert features.is_contiguous() + assert indices.is_contiguous() + assert weight.is_contiguous() + + B, c, m = features.size() + n = indices.size(1) + ctx.three_interpolate_for_backward = (indices, weight, m) + output = torch.cuda.FloatTensor(B, c, n) + + ext_module.three_interpolate_forward( + features, indices, weight, output, b=B, c=c, m=m, n=n) + return output + + @staticmethod + def backward( + ctx, grad_out: torch.Tensor + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Args: + grad_out (Tensor): (B, C, N) tensor with gradients of outputs + + Returns: + Tensor: (B, C, M) tensor with gradients of features + """ + idx, weight, m = ctx.three_interpolate_for_backward + B, c, n = grad_out.size() + + grad_features = torch.cuda.FloatTensor(B, c, m).zero_() + grad_out_data = grad_out.data.contiguous() + + ext_module.three_interpolate_backward( + grad_out_data, idx, weight, grad_features.data, b=B, c=c, n=n, m=m) + return grad_features, None, None + + +three_interpolate = ThreeInterpolate.apply diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/three_nn.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/three_nn.py new file mode 100644 index 0000000..2b01047 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/three_nn.py @@ -0,0 +1,51 @@ +from typing import Tuple + +import torch +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', ['three_nn_forward']) + + +class ThreeNN(Function): + """Find the top-3 nearest neighbors of the target set from the source set. + + Please refer to `Paper of PointNet++ `_ + for more details. + """ + + @staticmethod + def forward(ctx, target: torch.Tensor, + source: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Args: + target (Tensor): shape (B, N, 3), points set that needs to + find the nearest neighbors. + source (Tensor): shape (B, M, 3), points set that is used + to find the nearest neighbors of points in target set. + + Returns: + Tensor: shape (B, N, 3), L2 distance of each point in target + set to their corresponding nearest neighbors. + """ + target = target.contiguous() + source = source.contiguous() + + B, N, _ = target.size() + m = source.size(1) + dist2 = torch.cuda.FloatTensor(B, N, 3) + idx = torch.cuda.IntTensor(B, N, 3) + + ext_module.three_nn_forward(target, source, dist2, idx, b=B, n=N, m=m) + if torch.__version__ != 'parrots': + ctx.mark_non_differentiable(idx) + + return torch.sqrt(dist2), idx + + @staticmethod + def backward(ctx, a=None, b=None): + return None, None + + +three_nn = ThreeNN.apply diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/tin_shift.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/tin_shift.py new file mode 100644 index 0000000..472c9fc --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/tin_shift.py @@ -0,0 +1,68 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Code reference from "Temporal Interlacing Network" +# https://github.com/deepcs233/TIN/blob/master/cuda_shift/rtc_wrap.py +# Hao Shao, Shengju Qian, Yu Liu +# shaoh19@mails.tsinghua.edu.cn, sjqian@cse.cuhk.edu.hk, yuliu@ee.cuhk.edu.hk + +import torch +import torch.nn as nn +from torch.autograd import Function + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext('_ext', + ['tin_shift_forward', 'tin_shift_backward']) + + +class TINShiftFunction(Function): + + @staticmethod + def forward(ctx, input, shift): + C = input.size(2) + num_segments = shift.size(1) + if C // num_segments <= 0 or C % num_segments != 0: + raise ValueError('C should be a multiple of num_segments, ' + f'but got C={C} and num_segments={num_segments}.') + + ctx.save_for_backward(shift) + + out = torch.zeros_like(input) + ext_module.tin_shift_forward(input, shift, out) + + return out + + @staticmethod + def backward(ctx, grad_output): + + shift = ctx.saved_tensors[0] + data_grad_input = grad_output.new(*grad_output.size()).zero_() + shift_grad_input = shift.new(*shift.size()).zero_() + ext_module.tin_shift_backward(grad_output, shift, data_grad_input) + + return data_grad_input, shift_grad_input + + +tin_shift = TINShiftFunction.apply + + +class TINShift(nn.Module): + """Temporal Interlace Shift. + + Temporal Interlace shift is a differentiable temporal-wise frame shifting + which is proposed in "Temporal Interlacing Network" + + Please refer to https://arxiv.org/abs/2001.06499 for more details. + Code is modified from https://github.com/mit-han-lab/temporal-shift-module + """ + + def forward(self, input, shift): + """Perform temporal interlace shift. + + Args: + input (Tensor): Feature map with shape [N, num_segments, C, H * W]. + shift (Tensor): Shift tensor with shape [N, num_segments]. + + Returns: + Feature map after temporal interlace shift. + """ + return tin_shift(input, shift) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/upfirdn2d.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/upfirdn2d.py new file mode 100644 index 0000000..c8bb2c3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/upfirdn2d.py @@ -0,0 +1,330 @@ +# modified from https://github.com/rosinality/stylegan2-pytorch/blob/master/op/upfirdn2d.py # noqa:E501 + +# Copyright (c) 2021, NVIDIA Corporation. All rights reserved. +# NVIDIA Source Code License for StyleGAN2 with Adaptive Discriminator +# Augmentation (ADA) +# ======================================================================= + +# 1. Definitions + +# "Licensor" means any person or entity that distributes its Work. + +# "Software" means the original work of authorship made available under +# this License. + +# "Work" means the Software and any additions to or derivative works of +# the Software that are made available under this License. + +# The terms "reproduce," "reproduction," "derivative works," and +# "distribution" have the meaning as provided under U.S. copyright law; +# provided, however, that 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. + +# Works, including the Software, are "made available" under this License +# by including in or with the Work either (a) a copyright notice +# referencing the applicability of this License to the Work, or (b) a +# copy of this License. + +# 2. License Grants + +# 2.1 Copyright Grant. Subject to the terms and conditions of this +# License, each Licensor grants to you a perpetual, worldwide, +# non-exclusive, royalty-free, copyright license to reproduce, +# prepare derivative works of, publicly display, publicly perform, +# sublicense and distribute its Work and any resulting derivative +# works in any form. + +# 3. Limitations + +# 3.1 Redistribution. You may reproduce or distribute the Work only +# if (a) you do so under this License, (b) you include a complete +# copy of this License with your distribution, and (c) you retain +# without modification any copyright, patent, trademark, or +# attribution notices that are present in the Work. + +# 3.2 Derivative Works. You may specify that additional or different +# terms apply to the use, reproduction, and distribution of your +# derivative works of the Work ("Your Terms") only if (a) Your Terms +# provide that the use limitation in Section 3.3 applies to your +# derivative works, and (b) you identify the specific derivative +# works that are subject to Your Terms. Notwithstanding Your Terms, +# this License (including the redistribution requirements in Section +# 3.1) will continue to apply to the Work itself. + +# 3.3 Use Limitation. The Work and any derivative works thereof only +# may be used or intended for use non-commercially. Notwithstanding +# the foregoing, NVIDIA and its affiliates may use the Work and any +# derivative works commercially. As used herein, "non-commercially" +# means for research or evaluation purposes only. + +# 3.4 Patent Claims. If you bring or threaten to bring a patent claim +# against any Licensor (including any claim, cross-claim or +# counterclaim in a lawsuit) to enforce any patents that you allege +# are infringed by any Work, then your rights under this License from +# such Licensor (including the grant in Section 2.1) will terminate +# immediately. + +# 3.5 Trademarks. This License does not grant any rights to use any +# Licensor’s or its affiliates’ names, logos, or trademarks, except +# as necessary to reproduce the notices described in this License. + +# 3.6 Termination. If you violate any term of this License, then your +# rights under this License (including the grant in Section 2.1) will +# terminate immediately. + +# 4. Disclaimer of Warranty. + +# THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR +# NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER +# THIS LICENSE. + +# 5. Limitation of Liability. + +# EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL +# THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE +# SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, +# INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF +# OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK +# (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, +# LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER +# COMMERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGES. + +# ======================================================================= + +import torch +from torch.autograd import Function +from torch.nn import functional as F + +from annotator.uniformer.mmcv.utils import to_2tuple +from ..utils import ext_loader + +upfirdn2d_ext = ext_loader.load_ext('_ext', ['upfirdn2d']) + + +class UpFirDn2dBackward(Function): + + @staticmethod + def forward(ctx, grad_output, kernel, grad_kernel, up, down, pad, g_pad, + in_size, out_size): + + up_x, up_y = up + down_x, down_y = down + g_pad_x0, g_pad_x1, g_pad_y0, g_pad_y1 = g_pad + + grad_output = grad_output.reshape(-1, out_size[0], out_size[1], 1) + + grad_input = upfirdn2d_ext.upfirdn2d( + grad_output, + grad_kernel, + up_x=down_x, + up_y=down_y, + down_x=up_x, + down_y=up_y, + pad_x0=g_pad_x0, + pad_x1=g_pad_x1, + pad_y0=g_pad_y0, + pad_y1=g_pad_y1) + grad_input = grad_input.view(in_size[0], in_size[1], in_size[2], + in_size[3]) + + ctx.save_for_backward(kernel) + + pad_x0, pad_x1, pad_y0, pad_y1 = pad + + ctx.up_x = up_x + ctx.up_y = up_y + ctx.down_x = down_x + ctx.down_y = down_y + ctx.pad_x0 = pad_x0 + ctx.pad_x1 = pad_x1 + ctx.pad_y0 = pad_y0 + ctx.pad_y1 = pad_y1 + ctx.in_size = in_size + ctx.out_size = out_size + + return grad_input + + @staticmethod + def backward(ctx, gradgrad_input): + kernel, = ctx.saved_tensors + + gradgrad_input = gradgrad_input.reshape(-1, ctx.in_size[2], + ctx.in_size[3], 1) + + gradgrad_out = upfirdn2d_ext.upfirdn2d( + gradgrad_input, + kernel, + up_x=ctx.up_x, + up_y=ctx.up_y, + down_x=ctx.down_x, + down_y=ctx.down_y, + pad_x0=ctx.pad_x0, + pad_x1=ctx.pad_x1, + pad_y0=ctx.pad_y0, + pad_y1=ctx.pad_y1) + # gradgrad_out = gradgrad_out.view(ctx.in_size[0], ctx.out_size[0], + # ctx.out_size[1], ctx.in_size[3]) + gradgrad_out = gradgrad_out.view(ctx.in_size[0], ctx.in_size[1], + ctx.out_size[0], ctx.out_size[1]) + + return gradgrad_out, None, None, None, None, None, None, None, None + + +class UpFirDn2d(Function): + + @staticmethod + def forward(ctx, input, kernel, up, down, pad): + up_x, up_y = up + down_x, down_y = down + pad_x0, pad_x1, pad_y0, pad_y1 = pad + + kernel_h, kernel_w = kernel.shape + batch, channel, in_h, in_w = input.shape + ctx.in_size = input.shape + + input = input.reshape(-1, in_h, in_w, 1) + + ctx.save_for_backward(kernel, torch.flip(kernel, [0, 1])) + + out_h = (in_h * up_y + pad_y0 + pad_y1 - kernel_h) // down_y + 1 + out_w = (in_w * up_x + pad_x0 + pad_x1 - kernel_w) // down_x + 1 + ctx.out_size = (out_h, out_w) + + ctx.up = (up_x, up_y) + ctx.down = (down_x, down_y) + ctx.pad = (pad_x0, pad_x1, pad_y0, pad_y1) + + g_pad_x0 = kernel_w - pad_x0 - 1 + g_pad_y0 = kernel_h - pad_y0 - 1 + g_pad_x1 = in_w * up_x - out_w * down_x + pad_x0 - up_x + 1 + g_pad_y1 = in_h * up_y - out_h * down_y + pad_y0 - up_y + 1 + + ctx.g_pad = (g_pad_x0, g_pad_x1, g_pad_y0, g_pad_y1) + + out = upfirdn2d_ext.upfirdn2d( + input, + kernel, + up_x=up_x, + up_y=up_y, + down_x=down_x, + down_y=down_y, + pad_x0=pad_x0, + pad_x1=pad_x1, + pad_y0=pad_y0, + pad_y1=pad_y1) + # out = out.view(major, out_h, out_w, minor) + out = out.view(-1, channel, out_h, out_w) + + return out + + @staticmethod + def backward(ctx, grad_output): + kernel, grad_kernel = ctx.saved_tensors + + grad_input = UpFirDn2dBackward.apply( + grad_output, + kernel, + grad_kernel, + ctx.up, + ctx.down, + ctx.pad, + ctx.g_pad, + ctx.in_size, + ctx.out_size, + ) + + return grad_input, None, None, None, None + + +def upfirdn2d(input, kernel, up=1, down=1, pad=(0, 0)): + """UpFRIDn for 2d features. + + UpFIRDn is short for upsample, apply FIR filter and downsample. More + details can be found in: + https://www.mathworks.com/help/signal/ref/upfirdn.html + + Args: + input (Tensor): Tensor with shape of (n, c, h, w). + kernel (Tensor): Filter kernel. + up (int | tuple[int], optional): Upsampling factor. If given a number, + we will use this factor for the both height and width side. + Defaults to 1. + down (int | tuple[int], optional): Downsampling factor. If given a + number, we will use this factor for the both height and width side. + Defaults to 1. + pad (tuple[int], optional): Padding for tensors, (x_pad, y_pad) or + (x_pad_0, x_pad_1, y_pad_0, y_pad_1). Defaults to (0, 0). + + Returns: + Tensor: Tensor after UpFIRDn. + """ + if input.device.type == 'cpu': + if len(pad) == 2: + pad = (pad[0], pad[1], pad[0], pad[1]) + + up = to_2tuple(up) + + down = to_2tuple(down) + + out = upfirdn2d_native(input, kernel, up[0], up[1], down[0], down[1], + pad[0], pad[1], pad[2], pad[3]) + else: + _up = to_2tuple(up) + + _down = to_2tuple(down) + + if len(pad) == 4: + _pad = pad + elif len(pad) == 2: + _pad = (pad[0], pad[1], pad[0], pad[1]) + + out = UpFirDn2d.apply(input, kernel, _up, _down, _pad) + + return out + + +def upfirdn2d_native(input, kernel, up_x, up_y, down_x, down_y, pad_x0, pad_x1, + pad_y0, pad_y1): + _, channel, in_h, in_w = input.shape + input = input.reshape(-1, in_h, in_w, 1) + + _, in_h, in_w, minor = input.shape + kernel_h, kernel_w = kernel.shape + + out = input.view(-1, in_h, 1, in_w, 1, minor) + out = F.pad(out, [0, 0, 0, up_x - 1, 0, 0, 0, up_y - 1]) + out = out.view(-1, in_h * up_y, in_w * up_x, minor) + + out = F.pad( + out, + [0, 0, + max(pad_x0, 0), + max(pad_x1, 0), + max(pad_y0, 0), + max(pad_y1, 0)]) + out = out[:, + max(-pad_y0, 0):out.shape[1] - max(-pad_y1, 0), + max(-pad_x0, 0):out.shape[2] - max(-pad_x1, 0), :, ] + + out = out.permute(0, 3, 1, 2) + out = out.reshape( + [-1, 1, in_h * up_y + pad_y0 + pad_y1, in_w * up_x + pad_x0 + pad_x1]) + w = torch.flip(kernel, [0, 1]).view(1, 1, kernel_h, kernel_w) + out = F.conv2d(out, w) + out = out.reshape( + -1, + minor, + in_h * up_y + pad_y0 + pad_y1 - kernel_h + 1, + in_w * up_x + pad_x0 + pad_x1 - kernel_w + 1, + ) + out = out.permute(0, 2, 3, 1) + out = out[:, ::down_y, ::down_x, :] + + out_h = (in_h * up_y + pad_y0 + pad_y1 - kernel_h) // down_y + 1 + out_w = (in_w * up_x + pad_x0 + pad_x1 - kernel_w) // down_x + 1 + + return out.view(-1, channel, out_h, out_w) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/voxelize.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/voxelize.py new file mode 100644 index 0000000..ca3226a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/ops/voxelize.py @@ -0,0 +1,132 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import nn +from torch.autograd import Function +from torch.nn.modules.utils import _pair + +from ..utils import ext_loader + +ext_module = ext_loader.load_ext( + '_ext', ['dynamic_voxelize_forward', 'hard_voxelize_forward']) + + +class _Voxelization(Function): + + @staticmethod + def forward(ctx, + points, + voxel_size, + coors_range, + max_points=35, + max_voxels=20000): + """Convert kitti points(N, >=3) to voxels. + + Args: + points (torch.Tensor): [N, ndim]. Points[:, :3] contain xyz points + and points[:, 3:] contain other information like reflectivity. + voxel_size (tuple or float): The size of voxel with the shape of + [3]. + coors_range (tuple or float): The coordinate range of voxel with + the shape of [6]. + max_points (int, optional): maximum points contained in a voxel. if + max_points=-1, it means using dynamic_voxelize. Default: 35. + max_voxels (int, optional): maximum voxels this function create. + for second, 20000 is a good choice. Users should shuffle points + before call this function because max_voxels may drop points. + Default: 20000. + + Returns: + voxels_out (torch.Tensor): Output voxels with the shape of [M, + max_points, ndim]. Only contain points and returned when + max_points != -1. + coors_out (torch.Tensor): Output coordinates with the shape of + [M, 3]. + num_points_per_voxel_out (torch.Tensor): Num points per voxel with + the shape of [M]. Only returned when max_points != -1. + """ + if max_points == -1 or max_voxels == -1: + coors = points.new_zeros(size=(points.size(0), 3), dtype=torch.int) + ext_module.dynamic_voxelize_forward(points, coors, voxel_size, + coors_range, 3) + return coors + else: + voxels = points.new_zeros( + size=(max_voxels, max_points, points.size(1))) + coors = points.new_zeros(size=(max_voxels, 3), dtype=torch.int) + num_points_per_voxel = points.new_zeros( + size=(max_voxels, ), dtype=torch.int) + voxel_num = ext_module.hard_voxelize_forward( + points, voxels, coors, num_points_per_voxel, voxel_size, + coors_range, max_points, max_voxels, 3) + # select the valid voxels + voxels_out = voxels[:voxel_num] + coors_out = coors[:voxel_num] + num_points_per_voxel_out = num_points_per_voxel[:voxel_num] + return voxels_out, coors_out, num_points_per_voxel_out + + +voxelization = _Voxelization.apply + + +class Voxelization(nn.Module): + """Convert kitti points(N, >=3) to voxels. + + Please refer to `PVCNN `_ for more + details. + + Args: + voxel_size (tuple or float): The size of voxel with the shape of [3]. + point_cloud_range (tuple or float): The coordinate range of voxel with + the shape of [6]. + max_num_points (int): maximum points contained in a voxel. if + max_points=-1, it means using dynamic_voxelize. + max_voxels (int, optional): maximum voxels this function create. + for second, 20000 is a good choice. Users should shuffle points + before call this function because max_voxels may drop points. + Default: 20000. + """ + + def __init__(self, + voxel_size, + point_cloud_range, + max_num_points, + max_voxels=20000): + super().__init__() + + self.voxel_size = voxel_size + self.point_cloud_range = point_cloud_range + self.max_num_points = max_num_points + if isinstance(max_voxels, tuple): + self.max_voxels = max_voxels + else: + self.max_voxels = _pair(max_voxels) + + point_cloud_range = torch.tensor( + point_cloud_range, dtype=torch.float32) + voxel_size = torch.tensor(voxel_size, dtype=torch.float32) + grid_size = (point_cloud_range[3:] - + point_cloud_range[:3]) / voxel_size + grid_size = torch.round(grid_size).long() + input_feat_shape = grid_size[:2] + self.grid_size = grid_size + # the origin shape is as [x-len, y-len, z-len] + # [w, h, d] -> [d, h, w] + self.pcd_shape = [*input_feat_shape, 1][::-1] + + def forward(self, input): + if self.training: + max_voxels = self.max_voxels[0] + else: + max_voxels = self.max_voxels[1] + + return voxelization(input, self.voxel_size, self.point_cloud_range, + self.max_num_points, max_voxels) + + def __repr__(self): + s = self.__class__.__name__ + '(' + s += 'voxel_size=' + str(self.voxel_size) + s += ', point_cloud_range=' + str(self.point_cloud_range) + s += ', max_num_points=' + str(self.max_num_points) + s += ', max_voxels=' + str(self.max_voxels) + s += ')' + return s diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/__init__.py new file mode 100644 index 0000000..2ed2c17 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .collate import collate +from .data_container import DataContainer +from .data_parallel import MMDataParallel +from .distributed import MMDistributedDataParallel +from .registry import MODULE_WRAPPERS +from .scatter_gather import scatter, scatter_kwargs +from .utils import is_module_wrapper + +__all__ = [ + 'collate', 'DataContainer', 'MMDataParallel', 'MMDistributedDataParallel', + 'scatter', 'scatter_kwargs', 'is_module_wrapper', 'MODULE_WRAPPERS' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/_functions.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/_functions.py new file mode 100644 index 0000000..9b5a8a4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/_functions.py @@ -0,0 +1,79 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch.nn.parallel._functions import _get_stream + + +def scatter(input, devices, streams=None): + """Scatters tensor across multiple GPUs.""" + if streams is None: + streams = [None] * len(devices) + + if isinstance(input, list): + chunk_size = (len(input) - 1) // len(devices) + 1 + outputs = [ + scatter(input[i], [devices[i // chunk_size]], + [streams[i // chunk_size]]) for i in range(len(input)) + ] + return outputs + elif isinstance(input, torch.Tensor): + output = input.contiguous() + # TODO: copy to a pinned buffer first (if copying from CPU) + stream = streams[0] if output.numel() > 0 else None + if devices != [-1]: + with torch.cuda.device(devices[0]), torch.cuda.stream(stream): + output = output.cuda(devices[0], non_blocking=True) + else: + # unsqueeze the first dimension thus the tensor's shape is the + # same as those scattered with GPU. + output = output.unsqueeze(0) + return output + else: + raise Exception(f'Unknown type {type(input)}.') + + +def synchronize_stream(output, devices, streams): + if isinstance(output, list): + chunk_size = len(output) // len(devices) + for i in range(len(devices)): + for j in range(chunk_size): + synchronize_stream(output[i * chunk_size + j], [devices[i]], + [streams[i]]) + elif isinstance(output, torch.Tensor): + if output.numel() != 0: + with torch.cuda.device(devices[0]): + main_stream = torch.cuda.current_stream() + main_stream.wait_stream(streams[0]) + output.record_stream(main_stream) + else: + raise Exception(f'Unknown type {type(output)}.') + + +def get_input_device(input): + if isinstance(input, list): + for item in input: + input_device = get_input_device(item) + if input_device != -1: + return input_device + return -1 + elif isinstance(input, torch.Tensor): + return input.get_device() if input.is_cuda else -1 + else: + raise Exception(f'Unknown type {type(input)}.') + + +class Scatter: + + @staticmethod + def forward(target_gpus, input): + input_device = get_input_device(input) + streams = None + if input_device == -1 and target_gpus != [-1]: + # Perform CPU to GPU copies in a background stream + streams = [_get_stream(device) for device in target_gpus] + + outputs = scatter(input, target_gpus, streams) + # Synchronize with the copy stream + if streams is not None: + synchronize_stream(outputs, target_gpus, streams) + + return tuple(outputs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/collate.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/collate.py new file mode 100644 index 0000000..ad74919 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/collate.py @@ -0,0 +1,84 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections.abc import Mapping, Sequence + +import torch +import torch.nn.functional as F +from torch.utils.data.dataloader import default_collate + +from .data_container import DataContainer + + +def collate(batch, samples_per_gpu=1): + """Puts each data field into a tensor/DataContainer with outer dimension + batch size. + + Extend default_collate to add support for + :type:`~mmcv.parallel.DataContainer`. There are 3 cases. + + 1. cpu_only = True, e.g., meta data + 2. cpu_only = False, stack = True, e.g., images tensors + 3. cpu_only = False, stack = False, e.g., gt bboxes + """ + + if not isinstance(batch, Sequence): + raise TypeError(f'{batch.dtype} is not supported.') + + if isinstance(batch[0], DataContainer): + stacked = [] + if batch[0].cpu_only: + for i in range(0, len(batch), samples_per_gpu): + stacked.append( + [sample.data for sample in batch[i:i + samples_per_gpu]]) + return DataContainer( + stacked, batch[0].stack, batch[0].padding_value, cpu_only=True) + elif batch[0].stack: + for i in range(0, len(batch), samples_per_gpu): + assert isinstance(batch[i].data, torch.Tensor) + + if batch[i].pad_dims is not None: + ndim = batch[i].dim() + assert ndim > batch[i].pad_dims + max_shape = [0 for _ in range(batch[i].pad_dims)] + for dim in range(1, batch[i].pad_dims + 1): + max_shape[dim - 1] = batch[i].size(-dim) + for sample in batch[i:i + samples_per_gpu]: + for dim in range(0, ndim - batch[i].pad_dims): + assert batch[i].size(dim) == sample.size(dim) + for dim in range(1, batch[i].pad_dims + 1): + max_shape[dim - 1] = max(max_shape[dim - 1], + sample.size(-dim)) + padded_samples = [] + for sample in batch[i:i + samples_per_gpu]: + pad = [0 for _ in range(batch[i].pad_dims * 2)] + for dim in range(1, batch[i].pad_dims + 1): + pad[2 * dim - + 1] = max_shape[dim - 1] - sample.size(-dim) + padded_samples.append( + F.pad( + sample.data, pad, value=sample.padding_value)) + stacked.append(default_collate(padded_samples)) + elif batch[i].pad_dims is None: + stacked.append( + default_collate([ + sample.data + for sample in batch[i:i + samples_per_gpu] + ])) + else: + raise ValueError( + 'pad_dims should be either None or integers (1-3)') + + else: + for i in range(0, len(batch), samples_per_gpu): + stacked.append( + [sample.data for sample in batch[i:i + samples_per_gpu]]) + return DataContainer(stacked, batch[0].stack, batch[0].padding_value) + elif isinstance(batch[0], Sequence): + transposed = zip(*batch) + return [collate(samples, samples_per_gpu) for samples in transposed] + elif isinstance(batch[0], Mapping): + return { + key: collate([d[key] for d in batch], samples_per_gpu) + for key in batch[0] + } + else: + return default_collate(batch) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/data_container.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/data_container.py new file mode 100644 index 0000000..cedb0d3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/data_container.py @@ -0,0 +1,89 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import functools + +import torch + + +def assert_tensor_type(func): + + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not isinstance(args[0].data, torch.Tensor): + raise AttributeError( + f'{args[0].__class__.__name__} has no attribute ' + f'{func.__name__} for type {args[0].datatype}') + return func(*args, **kwargs) + + return wrapper + + +class DataContainer: + """A container for any type of objects. + + Typically tensors will be stacked in the collate function and sliced along + some dimension in the scatter function. This behavior has some limitations. + 1. All tensors have to be the same size. + 2. Types are limited (numpy array or Tensor). + + We design `DataContainer` and `MMDataParallel` to overcome these + limitations. The behavior can be either of the following. + + - copy to GPU, pad all tensors to the same size and stack them + - copy to GPU without stacking + - leave the objects as is and pass it to the model + - pad_dims specifies the number of last few dimensions to do padding + """ + + def __init__(self, + data, + stack=False, + padding_value=0, + cpu_only=False, + pad_dims=2): + self._data = data + self._cpu_only = cpu_only + self._stack = stack + self._padding_value = padding_value + assert pad_dims in [None, 1, 2, 3] + self._pad_dims = pad_dims + + def __repr__(self): + return f'{self.__class__.__name__}({repr(self.data)})' + + def __len__(self): + return len(self._data) + + @property + def data(self): + return self._data + + @property + def datatype(self): + if isinstance(self.data, torch.Tensor): + return self.data.type() + else: + return type(self.data) + + @property + def cpu_only(self): + return self._cpu_only + + @property + def stack(self): + return self._stack + + @property + def padding_value(self): + return self._padding_value + + @property + def pad_dims(self): + return self._pad_dims + + @assert_tensor_type + def size(self, *args, **kwargs): + return self.data.size(*args, **kwargs) + + @assert_tensor_type + def dim(self): + return self.data.dim() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/data_parallel.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/data_parallel.py new file mode 100644 index 0000000..79b5f69 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/data_parallel.py @@ -0,0 +1,89 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from itertools import chain + +from torch.nn.parallel import DataParallel + +from .scatter_gather import scatter_kwargs + + +class MMDataParallel(DataParallel): + """The DataParallel module that supports DataContainer. + + MMDataParallel has two main differences with PyTorch DataParallel: + + - It supports a custom type :class:`DataContainer` which allows more + flexible control of input data during both GPU and CPU inference. + - It implement two more APIs ``train_step()`` and ``val_step()``. + + Args: + module (:class:`nn.Module`): Module to be encapsulated. + device_ids (list[int]): Device IDS of modules to be scattered to. + Defaults to None when GPU is not available. + output_device (str | int): Device ID for output. Defaults to None. + dim (int): Dimension used to scatter the data. Defaults to 0. + """ + + def __init__(self, *args, dim=0, **kwargs): + super(MMDataParallel, self).__init__(*args, dim=dim, **kwargs) + self.dim = dim + + def forward(self, *inputs, **kwargs): + """Override the original forward function. + + The main difference lies in the CPU inference where the data in + :class:`DataContainers` will still be gathered. + """ + if not self.device_ids: + # We add the following line thus the module could gather and + # convert data containers as those in GPU inference + inputs, kwargs = self.scatter(inputs, kwargs, [-1]) + return self.module(*inputs[0], **kwargs[0]) + else: + return super().forward(*inputs, **kwargs) + + def scatter(self, inputs, kwargs, device_ids): + return scatter_kwargs(inputs, kwargs, device_ids, dim=self.dim) + + def train_step(self, *inputs, **kwargs): + if not self.device_ids: + # We add the following line thus the module could gather and + # convert data containers as those in GPU inference + inputs, kwargs = self.scatter(inputs, kwargs, [-1]) + return self.module.train_step(*inputs[0], **kwargs[0]) + + assert len(self.device_ids) == 1, \ + ('MMDataParallel only supports single GPU training, if you need to' + ' train with multiple GPUs, please use MMDistributedDataParallel' + 'instead.') + + for t in chain(self.module.parameters(), self.module.buffers()): + if t.device != self.src_device_obj: + raise RuntimeError( + 'module must have its parameters and buffers ' + f'on device {self.src_device_obj} (device_ids[0]) but ' + f'found one of them on device: {t.device}') + + inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids) + return self.module.train_step(*inputs[0], **kwargs[0]) + + def val_step(self, *inputs, **kwargs): + if not self.device_ids: + # We add the following line thus the module could gather and + # convert data containers as those in GPU inference + inputs, kwargs = self.scatter(inputs, kwargs, [-1]) + return self.module.val_step(*inputs[0], **kwargs[0]) + + assert len(self.device_ids) == 1, \ + ('MMDataParallel only supports single GPU training, if you need to' + ' train with multiple GPUs, please use MMDistributedDataParallel' + ' instead.') + + for t in chain(self.module.parameters(), self.module.buffers()): + if t.device != self.src_device_obj: + raise RuntimeError( + 'module must have its parameters and buffers ' + f'on device {self.src_device_obj} (device_ids[0]) but ' + f'found one of them on device: {t.device}') + + inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids) + return self.module.val_step(*inputs[0], **kwargs[0]) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/distributed.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/distributed.py new file mode 100644 index 0000000..1e4c279 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/distributed.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch.nn.parallel.distributed import (DistributedDataParallel, + _find_tensors) + +from annotator.uniformer.mmcv import print_log +from annotator.uniformer.mmcv.utils import TORCH_VERSION, digit_version +from .scatter_gather import scatter_kwargs + + +class MMDistributedDataParallel(DistributedDataParallel): + """The DDP module that supports DataContainer. + + MMDDP has two main differences with PyTorch DDP: + + - It supports a custom type :class:`DataContainer` which allows more + flexible control of input data. + - It implement two APIs ``train_step()`` and ``val_step()``. + """ + + def to_kwargs(self, inputs, kwargs, device_id): + # Use `self.to_kwargs` instead of `self.scatter` in pytorch1.8 + # to move all tensors to device_id + return scatter_kwargs(inputs, kwargs, [device_id], dim=self.dim) + + def scatter(self, inputs, kwargs, device_ids): + return scatter_kwargs(inputs, kwargs, device_ids, dim=self.dim) + + def train_step(self, *inputs, **kwargs): + """train_step() API for module wrapped by DistributedDataParallel. + + This method is basically the same as + ``DistributedDataParallel.forward()``, while replacing + ``self.module.forward()`` with ``self.module.train_step()``. + It is compatible with PyTorch 1.1 - 1.5. + """ + + # In PyTorch >= 1.7, ``reducer._rebuild_buckets()`` is moved from the + # end of backward to the beginning of forward. + if ('parrots' not in TORCH_VERSION + and digit_version(TORCH_VERSION) >= digit_version('1.7') + and self.reducer._rebuild_buckets()): + print_log( + 'Reducer buckets have been rebuilt in this iteration.', + logger='mmcv') + + if getattr(self, 'require_forward_param_sync', True): + self._sync_params() + if self.device_ids: + inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids) + if len(self.device_ids) == 1: + output = self.module.train_step(*inputs[0], **kwargs[0]) + else: + outputs = self.parallel_apply( + self._module_copies[:len(inputs)], inputs, kwargs) + output = self.gather(outputs, self.output_device) + else: + output = self.module.train_step(*inputs, **kwargs) + + if torch.is_grad_enabled() and getattr( + self, 'require_backward_grad_sync', True): + if self.find_unused_parameters: + self.reducer.prepare_for_backward(list(_find_tensors(output))) + else: + self.reducer.prepare_for_backward([]) + else: + if ('parrots' not in TORCH_VERSION + and digit_version(TORCH_VERSION) > digit_version('1.2')): + self.require_forward_param_sync = False + return output + + def val_step(self, *inputs, **kwargs): + """val_step() API for module wrapped by DistributedDataParallel. + + This method is basically the same as + ``DistributedDataParallel.forward()``, while replacing + ``self.module.forward()`` with ``self.module.val_step()``. + It is compatible with PyTorch 1.1 - 1.5. + """ + # In PyTorch >= 1.7, ``reducer._rebuild_buckets()`` is moved from the + # end of backward to the beginning of forward. + if ('parrots' not in TORCH_VERSION + and digit_version(TORCH_VERSION) >= digit_version('1.7') + and self.reducer._rebuild_buckets()): + print_log( + 'Reducer buckets have been rebuilt in this iteration.', + logger='mmcv') + + if getattr(self, 'require_forward_param_sync', True): + self._sync_params() + if self.device_ids: + inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids) + if len(self.device_ids) == 1: + output = self.module.val_step(*inputs[0], **kwargs[0]) + else: + outputs = self.parallel_apply( + self._module_copies[:len(inputs)], inputs, kwargs) + output = self.gather(outputs, self.output_device) + else: + output = self.module.val_step(*inputs, **kwargs) + + if torch.is_grad_enabled() and getattr( + self, 'require_backward_grad_sync', True): + if self.find_unused_parameters: + self.reducer.prepare_for_backward(list(_find_tensors(output))) + else: + self.reducer.prepare_for_backward([]) + else: + if ('parrots' not in TORCH_VERSION + and digit_version(TORCH_VERSION) > digit_version('1.2')): + self.require_forward_param_sync = False + return output diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/distributed_deprecated.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/distributed_deprecated.py new file mode 100644 index 0000000..676937a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/distributed_deprecated.py @@ -0,0 +1,70 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.distributed as dist +import torch.nn as nn +from torch._utils import (_flatten_dense_tensors, _take_tensors, + _unflatten_dense_tensors) + +from annotator.uniformer.mmcv.utils import TORCH_VERSION, digit_version +from .registry import MODULE_WRAPPERS +from .scatter_gather import scatter_kwargs + + +@MODULE_WRAPPERS.register_module() +class MMDistributedDataParallel(nn.Module): + + def __init__(self, + module, + dim=0, + broadcast_buffers=True, + bucket_cap_mb=25): + super(MMDistributedDataParallel, self).__init__() + self.module = module + self.dim = dim + self.broadcast_buffers = broadcast_buffers + + self.broadcast_bucket_size = bucket_cap_mb * 1024 * 1024 + self._sync_params() + + def _dist_broadcast_coalesced(self, tensors, buffer_size): + for tensors in _take_tensors(tensors, buffer_size): + flat_tensors = _flatten_dense_tensors(tensors) + dist.broadcast(flat_tensors, 0) + for tensor, synced in zip( + tensors, _unflatten_dense_tensors(flat_tensors, tensors)): + tensor.copy_(synced) + + def _sync_params(self): + module_states = list(self.module.state_dict().values()) + if len(module_states) > 0: + self._dist_broadcast_coalesced(module_states, + self.broadcast_bucket_size) + if self.broadcast_buffers: + if (TORCH_VERSION != 'parrots' + and digit_version(TORCH_VERSION) < digit_version('1.0')): + buffers = [b.data for b in self.module._all_buffers()] + else: + buffers = [b.data for b in self.module.buffers()] + if len(buffers) > 0: + self._dist_broadcast_coalesced(buffers, + self.broadcast_bucket_size) + + def scatter(self, inputs, kwargs, device_ids): + return scatter_kwargs(inputs, kwargs, device_ids, dim=self.dim) + + def forward(self, *inputs, **kwargs): + inputs, kwargs = self.scatter(inputs, kwargs, + [torch.cuda.current_device()]) + return self.module(*inputs[0], **kwargs[0]) + + def train_step(self, *inputs, **kwargs): + inputs, kwargs = self.scatter(inputs, kwargs, + [torch.cuda.current_device()]) + output = self.module.train_step(*inputs[0], **kwargs[0]) + return output + + def val_step(self, *inputs, **kwargs): + inputs, kwargs = self.scatter(inputs, kwargs, + [torch.cuda.current_device()]) + output = self.module.val_step(*inputs[0], **kwargs[0]) + return output diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/registry.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/registry.py new file mode 100644 index 0000000..a204a07 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/registry.py @@ -0,0 +1,8 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from torch.nn.parallel import DataParallel, DistributedDataParallel + +from annotator.uniformer.mmcv.utils import Registry + +MODULE_WRAPPERS = Registry('module wrapper') +MODULE_WRAPPERS.register_module(module=DataParallel) +MODULE_WRAPPERS.register_module(module=DistributedDataParallel) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/scatter_gather.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/scatter_gather.py new file mode 100644 index 0000000..900ff88 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/scatter_gather.py @@ -0,0 +1,59 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch.nn.parallel._functions import Scatter as OrigScatter + +from ._functions import Scatter +from .data_container import DataContainer + + +def scatter(inputs, target_gpus, dim=0): + """Scatter inputs to target gpus. + + The only difference from original :func:`scatter` is to add support for + :type:`~mmcv.parallel.DataContainer`. + """ + + def scatter_map(obj): + if isinstance(obj, torch.Tensor): + if target_gpus != [-1]: + return OrigScatter.apply(target_gpus, None, dim, obj) + else: + # for CPU inference we use self-implemented scatter + return Scatter.forward(target_gpus, obj) + if isinstance(obj, DataContainer): + if obj.cpu_only: + return obj.data + else: + return Scatter.forward(target_gpus, obj.data) + if isinstance(obj, tuple) and len(obj) > 0: + return list(zip(*map(scatter_map, obj))) + if isinstance(obj, list) and len(obj) > 0: + out = list(map(list, zip(*map(scatter_map, obj)))) + return out + if isinstance(obj, dict) and len(obj) > 0: + out = list(map(type(obj), zip(*map(scatter_map, obj.items())))) + return out + return [obj for targets in target_gpus] + + # After scatter_map is called, a scatter_map cell will exist. This cell + # has a reference to the actual function scatter_map, which has references + # to a closure that has a reference to the scatter_map cell (because the + # fn is recursive). To avoid this reference cycle, we set the function to + # None, clearing the cell + try: + return scatter_map(inputs) + finally: + scatter_map = None + + +def scatter_kwargs(inputs, kwargs, target_gpus, dim=0): + """Scatter with support for kwargs dictionary.""" + inputs = scatter(inputs, target_gpus, dim) if inputs else [] + kwargs = scatter(kwargs, target_gpus, dim) if kwargs else [] + if len(inputs) < len(kwargs): + inputs.extend([() for _ in range(len(kwargs) - len(inputs))]) + elif len(kwargs) < len(inputs): + kwargs.extend([{} for _ in range(len(inputs) - len(kwargs))]) + inputs = tuple(inputs) + kwargs = tuple(kwargs) + return inputs, kwargs diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/utils.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/utils.py new file mode 100644 index 0000000..0f5712c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/parallel/utils.py @@ -0,0 +1,20 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .registry import MODULE_WRAPPERS + + +def is_module_wrapper(module): + """Check if a module is a module wrapper. + + The following 3 modules in MMCV (and their subclasses) are regarded as + module wrappers: DataParallel, DistributedDataParallel, + MMDistributedDataParallel (the deprecated version). You may add you own + module wrapper by registering it to mmcv.parallel.MODULE_WRAPPERS. + + Args: + module (nn.Module): The module to be checked. + + Returns: + bool: True if the input module is a module wrapper. + """ + module_wrappers = tuple(MODULE_WRAPPERS.module_dict.values()) + return isinstance(module, module_wrappers) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/__init__.py new file mode 100644 index 0000000..52e4b48 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/__init__.py @@ -0,0 +1,47 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_module import BaseModule, ModuleList, Sequential +from .base_runner import BaseRunner +from .builder import RUNNERS, build_runner +from .checkpoint import (CheckpointLoader, _load_checkpoint, + _load_checkpoint_with_prefix, load_checkpoint, + load_state_dict, save_checkpoint, weights_to_cpu) +from .default_constructor import DefaultRunnerConstructor +from .dist_utils import (allreduce_grads, allreduce_params, get_dist_info, + init_dist, master_only) +from .epoch_based_runner import EpochBasedRunner, Runner +from .fp16_utils import LossScaler, auto_fp16, force_fp32, wrap_fp16_model +from .hooks import (HOOKS, CheckpointHook, ClosureHook, DistEvalHook, + DistSamplerSeedHook, DvcliveLoggerHook, EMAHook, EvalHook, + Fp16OptimizerHook, GradientCumulativeFp16OptimizerHook, + GradientCumulativeOptimizerHook, Hook, IterTimerHook, + LoggerHook, LrUpdaterHook, MlflowLoggerHook, + NeptuneLoggerHook, OptimizerHook, PaviLoggerHook, + SyncBuffersHook, TensorboardLoggerHook, TextLoggerHook, + WandbLoggerHook) +from .iter_based_runner import IterBasedRunner, IterLoader +from .log_buffer import LogBuffer +from .optimizer import (OPTIMIZER_BUILDERS, OPTIMIZERS, + DefaultOptimizerConstructor, build_optimizer, + build_optimizer_constructor) +from .priority import Priority, get_priority +from .utils import get_host_info, get_time_str, obj_from_dict, set_random_seed + +__all__ = [ + 'BaseRunner', 'Runner', 'EpochBasedRunner', 'IterBasedRunner', 'LogBuffer', + 'HOOKS', 'Hook', 'CheckpointHook', 'ClosureHook', 'LrUpdaterHook', + 'OptimizerHook', 'IterTimerHook', 'DistSamplerSeedHook', 'LoggerHook', + 'PaviLoggerHook', 'TextLoggerHook', 'TensorboardLoggerHook', + 'NeptuneLoggerHook', 'WandbLoggerHook', 'MlflowLoggerHook', + 'DvcliveLoggerHook', '_load_checkpoint', 'load_state_dict', + 'load_checkpoint', 'weights_to_cpu', 'save_checkpoint', 'Priority', + 'get_priority', 'get_host_info', 'get_time_str', 'obj_from_dict', + 'init_dist', 'get_dist_info', 'master_only', 'OPTIMIZER_BUILDERS', + 'OPTIMIZERS', 'DefaultOptimizerConstructor', 'build_optimizer', + 'build_optimizer_constructor', 'IterLoader', 'set_random_seed', + 'auto_fp16', 'force_fp32', 'wrap_fp16_model', 'Fp16OptimizerHook', + 'SyncBuffersHook', 'EMAHook', 'build_runner', 'RUNNERS', 'allreduce_grads', + 'allreduce_params', 'LossScaler', 'CheckpointLoader', 'BaseModule', + '_load_checkpoint_with_prefix', 'EvalHook', 'DistEvalHook', 'Sequential', + 'ModuleList', 'GradientCumulativeOptimizerHook', + 'GradientCumulativeFp16OptimizerHook', 'DefaultRunnerConstructor' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/base_module.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/base_module.py new file mode 100644 index 0000000..617fad9 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/base_module.py @@ -0,0 +1,195 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings +from abc import ABCMeta +from collections import defaultdict +from logging import FileHandler + +import torch.nn as nn + +from annotator.uniformer.mmcv.runner.dist_utils import master_only +from annotator.uniformer.mmcv.utils.logging import get_logger, logger_initialized, print_log + + +class BaseModule(nn.Module, metaclass=ABCMeta): + """Base module for all modules in openmmlab. + + ``BaseModule`` is a wrapper of ``torch.nn.Module`` with additional + functionality of parameter initialization. Compared with + ``torch.nn.Module``, ``BaseModule`` mainly adds three attributes. + + - ``init_cfg``: the config to control the initialization. + - ``init_weights``: The function of parameter + initialization and recording initialization + information. + - ``_params_init_info``: Used to track the parameter + initialization information. This attribute only + exists during executing the ``init_weights``. + + Args: + init_cfg (dict, optional): Initialization config dict. + """ + + def __init__(self, init_cfg=None): + """Initialize BaseModule, inherited from `torch.nn.Module`""" + + # NOTE init_cfg can be defined in different levels, but init_cfg + # in low levels has a higher priority. + + super(BaseModule, self).__init__() + # define default value of init_cfg instead of hard code + # in init_weights() function + self._is_init = False + + self.init_cfg = copy.deepcopy(init_cfg) + + # Backward compatibility in derived classes + # if pretrained is not None: + # warnings.warn('DeprecationWarning: pretrained is a deprecated \ + # key, please consider using init_cfg') + # self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + + @property + def is_init(self): + return self._is_init + + def init_weights(self): + """Initialize the weights.""" + + is_top_level_module = False + # check if it is top-level module + if not hasattr(self, '_params_init_info'): + # The `_params_init_info` is used to record the initialization + # information of the parameters + # the key should be the obj:`nn.Parameter` of model and the value + # should be a dict containing + # - init_info (str): The string that describes the initialization. + # - tmp_mean_value (FloatTensor): The mean of the parameter, + # which indicates whether the parameter has been modified. + # this attribute would be deleted after all parameters + # is initialized. + self._params_init_info = defaultdict(dict) + is_top_level_module = True + + # Initialize the `_params_init_info`, + # When detecting the `tmp_mean_value` of + # the corresponding parameter is changed, update related + # initialization information + for name, param in self.named_parameters(): + self._params_init_info[param][ + 'init_info'] = f'The value is the same before and ' \ + f'after calling `init_weights` ' \ + f'of {self.__class__.__name__} ' + self._params_init_info[param][ + 'tmp_mean_value'] = param.data.mean() + + # pass `params_init_info` to all submodules + # All submodules share the same `params_init_info`, + # so it will be updated when parameters are + # modified at any level of the model. + for sub_module in self.modules(): + sub_module._params_init_info = self._params_init_info + + # Get the initialized logger, if not exist, + # create a logger named `mmcv` + logger_names = list(logger_initialized.keys()) + logger_name = logger_names[0] if logger_names else 'mmcv' + + from ..cnn import initialize + from ..cnn.utils.weight_init import update_init_info + module_name = self.__class__.__name__ + if not self._is_init: + if self.init_cfg: + print_log( + f'initialize {module_name} with init_cfg {self.init_cfg}', + logger=logger_name) + initialize(self, self.init_cfg) + if isinstance(self.init_cfg, dict): + # prevent the parameters of + # the pre-trained model + # from being overwritten by + # the `init_weights` + if self.init_cfg['type'] == 'Pretrained': + return + + for m in self.children(): + if hasattr(m, 'init_weights'): + m.init_weights() + # users may overload the `init_weights` + update_init_info( + m, + init_info=f'Initialized by ' + f'user-defined `init_weights`' + f' in {m.__class__.__name__} ') + + self._is_init = True + else: + warnings.warn(f'init_weights of {self.__class__.__name__} has ' + f'been called more than once.') + + if is_top_level_module: + self._dump_init_info(logger_name) + + for sub_module in self.modules(): + del sub_module._params_init_info + + @master_only + def _dump_init_info(self, logger_name): + """Dump the initialization information to a file named + `initialization.log.json` in workdir. + + Args: + logger_name (str): The name of logger. + """ + + logger = get_logger(logger_name) + + with_file_handler = False + # dump the information to the logger file if there is a `FileHandler` + for handler in logger.handlers: + if isinstance(handler, FileHandler): + handler.stream.write( + 'Name of parameter - Initialization information\n') + for name, param in self.named_parameters(): + handler.stream.write( + f'\n{name} - {param.shape}: ' + f"\n{self._params_init_info[param]['init_info']} \n") + handler.stream.flush() + with_file_handler = True + if not with_file_handler: + for name, param in self.named_parameters(): + print_log( + f'\n{name} - {param.shape}: ' + f"\n{self._params_init_info[param]['init_info']} \n ", + logger=logger_name) + + def __repr__(self): + s = super().__repr__() + if self.init_cfg: + s += f'\ninit_cfg={self.init_cfg}' + return s + + +class Sequential(BaseModule, nn.Sequential): + """Sequential module in openmmlab. + + Args: + init_cfg (dict, optional): Initialization config dict. + """ + + def __init__(self, *args, init_cfg=None): + BaseModule.__init__(self, init_cfg) + nn.Sequential.__init__(self, *args) + + +class ModuleList(BaseModule, nn.ModuleList): + """ModuleList in openmmlab. + + Args: + modules (iterable, optional): an iterable of modules to add. + init_cfg (dict, optional): Initialization config dict. + """ + + def __init__(self, modules=None, init_cfg=None): + BaseModule.__init__(self, init_cfg) + nn.ModuleList.__init__(self, modules) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/base_runner.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/base_runner.py new file mode 100644 index 0000000..4928db0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/base_runner.py @@ -0,0 +1,542 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import logging +import os.path as osp +import warnings +from abc import ABCMeta, abstractmethod + +import torch +from torch.optim import Optimizer + +import annotator.uniformer.mmcv as mmcv +from ..parallel import is_module_wrapper +from .checkpoint import load_checkpoint +from .dist_utils import get_dist_info +from .hooks import HOOKS, Hook +from .log_buffer import LogBuffer +from .priority import Priority, get_priority +from .utils import get_time_str + + +class BaseRunner(metaclass=ABCMeta): + """The base class of Runner, a training helper for PyTorch. + + All subclasses should implement the following APIs: + + - ``run()`` + - ``train()`` + - ``val()`` + - ``save_checkpoint()`` + + Args: + model (:obj:`torch.nn.Module`): The model to be run. + batch_processor (callable): A callable method that process a data + batch. The interface of this method should be + `batch_processor(model, data, train_mode) -> dict` + optimizer (dict or :obj:`torch.optim.Optimizer`): It can be either an + optimizer (in most cases) or a dict of optimizers (in models that + requires more than one optimizer, e.g., GAN). + work_dir (str, optional): The working directory to save checkpoints + and logs. Defaults to None. + logger (:obj:`logging.Logger`): Logger used during training. + Defaults to None. (The default value is just for backward + compatibility) + meta (dict | None): A dict records some import information such as + environment info and seed, which will be logged in logger hook. + Defaults to None. + max_epochs (int, optional): Total training epochs. + max_iters (int, optional): Total training iterations. + """ + + def __init__(self, + model, + batch_processor=None, + optimizer=None, + work_dir=None, + logger=None, + meta=None, + max_iters=None, + max_epochs=None): + if batch_processor is not None: + if not callable(batch_processor): + raise TypeError('batch_processor must be callable, ' + f'but got {type(batch_processor)}') + warnings.warn('batch_processor is deprecated, please implement ' + 'train_step() and val_step() in the model instead.') + # raise an error is `batch_processor` is not None and + # `model.train_step()` exists. + if is_module_wrapper(model): + _model = model.module + else: + _model = model + if hasattr(_model, 'train_step') or hasattr(_model, 'val_step'): + raise RuntimeError( + 'batch_processor and model.train_step()/model.val_step() ' + 'cannot be both available.') + else: + assert hasattr(model, 'train_step') + + # check the type of `optimizer` + if isinstance(optimizer, dict): + for name, optim in optimizer.items(): + if not isinstance(optim, Optimizer): + raise TypeError( + f'optimizer must be a dict of torch.optim.Optimizers, ' + f'but optimizer["{name}"] is a {type(optim)}') + elif not isinstance(optimizer, Optimizer) and optimizer is not None: + raise TypeError( + f'optimizer must be a torch.optim.Optimizer object ' + f'or dict or None, but got {type(optimizer)}') + + # check the type of `logger` + if not isinstance(logger, logging.Logger): + raise TypeError(f'logger must be a logging.Logger object, ' + f'but got {type(logger)}') + + # check the type of `meta` + if meta is not None and not isinstance(meta, dict): + raise TypeError( + f'meta must be a dict or None, but got {type(meta)}') + + self.model = model + self.batch_processor = batch_processor + self.optimizer = optimizer + self.logger = logger + self.meta = meta + # create work_dir + if mmcv.is_str(work_dir): + self.work_dir = osp.abspath(work_dir) + mmcv.mkdir_or_exist(self.work_dir) + elif work_dir is None: + self.work_dir = None + else: + raise TypeError('"work_dir" must be a str or None') + + # get model name from the model class + if hasattr(self.model, 'module'): + self._model_name = self.model.module.__class__.__name__ + else: + self._model_name = self.model.__class__.__name__ + + self._rank, self._world_size = get_dist_info() + self.timestamp = get_time_str() + self.mode = None + self._hooks = [] + self._epoch = 0 + self._iter = 0 + self._inner_iter = 0 + + if max_epochs is not None and max_iters is not None: + raise ValueError( + 'Only one of `max_epochs` or `max_iters` can be set.') + + self._max_epochs = max_epochs + self._max_iters = max_iters + # TODO: Redesign LogBuffer, it is not flexible and elegant enough + self.log_buffer = LogBuffer() + + @property + def model_name(self): + """str: Name of the model, usually the module class name.""" + return self._model_name + + @property + def rank(self): + """int: Rank of current process. (distributed training)""" + return self._rank + + @property + def world_size(self): + """int: Number of processes participating in the job. + (distributed training)""" + return self._world_size + + @property + def hooks(self): + """list[:obj:`Hook`]: A list of registered hooks.""" + return self._hooks + + @property + def epoch(self): + """int: Current epoch.""" + return self._epoch + + @property + def iter(self): + """int: Current iteration.""" + return self._iter + + @property + def inner_iter(self): + """int: Iteration in an epoch.""" + return self._inner_iter + + @property + def max_epochs(self): + """int: Maximum training epochs.""" + return self._max_epochs + + @property + def max_iters(self): + """int: Maximum training iterations.""" + return self._max_iters + + @abstractmethod + def train(self): + pass + + @abstractmethod + def val(self): + pass + + @abstractmethod + def run(self, data_loaders, workflow, **kwargs): + pass + + @abstractmethod + def save_checkpoint(self, + out_dir, + filename_tmpl, + save_optimizer=True, + meta=None, + create_symlink=True): + pass + + def current_lr(self): + """Get current learning rates. + + Returns: + list[float] | dict[str, list[float]]: Current learning rates of all + param groups. If the runner has a dict of optimizers, this + method will return a dict. + """ + if isinstance(self.optimizer, torch.optim.Optimizer): + lr = [group['lr'] for group in self.optimizer.param_groups] + elif isinstance(self.optimizer, dict): + lr = dict() + for name, optim in self.optimizer.items(): + lr[name] = [group['lr'] for group in optim.param_groups] + else: + raise RuntimeError( + 'lr is not applicable because optimizer does not exist.') + return lr + + def current_momentum(self): + """Get current momentums. + + Returns: + list[float] | dict[str, list[float]]: Current momentums of all + param groups. If the runner has a dict of optimizers, this + method will return a dict. + """ + + def _get_momentum(optimizer): + momentums = [] + for group in optimizer.param_groups: + if 'momentum' in group.keys(): + momentums.append(group['momentum']) + elif 'betas' in group.keys(): + momentums.append(group['betas'][0]) + else: + momentums.append(0) + return momentums + + if self.optimizer is None: + raise RuntimeError( + 'momentum is not applicable because optimizer does not exist.') + elif isinstance(self.optimizer, torch.optim.Optimizer): + momentums = _get_momentum(self.optimizer) + elif isinstance(self.optimizer, dict): + momentums = dict() + for name, optim in self.optimizer.items(): + momentums[name] = _get_momentum(optim) + return momentums + + def register_hook(self, hook, priority='NORMAL'): + """Register a hook into the hook list. + + The hook will be inserted into a priority queue, with the specified + priority (See :class:`Priority` for details of priorities). + For hooks with the same priority, they will be triggered in the same + order as they are registered. + + Args: + hook (:obj:`Hook`): The hook to be registered. + priority (int or str or :obj:`Priority`): Hook priority. + Lower value means higher priority. + """ + assert isinstance(hook, Hook) + if hasattr(hook, 'priority'): + raise ValueError('"priority" is a reserved attribute for hooks') + priority = get_priority(priority) + hook.priority = priority + # insert the hook to a sorted list + inserted = False + for i in range(len(self._hooks) - 1, -1, -1): + if priority >= self._hooks[i].priority: + self._hooks.insert(i + 1, hook) + inserted = True + break + if not inserted: + self._hooks.insert(0, hook) + + def register_hook_from_cfg(self, hook_cfg): + """Register a hook from its cfg. + + Args: + hook_cfg (dict): Hook config. It should have at least keys 'type' + and 'priority' indicating its type and priority. + + Notes: + The specific hook class to register should not use 'type' and + 'priority' arguments during initialization. + """ + hook_cfg = hook_cfg.copy() + priority = hook_cfg.pop('priority', 'NORMAL') + hook = mmcv.build_from_cfg(hook_cfg, HOOKS) + self.register_hook(hook, priority=priority) + + def call_hook(self, fn_name): + """Call all hooks. + + Args: + fn_name (str): The function name in each hook to be called, such as + "before_train_epoch". + """ + for hook in self._hooks: + getattr(hook, fn_name)(self) + + def get_hook_info(self): + # Get hooks info in each stage + stage_hook_map = {stage: [] for stage in Hook.stages} + for hook in self.hooks: + try: + priority = Priority(hook.priority).name + except ValueError: + priority = hook.priority + classname = hook.__class__.__name__ + hook_info = f'({priority:<12}) {classname:<35}' + for trigger_stage in hook.get_triggered_stages(): + stage_hook_map[trigger_stage].append(hook_info) + + stage_hook_infos = [] + for stage in Hook.stages: + hook_infos = stage_hook_map[stage] + if len(hook_infos) > 0: + info = f'{stage}:\n' + info += '\n'.join(hook_infos) + info += '\n -------------------- ' + stage_hook_infos.append(info) + return '\n'.join(stage_hook_infos) + + def load_checkpoint(self, + filename, + map_location='cpu', + strict=False, + revise_keys=[(r'^module.', '')]): + return load_checkpoint( + self.model, + filename, + map_location, + strict, + self.logger, + revise_keys=revise_keys) + + def resume(self, + checkpoint, + resume_optimizer=True, + map_location='default'): + if map_location == 'default': + if torch.cuda.is_available(): + device_id = torch.cuda.current_device() + checkpoint = self.load_checkpoint( + checkpoint, + map_location=lambda storage, loc: storage.cuda(device_id)) + else: + checkpoint = self.load_checkpoint(checkpoint) + else: + checkpoint = self.load_checkpoint( + checkpoint, map_location=map_location) + + self._epoch = checkpoint['meta']['epoch'] + self._iter = checkpoint['meta']['iter'] + if self.meta is None: + self.meta = {} + self.meta.setdefault('hook_msgs', {}) + # load `last_ckpt`, `best_score`, `best_ckpt`, etc. for hook messages + self.meta['hook_msgs'].update(checkpoint['meta'].get('hook_msgs', {})) + + # Re-calculate the number of iterations when resuming + # models with different number of GPUs + if 'config' in checkpoint['meta']: + config = mmcv.Config.fromstring( + checkpoint['meta']['config'], file_format='.py') + previous_gpu_ids = config.get('gpu_ids', None) + if previous_gpu_ids and len(previous_gpu_ids) > 0 and len( + previous_gpu_ids) != self.world_size: + self._iter = int(self._iter * len(previous_gpu_ids) / + self.world_size) + self.logger.info('the iteration number is changed due to ' + 'change of GPU number') + + # resume meta information meta + self.meta = checkpoint['meta'] + + if 'optimizer' in checkpoint and resume_optimizer: + if isinstance(self.optimizer, Optimizer): + self.optimizer.load_state_dict(checkpoint['optimizer']) + elif isinstance(self.optimizer, dict): + for k in self.optimizer.keys(): + self.optimizer[k].load_state_dict( + checkpoint['optimizer'][k]) + else: + raise TypeError( + 'Optimizer should be dict or torch.optim.Optimizer ' + f'but got {type(self.optimizer)}') + + self.logger.info('resumed epoch %d, iter %d', self.epoch, self.iter) + + def register_lr_hook(self, lr_config): + if lr_config is None: + return + elif isinstance(lr_config, dict): + assert 'policy' in lr_config + policy_type = lr_config.pop('policy') + # If the type of policy is all in lower case, e.g., 'cyclic', + # then its first letter will be capitalized, e.g., to be 'Cyclic'. + # This is for the convenient usage of Lr updater. + # Since this is not applicable for ` + # CosineAnnealingLrUpdater`, + # the string will not be changed if it contains capital letters. + if policy_type == policy_type.lower(): + policy_type = policy_type.title() + hook_type = policy_type + 'LrUpdaterHook' + lr_config['type'] = hook_type + hook = mmcv.build_from_cfg(lr_config, HOOKS) + else: + hook = lr_config + self.register_hook(hook, priority='VERY_HIGH') + + def register_momentum_hook(self, momentum_config): + if momentum_config is None: + return + if isinstance(momentum_config, dict): + assert 'policy' in momentum_config + policy_type = momentum_config.pop('policy') + # If the type of policy is all in lower case, e.g., 'cyclic', + # then its first letter will be capitalized, e.g., to be 'Cyclic'. + # This is for the convenient usage of momentum updater. + # Since this is not applicable for + # `CosineAnnealingMomentumUpdater`, + # the string will not be changed if it contains capital letters. + if policy_type == policy_type.lower(): + policy_type = policy_type.title() + hook_type = policy_type + 'MomentumUpdaterHook' + momentum_config['type'] = hook_type + hook = mmcv.build_from_cfg(momentum_config, HOOKS) + else: + hook = momentum_config + self.register_hook(hook, priority='HIGH') + + def register_optimizer_hook(self, optimizer_config): + if optimizer_config is None: + return + if isinstance(optimizer_config, dict): + optimizer_config.setdefault('type', 'OptimizerHook') + hook = mmcv.build_from_cfg(optimizer_config, HOOKS) + else: + hook = optimizer_config + self.register_hook(hook, priority='ABOVE_NORMAL') + + def register_checkpoint_hook(self, checkpoint_config): + if checkpoint_config is None: + return + if isinstance(checkpoint_config, dict): + checkpoint_config.setdefault('type', 'CheckpointHook') + hook = mmcv.build_from_cfg(checkpoint_config, HOOKS) + else: + hook = checkpoint_config + self.register_hook(hook, priority='NORMAL') + + def register_logger_hooks(self, log_config): + if log_config is None: + return + log_interval = log_config['interval'] + for info in log_config['hooks']: + logger_hook = mmcv.build_from_cfg( + info, HOOKS, default_args=dict(interval=log_interval)) + self.register_hook(logger_hook, priority='VERY_LOW') + + def register_timer_hook(self, timer_config): + if timer_config is None: + return + if isinstance(timer_config, dict): + timer_config_ = copy.deepcopy(timer_config) + hook = mmcv.build_from_cfg(timer_config_, HOOKS) + else: + hook = timer_config + self.register_hook(hook, priority='LOW') + + def register_custom_hooks(self, custom_config): + if custom_config is None: + return + + if not isinstance(custom_config, list): + custom_config = [custom_config] + + for item in custom_config: + if isinstance(item, dict): + self.register_hook_from_cfg(item) + else: + self.register_hook(item, priority='NORMAL') + + def register_profiler_hook(self, profiler_config): + if profiler_config is None: + return + if isinstance(profiler_config, dict): + profiler_config.setdefault('type', 'ProfilerHook') + hook = mmcv.build_from_cfg(profiler_config, HOOKS) + else: + hook = profiler_config + self.register_hook(hook) + + def register_training_hooks(self, + lr_config, + optimizer_config=None, + checkpoint_config=None, + log_config=None, + momentum_config=None, + timer_config=dict(type='IterTimerHook'), + custom_hooks_config=None): + """Register default and custom hooks for training. + + Default and custom hooks include: + + +----------------------+-------------------------+ + | Hooks | Priority | + +======================+=========================+ + | LrUpdaterHook | VERY_HIGH (10) | + +----------------------+-------------------------+ + | MomentumUpdaterHook | HIGH (30) | + +----------------------+-------------------------+ + | OptimizerStepperHook | ABOVE_NORMAL (40) | + +----------------------+-------------------------+ + | CheckpointSaverHook | NORMAL (50) | + +----------------------+-------------------------+ + | IterTimerHook | LOW (70) | + +----------------------+-------------------------+ + | LoggerHook(s) | VERY_LOW (90) | + +----------------------+-------------------------+ + | CustomHook(s) | defaults to NORMAL (50) | + +----------------------+-------------------------+ + + If custom hooks have same priority with default hooks, custom hooks + will be triggered after default hooks. + """ + self.register_lr_hook(lr_config) + self.register_momentum_hook(momentum_config) + self.register_optimizer_hook(optimizer_config) + self.register_checkpoint_hook(checkpoint_config) + self.register_timer_hook(timer_config) + self.register_logger_hooks(log_config) + self.register_custom_hooks(custom_hooks_config) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/builder.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/builder.py new file mode 100644 index 0000000..77c96ba --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/builder.py @@ -0,0 +1,24 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy + +from ..utils import Registry + +RUNNERS = Registry('runner') +RUNNER_BUILDERS = Registry('runner builder') + + +def build_runner_constructor(cfg): + return RUNNER_BUILDERS.build(cfg) + + +def build_runner(cfg, default_args=None): + runner_cfg = copy.deepcopy(cfg) + constructor_type = runner_cfg.pop('constructor', + 'DefaultRunnerConstructor') + runner_constructor = build_runner_constructor( + dict( + type=constructor_type, + runner_cfg=runner_cfg, + default_args=default_args)) + runner = runner_constructor() + return runner diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/checkpoint.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/checkpoint.py new file mode 100644 index 0000000..b29ca32 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/checkpoint.py @@ -0,0 +1,707 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import io +import os +import os.path as osp +import pkgutil +import re +import time +import warnings +from collections import OrderedDict +from importlib import import_module +from tempfile import TemporaryDirectory + +import torch +import torchvision +from torch.optim import Optimizer +from torch.utils import model_zoo + +import annotator.uniformer.mmcv as mmcv +from ..fileio import FileClient +from ..fileio import load as load_file +from ..parallel import is_module_wrapper +from ..utils import mkdir_or_exist +from .dist_utils import get_dist_info + +ENV_MMCV_HOME = 'MMCV_HOME' +ENV_XDG_CACHE_HOME = 'XDG_CACHE_HOME' +DEFAULT_CACHE_DIR = '~/.cache' + + +def _get_mmcv_home(): + mmcv_home = os.path.expanduser( + os.getenv( + ENV_MMCV_HOME, + os.path.join( + os.getenv(ENV_XDG_CACHE_HOME, DEFAULT_CACHE_DIR), 'mmcv'))) + + mkdir_or_exist(mmcv_home) + return mmcv_home + + +def load_state_dict(module, state_dict, strict=False, logger=None): + """Load state_dict to a module. + + This method is modified from :meth:`torch.nn.Module.load_state_dict`. + Default value for ``strict`` is set to ``False`` and the message for + param mismatch will be shown even if strict is False. + + Args: + module (Module): Module that receives the state_dict. + state_dict (OrderedDict): Weights. + strict (bool): whether to strictly enforce that the keys + in :attr:`state_dict` match the keys returned by this module's + :meth:`~torch.nn.Module.state_dict` function. Default: ``False``. + logger (:obj:`logging.Logger`, optional): Logger to log the error + message. If not specified, print function will be used. + """ + unexpected_keys = [] + all_missing_keys = [] + err_msg = [] + + metadata = getattr(state_dict, '_metadata', None) + state_dict = state_dict.copy() + if metadata is not None: + state_dict._metadata = metadata + + # use _load_from_state_dict to enable checkpoint version control + def load(module, prefix=''): + # recursively check parallel module in case that the model has a + # complicated structure, e.g., nn.Module(nn.Module(DDP)) + if is_module_wrapper(module): + module = module.module + local_metadata = {} if metadata is None else metadata.get( + prefix[:-1], {}) + module._load_from_state_dict(state_dict, prefix, local_metadata, True, + all_missing_keys, unexpected_keys, + err_msg) + for name, child in module._modules.items(): + if child is not None: + load(child, prefix + name + '.') + + load(module) + load = None # break load->load reference cycle + + # ignore "num_batches_tracked" of BN layers + missing_keys = [ + key for key in all_missing_keys if 'num_batches_tracked' not in key + ] + + if unexpected_keys: + err_msg.append('unexpected key in source ' + f'state_dict: {", ".join(unexpected_keys)}\n') + if missing_keys: + err_msg.append( + f'missing keys in source state_dict: {", ".join(missing_keys)}\n') + + rank, _ = get_dist_info() + if len(err_msg) > 0 and rank == 0: + err_msg.insert( + 0, 'The model and loaded state dict do not match exactly\n') + err_msg = '\n'.join(err_msg) + if strict: + raise RuntimeError(err_msg) + elif logger is not None: + logger.warning(err_msg) + else: + print(err_msg) + + +def get_torchvision_models(): + model_urls = dict() + for _, name, ispkg in pkgutil.walk_packages(torchvision.models.__path__): + if ispkg: + continue + _zoo = import_module(f'torchvision.models.{name}') + if hasattr(_zoo, 'model_urls'): + _urls = getattr(_zoo, 'model_urls') + model_urls.update(_urls) + return model_urls + + +def get_external_models(): + mmcv_home = _get_mmcv_home() + default_json_path = osp.join(mmcv.__path__[0], 'model_zoo/open_mmlab.json') + default_urls = load_file(default_json_path) + assert isinstance(default_urls, dict) + external_json_path = osp.join(mmcv_home, 'open_mmlab.json') + if osp.exists(external_json_path): + external_urls = load_file(external_json_path) + assert isinstance(external_urls, dict) + default_urls.update(external_urls) + + return default_urls + + +def get_mmcls_models(): + mmcls_json_path = osp.join(mmcv.__path__[0], 'model_zoo/mmcls.json') + mmcls_urls = load_file(mmcls_json_path) + + return mmcls_urls + + +def get_deprecated_model_names(): + deprecate_json_path = osp.join(mmcv.__path__[0], + 'model_zoo/deprecated.json') + deprecate_urls = load_file(deprecate_json_path) + assert isinstance(deprecate_urls, dict) + + return deprecate_urls + + +def _process_mmcls_checkpoint(checkpoint): + state_dict = checkpoint['state_dict'] + new_state_dict = OrderedDict() + for k, v in state_dict.items(): + if k.startswith('backbone.'): + new_state_dict[k[9:]] = v + new_checkpoint = dict(state_dict=new_state_dict) + + return new_checkpoint + + +class CheckpointLoader: + """A general checkpoint loader to manage all schemes.""" + + _schemes = {} + + @classmethod + def _register_scheme(cls, prefixes, loader, force=False): + if isinstance(prefixes, str): + prefixes = [prefixes] + else: + assert isinstance(prefixes, (list, tuple)) + for prefix in prefixes: + if (prefix not in cls._schemes) or force: + cls._schemes[prefix] = loader + else: + raise KeyError( + f'{prefix} is already registered as a loader backend, ' + 'add "force=True" if you want to override it') + # sort, longer prefixes take priority + cls._schemes = OrderedDict( + sorted(cls._schemes.items(), key=lambda t: t[0], reverse=True)) + + @classmethod + def register_scheme(cls, prefixes, loader=None, force=False): + """Register a loader to CheckpointLoader. + + This method can be used as a normal class method or a decorator. + + Args: + prefixes (str or list[str] or tuple[str]): + The prefix of the registered loader. + loader (function, optional): The loader function to be registered. + When this method is used as a decorator, loader is None. + Defaults to None. + force (bool, optional): Whether to override the loader + if the prefix has already been registered. Defaults to False. + """ + + if loader is not None: + cls._register_scheme(prefixes, loader, force=force) + return + + def _register(loader_cls): + cls._register_scheme(prefixes, loader_cls, force=force) + return loader_cls + + return _register + + @classmethod + def _get_checkpoint_loader(cls, path): + """Finds a loader that supports the given path. Falls back to the local + loader if no other loader is found. + + Args: + path (str): checkpoint path + + Returns: + loader (function): checkpoint loader + """ + + for p in cls._schemes: + if path.startswith(p): + return cls._schemes[p] + + @classmethod + def load_checkpoint(cls, filename, map_location=None, logger=None): + """load checkpoint through URL scheme path. + + Args: + filename (str): checkpoint file name with given prefix + map_location (str, optional): Same as :func:`torch.load`. + Default: None + logger (:mod:`logging.Logger`, optional): The logger for message. + Default: None + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + + checkpoint_loader = cls._get_checkpoint_loader(filename) + class_name = checkpoint_loader.__name__ + mmcv.print_log( + f'load checkpoint from {class_name[10:]} path: {filename}', logger) + return checkpoint_loader(filename, map_location) + + +@CheckpointLoader.register_scheme(prefixes='') +def load_from_local(filename, map_location): + """load checkpoint by local file path. + + Args: + filename (str): local checkpoint file path + map_location (str, optional): Same as :func:`torch.load`. + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + + if not osp.isfile(filename): + raise IOError(f'{filename} is not a checkpoint file') + checkpoint = torch.load(filename, map_location=map_location) + return checkpoint + + +@CheckpointLoader.register_scheme(prefixes=('http://', 'https://')) +def load_from_http(filename, map_location=None, model_dir=None): + """load checkpoint through HTTP or HTTPS scheme path. In distributed + setting, this function only download checkpoint at local rank 0. + + Args: + filename (str): checkpoint file path with modelzoo or + torchvision prefix + map_location (str, optional): Same as :func:`torch.load`. + model_dir (string, optional): directory in which to save the object, + Default: None + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + rank, world_size = get_dist_info() + rank = int(os.environ.get('LOCAL_RANK', rank)) + if rank == 0: + checkpoint = model_zoo.load_url( + filename, model_dir=model_dir, map_location=map_location) + if world_size > 1: + torch.distributed.barrier() + if rank > 0: + checkpoint = model_zoo.load_url( + filename, model_dir=model_dir, map_location=map_location) + return checkpoint + + +@CheckpointLoader.register_scheme(prefixes='pavi://') +def load_from_pavi(filename, map_location=None): + """load checkpoint through the file path prefixed with pavi. In distributed + setting, this function download ckpt at all ranks to different temporary + directories. + + Args: + filename (str): checkpoint file path with pavi prefix + map_location (str, optional): Same as :func:`torch.load`. + Default: None + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + assert filename.startswith('pavi://'), \ + f'Expected filename startswith `pavi://`, but get {filename}' + model_path = filename[7:] + + try: + from pavi import modelcloud + except ImportError: + raise ImportError( + 'Please install pavi to load checkpoint from modelcloud.') + + model = modelcloud.get(model_path) + with TemporaryDirectory() as tmp_dir: + downloaded_file = osp.join(tmp_dir, model.name) + model.download(downloaded_file) + checkpoint = torch.load(downloaded_file, map_location=map_location) + return checkpoint + + +@CheckpointLoader.register_scheme(prefixes='s3://') +def load_from_ceph(filename, map_location=None, backend='petrel'): + """load checkpoint through the file path prefixed with s3. In distributed + setting, this function download ckpt at all ranks to different temporary + directories. + + Args: + filename (str): checkpoint file path with s3 prefix + map_location (str, optional): Same as :func:`torch.load`. + backend (str, optional): The storage backend type. Options are 'ceph', + 'petrel'. Default: 'petrel'. + + .. warning:: + :class:`mmcv.fileio.file_client.CephBackend` will be deprecated, + please use :class:`mmcv.fileio.file_client.PetrelBackend` instead. + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + allowed_backends = ['ceph', 'petrel'] + if backend not in allowed_backends: + raise ValueError(f'Load from Backend {backend} is not supported.') + + if backend == 'ceph': + warnings.warn( + 'CephBackend will be deprecated, please use PetrelBackend instead') + + # CephClient and PetrelBackend have the same prefix 's3://' and the latter + # will be chosen as default. If PetrelBackend can not be instantiated + # successfully, the CephClient will be chosen. + try: + file_client = FileClient(backend=backend) + except ImportError: + allowed_backends.remove(backend) + file_client = FileClient(backend=allowed_backends[0]) + + with io.BytesIO(file_client.get(filename)) as buffer: + checkpoint = torch.load(buffer, map_location=map_location) + return checkpoint + + +@CheckpointLoader.register_scheme(prefixes=('modelzoo://', 'torchvision://')) +def load_from_torchvision(filename, map_location=None): + """load checkpoint through the file path prefixed with modelzoo or + torchvision. + + Args: + filename (str): checkpoint file path with modelzoo or + torchvision prefix + map_location (str, optional): Same as :func:`torch.load`. + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + model_urls = get_torchvision_models() + if filename.startswith('modelzoo://'): + warnings.warn('The URL scheme of "modelzoo://" is deprecated, please ' + 'use "torchvision://" instead') + model_name = filename[11:] + else: + model_name = filename[14:] + return load_from_http(model_urls[model_name], map_location=map_location) + + +@CheckpointLoader.register_scheme(prefixes=('open-mmlab://', 'openmmlab://')) +def load_from_openmmlab(filename, map_location=None): + """load checkpoint through the file path prefixed with open-mmlab or + openmmlab. + + Args: + filename (str): checkpoint file path with open-mmlab or + openmmlab prefix + map_location (str, optional): Same as :func:`torch.load`. + Default: None + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + + model_urls = get_external_models() + prefix_str = 'open-mmlab://' + if filename.startswith(prefix_str): + model_name = filename[13:] + else: + model_name = filename[12:] + prefix_str = 'openmmlab://' + + deprecated_urls = get_deprecated_model_names() + if model_name in deprecated_urls: + warnings.warn(f'{prefix_str}{model_name} is deprecated in favor ' + f'of {prefix_str}{deprecated_urls[model_name]}') + model_name = deprecated_urls[model_name] + model_url = model_urls[model_name] + # check if is url + if model_url.startswith(('http://', 'https://')): + checkpoint = load_from_http(model_url, map_location=map_location) + else: + filename = osp.join(_get_mmcv_home(), model_url) + if not osp.isfile(filename): + raise IOError(f'{filename} is not a checkpoint file') + checkpoint = torch.load(filename, map_location=map_location) + return checkpoint + + +@CheckpointLoader.register_scheme(prefixes='mmcls://') +def load_from_mmcls(filename, map_location=None): + """load checkpoint through the file path prefixed with mmcls. + + Args: + filename (str): checkpoint file path with mmcls prefix + map_location (str, optional): Same as :func:`torch.load`. + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + + model_urls = get_mmcls_models() + model_name = filename[8:] + checkpoint = load_from_http( + model_urls[model_name], map_location=map_location) + checkpoint = _process_mmcls_checkpoint(checkpoint) + return checkpoint + + +def _load_checkpoint(filename, map_location=None, logger=None): + """Load checkpoint from somewhere (modelzoo, file, url). + + Args: + filename (str): Accept local filepath, URL, ``torchvision://xxx``, + ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for + details. + map_location (str, optional): Same as :func:`torch.load`. + Default: None. + logger (:mod:`logging.Logger`, optional): The logger for error message. + Default: None + + Returns: + dict or OrderedDict: The loaded checkpoint. It can be either an + OrderedDict storing model weights or a dict containing other + information, which depends on the checkpoint. + """ + return CheckpointLoader.load_checkpoint(filename, map_location, logger) + + +def _load_checkpoint_with_prefix(prefix, filename, map_location=None): + """Load partial pretrained model with specific prefix. + + Args: + prefix (str): The prefix of sub-module. + filename (str): Accept local filepath, URL, ``torchvision://xxx``, + ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for + details. + map_location (str | None): Same as :func:`torch.load`. Default: None. + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + + checkpoint = _load_checkpoint(filename, map_location=map_location) + + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + else: + state_dict = checkpoint + if not prefix.endswith('.'): + prefix += '.' + prefix_len = len(prefix) + + state_dict = { + k[prefix_len:]: v + for k, v in state_dict.items() if k.startswith(prefix) + } + + assert state_dict, f'{prefix} is not in the pretrained model' + return state_dict + + +def load_checkpoint(model, + filename, + map_location=None, + strict=False, + logger=None, + revise_keys=[(r'^module\.', '')]): + """Load checkpoint from a file or URI. + + Args: + model (Module): Module to load checkpoint. + filename (str): Accept local filepath, URL, ``torchvision://xxx``, + ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for + details. + map_location (str): Same as :func:`torch.load`. + strict (bool): Whether to allow different params for the model and + checkpoint. + logger (:mod:`logging.Logger` or None): The logger for error message. + revise_keys (list): A list of customized keywords to modify the + state_dict in checkpoint. Each item is a (pattern, replacement) + pair of the regular expression operations. Default: strip + the prefix 'module.' by [(r'^module\\.', '')]. + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + checkpoint = _load_checkpoint(filename, map_location, logger) + # OrderedDict is a subclass of dict + if not isinstance(checkpoint, dict): + raise RuntimeError( + f'No state_dict found in checkpoint file {filename}') + # get state_dict from checkpoint + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + else: + state_dict = checkpoint + + # strip prefix of state_dict + metadata = getattr(state_dict, '_metadata', OrderedDict()) + for p, r in revise_keys: + state_dict = OrderedDict( + {re.sub(p, r, k): v + for k, v in state_dict.items()}) + # Keep metadata in state_dict + state_dict._metadata = metadata + + # load state_dict + load_state_dict(model, state_dict, strict, logger) + return checkpoint + + +def weights_to_cpu(state_dict): + """Copy a model state_dict to cpu. + + Args: + state_dict (OrderedDict): Model weights on GPU. + + Returns: + OrderedDict: Model weights on GPU. + """ + state_dict_cpu = OrderedDict() + for key, val in state_dict.items(): + state_dict_cpu[key] = val.cpu() + # Keep metadata in state_dict + state_dict_cpu._metadata = getattr(state_dict, '_metadata', OrderedDict()) + return state_dict_cpu + + +def _save_to_state_dict(module, destination, prefix, keep_vars): + """Saves module state to `destination` dictionary. + + This method is modified from :meth:`torch.nn.Module._save_to_state_dict`. + + Args: + module (nn.Module): The module to generate state_dict. + destination (dict): A dict where state will be stored. + prefix (str): The prefix for parameters and buffers used in this + module. + """ + for name, param in module._parameters.items(): + if param is not None: + destination[prefix + name] = param if keep_vars else param.detach() + for name, buf in module._buffers.items(): + # remove check of _non_persistent_buffers_set to allow nn.BatchNorm2d + if buf is not None: + destination[prefix + name] = buf if keep_vars else buf.detach() + + +def get_state_dict(module, destination=None, prefix='', keep_vars=False): + """Returns a dictionary containing a whole state of the module. + + Both parameters and persistent buffers (e.g. running averages) are + included. Keys are corresponding parameter and buffer names. + + This method is modified from :meth:`torch.nn.Module.state_dict` to + recursively check parallel module in case that the model has a complicated + structure, e.g., nn.Module(nn.Module(DDP)). + + Args: + module (nn.Module): The module to generate state_dict. + destination (OrderedDict): Returned dict for the state of the + module. + prefix (str): Prefix of the key. + keep_vars (bool): Whether to keep the variable property of the + parameters. Default: False. + + Returns: + dict: A dictionary containing a whole state of the module. + """ + # recursively check parallel module in case that the model has a + # complicated structure, e.g., nn.Module(nn.Module(DDP)) + if is_module_wrapper(module): + module = module.module + + # below is the same as torch.nn.Module.state_dict() + if destination is None: + destination = OrderedDict() + destination._metadata = OrderedDict() + destination._metadata[prefix[:-1]] = local_metadata = dict( + version=module._version) + _save_to_state_dict(module, destination, prefix, keep_vars) + for name, child in module._modules.items(): + if child is not None: + get_state_dict( + child, destination, prefix + name + '.', keep_vars=keep_vars) + for hook in module._state_dict_hooks.values(): + hook_result = hook(module, destination, prefix, local_metadata) + if hook_result is not None: + destination = hook_result + return destination + + +def save_checkpoint(model, + filename, + optimizer=None, + meta=None, + file_client_args=None): + """Save checkpoint to file. + + The checkpoint will have 3 fields: ``meta``, ``state_dict`` and + ``optimizer``. By default ``meta`` will contain version and time info. + + Args: + model (Module): Module whose params are to be saved. + filename (str): Checkpoint filename. + optimizer (:obj:`Optimizer`, optional): Optimizer to be saved. + meta (dict, optional): Metadata to be saved in checkpoint. + file_client_args (dict, optional): Arguments to instantiate a + FileClient. See :class:`mmcv.fileio.FileClient` for details. + Default: None. + `New in version 1.3.16.` + """ + if meta is None: + meta = {} + elif not isinstance(meta, dict): + raise TypeError(f'meta must be a dict or None, but got {type(meta)}') + meta.update(mmcv_version=mmcv.__version__, time=time.asctime()) + + if is_module_wrapper(model): + model = model.module + + if hasattr(model, 'CLASSES') and model.CLASSES is not None: + # save class name to the meta + meta.update(CLASSES=model.CLASSES) + + checkpoint = { + 'meta': meta, + 'state_dict': weights_to_cpu(get_state_dict(model)) + } + # save optimizer state dict in the checkpoint + if isinstance(optimizer, Optimizer): + checkpoint['optimizer'] = optimizer.state_dict() + elif isinstance(optimizer, dict): + checkpoint['optimizer'] = {} + for name, optim in optimizer.items(): + checkpoint['optimizer'][name] = optim.state_dict() + + if filename.startswith('pavi://'): + if file_client_args is not None: + raise ValueError( + 'file_client_args should be "None" if filename starts with' + f'"pavi://", but got {file_client_args}') + try: + from pavi import modelcloud + from pavi import exception + except ImportError: + raise ImportError( + 'Please install pavi to load checkpoint from modelcloud.') + model_path = filename[7:] + root = modelcloud.Folder() + model_dir, model_name = osp.split(model_path) + try: + model = modelcloud.get(model_dir) + except exception.NodeNotFoundError: + model = root.create_training_model(model_dir) + with TemporaryDirectory() as tmp_dir: + checkpoint_file = osp.join(tmp_dir, model_name) + with open(checkpoint_file, 'wb') as f: + torch.save(checkpoint, f) + f.flush() + model.create_file(checkpoint_file, name=model_name) + else: + file_client = FileClient.infer_client(file_client_args, filename) + with io.BytesIO() as f: + torch.save(checkpoint, f) + file_client.put(f.getvalue(), filename) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/default_constructor.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/default_constructor.py new file mode 100644 index 0000000..3f1f5b4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/default_constructor.py @@ -0,0 +1,44 @@ +from .builder import RUNNER_BUILDERS, RUNNERS + + +@RUNNER_BUILDERS.register_module() +class DefaultRunnerConstructor: + """Default constructor for runners. + + Custom existing `Runner` like `EpocBasedRunner` though `RunnerConstructor`. + For example, We can inject some new properties and functions for `Runner`. + + Example: + >>> from annotator.uniformer.mmcv.runner import RUNNER_BUILDERS, build_runner + >>> # Define a new RunnerReconstructor + >>> @RUNNER_BUILDERS.register_module() + >>> class MyRunnerConstructor: + ... def __init__(self, runner_cfg, default_args=None): + ... if not isinstance(runner_cfg, dict): + ... raise TypeError('runner_cfg should be a dict', + ... f'but got {type(runner_cfg)}') + ... self.runner_cfg = runner_cfg + ... self.default_args = default_args + ... + ... def __call__(self): + ... runner = RUNNERS.build(self.runner_cfg, + ... default_args=self.default_args) + ... # Add new properties for existing runner + ... runner.my_name = 'my_runner' + ... runner.my_function = lambda self: print(self.my_name) + ... ... + >>> # build your runner + >>> runner_cfg = dict(type='EpochBasedRunner', max_epochs=40, + ... constructor='MyRunnerConstructor') + >>> runner = build_runner(runner_cfg) + """ + + def __init__(self, runner_cfg, default_args=None): + if not isinstance(runner_cfg, dict): + raise TypeError('runner_cfg should be a dict', + f'but got {type(runner_cfg)}') + self.runner_cfg = runner_cfg + self.default_args = default_args + + def __call__(self): + return RUNNERS.build(self.runner_cfg, default_args=self.default_args) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/dist_utils.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/dist_utils.py new file mode 100644 index 0000000..d3a1ef3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/dist_utils.py @@ -0,0 +1,164 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import functools +import os +import subprocess +from collections import OrderedDict + +import torch +import torch.multiprocessing as mp +from torch import distributed as dist +from torch._utils import (_flatten_dense_tensors, _take_tensors, + _unflatten_dense_tensors) + + +def init_dist(launcher, backend='nccl', **kwargs): + if mp.get_start_method(allow_none=True) is None: + mp.set_start_method('spawn') + if launcher == 'pytorch': + _init_dist_pytorch(backend, **kwargs) + elif launcher == 'mpi': + _init_dist_mpi(backend, **kwargs) + elif launcher == 'slurm': + _init_dist_slurm(backend, **kwargs) + else: + raise ValueError(f'Invalid launcher type: {launcher}') + + +def _init_dist_pytorch(backend, **kwargs): + # TODO: use local_rank instead of rank % num_gpus + rank = int(os.environ['RANK']) + num_gpus = torch.cuda.device_count() + torch.cuda.set_device(rank % num_gpus) + dist.init_process_group(backend=backend, **kwargs) + + +def _init_dist_mpi(backend, **kwargs): + # TODO: use local_rank instead of rank % num_gpus + rank = int(os.environ['OMPI_COMM_WORLD_RANK']) + num_gpus = torch.cuda.device_count() + torch.cuda.set_device(rank % num_gpus) + dist.init_process_group(backend=backend, **kwargs) + + +def _init_dist_slurm(backend, port=None): + """Initialize slurm distributed training environment. + + If argument ``port`` is not specified, then the master port will be system + environment variable ``MASTER_PORT``. If ``MASTER_PORT`` is not in system + environment variable, then a default port ``29500`` will be used. + + Args: + backend (str): Backend of torch.distributed. + port (int, optional): Master port. Defaults to None. + """ + proc_id = int(os.environ['SLURM_PROCID']) + ntasks = int(os.environ['SLURM_NTASKS']) + node_list = os.environ['SLURM_NODELIST'] + num_gpus = torch.cuda.device_count() + torch.cuda.set_device(proc_id % num_gpus) + addr = subprocess.getoutput( + f'scontrol show hostname {node_list} | head -n1') + # specify master port + if port is not None: + os.environ['MASTER_PORT'] = str(port) + elif 'MASTER_PORT' in os.environ: + pass # use MASTER_PORT in the environment variable + else: + # 29500 is torch.distributed default port + os.environ['MASTER_PORT'] = '29500' + # use MASTER_ADDR in the environment variable if it already exists + if 'MASTER_ADDR' not in os.environ: + os.environ['MASTER_ADDR'] = addr + os.environ['WORLD_SIZE'] = str(ntasks) + os.environ['LOCAL_RANK'] = str(proc_id % num_gpus) + os.environ['RANK'] = str(proc_id) + dist.init_process_group(backend=backend) + + +def get_dist_info(): + if dist.is_available() and dist.is_initialized(): + rank = dist.get_rank() + world_size = dist.get_world_size() + else: + rank = 0 + world_size = 1 + return rank, world_size + + +def master_only(func): + + @functools.wraps(func) + def wrapper(*args, **kwargs): + rank, _ = get_dist_info() + if rank == 0: + return func(*args, **kwargs) + + return wrapper + + +def allreduce_params(params, coalesce=True, bucket_size_mb=-1): + """Allreduce parameters. + + Args: + params (list[torch.Parameters]): List of parameters or buffers of a + model. + coalesce (bool, optional): Whether allreduce parameters as a whole. + Defaults to True. + bucket_size_mb (int, optional): Size of bucket, the unit is MB. + Defaults to -1. + """ + _, world_size = get_dist_info() + if world_size == 1: + return + params = [param.data for param in params] + if coalesce: + _allreduce_coalesced(params, world_size, bucket_size_mb) + else: + for tensor in params: + dist.all_reduce(tensor.div_(world_size)) + + +def allreduce_grads(params, coalesce=True, bucket_size_mb=-1): + """Allreduce gradients. + + Args: + params (list[torch.Parameters]): List of parameters of a model + coalesce (bool, optional): Whether allreduce parameters as a whole. + Defaults to True. + bucket_size_mb (int, optional): Size of bucket, the unit is MB. + Defaults to -1. + """ + grads = [ + param.grad.data for param in params + if param.requires_grad and param.grad is not None + ] + _, world_size = get_dist_info() + if world_size == 1: + return + if coalesce: + _allreduce_coalesced(grads, world_size, bucket_size_mb) + else: + for tensor in grads: + dist.all_reduce(tensor.div_(world_size)) + + +def _allreduce_coalesced(tensors, world_size, bucket_size_mb=-1): + if bucket_size_mb > 0: + bucket_size_bytes = bucket_size_mb * 1024 * 1024 + buckets = _take_tensors(tensors, bucket_size_bytes) + else: + buckets = OrderedDict() + for tensor in tensors: + tp = tensor.type() + if tp not in buckets: + buckets[tp] = [] + buckets[tp].append(tensor) + buckets = buckets.values() + + for bucket in buckets: + flat_tensors = _flatten_dense_tensors(bucket) + dist.all_reduce(flat_tensors) + flat_tensors.div_(world_size) + for tensor, synced in zip( + bucket, _unflatten_dense_tensors(flat_tensors, bucket)): + tensor.copy_(synced) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/epoch_based_runner.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/epoch_based_runner.py new file mode 100644 index 0000000..766a9ce --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/epoch_based_runner.py @@ -0,0 +1,187 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import platform +import shutil +import time +import warnings + +import torch + +import annotator.uniformer.mmcv as mmcv +from .base_runner import BaseRunner +from .builder import RUNNERS +from .checkpoint import save_checkpoint +from .utils import get_host_info + + +@RUNNERS.register_module() +class EpochBasedRunner(BaseRunner): + """Epoch-based Runner. + + This runner train models epoch by epoch. + """ + + def run_iter(self, data_batch, train_mode, **kwargs): + if self.batch_processor is not None: + outputs = self.batch_processor( + self.model, data_batch, train_mode=train_mode, **kwargs) + elif train_mode: + outputs = self.model.train_step(data_batch, self.optimizer, + **kwargs) + else: + outputs = self.model.val_step(data_batch, self.optimizer, **kwargs) + if not isinstance(outputs, dict): + raise TypeError('"batch_processor()" or "model.train_step()"' + 'and "model.val_step()" must return a dict') + if 'log_vars' in outputs: + self.log_buffer.update(outputs['log_vars'], outputs['num_samples']) + self.outputs = outputs + + def train(self, data_loader, **kwargs): + self.model.train() + self.mode = 'train' + self.data_loader = data_loader + self._max_iters = self._max_epochs * len(self.data_loader) + self.call_hook('before_train_epoch') + time.sleep(2) # Prevent possible deadlock during epoch transition + for i, data_batch in enumerate(self.data_loader): + self._inner_iter = i + self.call_hook('before_train_iter') + self.run_iter(data_batch, train_mode=True, **kwargs) + self.call_hook('after_train_iter') + self._iter += 1 + + self.call_hook('after_train_epoch') + self._epoch += 1 + + @torch.no_grad() + def val(self, data_loader, **kwargs): + self.model.eval() + self.mode = 'val' + self.data_loader = data_loader + self.call_hook('before_val_epoch') + time.sleep(2) # Prevent possible deadlock during epoch transition + for i, data_batch in enumerate(self.data_loader): + self._inner_iter = i + self.call_hook('before_val_iter') + self.run_iter(data_batch, train_mode=False) + self.call_hook('after_val_iter') + + self.call_hook('after_val_epoch') + + def run(self, data_loaders, workflow, max_epochs=None, **kwargs): + """Start running. + + Args: + data_loaders (list[:obj:`DataLoader`]): Dataloaders for training + and validation. + workflow (list[tuple]): A list of (phase, epochs) to specify the + running order and epochs. E.g, [('train', 2), ('val', 1)] means + running 2 epochs for training and 1 epoch for validation, + iteratively. + """ + assert isinstance(data_loaders, list) + assert mmcv.is_list_of(workflow, tuple) + assert len(data_loaders) == len(workflow) + if max_epochs is not None: + warnings.warn( + 'setting max_epochs in run is deprecated, ' + 'please set max_epochs in runner_config', DeprecationWarning) + self._max_epochs = max_epochs + + assert self._max_epochs is not None, ( + 'max_epochs must be specified during instantiation') + + for i, flow in enumerate(workflow): + mode, epochs = flow + if mode == 'train': + self._max_iters = self._max_epochs * len(data_loaders[i]) + break + + work_dir = self.work_dir if self.work_dir is not None else 'NONE' + self.logger.info('Start running, host: %s, work_dir: %s', + get_host_info(), work_dir) + self.logger.info('Hooks will be executed in the following order:\n%s', + self.get_hook_info()) + self.logger.info('workflow: %s, max: %d epochs', workflow, + self._max_epochs) + self.call_hook('before_run') + + while self.epoch < self._max_epochs: + for i, flow in enumerate(workflow): + mode, epochs = flow + if isinstance(mode, str): # self.train() + if not hasattr(self, mode): + raise ValueError( + f'runner has no method named "{mode}" to run an ' + 'epoch') + epoch_runner = getattr(self, mode) + else: + raise TypeError( + 'mode in workflow must be a str, but got {}'.format( + type(mode))) + + for _ in range(epochs): + if mode == 'train' and self.epoch >= self._max_epochs: + break + epoch_runner(data_loaders[i], **kwargs) + + time.sleep(1) # wait for some hooks like loggers to finish + self.call_hook('after_run') + + def save_checkpoint(self, + out_dir, + filename_tmpl='epoch_{}.pth', + save_optimizer=True, + meta=None, + create_symlink=True): + """Save the checkpoint. + + Args: + out_dir (str): The directory that checkpoints are saved. + filename_tmpl (str, optional): The checkpoint filename template, + which contains a placeholder for the epoch number. + Defaults to 'epoch_{}.pth'. + save_optimizer (bool, optional): Whether to save the optimizer to + the checkpoint. Defaults to True. + meta (dict, optional): The meta information to be saved in the + checkpoint. Defaults to None. + create_symlink (bool, optional): Whether to create a symlink + "latest.pth" to point to the latest checkpoint. + Defaults to True. + """ + if meta is None: + meta = {} + elif not isinstance(meta, dict): + raise TypeError( + f'meta should be a dict or None, but got {type(meta)}') + if self.meta is not None: + meta.update(self.meta) + # Note: meta.update(self.meta) should be done before + # meta.update(epoch=self.epoch + 1, iter=self.iter) otherwise + # there will be problems with resumed checkpoints. + # More details in https://github.com/open-mmlab/mmcv/pull/1108 + meta.update(epoch=self.epoch + 1, iter=self.iter) + + filename = filename_tmpl.format(self.epoch + 1) + filepath = osp.join(out_dir, filename) + optimizer = self.optimizer if save_optimizer else None + save_checkpoint(self.model, filepath, optimizer=optimizer, meta=meta) + # in some environments, `os.symlink` is not supported, you may need to + # set `create_symlink` to False + if create_symlink: + dst_file = osp.join(out_dir, 'latest.pth') + if platform.system() != 'Windows': + mmcv.symlink(filename, dst_file) + else: + shutil.copy(filepath, dst_file) + + +@RUNNERS.register_module() +class Runner(EpochBasedRunner): + """Deprecated name of EpochBasedRunner.""" + + def __init__(self, *args, **kwargs): + warnings.warn( + 'Runner was deprecated, please use EpochBasedRunner instead') + super().__init__(*args, **kwargs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/fp16_utils.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/fp16_utils.py new file mode 100644 index 0000000..1981011 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/fp16_utils.py @@ -0,0 +1,410 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import functools +import warnings +from collections import abc +from inspect import getfullargspec + +import numpy as np +import torch +import torch.nn as nn + +from annotator.uniformer.mmcv.utils import TORCH_VERSION, digit_version +from .dist_utils import allreduce_grads as _allreduce_grads + +try: + # If PyTorch version >= 1.6.0, torch.cuda.amp.autocast would be imported + # and used; otherwise, auto fp16 will adopt mmcv's implementation. + # Note that when PyTorch >= 1.6.0, we still cast tensor types to fp16 + # manually, so the behavior may not be consistent with real amp. + from torch.cuda.amp import autocast +except ImportError: + pass + + +def cast_tensor_type(inputs, src_type, dst_type): + """Recursively convert Tensor in inputs from src_type to dst_type. + + Args: + inputs: Inputs that to be casted. + src_type (torch.dtype): Source type.. + dst_type (torch.dtype): Destination type. + + Returns: + The same type with inputs, but all contained Tensors have been cast. + """ + if isinstance(inputs, nn.Module): + return inputs + elif isinstance(inputs, torch.Tensor): + return inputs.to(dst_type) + elif isinstance(inputs, str): + return inputs + elif isinstance(inputs, np.ndarray): + return inputs + elif isinstance(inputs, abc.Mapping): + return type(inputs)({ + k: cast_tensor_type(v, src_type, dst_type) + for k, v in inputs.items() + }) + elif isinstance(inputs, abc.Iterable): + return type(inputs)( + cast_tensor_type(item, src_type, dst_type) for item in inputs) + else: + return inputs + + +def auto_fp16(apply_to=None, out_fp32=False): + """Decorator to enable fp16 training automatically. + + This decorator is useful when you write custom modules and want to support + mixed precision training. If inputs arguments are fp32 tensors, they will + be converted to fp16 automatically. Arguments other than fp32 tensors are + ignored. If you are using PyTorch >= 1.6, torch.cuda.amp is used as the + backend, otherwise, original mmcv implementation will be adopted. + + Args: + apply_to (Iterable, optional): The argument names to be converted. + `None` indicates all arguments. + out_fp32 (bool): Whether to convert the output back to fp32. + + Example: + + >>> import torch.nn as nn + >>> class MyModule1(nn.Module): + >>> + >>> # Convert x and y to fp16 + >>> @auto_fp16() + >>> def forward(self, x, y): + >>> pass + + >>> import torch.nn as nn + >>> class MyModule2(nn.Module): + >>> + >>> # convert pred to fp16 + >>> @auto_fp16(apply_to=('pred', )) + >>> def do_something(self, pred, others): + >>> pass + """ + + def auto_fp16_wrapper(old_func): + + @functools.wraps(old_func) + def new_func(*args, **kwargs): + # check if the module has set the attribute `fp16_enabled`, if not, + # just fallback to the original method. + if not isinstance(args[0], torch.nn.Module): + raise TypeError('@auto_fp16 can only be used to decorate the ' + 'method of nn.Module') + if not (hasattr(args[0], 'fp16_enabled') and args[0].fp16_enabled): + return old_func(*args, **kwargs) + + # get the arg spec of the decorated method + args_info = getfullargspec(old_func) + # get the argument names to be casted + args_to_cast = args_info.args if apply_to is None else apply_to + # convert the args that need to be processed + new_args = [] + # NOTE: default args are not taken into consideration + if args: + arg_names = args_info.args[:len(args)] + for i, arg_name in enumerate(arg_names): + if arg_name in args_to_cast: + new_args.append( + cast_tensor_type(args[i], torch.float, torch.half)) + else: + new_args.append(args[i]) + # convert the kwargs that need to be processed + new_kwargs = {} + if kwargs: + for arg_name, arg_value in kwargs.items(): + if arg_name in args_to_cast: + new_kwargs[arg_name] = cast_tensor_type( + arg_value, torch.float, torch.half) + else: + new_kwargs[arg_name] = arg_value + # apply converted arguments to the decorated method + if (TORCH_VERSION != 'parrots' and + digit_version(TORCH_VERSION) >= digit_version('1.6.0')): + with autocast(enabled=True): + output = old_func(*new_args, **new_kwargs) + else: + output = old_func(*new_args, **new_kwargs) + # cast the results back to fp32 if necessary + if out_fp32: + output = cast_tensor_type(output, torch.half, torch.float) + return output + + return new_func + + return auto_fp16_wrapper + + +def force_fp32(apply_to=None, out_fp16=False): + """Decorator to convert input arguments to fp32 in force. + + This decorator is useful when you write custom modules and want to support + mixed precision training. If there are some inputs that must be processed + in fp32 mode, then this decorator can handle it. If inputs arguments are + fp16 tensors, they will be converted to fp32 automatically. Arguments other + than fp16 tensors are ignored. If you are using PyTorch >= 1.6, + torch.cuda.amp is used as the backend, otherwise, original mmcv + implementation will be adopted. + + Args: + apply_to (Iterable, optional): The argument names to be converted. + `None` indicates all arguments. + out_fp16 (bool): Whether to convert the output back to fp16. + + Example: + + >>> import torch.nn as nn + >>> class MyModule1(nn.Module): + >>> + >>> # Convert x and y to fp32 + >>> @force_fp32() + >>> def loss(self, x, y): + >>> pass + + >>> import torch.nn as nn + >>> class MyModule2(nn.Module): + >>> + >>> # convert pred to fp32 + >>> @force_fp32(apply_to=('pred', )) + >>> def post_process(self, pred, others): + >>> pass + """ + + def force_fp32_wrapper(old_func): + + @functools.wraps(old_func) + def new_func(*args, **kwargs): + # check if the module has set the attribute `fp16_enabled`, if not, + # just fallback to the original method. + if not isinstance(args[0], torch.nn.Module): + raise TypeError('@force_fp32 can only be used to decorate the ' + 'method of nn.Module') + if not (hasattr(args[0], 'fp16_enabled') and args[0].fp16_enabled): + return old_func(*args, **kwargs) + # get the arg spec of the decorated method + args_info = getfullargspec(old_func) + # get the argument names to be casted + args_to_cast = args_info.args if apply_to is None else apply_to + # convert the args that need to be processed + new_args = [] + if args: + arg_names = args_info.args[:len(args)] + for i, arg_name in enumerate(arg_names): + if arg_name in args_to_cast: + new_args.append( + cast_tensor_type(args[i], torch.half, torch.float)) + else: + new_args.append(args[i]) + # convert the kwargs that need to be processed + new_kwargs = dict() + if kwargs: + for arg_name, arg_value in kwargs.items(): + if arg_name in args_to_cast: + new_kwargs[arg_name] = cast_tensor_type( + arg_value, torch.half, torch.float) + else: + new_kwargs[arg_name] = arg_value + # apply converted arguments to the decorated method + if (TORCH_VERSION != 'parrots' and + digit_version(TORCH_VERSION) >= digit_version('1.6.0')): + with autocast(enabled=False): + output = old_func(*new_args, **new_kwargs) + else: + output = old_func(*new_args, **new_kwargs) + # cast the results back to fp32 if necessary + if out_fp16: + output = cast_tensor_type(output, torch.float, torch.half) + return output + + return new_func + + return force_fp32_wrapper + + +def allreduce_grads(params, coalesce=True, bucket_size_mb=-1): + warnings.warning( + '"mmcv.runner.fp16_utils.allreduce_grads" is deprecated, and will be ' + 'removed in v2.8. Please switch to "mmcv.runner.allreduce_grads') + _allreduce_grads(params, coalesce=coalesce, bucket_size_mb=bucket_size_mb) + + +def wrap_fp16_model(model): + """Wrap the FP32 model to FP16. + + If you are using PyTorch >= 1.6, torch.cuda.amp is used as the + backend, otherwise, original mmcv implementation will be adopted. + + For PyTorch >= 1.6, this function will + 1. Set fp16 flag inside the model to True. + + Otherwise: + 1. Convert FP32 model to FP16. + 2. Remain some necessary layers to be FP32, e.g., normalization layers. + 3. Set `fp16_enabled` flag inside the model to True. + + Args: + model (nn.Module): Model in FP32. + """ + if (TORCH_VERSION == 'parrots' + or digit_version(TORCH_VERSION) < digit_version('1.6.0')): + # convert model to fp16 + model.half() + # patch the normalization layers to make it work in fp32 mode + patch_norm_fp32(model) + # set `fp16_enabled` flag + for m in model.modules(): + if hasattr(m, 'fp16_enabled'): + m.fp16_enabled = True + + +def patch_norm_fp32(module): + """Recursively convert normalization layers from FP16 to FP32. + + Args: + module (nn.Module): The modules to be converted in FP16. + + Returns: + nn.Module: The converted module, the normalization layers have been + converted to FP32. + """ + if isinstance(module, (nn.modules.batchnorm._BatchNorm, nn.GroupNorm)): + module.float() + if isinstance(module, nn.GroupNorm) or torch.__version__ < '1.3': + module.forward = patch_forward_method(module.forward, torch.half, + torch.float) + for child in module.children(): + patch_norm_fp32(child) + return module + + +def patch_forward_method(func, src_type, dst_type, convert_output=True): + """Patch the forward method of a module. + + Args: + func (callable): The original forward method. + src_type (torch.dtype): Type of input arguments to be converted from. + dst_type (torch.dtype): Type of input arguments to be converted to. + convert_output (bool): Whether to convert the output back to src_type. + + Returns: + callable: The patched forward method. + """ + + def new_forward(*args, **kwargs): + output = func(*cast_tensor_type(args, src_type, dst_type), + **cast_tensor_type(kwargs, src_type, dst_type)) + if convert_output: + output = cast_tensor_type(output, dst_type, src_type) + return output + + return new_forward + + +class LossScaler: + """Class that manages loss scaling in mixed precision training which + supports both dynamic or static mode. + + The implementation refers to + https://github.com/NVIDIA/apex/blob/master/apex/fp16_utils/loss_scaler.py. + Indirectly, by supplying ``mode='dynamic'`` for dynamic loss scaling. + It's important to understand how :class:`LossScaler` operates. + Loss scaling is designed to combat the problem of underflowing + gradients encountered at long times when training fp16 networks. + Dynamic loss scaling begins by attempting a very high loss + scale. Ironically, this may result in OVERflowing gradients. + If overflowing gradients are encountered, :class:`FP16_Optimizer` then + skips the update step for this particular iteration/minibatch, + and :class:`LossScaler` adjusts the loss scale to a lower value. + If a certain number of iterations occur without overflowing gradients + detected,:class:`LossScaler` increases the loss scale once more. + In this way :class:`LossScaler` attempts to "ride the edge" of always + using the highest loss scale possible without incurring overflow. + + Args: + init_scale (float): Initial loss scale value, default: 2**32. + scale_factor (float): Factor used when adjusting the loss scale. + Default: 2. + mode (str): Loss scaling mode. 'dynamic' or 'static' + scale_window (int): Number of consecutive iterations without an + overflow to wait before increasing the loss scale. Default: 1000. + """ + + def __init__(self, + init_scale=2**32, + mode='dynamic', + scale_factor=2., + scale_window=1000): + self.cur_scale = init_scale + self.cur_iter = 0 + assert mode in ('dynamic', + 'static'), 'mode can only be dynamic or static' + self.mode = mode + self.last_overflow_iter = -1 + self.scale_factor = scale_factor + self.scale_window = scale_window + + def has_overflow(self, params): + """Check if params contain overflow.""" + if self.mode != 'dynamic': + return False + for p in params: + if p.grad is not None and LossScaler._has_inf_or_nan(p.grad.data): + return True + return False + + def _has_inf_or_nan(x): + """Check if params contain NaN.""" + try: + cpu_sum = float(x.float().sum()) + except RuntimeError as instance: + if 'value cannot be converted' not in instance.args[0]: + raise + return True + else: + if cpu_sum == float('inf') or cpu_sum == -float('inf') \ + or cpu_sum != cpu_sum: + return True + return False + + def update_scale(self, overflow): + """update the current loss scale value when overflow happens.""" + if self.mode != 'dynamic': + return + if overflow: + self.cur_scale = max(self.cur_scale / self.scale_factor, 1) + self.last_overflow_iter = self.cur_iter + else: + if (self.cur_iter - self.last_overflow_iter) % \ + self.scale_window == 0: + self.cur_scale *= self.scale_factor + self.cur_iter += 1 + + def state_dict(self): + """Returns the state of the scaler as a :class:`dict`.""" + return dict( + cur_scale=self.cur_scale, + cur_iter=self.cur_iter, + mode=self.mode, + last_overflow_iter=self.last_overflow_iter, + scale_factor=self.scale_factor, + scale_window=self.scale_window) + + def load_state_dict(self, state_dict): + """Loads the loss_scaler state dict. + + Args: + state_dict (dict): scaler state. + """ + self.cur_scale = state_dict['cur_scale'] + self.cur_iter = state_dict['cur_iter'] + self.mode = state_dict['mode'] + self.last_overflow_iter = state_dict['last_overflow_iter'] + self.scale_factor = state_dict['scale_factor'] + self.scale_window = state_dict['scale_window'] + + @property + def loss_scale(self): + return self.cur_scale diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/__init__.py new file mode 100644 index 0000000..915af28 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/__init__.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .checkpoint import CheckpointHook +from .closure import ClosureHook +from .ema import EMAHook +from .evaluation import DistEvalHook, EvalHook +from .hook import HOOKS, Hook +from .iter_timer import IterTimerHook +from .logger import (DvcliveLoggerHook, LoggerHook, MlflowLoggerHook, + NeptuneLoggerHook, PaviLoggerHook, TensorboardLoggerHook, + TextLoggerHook, WandbLoggerHook) +from .lr_updater import LrUpdaterHook +from .memory import EmptyCacheHook +from .momentum_updater import MomentumUpdaterHook +from .optimizer import (Fp16OptimizerHook, GradientCumulativeFp16OptimizerHook, + GradientCumulativeOptimizerHook, OptimizerHook) +from .profiler import ProfilerHook +from .sampler_seed import DistSamplerSeedHook +from .sync_buffer import SyncBuffersHook + +__all__ = [ + 'HOOKS', 'Hook', 'CheckpointHook', 'ClosureHook', 'LrUpdaterHook', + 'OptimizerHook', 'Fp16OptimizerHook', 'IterTimerHook', + 'DistSamplerSeedHook', 'EmptyCacheHook', 'LoggerHook', 'MlflowLoggerHook', + 'PaviLoggerHook', 'TextLoggerHook', 'TensorboardLoggerHook', + 'NeptuneLoggerHook', 'WandbLoggerHook', 'DvcliveLoggerHook', + 'MomentumUpdaterHook', 'SyncBuffersHook', 'EMAHook', 'EvalHook', + 'DistEvalHook', 'ProfilerHook', 'GradientCumulativeOptimizerHook', + 'GradientCumulativeFp16OptimizerHook' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/checkpoint.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/checkpoint.py new file mode 100644 index 0000000..6af3fae --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/checkpoint.py @@ -0,0 +1,167 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import warnings + +from annotator.uniformer.mmcv.fileio import FileClient +from ..dist_utils import allreduce_params, master_only +from .hook import HOOKS, Hook + + +@HOOKS.register_module() +class CheckpointHook(Hook): + """Save checkpoints periodically. + + Args: + interval (int): The saving period. If ``by_epoch=True``, interval + indicates epochs, otherwise it indicates iterations. + Default: -1, which means "never". + by_epoch (bool): Saving checkpoints by epoch or by iteration. + Default: True. + save_optimizer (bool): Whether to save optimizer state_dict in the + checkpoint. It is usually used for resuming experiments. + Default: True. + out_dir (str, optional): The root directory to save checkpoints. If not + specified, ``runner.work_dir`` will be used by default. If + specified, the ``out_dir`` will be the concatenation of ``out_dir`` + and the last level directory of ``runner.work_dir``. + `Changed in version 1.3.16.` + max_keep_ckpts (int, optional): The maximum checkpoints to keep. + In some cases we want only the latest few checkpoints and would + like to delete old ones to save the disk space. + Default: -1, which means unlimited. + save_last (bool, optional): Whether to force the last checkpoint to be + saved regardless of interval. Default: True. + sync_buffer (bool, optional): Whether to synchronize buffers in + different gpus. Default: False. + file_client_args (dict, optional): Arguments to instantiate a + FileClient. See :class:`mmcv.fileio.FileClient` for details. + Default: None. + `New in version 1.3.16.` + + .. warning:: + Before v1.3.16, the ``out_dir`` argument indicates the path where the + checkpoint is stored. However, since v1.3.16, ``out_dir`` indicates the + root directory and the final path to save checkpoint is the + concatenation of ``out_dir`` and the last level directory of + ``runner.work_dir``. Suppose the value of ``out_dir`` is "/path/of/A" + and the value of ``runner.work_dir`` is "/path/of/B", then the final + path will be "/path/of/A/B". + """ + + def __init__(self, + interval=-1, + by_epoch=True, + save_optimizer=True, + out_dir=None, + max_keep_ckpts=-1, + save_last=True, + sync_buffer=False, + file_client_args=None, + **kwargs): + self.interval = interval + self.by_epoch = by_epoch + self.save_optimizer = save_optimizer + self.out_dir = out_dir + self.max_keep_ckpts = max_keep_ckpts + self.save_last = save_last + self.args = kwargs + self.sync_buffer = sync_buffer + self.file_client_args = file_client_args + + def before_run(self, runner): + if not self.out_dir: + self.out_dir = runner.work_dir + + self.file_client = FileClient.infer_client(self.file_client_args, + self.out_dir) + + # if `self.out_dir` is not equal to `runner.work_dir`, it means that + # `self.out_dir` is set so the final `self.out_dir` is the + # concatenation of `self.out_dir` and the last level directory of + # `runner.work_dir` + if self.out_dir != runner.work_dir: + basename = osp.basename(runner.work_dir.rstrip(osp.sep)) + self.out_dir = self.file_client.join_path(self.out_dir, basename) + + runner.logger.info((f'Checkpoints will be saved to {self.out_dir} by ' + f'{self.file_client.name}.')) + + # disable the create_symlink option because some file backends do not + # allow to create a symlink + if 'create_symlink' in self.args: + if self.args[ + 'create_symlink'] and not self.file_client.allow_symlink: + self.args['create_symlink'] = False + warnings.warn( + ('create_symlink is set as True by the user but is changed' + 'to be False because creating symbolic link is not ' + f'allowed in {self.file_client.name}')) + else: + self.args['create_symlink'] = self.file_client.allow_symlink + + def after_train_epoch(self, runner): + if not self.by_epoch: + return + + # save checkpoint for following cases: + # 1. every ``self.interval`` epochs + # 2. reach the last epoch of training + if self.every_n_epochs( + runner, self.interval) or (self.save_last + and self.is_last_epoch(runner)): + runner.logger.info( + f'Saving checkpoint at {runner.epoch + 1} epochs') + if self.sync_buffer: + allreduce_params(runner.model.buffers()) + self._save_checkpoint(runner) + + @master_only + def _save_checkpoint(self, runner): + """Save the current checkpoint and delete unwanted checkpoint.""" + runner.save_checkpoint( + self.out_dir, save_optimizer=self.save_optimizer, **self.args) + if runner.meta is not None: + if self.by_epoch: + cur_ckpt_filename = self.args.get( + 'filename_tmpl', 'epoch_{}.pth').format(runner.epoch + 1) + else: + cur_ckpt_filename = self.args.get( + 'filename_tmpl', 'iter_{}.pth').format(runner.iter + 1) + runner.meta.setdefault('hook_msgs', dict()) + runner.meta['hook_msgs']['last_ckpt'] = self.file_client.join_path( + self.out_dir, cur_ckpt_filename) + # remove other checkpoints + if self.max_keep_ckpts > 0: + if self.by_epoch: + name = 'epoch_{}.pth' + current_ckpt = runner.epoch + 1 + else: + name = 'iter_{}.pth' + current_ckpt = runner.iter + 1 + redundant_ckpts = range( + current_ckpt - self.max_keep_ckpts * self.interval, 0, + -self.interval) + filename_tmpl = self.args.get('filename_tmpl', name) + for _step in redundant_ckpts: + ckpt_path = self.file_client.join_path( + self.out_dir, filename_tmpl.format(_step)) + if self.file_client.isfile(ckpt_path): + self.file_client.remove(ckpt_path) + else: + break + + def after_train_iter(self, runner): + if self.by_epoch: + return + + # save checkpoint for following cases: + # 1. every ``self.interval`` iterations + # 2. reach the last iteration of training + if self.every_n_iters( + runner, self.interval) or (self.save_last + and self.is_last_iter(runner)): + runner.logger.info( + f'Saving checkpoint at {runner.iter + 1} iterations') + if self.sync_buffer: + allreduce_params(runner.model.buffers()) + self._save_checkpoint(runner) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/closure.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/closure.py new file mode 100644 index 0000000..b955f81 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/closure.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .hook import HOOKS, Hook + + +@HOOKS.register_module() +class ClosureHook(Hook): + + def __init__(self, fn_name, fn): + assert hasattr(self, fn_name) + assert callable(fn) + setattr(self, fn_name, fn) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/ema.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/ema.py new file mode 100644 index 0000000..15c7e68 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/ema.py @@ -0,0 +1,89 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ...parallel import is_module_wrapper +from ..hooks.hook import HOOKS, Hook + + +@HOOKS.register_module() +class EMAHook(Hook): + r"""Exponential Moving Average Hook. + + Use Exponential Moving Average on all parameters of model in training + process. All parameters have a ema backup, which update by the formula + as below. EMAHook takes priority over EvalHook and CheckpointSaverHook. + + .. math:: + + \text{Xema\_{t+1}} = (1 - \text{momentum}) \times + \text{Xema\_{t}} + \text{momentum} \times X_t + + Args: + momentum (float): The momentum used for updating ema parameter. + Defaults to 0.0002. + interval (int): Update ema parameter every interval iteration. + Defaults to 1. + warm_up (int): During first warm_up steps, we may use smaller momentum + to update ema parameters more slowly. Defaults to 100. + resume_from (str): The checkpoint path. Defaults to None. + """ + + def __init__(self, + momentum=0.0002, + interval=1, + warm_up=100, + resume_from=None): + assert isinstance(interval, int) and interval > 0 + self.warm_up = warm_up + self.interval = interval + assert momentum > 0 and momentum < 1 + self.momentum = momentum**interval + self.checkpoint = resume_from + + def before_run(self, runner): + """To resume model with it's ema parameters more friendly. + + Register ema parameter as ``named_buffer`` to model + """ + model = runner.model + if is_module_wrapper(model): + model = model.module + self.param_ema_buffer = {} + self.model_parameters = dict(model.named_parameters(recurse=True)) + for name, value in self.model_parameters.items(): + # "." is not allowed in module's buffer name + buffer_name = f"ema_{name.replace('.', '_')}" + self.param_ema_buffer[name] = buffer_name + model.register_buffer(buffer_name, value.data.clone()) + self.model_buffers = dict(model.named_buffers(recurse=True)) + if self.checkpoint is not None: + runner.resume(self.checkpoint) + + def after_train_iter(self, runner): + """Update ema parameter every self.interval iterations.""" + curr_step = runner.iter + # We warm up the momentum considering the instability at beginning + momentum = min(self.momentum, + (1 + curr_step) / (self.warm_up + curr_step)) + if curr_step % self.interval != 0: + return + for name, parameter in self.model_parameters.items(): + buffer_name = self.param_ema_buffer[name] + buffer_parameter = self.model_buffers[buffer_name] + buffer_parameter.mul_(1 - momentum).add_(momentum, parameter.data) + + def after_train_epoch(self, runner): + """We load parameter values from ema backup to model before the + EvalHook.""" + self._swap_ema_parameters() + + def before_train_epoch(self, runner): + """We recover model's parameter from ema backup after last epoch's + EvalHook.""" + self._swap_ema_parameters() + + def _swap_ema_parameters(self): + """Swap the parameter of model with parameter in ema_buffer.""" + for name, value in self.model_parameters.items(): + temp = value.data.clone() + ema_buffer = self.model_buffers[self.param_ema_buffer[name]] + value.data.copy_(ema_buffer.data) + ema_buffer.data.copy_(temp) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/evaluation.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/evaluation.py new file mode 100644 index 0000000..4d00999 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/evaluation.py @@ -0,0 +1,509 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import warnings +from math import inf + +import torch.distributed as dist +from torch.nn.modules.batchnorm import _BatchNorm +from torch.utils.data import DataLoader + +from annotator.uniformer.mmcv.fileio import FileClient +from annotator.uniformer.mmcv.utils import is_seq_of +from .hook import Hook +from .logger import LoggerHook + + +class EvalHook(Hook): + """Non-Distributed evaluation hook. + + This hook will regularly perform evaluation in a given interval when + performing in non-distributed environment. + + Args: + dataloader (DataLoader): A PyTorch dataloader, whose dataset has + implemented ``evaluate`` function. + start (int | None, optional): Evaluation starting epoch. It enables + evaluation before the training starts if ``start`` <= the resuming + epoch. If None, whether to evaluate is merely decided by + ``interval``. Default: None. + interval (int): Evaluation interval. Default: 1. + by_epoch (bool): Determine perform evaluation by epoch or by iteration. + If set to True, it will perform by epoch. Otherwise, by iteration. + Default: True. + save_best (str, optional): If a metric is specified, it would measure + the best checkpoint during evaluation. The information about best + checkpoint would be saved in ``runner.meta['hook_msgs']`` to keep + best score value and best checkpoint path, which will be also + loaded when resume checkpoint. Options are the evaluation metrics + on the test dataset. e.g., ``bbox_mAP``, ``segm_mAP`` for bbox + detection and instance segmentation. ``AR@100`` for proposal + recall. If ``save_best`` is ``auto``, the first key of the returned + ``OrderedDict`` result will be used. Default: None. + rule (str | None, optional): Comparison rule for best score. If set to + None, it will infer a reasonable rule. Keys such as 'acc', 'top' + .etc will be inferred by 'greater' rule. Keys contain 'loss' will + be inferred by 'less' rule. Options are 'greater', 'less', None. + Default: None. + test_fn (callable, optional): test a model with samples from a + dataloader, and return the test results. If ``None``, the default + test function ``mmcv.engine.single_gpu_test`` will be used. + (default: ``None``) + greater_keys (List[str] | None, optional): Metric keys that will be + inferred by 'greater' comparison rule. If ``None``, + _default_greater_keys will be used. (default: ``None``) + less_keys (List[str] | None, optional): Metric keys that will be + inferred by 'less' comparison rule. If ``None``, _default_less_keys + will be used. (default: ``None``) + out_dir (str, optional): The root directory to save checkpoints. If not + specified, `runner.work_dir` will be used by default. If specified, + the `out_dir` will be the concatenation of `out_dir` and the last + level directory of `runner.work_dir`. + `New in version 1.3.16.` + file_client_args (dict): Arguments to instantiate a FileClient. + See :class:`mmcv.fileio.FileClient` for details. Default: None. + `New in version 1.3.16.` + **eval_kwargs: Evaluation arguments fed into the evaluate function of + the dataset. + + Notes: + If new arguments are added for EvalHook, tools/test.py, + tools/eval_metric.py may be affected. + """ + + # Since the key for determine greater or less is related to the downstream + # tasks, downstream repos may need to overwrite the following inner + # variable accordingly. + + rule_map = {'greater': lambda x, y: x > y, 'less': lambda x, y: x < y} + init_value_map = {'greater': -inf, 'less': inf} + _default_greater_keys = [ + 'acc', 'top', 'AR@', 'auc', 'precision', 'mAP', 'mDice', 'mIoU', + 'mAcc', 'aAcc' + ] + _default_less_keys = ['loss'] + + def __init__(self, + dataloader, + start=None, + interval=1, + by_epoch=True, + save_best=None, + rule=None, + test_fn=None, + greater_keys=None, + less_keys=None, + out_dir=None, + file_client_args=None, + **eval_kwargs): + if not isinstance(dataloader, DataLoader): + raise TypeError(f'dataloader must be a pytorch DataLoader, ' + f'but got {type(dataloader)}') + + if interval <= 0: + raise ValueError(f'interval must be a positive number, ' + f'but got {interval}') + + assert isinstance(by_epoch, bool), '``by_epoch`` should be a boolean' + + if start is not None and start < 0: + raise ValueError(f'The evaluation start epoch {start} is smaller ' + f'than 0') + + self.dataloader = dataloader + self.interval = interval + self.start = start + self.by_epoch = by_epoch + + assert isinstance(save_best, str) or save_best is None, \ + '""save_best"" should be a str or None ' \ + f'rather than {type(save_best)}' + self.save_best = save_best + self.eval_kwargs = eval_kwargs + self.initial_flag = True + + if test_fn is None: + from annotator.uniformer.mmcv.engine import single_gpu_test + self.test_fn = single_gpu_test + else: + self.test_fn = test_fn + + if greater_keys is None: + self.greater_keys = self._default_greater_keys + else: + if not isinstance(greater_keys, (list, tuple)): + greater_keys = (greater_keys, ) + assert is_seq_of(greater_keys, str) + self.greater_keys = greater_keys + + if less_keys is None: + self.less_keys = self._default_less_keys + else: + if not isinstance(less_keys, (list, tuple)): + less_keys = (less_keys, ) + assert is_seq_of(less_keys, str) + self.less_keys = less_keys + + if self.save_best is not None: + self.best_ckpt_path = None + self._init_rule(rule, self.save_best) + + self.out_dir = out_dir + self.file_client_args = file_client_args + + def _init_rule(self, rule, key_indicator): + """Initialize rule, key_indicator, comparison_func, and best score. + + Here is the rule to determine which rule is used for key indicator + when the rule is not specific (note that the key indicator matching + is case-insensitive): + 1. If the key indicator is in ``self.greater_keys``, the rule will be + specified as 'greater'. + 2. Or if the key indicator is in ``self.less_keys``, the rule will be + specified as 'less'. + 3. Or if the key indicator is equal to the substring in any one item + in ``self.greater_keys``, the rule will be specified as 'greater'. + 4. Or if the key indicator is equal to the substring in any one item + in ``self.less_keys``, the rule will be specified as 'less'. + + Args: + rule (str | None): Comparison rule for best score. + key_indicator (str | None): Key indicator to determine the + comparison rule. + """ + if rule not in self.rule_map and rule is not None: + raise KeyError(f'rule must be greater, less or None, ' + f'but got {rule}.') + + if rule is None: + if key_indicator != 'auto': + # `_lc` here means we use the lower case of keys for + # case-insensitive matching + key_indicator_lc = key_indicator.lower() + greater_keys = [key.lower() for key in self.greater_keys] + less_keys = [key.lower() for key in self.less_keys] + + if key_indicator_lc in greater_keys: + rule = 'greater' + elif key_indicator_lc in less_keys: + rule = 'less' + elif any(key in key_indicator_lc for key in greater_keys): + rule = 'greater' + elif any(key in key_indicator_lc for key in less_keys): + rule = 'less' + else: + raise ValueError(f'Cannot infer the rule for key ' + f'{key_indicator}, thus a specific rule ' + f'must be specified.') + self.rule = rule + self.key_indicator = key_indicator + if self.rule is not None: + self.compare_func = self.rule_map[self.rule] + + def before_run(self, runner): + if not self.out_dir: + self.out_dir = runner.work_dir + + self.file_client = FileClient.infer_client(self.file_client_args, + self.out_dir) + + # if `self.out_dir` is not equal to `runner.work_dir`, it means that + # `self.out_dir` is set so the final `self.out_dir` is the + # concatenation of `self.out_dir` and the last level directory of + # `runner.work_dir` + if self.out_dir != runner.work_dir: + basename = osp.basename(runner.work_dir.rstrip(osp.sep)) + self.out_dir = self.file_client.join_path(self.out_dir, basename) + runner.logger.info( + (f'The best checkpoint will be saved to {self.out_dir} by ' + f'{self.file_client.name}')) + + if self.save_best is not None: + if runner.meta is None: + warnings.warn('runner.meta is None. Creating an empty one.') + runner.meta = dict() + runner.meta.setdefault('hook_msgs', dict()) + self.best_ckpt_path = runner.meta['hook_msgs'].get( + 'best_ckpt', None) + + def before_train_iter(self, runner): + """Evaluate the model only at the start of training by iteration.""" + if self.by_epoch or not self.initial_flag: + return + if self.start is not None and runner.iter >= self.start: + self.after_train_iter(runner) + self.initial_flag = False + + def before_train_epoch(self, runner): + """Evaluate the model only at the start of training by epoch.""" + if not (self.by_epoch and self.initial_flag): + return + if self.start is not None and runner.epoch >= self.start: + self.after_train_epoch(runner) + self.initial_flag = False + + def after_train_iter(self, runner): + """Called after every training iter to evaluate the results.""" + if not self.by_epoch and self._should_evaluate(runner): + # Because the priority of EvalHook is higher than LoggerHook, the + # training log and the evaluating log are mixed. Therefore, + # we need to dump the training log and clear it before evaluating + # log is generated. In addition, this problem will only appear in + # `IterBasedRunner` whose `self.by_epoch` is False, because + # `EpochBasedRunner` whose `self.by_epoch` is True calls + # `_do_evaluate` in `after_train_epoch` stage, and at this stage + # the training log has been printed, so it will not cause any + # problem. more details at + # https://github.com/open-mmlab/mmsegmentation/issues/694 + for hook in runner._hooks: + if isinstance(hook, LoggerHook): + hook.after_train_iter(runner) + runner.log_buffer.clear() + + self._do_evaluate(runner) + + def after_train_epoch(self, runner): + """Called after every training epoch to evaluate the results.""" + if self.by_epoch and self._should_evaluate(runner): + self._do_evaluate(runner) + + def _do_evaluate(self, runner): + """perform evaluation and save ckpt.""" + results = self.test_fn(runner.model, self.dataloader) + runner.log_buffer.output['eval_iter_num'] = len(self.dataloader) + key_score = self.evaluate(runner, results) + # the key_score may be `None` so it needs to skip the action to save + # the best checkpoint + if self.save_best and key_score: + self._save_ckpt(runner, key_score) + + def _should_evaluate(self, runner): + """Judge whether to perform evaluation. + + Here is the rule to judge whether to perform evaluation: + 1. It will not perform evaluation during the epoch/iteration interval, + which is determined by ``self.interval``. + 2. It will not perform evaluation if the start time is larger than + current time. + 3. It will not perform evaluation when current time is larger than + the start time but during epoch/iteration interval. + + Returns: + bool: The flag indicating whether to perform evaluation. + """ + if self.by_epoch: + current = runner.epoch + check_time = self.every_n_epochs + else: + current = runner.iter + check_time = self.every_n_iters + + if self.start is None: + if not check_time(runner, self.interval): + # No evaluation during the interval. + return False + elif (current + 1) < self.start: + # No evaluation if start is larger than the current time. + return False + else: + # Evaluation only at epochs/iters 3, 5, 7... + # if start==3 and interval==2 + if (current + 1 - self.start) % self.interval: + return False + return True + + def _save_ckpt(self, runner, key_score): + """Save the best checkpoint. + + It will compare the score according to the compare function, write + related information (best score, best checkpoint path) and save the + best checkpoint into ``work_dir``. + """ + if self.by_epoch: + current = f'epoch_{runner.epoch + 1}' + cur_type, cur_time = 'epoch', runner.epoch + 1 + else: + current = f'iter_{runner.iter + 1}' + cur_type, cur_time = 'iter', runner.iter + 1 + + best_score = runner.meta['hook_msgs'].get( + 'best_score', self.init_value_map[self.rule]) + if self.compare_func(key_score, best_score): + best_score = key_score + runner.meta['hook_msgs']['best_score'] = best_score + + if self.best_ckpt_path and self.file_client.isfile( + self.best_ckpt_path): + self.file_client.remove(self.best_ckpt_path) + runner.logger.info( + (f'The previous best checkpoint {self.best_ckpt_path} was ' + 'removed')) + + best_ckpt_name = f'best_{self.key_indicator}_{current}.pth' + self.best_ckpt_path = self.file_client.join_path( + self.out_dir, best_ckpt_name) + runner.meta['hook_msgs']['best_ckpt'] = self.best_ckpt_path + + runner.save_checkpoint( + self.out_dir, best_ckpt_name, create_symlink=False) + runner.logger.info( + f'Now best checkpoint is saved as {best_ckpt_name}.') + runner.logger.info( + f'Best {self.key_indicator} is {best_score:0.4f} ' + f'at {cur_time} {cur_type}.') + + def evaluate(self, runner, results): + """Evaluate the results. + + Args: + runner (:obj:`mmcv.Runner`): The underlined training runner. + results (list): Output results. + """ + eval_res = self.dataloader.dataset.evaluate( + results, logger=runner.logger, **self.eval_kwargs) + + for name, val in eval_res.items(): + runner.log_buffer.output[name] = val + runner.log_buffer.ready = True + + if self.save_best is not None: + # If the performance of model is pool, the `eval_res` may be an + # empty dict and it will raise exception when `self.save_best` is + # not None. More details at + # https://github.com/open-mmlab/mmdetection/issues/6265. + if not eval_res: + warnings.warn( + 'Since `eval_res` is an empty dict, the behavior to save ' + 'the best checkpoint will be skipped in this evaluation.') + return None + + if self.key_indicator == 'auto': + # infer from eval_results + self._init_rule(self.rule, list(eval_res.keys())[0]) + return eval_res[self.key_indicator] + + return None + + +class DistEvalHook(EvalHook): + """Distributed evaluation hook. + + This hook will regularly perform evaluation in a given interval when + performing in distributed environment. + + Args: + dataloader (DataLoader): A PyTorch dataloader, whose dataset has + implemented ``evaluate`` function. + start (int | None, optional): Evaluation starting epoch. It enables + evaluation before the training starts if ``start`` <= the resuming + epoch. If None, whether to evaluate is merely decided by + ``interval``. Default: None. + interval (int): Evaluation interval. Default: 1. + by_epoch (bool): Determine perform evaluation by epoch or by iteration. + If set to True, it will perform by epoch. Otherwise, by iteration. + default: True. + save_best (str, optional): If a metric is specified, it would measure + the best checkpoint during evaluation. The information about best + checkpoint would be saved in ``runner.meta['hook_msgs']`` to keep + best score value and best checkpoint path, which will be also + loaded when resume checkpoint. Options are the evaluation metrics + on the test dataset. e.g., ``bbox_mAP``, ``segm_mAP`` for bbox + detection and instance segmentation. ``AR@100`` for proposal + recall. If ``save_best`` is ``auto``, the first key of the returned + ``OrderedDict`` result will be used. Default: None. + rule (str | None, optional): Comparison rule for best score. If set to + None, it will infer a reasonable rule. Keys such as 'acc', 'top' + .etc will be inferred by 'greater' rule. Keys contain 'loss' will + be inferred by 'less' rule. Options are 'greater', 'less', None. + Default: None. + test_fn (callable, optional): test a model with samples from a + dataloader in a multi-gpu manner, and return the test results. If + ``None``, the default test function ``mmcv.engine.multi_gpu_test`` + will be used. (default: ``None``) + tmpdir (str | None): Temporary directory to save the results of all + processes. Default: None. + gpu_collect (bool): Whether to use gpu or cpu to collect results. + Default: False. + broadcast_bn_buffer (bool): Whether to broadcast the + buffer(running_mean and running_var) of rank 0 to other rank + before evaluation. Default: True. + out_dir (str, optional): The root directory to save checkpoints. If not + specified, `runner.work_dir` will be used by default. If specified, + the `out_dir` will be the concatenation of `out_dir` and the last + level directory of `runner.work_dir`. + file_client_args (dict): Arguments to instantiate a FileClient. + See :class:`mmcv.fileio.FileClient` for details. Default: None. + **eval_kwargs: Evaluation arguments fed into the evaluate function of + the dataset. + """ + + def __init__(self, + dataloader, + start=None, + interval=1, + by_epoch=True, + save_best=None, + rule=None, + test_fn=None, + greater_keys=None, + less_keys=None, + broadcast_bn_buffer=True, + tmpdir=None, + gpu_collect=False, + out_dir=None, + file_client_args=None, + **eval_kwargs): + + if test_fn is None: + from annotator.uniformer.mmcv.engine import multi_gpu_test + test_fn = multi_gpu_test + + super().__init__( + dataloader, + start=start, + interval=interval, + by_epoch=by_epoch, + save_best=save_best, + rule=rule, + test_fn=test_fn, + greater_keys=greater_keys, + less_keys=less_keys, + out_dir=out_dir, + file_client_args=file_client_args, + **eval_kwargs) + + self.broadcast_bn_buffer = broadcast_bn_buffer + self.tmpdir = tmpdir + self.gpu_collect = gpu_collect + + def _do_evaluate(self, runner): + """perform evaluation and save ckpt.""" + # Synchronization of BatchNorm's buffer (running_mean + # and running_var) is not supported in the DDP of pytorch, + # which may cause the inconsistent performance of models in + # different ranks, so we broadcast BatchNorm's buffers + # of rank 0 to other ranks to avoid this. + if self.broadcast_bn_buffer: + model = runner.model + for name, module in model.named_modules(): + if isinstance(module, + _BatchNorm) and module.track_running_stats: + dist.broadcast(module.running_var, 0) + dist.broadcast(module.running_mean, 0) + + tmpdir = self.tmpdir + if tmpdir is None: + tmpdir = osp.join(runner.work_dir, '.eval_hook') + + results = self.test_fn( + runner.model, + self.dataloader, + tmpdir=tmpdir, + gpu_collect=self.gpu_collect) + if runner.rank == 0: + print('\n') + runner.log_buffer.output['eval_iter_num'] = len(self.dataloader) + key_score = self.evaluate(runner, results) + # the key_score may be `None` so it needs to skip the action to + # save the best checkpoint + if self.save_best and key_score: + self._save_ckpt(runner, key_score) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/hook.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/hook.py new file mode 100644 index 0000000..b8855c1 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/hook.py @@ -0,0 +1,92 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from annotator.uniformer.mmcv.utils import Registry, is_method_overridden + +HOOKS = Registry('hook') + + +class Hook: + stages = ('before_run', 'before_train_epoch', 'before_train_iter', + 'after_train_iter', 'after_train_epoch', 'before_val_epoch', + 'before_val_iter', 'after_val_iter', 'after_val_epoch', + 'after_run') + + def before_run(self, runner): + pass + + def after_run(self, runner): + pass + + def before_epoch(self, runner): + pass + + def after_epoch(self, runner): + pass + + def before_iter(self, runner): + pass + + def after_iter(self, runner): + pass + + def before_train_epoch(self, runner): + self.before_epoch(runner) + + def before_val_epoch(self, runner): + self.before_epoch(runner) + + def after_train_epoch(self, runner): + self.after_epoch(runner) + + def after_val_epoch(self, runner): + self.after_epoch(runner) + + def before_train_iter(self, runner): + self.before_iter(runner) + + def before_val_iter(self, runner): + self.before_iter(runner) + + def after_train_iter(self, runner): + self.after_iter(runner) + + def after_val_iter(self, runner): + self.after_iter(runner) + + def every_n_epochs(self, runner, n): + return (runner.epoch + 1) % n == 0 if n > 0 else False + + def every_n_inner_iters(self, runner, n): + return (runner.inner_iter + 1) % n == 0 if n > 0 else False + + def every_n_iters(self, runner, n): + return (runner.iter + 1) % n == 0 if n > 0 else False + + def end_of_epoch(self, runner): + return runner.inner_iter + 1 == len(runner.data_loader) + + def is_last_epoch(self, runner): + return runner.epoch + 1 == runner._max_epochs + + def is_last_iter(self, runner): + return runner.iter + 1 == runner._max_iters + + def get_triggered_stages(self): + trigger_stages = set() + for stage in Hook.stages: + if is_method_overridden(stage, Hook, self): + trigger_stages.add(stage) + + # some methods will be triggered in multi stages + # use this dict to map method to stages. + method_stages_map = { + 'before_epoch': ['before_train_epoch', 'before_val_epoch'], + 'after_epoch': ['after_train_epoch', 'after_val_epoch'], + 'before_iter': ['before_train_iter', 'before_val_iter'], + 'after_iter': ['after_train_iter', 'after_val_iter'], + } + + for method, map_stages in method_stages_map.items(): + if is_method_overridden(method, Hook, self): + trigger_stages.update(map_stages) + + return [stage for stage in Hook.stages if stage in trigger_stages] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/iter_timer.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/iter_timer.py new file mode 100644 index 0000000..cfd5002 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/iter_timer.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import time + +from .hook import HOOKS, Hook + + +@HOOKS.register_module() +class IterTimerHook(Hook): + + def before_epoch(self, runner): + self.t = time.time() + + def before_iter(self, runner): + runner.log_buffer.update({'data_time': time.time() - self.t}) + + def after_iter(self, runner): + runner.log_buffer.update({'time': time.time() - self.t}) + self.t = time.time() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/__init__.py new file mode 100644 index 0000000..a0b6b34 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base import LoggerHook +from .dvclive import DvcliveLoggerHook +from .mlflow import MlflowLoggerHook +from .neptune import NeptuneLoggerHook +from .pavi import PaviLoggerHook +from .tensorboard import TensorboardLoggerHook +from .text import TextLoggerHook +from .wandb import WandbLoggerHook + +__all__ = [ + 'LoggerHook', 'MlflowLoggerHook', 'PaviLoggerHook', + 'TensorboardLoggerHook', 'TextLoggerHook', 'WandbLoggerHook', + 'NeptuneLoggerHook', 'DvcliveLoggerHook' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/base.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/base.py new file mode 100644 index 0000000..f845256 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/base.py @@ -0,0 +1,166 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numbers +from abc import ABCMeta, abstractmethod + +import numpy as np +import torch + +from ..hook import Hook + + +class LoggerHook(Hook): + """Base class for logger hooks. + + Args: + interval (int): Logging interval (every k iterations). + ignore_last (bool): Ignore the log of last iterations in each epoch + if less than `interval`. + reset_flag (bool): Whether to clear the output buffer after logging. + by_epoch (bool): Whether EpochBasedRunner is used. + """ + + __metaclass__ = ABCMeta + + def __init__(self, + interval=10, + ignore_last=True, + reset_flag=False, + by_epoch=True): + self.interval = interval + self.ignore_last = ignore_last + self.reset_flag = reset_flag + self.by_epoch = by_epoch + + @abstractmethod + def log(self, runner): + pass + + @staticmethod + def is_scalar(val, include_np=True, include_torch=True): + """Tell the input variable is a scalar or not. + + Args: + val: Input variable. + include_np (bool): Whether include 0-d np.ndarray as a scalar. + include_torch (bool): Whether include 0-d torch.Tensor as a scalar. + + Returns: + bool: True or False. + """ + if isinstance(val, numbers.Number): + return True + elif include_np and isinstance(val, np.ndarray) and val.ndim == 0: + return True + elif include_torch and isinstance(val, torch.Tensor) and len(val) == 1: + return True + else: + return False + + def get_mode(self, runner): + if runner.mode == 'train': + if 'time' in runner.log_buffer.output: + mode = 'train' + else: + mode = 'val' + elif runner.mode == 'val': + mode = 'val' + else: + raise ValueError(f"runner mode should be 'train' or 'val', " + f'but got {runner.mode}') + return mode + + def get_epoch(self, runner): + if runner.mode == 'train': + epoch = runner.epoch + 1 + elif runner.mode == 'val': + # normal val mode + # runner.epoch += 1 has been done before val workflow + epoch = runner.epoch + else: + raise ValueError(f"runner mode should be 'train' or 'val', " + f'but got {runner.mode}') + return epoch + + def get_iter(self, runner, inner_iter=False): + """Get the current training iteration step.""" + if self.by_epoch and inner_iter: + current_iter = runner.inner_iter + 1 + else: + current_iter = runner.iter + 1 + return current_iter + + def get_lr_tags(self, runner): + tags = {} + lrs = runner.current_lr() + if isinstance(lrs, dict): + for name, value in lrs.items(): + tags[f'learning_rate/{name}'] = value[0] + else: + tags['learning_rate'] = lrs[0] + return tags + + def get_momentum_tags(self, runner): + tags = {} + momentums = runner.current_momentum() + if isinstance(momentums, dict): + for name, value in momentums.items(): + tags[f'momentum/{name}'] = value[0] + else: + tags['momentum'] = momentums[0] + return tags + + def get_loggable_tags(self, + runner, + allow_scalar=True, + allow_text=False, + add_mode=True, + tags_to_skip=('time', 'data_time')): + tags = {} + for var, val in runner.log_buffer.output.items(): + if var in tags_to_skip: + continue + if self.is_scalar(val) and not allow_scalar: + continue + if isinstance(val, str) and not allow_text: + continue + if add_mode: + var = f'{self.get_mode(runner)}/{var}' + tags[var] = val + tags.update(self.get_lr_tags(runner)) + tags.update(self.get_momentum_tags(runner)) + return tags + + def before_run(self, runner): + for hook in runner.hooks[::-1]: + if isinstance(hook, LoggerHook): + hook.reset_flag = True + break + + def before_epoch(self, runner): + runner.log_buffer.clear() # clear logs of last epoch + + def after_train_iter(self, runner): + if self.by_epoch and self.every_n_inner_iters(runner, self.interval): + runner.log_buffer.average(self.interval) + elif not self.by_epoch and self.every_n_iters(runner, self.interval): + runner.log_buffer.average(self.interval) + elif self.end_of_epoch(runner) and not self.ignore_last: + # not precise but more stable + runner.log_buffer.average(self.interval) + + if runner.log_buffer.ready: + self.log(runner) + if self.reset_flag: + runner.log_buffer.clear_output() + + def after_train_epoch(self, runner): + if runner.log_buffer.ready: + self.log(runner) + if self.reset_flag: + runner.log_buffer.clear_output() + + def after_val_epoch(self, runner): + runner.log_buffer.average() + self.log(runner) + if self.reset_flag: + runner.log_buffer.clear_output() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/dvclive.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/dvclive.py new file mode 100644 index 0000000..687cdc5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/dvclive.py @@ -0,0 +1,58 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ...dist_utils import master_only +from ..hook import HOOKS +from .base import LoggerHook + + +@HOOKS.register_module() +class DvcliveLoggerHook(LoggerHook): + """Class to log metrics with dvclive. + + It requires `dvclive`_ to be installed. + + Args: + path (str): Directory where dvclive will write TSV log files. + interval (int): Logging interval (every k iterations). + Default 10. + ignore_last (bool): Ignore the log of last iterations in each epoch + if less than `interval`. + Default: True. + reset_flag (bool): Whether to clear the output buffer after logging. + Default: True. + by_epoch (bool): Whether EpochBasedRunner is used. + Default: True. + + .. _dvclive: + https://dvc.org/doc/dvclive + """ + + def __init__(self, + path, + interval=10, + ignore_last=True, + reset_flag=True, + by_epoch=True): + + super(DvcliveLoggerHook, self).__init__(interval, ignore_last, + reset_flag, by_epoch) + self.path = path + self.import_dvclive() + + def import_dvclive(self): + try: + import dvclive + except ImportError: + raise ImportError( + 'Please run "pip install dvclive" to install dvclive') + self.dvclive = dvclive + + @master_only + def before_run(self, runner): + self.dvclive.init(self.path) + + @master_only + def log(self, runner): + tags = self.get_loggable_tags(runner) + if tags: + for k, v in tags.items(): + self.dvclive.log(k, v, step=self.get_iter(runner)) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/mlflow.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/mlflow.py new file mode 100644 index 0000000..f9a7259 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/mlflow.py @@ -0,0 +1,78 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ...dist_utils import master_only +from ..hook import HOOKS +from .base import LoggerHook + + +@HOOKS.register_module() +class MlflowLoggerHook(LoggerHook): + + def __init__(self, + exp_name=None, + tags=None, + log_model=True, + interval=10, + ignore_last=True, + reset_flag=False, + by_epoch=True): + """Class to log metrics and (optionally) a trained model to MLflow. + + It requires `MLflow`_ to be installed. + + Args: + exp_name (str, optional): Name of the experiment to be used. + Default None. + If not None, set the active experiment. + If experiment does not exist, an experiment with provided name + will be created. + tags (dict of str: str, optional): Tags for the current run. + Default None. + If not None, set tags for the current run. + log_model (bool, optional): Whether to log an MLflow artifact. + Default True. + If True, log runner.model as an MLflow artifact + for the current run. + interval (int): Logging interval (every k iterations). + ignore_last (bool): Ignore the log of last iterations in each epoch + if less than `interval`. + reset_flag (bool): Whether to clear the output buffer after logging + by_epoch (bool): Whether EpochBasedRunner is used. + + .. _MLflow: + https://www.mlflow.org/docs/latest/index.html + """ + super(MlflowLoggerHook, self).__init__(interval, ignore_last, + reset_flag, by_epoch) + self.import_mlflow() + self.exp_name = exp_name + self.tags = tags + self.log_model = log_model + + def import_mlflow(self): + try: + import mlflow + import mlflow.pytorch as mlflow_pytorch + except ImportError: + raise ImportError( + 'Please run "pip install mlflow" to install mlflow') + self.mlflow = mlflow + self.mlflow_pytorch = mlflow_pytorch + + @master_only + def before_run(self, runner): + super(MlflowLoggerHook, self).before_run(runner) + if self.exp_name is not None: + self.mlflow.set_experiment(self.exp_name) + if self.tags is not None: + self.mlflow.set_tags(self.tags) + + @master_only + def log(self, runner): + tags = self.get_loggable_tags(runner) + if tags: + self.mlflow.log_metrics(tags, step=self.get_iter(runner)) + + @master_only + def after_run(self, runner): + if self.log_model: + self.mlflow_pytorch.log_model(runner.model, 'models') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/neptune.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/neptune.py new file mode 100644 index 0000000..7a38772 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/neptune.py @@ -0,0 +1,82 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ...dist_utils import master_only +from ..hook import HOOKS +from .base import LoggerHook + + +@HOOKS.register_module() +class NeptuneLoggerHook(LoggerHook): + """Class to log metrics to NeptuneAI. + + It requires `neptune-client` to be installed. + + Args: + init_kwargs (dict): a dict contains the initialization keys as below: + - project (str): Name of a project in a form of + namespace/project_name. If None, the value of + NEPTUNE_PROJECT environment variable will be taken. + - api_token (str): User’s API token. + If None, the value of NEPTUNE_API_TOKEN environment + variable will be taken. Note: It is strongly recommended + to use NEPTUNE_API_TOKEN environment variable rather than + placing your API token in plain text in your source code. + - name (str, optional, default is 'Untitled'): Editable name of + the run. Name is displayed in the run's Details and in + Runs table as a column. + Check https://docs.neptune.ai/api-reference/neptune#init for + more init arguments. + interval (int): Logging interval (every k iterations). + ignore_last (bool): Ignore the log of last iterations in each epoch + if less than `interval`. + reset_flag (bool): Whether to clear the output buffer after logging + by_epoch (bool): Whether EpochBasedRunner is used. + + .. _NeptuneAI: + https://docs.neptune.ai/you-should-know/logging-metadata + """ + + def __init__(self, + init_kwargs=None, + interval=10, + ignore_last=True, + reset_flag=True, + with_step=True, + by_epoch=True): + + super(NeptuneLoggerHook, self).__init__(interval, ignore_last, + reset_flag, by_epoch) + self.import_neptune() + self.init_kwargs = init_kwargs + self.with_step = with_step + + def import_neptune(self): + try: + import neptune.new as neptune + except ImportError: + raise ImportError( + 'Please run "pip install neptune-client" to install neptune') + self.neptune = neptune + self.run = None + + @master_only + def before_run(self, runner): + if self.init_kwargs: + self.run = self.neptune.init(**self.init_kwargs) + else: + self.run = self.neptune.init() + + @master_only + def log(self, runner): + tags = self.get_loggable_tags(runner) + if tags: + for tag_name, tag_value in tags.items(): + if self.with_step: + self.run[tag_name].log( + tag_value, step=self.get_iter(runner)) + else: + tags['global_step'] = self.get_iter(runner) + self.run[tag_name].log(tags) + + @master_only + def after_run(self, runner): + self.run.stop() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/pavi.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/pavi.py new file mode 100644 index 0000000..1dcf146 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/pavi.py @@ -0,0 +1,117 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +import os +import os.path as osp + +import torch +import yaml + +import annotator.uniformer.mmcv as mmcv +from ....parallel.utils import is_module_wrapper +from ...dist_utils import master_only +from ..hook import HOOKS +from .base import LoggerHook + + +@HOOKS.register_module() +class PaviLoggerHook(LoggerHook): + + def __init__(self, + init_kwargs=None, + add_graph=False, + add_last_ckpt=False, + interval=10, + ignore_last=True, + reset_flag=False, + by_epoch=True, + img_key='img_info'): + super(PaviLoggerHook, self).__init__(interval, ignore_last, reset_flag, + by_epoch) + self.init_kwargs = init_kwargs + self.add_graph = add_graph + self.add_last_ckpt = add_last_ckpt + self.img_key = img_key + + @master_only + def before_run(self, runner): + super(PaviLoggerHook, self).before_run(runner) + try: + from pavi import SummaryWriter + except ImportError: + raise ImportError('Please run "pip install pavi" to install pavi.') + + self.run_name = runner.work_dir.split('/')[-1] + + if not self.init_kwargs: + self.init_kwargs = dict() + self.init_kwargs['name'] = self.run_name + self.init_kwargs['model'] = runner._model_name + if runner.meta is not None: + if 'config_dict' in runner.meta: + config_dict = runner.meta['config_dict'] + assert isinstance( + config_dict, + dict), ('meta["config_dict"] has to be of a dict, ' + f'but got {type(config_dict)}') + elif 'config_file' in runner.meta: + config_file = runner.meta['config_file'] + config_dict = dict(mmcv.Config.fromfile(config_file)) + else: + config_dict = None + if config_dict is not None: + # 'max_.*iter' is parsed in pavi sdk as the maximum iterations + # to properly set up the progress bar. + config_dict = config_dict.copy() + config_dict.setdefault('max_iter', runner.max_iters) + # non-serializable values are first converted in + # mmcv.dump to json + config_dict = json.loads( + mmcv.dump(config_dict, file_format='json')) + session_text = yaml.dump(config_dict) + self.init_kwargs['session_text'] = session_text + self.writer = SummaryWriter(**self.init_kwargs) + + def get_step(self, runner): + """Get the total training step/epoch.""" + if self.get_mode(runner) == 'val' and self.by_epoch: + return self.get_epoch(runner) + else: + return self.get_iter(runner) + + @master_only + def log(self, runner): + tags = self.get_loggable_tags(runner, add_mode=False) + if tags: + self.writer.add_scalars( + self.get_mode(runner), tags, self.get_step(runner)) + + @master_only + def after_run(self, runner): + if self.add_last_ckpt: + ckpt_path = osp.join(runner.work_dir, 'latest.pth') + if osp.islink(ckpt_path): + ckpt_path = osp.join(runner.work_dir, os.readlink(ckpt_path)) + + if osp.isfile(ckpt_path): + # runner.epoch += 1 has been done before `after_run`. + iteration = runner.epoch if self.by_epoch else runner.iter + return self.writer.add_snapshot_file( + tag=self.run_name, + snapshot_file_path=ckpt_path, + iteration=iteration) + + # flush the buffer and send a task ending signal to Pavi + self.writer.close() + + @master_only + def before_epoch(self, runner): + if runner.epoch == 0 and self.add_graph: + if is_module_wrapper(runner.model): + _model = runner.model.module + else: + _model = runner.model + device = next(_model.parameters()).device + data = next(iter(runner.data_loader)) + image = data[self.img_key][0:1].to(device) + with torch.no_grad(): + self.writer.add_graph(_model, image) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/tensorboard.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/tensorboard.py new file mode 100644 index 0000000..4dd5011 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/tensorboard.py @@ -0,0 +1,57 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp + +from annotator.uniformer.mmcv.utils import TORCH_VERSION, digit_version +from ...dist_utils import master_only +from ..hook import HOOKS +from .base import LoggerHook + + +@HOOKS.register_module() +class TensorboardLoggerHook(LoggerHook): + + def __init__(self, + log_dir=None, + interval=10, + ignore_last=True, + reset_flag=False, + by_epoch=True): + super(TensorboardLoggerHook, self).__init__(interval, ignore_last, + reset_flag, by_epoch) + self.log_dir = log_dir + + @master_only + def before_run(self, runner): + super(TensorboardLoggerHook, self).before_run(runner) + if (TORCH_VERSION == 'parrots' + or digit_version(TORCH_VERSION) < digit_version('1.1')): + try: + from tensorboardX import SummaryWriter + except ImportError: + raise ImportError('Please install tensorboardX to use ' + 'TensorboardLoggerHook.') + else: + try: + from torch.utils.tensorboard import SummaryWriter + except ImportError: + raise ImportError( + 'Please run "pip install future tensorboard" to install ' + 'the dependencies to use torch.utils.tensorboard ' + '(applicable to PyTorch 1.1 or higher)') + + if self.log_dir is None: + self.log_dir = osp.join(runner.work_dir, 'tf_logs') + self.writer = SummaryWriter(self.log_dir) + + @master_only + def log(self, runner): + tags = self.get_loggable_tags(runner, allow_text=True) + for tag, val in tags.items(): + if isinstance(val, str): + self.writer.add_text(tag, val, self.get_iter(runner)) + else: + self.writer.add_scalar(tag, val, self.get_iter(runner)) + + @master_only + def after_run(self, runner): + self.writer.close() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/text.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/text.py new file mode 100644 index 0000000..87b1a3e --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/text.py @@ -0,0 +1,256 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import datetime +import os +import os.path as osp +from collections import OrderedDict + +import torch +import torch.distributed as dist + +import annotator.uniformer.mmcv as mmcv +from annotator.uniformer.mmcv.fileio.file_client import FileClient +from annotator.uniformer.mmcv.utils import is_tuple_of, scandir +from ..hook import HOOKS +from .base import LoggerHook + + +@HOOKS.register_module() +class TextLoggerHook(LoggerHook): + """Logger hook in text. + + In this logger hook, the information will be printed on terminal and + saved in json file. + + Args: + by_epoch (bool, optional): Whether EpochBasedRunner is used. + Default: True. + interval (int, optional): Logging interval (every k iterations). + Default: 10. + ignore_last (bool, optional): Ignore the log of last iterations in each + epoch if less than :attr:`interval`. Default: True. + reset_flag (bool, optional): Whether to clear the output buffer after + logging. Default: False. + interval_exp_name (int, optional): Logging interval for experiment + name. This feature is to help users conveniently get the experiment + information from screen or log file. Default: 1000. + out_dir (str, optional): Logs are saved in ``runner.work_dir`` default. + If ``out_dir`` is specified, logs will be copied to a new directory + which is the concatenation of ``out_dir`` and the last level + directory of ``runner.work_dir``. Default: None. + `New in version 1.3.16.` + out_suffix (str or tuple[str], optional): Those filenames ending with + ``out_suffix`` will be copied to ``out_dir``. + Default: ('.log.json', '.log', '.py'). + `New in version 1.3.16.` + keep_local (bool, optional): Whether to keep local log when + :attr:`out_dir` is specified. If False, the local log will be + removed. Default: True. + `New in version 1.3.16.` + file_client_args (dict, optional): Arguments to instantiate a + FileClient. See :class:`mmcv.fileio.FileClient` for details. + Default: None. + `New in version 1.3.16.` + """ + + def __init__(self, + by_epoch=True, + interval=10, + ignore_last=True, + reset_flag=False, + interval_exp_name=1000, + out_dir=None, + out_suffix=('.log.json', '.log', '.py'), + keep_local=True, + file_client_args=None): + super(TextLoggerHook, self).__init__(interval, ignore_last, reset_flag, + by_epoch) + self.by_epoch = by_epoch + self.time_sec_tot = 0 + self.interval_exp_name = interval_exp_name + + if out_dir is None and file_client_args is not None: + raise ValueError( + 'file_client_args should be "None" when `out_dir` is not' + 'specified.') + self.out_dir = out_dir + + if not (out_dir is None or isinstance(out_dir, str) + or is_tuple_of(out_dir, str)): + raise TypeError('out_dir should be "None" or string or tuple of ' + 'string, but got {out_dir}') + self.out_suffix = out_suffix + + self.keep_local = keep_local + self.file_client_args = file_client_args + if self.out_dir is not None: + self.file_client = FileClient.infer_client(file_client_args, + self.out_dir) + + def before_run(self, runner): + super(TextLoggerHook, self).before_run(runner) + + if self.out_dir is not None: + self.file_client = FileClient.infer_client(self.file_client_args, + self.out_dir) + # The final `self.out_dir` is the concatenation of `self.out_dir` + # and the last level directory of `runner.work_dir` + basename = osp.basename(runner.work_dir.rstrip(osp.sep)) + self.out_dir = self.file_client.join_path(self.out_dir, basename) + runner.logger.info( + (f'Text logs will be saved to {self.out_dir} by ' + f'{self.file_client.name} after the training process.')) + + self.start_iter = runner.iter + self.json_log_path = osp.join(runner.work_dir, + f'{runner.timestamp}.log.json') + if runner.meta is not None: + self._dump_log(runner.meta, runner) + + def _get_max_memory(self, runner): + device = getattr(runner.model, 'output_device', None) + mem = torch.cuda.max_memory_allocated(device=device) + mem_mb = torch.tensor([mem / (1024 * 1024)], + dtype=torch.int, + device=device) + if runner.world_size > 1: + dist.reduce(mem_mb, 0, op=dist.ReduceOp.MAX) + return mem_mb.item() + + def _log_info(self, log_dict, runner): + # print exp name for users to distinguish experiments + # at every ``interval_exp_name`` iterations and the end of each epoch + if runner.meta is not None and 'exp_name' in runner.meta: + if (self.every_n_iters(runner, self.interval_exp_name)) or ( + self.by_epoch and self.end_of_epoch(runner)): + exp_info = f'Exp name: {runner.meta["exp_name"]}' + runner.logger.info(exp_info) + + if log_dict['mode'] == 'train': + if isinstance(log_dict['lr'], dict): + lr_str = [] + for k, val in log_dict['lr'].items(): + lr_str.append(f'lr_{k}: {val:.3e}') + lr_str = ' '.join(lr_str) + else: + lr_str = f'lr: {log_dict["lr"]:.3e}' + + # by epoch: Epoch [4][100/1000] + # by iter: Iter [100/100000] + if self.by_epoch: + log_str = f'Epoch [{log_dict["epoch"]}]' \ + f'[{log_dict["iter"]}/{len(runner.data_loader)}]\t' + else: + log_str = f'Iter [{log_dict["iter"]}/{runner.max_iters}]\t' + log_str += f'{lr_str}, ' + + if 'time' in log_dict.keys(): + self.time_sec_tot += (log_dict['time'] * self.interval) + time_sec_avg = self.time_sec_tot / ( + runner.iter - self.start_iter + 1) + eta_sec = time_sec_avg * (runner.max_iters - runner.iter - 1) + eta_str = str(datetime.timedelta(seconds=int(eta_sec))) + log_str += f'eta: {eta_str}, ' + log_str += f'time: {log_dict["time"]:.3f}, ' \ + f'data_time: {log_dict["data_time"]:.3f}, ' + # statistic memory + if torch.cuda.is_available(): + log_str += f'memory: {log_dict["memory"]}, ' + else: + # val/test time + # here 1000 is the length of the val dataloader + # by epoch: Epoch[val] [4][1000] + # by iter: Iter[val] [1000] + if self.by_epoch: + log_str = f'Epoch({log_dict["mode"]}) ' \ + f'[{log_dict["epoch"]}][{log_dict["iter"]}]\t' + else: + log_str = f'Iter({log_dict["mode"]}) [{log_dict["iter"]}]\t' + + log_items = [] + for name, val in log_dict.items(): + # TODO: resolve this hack + # these items have been in log_str + if name in [ + 'mode', 'Epoch', 'iter', 'lr', 'time', 'data_time', + 'memory', 'epoch' + ]: + continue + if isinstance(val, float): + val = f'{val:.4f}' + log_items.append(f'{name}: {val}') + log_str += ', '.join(log_items) + + runner.logger.info(log_str) + + def _dump_log(self, log_dict, runner): + # dump log in json format + json_log = OrderedDict() + for k, v in log_dict.items(): + json_log[k] = self._round_float(v) + # only append log at last line + if runner.rank == 0: + with open(self.json_log_path, 'a+') as f: + mmcv.dump(json_log, f, file_format='json') + f.write('\n') + + def _round_float(self, items): + if isinstance(items, list): + return [self._round_float(item) for item in items] + elif isinstance(items, float): + return round(items, 5) + else: + return items + + def log(self, runner): + if 'eval_iter_num' in runner.log_buffer.output: + # this doesn't modify runner.iter and is regardless of by_epoch + cur_iter = runner.log_buffer.output.pop('eval_iter_num') + else: + cur_iter = self.get_iter(runner, inner_iter=True) + + log_dict = OrderedDict( + mode=self.get_mode(runner), + epoch=self.get_epoch(runner), + iter=cur_iter) + + # only record lr of the first param group + cur_lr = runner.current_lr() + if isinstance(cur_lr, list): + log_dict['lr'] = cur_lr[0] + else: + assert isinstance(cur_lr, dict) + log_dict['lr'] = {} + for k, lr_ in cur_lr.items(): + assert isinstance(lr_, list) + log_dict['lr'].update({k: lr_[0]}) + + if 'time' in runner.log_buffer.output: + # statistic memory + if torch.cuda.is_available(): + log_dict['memory'] = self._get_max_memory(runner) + + log_dict = dict(log_dict, **runner.log_buffer.output) + + self._log_info(log_dict, runner) + self._dump_log(log_dict, runner) + return log_dict + + def after_run(self, runner): + # copy or upload logs to self.out_dir + if self.out_dir is not None: + for filename in scandir(runner.work_dir, self.out_suffix, True): + local_filepath = osp.join(runner.work_dir, filename) + out_filepath = self.file_client.join_path( + self.out_dir, filename) + with open(local_filepath, 'r') as f: + self.file_client.put_text(f.read(), out_filepath) + + runner.logger.info( + (f'The file {local_filepath} has been uploaded to ' + f'{out_filepath}.')) + + if not self.keep_local: + os.remove(local_filepath) + runner.logger.info( + (f'{local_filepath} was removed due to the ' + '`self.keep_local=False`')) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/wandb.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/wandb.py new file mode 100644 index 0000000..9f68084 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/logger/wandb.py @@ -0,0 +1,56 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ...dist_utils import master_only +from ..hook import HOOKS +from .base import LoggerHook + + +@HOOKS.register_module() +class WandbLoggerHook(LoggerHook): + + def __init__(self, + init_kwargs=None, + interval=10, + ignore_last=True, + reset_flag=False, + commit=True, + by_epoch=True, + with_step=True): + super(WandbLoggerHook, self).__init__(interval, ignore_last, + reset_flag, by_epoch) + self.import_wandb() + self.init_kwargs = init_kwargs + self.commit = commit + self.with_step = with_step + + def import_wandb(self): + try: + import wandb + except ImportError: + raise ImportError( + 'Please run "pip install wandb" to install wandb') + self.wandb = wandb + + @master_only + def before_run(self, runner): + super(WandbLoggerHook, self).before_run(runner) + if self.wandb is None: + self.import_wandb() + if self.init_kwargs: + self.wandb.init(**self.init_kwargs) + else: + self.wandb.init() + + @master_only + def log(self, runner): + tags = self.get_loggable_tags(runner) + if tags: + if self.with_step: + self.wandb.log( + tags, step=self.get_iter(runner), commit=self.commit) + else: + tags['global_step'] = self.get_iter(runner) + self.wandb.log(tags, commit=self.commit) + + @master_only + def after_run(self, runner): + self.wandb.join() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/lr_updater.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/lr_updater.py new file mode 100644 index 0000000..6365908 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/lr_updater.py @@ -0,0 +1,670 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numbers +from math import cos, pi + +import annotator.uniformer.mmcv as mmcv +from .hook import HOOKS, Hook + + +class LrUpdaterHook(Hook): + """LR Scheduler in MMCV. + + Args: + by_epoch (bool): LR changes epoch by epoch + warmup (string): Type of warmup used. It can be None(use no warmup), + 'constant', 'linear' or 'exp' + warmup_iters (int): The number of iterations or epochs that warmup + lasts + warmup_ratio (float): LR used at the beginning of warmup equals to + warmup_ratio * initial_lr + warmup_by_epoch (bool): When warmup_by_epoch == True, warmup_iters + means the number of epochs that warmup lasts, otherwise means the + number of iteration that warmup lasts + """ + + def __init__(self, + by_epoch=True, + warmup=None, + warmup_iters=0, + warmup_ratio=0.1, + warmup_by_epoch=False): + # validate the "warmup" argument + if warmup is not None: + if warmup not in ['constant', 'linear', 'exp']: + raise ValueError( + f'"{warmup}" is not a supported type for warming up, valid' + ' types are "constant" and "linear"') + if warmup is not None: + assert warmup_iters > 0, \ + '"warmup_iters" must be a positive integer' + assert 0 < warmup_ratio <= 1.0, \ + '"warmup_ratio" must be in range (0,1]' + + self.by_epoch = by_epoch + self.warmup = warmup + self.warmup_iters = warmup_iters + self.warmup_ratio = warmup_ratio + self.warmup_by_epoch = warmup_by_epoch + + if self.warmup_by_epoch: + self.warmup_epochs = self.warmup_iters + self.warmup_iters = None + else: + self.warmup_epochs = None + + self.base_lr = [] # initial lr for all param groups + self.regular_lr = [] # expected lr if no warming up is performed + + def _set_lr(self, runner, lr_groups): + if isinstance(runner.optimizer, dict): + for k, optim in runner.optimizer.items(): + for param_group, lr in zip(optim.param_groups, lr_groups[k]): + param_group['lr'] = lr + else: + for param_group, lr in zip(runner.optimizer.param_groups, + lr_groups): + param_group['lr'] = lr + + def get_lr(self, runner, base_lr): + raise NotImplementedError + + def get_regular_lr(self, runner): + if isinstance(runner.optimizer, dict): + lr_groups = {} + for k in runner.optimizer.keys(): + _lr_group = [ + self.get_lr(runner, _base_lr) + for _base_lr in self.base_lr[k] + ] + lr_groups.update({k: _lr_group}) + + return lr_groups + else: + return [self.get_lr(runner, _base_lr) for _base_lr in self.base_lr] + + def get_warmup_lr(self, cur_iters): + + def _get_warmup_lr(cur_iters, regular_lr): + if self.warmup == 'constant': + warmup_lr = [_lr * self.warmup_ratio for _lr in regular_lr] + elif self.warmup == 'linear': + k = (1 - cur_iters / self.warmup_iters) * (1 - + self.warmup_ratio) + warmup_lr = [_lr * (1 - k) for _lr in regular_lr] + elif self.warmup == 'exp': + k = self.warmup_ratio**(1 - cur_iters / self.warmup_iters) + warmup_lr = [_lr * k for _lr in regular_lr] + return warmup_lr + + if isinstance(self.regular_lr, dict): + lr_groups = {} + for key, regular_lr in self.regular_lr.items(): + lr_groups[key] = _get_warmup_lr(cur_iters, regular_lr) + return lr_groups + else: + return _get_warmup_lr(cur_iters, self.regular_lr) + + def before_run(self, runner): + # NOTE: when resuming from a checkpoint, if 'initial_lr' is not saved, + # it will be set according to the optimizer params + if isinstance(runner.optimizer, dict): + self.base_lr = {} + for k, optim in runner.optimizer.items(): + for group in optim.param_groups: + group.setdefault('initial_lr', group['lr']) + _base_lr = [ + group['initial_lr'] for group in optim.param_groups + ] + self.base_lr.update({k: _base_lr}) + else: + for group in runner.optimizer.param_groups: + group.setdefault('initial_lr', group['lr']) + self.base_lr = [ + group['initial_lr'] for group in runner.optimizer.param_groups + ] + + def before_train_epoch(self, runner): + if self.warmup_iters is None: + epoch_len = len(runner.data_loader) + self.warmup_iters = self.warmup_epochs * epoch_len + + if not self.by_epoch: + return + + self.regular_lr = self.get_regular_lr(runner) + self._set_lr(runner, self.regular_lr) + + def before_train_iter(self, runner): + cur_iter = runner.iter + if not self.by_epoch: + self.regular_lr = self.get_regular_lr(runner) + if self.warmup is None or cur_iter >= self.warmup_iters: + self._set_lr(runner, self.regular_lr) + else: + warmup_lr = self.get_warmup_lr(cur_iter) + self._set_lr(runner, warmup_lr) + elif self.by_epoch: + if self.warmup is None or cur_iter > self.warmup_iters: + return + elif cur_iter == self.warmup_iters: + self._set_lr(runner, self.regular_lr) + else: + warmup_lr = self.get_warmup_lr(cur_iter) + self._set_lr(runner, warmup_lr) + + +@HOOKS.register_module() +class FixedLrUpdaterHook(LrUpdaterHook): + + def __init__(self, **kwargs): + super(FixedLrUpdaterHook, self).__init__(**kwargs) + + def get_lr(self, runner, base_lr): + return base_lr + + +@HOOKS.register_module() +class StepLrUpdaterHook(LrUpdaterHook): + """Step LR scheduler with min_lr clipping. + + Args: + step (int | list[int]): Step to decay the LR. If an int value is given, + regard it as the decay interval. If a list is given, decay LR at + these steps. + gamma (float, optional): Decay LR ratio. Default: 0.1. + min_lr (float, optional): Minimum LR value to keep. If LR after decay + is lower than `min_lr`, it will be clipped to this value. If None + is given, we don't perform lr clipping. Default: None. + """ + + def __init__(self, step, gamma=0.1, min_lr=None, **kwargs): + if isinstance(step, list): + assert mmcv.is_list_of(step, int) + assert all([s > 0 for s in step]) + elif isinstance(step, int): + assert step > 0 + else: + raise TypeError('"step" must be a list or integer') + self.step = step + self.gamma = gamma + self.min_lr = min_lr + super(StepLrUpdaterHook, self).__init__(**kwargs) + + def get_lr(self, runner, base_lr): + progress = runner.epoch if self.by_epoch else runner.iter + + # calculate exponential term + if isinstance(self.step, int): + exp = progress // self.step + else: + exp = len(self.step) + for i, s in enumerate(self.step): + if progress < s: + exp = i + break + + lr = base_lr * (self.gamma**exp) + if self.min_lr is not None: + # clip to a minimum value + lr = max(lr, self.min_lr) + return lr + + +@HOOKS.register_module() +class ExpLrUpdaterHook(LrUpdaterHook): + + def __init__(self, gamma, **kwargs): + self.gamma = gamma + super(ExpLrUpdaterHook, self).__init__(**kwargs) + + def get_lr(self, runner, base_lr): + progress = runner.epoch if self.by_epoch else runner.iter + return base_lr * self.gamma**progress + + +@HOOKS.register_module() +class PolyLrUpdaterHook(LrUpdaterHook): + + def __init__(self, power=1., min_lr=0., **kwargs): + self.power = power + self.min_lr = min_lr + super(PolyLrUpdaterHook, self).__init__(**kwargs) + + def get_lr(self, runner, base_lr): + if self.by_epoch: + progress = runner.epoch + max_progress = runner.max_epochs + else: + progress = runner.iter + max_progress = runner.max_iters + coeff = (1 - progress / max_progress)**self.power + return (base_lr - self.min_lr) * coeff + self.min_lr + + +@HOOKS.register_module() +class InvLrUpdaterHook(LrUpdaterHook): + + def __init__(self, gamma, power=1., **kwargs): + self.gamma = gamma + self.power = power + super(InvLrUpdaterHook, self).__init__(**kwargs) + + def get_lr(self, runner, base_lr): + progress = runner.epoch if self.by_epoch else runner.iter + return base_lr * (1 + self.gamma * progress)**(-self.power) + + +@HOOKS.register_module() +class CosineAnnealingLrUpdaterHook(LrUpdaterHook): + + def __init__(self, min_lr=None, min_lr_ratio=None, **kwargs): + assert (min_lr is None) ^ (min_lr_ratio is None) + self.min_lr = min_lr + self.min_lr_ratio = min_lr_ratio + super(CosineAnnealingLrUpdaterHook, self).__init__(**kwargs) + + def get_lr(self, runner, base_lr): + if self.by_epoch: + progress = runner.epoch + max_progress = runner.max_epochs + else: + progress = runner.iter + max_progress = runner.max_iters + + if self.min_lr_ratio is not None: + target_lr = base_lr * self.min_lr_ratio + else: + target_lr = self.min_lr + return annealing_cos(base_lr, target_lr, progress / max_progress) + + +@HOOKS.register_module() +class FlatCosineAnnealingLrUpdaterHook(LrUpdaterHook): + """Flat + Cosine lr schedule. + + Modified from https://github.com/fastai/fastai/blob/master/fastai/callback/schedule.py#L128 # noqa: E501 + + Args: + start_percent (float): When to start annealing the learning rate + after the percentage of the total training steps. + The value should be in range [0, 1). + Default: 0.75 + min_lr (float, optional): The minimum lr. Default: None. + min_lr_ratio (float, optional): The ratio of minimum lr to the base lr. + Either `min_lr` or `min_lr_ratio` should be specified. + Default: None. + """ + + def __init__(self, + start_percent=0.75, + min_lr=None, + min_lr_ratio=None, + **kwargs): + assert (min_lr is None) ^ (min_lr_ratio is None) + if start_percent < 0 or start_percent > 1 or not isinstance( + start_percent, float): + raise ValueError( + 'expected float between 0 and 1 start_percent, but ' + f'got {start_percent}') + self.start_percent = start_percent + self.min_lr = min_lr + self.min_lr_ratio = min_lr_ratio + super(FlatCosineAnnealingLrUpdaterHook, self).__init__(**kwargs) + + def get_lr(self, runner, base_lr): + if self.by_epoch: + start = round(runner.max_epochs * self.start_percent) + progress = runner.epoch - start + max_progress = runner.max_epochs - start + else: + start = round(runner.max_iters * self.start_percent) + progress = runner.iter - start + max_progress = runner.max_iters - start + + if self.min_lr_ratio is not None: + target_lr = base_lr * self.min_lr_ratio + else: + target_lr = self.min_lr + + if progress < 0: + return base_lr + else: + return annealing_cos(base_lr, target_lr, progress / max_progress) + + +@HOOKS.register_module() +class CosineRestartLrUpdaterHook(LrUpdaterHook): + """Cosine annealing with restarts learning rate scheme. + + Args: + periods (list[int]): Periods for each cosine anneling cycle. + restart_weights (list[float], optional): Restart weights at each + restart iteration. Default: [1]. + min_lr (float, optional): The minimum lr. Default: None. + min_lr_ratio (float, optional): The ratio of minimum lr to the base lr. + Either `min_lr` or `min_lr_ratio` should be specified. + Default: None. + """ + + def __init__(self, + periods, + restart_weights=[1], + min_lr=None, + min_lr_ratio=None, + **kwargs): + assert (min_lr is None) ^ (min_lr_ratio is None) + self.periods = periods + self.min_lr = min_lr + self.min_lr_ratio = min_lr_ratio + self.restart_weights = restart_weights + assert (len(self.periods) == len(self.restart_weights) + ), 'periods and restart_weights should have the same length.' + super(CosineRestartLrUpdaterHook, self).__init__(**kwargs) + + self.cumulative_periods = [ + sum(self.periods[0:i + 1]) for i in range(0, len(self.periods)) + ] + + def get_lr(self, runner, base_lr): + if self.by_epoch: + progress = runner.epoch + else: + progress = runner.iter + + if self.min_lr_ratio is not None: + target_lr = base_lr * self.min_lr_ratio + else: + target_lr = self.min_lr + + idx = get_position_from_periods(progress, self.cumulative_periods) + current_weight = self.restart_weights[idx] + nearest_restart = 0 if idx == 0 else self.cumulative_periods[idx - 1] + current_periods = self.periods[idx] + + alpha = min((progress - nearest_restart) / current_periods, 1) + return annealing_cos(base_lr, target_lr, alpha, current_weight) + + +def get_position_from_periods(iteration, cumulative_periods): + """Get the position from a period list. + + It will return the index of the right-closest number in the period list. + For example, the cumulative_periods = [100, 200, 300, 400], + if iteration == 50, return 0; + if iteration == 210, return 2; + if iteration == 300, return 3. + + Args: + iteration (int): Current iteration. + cumulative_periods (list[int]): Cumulative period list. + + Returns: + int: The position of the right-closest number in the period list. + """ + for i, period in enumerate(cumulative_periods): + if iteration < period: + return i + raise ValueError(f'Current iteration {iteration} exceeds ' + f'cumulative_periods {cumulative_periods}') + + +@HOOKS.register_module() +class CyclicLrUpdaterHook(LrUpdaterHook): + """Cyclic LR Scheduler. + + Implement the cyclical learning rate policy (CLR) described in + https://arxiv.org/pdf/1506.01186.pdf + + Different from the original paper, we use cosine annealing rather than + triangular policy inside a cycle. This improves the performance in the + 3D detection area. + + Args: + by_epoch (bool): Whether to update LR by epoch. + target_ratio (tuple[float]): Relative ratio of the highest LR and the + lowest LR to the initial LR. + cyclic_times (int): Number of cycles during training + step_ratio_up (float): The ratio of the increasing process of LR in + the total cycle. + anneal_strategy (str): {'cos', 'linear'} + Specifies the annealing strategy: 'cos' for cosine annealing, + 'linear' for linear annealing. Default: 'cos'. + """ + + def __init__(self, + by_epoch=False, + target_ratio=(10, 1e-4), + cyclic_times=1, + step_ratio_up=0.4, + anneal_strategy='cos', + **kwargs): + if isinstance(target_ratio, float): + target_ratio = (target_ratio, target_ratio / 1e5) + elif isinstance(target_ratio, tuple): + target_ratio = (target_ratio[0], target_ratio[0] / 1e5) \ + if len(target_ratio) == 1 else target_ratio + else: + raise ValueError('target_ratio should be either float ' + f'or tuple, got {type(target_ratio)}') + + assert len(target_ratio) == 2, \ + '"target_ratio" must be list or tuple of two floats' + assert 0 <= step_ratio_up < 1.0, \ + '"step_ratio_up" must be in range [0,1)' + + self.target_ratio = target_ratio + self.cyclic_times = cyclic_times + self.step_ratio_up = step_ratio_up + self.lr_phases = [] # init lr_phases + # validate anneal_strategy + if anneal_strategy not in ['cos', 'linear']: + raise ValueError('anneal_strategy must be one of "cos" or ' + f'"linear", instead got {anneal_strategy}') + elif anneal_strategy == 'cos': + self.anneal_func = annealing_cos + elif anneal_strategy == 'linear': + self.anneal_func = annealing_linear + + assert not by_epoch, \ + 'currently only support "by_epoch" = False' + super(CyclicLrUpdaterHook, self).__init__(by_epoch, **kwargs) + + def before_run(self, runner): + super(CyclicLrUpdaterHook, self).before_run(runner) + # initiate lr_phases + # total lr_phases are separated as up and down + max_iter_per_phase = runner.max_iters // self.cyclic_times + iter_up_phase = int(self.step_ratio_up * max_iter_per_phase) + self.lr_phases.append( + [0, iter_up_phase, max_iter_per_phase, 1, self.target_ratio[0]]) + self.lr_phases.append([ + iter_up_phase, max_iter_per_phase, max_iter_per_phase, + self.target_ratio[0], self.target_ratio[1] + ]) + + def get_lr(self, runner, base_lr): + curr_iter = runner.iter + for (start_iter, end_iter, max_iter_per_phase, start_ratio, + end_ratio) in self.lr_phases: + curr_iter %= max_iter_per_phase + if start_iter <= curr_iter < end_iter: + progress = curr_iter - start_iter + return self.anneal_func(base_lr * start_ratio, + base_lr * end_ratio, + progress / (end_iter - start_iter)) + + +@HOOKS.register_module() +class OneCycleLrUpdaterHook(LrUpdaterHook): + """One Cycle LR Scheduler. + + The 1cycle learning rate policy changes the learning rate after every + batch. The one cycle learning rate policy is described in + https://arxiv.org/pdf/1708.07120.pdf + + Args: + max_lr (float or list): Upper learning rate boundaries in the cycle + for each parameter group. + total_steps (int, optional): The total number of steps in the cycle. + Note that if a value is not provided here, it will be the max_iter + of runner. Default: None. + pct_start (float): The percentage of the cycle (in number of steps) + spent increasing the learning rate. + Default: 0.3 + anneal_strategy (str): {'cos', 'linear'} + Specifies the annealing strategy: 'cos' for cosine annealing, + 'linear' for linear annealing. + Default: 'cos' + div_factor (float): Determines the initial learning rate via + initial_lr = max_lr/div_factor + Default: 25 + final_div_factor (float): Determines the minimum learning rate via + min_lr = initial_lr/final_div_factor + Default: 1e4 + three_phase (bool): If three_phase is True, use a third phase of the + schedule to annihilate the learning rate according to + final_div_factor instead of modifying the second phase (the first + two phases will be symmetrical about the step indicated by + pct_start). + Default: False + """ + + def __init__(self, + max_lr, + total_steps=None, + pct_start=0.3, + anneal_strategy='cos', + div_factor=25, + final_div_factor=1e4, + three_phase=False, + **kwargs): + # validate by_epoch, currently only support by_epoch = False + if 'by_epoch' not in kwargs: + kwargs['by_epoch'] = False + else: + assert not kwargs['by_epoch'], \ + 'currently only support "by_epoch" = False' + if not isinstance(max_lr, (numbers.Number, list, dict)): + raise ValueError('the type of max_lr must be the one of list or ' + f'dict, but got {type(max_lr)}') + self._max_lr = max_lr + if total_steps is not None: + if not isinstance(total_steps, int): + raise ValueError('the type of total_steps must be int, but' + f'got {type(total_steps)}') + self.total_steps = total_steps + # validate pct_start + if pct_start < 0 or pct_start > 1 or not isinstance(pct_start, float): + raise ValueError('expected float between 0 and 1 pct_start, but ' + f'got {pct_start}') + self.pct_start = pct_start + # validate anneal_strategy + if anneal_strategy not in ['cos', 'linear']: + raise ValueError('anneal_strategy must be one of "cos" or ' + f'"linear", instead got {anneal_strategy}') + elif anneal_strategy == 'cos': + self.anneal_func = annealing_cos + elif anneal_strategy == 'linear': + self.anneal_func = annealing_linear + self.div_factor = div_factor + self.final_div_factor = final_div_factor + self.three_phase = three_phase + self.lr_phases = [] # init lr_phases + super(OneCycleLrUpdaterHook, self).__init__(**kwargs) + + def before_run(self, runner): + if hasattr(self, 'total_steps'): + total_steps = self.total_steps + else: + total_steps = runner.max_iters + if total_steps < runner.max_iters: + raise ValueError( + 'The total steps must be greater than or equal to max ' + f'iterations {runner.max_iters} of runner, but total steps ' + f'is {total_steps}.') + + if isinstance(runner.optimizer, dict): + self.base_lr = {} + for k, optim in runner.optimizer.items(): + _max_lr = format_param(k, optim, self._max_lr) + self.base_lr[k] = [lr / self.div_factor for lr in _max_lr] + for group, lr in zip(optim.param_groups, self.base_lr[k]): + group.setdefault('initial_lr', lr) + else: + k = type(runner.optimizer).__name__ + _max_lr = format_param(k, runner.optimizer, self._max_lr) + self.base_lr = [lr / self.div_factor for lr in _max_lr] + for group, lr in zip(runner.optimizer.param_groups, self.base_lr): + group.setdefault('initial_lr', lr) + + if self.three_phase: + self.lr_phases.append( + [float(self.pct_start * total_steps) - 1, 1, self.div_factor]) + self.lr_phases.append([ + float(2 * self.pct_start * total_steps) - 2, self.div_factor, 1 + ]) + self.lr_phases.append( + [total_steps - 1, 1, 1 / self.final_div_factor]) + else: + self.lr_phases.append( + [float(self.pct_start * total_steps) - 1, 1, self.div_factor]) + self.lr_phases.append( + [total_steps - 1, self.div_factor, 1 / self.final_div_factor]) + + def get_lr(self, runner, base_lr): + curr_iter = runner.iter + start_iter = 0 + for i, (end_iter, start_lr, end_lr) in enumerate(self.lr_phases): + if curr_iter <= end_iter: + pct = (curr_iter - start_iter) / (end_iter - start_iter) + lr = self.anneal_func(base_lr * start_lr, base_lr * end_lr, + pct) + break + start_iter = end_iter + return lr + + +def annealing_cos(start, end, factor, weight=1): + """Calculate annealing cos learning rate. + + Cosine anneal from `weight * start + (1 - weight) * end` to `end` as + percentage goes from 0.0 to 1.0. + + Args: + start (float): The starting learning rate of the cosine annealing. + end (float): The ending learing rate of the cosine annealing. + factor (float): The coefficient of `pi` when calculating the current + percentage. Range from 0.0 to 1.0. + weight (float, optional): The combination factor of `start` and `end` + when calculating the actual starting learning rate. Default to 1. + """ + cos_out = cos(pi * factor) + 1 + return end + 0.5 * weight * (start - end) * cos_out + + +def annealing_linear(start, end, factor): + """Calculate annealing linear learning rate. + + Linear anneal from `start` to `end` as percentage goes from 0.0 to 1.0. + + Args: + start (float): The starting learning rate of the linear annealing. + end (float): The ending learing rate of the linear annealing. + factor (float): The coefficient of `pi` when calculating the current + percentage. Range from 0.0 to 1.0. + """ + return start + (end - start) * factor + + +def format_param(name, optim, param): + if isinstance(param, numbers.Number): + return [param] * len(optim.param_groups) + elif isinstance(param, (list, tuple)): # multi param groups + if len(param) != len(optim.param_groups): + raise ValueError(f'expected {len(optim.param_groups)} ' + f'values for {name}, got {len(param)}') + return param + else: # multi optimizers + if name not in param: + raise KeyError(f'{name} is not found in {param.keys()}') + return param[name] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/memory.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/memory.py new file mode 100644 index 0000000..70cf9a8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/memory.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from .hook import HOOKS, Hook + + +@HOOKS.register_module() +class EmptyCacheHook(Hook): + + def __init__(self, before_epoch=False, after_epoch=True, after_iter=False): + self._before_epoch = before_epoch + self._after_epoch = after_epoch + self._after_iter = after_iter + + def after_iter(self, runner): + if self._after_iter: + torch.cuda.empty_cache() + + def before_epoch(self, runner): + if self._before_epoch: + torch.cuda.empty_cache() + + def after_epoch(self, runner): + if self._after_epoch: + torch.cuda.empty_cache() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/momentum_updater.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/momentum_updater.py new file mode 100644 index 0000000..6043775 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/momentum_updater.py @@ -0,0 +1,493 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import annotator.uniformer.mmcv as mmcv +from .hook import HOOKS, Hook +from .lr_updater import annealing_cos, annealing_linear, format_param + + +class MomentumUpdaterHook(Hook): + + def __init__(self, + by_epoch=True, + warmup=None, + warmup_iters=0, + warmup_ratio=0.9): + # validate the "warmup" argument + if warmup is not None: + if warmup not in ['constant', 'linear', 'exp']: + raise ValueError( + f'"{warmup}" is not a supported type for warming up, valid' + ' types are "constant" and "linear"') + if warmup is not None: + assert warmup_iters > 0, \ + '"warmup_iters" must be a positive integer' + assert 0 < warmup_ratio <= 1.0, \ + '"warmup_momentum" must be in range (0,1]' + + self.by_epoch = by_epoch + self.warmup = warmup + self.warmup_iters = warmup_iters + self.warmup_ratio = warmup_ratio + + self.base_momentum = [] # initial momentum for all param groups + self.regular_momentum = [ + ] # expected momentum if no warming up is performed + + def _set_momentum(self, runner, momentum_groups): + if isinstance(runner.optimizer, dict): + for k, optim in runner.optimizer.items(): + for param_group, mom in zip(optim.param_groups, + momentum_groups[k]): + if 'momentum' in param_group.keys(): + param_group['momentum'] = mom + elif 'betas' in param_group.keys(): + param_group['betas'] = (mom, param_group['betas'][1]) + else: + for param_group, mom in zip(runner.optimizer.param_groups, + momentum_groups): + if 'momentum' in param_group.keys(): + param_group['momentum'] = mom + elif 'betas' in param_group.keys(): + param_group['betas'] = (mom, param_group['betas'][1]) + + def get_momentum(self, runner, base_momentum): + raise NotImplementedError + + def get_regular_momentum(self, runner): + if isinstance(runner.optimizer, dict): + momentum_groups = {} + for k in runner.optimizer.keys(): + _momentum_group = [ + self.get_momentum(runner, _base_momentum) + for _base_momentum in self.base_momentum[k] + ] + momentum_groups.update({k: _momentum_group}) + return momentum_groups + else: + return [ + self.get_momentum(runner, _base_momentum) + for _base_momentum in self.base_momentum + ] + + def get_warmup_momentum(self, cur_iters): + + def _get_warmup_momentum(cur_iters, regular_momentum): + if self.warmup == 'constant': + warmup_momentum = [ + _momentum / self.warmup_ratio + for _momentum in self.regular_momentum + ] + elif self.warmup == 'linear': + k = (1 - cur_iters / self.warmup_iters) * (1 - + self.warmup_ratio) + warmup_momentum = [ + _momentum / (1 - k) for _momentum in self.regular_mom + ] + elif self.warmup == 'exp': + k = self.warmup_ratio**(1 - cur_iters / self.warmup_iters) + warmup_momentum = [ + _momentum / k for _momentum in self.regular_mom + ] + return warmup_momentum + + if isinstance(self.regular_momentum, dict): + momentum_groups = {} + for key, regular_momentum in self.regular_momentum.items(): + momentum_groups[key] = _get_warmup_momentum( + cur_iters, regular_momentum) + return momentum_groups + else: + return _get_warmup_momentum(cur_iters, self.regular_momentum) + + def before_run(self, runner): + # NOTE: when resuming from a checkpoint, + # if 'initial_momentum' is not saved, + # it will be set according to the optimizer params + if isinstance(runner.optimizer, dict): + self.base_momentum = {} + for k, optim in runner.optimizer.items(): + for group in optim.param_groups: + if 'momentum' in group.keys(): + group.setdefault('initial_momentum', group['momentum']) + else: + group.setdefault('initial_momentum', group['betas'][0]) + _base_momentum = [ + group['initial_momentum'] for group in optim.param_groups + ] + self.base_momentum.update({k: _base_momentum}) + else: + for group in runner.optimizer.param_groups: + if 'momentum' in group.keys(): + group.setdefault('initial_momentum', group['momentum']) + else: + group.setdefault('initial_momentum', group['betas'][0]) + self.base_momentum = [ + group['initial_momentum'] + for group in runner.optimizer.param_groups + ] + + def before_train_epoch(self, runner): + if not self.by_epoch: + return + self.regular_mom = self.get_regular_momentum(runner) + self._set_momentum(runner, self.regular_mom) + + def before_train_iter(self, runner): + cur_iter = runner.iter + if not self.by_epoch: + self.regular_mom = self.get_regular_momentum(runner) + if self.warmup is None or cur_iter >= self.warmup_iters: + self._set_momentum(runner, self.regular_mom) + else: + warmup_momentum = self.get_warmup_momentum(cur_iter) + self._set_momentum(runner, warmup_momentum) + elif self.by_epoch: + if self.warmup is None or cur_iter > self.warmup_iters: + return + elif cur_iter == self.warmup_iters: + self._set_momentum(runner, self.regular_mom) + else: + warmup_momentum = self.get_warmup_momentum(cur_iter) + self._set_momentum(runner, warmup_momentum) + + +@HOOKS.register_module() +class StepMomentumUpdaterHook(MomentumUpdaterHook): + """Step momentum scheduler with min value clipping. + + Args: + step (int | list[int]): Step to decay the momentum. If an int value is + given, regard it as the decay interval. If a list is given, decay + momentum at these steps. + gamma (float, optional): Decay momentum ratio. Default: 0.5. + min_momentum (float, optional): Minimum momentum value to keep. If + momentum after decay is lower than this value, it will be clipped + accordingly. If None is given, we don't perform lr clipping. + Default: None. + """ + + def __init__(self, step, gamma=0.5, min_momentum=None, **kwargs): + if isinstance(step, list): + assert mmcv.is_list_of(step, int) + assert all([s > 0 for s in step]) + elif isinstance(step, int): + assert step > 0 + else: + raise TypeError('"step" must be a list or integer') + self.step = step + self.gamma = gamma + self.min_momentum = min_momentum + super(StepMomentumUpdaterHook, self).__init__(**kwargs) + + def get_momentum(self, runner, base_momentum): + progress = runner.epoch if self.by_epoch else runner.iter + + # calculate exponential term + if isinstance(self.step, int): + exp = progress // self.step + else: + exp = len(self.step) + for i, s in enumerate(self.step): + if progress < s: + exp = i + break + + momentum = base_momentum * (self.gamma**exp) + if self.min_momentum is not None: + # clip to a minimum value + momentum = max(momentum, self.min_momentum) + return momentum + + +@HOOKS.register_module() +class CosineAnnealingMomentumUpdaterHook(MomentumUpdaterHook): + + def __init__(self, min_momentum=None, min_momentum_ratio=None, **kwargs): + assert (min_momentum is None) ^ (min_momentum_ratio is None) + self.min_momentum = min_momentum + self.min_momentum_ratio = min_momentum_ratio + super(CosineAnnealingMomentumUpdaterHook, self).__init__(**kwargs) + + def get_momentum(self, runner, base_momentum): + if self.by_epoch: + progress = runner.epoch + max_progress = runner.max_epochs + else: + progress = runner.iter + max_progress = runner.max_iters + if self.min_momentum_ratio is not None: + target_momentum = base_momentum * self.min_momentum_ratio + else: + target_momentum = self.min_momentum + return annealing_cos(base_momentum, target_momentum, + progress / max_progress) + + +@HOOKS.register_module() +class CyclicMomentumUpdaterHook(MomentumUpdaterHook): + """Cyclic momentum Scheduler. + + Implement the cyclical momentum scheduler policy described in + https://arxiv.org/pdf/1708.07120.pdf + + This momentum scheduler usually used together with the CyclicLRUpdater + to improve the performance in the 3D detection area. + + Attributes: + target_ratio (tuple[float]): Relative ratio of the lowest momentum and + the highest momentum to the initial momentum. + cyclic_times (int): Number of cycles during training + step_ratio_up (float): The ratio of the increasing process of momentum + in the total cycle. + by_epoch (bool): Whether to update momentum by epoch. + """ + + def __init__(self, + by_epoch=False, + target_ratio=(0.85 / 0.95, 1), + cyclic_times=1, + step_ratio_up=0.4, + **kwargs): + if isinstance(target_ratio, float): + target_ratio = (target_ratio, target_ratio / 1e5) + elif isinstance(target_ratio, tuple): + target_ratio = (target_ratio[0], target_ratio[0] / 1e5) \ + if len(target_ratio) == 1 else target_ratio + else: + raise ValueError('target_ratio should be either float ' + f'or tuple, got {type(target_ratio)}') + + assert len(target_ratio) == 2, \ + '"target_ratio" must be list or tuple of two floats' + assert 0 <= step_ratio_up < 1.0, \ + '"step_ratio_up" must be in range [0,1)' + + self.target_ratio = target_ratio + self.cyclic_times = cyclic_times + self.step_ratio_up = step_ratio_up + self.momentum_phases = [] # init momentum_phases + # currently only support by_epoch=False + assert not by_epoch, \ + 'currently only support "by_epoch" = False' + super(CyclicMomentumUpdaterHook, self).__init__(by_epoch, **kwargs) + + def before_run(self, runner): + super(CyclicMomentumUpdaterHook, self).before_run(runner) + # initiate momentum_phases + # total momentum_phases are separated as up and down + max_iter_per_phase = runner.max_iters // self.cyclic_times + iter_up_phase = int(self.step_ratio_up * max_iter_per_phase) + self.momentum_phases.append( + [0, iter_up_phase, max_iter_per_phase, 1, self.target_ratio[0]]) + self.momentum_phases.append([ + iter_up_phase, max_iter_per_phase, max_iter_per_phase, + self.target_ratio[0], self.target_ratio[1] + ]) + + def get_momentum(self, runner, base_momentum): + curr_iter = runner.iter + for (start_iter, end_iter, max_iter_per_phase, start_ratio, + end_ratio) in self.momentum_phases: + curr_iter %= max_iter_per_phase + if start_iter <= curr_iter < end_iter: + progress = curr_iter - start_iter + return annealing_cos(base_momentum * start_ratio, + base_momentum * end_ratio, + progress / (end_iter - start_iter)) + + +@HOOKS.register_module() +class OneCycleMomentumUpdaterHook(MomentumUpdaterHook): + """OneCycle momentum Scheduler. + + This momentum scheduler usually used together with the OneCycleLrUpdater + to improve the performance. + + Args: + base_momentum (float or list): Lower momentum boundaries in the cycle + for each parameter group. Note that momentum is cycled inversely + to learning rate; at the peak of a cycle, momentum is + 'base_momentum' and learning rate is 'max_lr'. + Default: 0.85 + max_momentum (float or list): Upper momentum boundaries in the cycle + for each parameter group. Functionally, + it defines the cycle amplitude (max_momentum - base_momentum). + Note that momentum is cycled inversely + to learning rate; at the start of a cycle, momentum is + 'max_momentum' and learning rate is 'base_lr' + Default: 0.95 + pct_start (float): The percentage of the cycle (in number of steps) + spent increasing the learning rate. + Default: 0.3 + anneal_strategy (str): {'cos', 'linear'} + Specifies the annealing strategy: 'cos' for cosine annealing, + 'linear' for linear annealing. + Default: 'cos' + three_phase (bool): If three_phase is True, use a third phase of the + schedule to annihilate the learning rate according to + final_div_factor instead of modifying the second phase (the first + two phases will be symmetrical about the step indicated by + pct_start). + Default: False + """ + + def __init__(self, + base_momentum=0.85, + max_momentum=0.95, + pct_start=0.3, + anneal_strategy='cos', + three_phase=False, + **kwargs): + # validate by_epoch, currently only support by_epoch=False + if 'by_epoch' not in kwargs: + kwargs['by_epoch'] = False + else: + assert not kwargs['by_epoch'], \ + 'currently only support "by_epoch" = False' + if not isinstance(base_momentum, (float, list, dict)): + raise ValueError('base_momentum must be the type among of float,' + 'list or dict.') + self._base_momentum = base_momentum + if not isinstance(max_momentum, (float, list, dict)): + raise ValueError('max_momentum must be the type among of float,' + 'list or dict.') + self._max_momentum = max_momentum + # validate pct_start + if pct_start < 0 or pct_start > 1 or not isinstance(pct_start, float): + raise ValueError('Expected float between 0 and 1 pct_start, but ' + f'got {pct_start}') + self.pct_start = pct_start + # validate anneal_strategy + if anneal_strategy not in ['cos', 'linear']: + raise ValueError('anneal_strategy must by one of "cos" or ' + f'"linear", instead got {anneal_strategy}') + elif anneal_strategy == 'cos': + self.anneal_func = annealing_cos + elif anneal_strategy == 'linear': + self.anneal_func = annealing_linear + self.three_phase = three_phase + self.momentum_phases = [] # init momentum_phases + super(OneCycleMomentumUpdaterHook, self).__init__(**kwargs) + + def before_run(self, runner): + if isinstance(runner.optimizer, dict): + for k, optim in runner.optimizer.items(): + if ('momentum' not in optim.defaults + and 'betas' not in optim.defaults): + raise ValueError('optimizer must support momentum with' + 'option enabled') + self.use_beta1 = 'betas' in optim.defaults + _base_momentum = format_param(k, optim, self._base_momentum) + _max_momentum = format_param(k, optim, self._max_momentum) + for group, b_momentum, m_momentum in zip( + optim.param_groups, _base_momentum, _max_momentum): + if self.use_beta1: + _, beta2 = group['betas'] + group['betas'] = (m_momentum, beta2) + else: + group['momentum'] = m_momentum + group['base_momentum'] = b_momentum + group['max_momentum'] = m_momentum + else: + optim = runner.optimizer + if ('momentum' not in optim.defaults + and 'betas' not in optim.defaults): + raise ValueError('optimizer must support momentum with' + 'option enabled') + self.use_beta1 = 'betas' in optim.defaults + k = type(optim).__name__ + _base_momentum = format_param(k, optim, self._base_momentum) + _max_momentum = format_param(k, optim, self._max_momentum) + for group, b_momentum, m_momentum in zip(optim.param_groups, + _base_momentum, + _max_momentum): + if self.use_beta1: + _, beta2 = group['betas'] + group['betas'] = (m_momentum, beta2) + else: + group['momentum'] = m_momentum + group['base_momentum'] = b_momentum + group['max_momentum'] = m_momentum + + if self.three_phase: + self.momentum_phases.append({ + 'end_iter': + float(self.pct_start * runner.max_iters) - 1, + 'start_momentum': + 'max_momentum', + 'end_momentum': + 'base_momentum' + }) + self.momentum_phases.append({ + 'end_iter': + float(2 * self.pct_start * runner.max_iters) - 2, + 'start_momentum': + 'base_momentum', + 'end_momentum': + 'max_momentum' + }) + self.momentum_phases.append({ + 'end_iter': runner.max_iters - 1, + 'start_momentum': 'max_momentum', + 'end_momentum': 'max_momentum' + }) + else: + self.momentum_phases.append({ + 'end_iter': + float(self.pct_start * runner.max_iters) - 1, + 'start_momentum': + 'max_momentum', + 'end_momentum': + 'base_momentum' + }) + self.momentum_phases.append({ + 'end_iter': runner.max_iters - 1, + 'start_momentum': 'base_momentum', + 'end_momentum': 'max_momentum' + }) + + def _set_momentum(self, runner, momentum_groups): + if isinstance(runner.optimizer, dict): + for k, optim in runner.optimizer.items(): + for param_group, mom in zip(optim.param_groups, + momentum_groups[k]): + if 'momentum' in param_group.keys(): + param_group['momentum'] = mom + elif 'betas' in param_group.keys(): + param_group['betas'] = (mom, param_group['betas'][1]) + else: + for param_group, mom in zip(runner.optimizer.param_groups, + momentum_groups): + if 'momentum' in param_group.keys(): + param_group['momentum'] = mom + elif 'betas' in param_group.keys(): + param_group['betas'] = (mom, param_group['betas'][1]) + + def get_momentum(self, runner, param_group): + curr_iter = runner.iter + start_iter = 0 + for i, phase in enumerate(self.momentum_phases): + end_iter = phase['end_iter'] + if curr_iter <= end_iter or i == len(self.momentum_phases) - 1: + pct = (curr_iter - start_iter) / (end_iter - start_iter) + momentum = self.anneal_func( + param_group[phase['start_momentum']], + param_group[phase['end_momentum']], pct) + break + start_iter = end_iter + return momentum + + def get_regular_momentum(self, runner): + if isinstance(runner.optimizer, dict): + momentum_groups = {} + for k, optim in runner.optimizer.items(): + _momentum_group = [ + self.get_momentum(runner, param_group) + for param_group in optim.param_groups + ] + momentum_groups.update({k: _momentum_group}) + return momentum_groups + else: + momentum_groups = [] + for param_group in runner.optimizer.param_groups: + momentum_groups.append(self.get_momentum(runner, param_group)) + return momentum_groups diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/optimizer.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/optimizer.py new file mode 100644 index 0000000..4ef3e9f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/optimizer.py @@ -0,0 +1,508 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from collections import defaultdict +from itertools import chain + +from torch.nn.utils import clip_grad + +from annotator.uniformer.mmcv.utils import TORCH_VERSION, _BatchNorm, digit_version +from ..dist_utils import allreduce_grads +from ..fp16_utils import LossScaler, wrap_fp16_model +from .hook import HOOKS, Hook + +try: + # If PyTorch version >= 1.6.0, torch.cuda.amp.GradScaler would be imported + # and used; otherwise, auto fp16 will adopt mmcv's implementation. + from torch.cuda.amp import GradScaler +except ImportError: + pass + + +@HOOKS.register_module() +class OptimizerHook(Hook): + + def __init__(self, grad_clip=None): + self.grad_clip = grad_clip + + def clip_grads(self, params): + params = list( + filter(lambda p: p.requires_grad and p.grad is not None, params)) + if len(params) > 0: + return clip_grad.clip_grad_norm_(params, **self.grad_clip) + + def after_train_iter(self, runner): + runner.optimizer.zero_grad() + runner.outputs['loss'].backward() + if self.grad_clip is not None: + grad_norm = self.clip_grads(runner.model.parameters()) + if grad_norm is not None: + # Add grad norm to the logger + runner.log_buffer.update({'grad_norm': float(grad_norm)}, + runner.outputs['num_samples']) + runner.optimizer.step() + + +@HOOKS.register_module() +class GradientCumulativeOptimizerHook(OptimizerHook): + """Optimizer Hook implements multi-iters gradient cumulating. + + Args: + cumulative_iters (int, optional): Num of gradient cumulative iters. + The optimizer will step every `cumulative_iters` iters. + Defaults to 1. + + Examples: + >>> # Use cumulative_iters to simulate a large batch size + >>> # It is helpful when the hardware cannot handle a large batch size. + >>> loader = DataLoader(data, batch_size=64) + >>> optim_hook = GradientCumulativeOptimizerHook(cumulative_iters=4) + >>> # almost equals to + >>> loader = DataLoader(data, batch_size=256) + >>> optim_hook = OptimizerHook() + """ + + def __init__(self, cumulative_iters=1, **kwargs): + super(GradientCumulativeOptimizerHook, self).__init__(**kwargs) + + assert isinstance(cumulative_iters, int) and cumulative_iters > 0, \ + f'cumulative_iters only accepts positive int, but got ' \ + f'{type(cumulative_iters)} instead.' + + self.cumulative_iters = cumulative_iters + self.divisible_iters = 0 + self.remainder_iters = 0 + self.initialized = False + + def has_batch_norm(self, module): + if isinstance(module, _BatchNorm): + return True + for m in module.children(): + if self.has_batch_norm(m): + return True + return False + + def _init(self, runner): + if runner.iter % self.cumulative_iters != 0: + runner.logger.warning( + 'Resume iter number is not divisible by cumulative_iters in ' + 'GradientCumulativeOptimizerHook, which means the gradient of ' + 'some iters is lost and the result may be influenced slightly.' + ) + + if self.has_batch_norm(runner.model) and self.cumulative_iters > 1: + runner.logger.warning( + 'GradientCumulativeOptimizerHook may slightly decrease ' + 'performance if the model has BatchNorm layers.') + + residual_iters = runner.max_iters - runner.iter + + self.divisible_iters = ( + residual_iters // self.cumulative_iters * self.cumulative_iters) + self.remainder_iters = residual_iters - self.divisible_iters + + self.initialized = True + + def after_train_iter(self, runner): + if not self.initialized: + self._init(runner) + + if runner.iter < self.divisible_iters: + loss_factor = self.cumulative_iters + else: + loss_factor = self.remainder_iters + loss = runner.outputs['loss'] + loss = loss / loss_factor + loss.backward() + + if (self.every_n_iters(runner, self.cumulative_iters) + or self.is_last_iter(runner)): + + if self.grad_clip is not None: + grad_norm = self.clip_grads(runner.model.parameters()) + if grad_norm is not None: + # Add grad norm to the logger + runner.log_buffer.update({'grad_norm': float(grad_norm)}, + runner.outputs['num_samples']) + runner.optimizer.step() + runner.optimizer.zero_grad() + + +if (TORCH_VERSION != 'parrots' + and digit_version(TORCH_VERSION) >= digit_version('1.6.0')): + + @HOOKS.register_module() + class Fp16OptimizerHook(OptimizerHook): + """FP16 optimizer hook (using PyTorch's implementation). + + If you are using PyTorch >= 1.6, torch.cuda.amp is used as the backend, + to take care of the optimization procedure. + + Args: + loss_scale (float | str | dict): Scale factor configuration. + If loss_scale is a float, static loss scaling will be used with + the specified scale. If loss_scale is a string, it must be + 'dynamic', then dynamic loss scaling will be used. + It can also be a dict containing arguments of GradScalar. + Defaults to 512. For Pytorch >= 1.6, mmcv uses official + implementation of GradScaler. If you use a dict version of + loss_scale to create GradScaler, please refer to: + https://pytorch.org/docs/stable/amp.html#torch.cuda.amp.GradScaler + for the parameters. + + Examples: + >>> loss_scale = dict( + ... init_scale=65536.0, + ... growth_factor=2.0, + ... backoff_factor=0.5, + ... growth_interval=2000 + ... ) + >>> optimizer_hook = Fp16OptimizerHook(loss_scale=loss_scale) + """ + + def __init__(self, + grad_clip=None, + coalesce=True, + bucket_size_mb=-1, + loss_scale=512., + distributed=True): + self.grad_clip = grad_clip + self.coalesce = coalesce + self.bucket_size_mb = bucket_size_mb + self.distributed = distributed + self._scale_update_param = None + if loss_scale == 'dynamic': + self.loss_scaler = GradScaler() + elif isinstance(loss_scale, float): + self._scale_update_param = loss_scale + self.loss_scaler = GradScaler(init_scale=loss_scale) + elif isinstance(loss_scale, dict): + self.loss_scaler = GradScaler(**loss_scale) + else: + raise ValueError('loss_scale must be of type float, dict, or ' + f'"dynamic", got {loss_scale}') + + def before_run(self, runner): + """Preparing steps before Mixed Precision Training.""" + # wrap model mode to fp16 + wrap_fp16_model(runner.model) + # resume from state dict + if 'fp16' in runner.meta and 'loss_scaler' in runner.meta['fp16']: + scaler_state_dict = runner.meta['fp16']['loss_scaler'] + self.loss_scaler.load_state_dict(scaler_state_dict) + + def copy_grads_to_fp32(self, fp16_net, fp32_weights): + """Copy gradients from fp16 model to fp32 weight copy.""" + for fp32_param, fp16_param in zip(fp32_weights, + fp16_net.parameters()): + if fp16_param.grad is not None: + if fp32_param.grad is None: + fp32_param.grad = fp32_param.data.new( + fp32_param.size()) + fp32_param.grad.copy_(fp16_param.grad) + + def copy_params_to_fp16(self, fp16_net, fp32_weights): + """Copy updated params from fp32 weight copy to fp16 model.""" + for fp16_param, fp32_param in zip(fp16_net.parameters(), + fp32_weights): + fp16_param.data.copy_(fp32_param.data) + + def after_train_iter(self, runner): + """Backward optimization steps for Mixed Precision Training. For + dynamic loss scaling, please refer to + https://pytorch.org/docs/stable/amp.html#torch.cuda.amp.GradScaler. + + 1. Scale the loss by a scale factor. + 2. Backward the loss to obtain the gradients. + 3. Unscale the optimizer’s gradient tensors. + 4. Call optimizer.step() and update scale factor. + 5. Save loss_scaler state_dict for resume purpose. + """ + # clear grads of last iteration + runner.model.zero_grad() + runner.optimizer.zero_grad() + + self.loss_scaler.scale(runner.outputs['loss']).backward() + self.loss_scaler.unscale_(runner.optimizer) + # grad clip + if self.grad_clip is not None: + grad_norm = self.clip_grads(runner.model.parameters()) + if grad_norm is not None: + # Add grad norm to the logger + runner.log_buffer.update({'grad_norm': float(grad_norm)}, + runner.outputs['num_samples']) + # backward and update scaler + self.loss_scaler.step(runner.optimizer) + self.loss_scaler.update(self._scale_update_param) + + # save state_dict of loss_scaler + runner.meta.setdefault( + 'fp16', {})['loss_scaler'] = self.loss_scaler.state_dict() + + @HOOKS.register_module() + class GradientCumulativeFp16OptimizerHook(GradientCumulativeOptimizerHook, + Fp16OptimizerHook): + """Fp16 optimizer Hook (using PyTorch's implementation) implements + multi-iters gradient cumulating. + + If you are using PyTorch >= 1.6, torch.cuda.amp is used as the backend, + to take care of the optimization procedure. + """ + + def __init__(self, *args, **kwargs): + super(GradientCumulativeFp16OptimizerHook, + self).__init__(*args, **kwargs) + + def after_train_iter(self, runner): + if not self.initialized: + self._init(runner) + + if runner.iter < self.divisible_iters: + loss_factor = self.cumulative_iters + else: + loss_factor = self.remainder_iters + loss = runner.outputs['loss'] + loss = loss / loss_factor + + self.loss_scaler.scale(loss).backward() + + if (self.every_n_iters(runner, self.cumulative_iters) + or self.is_last_iter(runner)): + + # copy fp16 grads in the model to fp32 params in the optimizer + self.loss_scaler.unscale_(runner.optimizer) + + if self.grad_clip is not None: + grad_norm = self.clip_grads(runner.model.parameters()) + if grad_norm is not None: + # Add grad norm to the logger + runner.log_buffer.update( + {'grad_norm': float(grad_norm)}, + runner.outputs['num_samples']) + + # backward and update scaler + self.loss_scaler.step(runner.optimizer) + self.loss_scaler.update(self._scale_update_param) + + # save state_dict of loss_scaler + runner.meta.setdefault( + 'fp16', {})['loss_scaler'] = self.loss_scaler.state_dict() + + # clear grads + runner.model.zero_grad() + runner.optimizer.zero_grad() + +else: + + @HOOKS.register_module() + class Fp16OptimizerHook(OptimizerHook): + """FP16 optimizer hook (mmcv's implementation). + + The steps of fp16 optimizer is as follows. + 1. Scale the loss value. + 2. BP in the fp16 model. + 2. Copy gradients from fp16 model to fp32 weights. + 3. Update fp32 weights. + 4. Copy updated parameters from fp32 weights to fp16 model. + + Refer to https://arxiv.org/abs/1710.03740 for more details. + + Args: + loss_scale (float | str | dict): Scale factor configuration. + If loss_scale is a float, static loss scaling will be used with + the specified scale. If loss_scale is a string, it must be + 'dynamic', then dynamic loss scaling will be used. + It can also be a dict containing arguments of LossScaler. + Defaults to 512. + """ + + def __init__(self, + grad_clip=None, + coalesce=True, + bucket_size_mb=-1, + loss_scale=512., + distributed=True): + self.grad_clip = grad_clip + self.coalesce = coalesce + self.bucket_size_mb = bucket_size_mb + self.distributed = distributed + if loss_scale == 'dynamic': + self.loss_scaler = LossScaler(mode='dynamic') + elif isinstance(loss_scale, float): + self.loss_scaler = LossScaler( + init_scale=loss_scale, mode='static') + elif isinstance(loss_scale, dict): + self.loss_scaler = LossScaler(**loss_scale) + else: + raise ValueError('loss_scale must be of type float, dict, or ' + f'"dynamic", got {loss_scale}') + + def before_run(self, runner): + """Preparing steps before Mixed Precision Training. + + 1. Make a master copy of fp32 weights for optimization. + 2. Convert the main model from fp32 to fp16. + """ + # keep a copy of fp32 weights + old_groups = runner.optimizer.param_groups + runner.optimizer.param_groups = copy.deepcopy( + runner.optimizer.param_groups) + state = defaultdict(dict) + p_map = { + old_p: p + for old_p, p in zip( + chain(*(g['params'] for g in old_groups)), + chain(*(g['params'] + for g in runner.optimizer.param_groups))) + } + for k, v in runner.optimizer.state.items(): + state[p_map[k]] = v + runner.optimizer.state = state + # convert model to fp16 + wrap_fp16_model(runner.model) + # resume from state dict + if 'fp16' in runner.meta and 'loss_scaler' in runner.meta['fp16']: + scaler_state_dict = runner.meta['fp16']['loss_scaler'] + self.loss_scaler.load_state_dict(scaler_state_dict) + + def copy_grads_to_fp32(self, fp16_net, fp32_weights): + """Copy gradients from fp16 model to fp32 weight copy.""" + for fp32_param, fp16_param in zip(fp32_weights, + fp16_net.parameters()): + if fp16_param.grad is not None: + if fp32_param.grad is None: + fp32_param.grad = fp32_param.data.new( + fp32_param.size()) + fp32_param.grad.copy_(fp16_param.grad) + + def copy_params_to_fp16(self, fp16_net, fp32_weights): + """Copy updated params from fp32 weight copy to fp16 model.""" + for fp16_param, fp32_param in zip(fp16_net.parameters(), + fp32_weights): + fp16_param.data.copy_(fp32_param.data) + + def after_train_iter(self, runner): + """Backward optimization steps for Mixed Precision Training. For + dynamic loss scaling, please refer `loss_scalar.py` + + 1. Scale the loss by a scale factor. + 2. Backward the loss to obtain the gradients (fp16). + 3. Copy gradients from the model to the fp32 weight copy. + 4. Scale the gradients back and update the fp32 weight copy. + 5. Copy back the params from fp32 weight copy to the fp16 model. + 6. Save loss_scaler state_dict for resume purpose. + """ + # clear grads of last iteration + runner.model.zero_grad() + runner.optimizer.zero_grad() + # scale the loss value + scaled_loss = runner.outputs['loss'] * self.loss_scaler.loss_scale + scaled_loss.backward() + # copy fp16 grads in the model to fp32 params in the optimizer + + fp32_weights = [] + for param_group in runner.optimizer.param_groups: + fp32_weights += param_group['params'] + self.copy_grads_to_fp32(runner.model, fp32_weights) + # allreduce grads + if self.distributed: + allreduce_grads(fp32_weights, self.coalesce, + self.bucket_size_mb) + + has_overflow = self.loss_scaler.has_overflow(fp32_weights) + # if has overflow, skip this iteration + if not has_overflow: + # scale the gradients back + for param in fp32_weights: + if param.grad is not None: + param.grad.div_(self.loss_scaler.loss_scale) + if self.grad_clip is not None: + grad_norm = self.clip_grads(fp32_weights) + if grad_norm is not None: + # Add grad norm to the logger + runner.log_buffer.update( + {'grad_norm': float(grad_norm)}, + runner.outputs['num_samples']) + # update fp32 params + runner.optimizer.step() + # copy fp32 params to the fp16 model + self.copy_params_to_fp16(runner.model, fp32_weights) + self.loss_scaler.update_scale(has_overflow) + if has_overflow: + runner.logger.warning('Check overflow, downscale loss scale ' + f'to {self.loss_scaler.cur_scale}') + + # save state_dict of loss_scaler + runner.meta.setdefault( + 'fp16', {})['loss_scaler'] = self.loss_scaler.state_dict() + + @HOOKS.register_module() + class GradientCumulativeFp16OptimizerHook(GradientCumulativeOptimizerHook, + Fp16OptimizerHook): + """Fp16 optimizer Hook (using mmcv implementation) implements multi- + iters gradient cumulating.""" + + def __init__(self, *args, **kwargs): + super(GradientCumulativeFp16OptimizerHook, + self).__init__(*args, **kwargs) + + def after_train_iter(self, runner): + if not self.initialized: + self._init(runner) + + if runner.iter < self.divisible_iters: + loss_factor = self.cumulative_iters + else: + loss_factor = self.remainder_iters + + loss = runner.outputs['loss'] + loss = loss / loss_factor + + # scale the loss value + scaled_loss = loss * self.loss_scaler.loss_scale + scaled_loss.backward() + + if (self.every_n_iters(runner, self.cumulative_iters) + or self.is_last_iter(runner)): + + # copy fp16 grads in the model to fp32 params in the optimizer + fp32_weights = [] + for param_group in runner.optimizer.param_groups: + fp32_weights += param_group['params'] + self.copy_grads_to_fp32(runner.model, fp32_weights) + # allreduce grads + if self.distributed: + allreduce_grads(fp32_weights, self.coalesce, + self.bucket_size_mb) + + has_overflow = self.loss_scaler.has_overflow(fp32_weights) + # if has overflow, skip this iteration + if not has_overflow: + # scale the gradients back + for param in fp32_weights: + if param.grad is not None: + param.grad.div_(self.loss_scaler.loss_scale) + if self.grad_clip is not None: + grad_norm = self.clip_grads(fp32_weights) + if grad_norm is not None: + # Add grad norm to the logger + runner.log_buffer.update( + {'grad_norm': float(grad_norm)}, + runner.outputs['num_samples']) + # update fp32 params + runner.optimizer.step() + # copy fp32 params to the fp16 model + self.copy_params_to_fp16(runner.model, fp32_weights) + else: + runner.logger.warning( + 'Check overflow, downscale loss scale ' + f'to {self.loss_scaler.cur_scale}') + + self.loss_scaler.update_scale(has_overflow) + + # save state_dict of loss_scaler + runner.meta.setdefault( + 'fp16', {})['loss_scaler'] = self.loss_scaler.state_dict() + + # clear grads + runner.model.zero_grad() + runner.optimizer.zero_grad() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/profiler.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/profiler.py new file mode 100644 index 0000000..b702369 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/profiler.py @@ -0,0 +1,180 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import Callable, List, Optional, Union + +import torch + +from ..dist_utils import master_only +from .hook import HOOKS, Hook + + +@HOOKS.register_module() +class ProfilerHook(Hook): + """Profiler to analyze performance during training. + + PyTorch Profiler is a tool that allows the collection of the performance + metrics during the training. More details on Profiler can be found at + https://pytorch.org/docs/1.8.1/profiler.html#torch.profiler.profile + + Args: + by_epoch (bool): Profile performance by epoch or by iteration. + Default: True. + profile_iters (int): Number of iterations for profiling. + If ``by_epoch=True``, profile_iters indicates that they are the + first profile_iters epochs at the beginning of the + training, otherwise it indicates the first profile_iters + iterations. Default: 1. + activities (list[str]): List of activity groups (CPU, CUDA) to use in + profiling. Default: ['cpu', 'cuda']. + schedule (dict, optional): Config of generating the callable schedule. + if schedule is None, profiler will not add step markers into the + trace and table view. Default: None. + on_trace_ready (callable, dict): Either a handler or a dict of generate + handler. Default: None. + record_shapes (bool): Save information about operator's input shapes. + Default: False. + profile_memory (bool): Track tensor memory allocation/deallocation. + Default: False. + with_stack (bool): Record source information (file and line number) + for the ops. Default: False. + with_flops (bool): Use formula to estimate the FLOPS of specific + operators (matrix multiplication and 2D convolution). + Default: False. + json_trace_path (str, optional): Exports the collected trace in Chrome + JSON format. Default: None. + + Example: + >>> runner = ... # instantiate a Runner + >>> # tensorboard trace + >>> trace_config = dict(type='tb_trace', dir_name='work_dir') + >>> profiler_config = dict(on_trace_ready=trace_config) + >>> runner.register_profiler_hook(profiler_config) + >>> runner.run(data_loaders=[trainloader], workflow=[('train', 1)]) + """ + + def __init__(self, + by_epoch: bool = True, + profile_iters: int = 1, + activities: List[str] = ['cpu', 'cuda'], + schedule: Optional[dict] = None, + on_trace_ready: Optional[Union[Callable, dict]] = None, + record_shapes: bool = False, + profile_memory: bool = False, + with_stack: bool = False, + with_flops: bool = False, + json_trace_path: Optional[str] = None) -> None: + try: + from torch import profiler # torch version >= 1.8.1 + except ImportError: + raise ImportError('profiler is the new feature of torch1.8.1, ' + f'but your version is {torch.__version__}') + + assert isinstance(by_epoch, bool), '``by_epoch`` should be a boolean.' + self.by_epoch = by_epoch + + if profile_iters < 1: + raise ValueError('profile_iters should be greater than 0, but got ' + f'{profile_iters}') + self.profile_iters = profile_iters + + if not isinstance(activities, list): + raise ValueError( + f'activities should be list, but got {type(activities)}') + self.activities = [] + for activity in activities: + activity = activity.lower() + if activity == 'cpu': + self.activities.append(profiler.ProfilerActivity.CPU) + elif activity == 'cuda': + self.activities.append(profiler.ProfilerActivity.CUDA) + else: + raise ValueError( + f'activity should be "cpu" or "cuda", but got {activity}') + + if schedule is not None: + self.schedule = profiler.schedule(**schedule) + else: + self.schedule = None + + self.on_trace_ready = on_trace_ready + self.record_shapes = record_shapes + self.profile_memory = profile_memory + self.with_stack = with_stack + self.with_flops = with_flops + self.json_trace_path = json_trace_path + + @master_only + def before_run(self, runner): + if self.by_epoch and runner.max_epochs < self.profile_iters: + raise ValueError('self.profile_iters should not be greater than ' + f'{runner.max_epochs}') + + if not self.by_epoch and runner.max_iters < self.profile_iters: + raise ValueError('self.profile_iters should not be greater than ' + f'{runner.max_iters}') + + if callable(self.on_trace_ready): # handler + _on_trace_ready = self.on_trace_ready + elif isinstance(self.on_trace_ready, dict): # config of handler + trace_cfg = self.on_trace_ready.copy() + trace_type = trace_cfg.pop('type') # log_trace handler + if trace_type == 'log_trace': + + def _log_handler(prof): + print(prof.key_averages().table(**trace_cfg)) + + _on_trace_ready = _log_handler + elif trace_type == 'tb_trace': # tensorboard_trace handler + try: + import torch_tb_profiler # noqa: F401 + except ImportError: + raise ImportError('please run "pip install ' + 'torch-tb-profiler" to install ' + 'torch_tb_profiler') + _on_trace_ready = torch.profiler.tensorboard_trace_handler( + **trace_cfg) + else: + raise ValueError('trace_type should be "log_trace" or ' + f'"tb_trace", but got {trace_type}') + elif self.on_trace_ready is None: + _on_trace_ready = None # type: ignore + else: + raise ValueError('on_trace_ready should be handler, dict or None, ' + f'but got {type(self.on_trace_ready)}') + + if runner.max_epochs > 1: + warnings.warn(f'profiler will profile {runner.max_epochs} epochs ' + 'instead of 1 epoch. Since profiler will slow down ' + 'the training, it is recommended to train 1 epoch ' + 'with ProfilerHook and adjust your setting according' + ' to the profiler summary. During normal training ' + '(epoch > 1), you may disable the ProfilerHook.') + + self.profiler = torch.profiler.profile( + activities=self.activities, + schedule=self.schedule, + on_trace_ready=_on_trace_ready, + record_shapes=self.record_shapes, + profile_memory=self.profile_memory, + with_stack=self.with_stack, + with_flops=self.with_flops) + + self.profiler.__enter__() + runner.logger.info('profiler is profiling...') + + @master_only + def after_train_epoch(self, runner): + if self.by_epoch and runner.epoch == self.profile_iters - 1: + runner.logger.info('profiler may take a few minutes...') + self.profiler.__exit__(None, None, None) + if self.json_trace_path is not None: + self.profiler.export_chrome_trace(self.json_trace_path) + + @master_only + def after_train_iter(self, runner): + self.profiler.step() + if not self.by_epoch and runner.iter == self.profile_iters - 1: + runner.logger.info('profiler may take a few minutes...') + self.profiler.__exit__(None, None, None) + if self.json_trace_path is not None: + self.profiler.export_chrome_trace(self.json_trace_path) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/sampler_seed.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/sampler_seed.py new file mode 100644 index 0000000..ee0dc6b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/sampler_seed.py @@ -0,0 +1,20 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .hook import HOOKS, Hook + + +@HOOKS.register_module() +class DistSamplerSeedHook(Hook): + """Data-loading sampler for distributed training. + + When distributed training, it is only useful in conjunction with + :obj:`EpochBasedRunner`, while :obj:`IterBasedRunner` achieves the same + purpose with :obj:`IterLoader`. + """ + + def before_epoch(self, runner): + if hasattr(runner.data_loader.sampler, 'set_epoch'): + # in case the data loader uses `SequentialSampler` in Pytorch + runner.data_loader.sampler.set_epoch(runner.epoch) + elif hasattr(runner.data_loader.batch_sampler.sampler, 'set_epoch'): + # batch sampler in pytorch warps the sampler as its attributes. + runner.data_loader.batch_sampler.sampler.set_epoch(runner.epoch) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/sync_buffer.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/sync_buffer.py new file mode 100644 index 0000000..6376b7f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/hooks/sync_buffer.py @@ -0,0 +1,22 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ..dist_utils import allreduce_params +from .hook import HOOKS, Hook + + +@HOOKS.register_module() +class SyncBuffersHook(Hook): + """Synchronize model buffers such as running_mean and running_var in BN at + the end of each epoch. + + Args: + distributed (bool): Whether distributed training is used. It is + effective only for distributed training. Defaults to True. + """ + + def __init__(self, distributed=True): + self.distributed = distributed + + def after_epoch(self, runner): + """All-reduce model buffers at the end of each epoch.""" + if self.distributed: + allreduce_params(runner.model.buffers()) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/iter_based_runner.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/iter_based_runner.py new file mode 100644 index 0000000..1df4de8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/iter_based_runner.py @@ -0,0 +1,273 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import platform +import shutil +import time +import warnings + +import torch +from torch.optim import Optimizer + +import annotator.uniformer.mmcv as mmcv +from .base_runner import BaseRunner +from .builder import RUNNERS +from .checkpoint import save_checkpoint +from .hooks import IterTimerHook +from .utils import get_host_info + + +class IterLoader: + + def __init__(self, dataloader): + self._dataloader = dataloader + self.iter_loader = iter(self._dataloader) + self._epoch = 0 + + @property + def epoch(self): + return self._epoch + + def __next__(self): + try: + data = next(self.iter_loader) + except StopIteration: + self._epoch += 1 + if hasattr(self._dataloader.sampler, 'set_epoch'): + self._dataloader.sampler.set_epoch(self._epoch) + time.sleep(2) # Prevent possible deadlock during epoch transition + self.iter_loader = iter(self._dataloader) + data = next(self.iter_loader) + + return data + + def __len__(self): + return len(self._dataloader) + + +@RUNNERS.register_module() +class IterBasedRunner(BaseRunner): + """Iteration-based Runner. + + This runner train models iteration by iteration. + """ + + def train(self, data_loader, **kwargs): + self.model.train() + self.mode = 'train' + self.data_loader = data_loader + self._epoch = data_loader.epoch + data_batch = next(data_loader) + self.call_hook('before_train_iter') + outputs = self.model.train_step(data_batch, self.optimizer, **kwargs) + if not isinstance(outputs, dict): + raise TypeError('model.train_step() must return a dict') + if 'log_vars' in outputs: + self.log_buffer.update(outputs['log_vars'], outputs['num_samples']) + self.outputs = outputs + self.call_hook('after_train_iter') + self._inner_iter += 1 + self._iter += 1 + + @torch.no_grad() + def val(self, data_loader, **kwargs): + self.model.eval() + self.mode = 'val' + self.data_loader = data_loader + data_batch = next(data_loader) + self.call_hook('before_val_iter') + outputs = self.model.val_step(data_batch, **kwargs) + if not isinstance(outputs, dict): + raise TypeError('model.val_step() must return a dict') + if 'log_vars' in outputs: + self.log_buffer.update(outputs['log_vars'], outputs['num_samples']) + self.outputs = outputs + self.call_hook('after_val_iter') + self._inner_iter += 1 + + def run(self, data_loaders, workflow, max_iters=None, **kwargs): + """Start running. + + Args: + data_loaders (list[:obj:`DataLoader`]): Dataloaders for training + and validation. + workflow (list[tuple]): A list of (phase, iters) to specify the + running order and iterations. E.g, [('train', 10000), + ('val', 1000)] means running 10000 iterations for training and + 1000 iterations for validation, iteratively. + """ + assert isinstance(data_loaders, list) + assert mmcv.is_list_of(workflow, tuple) + assert len(data_loaders) == len(workflow) + if max_iters is not None: + warnings.warn( + 'setting max_iters in run is deprecated, ' + 'please set max_iters in runner_config', DeprecationWarning) + self._max_iters = max_iters + assert self._max_iters is not None, ( + 'max_iters must be specified during instantiation') + + work_dir = self.work_dir if self.work_dir is not None else 'NONE' + self.logger.info('Start running, host: %s, work_dir: %s', + get_host_info(), work_dir) + self.logger.info('Hooks will be executed in the following order:\n%s', + self.get_hook_info()) + self.logger.info('workflow: %s, max: %d iters', workflow, + self._max_iters) + self.call_hook('before_run') + + iter_loaders = [IterLoader(x) for x in data_loaders] + + self.call_hook('before_epoch') + + while self.iter < self._max_iters: + for i, flow in enumerate(workflow): + self._inner_iter = 0 + mode, iters = flow + if not isinstance(mode, str) or not hasattr(self, mode): + raise ValueError( + 'runner has no method named "{}" to run a workflow'. + format(mode)) + iter_runner = getattr(self, mode) + for _ in range(iters): + if mode == 'train' and self.iter >= self._max_iters: + break + iter_runner(iter_loaders[i], **kwargs) + + time.sleep(1) # wait for some hooks like loggers to finish + self.call_hook('after_epoch') + self.call_hook('after_run') + + def resume(self, + checkpoint, + resume_optimizer=True, + map_location='default'): + """Resume model from checkpoint. + + Args: + checkpoint (str): Checkpoint to resume from. + resume_optimizer (bool, optional): Whether resume the optimizer(s) + if the checkpoint file includes optimizer(s). Default to True. + map_location (str, optional): Same as :func:`torch.load`. + Default to 'default'. + """ + if map_location == 'default': + device_id = torch.cuda.current_device() + checkpoint = self.load_checkpoint( + checkpoint, + map_location=lambda storage, loc: storage.cuda(device_id)) + else: + checkpoint = self.load_checkpoint( + checkpoint, map_location=map_location) + + self._epoch = checkpoint['meta']['epoch'] + self._iter = checkpoint['meta']['iter'] + self._inner_iter = checkpoint['meta']['iter'] + if 'optimizer' in checkpoint and resume_optimizer: + if isinstance(self.optimizer, Optimizer): + self.optimizer.load_state_dict(checkpoint['optimizer']) + elif isinstance(self.optimizer, dict): + for k in self.optimizer.keys(): + self.optimizer[k].load_state_dict( + checkpoint['optimizer'][k]) + else: + raise TypeError( + 'Optimizer should be dict or torch.optim.Optimizer ' + f'but got {type(self.optimizer)}') + + self.logger.info(f'resumed from epoch: {self.epoch}, iter {self.iter}') + + def save_checkpoint(self, + out_dir, + filename_tmpl='iter_{}.pth', + meta=None, + save_optimizer=True, + create_symlink=True): + """Save checkpoint to file. + + Args: + out_dir (str): Directory to save checkpoint files. + filename_tmpl (str, optional): Checkpoint file template. + Defaults to 'iter_{}.pth'. + meta (dict, optional): Metadata to be saved in checkpoint. + Defaults to None. + save_optimizer (bool, optional): Whether save optimizer. + Defaults to True. + create_symlink (bool, optional): Whether create symlink to the + latest checkpoint file. Defaults to True. + """ + if meta is None: + meta = {} + elif not isinstance(meta, dict): + raise TypeError( + f'meta should be a dict or None, but got {type(meta)}') + if self.meta is not None: + meta.update(self.meta) + # Note: meta.update(self.meta) should be done before + # meta.update(epoch=self.epoch + 1, iter=self.iter) otherwise + # there will be problems with resumed checkpoints. + # More details in https://github.com/open-mmlab/mmcv/pull/1108 + meta.update(epoch=self.epoch + 1, iter=self.iter) + + filename = filename_tmpl.format(self.iter + 1) + filepath = osp.join(out_dir, filename) + optimizer = self.optimizer if save_optimizer else None + save_checkpoint(self.model, filepath, optimizer=optimizer, meta=meta) + # in some environments, `os.symlink` is not supported, you may need to + # set `create_symlink` to False + if create_symlink: + dst_file = osp.join(out_dir, 'latest.pth') + if platform.system() != 'Windows': + mmcv.symlink(filename, dst_file) + else: + shutil.copy(filepath, dst_file) + + def register_training_hooks(self, + lr_config, + optimizer_config=None, + checkpoint_config=None, + log_config=None, + momentum_config=None, + custom_hooks_config=None): + """Register default hooks for iter-based training. + + Checkpoint hook, optimizer stepper hook and logger hooks will be set to + `by_epoch=False` by default. + + Default hooks include: + + +----------------------+-------------------------+ + | Hooks | Priority | + +======================+=========================+ + | LrUpdaterHook | VERY_HIGH (10) | + +----------------------+-------------------------+ + | MomentumUpdaterHook | HIGH (30) | + +----------------------+-------------------------+ + | OptimizerStepperHook | ABOVE_NORMAL (40) | + +----------------------+-------------------------+ + | CheckpointSaverHook | NORMAL (50) | + +----------------------+-------------------------+ + | IterTimerHook | LOW (70) | + +----------------------+-------------------------+ + | LoggerHook(s) | VERY_LOW (90) | + +----------------------+-------------------------+ + | CustomHook(s) | defaults to NORMAL (50) | + +----------------------+-------------------------+ + + If custom hooks have same priority with default hooks, custom hooks + will be triggered after default hooks. + """ + if checkpoint_config is not None: + checkpoint_config.setdefault('by_epoch', False) + if lr_config is not None: + lr_config.setdefault('by_epoch', False) + if log_config is not None: + for info in log_config['hooks']: + info.setdefault('by_epoch', False) + super(IterBasedRunner, self).register_training_hooks( + lr_config=lr_config, + momentum_config=momentum_config, + optimizer_config=optimizer_config, + checkpoint_config=checkpoint_config, + log_config=log_config, + timer_config=IterTimerHook(), + custom_hooks_config=custom_hooks_config) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/log_buffer.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/log_buffer.py new file mode 100644 index 0000000..d949e29 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/log_buffer.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections import OrderedDict + +import numpy as np + + +class LogBuffer: + + def __init__(self): + self.val_history = OrderedDict() + self.n_history = OrderedDict() + self.output = OrderedDict() + self.ready = False + + def clear(self): + self.val_history.clear() + self.n_history.clear() + self.clear_output() + + def clear_output(self): + self.output.clear() + self.ready = False + + def update(self, vars, count=1): + assert isinstance(vars, dict) + for key, var in vars.items(): + if key not in self.val_history: + self.val_history[key] = [] + self.n_history[key] = [] + self.val_history[key].append(var) + self.n_history[key].append(count) + + def average(self, n=0): + """Average latest n values or all values.""" + assert n >= 0 + for key in self.val_history: + values = np.array(self.val_history[key][-n:]) + nums = np.array(self.n_history[key][-n:]) + avg = np.sum(values * nums) / np.sum(nums) + self.output[key] = avg + self.ready = True diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/__init__.py new file mode 100644 index 0000000..53c34d0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .builder import (OPTIMIZER_BUILDERS, OPTIMIZERS, build_optimizer, + build_optimizer_constructor) +from .default_constructor import DefaultOptimizerConstructor + +__all__ = [ + 'OPTIMIZER_BUILDERS', 'OPTIMIZERS', 'DefaultOptimizerConstructor', + 'build_optimizer', 'build_optimizer_constructor' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/builder.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/builder.py new file mode 100644 index 0000000..f9234ee --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/builder.py @@ -0,0 +1,44 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import inspect + +import torch + +from ...utils import Registry, build_from_cfg + +OPTIMIZERS = Registry('optimizer') +OPTIMIZER_BUILDERS = Registry('optimizer builder') + + +def register_torch_optimizers(): + torch_optimizers = [] + for module_name in dir(torch.optim): + if module_name.startswith('__'): + continue + _optim = getattr(torch.optim, module_name) + if inspect.isclass(_optim) and issubclass(_optim, + torch.optim.Optimizer): + OPTIMIZERS.register_module()(_optim) + torch_optimizers.append(module_name) + return torch_optimizers + + +TORCH_OPTIMIZERS = register_torch_optimizers() + + +def build_optimizer_constructor(cfg): + return build_from_cfg(cfg, OPTIMIZER_BUILDERS) + + +def build_optimizer(model, cfg): + optimizer_cfg = copy.deepcopy(cfg) + constructor_type = optimizer_cfg.pop('constructor', + 'DefaultOptimizerConstructor') + paramwise_cfg = optimizer_cfg.pop('paramwise_cfg', None) + optim_constructor = build_optimizer_constructor( + dict( + type=constructor_type, + optimizer_cfg=optimizer_cfg, + paramwise_cfg=paramwise_cfg)) + optimizer = optim_constructor(model) + return optimizer diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/default_constructor.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/default_constructor.py new file mode 100644 index 0000000..2c0da35 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/optimizer/default_constructor.py @@ -0,0 +1,249 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch +from torch.nn import GroupNorm, LayerNorm + +from annotator.uniformer.mmcv.utils import _BatchNorm, _InstanceNorm, build_from_cfg, is_list_of +from annotator.uniformer.mmcv.utils.ext_loader import check_ops_exist +from .builder import OPTIMIZER_BUILDERS, OPTIMIZERS + + +@OPTIMIZER_BUILDERS.register_module() +class DefaultOptimizerConstructor: + """Default constructor for optimizers. + + 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 the following fields: + + - ``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. + - ``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. Default: False. + + 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. + + Args: + model (:obj:`nn.Module`): The model with parameters to be optimized. + optimizer_cfg (dict): The config dict of the optimizer. + Positional fields are + + - `type`: class name of the optimizer. + + Optional fields 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) + >>> optimizer_cfg = dict(type='SGD', lr=0.01, momentum=0.9, + >>> weight_decay=0.0001) + >>> paramwise_cfg = dict(norm_decay_mult=0.) + >>> optim_builder = DefaultOptimizerConstructor( + >>> optimizer_cfg, paramwise_cfg) + >>> optimizer = optim_builder(model) + + Example 2: + >>> # assume model have attribute model.backbone and model.cls_head + >>> optimizer_cfg = 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_builder = DefaultOptimizerConstructor( + >>> optimizer_cfg, paramwise_cfg) + >>> optimizer = optim_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 __init__(self, optimizer_cfg, paramwise_cfg=None): + if not isinstance(optimizer_cfg, dict): + raise TypeError('optimizer_cfg should be a dict', + f'but got {type(optimizer_cfg)}') + self.optimizer_cfg = optimizer_cfg + self.paramwise_cfg = {} if paramwise_cfg is None else paramwise_cfg + self.base_lr = optimizer_cfg.get('lr', None) + self.base_wd = optimizer_cfg.get('weight_decay', None) + self._validate_cfg() + + def _validate_cfg(self): + if not isinstance(self.paramwise_cfg, dict): + raise TypeError('paramwise_cfg should be None or a dict, ' + f'but got {type(self.paramwise_cfg)}') + + if 'custom_keys' in self.paramwise_cfg: + if not isinstance(self.paramwise_cfg['custom_keys'], dict): + raise TypeError( + 'If specified, custom_keys must be a dict, ' + f'but got {type(self.paramwise_cfg["custom_keys"])}') + if self.base_wd is None: + for key in self.paramwise_cfg['custom_keys']: + if 'decay_mult' in self.paramwise_cfg['custom_keys'][key]: + raise ValueError('base_wd should not be None') + + # get base lr and weight decay + # weight_decay must be explicitly specified if mult is specified + if ('bias_decay_mult' in self.paramwise_cfg + or 'norm_decay_mult' in self.paramwise_cfg + or 'dwconv_decay_mult' in self.paramwise_cfg): + if self.base_wd is None: + raise ValueError('base_wd should not be None') + + def _is_in(self, param_group, param_group_list): + assert is_list_of(param_group_list, dict) + param = set(param_group['params']) + param_set = set() + for group in param_group_list: + param_set.update(set(group['params'])) + + return not param.isdisjoint(param_set) + + def add_params(self, params, module, prefix='', is_dcn_module=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', 1.) + bias_decay_mult = self.paramwise_cfg.get('bias_decay_mult', 1.) + norm_decay_mult = self.paramwise_cfg.get('norm_decay_mult', 1.) + dwconv_decay_mult = self.paramwise_cfg.get('dwconv_decay_mult', 1.) + bypass_duplicate = self.paramwise_cfg.get('bypass_duplicate', False) + dcn_offset_lr_mult = self.paramwise_cfg.get('dcn_offset_lr_mult', 1.) + + # 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 not param.requires_grad: + params.append(param_group) + continue + if bypass_duplicate and self._is_in(param_group, params): + warnings.warn(f'{prefix} is duplicate. It is skipped since ' + f'bypass_duplicate={bypass_duplicate}') + 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 + break + + if not is_custom: + # 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): + param_group['lr'] = self.base_lr * bias_lr_mult + + if (prefix.find('conv_offset') != -1 and is_dcn_module + 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: + param_group[ + 'weight_decay'] = self.base_wd * norm_decay_mult + # depth-wise conv + elif is_dwconv: + param_group[ + 'weight_decay'] = self.base_wd * dwconv_decay_mult + # bias lr and decay + elif name == 'bias' and not is_dcn_module: + # TODO: current bias_decay_mult will have affect on DCN + param_group[ + 'weight_decay'] = self.base_wd * bias_decay_mult + params.append(param_group) + + if check_ops_exist(): + from annotator.uniformer.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) + + def __call__(self, model): + if hasattr(model, 'module'): + model = model.module + + optimizer_cfg = self.optimizer_cfg.copy() + # if no paramwise option is specified, just use the global setting + if not self.paramwise_cfg: + optimizer_cfg['params'] = model.parameters() + return build_from_cfg(optimizer_cfg, OPTIMIZERS) + + # set param-wise lr and weight decay recursively + params = [] + self.add_params(params, model) + optimizer_cfg['params'] = params + + return build_from_cfg(optimizer_cfg, OPTIMIZERS) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/priority.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/priority.py new file mode 100644 index 0000000..64cc4e3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/priority.py @@ -0,0 +1,60 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from enum import Enum + + +class Priority(Enum): + """Hook priority levels. + + +--------------+------------+ + | Level | Value | + +==============+============+ + | HIGHEST | 0 | + +--------------+------------+ + | VERY_HIGH | 10 | + +--------------+------------+ + | HIGH | 30 | + +--------------+------------+ + | ABOVE_NORMAL | 40 | + +--------------+------------+ + | NORMAL | 50 | + +--------------+------------+ + | BELOW_NORMAL | 60 | + +--------------+------------+ + | LOW | 70 | + +--------------+------------+ + | VERY_LOW | 90 | + +--------------+------------+ + | LOWEST | 100 | + +--------------+------------+ + """ + + HIGHEST = 0 + VERY_HIGH = 10 + HIGH = 30 + ABOVE_NORMAL = 40 + NORMAL = 50 + BELOW_NORMAL = 60 + LOW = 70 + VERY_LOW = 90 + LOWEST = 100 + + +def get_priority(priority): + """Get priority value. + + Args: + priority (int or str or :obj:`Priority`): Priority. + + Returns: + int: The priority value. + """ + if isinstance(priority, int): + if priority < 0 or priority > 100: + raise ValueError('priority must be between 0 and 100') + return priority + elif isinstance(priority, Priority): + return priority.value + elif isinstance(priority, str): + return Priority[priority.upper()].value + else: + raise TypeError('priority must be an integer or Priority enum value') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/utils.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/utils.py new file mode 100644 index 0000000..c5befb8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/runner/utils.py @@ -0,0 +1,93 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import random +import sys +import time +import warnings +from getpass import getuser +from socket import gethostname + +import numpy as np +import torch + +import annotator.uniformer.mmcv as mmcv + + +def get_host_info(): + """Get hostname and username. + + Return empty string if exception raised, e.g. ``getpass.getuser()`` will + lead to error in docker container + """ + host = '' + try: + host = f'{getuser()}@{gethostname()}' + except Exception as e: + warnings.warn(f'Host or user not found: {str(e)}') + finally: + return host + + +def get_time_str(): + return time.strftime('%Y%m%d_%H%M%S', time.localtime()) + + +def obj_from_dict(info, parent=None, default_args=None): + """Initialize an object from dict. + + The dict must contain the key "type", which indicates the object type, it + can be either a string or type, such as "list" or ``list``. Remaining + fields are treated as the arguments for constructing the object. + + Args: + info (dict): Object types and arguments. + parent (:class:`module`): Module which may containing expected object + classes. + default_args (dict, optional): Default arguments for initializing the + object. + + Returns: + any type: Object built from the dict. + """ + assert isinstance(info, dict) and 'type' in info + assert isinstance(default_args, dict) or default_args is None + args = info.copy() + obj_type = args.pop('type') + if mmcv.is_str(obj_type): + if parent is not None: + obj_type = getattr(parent, obj_type) + else: + obj_type = sys.modules[obj_type] + elif not isinstance(obj_type, type): + raise TypeError('type must be a str or valid type, but ' + f'got {type(obj_type)}') + if default_args is not None: + for name, value in default_args.items(): + args.setdefault(name, value) + return obj_type(**args) + + +def set_random_seed(seed, deterministic=False, use_rank_shift=False): + """Set random seed. + + Args: + seed (int): Seed to be used. + deterministic (bool): Whether to set the deterministic option for + CUDNN backend, i.e., set `torch.backends.cudnn.deterministic` + to True and `torch.backends.cudnn.benchmark` to False. + Default: False. + rank_shift (bool): Whether to add rank number to the random seed to + have different random seed in different threads. Default: False. + """ + if use_rank_shift: + rank, _ = mmcv.runner.get_dist_info() + seed += rank + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + os.environ['PYTHONHASHSEED'] = str(seed) + if deterministic: + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/__init__.py new file mode 100644 index 0000000..378a006 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/__init__.py @@ -0,0 +1,69 @@ +# flake8: noqa +# Copyright (c) OpenMMLab. All rights reserved. +from .config import Config, ConfigDict, DictAction +from .misc import (check_prerequisites, concat_list, deprecated_api_warning, + has_method, import_modules_from_strings, is_list_of, + is_method_overridden, is_seq_of, is_str, is_tuple_of, + iter_cast, list_cast, requires_executable, requires_package, + slice_list, to_1tuple, to_2tuple, to_3tuple, to_4tuple, + to_ntuple, tuple_cast) +from .path import (check_file_exist, fopen, is_filepath, mkdir_or_exist, + scandir, symlink) +from .progressbar import (ProgressBar, track_iter_progress, + track_parallel_progress, track_progress) +from .testing import (assert_attrs_equal, assert_dict_contains_subset, + assert_dict_has_keys, assert_is_norm_layer, + assert_keys_equal, assert_params_all_zeros, + check_python_script) +from .timer import Timer, TimerError, check_time +from .version_utils import digit_version, get_git_hash + +try: + import torch +except ImportError: + __all__ = [ + 'Config', 'ConfigDict', 'DictAction', 'is_str', 'iter_cast', + 'list_cast', 'tuple_cast', 'is_seq_of', 'is_list_of', 'is_tuple_of', + 'slice_list', 'concat_list', 'check_prerequisites', 'requires_package', + 'requires_executable', 'is_filepath', 'fopen', 'check_file_exist', + 'mkdir_or_exist', 'symlink', 'scandir', 'ProgressBar', + 'track_progress', 'track_iter_progress', 'track_parallel_progress', + 'Timer', 'TimerError', 'check_time', 'deprecated_api_warning', + 'digit_version', 'get_git_hash', 'import_modules_from_strings', + 'assert_dict_contains_subset', 'assert_attrs_equal', + 'assert_dict_has_keys', 'assert_keys_equal', 'check_python_script', + 'to_1tuple', 'to_2tuple', 'to_3tuple', 'to_4tuple', 'to_ntuple', + 'is_method_overridden', 'has_method' + ] +else: + from .env import collect_env + from .logging import get_logger, print_log + from .parrots_jit import jit, skip_no_elena + from .parrots_wrapper import ( + TORCH_VERSION, BuildExtension, CppExtension, CUDAExtension, DataLoader, + PoolDataLoader, SyncBatchNorm, _AdaptiveAvgPoolNd, _AdaptiveMaxPoolNd, + _AvgPoolNd, _BatchNorm, _ConvNd, _ConvTransposeMixin, _InstanceNorm, + _MaxPoolNd, get_build_config, is_rocm_pytorch, _get_cuda_home) + from .registry import Registry, build_from_cfg + from .trace import is_jit_tracing + __all__ = [ + 'Config', 'ConfigDict', 'DictAction', 'collect_env', 'get_logger', + 'print_log', 'is_str', 'iter_cast', 'list_cast', 'tuple_cast', + 'is_seq_of', 'is_list_of', 'is_tuple_of', 'slice_list', 'concat_list', + 'check_prerequisites', 'requires_package', 'requires_executable', + 'is_filepath', 'fopen', 'check_file_exist', 'mkdir_or_exist', + 'symlink', 'scandir', 'ProgressBar', 'track_progress', + 'track_iter_progress', 'track_parallel_progress', 'Registry', + 'build_from_cfg', 'Timer', 'TimerError', 'check_time', 'SyncBatchNorm', + '_AdaptiveAvgPoolNd', '_AdaptiveMaxPoolNd', '_AvgPoolNd', '_BatchNorm', + '_ConvNd', '_ConvTransposeMixin', '_InstanceNorm', '_MaxPoolNd', + 'get_build_config', 'BuildExtension', 'CppExtension', 'CUDAExtension', + 'DataLoader', 'PoolDataLoader', 'TORCH_VERSION', + 'deprecated_api_warning', 'digit_version', 'get_git_hash', + 'import_modules_from_strings', 'jit', 'skip_no_elena', + 'assert_dict_contains_subset', 'assert_attrs_equal', + 'assert_dict_has_keys', 'assert_keys_equal', 'assert_is_norm_layer', + 'assert_params_all_zeros', 'check_python_script', + 'is_method_overridden', 'is_jit_tracing', 'is_rocm_pytorch', + '_get_cuda_home', 'has_method' + ] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/config.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/config.py new file mode 100644 index 0000000..1714935 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/config.py @@ -0,0 +1,688 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import ast +import copy +import os +import os.path as osp +import platform +import shutil +import sys +import tempfile +import uuid +import warnings +from argparse import Action, ArgumentParser +from collections import abc +from importlib import import_module + +from addict import Dict +from yapf.yapflib.yapf_api import FormatCode + +from .misc import import_modules_from_strings +from .path import check_file_exist + +if platform.system() == 'Windows': + import regex as re +else: + import re + +BASE_KEY = '_base_' +DELETE_KEY = '_delete_' +DEPRECATION_KEY = '_deprecation_' +RESERVED_KEYS = ['filename', 'text', 'pretty_text'] + + +class ConfigDict(Dict): + + def __missing__(self, name): + raise KeyError(name) + + def __getattr__(self, name): + try: + value = super(ConfigDict, self).__getattr__(name) + except KeyError: + ex = AttributeError(f"'{self.__class__.__name__}' object has no " + f"attribute '{name}'") + except Exception as e: + ex = e + else: + return value + raise ex + + +def add_args(parser, cfg, prefix=''): + for k, v in cfg.items(): + if isinstance(v, str): + parser.add_argument('--' + prefix + k) + elif isinstance(v, int): + parser.add_argument('--' + prefix + k, type=int) + elif isinstance(v, float): + parser.add_argument('--' + prefix + k, type=float) + elif isinstance(v, bool): + parser.add_argument('--' + prefix + k, action='store_true') + elif isinstance(v, dict): + add_args(parser, v, prefix + k + '.') + elif isinstance(v, abc.Iterable): + parser.add_argument('--' + prefix + k, type=type(v[0]), nargs='+') + else: + print(f'cannot parse key {prefix + k} of type {type(v)}') + return parser + + +class Config: + """A facility for config and config files. + + It supports common file formats as configs: python/json/yaml. The interface + is the same as a dict object and also allows access config values as + attributes. + + Example: + >>> cfg = Config(dict(a=1, b=dict(b1=[0, 1]))) + >>> cfg.a + 1 + >>> cfg.b + {'b1': [0, 1]} + >>> cfg.b.b1 + [0, 1] + >>> cfg = Config.fromfile('tests/data/config/a.py') + >>> cfg.filename + "/home/kchen/projects/mmcv/tests/data/config/a.py" + >>> cfg.item4 + 'test' + >>> cfg + "Config [path: /home/kchen/projects/mmcv/tests/data/config/a.py]: " + "{'item1': [1, 2], 'item2': {'a': 0}, 'item3': True, 'item4': 'test'}" + """ + + @staticmethod + def _validate_py_syntax(filename): + with open(filename, 'r', encoding='utf-8') as f: + # Setting encoding explicitly to resolve coding issue on windows + content = f.read() + try: + ast.parse(content) + except SyntaxError as e: + raise SyntaxError('There are syntax errors in config ' + f'file {filename}: {e}') + + @staticmethod + def _substitute_predefined_vars(filename, temp_config_name): + file_dirname = osp.dirname(filename) + file_basename = osp.basename(filename) + file_basename_no_extension = osp.splitext(file_basename)[0] + file_extname = osp.splitext(filename)[1] + support_templates = dict( + fileDirname=file_dirname, + fileBasename=file_basename, + fileBasenameNoExtension=file_basename_no_extension, + fileExtname=file_extname) + with open(filename, 'r', encoding='utf-8') as f: + # Setting encoding explicitly to resolve coding issue on windows + config_file = f.read() + for key, value in support_templates.items(): + regexp = r'\{\{\s*' + str(key) + r'\s*\}\}' + value = value.replace('\\', '/') + config_file = re.sub(regexp, value, config_file) + with open(temp_config_name, 'w', encoding='utf-8') as tmp_config_file: + tmp_config_file.write(config_file) + + @staticmethod + def _pre_substitute_base_vars(filename, temp_config_name): + """Substitute base variable placehoders to string, so that parsing + would work.""" + with open(filename, 'r', encoding='utf-8') as f: + # Setting encoding explicitly to resolve coding issue on windows + config_file = f.read() + base_var_dict = {} + regexp = r'\{\{\s*' + BASE_KEY + r'\.([\w\.]+)\s*\}\}' + base_vars = set(re.findall(regexp, config_file)) + for base_var in base_vars: + randstr = f'_{base_var}_{uuid.uuid4().hex.lower()[:6]}' + base_var_dict[randstr] = base_var + regexp = r'\{\{\s*' + BASE_KEY + r'\.' + base_var + r'\s*\}\}' + config_file = re.sub(regexp, f'"{randstr}"', config_file) + with open(temp_config_name, 'w', encoding='utf-8') as tmp_config_file: + tmp_config_file.write(config_file) + return base_var_dict + + @staticmethod + def _substitute_base_vars(cfg, base_var_dict, base_cfg): + """Substitute variable strings to their actual values.""" + cfg = copy.deepcopy(cfg) + + if isinstance(cfg, dict): + for k, v in cfg.items(): + if isinstance(v, str) and v in base_var_dict: + new_v = base_cfg + for new_k in base_var_dict[v].split('.'): + new_v = new_v[new_k] + cfg[k] = new_v + elif isinstance(v, (list, tuple, dict)): + cfg[k] = Config._substitute_base_vars( + v, base_var_dict, base_cfg) + elif isinstance(cfg, tuple): + cfg = tuple( + Config._substitute_base_vars(c, base_var_dict, base_cfg) + for c in cfg) + elif isinstance(cfg, list): + cfg = [ + Config._substitute_base_vars(c, base_var_dict, base_cfg) + for c in cfg + ] + elif isinstance(cfg, str) and cfg in base_var_dict: + new_v = base_cfg + for new_k in base_var_dict[cfg].split('.'): + new_v = new_v[new_k] + cfg = new_v + + return cfg + + @staticmethod + def _file2dict(filename, use_predefined_variables=True): + filename = osp.abspath(osp.expanduser(filename)) + check_file_exist(filename) + fileExtname = osp.splitext(filename)[1] + if fileExtname not in ['.py', '.json', '.yaml', '.yml']: + raise IOError('Only py/yml/yaml/json type are supported now!') + + with tempfile.TemporaryDirectory() as temp_config_dir: + temp_config_file = tempfile.NamedTemporaryFile( + dir=temp_config_dir, suffix=fileExtname) + if platform.system() == 'Windows': + temp_config_file.close() + temp_config_name = osp.basename(temp_config_file.name) + # Substitute predefined variables + if use_predefined_variables: + Config._substitute_predefined_vars(filename, + temp_config_file.name) + else: + shutil.copyfile(filename, temp_config_file.name) + # Substitute base variables from placeholders to strings + base_var_dict = Config._pre_substitute_base_vars( + temp_config_file.name, temp_config_file.name) + + if filename.endswith('.py'): + temp_module_name = osp.splitext(temp_config_name)[0] + sys.path.insert(0, temp_config_dir) + Config._validate_py_syntax(filename) + mod = import_module(temp_module_name) + sys.path.pop(0) + cfg_dict = { + name: value + for name, value in mod.__dict__.items() + if not name.startswith('__') + } + # delete imported module + del sys.modules[temp_module_name] + elif filename.endswith(('.yml', '.yaml', '.json')): + import annotator.uniformer.mmcv as mmcv + cfg_dict = mmcv.load(temp_config_file.name) + # close temp file + temp_config_file.close() + + # check deprecation information + if DEPRECATION_KEY in cfg_dict: + deprecation_info = cfg_dict.pop(DEPRECATION_KEY) + warning_msg = f'The config file {filename} will be deprecated ' \ + 'in the future.' + if 'expected' in deprecation_info: + warning_msg += f' Please use {deprecation_info["expected"]} ' \ + 'instead.' + if 'reference' in deprecation_info: + warning_msg += ' More information can be found at ' \ + f'{deprecation_info["reference"]}' + warnings.warn(warning_msg) + + cfg_text = filename + '\n' + with open(filename, 'r', encoding='utf-8') as f: + # Setting encoding explicitly to resolve coding issue on windows + cfg_text += f.read() + + if BASE_KEY in cfg_dict: + cfg_dir = osp.dirname(filename) + base_filename = cfg_dict.pop(BASE_KEY) + base_filename = base_filename if isinstance( + base_filename, list) else [base_filename] + + cfg_dict_list = list() + cfg_text_list = list() + for f in base_filename: + _cfg_dict, _cfg_text = Config._file2dict(osp.join(cfg_dir, f)) + cfg_dict_list.append(_cfg_dict) + cfg_text_list.append(_cfg_text) + + base_cfg_dict = dict() + for c in cfg_dict_list: + duplicate_keys = base_cfg_dict.keys() & c.keys() + if len(duplicate_keys) > 0: + raise KeyError('Duplicate key is not allowed among bases. ' + f'Duplicate keys: {duplicate_keys}') + base_cfg_dict.update(c) + + # Substitute base variables from strings to their actual values + cfg_dict = Config._substitute_base_vars(cfg_dict, base_var_dict, + base_cfg_dict) + + base_cfg_dict = Config._merge_a_into_b(cfg_dict, base_cfg_dict) + cfg_dict = base_cfg_dict + + # merge cfg_text + cfg_text_list.append(cfg_text) + cfg_text = '\n'.join(cfg_text_list) + + return cfg_dict, cfg_text + + @staticmethod + def _merge_a_into_b(a, b, allow_list_keys=False): + """merge dict ``a`` into dict ``b`` (non-inplace). + + Values in ``a`` will overwrite ``b``. ``b`` is copied first to avoid + in-place modifications. + + Args: + a (dict): The source dict to be merged into ``b``. + b (dict): The origin dict to be fetch keys from ``a``. + allow_list_keys (bool): If True, int string keys (e.g. '0', '1') + are allowed in source ``a`` and will replace the element of the + corresponding index in b if b is a list. Default: False. + + Returns: + dict: The modified dict of ``b`` using ``a``. + + Examples: + # Normally merge a into b. + >>> Config._merge_a_into_b( + ... dict(obj=dict(a=2)), dict(obj=dict(a=1))) + {'obj': {'a': 2}} + + # Delete b first and merge a into b. + >>> Config._merge_a_into_b( + ... dict(obj=dict(_delete_=True, a=2)), dict(obj=dict(a=1))) + {'obj': {'a': 2}} + + # b is a list + >>> Config._merge_a_into_b( + ... {'0': dict(a=2)}, [dict(a=1), dict(b=2)], True) + [{'a': 2}, {'b': 2}] + """ + b = b.copy() + for k, v in a.items(): + if allow_list_keys and k.isdigit() and isinstance(b, list): + k = int(k) + if len(b) <= k: + raise KeyError(f'Index {k} exceeds the length of list {b}') + b[k] = Config._merge_a_into_b(v, b[k], allow_list_keys) + elif isinstance(v, + dict) and k in b and not v.pop(DELETE_KEY, False): + allowed_types = (dict, list) if allow_list_keys else dict + if not isinstance(b[k], allowed_types): + raise TypeError( + f'{k}={v} in child config cannot inherit from base ' + f'because {k} is a dict in the child config but is of ' + f'type {type(b[k])} in base config. You may set ' + f'`{DELETE_KEY}=True` to ignore the base config') + b[k] = Config._merge_a_into_b(v, b[k], allow_list_keys) + else: + b[k] = v + return b + + @staticmethod + def fromfile(filename, + use_predefined_variables=True, + import_custom_modules=True): + cfg_dict, cfg_text = Config._file2dict(filename, + use_predefined_variables) + if import_custom_modules and cfg_dict.get('custom_imports', None): + import_modules_from_strings(**cfg_dict['custom_imports']) + return Config(cfg_dict, cfg_text=cfg_text, filename=filename) + + @staticmethod + def fromstring(cfg_str, file_format): + """Generate config from config str. + + Args: + cfg_str (str): Config str. + file_format (str): Config file format corresponding to the + config str. Only py/yml/yaml/json type are supported now! + + Returns: + obj:`Config`: Config obj. + """ + if file_format not in ['.py', '.json', '.yaml', '.yml']: + raise IOError('Only py/yml/yaml/json type are supported now!') + if file_format != '.py' and 'dict(' in cfg_str: + # check if users specify a wrong suffix for python + warnings.warn( + 'Please check "file_format", the file format may be .py') + with tempfile.NamedTemporaryFile( + 'w', encoding='utf-8', suffix=file_format, + delete=False) as temp_file: + temp_file.write(cfg_str) + # on windows, previous implementation cause error + # see PR 1077 for details + cfg = Config.fromfile(temp_file.name) + os.remove(temp_file.name) + return cfg + + @staticmethod + def auto_argparser(description=None): + """Generate argparser from config file automatically (experimental)""" + partial_parser = ArgumentParser(description=description) + partial_parser.add_argument('config', help='config file path') + cfg_file = partial_parser.parse_known_args()[0].config + cfg = Config.fromfile(cfg_file) + parser = ArgumentParser(description=description) + parser.add_argument('config', help='config file path') + add_args(parser, cfg) + return parser, cfg + + def __init__(self, cfg_dict=None, cfg_text=None, filename=None): + if cfg_dict is None: + cfg_dict = dict() + elif not isinstance(cfg_dict, dict): + raise TypeError('cfg_dict must be a dict, but ' + f'got {type(cfg_dict)}') + for key in cfg_dict: + if key in RESERVED_KEYS: + raise KeyError(f'{key} is reserved for config file') + + super(Config, self).__setattr__('_cfg_dict', ConfigDict(cfg_dict)) + super(Config, self).__setattr__('_filename', filename) + if cfg_text: + text = cfg_text + elif filename: + with open(filename, 'r') as f: + text = f.read() + else: + text = '' + super(Config, self).__setattr__('_text', text) + + @property + def filename(self): + return self._filename + + @property + def text(self): + return self._text + + @property + def pretty_text(self): + + indent = 4 + + def _indent(s_, num_spaces): + s = s_.split('\n') + if len(s) == 1: + return s_ + first = s.pop(0) + s = [(num_spaces * ' ') + line for line in s] + s = '\n'.join(s) + s = first + '\n' + s + return s + + def _format_basic_types(k, v, use_mapping=False): + if isinstance(v, str): + v_str = f"'{v}'" + else: + v_str = str(v) + + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f'{k_str}: {v_str}' + else: + attr_str = f'{str(k)}={v_str}' + attr_str = _indent(attr_str, indent) + + return attr_str + + def _format_list(k, v, use_mapping=False): + # check if all items in the list are dict + if all(isinstance(_, dict) for _ in v): + v_str = '[\n' + v_str += '\n'.join( + f'dict({_indent(_format_dict(v_), indent)}),' + for v_ in v).rstrip(',') + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f'{k_str}: {v_str}' + else: + attr_str = f'{str(k)}={v_str}' + attr_str = _indent(attr_str, indent) + ']' + else: + attr_str = _format_basic_types(k, v, use_mapping) + return attr_str + + def _contain_invalid_identifier(dict_str): + contain_invalid_identifier = False + for key_name in dict_str: + contain_invalid_identifier |= \ + (not str(key_name).isidentifier()) + return contain_invalid_identifier + + def _format_dict(input_dict, outest_level=False): + r = '' + s = [] + + use_mapping = _contain_invalid_identifier(input_dict) + if use_mapping: + r += '{' + for idx, (k, v) in enumerate(input_dict.items()): + is_last = idx >= len(input_dict) - 1 + end = '' if outest_level or is_last else ',' + if isinstance(v, dict): + v_str = '\n' + _format_dict(v) + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f'{k_str}: dict({v_str}' + else: + attr_str = f'{str(k)}=dict({v_str}' + attr_str = _indent(attr_str, indent) + ')' + end + elif isinstance(v, list): + attr_str = _format_list(k, v, use_mapping) + end + else: + attr_str = _format_basic_types(k, v, use_mapping) + end + + s.append(attr_str) + r += '\n'.join(s) + if use_mapping: + r += '}' + return r + + cfg_dict = self._cfg_dict.to_dict() + text = _format_dict(cfg_dict, outest_level=True) + # copied from setup.cfg + yapf_style = dict( + based_on_style='pep8', + blank_line_before_nested_class_or_def=True, + split_before_expression_after_opening_paren=True) + text, _ = FormatCode(text, style_config=yapf_style, verify=True) + + return text + + def __repr__(self): + return f'Config (path: {self.filename}): {self._cfg_dict.__repr__()}' + + def __len__(self): + return len(self._cfg_dict) + + def __getattr__(self, name): + return getattr(self._cfg_dict, name) + + def __getitem__(self, name): + return self._cfg_dict.__getitem__(name) + + def __setattr__(self, name, value): + if isinstance(value, dict): + value = ConfigDict(value) + self._cfg_dict.__setattr__(name, value) + + def __setitem__(self, name, value): + if isinstance(value, dict): + value = ConfigDict(value) + self._cfg_dict.__setitem__(name, value) + + def __iter__(self): + return iter(self._cfg_dict) + + def __getstate__(self): + return (self._cfg_dict, self._filename, self._text) + + def __setstate__(self, state): + _cfg_dict, _filename, _text = state + super(Config, self).__setattr__('_cfg_dict', _cfg_dict) + super(Config, self).__setattr__('_filename', _filename) + super(Config, self).__setattr__('_text', _text) + + def dump(self, file=None): + cfg_dict = super(Config, self).__getattribute__('_cfg_dict').to_dict() + if self.filename.endswith('.py'): + if file is None: + return self.pretty_text + else: + with open(file, 'w', encoding='utf-8') as f: + f.write(self.pretty_text) + else: + import annotator.uniformer.mmcv as mmcv + if file is None: + file_format = self.filename.split('.')[-1] + return mmcv.dump(cfg_dict, file_format=file_format) + else: + mmcv.dump(cfg_dict, file) + + def merge_from_dict(self, options, allow_list_keys=True): + """Merge list into cfg_dict. + + Merge the dict parsed by MultipleKVAction into this cfg. + + Examples: + >>> options = {'model.backbone.depth': 50, + ... 'model.backbone.with_cp':True} + >>> cfg = Config(dict(model=dict(backbone=dict(type='ResNet')))) + >>> cfg.merge_from_dict(options) + >>> cfg_dict = super(Config, self).__getattribute__('_cfg_dict') + >>> assert cfg_dict == dict( + ... model=dict(backbone=dict(depth=50, with_cp=True))) + + # Merge list element + >>> cfg = Config(dict(pipeline=[ + ... dict(type='LoadImage'), dict(type='LoadAnnotations')])) + >>> options = dict(pipeline={'0': dict(type='SelfLoadImage')}) + >>> cfg.merge_from_dict(options, allow_list_keys=True) + >>> cfg_dict = super(Config, self).__getattribute__('_cfg_dict') + >>> assert cfg_dict == dict(pipeline=[ + ... dict(type='SelfLoadImage'), dict(type='LoadAnnotations')]) + + Args: + options (dict): dict of configs to merge from. + allow_list_keys (bool): If True, int string keys (e.g. '0', '1') + are allowed in ``options`` and will replace the element of the + corresponding index in the config if the config is a list. + Default: True. + """ + option_cfg_dict = {} + for full_key, v in options.items(): + d = option_cfg_dict + key_list = full_key.split('.') + for subkey in key_list[:-1]: + d.setdefault(subkey, ConfigDict()) + d = d[subkey] + subkey = key_list[-1] + d[subkey] = v + + cfg_dict = super(Config, self).__getattribute__('_cfg_dict') + super(Config, self).__setattr__( + '_cfg_dict', + Config._merge_a_into_b( + option_cfg_dict, cfg_dict, allow_list_keys=allow_list_keys)) + + +class DictAction(Action): + """ + argparse action to split an argument into KEY=VALUE form + on the first = and append to a dictionary. List options can + be passed as comma separated values, i.e 'KEY=V1,V2,V3', or with explicit + brackets, i.e. 'KEY=[V1,V2,V3]'. It also support nested brackets to build + list/tuple values. e.g. 'KEY=[(V1,V2),(V3,V4)]' + """ + + @staticmethod + def _parse_int_float_bool(val): + try: + return int(val) + except ValueError: + pass + try: + return float(val) + except ValueError: + pass + if val.lower() in ['true', 'false']: + return True if val.lower() == 'true' else False + return val + + @staticmethod + def _parse_iterable(val): + """Parse iterable values in the string. + + All elements inside '()' or '[]' are treated as iterable values. + + Args: + val (str): Value string. + + Returns: + list | tuple: The expanded list or tuple from the string. + + Examples: + >>> DictAction._parse_iterable('1,2,3') + [1, 2, 3] + >>> DictAction._parse_iterable('[a, b, c]') + ['a', 'b', 'c'] + >>> DictAction._parse_iterable('[(1, 2, 3), [a, b], c]') + [(1, 2, 3), ['a', 'b'], 'c'] + """ + + def find_next_comma(string): + """Find the position of next comma in the string. + + If no ',' is found in the string, return the string length. All + chars inside '()' and '[]' are treated as one element and thus ',' + inside these brackets are ignored. + """ + assert (string.count('(') == string.count(')')) and ( + string.count('[') == string.count(']')), \ + f'Imbalanced brackets exist in {string}' + end = len(string) + for idx, char in enumerate(string): + pre = string[:idx] + # The string before this ',' is balanced + if ((char == ',') and (pre.count('(') == pre.count(')')) + and (pre.count('[') == pre.count(']'))): + end = idx + break + return end + + # Strip ' and " characters and replace whitespace. + val = val.strip('\'\"').replace(' ', '') + is_tuple = False + if val.startswith('(') and val.endswith(')'): + is_tuple = True + val = val[1:-1] + elif val.startswith('[') and val.endswith(']'): + val = val[1:-1] + elif ',' not in val: + # val is a single value + return DictAction._parse_int_float_bool(val) + + values = [] + while len(val) > 0: + comma_idx = find_next_comma(val) + element = DictAction._parse_iterable(val[:comma_idx]) + values.append(element) + val = val[comma_idx + 1:] + if is_tuple: + values = tuple(values) + return values + + def __call__(self, parser, namespace, values, option_string=None): + options = {} + for kv in values: + key, val = kv.split('=', maxsplit=1) + options[key] = self._parse_iterable(val) + setattr(namespace, self.dest, options) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/env.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/env.py new file mode 100644 index 0000000..e3f0d92 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/env.py @@ -0,0 +1,95 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""This file holding some environment constant for sharing by other files.""" + +import os.path as osp +import subprocess +import sys +from collections import defaultdict + +import cv2 +import torch + +import annotator.uniformer.mmcv as mmcv +from .parrots_wrapper import get_build_config + + +def collect_env(): + """Collect the information of the running environments. + + Returns: + dict: The environment information. The following fields are contained. + + - sys.platform: The variable of ``sys.platform``. + - Python: Python version. + - CUDA available: Bool, indicating if CUDA is available. + - GPU devices: Device type of each GPU. + - CUDA_HOME (optional): The env var ``CUDA_HOME``. + - NVCC (optional): NVCC version. + - GCC: GCC version, "n/a" if GCC is not installed. + - PyTorch: PyTorch version. + - PyTorch compiling details: The output of \ + ``torch.__config__.show()``. + - TorchVision (optional): TorchVision version. + - OpenCV: OpenCV version. + - MMCV: MMCV version. + - MMCV Compiler: The GCC version for compiling MMCV ops. + - MMCV CUDA Compiler: The CUDA version for compiling MMCV ops. + """ + env_info = {} + env_info['sys.platform'] = sys.platform + env_info['Python'] = sys.version.replace('\n', '') + + cuda_available = torch.cuda.is_available() + env_info['CUDA available'] = cuda_available + + if cuda_available: + devices = defaultdict(list) + for k in range(torch.cuda.device_count()): + devices[torch.cuda.get_device_name(k)].append(str(k)) + for name, device_ids in devices.items(): + env_info['GPU ' + ','.join(device_ids)] = name + + from annotator.uniformer.mmcv.utils.parrots_wrapper import _get_cuda_home + CUDA_HOME = _get_cuda_home() + env_info['CUDA_HOME'] = CUDA_HOME + + if CUDA_HOME is not None and osp.isdir(CUDA_HOME): + try: + nvcc = osp.join(CUDA_HOME, 'bin/nvcc') + nvcc = subprocess.check_output( + f'"{nvcc}" -V | tail -n1', shell=True) + nvcc = nvcc.decode('utf-8').strip() + except subprocess.SubprocessError: + nvcc = 'Not Available' + env_info['NVCC'] = nvcc + + try: + gcc = subprocess.check_output('gcc --version | head -n1', shell=True) + gcc = gcc.decode('utf-8').strip() + env_info['GCC'] = gcc + except subprocess.CalledProcessError: # gcc is unavailable + env_info['GCC'] = 'n/a' + + env_info['PyTorch'] = torch.__version__ + env_info['PyTorch compiling details'] = get_build_config() + + try: + import torchvision + env_info['TorchVision'] = torchvision.__version__ + except ModuleNotFoundError: + pass + + env_info['OpenCV'] = cv2.__version__ + + env_info['MMCV'] = mmcv.__version__ + + try: + from annotator.uniformer.mmcv.ops import get_compiler_version, get_compiling_cuda_version + except ModuleNotFoundError: + env_info['MMCV Compiler'] = 'n/a' + env_info['MMCV CUDA Compiler'] = 'n/a' + else: + env_info['MMCV Compiler'] = get_compiler_version() + env_info['MMCV CUDA Compiler'] = get_compiling_cuda_version() + + return env_info diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/ext_loader.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/ext_loader.py new file mode 100644 index 0000000..08132d2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/ext_loader.py @@ -0,0 +1,71 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import importlib +import os +import pkgutil +import warnings +from collections import namedtuple + +import torch + +if torch.__version__ != 'parrots': + + def load_ext(name, funcs): + ext = importlib.import_module('mmcv.' + name) + for fun in funcs: + assert hasattr(ext, fun), f'{fun} miss in module {name}' + return ext +else: + from parrots import extension + from parrots.base import ParrotsException + + has_return_value_ops = [ + 'nms', + 'softnms', + 'nms_match', + 'nms_rotated', + 'top_pool_forward', + 'top_pool_backward', + 'bottom_pool_forward', + 'bottom_pool_backward', + 'left_pool_forward', + 'left_pool_backward', + 'right_pool_forward', + 'right_pool_backward', + 'fused_bias_leakyrelu', + 'upfirdn2d', + 'ms_deform_attn_forward', + 'pixel_group', + 'contour_expand', + ] + + def get_fake_func(name, e): + + def fake_func(*args, **kwargs): + warnings.warn(f'{name} is not supported in parrots now') + raise e + + return fake_func + + def load_ext(name, funcs): + ExtModule = namedtuple('ExtModule', funcs) + ext_list = [] + lib_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + for fun in funcs: + try: + ext_fun = extension.load(fun, name, lib_dir=lib_root) + except ParrotsException as e: + if 'No element registered' not in e.message: + warnings.warn(e.message) + ext_fun = get_fake_func(fun, e) + ext_list.append(ext_fun) + else: + if fun in has_return_value_ops: + ext_list.append(ext_fun.op) + else: + ext_list.append(ext_fun.op_) + return ExtModule(*ext_list) + + +def check_ops_exist(): + ext_loader = pkgutil.find_loader('mmcv._ext') + return ext_loader is not None diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/logging.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/logging.py new file mode 100644 index 0000000..4aa0e04 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/logging.py @@ -0,0 +1,110 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging + +import torch.distributed as dist + +logger_initialized = {} + + +def get_logger(name, log_file=None, log_level=logging.INFO, file_mode='w'): + """Initialize and get a logger by name. + + If the logger has not been initialized, this method will initialize the + logger by adding one or two handlers, otherwise the initialized logger will + be directly returned. During initialization, a StreamHandler will always be + added. If `log_file` is specified and the process rank is 0, a FileHandler + will also be added. + + Args: + name (str): Logger name. + log_file (str | None): The log filename. If specified, a FileHandler + will be added to the logger. + log_level (int): The logger level. Note that only the process of + rank 0 is affected, and other processes will set the level to + "Error" thus be silent most of the time. + file_mode (str): The file mode used in opening log file. + Defaults to 'w'. + + Returns: + logging.Logger: The expected logger. + """ + logger = logging.getLogger(name) + if name in logger_initialized: + return logger + # handle hierarchical names + # e.g., logger "a" is initialized, then logger "a.b" will skip the + # initialization since it is a child of "a". + for logger_name in logger_initialized: + if name.startswith(logger_name): + return logger + + # handle duplicate logs to the console + # Starting in 1.8.0, PyTorch DDP attaches a StreamHandler (NOTSET) + # to the root logger. As logger.propagate is True by default, this root + # level handler causes logging messages from rank>0 processes to + # unexpectedly show up on the console, creating much unwanted clutter. + # To fix this issue, we set the root logger's StreamHandler, if any, to log + # at the ERROR level. + for handler in logger.root.handlers: + if type(handler) is logging.StreamHandler: + handler.setLevel(logging.ERROR) + + stream_handler = logging.StreamHandler() + handlers = [stream_handler] + + if dist.is_available() and dist.is_initialized(): + rank = dist.get_rank() + else: + rank = 0 + + # only rank 0 will add a FileHandler + if rank == 0 and log_file is not None: + # Here, the default behaviour of the official logger is 'a'. Thus, we + # provide an interface to change the file mode to the default + # behaviour. + file_handler = logging.FileHandler(log_file, file_mode) + handlers.append(file_handler) + + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + for handler in handlers: + handler.setFormatter(formatter) + handler.setLevel(log_level) + logger.addHandler(handler) + + if rank == 0: + logger.setLevel(log_level) + else: + logger.setLevel(logging.ERROR) + + logger_initialized[name] = True + + return logger + + +def print_log(msg, logger=None, level=logging.INFO): + """Print a log message. + + Args: + msg (str): The message to be logged. + logger (logging.Logger | str | None): The logger to be used. + Some special loggers are: + - "silent": no message will be printed. + - other str: the logger obtained with `get_root_logger(logger)`. + - None: The `print()` method will be used to print log messages. + level (int): Logging level. Only available when `logger` is a Logger + object or "root". + """ + if logger is None: + print(msg) + elif isinstance(logger, logging.Logger): + logger.log(level, msg) + elif logger == 'silent': + pass + elif isinstance(logger, str): + _logger = get_logger(logger) + _logger.log(level, msg) + else: + raise TypeError( + 'logger should be either a logging.Logger object, str, ' + f'"silent" or None, but got {type(logger)}') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/misc.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/misc.py new file mode 100644 index 0000000..2c58d0d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/misc.py @@ -0,0 +1,377 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import collections.abc +import functools +import itertools +import subprocess +import warnings +from collections import abc +from importlib import import_module +from inspect import getfullargspec +from itertools import repeat + + +# From PyTorch internals +def _ntuple(n): + + def parse(x): + if isinstance(x, collections.abc.Iterable): + return x + return tuple(repeat(x, n)) + + return parse + + +to_1tuple = _ntuple(1) +to_2tuple = _ntuple(2) +to_3tuple = _ntuple(3) +to_4tuple = _ntuple(4) +to_ntuple = _ntuple + + +def is_str(x): + """Whether the input is an string instance. + + Note: This method is deprecated since python 2 is no longer supported. + """ + return isinstance(x, str) + + +def import_modules_from_strings(imports, allow_failed_imports=False): + """Import modules from the given list of strings. + + Args: + imports (list | str | None): The given module names to be imported. + allow_failed_imports (bool): If True, the failed imports will return + None. Otherwise, an ImportError is raise. Default: False. + + Returns: + list[module] | module | None: The imported modules. + + Examples: + >>> osp, sys = import_modules_from_strings( + ... ['os.path', 'sys']) + >>> import os.path as osp_ + >>> import sys as sys_ + >>> assert osp == osp_ + >>> assert sys == sys_ + """ + if not imports: + return + single_import = False + if isinstance(imports, str): + single_import = True + imports = [imports] + if not isinstance(imports, list): + raise TypeError( + f'custom_imports must be a list but got type {type(imports)}') + imported = [] + for imp in imports: + if not isinstance(imp, str): + raise TypeError( + f'{imp} is of type {type(imp)} and cannot be imported.') + try: + imported_tmp = import_module(imp) + except ImportError: + if allow_failed_imports: + warnings.warn(f'{imp} failed to import and is ignored.', + UserWarning) + imported_tmp = None + else: + raise ImportError + imported.append(imported_tmp) + if single_import: + imported = imported[0] + return imported + + +def iter_cast(inputs, dst_type, return_type=None): + """Cast elements of an iterable object into some type. + + Args: + inputs (Iterable): The input object. + dst_type (type): Destination type. + return_type (type, optional): If specified, the output object will be + converted to this type, otherwise an iterator. + + Returns: + iterator or specified type: The converted object. + """ + if not isinstance(inputs, abc.Iterable): + raise TypeError('inputs must be an iterable object') + if not isinstance(dst_type, type): + raise TypeError('"dst_type" must be a valid type') + + out_iterable = map(dst_type, inputs) + + if return_type is None: + return out_iterable + else: + return return_type(out_iterable) + + +def list_cast(inputs, dst_type): + """Cast elements of an iterable object into a list of some type. + + A partial method of :func:`iter_cast`. + """ + return iter_cast(inputs, dst_type, return_type=list) + + +def tuple_cast(inputs, dst_type): + """Cast elements of an iterable object into a tuple of some type. + + A partial method of :func:`iter_cast`. + """ + return iter_cast(inputs, dst_type, return_type=tuple) + + +def is_seq_of(seq, expected_type, seq_type=None): + """Check whether it is a sequence of some type. + + Args: + seq (Sequence): The sequence to be checked. + expected_type (type): Expected type of sequence items. + seq_type (type, optional): Expected sequence type. + + Returns: + bool: Whether the sequence is valid. + """ + if seq_type is None: + exp_seq_type = abc.Sequence + else: + assert isinstance(seq_type, type) + exp_seq_type = seq_type + if not isinstance(seq, exp_seq_type): + return False + for item in seq: + if not isinstance(item, expected_type): + return False + return True + + +def is_list_of(seq, expected_type): + """Check whether it is a list of some type. + + A partial method of :func:`is_seq_of`. + """ + return is_seq_of(seq, expected_type, seq_type=list) + + +def is_tuple_of(seq, expected_type): + """Check whether it is a tuple of some type. + + A partial method of :func:`is_seq_of`. + """ + return is_seq_of(seq, expected_type, seq_type=tuple) + + +def slice_list(in_list, lens): + """Slice a list into several sub lists by a list of given length. + + Args: + in_list (list): The list to be sliced. + lens(int or list): The expected length of each out list. + + Returns: + list: A list of sliced list. + """ + if isinstance(lens, int): + assert len(in_list) % lens == 0 + lens = [lens] * int(len(in_list) / lens) + if not isinstance(lens, list): + raise TypeError('"indices" must be an integer or a list of integers') + elif sum(lens) != len(in_list): + raise ValueError('sum of lens and list length does not ' + f'match: {sum(lens)} != {len(in_list)}') + out_list = [] + idx = 0 + for i in range(len(lens)): + out_list.append(in_list[idx:idx + lens[i]]) + idx += lens[i] + return out_list + + +def concat_list(in_list): + """Concatenate a list of list into a single list. + + Args: + in_list (list): The list of list to be merged. + + Returns: + list: The concatenated flat list. + """ + return list(itertools.chain(*in_list)) + + +def check_prerequisites( + prerequisites, + checker, + msg_tmpl='Prerequisites "{}" are required in method "{}" but not ' + 'found, please install them first.'): # yapf: disable + """A decorator factory to check if prerequisites are satisfied. + + Args: + prerequisites (str of list[str]): Prerequisites to be checked. + checker (callable): The checker method that returns True if a + prerequisite is meet, False otherwise. + msg_tmpl (str): The message template with two variables. + + Returns: + decorator: A specific decorator. + """ + + def wrap(func): + + @functools.wraps(func) + def wrapped_func(*args, **kwargs): + requirements = [prerequisites] if isinstance( + prerequisites, str) else prerequisites + missing = [] + for item in requirements: + if not checker(item): + missing.append(item) + if missing: + print(msg_tmpl.format(', '.join(missing), func.__name__)) + raise RuntimeError('Prerequisites not meet.') + else: + return func(*args, **kwargs) + + return wrapped_func + + return wrap + + +def _check_py_package(package): + try: + import_module(package) + except ImportError: + return False + else: + return True + + +def _check_executable(cmd): + if subprocess.call(f'which {cmd}', shell=True) != 0: + return False + else: + return True + + +def requires_package(prerequisites): + """A decorator to check if some python packages are installed. + + Example: + >>> @requires_package('numpy') + >>> func(arg1, args): + >>> return numpy.zeros(1) + array([0.]) + >>> @requires_package(['numpy', 'non_package']) + >>> func(arg1, args): + >>> return numpy.zeros(1) + ImportError + """ + return check_prerequisites(prerequisites, checker=_check_py_package) + + +def requires_executable(prerequisites): + """A decorator to check if some executable files are installed. + + Example: + >>> @requires_executable('ffmpeg') + >>> func(arg1, args): + >>> print(1) + 1 + """ + return check_prerequisites(prerequisites, checker=_check_executable) + + +def deprecated_api_warning(name_dict, cls_name=None): + """A decorator to check if some arguments are deprecate and try to replace + deprecate src_arg_name to dst_arg_name. + + Args: + name_dict(dict): + key (str): Deprecate argument names. + val (str): Expected argument names. + + Returns: + func: New function. + """ + + def api_warning_wrapper(old_func): + + @functools.wraps(old_func) + def new_func(*args, **kwargs): + # get the arg spec of the decorated method + args_info = getfullargspec(old_func) + # get name of the function + func_name = old_func.__name__ + if cls_name is not None: + func_name = f'{cls_name}.{func_name}' + if args: + arg_names = args_info.args[:len(args)] + for src_arg_name, dst_arg_name in name_dict.items(): + if src_arg_name in arg_names: + warnings.warn( + f'"{src_arg_name}" is deprecated in ' + f'`{func_name}`, please use "{dst_arg_name}" ' + 'instead') + arg_names[arg_names.index(src_arg_name)] = dst_arg_name + if kwargs: + for src_arg_name, dst_arg_name in name_dict.items(): + if src_arg_name in kwargs: + + assert dst_arg_name not in kwargs, ( + f'The expected behavior is to replace ' + f'the deprecated key `{src_arg_name}` to ' + f'new key `{dst_arg_name}`, but got them ' + f'in the arguments at the same time, which ' + f'is confusing. `{src_arg_name} will be ' + f'deprecated in the future, please ' + f'use `{dst_arg_name}` instead.') + + warnings.warn( + f'"{src_arg_name}" is deprecated in ' + f'`{func_name}`, please use "{dst_arg_name}" ' + 'instead') + kwargs[dst_arg_name] = kwargs.pop(src_arg_name) + + # apply converted arguments to the decorated method + output = old_func(*args, **kwargs) + return output + + return new_func + + return api_warning_wrapper + + +def is_method_overridden(method, base_class, derived_class): + """Check if a method of base class is overridden in derived class. + + Args: + method (str): the method name to check. + base_class (type): the class of the base class. + derived_class (type | Any): the class or instance of the derived class. + """ + assert isinstance(base_class, type), \ + "base_class doesn't accept instance, Please pass class instead." + + if not isinstance(derived_class, type): + derived_class = derived_class.__class__ + + base_method = getattr(base_class, method) + derived_method = getattr(derived_class, method) + return derived_method != base_method + + +def has_method(obj: object, method: str) -> bool: + """Check whether the object has a method. + + Args: + method (str): The method name to check. + obj (object): The object to check. + + Returns: + bool: True if the object has the method else False. + """ + return hasattr(obj, method) and callable(getattr(obj, method)) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/parrots_jit.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/parrots_jit.py new file mode 100644 index 0000000..61873f6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/parrots_jit.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os + +from .parrots_wrapper import TORCH_VERSION + +parrots_jit_option = os.getenv('PARROTS_JIT_OPTION') + +if TORCH_VERSION == 'parrots' and parrots_jit_option == 'ON': + from parrots.jit import pat as jit +else: + + def jit(func=None, + check_input=None, + full_shape=True, + derivate=False, + coderize=False, + optimize=False): + + def wrapper(func): + + def wrapper_inner(*args, **kargs): + return func(*args, **kargs) + + return wrapper_inner + + if func is None: + return wrapper + else: + return func + + +if TORCH_VERSION == 'parrots': + from parrots.utils.tester import skip_no_elena +else: + + def skip_no_elena(func): + + def wrapper(*args, **kargs): + return func(*args, **kargs) + + return wrapper diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/parrots_wrapper.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/parrots_wrapper.py new file mode 100644 index 0000000..93c9764 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/parrots_wrapper.py @@ -0,0 +1,107 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from functools import partial + +import torch + +TORCH_VERSION = torch.__version__ + + +def is_rocm_pytorch() -> bool: + is_rocm = False + if TORCH_VERSION != 'parrots': + try: + from torch.utils.cpp_extension import ROCM_HOME + is_rocm = True if ((torch.version.hip is not None) and + (ROCM_HOME is not None)) else False + except ImportError: + pass + return is_rocm + + +def _get_cuda_home(): + if TORCH_VERSION == 'parrots': + from parrots.utils.build_extension import CUDA_HOME + else: + if is_rocm_pytorch(): + from torch.utils.cpp_extension import ROCM_HOME + CUDA_HOME = ROCM_HOME + else: + from torch.utils.cpp_extension import CUDA_HOME + return CUDA_HOME + + +def get_build_config(): + if TORCH_VERSION == 'parrots': + from parrots.config import get_build_info + return get_build_info() + else: + return torch.__config__.show() + + +def _get_conv(): + if TORCH_VERSION == 'parrots': + from parrots.nn.modules.conv import _ConvNd, _ConvTransposeMixin + else: + from torch.nn.modules.conv import _ConvNd, _ConvTransposeMixin + return _ConvNd, _ConvTransposeMixin + + +def _get_dataloader(): + if TORCH_VERSION == 'parrots': + from torch.utils.data import DataLoader, PoolDataLoader + else: + from torch.utils.data import DataLoader + PoolDataLoader = DataLoader + return DataLoader, PoolDataLoader + + +def _get_extension(): + if TORCH_VERSION == 'parrots': + from parrots.utils.build_extension import BuildExtension, Extension + CppExtension = partial(Extension, cuda=False) + CUDAExtension = partial(Extension, cuda=True) + else: + from torch.utils.cpp_extension import (BuildExtension, CppExtension, + CUDAExtension) + return BuildExtension, CppExtension, CUDAExtension + + +def _get_pool(): + if TORCH_VERSION == 'parrots': + from parrots.nn.modules.pool import (_AdaptiveAvgPoolNd, + _AdaptiveMaxPoolNd, _AvgPoolNd, + _MaxPoolNd) + else: + from torch.nn.modules.pooling import (_AdaptiveAvgPoolNd, + _AdaptiveMaxPoolNd, _AvgPoolNd, + _MaxPoolNd) + return _AdaptiveAvgPoolNd, _AdaptiveMaxPoolNd, _AvgPoolNd, _MaxPoolNd + + +def _get_norm(): + if TORCH_VERSION == 'parrots': + from parrots.nn.modules.batchnorm import _BatchNorm, _InstanceNorm + SyncBatchNorm_ = torch.nn.SyncBatchNorm2d + else: + from torch.nn.modules.instancenorm import _InstanceNorm + from torch.nn.modules.batchnorm import _BatchNorm + SyncBatchNorm_ = torch.nn.SyncBatchNorm + return _BatchNorm, _InstanceNorm, SyncBatchNorm_ + + +_ConvNd, _ConvTransposeMixin = _get_conv() +DataLoader, PoolDataLoader = _get_dataloader() +BuildExtension, CppExtension, CUDAExtension = _get_extension() +_BatchNorm, _InstanceNorm, SyncBatchNorm_ = _get_norm() +_AdaptiveAvgPoolNd, _AdaptiveMaxPoolNd, _AvgPoolNd, _MaxPoolNd = _get_pool() + + +class SyncBatchNorm(SyncBatchNorm_): + + def _check_input_dim(self, input): + if TORCH_VERSION == 'parrots': + if input.dim() < 2: + raise ValueError( + f'expected at least 2D input (got {input.dim()}D input)') + else: + super()._check_input_dim(input) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/path.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/path.py new file mode 100644 index 0000000..7dab4b3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/path.py @@ -0,0 +1,101 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +from pathlib import Path + +from .misc import is_str + + +def is_filepath(x): + return is_str(x) or isinstance(x, Path) + + +def fopen(filepath, *args, **kwargs): + if is_str(filepath): + return open(filepath, *args, **kwargs) + elif isinstance(filepath, Path): + return filepath.open(*args, **kwargs) + raise ValueError('`filepath` should be a string or a Path') + + +def check_file_exist(filename, msg_tmpl='file "{}" does not exist'): + if not osp.isfile(filename): + raise FileNotFoundError(msg_tmpl.format(filename)) + + +def mkdir_or_exist(dir_name, mode=0o777): + if dir_name == '': + return + dir_name = osp.expanduser(dir_name) + os.makedirs(dir_name, mode=mode, exist_ok=True) + + +def symlink(src, dst, overwrite=True, **kwargs): + if os.path.lexists(dst) and overwrite: + os.remove(dst) + os.symlink(src, dst, **kwargs) + + +def scandir(dir_path, suffix=None, recursive=False, case_sensitive=True): + """Scan a directory to find the interested files. + + Args: + dir_path (str | obj:`Path`): Path of the directory. + suffix (str | tuple(str), optional): File suffix that we are + interested in. Default: None. + recursive (bool, optional): If set to True, recursively scan the + directory. Default: False. + case_sensitive (bool, optional) : If set to False, ignore the case of + suffix. Default: True. + + Returns: + A generator for all the interested files with relative paths. + """ + if isinstance(dir_path, (str, Path)): + dir_path = str(dir_path) + else: + raise TypeError('"dir_path" must be a string or Path object') + + if (suffix is not None) and not isinstance(suffix, (str, tuple)): + raise TypeError('"suffix" must be a string or tuple of strings') + + if suffix is not None and not case_sensitive: + suffix = suffix.lower() if isinstance(suffix, str) else tuple( + item.lower() for item in suffix) + + root = dir_path + + def _scandir(dir_path, suffix, recursive, case_sensitive): + for entry in os.scandir(dir_path): + if not entry.name.startswith('.') and entry.is_file(): + rel_path = osp.relpath(entry.path, root) + _rel_path = rel_path if case_sensitive else rel_path.lower() + if suffix is None or _rel_path.endswith(suffix): + yield rel_path + elif recursive and os.path.isdir(entry.path): + # scan recursively if entry.path is a directory + yield from _scandir(entry.path, suffix, recursive, + case_sensitive) + + return _scandir(dir_path, suffix, recursive, case_sensitive) + + +def find_vcs_root(path, markers=('.git', )): + """Finds the root directory (including itself) of specified markers. + + Args: + path (str): Path of directory or file. + markers (list[str], optional): List of file or directory names. + + Returns: + The directory contained one of the markers or None if not found. + """ + if osp.isfile(path): + path = osp.dirname(path) + + prev, cur = None, osp.abspath(osp.expanduser(path)) + while cur != prev: + if any(osp.exists(osp.join(cur, marker)) for marker in markers): + return cur + prev, cur = cur, osp.split(cur)[0] + return None diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/progressbar.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/progressbar.py new file mode 100644 index 0000000..0062f67 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/progressbar.py @@ -0,0 +1,208 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import sys +from collections.abc import Iterable +from multiprocessing import Pool +from shutil import get_terminal_size + +from .timer import Timer + + +class ProgressBar: + """A progress bar which can print the progress.""" + + def __init__(self, task_num=0, bar_width=50, start=True, file=sys.stdout): + self.task_num = task_num + self.bar_width = bar_width + self.completed = 0 + self.file = file + if start: + self.start() + + @property + def terminal_width(self): + width, _ = get_terminal_size() + return width + + def start(self): + if self.task_num > 0: + self.file.write(f'[{" " * self.bar_width}] 0/{self.task_num}, ' + 'elapsed: 0s, ETA:') + else: + self.file.write('completed: 0, elapsed: 0s') + self.file.flush() + self.timer = Timer() + + def update(self, num_tasks=1): + assert num_tasks > 0 + self.completed += num_tasks + elapsed = self.timer.since_start() + if elapsed > 0: + fps = self.completed / elapsed + else: + fps = float('inf') + if self.task_num > 0: + percentage = self.completed / float(self.task_num) + eta = int(elapsed * (1 - percentage) / percentage + 0.5) + msg = f'\r[{{}}] {self.completed}/{self.task_num}, ' \ + f'{fps:.1f} task/s, elapsed: {int(elapsed + 0.5)}s, ' \ + f'ETA: {eta:5}s' + + bar_width = min(self.bar_width, + int(self.terminal_width - len(msg)) + 2, + int(self.terminal_width * 0.6)) + bar_width = max(2, bar_width) + mark_width = int(bar_width * percentage) + bar_chars = '>' * mark_width + ' ' * (bar_width - mark_width) + self.file.write(msg.format(bar_chars)) + else: + self.file.write( + f'completed: {self.completed}, elapsed: {int(elapsed + 0.5)}s,' + f' {fps:.1f} tasks/s') + self.file.flush() + + +def track_progress(func, tasks, bar_width=50, file=sys.stdout, **kwargs): + """Track the progress of tasks execution with a progress bar. + + Tasks are done with a simple for-loop. + + Args: + func (callable): The function to be applied to each task. + tasks (list or tuple[Iterable, int]): A list of tasks or + (tasks, total num). + bar_width (int): Width of progress bar. + + Returns: + list: The task results. + """ + if isinstance(tasks, tuple): + assert len(tasks) == 2 + assert isinstance(tasks[0], Iterable) + assert isinstance(tasks[1], int) + task_num = tasks[1] + tasks = tasks[0] + elif isinstance(tasks, Iterable): + task_num = len(tasks) + else: + raise TypeError( + '"tasks" must be an iterable object or a (iterator, int) tuple') + prog_bar = ProgressBar(task_num, bar_width, file=file) + results = [] + for task in tasks: + results.append(func(task, **kwargs)) + prog_bar.update() + prog_bar.file.write('\n') + return results + + +def init_pool(process_num, initializer=None, initargs=None): + if initializer is None: + return Pool(process_num) + elif initargs is None: + return Pool(process_num, initializer) + else: + if not isinstance(initargs, tuple): + raise TypeError('"initargs" must be a tuple') + return Pool(process_num, initializer, initargs) + + +def track_parallel_progress(func, + tasks, + nproc, + initializer=None, + initargs=None, + bar_width=50, + chunksize=1, + skip_first=False, + keep_order=True, + file=sys.stdout): + """Track the progress of parallel task execution with a progress bar. + + The built-in :mod:`multiprocessing` module is used for process pools and + tasks are done with :func:`Pool.map` or :func:`Pool.imap_unordered`. + + Args: + func (callable): The function to be applied to each task. + tasks (list or tuple[Iterable, int]): A list of tasks or + (tasks, total num). + nproc (int): Process (worker) number. + initializer (None or callable): Refer to :class:`multiprocessing.Pool` + for details. + initargs (None or tuple): Refer to :class:`multiprocessing.Pool` for + details. + chunksize (int): Refer to :class:`multiprocessing.Pool` for details. + bar_width (int): Width of progress bar. + skip_first (bool): Whether to skip the first sample for each worker + when estimating fps, since the initialization step may takes + longer. + keep_order (bool): If True, :func:`Pool.imap` is used, otherwise + :func:`Pool.imap_unordered` is used. + + Returns: + list: The task results. + """ + if isinstance(tasks, tuple): + assert len(tasks) == 2 + assert isinstance(tasks[0], Iterable) + assert isinstance(tasks[1], int) + task_num = tasks[1] + tasks = tasks[0] + elif isinstance(tasks, Iterable): + task_num = len(tasks) + else: + raise TypeError( + '"tasks" must be an iterable object or a (iterator, int) tuple') + pool = init_pool(nproc, initializer, initargs) + start = not skip_first + task_num -= nproc * chunksize * int(skip_first) + prog_bar = ProgressBar(task_num, bar_width, start, file=file) + results = [] + if keep_order: + gen = pool.imap(func, tasks, chunksize) + else: + gen = pool.imap_unordered(func, tasks, chunksize) + for result in gen: + results.append(result) + if skip_first: + if len(results) < nproc * chunksize: + continue + elif len(results) == nproc * chunksize: + prog_bar.start() + continue + prog_bar.update() + prog_bar.file.write('\n') + pool.close() + pool.join() + return results + + +def track_iter_progress(tasks, bar_width=50, file=sys.stdout): + """Track the progress of tasks iteration or enumeration with a progress + bar. + + Tasks are yielded with a simple for-loop. + + Args: + tasks (list or tuple[Iterable, int]): A list of tasks or + (tasks, total num). + bar_width (int): Width of progress bar. + + Yields: + list: The task results. + """ + if isinstance(tasks, tuple): + assert len(tasks) == 2 + assert isinstance(tasks[0], Iterable) + assert isinstance(tasks[1], int) + task_num = tasks[1] + tasks = tasks[0] + elif isinstance(tasks, Iterable): + task_num = len(tasks) + else: + raise TypeError( + '"tasks" must be an iterable object or a (iterator, int) tuple') + prog_bar = ProgressBar(task_num, bar_width, file=file) + for task in tasks: + yield task + prog_bar.update() + prog_bar.file.write('\n') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/registry.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/registry.py new file mode 100644 index 0000000..fa9df39 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/registry.py @@ -0,0 +1,315 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import inspect +import warnings +from functools import partial + +from .misc import is_seq_of + + +def build_from_cfg(cfg, registry, default_args=None): + """Build a module from config dict. + + Args: + cfg (dict): Config dict. It should at least contain the key "type". + registry (:obj:`Registry`): The registry to search the type from. + default_args (dict, optional): Default initialization arguments. + + Returns: + object: The constructed object. + """ + if not isinstance(cfg, dict): + raise TypeError(f'cfg must be a dict, but got {type(cfg)}') + if 'type' not in cfg: + if default_args is None or 'type' not in default_args: + raise KeyError( + '`cfg` or `default_args` must contain the key "type", ' + f'but got {cfg}\n{default_args}') + if not isinstance(registry, Registry): + raise TypeError('registry must be an mmcv.Registry object, ' + f'but got {type(registry)}') + if not (isinstance(default_args, dict) or default_args is None): + raise TypeError('default_args must be a dict or None, ' + f'but got {type(default_args)}') + + args = cfg.copy() + + if default_args is not None: + for name, value in default_args.items(): + args.setdefault(name, value) + + obj_type = args.pop('type') + if isinstance(obj_type, str): + obj_cls = registry.get(obj_type) + if obj_cls is None: + raise KeyError( + f'{obj_type} is not in the {registry.name} registry') + elif inspect.isclass(obj_type): + obj_cls = obj_type + else: + raise TypeError( + f'type must be a str or valid type, but got {type(obj_type)}') + try: + return obj_cls(**args) + except Exception as e: + # Normal TypeError does not print class name. + raise type(e)(f'{obj_cls.__name__}: {e}') + + +class Registry: + """A registry to map strings to classes. + + Registered object could be built from registry. + Example: + >>> MODELS = Registry('models') + >>> @MODELS.register_module() + >>> class ResNet: + >>> pass + >>> resnet = MODELS.build(dict(type='ResNet')) + + Please refer to + https://mmcv.readthedocs.io/en/latest/understand_mmcv/registry.html for + advanced usage. + + Args: + name (str): Registry name. + build_func(func, optional): Build function to construct instance from + Registry, func:`build_from_cfg` is used if neither ``parent`` or + ``build_func`` is specified. If ``parent`` is specified and + ``build_func`` is not given, ``build_func`` will be inherited + from ``parent``. Default: None. + parent (Registry, optional): Parent registry. The class registered in + children registry could be built from parent. Default: None. + scope (str, optional): The scope of registry. It is the key to search + for children registry. If not specified, scope will be the name of + the package where class is defined, e.g. mmdet, mmcls, mmseg. + Default: None. + """ + + def __init__(self, name, build_func=None, parent=None, scope=None): + self._name = name + self._module_dict = dict() + self._children = dict() + self._scope = self.infer_scope() if scope is None else scope + + # self.build_func will be set with the following priority: + # 1. build_func + # 2. parent.build_func + # 3. build_from_cfg + if build_func is None: + if parent is not None: + self.build_func = parent.build_func + else: + self.build_func = build_from_cfg + else: + self.build_func = build_func + if parent is not None: + assert isinstance(parent, Registry) + parent._add_children(self) + self.parent = parent + else: + self.parent = None + + def __len__(self): + return len(self._module_dict) + + def __contains__(self, key): + return self.get(key) is not None + + def __repr__(self): + format_str = self.__class__.__name__ + \ + f'(name={self._name}, ' \ + f'items={self._module_dict})' + return format_str + + @staticmethod + def infer_scope(): + """Infer the scope of registry. + + The name of the package where registry is defined will be returned. + + Example: + # in mmdet/models/backbone/resnet.py + >>> MODELS = Registry('models') + >>> @MODELS.register_module() + >>> class ResNet: + >>> pass + The scope of ``ResNet`` will be ``mmdet``. + + + Returns: + scope (str): The inferred scope name. + """ + # inspect.stack() trace where this function is called, the index-2 + # indicates the frame where `infer_scope()` is called + filename = inspect.getmodule(inspect.stack()[2][0]).__name__ + split_filename = filename.split('.') + return split_filename[0] + + @staticmethod + def split_scope_key(key): + """Split scope and key. + + The first scope will be split from key. + + Examples: + >>> Registry.split_scope_key('mmdet.ResNet') + 'mmdet', 'ResNet' + >>> Registry.split_scope_key('ResNet') + None, 'ResNet' + + Return: + scope (str, None): The first scope. + key (str): The remaining key. + """ + split_index = key.find('.') + if split_index != -1: + return key[:split_index], key[split_index + 1:] + else: + return None, key + + @property + def name(self): + return self._name + + @property + def scope(self): + return self._scope + + @property + def module_dict(self): + return self._module_dict + + @property + def children(self): + return self._children + + def get(self, key): + """Get the registry record. + + Args: + key (str): The class name in string format. + + Returns: + class: The corresponding class. + """ + scope, real_key = self.split_scope_key(key) + if scope is None or scope == self._scope: + # get from self + if real_key in self._module_dict: + return self._module_dict[real_key] + else: + # get from self._children + if scope in self._children: + return self._children[scope].get(real_key) + else: + # goto root + parent = self.parent + while parent.parent is not None: + parent = parent.parent + return parent.get(key) + + def build(self, *args, **kwargs): + return self.build_func(*args, **kwargs, registry=self) + + def _add_children(self, registry): + """Add children for a registry. + + The ``registry`` will be added as children based on its scope. + The parent registry could build objects from children registry. + + Example: + >>> models = Registry('models') + >>> mmdet_models = Registry('models', parent=models) + >>> @mmdet_models.register_module() + >>> class ResNet: + >>> pass + >>> resnet = models.build(dict(type='mmdet.ResNet')) + """ + + assert isinstance(registry, Registry) + assert registry.scope is not None + assert registry.scope not in self.children, \ + f'scope {registry.scope} exists in {self.name} registry' + self.children[registry.scope] = registry + + def _register_module(self, module_class, module_name=None, force=False): + if not inspect.isclass(module_class): + raise TypeError('module must be a class, ' + f'but got {type(module_class)}') + + if module_name is None: + module_name = module_class.__name__ + if isinstance(module_name, str): + module_name = [module_name] + for name in module_name: + if not force and name in self._module_dict: + raise KeyError(f'{name} is already registered ' + f'in {self.name}') + self._module_dict[name] = module_class + + def deprecated_register_module(self, cls=None, force=False): + warnings.warn( + 'The old API of register_module(module, force=False) ' + 'is deprecated and will be removed, please use the new API ' + 'register_module(name=None, force=False, module=None) instead.') + if cls is None: + return partial(self.deprecated_register_module, force=force) + self._register_module(cls, force=force) + return cls + + def register_module(self, name=None, force=False, module=None): + """Register a module. + + A record will be added to `self._module_dict`, whose key is the class + name or the specified name, and value is the class itself. + It can be used as a decorator or a normal function. + + Example: + >>> backbones = Registry('backbone') + >>> @backbones.register_module() + >>> class ResNet: + >>> pass + + >>> backbones = Registry('backbone') + >>> @backbones.register_module(name='mnet') + >>> class MobileNet: + >>> pass + + >>> backbones = Registry('backbone') + >>> class ResNet: + >>> pass + >>> backbones.register_module(ResNet) + + Args: + name (str | None): The module name to be registered. If not + specified, the class name will be used. + force (bool, optional): Whether to override an existing class with + the same name. Default: False. + module (type): Module class to be registered. + """ + if not isinstance(force, bool): + raise TypeError(f'force must be a boolean, but got {type(force)}') + # NOTE: This is a walkaround to be compatible with the old api, + # while it may introduce unexpected bugs. + if isinstance(name, type): + return self.deprecated_register_module(name, force=force) + + # raise the error ahead of time + if not (name is None or isinstance(name, str) or is_seq_of(name, str)): + raise TypeError( + 'name must be either of None, an instance of str or a sequence' + f' of str, but got {type(name)}') + + # use it as a normal method: x.register_module(module=SomeClass) + if module is not None: + self._register_module( + module_class=module, module_name=name, force=force) + return module + + # use it as a decorator: @x.register_module() + def _register(cls): + self._register_module( + module_class=cls, module_name=name, force=force) + return cls + + return _register diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/testing.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/testing.py new file mode 100644 index 0000000..a27f936 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/testing.py @@ -0,0 +1,140 @@ +# Copyright (c) Open-MMLab. +import sys +from collections.abc import Iterable +from runpy import run_path +from shlex import split +from typing import Any, Dict, List +from unittest.mock import patch + + +def check_python_script(cmd): + """Run the python cmd script with `__main__`. The difference between + `os.system` is that, this function exectues code in the current process, so + that it can be tracked by coverage tools. Currently it supports two forms: + + - ./tests/data/scripts/hello.py zz + - python tests/data/scripts/hello.py zz + """ + args = split(cmd) + if args[0] == 'python': + args = args[1:] + with patch.object(sys, 'argv', args): + run_path(args[0], run_name='__main__') + + +def _any(judge_result): + """Since built-in ``any`` works only when the element of iterable is not + iterable, implement the function.""" + if not isinstance(judge_result, Iterable): + return judge_result + + try: + for element in judge_result: + if _any(element): + return True + except TypeError: + # Maybe encounter the case: torch.tensor(True) | torch.tensor(False) + if judge_result: + return True + return False + + +def assert_dict_contains_subset(dict_obj: Dict[Any, Any], + expected_subset: Dict[Any, Any]) -> bool: + """Check if the dict_obj contains the expected_subset. + + Args: + dict_obj (Dict[Any, Any]): Dict object to be checked. + expected_subset (Dict[Any, Any]): Subset expected to be contained in + dict_obj. + + Returns: + bool: Whether the dict_obj contains the expected_subset. + """ + + for key, value in expected_subset.items(): + if key not in dict_obj.keys() or _any(dict_obj[key] != value): + return False + return True + + +def assert_attrs_equal(obj: Any, expected_attrs: Dict[str, Any]) -> bool: + """Check if attribute of class object is correct. + + Args: + obj (object): Class object to be checked. + expected_attrs (Dict[str, Any]): Dict of the expected attrs. + + Returns: + bool: Whether the attribute of class object is correct. + """ + for attr, value in expected_attrs.items(): + if not hasattr(obj, attr) or _any(getattr(obj, attr) != value): + return False + return True + + +def assert_dict_has_keys(obj: Dict[str, Any], + expected_keys: List[str]) -> bool: + """Check if the obj has all the expected_keys. + + Args: + obj (Dict[str, Any]): Object to be checked. + expected_keys (List[str]): Keys expected to contained in the keys of + the obj. + + Returns: + bool: Whether the obj has the expected keys. + """ + return set(expected_keys).issubset(set(obj.keys())) + + +def assert_keys_equal(result_keys: List[str], target_keys: List[str]) -> bool: + """Check if target_keys is equal to result_keys. + + Args: + result_keys (List[str]): Result keys to be checked. + target_keys (List[str]): Target keys to be checked. + + Returns: + bool: Whether target_keys is equal to result_keys. + """ + return set(result_keys) == set(target_keys) + + +def assert_is_norm_layer(module) -> bool: + """Check if the module is a norm layer. + + Args: + module (nn.Module): The module to be checked. + + Returns: + bool: Whether the module is a norm layer. + """ + from .parrots_wrapper import _BatchNorm, _InstanceNorm + from torch.nn import GroupNorm, LayerNorm + norm_layer_candidates = (_BatchNorm, _InstanceNorm, GroupNorm, LayerNorm) + return isinstance(module, norm_layer_candidates) + + +def assert_params_all_zeros(module) -> bool: + """Check if the parameters of the module is all zeros. + + Args: + module (nn.Module): The module to be checked. + + Returns: + bool: Whether the parameters of the module is all zeros. + """ + weight_data = module.weight.data + is_weight_zero = weight_data.allclose( + weight_data.new_zeros(weight_data.size())) + + if hasattr(module, 'bias') and module.bias is not None: + bias_data = module.bias.data + is_bias_zero = bias_data.allclose( + bias_data.new_zeros(bias_data.size())) + else: + is_bias_zero = True + + return is_weight_zero and is_bias_zero diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/timer.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/timer.py new file mode 100644 index 0000000..e3db7d4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/timer.py @@ -0,0 +1,118 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from time import time + + +class TimerError(Exception): + + def __init__(self, message): + self.message = message + super(TimerError, self).__init__(message) + + +class Timer: + """A flexible Timer class. + + :Example: + + >>> import time + >>> import annotator.uniformer.mmcv as mmcv + >>> with mmcv.Timer(): + >>> # simulate a code block that will run for 1s + >>> time.sleep(1) + 1.000 + >>> with mmcv.Timer(print_tmpl='it takes {:.1f} seconds'): + >>> # simulate a code block that will run for 1s + >>> time.sleep(1) + it takes 1.0 seconds + >>> timer = mmcv.Timer() + >>> time.sleep(0.5) + >>> print(timer.since_start()) + 0.500 + >>> time.sleep(0.5) + >>> print(timer.since_last_check()) + 0.500 + >>> print(timer.since_start()) + 1.000 + """ + + def __init__(self, start=True, print_tmpl=None): + self._is_running = False + self.print_tmpl = print_tmpl if print_tmpl else '{:.3f}' + if start: + self.start() + + @property + def is_running(self): + """bool: indicate whether the timer is running""" + return self._is_running + + def __enter__(self): + self.start() + return self + + def __exit__(self, type, value, traceback): + print(self.print_tmpl.format(self.since_last_check())) + self._is_running = False + + def start(self): + """Start the timer.""" + if not self._is_running: + self._t_start = time() + self._is_running = True + self._t_last = time() + + def since_start(self): + """Total time since the timer is started. + + Returns (float): Time in seconds. + """ + if not self._is_running: + raise TimerError('timer is not running') + self._t_last = time() + return self._t_last - self._t_start + + def since_last_check(self): + """Time since the last checking. + + Either :func:`since_start` or :func:`since_last_check` is a checking + operation. + + Returns (float): Time in seconds. + """ + if not self._is_running: + raise TimerError('timer is not running') + dur = time() - self._t_last + self._t_last = time() + return dur + + +_g_timers = {} # global timers + + +def check_time(timer_id): + """Add check points in a single line. + + This method is suitable for running a task on a list of items. A timer will + be registered when the method is called for the first time. + + :Example: + + >>> import time + >>> import annotator.uniformer.mmcv as mmcv + >>> for i in range(1, 6): + >>> # simulate a code block + >>> time.sleep(i) + >>> mmcv.check_time('task1') + 2.000 + 3.000 + 4.000 + 5.000 + + Args: + timer_id (str): Timer identifier. + """ + if timer_id not in _g_timers: + _g_timers[timer_id] = Timer() + return 0 + else: + return _g_timers[timer_id].since_last_check() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/trace.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/trace.py new file mode 100644 index 0000000..5ca99dc --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/trace.py @@ -0,0 +1,23 @@ +import warnings + +import torch + +from annotator.uniformer.mmcv.utils import digit_version + + +def is_jit_tracing() -> bool: + if (torch.__version__ != 'parrots' + and digit_version(torch.__version__) >= digit_version('1.6.0')): + on_trace = torch.jit.is_tracing() + # In PyTorch 1.6, torch.jit.is_tracing has a bug. + # Refers to https://github.com/pytorch/pytorch/issues/42448 + if isinstance(on_trace, bool): + return on_trace + else: + return torch._C._is_tracing() + else: + warnings.warn( + 'torch.jit.is_tracing is only supported after v1.6.0. ' + 'Therefore is_tracing returns False automatically. Please ' + 'set on_trace manually if you are using trace.', UserWarning) + return False diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/version_utils.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/version_utils.py new file mode 100644 index 0000000..963c45a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/utils/version_utils.py @@ -0,0 +1,90 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import subprocess +import warnings + +from packaging.version import parse + + +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). + """ + assert 'parrots' not in version_str + 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) + + +def _minimal_ext_cmd(cmd): + # construct minimal environment + env = {} + for k in ['SYSTEMROOT', 'PATH', 'HOME']: + v = os.environ.get(k) + if v is not None: + env[k] = v + # LANGUAGE is used on win32 + env['LANGUAGE'] = 'C' + env['LANG'] = 'C' + env['LC_ALL'] = 'C' + out = subprocess.Popen( + cmd, stdout=subprocess.PIPE, env=env).communicate()[0] + return out + + +def get_git_hash(fallback='unknown', digits=None): + """Get the git hash of the current repo. + + Args: + fallback (str, optional): The fallback string when git hash is + unavailable. Defaults to 'unknown'. + digits (int, optional): kept digits of the hash. Defaults to None, + meaning all digits are kept. + + Returns: + str: Git commit hash. + """ + + if digits is not None and not isinstance(digits, int): + raise TypeError('digits must be None or an integer') + + try: + out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD']) + sha = out.strip().decode('ascii') + if digits is not None: + sha = sha[:digits] + except OSError: + sha = fallback + + return sha diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/version.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/version.py new file mode 100644 index 0000000..1cce4e5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/version.py @@ -0,0 +1,35 @@ +# Copyright (c) OpenMMLab. All rights reserved. +__version__ = '1.3.17' + + +def parse_version_info(version_str: str, length: int = 4) -> tuple: + """Parse a version string into a tuple. + + Args: + version_str (str): The version string. + length (int): The maximum number of version levels. Default: 4. + + Returns: + tuple[int | str]: The version info, e.g., "1.3.0" is parsed into + (1, 3, 0, 0, 0, 0), and "2.0.0rc1" is parsed into + (2, 0, 0, 0, 'rc', 1) (when length is set to 4). + """ + from packaging.version import parse + 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: + release.extend(list(version.pre)) + elif version.is_postrelease: + release.extend(list(version.post)) + else: + release.extend([0, 0]) + return tuple(release) + + +version_info = tuple(int(x) for x in __version__.split('.')[:3]) + +__all__ = ['__version__', 'version_info', 'parse_version_info'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/video/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/video/__init__.py new file mode 100644 index 0000000..73199b0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/video/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .io import Cache, VideoReader, frames2video +from .optflow import (dequantize_flow, flow_from_bytes, flow_warp, flowread, + flowwrite, quantize_flow, sparse_flow_from_bytes) +from .processing import concat_video, convert_video, cut_video, resize_video + +__all__ = [ + 'Cache', 'VideoReader', 'frames2video', 'convert_video', 'resize_video', + 'cut_video', 'concat_video', 'flowread', 'flowwrite', 'quantize_flow', + 'dequantize_flow', 'flow_warp', 'flow_from_bytes', 'sparse_flow_from_bytes' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/video/io.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/video/io.py new file mode 100644 index 0000000..9879154 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/video/io.py @@ -0,0 +1,318 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from collections import OrderedDict + +import cv2 +from cv2 import (CAP_PROP_FOURCC, CAP_PROP_FPS, CAP_PROP_FRAME_COUNT, + CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, + CAP_PROP_POS_FRAMES, VideoWriter_fourcc) + +from annotator.uniformer.mmcv.utils import (check_file_exist, mkdir_or_exist, scandir, + track_progress) + + +class Cache: + + def __init__(self, capacity): + self._cache = OrderedDict() + self._capacity = int(capacity) + if capacity <= 0: + raise ValueError('capacity must be a positive integer') + + @property + def capacity(self): + return self._capacity + + @property + def size(self): + return len(self._cache) + + def put(self, key, val): + if key in self._cache: + return + if len(self._cache) >= self.capacity: + self._cache.popitem(last=False) + self._cache[key] = val + + def get(self, key, default=None): + val = self._cache[key] if key in self._cache else default + return val + + +class VideoReader: + """Video class with similar usage to a list object. + + This video warpper class provides convenient apis to access frames. + There exists an issue of OpenCV's VideoCapture class that jumping to a + certain frame may be inaccurate. It is fixed in this class by checking + the position after jumping each time. + Cache is used when decoding videos. So if the same frame is visited for + the second time, there is no need to decode again if it is stored in the + cache. + + :Example: + + >>> import annotator.uniformer.mmcv as mmcv + >>> v = mmcv.VideoReader('sample.mp4') + >>> len(v) # get the total frame number with `len()` + 120 + >>> for img in v: # v is iterable + >>> mmcv.imshow(img) + >>> v[5] # get the 6th frame + """ + + def __init__(self, filename, cache_capacity=10): + # Check whether the video path is a url + if not filename.startswith(('https://', 'http://')): + check_file_exist(filename, 'Video file not found: ' + filename) + self._vcap = cv2.VideoCapture(filename) + assert cache_capacity > 0 + self._cache = Cache(cache_capacity) + self._position = 0 + # get basic info + self._width = int(self._vcap.get(CAP_PROP_FRAME_WIDTH)) + self._height = int(self._vcap.get(CAP_PROP_FRAME_HEIGHT)) + self._fps = self._vcap.get(CAP_PROP_FPS) + self._frame_cnt = int(self._vcap.get(CAP_PROP_FRAME_COUNT)) + self._fourcc = self._vcap.get(CAP_PROP_FOURCC) + + @property + def vcap(self): + """:obj:`cv2.VideoCapture`: The raw VideoCapture object.""" + return self._vcap + + @property + def opened(self): + """bool: Indicate whether the video is opened.""" + return self._vcap.isOpened() + + @property + def width(self): + """int: Width of video frames.""" + return self._width + + @property + def height(self): + """int: Height of video frames.""" + return self._height + + @property + def resolution(self): + """tuple: Video resolution (width, height).""" + return (self._width, self._height) + + @property + def fps(self): + """float: FPS of the video.""" + return self._fps + + @property + def frame_cnt(self): + """int: Total frames of the video.""" + return self._frame_cnt + + @property + def fourcc(self): + """str: "Four character code" of the video.""" + return self._fourcc + + @property + def position(self): + """int: Current cursor position, indicating frame decoded.""" + return self._position + + def _get_real_position(self): + return int(round(self._vcap.get(CAP_PROP_POS_FRAMES))) + + def _set_real_position(self, frame_id): + self._vcap.set(CAP_PROP_POS_FRAMES, frame_id) + pos = self._get_real_position() + for _ in range(frame_id - pos): + self._vcap.read() + self._position = frame_id + + def read(self): + """Read the next frame. + + If the next frame have been decoded before and in the cache, then + return it directly, otherwise decode, cache and return it. + + Returns: + ndarray or None: Return the frame if successful, otherwise None. + """ + # pos = self._position + if self._cache: + img = self._cache.get(self._position) + if img is not None: + ret = True + else: + if self._position != self._get_real_position(): + self._set_real_position(self._position) + ret, img = self._vcap.read() + if ret: + self._cache.put(self._position, img) + else: + ret, img = self._vcap.read() + if ret: + self._position += 1 + return img + + def get_frame(self, frame_id): + """Get frame by index. + + Args: + frame_id (int): Index of the expected frame, 0-based. + + Returns: + ndarray or None: Return the frame if successful, otherwise None. + """ + if frame_id < 0 or frame_id >= self._frame_cnt: + raise IndexError( + f'"frame_id" must be between 0 and {self._frame_cnt - 1}') + if frame_id == self._position: + return self.read() + if self._cache: + img = self._cache.get(frame_id) + if img is not None: + self._position = frame_id + 1 + return img + self._set_real_position(frame_id) + ret, img = self._vcap.read() + if ret: + if self._cache: + self._cache.put(self._position, img) + self._position += 1 + return img + + def current_frame(self): + """Get the current frame (frame that is just visited). + + Returns: + ndarray or None: If the video is fresh, return None, otherwise + return the frame. + """ + if self._position == 0: + return None + return self._cache.get(self._position - 1) + + def cvt2frames(self, + frame_dir, + file_start=0, + filename_tmpl='{:06d}.jpg', + start=0, + max_num=0, + show_progress=True): + """Convert a video to frame images. + + Args: + frame_dir (str): Output directory to store all the frame images. + file_start (int): Filenames will start from the specified number. + filename_tmpl (str): Filename template with the index as the + placeholder. + start (int): The starting frame index. + max_num (int): Maximum number of frames to be written. + show_progress (bool): Whether to show a progress bar. + """ + mkdir_or_exist(frame_dir) + if max_num == 0: + task_num = self.frame_cnt - start + else: + task_num = min(self.frame_cnt - start, max_num) + if task_num <= 0: + raise ValueError('start must be less than total frame number') + if start > 0: + self._set_real_position(start) + + def write_frame(file_idx): + img = self.read() + if img is None: + return + filename = osp.join(frame_dir, filename_tmpl.format(file_idx)) + cv2.imwrite(filename, img) + + if show_progress: + track_progress(write_frame, range(file_start, + file_start + task_num)) + else: + for i in range(task_num): + write_frame(file_start + i) + + def __len__(self): + return self.frame_cnt + + def __getitem__(self, index): + if isinstance(index, slice): + return [ + self.get_frame(i) + for i in range(*index.indices(self.frame_cnt)) + ] + # support negative indexing + if index < 0: + index += self.frame_cnt + if index < 0: + raise IndexError('index out of range') + return self.get_frame(index) + + def __iter__(self): + self._set_real_position(0) + return self + + def __next__(self): + img = self.read() + if img is not None: + return img + else: + raise StopIteration + + next = __next__ + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._vcap.release() + + +def frames2video(frame_dir, + video_file, + fps=30, + fourcc='XVID', + filename_tmpl='{:06d}.jpg', + start=0, + end=0, + show_progress=True): + """Read the frame images from a directory and join them as a video. + + Args: + frame_dir (str): The directory containing video frames. + video_file (str): Output filename. + fps (float): FPS of the output video. + fourcc (str): Fourcc of the output video, this should be compatible + with the output file type. + filename_tmpl (str): Filename template with the index as the variable. + start (int): Starting frame index. + end (int): Ending frame index. + show_progress (bool): Whether to show a progress bar. + """ + if end == 0: + ext = filename_tmpl.split('.')[-1] + end = len([name for name in scandir(frame_dir, ext)]) + first_file = osp.join(frame_dir, filename_tmpl.format(start)) + check_file_exist(first_file, 'The start frame not found: ' + first_file) + img = cv2.imread(first_file) + height, width = img.shape[:2] + resolution = (width, height) + vwriter = cv2.VideoWriter(video_file, VideoWriter_fourcc(*fourcc), fps, + resolution) + + def write_frame(file_idx): + filename = osp.join(frame_dir, filename_tmpl.format(file_idx)) + img = cv2.imread(filename) + vwriter.write(img) + + if show_progress: + track_progress(write_frame, range(start, end)) + else: + for i in range(start, end): + write_frame(i) + vwriter.release() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/video/optflow.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/video/optflow.py new file mode 100644 index 0000000..84160f8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/video/optflow.py @@ -0,0 +1,254 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import cv2 +import numpy as np + +from annotator.uniformer.mmcv.arraymisc import dequantize, quantize +from annotator.uniformer.mmcv.image import imread, imwrite +from annotator.uniformer.mmcv.utils import is_str + + +def flowread(flow_or_path, quantize=False, concat_axis=0, *args, **kwargs): + """Read an optical flow map. + + Args: + flow_or_path (ndarray or str): A flow map or filepath. + quantize (bool): whether to read quantized pair, if set to True, + remaining args will be passed to :func:`dequantize_flow`. + concat_axis (int): The axis that dx and dy are concatenated, + can be either 0 or 1. Ignored if quantize is False. + + Returns: + ndarray: Optical flow represented as a (h, w, 2) numpy array + """ + if isinstance(flow_or_path, np.ndarray): + if (flow_or_path.ndim != 3) or (flow_or_path.shape[-1] != 2): + raise ValueError(f'Invalid flow with shape {flow_or_path.shape}') + return flow_or_path + elif not is_str(flow_or_path): + raise TypeError(f'"flow_or_path" must be a filename or numpy array, ' + f'not {type(flow_or_path)}') + + if not quantize: + with open(flow_or_path, 'rb') as f: + try: + header = f.read(4).decode('utf-8') + except Exception: + raise IOError(f'Invalid flow file: {flow_or_path}') + else: + if header != 'PIEH': + raise IOError(f'Invalid flow file: {flow_or_path}, ' + 'header does not contain PIEH') + + w = np.fromfile(f, np.int32, 1).squeeze() + h = np.fromfile(f, np.int32, 1).squeeze() + flow = np.fromfile(f, np.float32, w * h * 2).reshape((h, w, 2)) + else: + assert concat_axis in [0, 1] + cat_flow = imread(flow_or_path, flag='unchanged') + if cat_flow.ndim != 2: + raise IOError( + f'{flow_or_path} is not a valid quantized flow file, ' + f'its dimension is {cat_flow.ndim}.') + assert cat_flow.shape[concat_axis] % 2 == 0 + dx, dy = np.split(cat_flow, 2, axis=concat_axis) + flow = dequantize_flow(dx, dy, *args, **kwargs) + + return flow.astype(np.float32) + + +def flowwrite(flow, filename, quantize=False, concat_axis=0, *args, **kwargs): + """Write optical flow to file. + + If the flow is not quantized, it will be saved as a .flo file losslessly, + otherwise a jpeg image which is lossy but of much smaller size. (dx and dy + will be concatenated horizontally into a single image if quantize is True.) + + Args: + flow (ndarray): (h, w, 2) array of optical flow. + filename (str): Output filepath. + quantize (bool): Whether to quantize the flow and save it to 2 jpeg + images. If set to True, remaining args will be passed to + :func:`quantize_flow`. + concat_axis (int): The axis that dx and dy are concatenated, + can be either 0 or 1. Ignored if quantize is False. + """ + if not quantize: + with open(filename, 'wb') as f: + f.write('PIEH'.encode('utf-8')) + np.array([flow.shape[1], flow.shape[0]], dtype=np.int32).tofile(f) + flow = flow.astype(np.float32) + flow.tofile(f) + f.flush() + else: + assert concat_axis in [0, 1] + dx, dy = quantize_flow(flow, *args, **kwargs) + dxdy = np.concatenate((dx, dy), axis=concat_axis) + imwrite(dxdy, filename) + + +def quantize_flow(flow, max_val=0.02, norm=True): + """Quantize flow to [0, 255]. + + After this step, the size of flow will be much smaller, and can be + dumped as jpeg images. + + Args: + flow (ndarray): (h, w, 2) array of optical flow. + max_val (float): Maximum value of flow, values beyond + [-max_val, max_val] will be truncated. + norm (bool): Whether to divide flow values by image width/height. + + Returns: + tuple[ndarray]: Quantized dx and dy. + """ + h, w, _ = flow.shape + dx = flow[..., 0] + dy = flow[..., 1] + if norm: + dx = dx / w # avoid inplace operations + dy = dy / h + # use 255 levels instead of 256 to make sure 0 is 0 after dequantization. + flow_comps = [ + quantize(d, -max_val, max_val, 255, np.uint8) for d in [dx, dy] + ] + return tuple(flow_comps) + + +def dequantize_flow(dx, dy, max_val=0.02, denorm=True): + """Recover from quantized flow. + + Args: + dx (ndarray): Quantized dx. + dy (ndarray): Quantized dy. + max_val (float): Maximum value used when quantizing. + denorm (bool): Whether to multiply flow values with width/height. + + Returns: + ndarray: Dequantized flow. + """ + assert dx.shape == dy.shape + assert dx.ndim == 2 or (dx.ndim == 3 and dx.shape[-1] == 1) + + dx, dy = [dequantize(d, -max_val, max_val, 255) for d in [dx, dy]] + + if denorm: + dx *= dx.shape[1] + dy *= dx.shape[0] + flow = np.dstack((dx, dy)) + return flow + + +def flow_warp(img, flow, filling_value=0, interpolate_mode='nearest'): + """Use flow to warp img. + + Args: + img (ndarray, float or uint8): Image to be warped. + flow (ndarray, float): Optical Flow. + filling_value (int): The missing pixels will be set with filling_value. + interpolate_mode (str): bilinear -> Bilinear Interpolation; + nearest -> Nearest Neighbor. + + Returns: + ndarray: Warped image with the same shape of img + """ + warnings.warn('This function is just for prototyping and cannot ' + 'guarantee the computational efficiency.') + assert flow.ndim == 3, 'Flow must be in 3D arrays.' + height = flow.shape[0] + width = flow.shape[1] + channels = img.shape[2] + + output = np.ones( + (height, width, channels), dtype=img.dtype) * filling_value + + grid = np.indices((height, width)).swapaxes(0, 1).swapaxes(1, 2) + dx = grid[:, :, 0] + flow[:, :, 1] + dy = grid[:, :, 1] + flow[:, :, 0] + sx = np.floor(dx).astype(int) + sy = np.floor(dy).astype(int) + valid = (sx >= 0) & (sx < height - 1) & (sy >= 0) & (sy < width - 1) + + if interpolate_mode == 'nearest': + output[valid, :] = img[dx[valid].round().astype(int), + dy[valid].round().astype(int), :] + elif interpolate_mode == 'bilinear': + # dirty walkround for integer positions + eps_ = 1e-6 + dx, dy = dx + eps_, dy + eps_ + left_top_ = img[np.floor(dx[valid]).astype(int), + np.floor(dy[valid]).astype(int), :] * ( + np.ceil(dx[valid]) - dx[valid])[:, None] * ( + np.ceil(dy[valid]) - dy[valid])[:, None] + left_down_ = img[np.ceil(dx[valid]).astype(int), + np.floor(dy[valid]).astype(int), :] * ( + dx[valid] - np.floor(dx[valid]))[:, None] * ( + np.ceil(dy[valid]) - dy[valid])[:, None] + right_top_ = img[np.floor(dx[valid]).astype(int), + np.ceil(dy[valid]).astype(int), :] * ( + np.ceil(dx[valid]) - dx[valid])[:, None] * ( + dy[valid] - np.floor(dy[valid]))[:, None] + right_down_ = img[np.ceil(dx[valid]).astype(int), + np.ceil(dy[valid]).astype(int), :] * ( + dx[valid] - np.floor(dx[valid]))[:, None] * ( + dy[valid] - np.floor(dy[valid]))[:, None] + output[valid, :] = left_top_ + left_down_ + right_top_ + right_down_ + else: + raise NotImplementedError( + 'We only support interpolation modes of nearest and bilinear, ' + f'but got {interpolate_mode}.') + return output.astype(img.dtype) + + +def flow_from_bytes(content): + """Read dense optical flow from bytes. + + .. note:: + This load optical flow function works for FlyingChairs, FlyingThings3D, + Sintel, FlyingChairsOcc datasets, but cannot load the data from + ChairsSDHom. + + Args: + content (bytes): Optical flow bytes got from files or other streams. + + Returns: + ndarray: Loaded optical flow with the shape (H, W, 2). + """ + + # header in first 4 bytes + header = content[:4] + if header.decode('utf-8') != 'PIEH': + raise Exception('Flow file header does not contain PIEH') + # width in second 4 bytes + width = np.frombuffer(content[4:], np.int32, 1).squeeze() + # height in third 4 bytes + height = np.frombuffer(content[8:], np.int32, 1).squeeze() + # after first 12 bytes, all bytes are flow + flow = np.frombuffer(content[12:], np.float32, width * height * 2).reshape( + (height, width, 2)) + + return flow + + +def sparse_flow_from_bytes(content): + """Read the optical flow in KITTI datasets from bytes. + + This function is modified from RAFT load the `KITTI datasets + `_. + + Args: + content (bytes): Optical flow bytes got from files or other streams. + + Returns: + Tuple(ndarray, ndarray): Loaded optical flow with the shape (H, W, 2) + and flow valid mask with the shape (H, W). + """ # nopa + + content = np.frombuffer(content, np.uint8) + flow = cv2.imdecode(content, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_COLOR) + flow = flow[:, :, ::-1].astype(np.float32) + # flow shape (H, W, 2) valid shape (H, W) + flow, valid = flow[:, :, :2], flow[:, :, 2] + flow = (flow - 2**15) / 64.0 + return flow, valid diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/video/processing.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/video/processing.py new file mode 100644 index 0000000..3d90b96 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/video/processing.py @@ -0,0 +1,160 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +import subprocess +import tempfile + +from annotator.uniformer.mmcv.utils import requires_executable + + +@requires_executable('ffmpeg') +def convert_video(in_file, + out_file, + print_cmd=False, + pre_options='', + **kwargs): + """Convert a video with ffmpeg. + + This provides a general api to ffmpeg, the executed command is:: + + `ffmpeg -y -i ` + + Options(kwargs) are mapped to ffmpeg commands with the following rules: + + - key=val: "-key val" + - key=True: "-key" + - key=False: "" + + Args: + in_file (str): Input video filename. + out_file (str): Output video filename. + pre_options (str): Options appears before "-i ". + print_cmd (bool): Whether to print the final ffmpeg command. + """ + options = [] + for k, v in kwargs.items(): + if isinstance(v, bool): + if v: + options.append(f'-{k}') + elif k == 'log_level': + assert v in [ + 'quiet', 'panic', 'fatal', 'error', 'warning', 'info', + 'verbose', 'debug', 'trace' + ] + options.append(f'-loglevel {v}') + else: + options.append(f'-{k} {v}') + cmd = f'ffmpeg -y {pre_options} -i {in_file} {" ".join(options)} ' \ + f'{out_file}' + if print_cmd: + print(cmd) + subprocess.call(cmd, shell=True) + + +@requires_executable('ffmpeg') +def resize_video(in_file, + out_file, + size=None, + ratio=None, + keep_ar=False, + log_level='info', + print_cmd=False): + """Resize a video. + + Args: + in_file (str): Input video filename. + out_file (str): Output video filename. + size (tuple): Expected size (w, h), eg, (320, 240) or (320, -1). + ratio (tuple or float): Expected resize ratio, (2, 0.5) means + (w*2, h*0.5). + keep_ar (bool): Whether to keep original aspect ratio. + log_level (str): Logging level of ffmpeg. + print_cmd (bool): Whether to print the final ffmpeg command. + """ + if size is None and ratio is None: + raise ValueError('expected size or ratio must be specified') + if size is not None and ratio is not None: + raise ValueError('size and ratio cannot be specified at the same time') + options = {'log_level': log_level} + if size: + if not keep_ar: + options['vf'] = f'scale={size[0]}:{size[1]}' + else: + options['vf'] = f'scale=w={size[0]}:h={size[1]}:' \ + 'force_original_aspect_ratio=decrease' + else: + if not isinstance(ratio, tuple): + ratio = (ratio, ratio) + options['vf'] = f'scale="trunc(iw*{ratio[0]}):trunc(ih*{ratio[1]})"' + convert_video(in_file, out_file, print_cmd, **options) + + +@requires_executable('ffmpeg') +def cut_video(in_file, + out_file, + start=None, + end=None, + vcodec=None, + acodec=None, + log_level='info', + print_cmd=False): + """Cut a clip from a video. + + Args: + in_file (str): Input video filename. + out_file (str): Output video filename. + start (None or float): Start time (in seconds). + end (None or float): End time (in seconds). + vcodec (None or str): Output video codec, None for unchanged. + acodec (None or str): Output audio codec, None for unchanged. + log_level (str): Logging level of ffmpeg. + print_cmd (bool): Whether to print the final ffmpeg command. + """ + options = {'log_level': log_level} + if vcodec is None: + options['vcodec'] = 'copy' + if acodec is None: + options['acodec'] = 'copy' + if start: + options['ss'] = start + else: + start = 0 + if end: + options['t'] = end - start + convert_video(in_file, out_file, print_cmd, **options) + + +@requires_executable('ffmpeg') +def concat_video(video_list, + out_file, + vcodec=None, + acodec=None, + log_level='info', + print_cmd=False): + """Concatenate multiple videos into a single one. + + Args: + video_list (list): A list of video filenames + out_file (str): Output video filename + vcodec (None or str): Output video codec, None for unchanged + acodec (None or str): Output audio codec, None for unchanged + log_level (str): Logging level of ffmpeg. + print_cmd (bool): Whether to print the final ffmpeg command. + """ + tmp_filehandler, tmp_filename = tempfile.mkstemp(suffix='.txt', text=True) + with open(tmp_filename, 'w') as f: + for filename in video_list: + f.write(f'file {osp.abspath(filename)}\n') + options = {'log_level': log_level} + if vcodec is None: + options['vcodec'] = 'copy' + if acodec is None: + options['acodec'] = 'copy' + convert_video( + tmp_filename, + out_file, + print_cmd, + pre_options='-f concat -safe 0', + **options) + os.close(tmp_filehandler) + os.remove(tmp_filename) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/__init__.py new file mode 100644 index 0000000..835df13 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .color import Color, color_val +from .image import imshow, imshow_bboxes, imshow_det_bboxes +from .optflow import flow2rgb, flowshow, make_color_wheel + +__all__ = [ + 'Color', 'color_val', 'imshow', 'imshow_bboxes', 'imshow_det_bboxes', + 'flowshow', 'flow2rgb', 'make_color_wheel' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/color.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/color.py new file mode 100644 index 0000000..9041e0e --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/color.py @@ -0,0 +1,51 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from enum import Enum + +import numpy as np + +from annotator.uniformer.mmcv.utils import is_str + + +class Color(Enum): + """An enum that defines common colors. + + Contains red, green, blue, cyan, yellow, magenta, white and black. + """ + red = (0, 0, 255) + green = (0, 255, 0) + blue = (255, 0, 0) + cyan = (255, 255, 0) + yellow = (0, 255, 255) + magenta = (255, 0, 255) + white = (255, 255, 255) + black = (0, 0, 0) + + +def color_val(color): + """Convert various input to color tuples. + + Args: + color (:obj:`Color`/str/tuple/int/ndarray): Color inputs + + Returns: + tuple[int]: A tuple of 3 integers indicating BGR channels. + """ + if is_str(color): + return Color[color].value + elif isinstance(color, Color): + return color.value + elif isinstance(color, tuple): + assert len(color) == 3 + for channel in color: + assert 0 <= channel <= 255 + return color + elif isinstance(color, int): + assert 0 <= color <= 255 + return color, color, color + elif isinstance(color, np.ndarray): + assert color.ndim == 1 and color.size == 3 + assert np.all((color >= 0) & (color <= 255)) + color = color.astype(np.uint8) + return tuple(color) + else: + raise TypeError(f'Invalid type for color: {type(color)}') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/image.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/image.py new file mode 100644 index 0000000..61a56c7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/image.py @@ -0,0 +1,152 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import cv2 +import numpy as np + +from annotator.uniformer.mmcv.image import imread, imwrite +from .color import color_val + + +def imshow(img, win_name='', wait_time=0): + """Show an image. + + Args: + img (str or ndarray): The image to be displayed. + win_name (str): The window name. + wait_time (int): Value of waitKey param. + """ + cv2.imshow(win_name, imread(img)) + if wait_time == 0: # prevent from hanging if windows was closed + while True: + ret = cv2.waitKey(1) + + closed = cv2.getWindowProperty(win_name, cv2.WND_PROP_VISIBLE) < 1 + # if user closed window or if some key pressed + if closed or ret != -1: + break + else: + ret = cv2.waitKey(wait_time) + + +def imshow_bboxes(img, + bboxes, + colors='green', + top_k=-1, + thickness=1, + show=True, + win_name='', + wait_time=0, + out_file=None): + """Draw bboxes on an image. + + Args: + img (str or ndarray): The image to be displayed. + bboxes (list or ndarray): A list of ndarray of shape (k, 4). + colors (list[str or tuple or Color]): A list of colors. + top_k (int): Plot the first k bboxes only if set positive. + thickness (int): Thickness of lines. + show (bool): Whether to show the image. + win_name (str): The window name. + wait_time (int): Value of waitKey param. + out_file (str, optional): The filename to write the image. + + Returns: + ndarray: The image with bboxes drawn on it. + """ + img = imread(img) + img = np.ascontiguousarray(img) + + if isinstance(bboxes, np.ndarray): + bboxes = [bboxes] + if not isinstance(colors, list): + colors = [colors for _ in range(len(bboxes))] + colors = [color_val(c) for c in colors] + assert len(bboxes) == len(colors) + + for i, _bboxes in enumerate(bboxes): + _bboxes = _bboxes.astype(np.int32) + if top_k <= 0: + _top_k = _bboxes.shape[0] + else: + _top_k = min(top_k, _bboxes.shape[0]) + for j in range(_top_k): + left_top = (_bboxes[j, 0], _bboxes[j, 1]) + right_bottom = (_bboxes[j, 2], _bboxes[j, 3]) + cv2.rectangle( + img, left_top, right_bottom, colors[i], thickness=thickness) + + if show: + imshow(img, win_name, wait_time) + if out_file is not None: + imwrite(img, out_file) + return img + + +def imshow_det_bboxes(img, + bboxes, + labels, + class_names=None, + score_thr=0, + bbox_color='green', + text_color='green', + thickness=1, + font_scale=0.5, + show=True, + win_name='', + wait_time=0, + out_file=None): + """Draw bboxes and class labels (with scores) on an image. + + Args: + img (str or ndarray): The image to be displayed. + bboxes (ndarray): Bounding boxes (with scores), shaped (n, 4) or + (n, 5). + labels (ndarray): Labels of bboxes. + class_names (list[str]): Names of each classes. + score_thr (float): Minimum score of bboxes to be shown. + bbox_color (str or tuple or :obj:`Color`): Color of bbox lines. + text_color (str or tuple or :obj:`Color`): Color of texts. + thickness (int): Thickness of lines. + font_scale (float): Font scales of texts. + show (bool): Whether to show the image. + win_name (str): The window name. + wait_time (int): Value of waitKey param. + out_file (str or None): The filename to write the image. + + Returns: + ndarray: The image with bboxes drawn on it. + """ + assert bboxes.ndim == 2 + assert labels.ndim == 1 + assert bboxes.shape[0] == labels.shape[0] + assert bboxes.shape[1] == 4 or bboxes.shape[1] == 5 + img = imread(img) + img = np.ascontiguousarray(img) + + if score_thr > 0: + assert bboxes.shape[1] == 5 + scores = bboxes[:, -1] + inds = scores > score_thr + bboxes = bboxes[inds, :] + labels = labels[inds] + + bbox_color = color_val(bbox_color) + text_color = color_val(text_color) + + for bbox, label in zip(bboxes, labels): + bbox_int = bbox.astype(np.int32) + left_top = (bbox_int[0], bbox_int[1]) + right_bottom = (bbox_int[2], bbox_int[3]) + cv2.rectangle( + img, left_top, right_bottom, bbox_color, thickness=thickness) + label_text = class_names[ + label] if class_names is not None else f'cls {label}' + if len(bbox) > 4: + label_text += f'|{bbox[-1]:.02f}' + cv2.putText(img, label_text, (bbox_int[0], bbox_int[1] - 2), + cv2.FONT_HERSHEY_COMPLEX, font_scale, text_color) + + if show: + imshow(img, win_name, wait_time) + if out_file is not None: + imwrite(img, out_file) + return img diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/optflow.py b/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/optflow.py new file mode 100644 index 0000000..c3870c7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv/visualization/optflow.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from __future__ import division + +import numpy as np + +from annotator.uniformer.mmcv.image import rgb2bgr +from annotator.uniformer.mmcv.video import flowread +from .image import imshow + + +def flowshow(flow, win_name='', wait_time=0): + """Show optical flow. + + Args: + flow (ndarray or str): The optical flow to be displayed. + win_name (str): The window name. + wait_time (int): Value of waitKey param. + """ + flow = flowread(flow) + flow_img = flow2rgb(flow) + imshow(rgb2bgr(flow_img), win_name, wait_time) + + +def flow2rgb(flow, color_wheel=None, unknown_thr=1e6): + """Convert flow map to RGB image. + + Args: + flow (ndarray): Array of optical flow. + color_wheel (ndarray or None): Color wheel used to map flow field to + RGB colorspace. Default color wheel will be used if not specified. + unknown_thr (str): Values above this threshold will be marked as + unknown and thus ignored. + + Returns: + ndarray: RGB image that can be visualized. + """ + assert flow.ndim == 3 and flow.shape[-1] == 2 + if color_wheel is None: + color_wheel = make_color_wheel() + assert color_wheel.ndim == 2 and color_wheel.shape[1] == 3 + num_bins = color_wheel.shape[0] + + dx = flow[:, :, 0].copy() + dy = flow[:, :, 1].copy() + + ignore_inds = ( + np.isnan(dx) | np.isnan(dy) | (np.abs(dx) > unknown_thr) | + (np.abs(dy) > unknown_thr)) + dx[ignore_inds] = 0 + dy[ignore_inds] = 0 + + rad = np.sqrt(dx**2 + dy**2) + if np.any(rad > np.finfo(float).eps): + max_rad = np.max(rad) + dx /= max_rad + dy /= max_rad + + rad = np.sqrt(dx**2 + dy**2) + angle = np.arctan2(-dy, -dx) / np.pi + + bin_real = (angle + 1) / 2 * (num_bins - 1) + bin_left = np.floor(bin_real).astype(int) + bin_right = (bin_left + 1) % num_bins + w = (bin_real - bin_left.astype(np.float32))[..., None] + flow_img = (1 - + w) * color_wheel[bin_left, :] + w * color_wheel[bin_right, :] + small_ind = rad <= 1 + flow_img[small_ind] = 1 - rad[small_ind, None] * (1 - flow_img[small_ind]) + flow_img[np.logical_not(small_ind)] *= 0.75 + + flow_img[ignore_inds, :] = 0 + + return flow_img + + +def make_color_wheel(bins=None): + """Build a color wheel. + + Args: + bins(list or tuple, optional): Specify the number of bins for each + color range, corresponding to six ranges: red -> yellow, + yellow -> green, green -> cyan, cyan -> blue, blue -> magenta, + magenta -> red. [15, 6, 4, 11, 13, 6] is used for default + (see Middlebury). + + Returns: + ndarray: Color wheel of shape (total_bins, 3). + """ + if bins is None: + bins = [15, 6, 4, 11, 13, 6] + assert len(bins) == 6 + + RY, YG, GC, CB, BM, MR = tuple(bins) + + ry = [1, np.arange(RY) / RY, 0] + yg = [1 - np.arange(YG) / YG, 1, 0] + gc = [0, 1, np.arange(GC) / GC] + cb = [0, 1 - np.arange(CB) / CB, 1] + bm = [np.arange(BM) / BM, 0, 1] + mr = [1, 0, 1 - np.arange(MR) / MR] + + num_bins = RY + YG + GC + CB + BM + MR + + color_wheel = np.zeros((3, num_bins), dtype=np.float32) + + col = 0 + for i, color in enumerate([ry, yg, gc, cb, bm, mr]): + for j in range(3): + color_wheel[j, col:col + bins[i]] = color[j] + col += bins[i] + + return color_wheel.T diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv_custom/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmcv_custom/__init__.py new file mode 100644 index 0000000..4b95873 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv_custom/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from .checkpoint import load_checkpoint + +__all__ = ['load_checkpoint'] \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/uniformer/mmcv_custom/checkpoint.py b/comparison_models/ControlNet/annotator/uniformer/mmcv_custom/checkpoint.py new file mode 100644 index 0000000..19b87fe --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmcv_custom/checkpoint.py @@ -0,0 +1,500 @@ +# Copyright (c) Open-MMLab. All rights reserved. +import io +import os +import os.path as osp +import pkgutil +import time +import warnings +from collections import OrderedDict +from importlib import import_module +from tempfile import TemporaryDirectory + +import torch +import torchvision +from torch.optim import Optimizer +from torch.utils import model_zoo +from torch.nn import functional as F + +import annotator.uniformer.mmcv as mmcv +from annotator.uniformer.mmcv.fileio import FileClient +from annotator.uniformer.mmcv.fileio import load as load_file +from annotator.uniformer.mmcv.parallel import is_module_wrapper +from annotator.uniformer.mmcv.utils import mkdir_or_exist +from annotator.uniformer.mmcv.runner import get_dist_info + +ENV_MMCV_HOME = 'MMCV_HOME' +ENV_XDG_CACHE_HOME = 'XDG_CACHE_HOME' +DEFAULT_CACHE_DIR = '~/.cache' + + +def _get_mmcv_home(): + mmcv_home = os.path.expanduser( + os.getenv( + ENV_MMCV_HOME, + os.path.join( + os.getenv(ENV_XDG_CACHE_HOME, DEFAULT_CACHE_DIR), 'mmcv'))) + + mkdir_or_exist(mmcv_home) + return mmcv_home + + +def load_state_dict(module, state_dict, strict=False, logger=None): + """Load state_dict to a module. + + This method is modified from :meth:`torch.nn.Module.load_state_dict`. + Default value for ``strict`` is set to ``False`` and the message for + param mismatch will be shown even if strict is False. + + Args: + module (Module): Module that receives the state_dict. + state_dict (OrderedDict): Weights. + strict (bool): whether to strictly enforce that the keys + in :attr:`state_dict` match the keys returned by this module's + :meth:`~torch.nn.Module.state_dict` function. Default: ``False``. + logger (:obj:`logging.Logger`, optional): Logger to log the error + message. If not specified, print function will be used. + """ + unexpected_keys = [] + all_missing_keys = [] + err_msg = [] + + metadata = getattr(state_dict, '_metadata', None) + state_dict = state_dict.copy() + if metadata is not None: + state_dict._metadata = metadata + + # use _load_from_state_dict to enable checkpoint version control + def load(module, prefix=''): + # recursively check parallel module in case that the model has a + # complicated structure, e.g., nn.Module(nn.Module(DDP)) + if is_module_wrapper(module): + module = module.module + local_metadata = {} if metadata is None else metadata.get( + prefix[:-1], {}) + module._load_from_state_dict(state_dict, prefix, local_metadata, True, + all_missing_keys, unexpected_keys, + err_msg) + for name, child in module._modules.items(): + if child is not None: + load(child, prefix + name + '.') + + load(module) + load = None # break load->load reference cycle + + # ignore "num_batches_tracked" of BN layers + missing_keys = [ + key for key in all_missing_keys if 'num_batches_tracked' not in key + ] + + if unexpected_keys: + err_msg.append('unexpected key in source ' + f'state_dict: {", ".join(unexpected_keys)}\n') + if missing_keys: + err_msg.append( + f'missing keys in source state_dict: {", ".join(missing_keys)}\n') + + rank, _ = get_dist_info() + if len(err_msg) > 0 and rank == 0: + err_msg.insert( + 0, 'The model and loaded state dict do not match exactly\n') + err_msg = '\n'.join(err_msg) + if strict: + raise RuntimeError(err_msg) + elif logger is not None: + logger.warning(err_msg) + else: + print(err_msg) + + +def load_url_dist(url, model_dir=None): + """In distributed setting, this function only download checkpoint at local + rank 0.""" + rank, world_size = get_dist_info() + rank = int(os.environ.get('LOCAL_RANK', rank)) + if rank == 0: + checkpoint = model_zoo.load_url(url, model_dir=model_dir) + if world_size > 1: + torch.distributed.barrier() + if rank > 0: + checkpoint = model_zoo.load_url(url, model_dir=model_dir) + return checkpoint + + +def load_pavimodel_dist(model_path, map_location=None): + """In distributed setting, this function only download checkpoint at local + rank 0.""" + try: + from pavi import modelcloud + except ImportError: + raise ImportError( + 'Please install pavi to load checkpoint from modelcloud.') + rank, world_size = get_dist_info() + rank = int(os.environ.get('LOCAL_RANK', rank)) + if rank == 0: + model = modelcloud.get(model_path) + with TemporaryDirectory() as tmp_dir: + downloaded_file = osp.join(tmp_dir, model.name) + model.download(downloaded_file) + checkpoint = torch.load(downloaded_file, map_location=map_location) + if world_size > 1: + torch.distributed.barrier() + if rank > 0: + model = modelcloud.get(model_path) + with TemporaryDirectory() as tmp_dir: + downloaded_file = osp.join(tmp_dir, model.name) + model.download(downloaded_file) + checkpoint = torch.load( + downloaded_file, map_location=map_location) + return checkpoint + + +def load_fileclient_dist(filename, backend, map_location): + """In distributed setting, this function only download checkpoint at local + rank 0.""" + rank, world_size = get_dist_info() + rank = int(os.environ.get('LOCAL_RANK', rank)) + allowed_backends = ['ceph'] + if backend not in allowed_backends: + raise ValueError(f'Load from Backend {backend} is not supported.') + if rank == 0: + fileclient = FileClient(backend=backend) + buffer = io.BytesIO(fileclient.get(filename)) + checkpoint = torch.load(buffer, map_location=map_location) + if world_size > 1: + torch.distributed.barrier() + if rank > 0: + fileclient = FileClient(backend=backend) + buffer = io.BytesIO(fileclient.get(filename)) + checkpoint = torch.load(buffer, map_location=map_location) + return checkpoint + + +def get_torchvision_models(): + model_urls = dict() + for _, name, ispkg in pkgutil.walk_packages(torchvision.models.__path__): + if ispkg: + continue + _zoo = import_module(f'torchvision.models.{name}') + if hasattr(_zoo, 'model_urls'): + _urls = getattr(_zoo, 'model_urls') + model_urls.update(_urls) + return model_urls + + +def get_external_models(): + mmcv_home = _get_mmcv_home() + default_json_path = osp.join(mmcv.__path__[0], 'model_zoo/open_mmlab.json') + default_urls = load_file(default_json_path) + assert isinstance(default_urls, dict) + external_json_path = osp.join(mmcv_home, 'open_mmlab.json') + if osp.exists(external_json_path): + external_urls = load_file(external_json_path) + assert isinstance(external_urls, dict) + default_urls.update(external_urls) + + return default_urls + + +def get_mmcls_models(): + mmcls_json_path = osp.join(mmcv.__path__[0], 'model_zoo/mmcls.json') + mmcls_urls = load_file(mmcls_json_path) + + return mmcls_urls + + +def get_deprecated_model_names(): + deprecate_json_path = osp.join(mmcv.__path__[0], + 'model_zoo/deprecated.json') + deprecate_urls = load_file(deprecate_json_path) + assert isinstance(deprecate_urls, dict) + + return deprecate_urls + + +def _process_mmcls_checkpoint(checkpoint): + state_dict = checkpoint['state_dict'] + new_state_dict = OrderedDict() + for k, v in state_dict.items(): + if k.startswith('backbone.'): + new_state_dict[k[9:]] = v + new_checkpoint = dict(state_dict=new_state_dict) + + return new_checkpoint + + +def _load_checkpoint(filename, map_location=None): + """Load checkpoint from somewhere (modelzoo, file, url). + + Args: + filename (str): Accept local filepath, URL, ``torchvision://xxx``, + ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for + details. + map_location (str | None): Same as :func:`torch.load`. Default: None. + + Returns: + dict | OrderedDict: The loaded checkpoint. It can be either an + OrderedDict storing model weights or a dict containing other + information, which depends on the checkpoint. + """ + if filename.startswith('modelzoo://'): + warnings.warn('The URL scheme of "modelzoo://" is deprecated, please ' + 'use "torchvision://" instead') + model_urls = get_torchvision_models() + model_name = filename[11:] + checkpoint = load_url_dist(model_urls[model_name]) + elif filename.startswith('torchvision://'): + model_urls = get_torchvision_models() + model_name = filename[14:] + checkpoint = load_url_dist(model_urls[model_name]) + elif filename.startswith('open-mmlab://'): + model_urls = get_external_models() + model_name = filename[13:] + deprecated_urls = get_deprecated_model_names() + if model_name in deprecated_urls: + warnings.warn(f'open-mmlab://{model_name} is deprecated in favor ' + f'of open-mmlab://{deprecated_urls[model_name]}') + model_name = deprecated_urls[model_name] + model_url = model_urls[model_name] + # check if is url + if model_url.startswith(('http://', 'https://')): + checkpoint = load_url_dist(model_url) + else: + filename = osp.join(_get_mmcv_home(), model_url) + if not osp.isfile(filename): + raise IOError(f'{filename} is not a checkpoint file') + checkpoint = torch.load(filename, map_location=map_location) + elif filename.startswith('mmcls://'): + model_urls = get_mmcls_models() + model_name = filename[8:] + checkpoint = load_url_dist(model_urls[model_name]) + checkpoint = _process_mmcls_checkpoint(checkpoint) + elif filename.startswith(('http://', 'https://')): + checkpoint = load_url_dist(filename) + elif filename.startswith('pavi://'): + model_path = filename[7:] + checkpoint = load_pavimodel_dist(model_path, map_location=map_location) + elif filename.startswith('s3://'): + checkpoint = load_fileclient_dist( + filename, backend='ceph', map_location=map_location) + else: + if not osp.isfile(filename): + raise IOError(f'{filename} is not a checkpoint file') + checkpoint = torch.load(filename, map_location=map_location) + return checkpoint + + +def load_checkpoint(model, + filename, + map_location='cpu', + strict=False, + logger=None): + """Load checkpoint from a file or URI. + + Args: + model (Module): Module to load checkpoint. + filename (str): Accept local filepath, URL, ``torchvision://xxx``, + ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for + details. + map_location (str): Same as :func:`torch.load`. + strict (bool): Whether to allow different params for the model and + checkpoint. + logger (:mod:`logging.Logger` or None): The logger for error message. + + Returns: + dict or OrderedDict: The loaded checkpoint. + """ + checkpoint = _load_checkpoint(filename, map_location) + # OrderedDict is a subclass of dict + if not isinstance(checkpoint, dict): + raise RuntimeError( + f'No state_dict found in checkpoint file {filename}') + # get state_dict from checkpoint + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + # 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()} + + # for MoBY, load model of online branch + if sorted(list(state_dict.keys()))[0].startswith('encoder'): + state_dict = {k.replace('encoder.', ''): v for k, v in state_dict.items() if k.startswith('encoder.')} + + # 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 = model.absolute_pos_embed.size() + if N1 != N2 or C1 != C2 or L != H*W: + logger.warning("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) + + # 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] + table_current = model.state_dict()[table_key] + L1, nH1 = table_pretrained.size() + L2, nH2 = table_current.size() + if nH1 != nH2: + logger.warning(f"Error in loading {table_key}, pass") + else: + if L1 != L2: + S1 = int(L1 ** 0.5) + S2 = int(L2 ** 0.5) + table_pretrained_resized = F.interpolate( + table_pretrained.permute(1, 0).view(1, nH1, S1, S1), + size=(S2, S2), mode='bicubic') + state_dict[table_key] = table_pretrained_resized.view(nH2, L2).permute(1, 0) + + # load state_dict + load_state_dict(model, state_dict, strict, logger) + return checkpoint + + +def weights_to_cpu(state_dict): + """Copy a model state_dict to cpu. + + Args: + state_dict (OrderedDict): Model weights on GPU. + + Returns: + OrderedDict: Model weights on GPU. + """ + state_dict_cpu = OrderedDict() + for key, val in state_dict.items(): + state_dict_cpu[key] = val.cpu() + return state_dict_cpu + + +def _save_to_state_dict(module, destination, prefix, keep_vars): + """Saves module state to `destination` dictionary. + + This method is modified from :meth:`torch.nn.Module._save_to_state_dict`. + + Args: + module (nn.Module): The module to generate state_dict. + destination (dict): A dict where state will be stored. + prefix (str): The prefix for parameters and buffers used in this + module. + """ + for name, param in module._parameters.items(): + if param is not None: + destination[prefix + name] = param if keep_vars else param.detach() + for name, buf in module._buffers.items(): + # remove check of _non_persistent_buffers_set to allow nn.BatchNorm2d + if buf is not None: + destination[prefix + name] = buf if keep_vars else buf.detach() + + +def get_state_dict(module, destination=None, prefix='', keep_vars=False): + """Returns a dictionary containing a whole state of the module. + + Both parameters and persistent buffers (e.g. running averages) are + included. Keys are corresponding parameter and buffer names. + + This method is modified from :meth:`torch.nn.Module.state_dict` to + recursively check parallel module in case that the model has a complicated + structure, e.g., nn.Module(nn.Module(DDP)). + + Args: + module (nn.Module): The module to generate state_dict. + destination (OrderedDict): Returned dict for the state of the + module. + prefix (str): Prefix of the key. + keep_vars (bool): Whether to keep the variable property of the + parameters. Default: False. + + Returns: + dict: A dictionary containing a whole state of the module. + """ + # recursively check parallel module in case that the model has a + # complicated structure, e.g., nn.Module(nn.Module(DDP)) + if is_module_wrapper(module): + module = module.module + + # below is the same as torch.nn.Module.state_dict() + if destination is None: + destination = OrderedDict() + destination._metadata = OrderedDict() + destination._metadata[prefix[:-1]] = local_metadata = dict( + version=module._version) + _save_to_state_dict(module, destination, prefix, keep_vars) + for name, child in module._modules.items(): + if child is not None: + get_state_dict( + child, destination, prefix + name + '.', keep_vars=keep_vars) + for hook in module._state_dict_hooks.values(): + hook_result = hook(module, destination, prefix, local_metadata) + if hook_result is not None: + destination = hook_result + return destination + + +def save_checkpoint(model, filename, optimizer=None, meta=None): + """Save checkpoint to file. + + The checkpoint will have 3 fields: ``meta``, ``state_dict`` and + ``optimizer``. By default ``meta`` will contain version and time info. + + Args: + model (Module): Module whose params are to be saved. + filename (str): Checkpoint filename. + optimizer (:obj:`Optimizer`, optional): Optimizer to be saved. + meta (dict, optional): Metadata to be saved in checkpoint. + """ + if meta is None: + meta = {} + elif not isinstance(meta, dict): + raise TypeError(f'meta must be a dict or None, but got {type(meta)}') + meta.update(mmcv_version=mmcv.__version__, time=time.asctime()) + + if is_module_wrapper(model): + model = model.module + + if hasattr(model, 'CLASSES') and model.CLASSES is not None: + # save class name to the meta + meta.update(CLASSES=model.CLASSES) + + checkpoint = { + 'meta': meta, + 'state_dict': weights_to_cpu(get_state_dict(model)) + } + # save optimizer state dict in the checkpoint + if isinstance(optimizer, Optimizer): + checkpoint['optimizer'] = optimizer.state_dict() + elif isinstance(optimizer, dict): + checkpoint['optimizer'] = {} + for name, optim in optimizer.items(): + checkpoint['optimizer'][name] = optim.state_dict() + + if filename.startswith('pavi://'): + try: + from pavi import modelcloud + from pavi.exception import NodeNotFoundError + except ImportError: + raise ImportError( + 'Please install pavi to load checkpoint from modelcloud.') + model_path = filename[7:] + root = modelcloud.Folder() + model_dir, model_name = osp.split(model_path) + try: + model = modelcloud.get(model_dir) + except NodeNotFoundError: + model = root.create_training_model(model_dir) + with TemporaryDirectory() as tmp_dir: + checkpoint_file = osp.join(tmp_dir, model_name) + with open(checkpoint_file, 'wb') as f: + torch.save(checkpoint, f) + f.flush() + model.create_file(checkpoint_file, name=model_name) + else: + mmcv.mkdir_or_exist(osp.dirname(filename)) + # immediately flush buffer + with open(filename, 'wb') as f: + torch.save(checkpoint, f) + f.flush() \ No newline at end of file diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/__init__.py new file mode 100644 index 0000000..170724b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/__init__.py @@ -0,0 +1,9 @@ +from .inference import inference_segmentor, init_segmentor, show_result_pyplot +from .test import multi_gpu_test, single_gpu_test +from .train import get_root_logger, set_random_seed, train_segmentor + +__all__ = [ + 'get_root_logger', 'set_random_seed', 'train_segmentor', 'init_segmentor', + 'inference_segmentor', 'multi_gpu_test', 'single_gpu_test', + 'show_result_pyplot' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/inference.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/inference.py new file mode 100644 index 0000000..90bc1c0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/inference.py @@ -0,0 +1,136 @@ +import matplotlib.pyplot as plt +import annotator.uniformer.mmcv as mmcv +import torch +from annotator.uniformer.mmcv.parallel import collate, scatter +from annotator.uniformer.mmcv.runner import load_checkpoint + +from annotator.uniformer.mmseg.datasets.pipelines import Compose +from annotator.uniformer.mmseg.models import build_segmentor + + +def init_segmentor(config, checkpoint=None, device='cuda:0'): + """Initialize a segmentor from config file. + + Args: + config (str or :obj:`mmcv.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'. + Use 'cpu' for loading model on CPU. + Returns: + nn.Module: The constructed segmentor. + """ + if isinstance(config, str): + config = mmcv.Config.fromfile(config) + elif not isinstance(config, mmcv.Config): + raise TypeError('config must be a filename or Config object, ' + 'but got {}'.format(type(config))) + config.model.pretrained = None + config.model.train_cfg = None + model = build_segmentor(config.model, test_cfg=config.get('test_cfg')) + if checkpoint is not None: + checkpoint = load_checkpoint(model, checkpoint, map_location='cpu') + model.CLASSES = checkpoint['meta']['CLASSES'] + model.PALETTE = checkpoint['meta']['PALETTE'] + model.cfg = config # save the config in the model for convenience + model.to(device) + model.eval() + return model + + +class LoadImage: + """A simple pipeline to load image.""" + + def __call__(self, results): + """Call function to load images into results. + + Args: + results (dict): A result dict contains the file name + of the image to be read. + + Returns: + dict: ``results`` will be returned containing loaded image. + """ + + if isinstance(results['img'], str): + results['filename'] = results['img'] + results['ori_filename'] = results['img'] + else: + results['filename'] = None + results['ori_filename'] = None + img = mmcv.imread(results['img']) + results['img'] = img + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + return results + + +def inference_segmentor(model, img): + """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: + (list[Tensor]): The segmentation result. + """ + cfg = model.cfg + device = next(model.parameters()).device # model device + # build the data pipeline + test_pipeline = [LoadImage()] + cfg.data.test.pipeline[1:] + test_pipeline = Compose(test_pipeline) + # prepare data + data = dict(img=img) + data = test_pipeline(data) + data = collate([data], samples_per_gpu=1) + if next(model.parameters()).is_cuda: + # scatter to specified GPU + data = scatter(data, [device])[0] + else: + data['img_metas'] = [i.data[0] for i in data['img_metas']] + + # forward the model + with torch.no_grad(): + result = model(return_loss=False, rescale=True, **data) + return result + + +def show_result_pyplot(model, + img, + result, + palette=None, + fig_size=(15, 10), + opacity=0.5, + title='', + block=True): + """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 (list): The segmentation result. + palette (list[list[int]]] | None): The palette of segmentation + map. If None is given, random palette will be generated. + Default: None + fig_size (tuple): Figure size of the pyplot figure. + 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 ''. + block (bool): Whether to block the pyplot figure. + Default is True. + """ + if hasattr(model, 'module'): + model = model.module + img = model.show_result( + img, result, palette=palette, show=False, opacity=opacity) + # plt.figure(figsize=fig_size) + # plt.imshow(mmcv.bgr2rgb(img)) + # plt.title(title) + # plt.tight_layout() + # plt.show(block=block) + return mmcv.bgr2rgb(img) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/test.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/test.py new file mode 100644 index 0000000..e574eb7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/test.py @@ -0,0 +1,238 @@ +import os.path as osp +import pickle +import shutil +import tempfile + +import annotator.uniformer.mmcv as mmcv +import numpy as np +import torch +import torch.distributed as dist +from annotator.uniformer.mmcv.image import tensor2imgs +from annotator.uniformer.mmcv.runner import get_dist_info + + +def np2tmp(array, temp_file_name=None): + """Save ndarray to local numpy file. + + Args: + array (ndarray): Ndarray to save. + temp_file_name (str): Numpy file name. If 'temp_file_name=None', this + function will generate a file name with tempfile.NamedTemporaryFile + to save ndarray. Default: None. + + Returns: + str: The numpy file name. + """ + + if temp_file_name is None: + temp_file_name = tempfile.NamedTemporaryFile( + suffix='.npy', delete=False).name + np.save(temp_file_name, array) + return temp_file_name + + +def single_gpu_test(model, + data_loader, + show=False, + out_dir=None, + efficient_test=False, + opacity=0.5): + """Test with single GPU. + + Args: + model (nn.Module): Model to be tested. + data_loader (utils.data.Dataloader): Pytorch data loader. + show (bool): Whether show results during inference. Default: False. + out_dir (str, optional): If specified, the results will be dumped into + the directory to save output results. + efficient_test (bool): Whether save the results as local numpy files to + save CPU memory during evaluation. Default: False. + opacity(float): Opacity of painted segmentation map. + Default 0.5. + Must be in (0, 1] range. + Returns: + list: The prediction results. + """ + + model.eval() + results = [] + dataset = data_loader.dataset + prog_bar = mmcv.ProgressBar(len(dataset)) + for i, data in enumerate(data_loader): + with torch.no_grad(): + result = model(return_loss=False, **data) + + if show or out_dir: + img_tensor = data['img'][0] + img_metas = data['img_metas'][0].data[0] + imgs = tensor2imgs(img_tensor, **img_metas[0]['img_norm_cfg']) + assert len(imgs) == len(img_metas) + + for img, img_meta in zip(imgs, img_metas): + h, w, _ = img_meta['img_shape'] + img_show = img[:h, :w, :] + + ori_h, ori_w = img_meta['ori_shape'][:-1] + img_show = mmcv.imresize(img_show, (ori_w, ori_h)) + + if out_dir: + out_file = osp.join(out_dir, img_meta['ori_filename']) + else: + out_file = None + + model.module.show_result( + img_show, + result, + palette=dataset.PALETTE, + show=show, + out_file=out_file, + opacity=opacity) + + if isinstance(result, list): + if efficient_test: + result = [np2tmp(_) for _ in result] + results.extend(result) + else: + if efficient_test: + result = np2tmp(result) + results.append(result) + + batch_size = len(result) + for _ in range(batch_size): + prog_bar.update() + return results + + +def multi_gpu_test(model, + data_loader, + tmpdir=None, + gpu_collect=False, + efficient_test=False): + """Test model with multiple gpus. + + This method tests model with multiple gpus and collects the results + under two different modes: gpu and cpu modes. By setting 'gpu_collect=True' + it encodes results to gpu tensors and use gpu communication for results + collection. On cpu mode it saves the results on different gpus to 'tmpdir' + and collects them by the rank 0 worker. + + Args: + model (nn.Module): Model to be tested. + data_loader (utils.data.Dataloader): Pytorch data loader. + tmpdir (str): Path of directory to save the temporary results from + different gpus under cpu mode. + gpu_collect (bool): Option to use either gpu or cpu to collect results. + efficient_test (bool): Whether save the results as local numpy files to + save CPU memory during evaluation. Default: False. + + Returns: + list: The prediction results. + """ + + model.eval() + results = [] + dataset = data_loader.dataset + rank, world_size = get_dist_info() + if rank == 0: + prog_bar = mmcv.ProgressBar(len(dataset)) + for i, data in enumerate(data_loader): + with torch.no_grad(): + result = model(return_loss=False, rescale=True, **data) + + if isinstance(result, list): + if efficient_test: + result = [np2tmp(_) for _ in result] + results.extend(result) + else: + if efficient_test: + result = np2tmp(result) + results.append(result) + + if rank == 0: + batch_size = data['img'][0].size(0) + for _ in range(batch_size * world_size): + prog_bar.update() + + # collect results from all ranks + if gpu_collect: + results = collect_results_gpu(results, len(dataset)) + else: + results = collect_results_cpu(results, len(dataset), tmpdir) + return results + + +def collect_results_cpu(result_part, size, tmpdir=None): + """Collect results with CPU.""" + rank, world_size = get_dist_info() + # create a tmp dir if it is not specified + if tmpdir is None: + MAX_LEN = 512 + # 32 is whitespace + dir_tensor = torch.full((MAX_LEN, ), + 32, + dtype=torch.uint8, + device='cuda') + if rank == 0: + tmpdir = tempfile.mkdtemp() + tmpdir = torch.tensor( + bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda') + dir_tensor[:len(tmpdir)] = tmpdir + dist.broadcast(dir_tensor, 0) + tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip() + else: + mmcv.mkdir_or_exist(tmpdir) + # dump the part result to the dir + mmcv.dump(result_part, osp.join(tmpdir, 'part_{}.pkl'.format(rank))) + dist.barrier() + # collect all parts + if rank != 0: + return None + else: + # load results of all parts from tmp dir + part_list = [] + for i in range(world_size): + part_file = osp.join(tmpdir, 'part_{}.pkl'.format(i)) + part_list.append(mmcv.load(part_file)) + # sort the results + ordered_results = [] + for res in zip(*part_list): + ordered_results.extend(list(res)) + # the dataloader may pad some samples + ordered_results = ordered_results[:size] + # remove tmp dir + shutil.rmtree(tmpdir) + return ordered_results + + +def collect_results_gpu(result_part, size): + """Collect results with GPU.""" + rank, world_size = get_dist_info() + # dump result part to tensor with pickle + part_tensor = torch.tensor( + bytearray(pickle.dumps(result_part)), dtype=torch.uint8, device='cuda') + # gather all result part tensor shape + shape_tensor = torch.tensor(part_tensor.shape, device='cuda') + shape_list = [shape_tensor.clone() for _ in range(world_size)] + dist.all_gather(shape_list, shape_tensor) + # padding result part tensor to max length + shape_max = torch.tensor(shape_list).max() + part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda') + part_send[:shape_tensor[0]] = part_tensor + part_recv_list = [ + part_tensor.new_zeros(shape_max) for _ in range(world_size) + ] + # gather all result part + dist.all_gather(part_recv_list, part_send) + + if rank == 0: + part_list = [] + for recv, shape in zip(part_recv_list, shape_list): + part_list.append( + pickle.loads(recv[:shape[0]].cpu().numpy().tobytes())) + # sort the results + ordered_results = [] + for res in zip(*part_list): + ordered_results.extend(list(res)) + # the dataloader may pad some samples + ordered_results = ordered_results[:size] + return ordered_results diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/train.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/train.py new file mode 100644 index 0000000..63f319a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/apis/train.py @@ -0,0 +1,116 @@ +import random +import warnings + +import numpy as np +import torch +from annotator.uniformer.mmcv.parallel import MMDataParallel, MMDistributedDataParallel +from annotator.uniformer.mmcv.runner import build_optimizer, build_runner + +from annotator.uniformer.mmseg.core import DistEvalHook, EvalHook +from annotator.uniformer.mmseg.datasets import build_dataloader, build_dataset +from annotator.uniformer.mmseg.utils import get_root_logger + + +def set_random_seed(seed, deterministic=False): + """Set random seed. + + Args: + seed (int): Seed to be used. + deterministic (bool): Whether to set the deterministic option for + CUDNN backend, i.e., set `torch.backends.cudnn.deterministic` + to True and `torch.backends.cudnn.benchmark` to False. + Default: False. + """ + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + if deterministic: + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + + +def train_segmentor(model, + dataset, + cfg, + distributed=False, + validate=False, + timestamp=None, + meta=None): + """Launch segmentor training.""" + logger = get_root_logger(cfg.log_level) + + # prepare data loaders + dataset = dataset if isinstance(dataset, (list, tuple)) else [dataset] + data_loaders = [ + build_dataloader( + ds, + cfg.data.samples_per_gpu, + cfg.data.workers_per_gpu, + # cfg.gpus will be ignored if distributed + len(cfg.gpu_ids), + dist=distributed, + seed=cfg.seed, + drop_last=True) for ds in dataset + ] + + # put model on gpus + if distributed: + find_unused_parameters = cfg.get('find_unused_parameters', False) + # Sets the `find_unused_parameters` parameter in + # torch.nn.parallel.DistributedDataParallel + model = MMDistributedDataParallel( + model.cuda(), + device_ids=[torch.cuda.current_device()], + broadcast_buffers=False, + find_unused_parameters=find_unused_parameters) + else: + model = MMDataParallel( + model.cuda(cfg.gpu_ids[0]), device_ids=cfg.gpu_ids) + + # build runner + optimizer = build_optimizer(model, cfg.optimizer) + + if cfg.get('runner') is None: + cfg.runner = {'type': 'IterBasedRunner', 'max_iters': cfg.total_iters} + warnings.warn( + 'config is now expected to have a `runner` section, ' + 'please set `runner` in your config.', UserWarning) + + runner = build_runner( + cfg.runner, + default_args=dict( + model=model, + batch_processor=None, + optimizer=optimizer, + work_dir=cfg.work_dir, + logger=logger, + meta=meta)) + + # register hooks + runner.register_training_hooks(cfg.lr_config, cfg.optimizer_config, + cfg.checkpoint_config, cfg.log_config, + cfg.get('momentum_config', None)) + + # an ugly walkaround to make the .log and .log.json filenames the same + runner.timestamp = timestamp + + # register eval hooks + if validate: + val_dataset = build_dataset(cfg.data.val, dict(test_mode=True)) + val_dataloader = build_dataloader( + val_dataset, + samples_per_gpu=1, + workers_per_gpu=cfg.data.workers_per_gpu, + dist=distributed, + shuffle=False) + eval_cfg = cfg.get('evaluation', {}) + eval_cfg['by_epoch'] = cfg.runner['type'] != 'IterBasedRunner' + eval_hook = DistEvalHook if distributed else EvalHook + runner.register_hook(eval_hook(val_dataloader, **eval_cfg), priority='LOW') + + if cfg.resume_from: + runner.resume(cfg.resume_from) + elif cfg.load_from: + runner.load_checkpoint(cfg.load_from) + runner.run(data_loaders, cfg.workflow) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/core/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/__init__.py new file mode 100644 index 0000000..9656055 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/__init__.py @@ -0,0 +1,3 @@ +from .evaluation import * # noqa: F401, F403 +from .seg import * # noqa: F401, F403 +from .utils import * # noqa: F401, F403 diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/__init__.py new file mode 100644 index 0000000..f7cc4b2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/__init__.py @@ -0,0 +1,8 @@ +from .class_names import get_classes, get_palette +from .eval_hooks import DistEvalHook, EvalHook +from .metrics import eval_metrics, mean_dice, mean_fscore, mean_iou + +__all__ = [ + 'EvalHook', 'DistEvalHook', 'mean_dice', 'mean_iou', 'mean_fscore', + 'eval_metrics', 'get_classes', 'get_palette' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/class_names.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/class_names.py new file mode 100644 index 0000000..ffae816 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/class_names.py @@ -0,0 +1,152 @@ +import annotator.uniformer.mmcv as mmcv + + +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 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]] + + +dataset_aliases = { + 'cityscapes': ['cityscapes'], + 'ade': ['ade', 'ade20k'], + 'voc': ['voc', 'pascal_voc', 'voc12', 'voc12aug'] +} + + +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 mmcv.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 mmcv.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/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/eval_hooks.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/eval_hooks.py new file mode 100644 index 0000000..6fc100c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/eval_hooks.py @@ -0,0 +1,109 @@ +import os.path as osp + +from annotator.uniformer.mmcv.runner import DistEvalHook as _DistEvalHook +from annotator.uniformer.mmcv.runner import EvalHook as _EvalHook + + +class EvalHook(_EvalHook): + """Single GPU EvalHook, with efficient test support. + + Args: + by_epoch (bool): Determine perform evaluation by epoch or by iteration. + If set to True, it will perform by epoch. Otherwise, by iteration. + Default: False. + efficient_test (bool): Whether save the results as local numpy files to + save CPU memory during evaluation. Default: False. + Returns: + list: The prediction results. + """ + + greater_keys = ['mIoU', 'mAcc', 'aAcc'] + + def __init__(self, *args, by_epoch=False, efficient_test=False, **kwargs): + super().__init__(*args, by_epoch=by_epoch, **kwargs) + self.efficient_test = efficient_test + + def after_train_iter(self, runner): + """After train epoch hook. + + Override default ``single_gpu_test``. + """ + if self.by_epoch or not self.every_n_iters(runner, self.interval): + return + from annotator.uniformer.mmseg.apis import single_gpu_test + runner.log_buffer.clear() + results = single_gpu_test( + runner.model, + self.dataloader, + show=False, + efficient_test=self.efficient_test) + self.evaluate(runner, results) + + def after_train_epoch(self, runner): + """After train epoch hook. + + Override default ``single_gpu_test``. + """ + if not self.by_epoch or not self.every_n_epochs(runner, self.interval): + return + from annotator.uniformer.mmseg.apis import single_gpu_test + runner.log_buffer.clear() + results = single_gpu_test(runner.model, self.dataloader, show=False) + self.evaluate(runner, results) + + +class DistEvalHook(_DistEvalHook): + """Distributed EvalHook, with efficient test support. + + Args: + by_epoch (bool): Determine perform evaluation by epoch or by iteration. + If set to True, it will perform by epoch. Otherwise, by iteration. + Default: False. + efficient_test (bool): Whether save the results as local numpy files to + save CPU memory during evaluation. Default: False. + Returns: + list: The prediction results. + """ + + greater_keys = ['mIoU', 'mAcc', 'aAcc'] + + def __init__(self, *args, by_epoch=False, efficient_test=False, **kwargs): + super().__init__(*args, by_epoch=by_epoch, **kwargs) + self.efficient_test = efficient_test + + def after_train_iter(self, runner): + """After train epoch hook. + + Override default ``multi_gpu_test``. + """ + if self.by_epoch or not self.every_n_iters(runner, self.interval): + return + from annotator.uniformer.mmseg.apis import multi_gpu_test + runner.log_buffer.clear() + results = multi_gpu_test( + runner.model, + self.dataloader, + tmpdir=osp.join(runner.work_dir, '.eval_hook'), + gpu_collect=self.gpu_collect, + efficient_test=self.efficient_test) + if runner.rank == 0: + print('\n') + self.evaluate(runner, results) + + def after_train_epoch(self, runner): + """After train epoch hook. + + Override default ``multi_gpu_test``. + """ + if not self.by_epoch or not self.every_n_epochs(runner, self.interval): + return + from annotator.uniformer.mmseg.apis import multi_gpu_test + runner.log_buffer.clear() + results = multi_gpu_test( + runner.model, + self.dataloader, + tmpdir=osp.join(runner.work_dir, '.eval_hook'), + gpu_collect=self.gpu_collect) + if runner.rank == 0: + print('\n') + self.evaluate(runner, results) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/metrics.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/metrics.py new file mode 100644 index 0000000..16c7dd4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/evaluation/metrics.py @@ -0,0 +1,326 @@ +from collections import OrderedDict + +import annotator.uniformer.mmcv as mmcv +import numpy as np +import torch + + +def f_score(precision, recall, beta=1): + """calcuate 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: False. + + Returns: + [torch.tensor]: The f-score value. + """ + score = (1 + beta**2) * (precision * recall) / ( + (beta**2 * precision) + recall) + return score + + +def intersect_and_union(pred_label, + label, + num_classes, + ignore_index, + label_map=dict(), + reduce_zero_label=False): + """Calculate intersection and Union. + + Args: + pred_label (ndarray | str): Prediction segmentation map + or predict result filename. + label (ndarray | str): Ground truth segmentation map + or label filename. + num_classes (int): Number of categories. + ignore_index (int): Index that will be ignored in evaluation. + label_map (dict): Mapping old labels to new labels. The parameter will + work only when label is str. Default: dict(). + reduce_zero_label (bool): Wether ignore zero label. The parameter will + work only when label is str. Default: False. + + 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. + """ + + if isinstance(pred_label, str): + pred_label = torch.from_numpy(np.load(pred_label)) + else: + pred_label = torch.from_numpy((pred_label)) + + if isinstance(label, str): + label = torch.from_numpy( + mmcv.imread(label, flag='unchanged', backend='pillow')) + else: + label = torch.from_numpy(label) + + if label_map is not None: + for old_id, new_id in label_map.items(): + label[label == old_id] = new_id + if reduce_zero_label: + label[label == 0] = 255 + label = label - 1 + label[label == 254] = 255 + + 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) + area_pred_label = torch.histc( + pred_label.float(), bins=(num_classes), min=0, max=num_classes - 1) + area_label = torch.histc( + label.float(), bins=(num_classes), min=0, max=num_classes - 1) + area_union = area_pred_label + area_label - area_intersect + return area_intersect, area_union, area_pred_label, area_label + + +def total_intersect_and_union(results, + gt_seg_maps, + num_classes, + ignore_index, + label_map=dict(), + reduce_zero_label=False): + """Calculate Total Intersection and Union. + + Args: + results (list[ndarray] | list[str]): List of prediction segmentation + maps or list of prediction result filenames. + gt_seg_maps (list[ndarray] | list[str]): list of ground truth + segmentation maps or list of label filenames. + num_classes (int): Number of categories. + ignore_index (int): Index that will be ignored in evaluation. + label_map (dict): Mapping old labels to new labels. Default: dict(). + reduce_zero_label (bool): Wether ignore zero label. Default: False. + + Returns: + ndarray: The intersection of prediction and ground truth histogram + on all classes. + ndarray: The union of prediction and ground truth histogram on all + classes. + ndarray: The prediction histogram on all classes. + ndarray: The ground truth histogram on all classes. + """ + num_imgs = len(results) + assert len(gt_seg_maps) == num_imgs + total_area_intersect = torch.zeros((num_classes, ), dtype=torch.float64) + total_area_union = torch.zeros((num_classes, ), dtype=torch.float64) + total_area_pred_label = torch.zeros((num_classes, ), dtype=torch.float64) + total_area_label = torch.zeros((num_classes, ), dtype=torch.float64) + for i in range(num_imgs): + area_intersect, area_union, area_pred_label, area_label = \ + intersect_and_union( + results[i], gt_seg_maps[i], num_classes, ignore_index, + label_map, reduce_zero_label) + total_area_intersect += area_intersect + total_area_union += area_union + total_area_pred_label += area_pred_label + total_area_label += area_label + return total_area_intersect, total_area_union, total_area_pred_label, \ + total_area_label + + +def mean_iou(results, + gt_seg_maps, + num_classes, + ignore_index, + nan_to_num=None, + label_map=dict(), + reduce_zero_label=False): + """Calculate Mean Intersection and Union (mIoU) + + Args: + results (list[ndarray] | list[str]): List of prediction segmentation + maps or list of prediction result filenames. + gt_seg_maps (list[ndarray] | list[str]): list of ground truth + segmentation maps or list of label filenames. + num_classes (int): Number of categories. + ignore_index (int): Index that will be ignored in evaluation. + nan_to_num (int, optional): If specified, NaN values will be replaced + by the numbers defined by the user. Default: None. + label_map (dict): Mapping old labels to new labels. Default: dict(). + reduce_zero_label (bool): Wether ignore zero label. Default: False. + + Returns: + dict[str, float | ndarray]: + float: Overall accuracy on all images. + ndarray: Per category accuracy, shape (num_classes, ). + ndarray: Per category IoU, shape (num_classes, ). + """ + iou_result = eval_metrics( + results=results, + gt_seg_maps=gt_seg_maps, + num_classes=num_classes, + ignore_index=ignore_index, + metrics=['mIoU'], + nan_to_num=nan_to_num, + label_map=label_map, + reduce_zero_label=reduce_zero_label) + return iou_result + + +def mean_dice(results, + gt_seg_maps, + num_classes, + ignore_index, + nan_to_num=None, + label_map=dict(), + reduce_zero_label=False): + """Calculate Mean Dice (mDice) + + Args: + results (list[ndarray] | list[str]): List of prediction segmentation + maps or list of prediction result filenames. + gt_seg_maps (list[ndarray] | list[str]): list of ground truth + segmentation maps or list of label filenames. + num_classes (int): Number of categories. + ignore_index (int): Index that will be ignored in evaluation. + nan_to_num (int, optional): If specified, NaN values will be replaced + by the numbers defined by the user. Default: None. + label_map (dict): Mapping old labels to new labels. Default: dict(). + reduce_zero_label (bool): Wether ignore zero label. Default: False. + + Returns: + dict[str, float | ndarray]: Default metrics. + float: Overall accuracy on all images. + ndarray: Per category accuracy, shape (num_classes, ). + ndarray: Per category dice, shape (num_classes, ). + """ + + dice_result = eval_metrics( + results=results, + gt_seg_maps=gt_seg_maps, + num_classes=num_classes, + ignore_index=ignore_index, + metrics=['mDice'], + nan_to_num=nan_to_num, + label_map=label_map, + reduce_zero_label=reduce_zero_label) + return dice_result + + +def mean_fscore(results, + gt_seg_maps, + num_classes, + ignore_index, + nan_to_num=None, + label_map=dict(), + reduce_zero_label=False, + beta=1): + """Calculate Mean Intersection and Union (mIoU) + + Args: + results (list[ndarray] | list[str]): List of prediction segmentation + maps or list of prediction result filenames. + gt_seg_maps (list[ndarray] | list[str]): list of ground truth + segmentation maps or list of label filenames. + num_classes (int): Number of categories. + ignore_index (int): Index that will be ignored in evaluation. + nan_to_num (int, optional): If specified, NaN values will be replaced + by the numbers defined by the user. Default: None. + label_map (dict): Mapping old labels to new labels. Default: dict(). + reduce_zero_label (bool): Wether ignore zero label. Default: False. + beta (int): Determines the weight of recall in the combined score. + Default: False. + + + Returns: + dict[str, float | ndarray]: Default metrics. + float: Overall accuracy on all images. + ndarray: Per category recall, shape (num_classes, ). + ndarray: Per category precision, shape (num_classes, ). + ndarray: Per category f-score, shape (num_classes, ). + """ + fscore_result = eval_metrics( + results=results, + gt_seg_maps=gt_seg_maps, + num_classes=num_classes, + ignore_index=ignore_index, + metrics=['mFscore'], + nan_to_num=nan_to_num, + label_map=label_map, + reduce_zero_label=reduce_zero_label, + beta=beta) + return fscore_result + + +def eval_metrics(results, + gt_seg_maps, + num_classes, + ignore_index, + metrics=['mIoU'], + nan_to_num=None, + label_map=dict(), + reduce_zero_label=False, + beta=1): + """Calculate evaluation metrics + Args: + results (list[ndarray] | list[str]): List of prediction segmentation + maps or list of prediction result filenames. + gt_seg_maps (list[ndarray] | list[str]): list of ground truth + segmentation maps or list of label filenames. + num_classes (int): Number of categories. + ignore_index (int): Index that will be ignored in evaluation. + 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. + label_map (dict): Mapping old labels to new labels. Default: dict(). + reduce_zero_label (bool): Wether ignore zero label. Default: False. + Returns: + float: Overall accuracy on all images. + ndarray: Per category accuracy, shape (num_classes, ). + ndarray: Per category evaluation metrics, shape (num_classes, ). + """ + if isinstance(metrics, str): + metrics = [metrics] + allowed_metrics = ['mIoU', 'mDice', 'mFscore'] + if not set(metrics).issubset(set(allowed_metrics)): + raise KeyError('metrics {} is not supported'.format(metrics)) + + total_area_intersect, total_area_union, total_area_pred_label, \ + total_area_label = total_intersect_and_union( + results, gt_seg_maps, num_classes, ignore_index, label_map, + reduce_zero_label) + 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/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/__init__.py new file mode 100644 index 0000000..93bc129 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/__init__.py @@ -0,0 +1,4 @@ +from .builder import build_pixel_sampler +from .sampler import BasePixelSampler, OHEMPixelSampler + +__all__ = ['build_pixel_sampler', 'BasePixelSampler', 'OHEMPixelSampler'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/builder.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/builder.py new file mode 100644 index 0000000..db61f03 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/builder.py @@ -0,0 +1,8 @@ +from annotator.uniformer.mmcv.utils import Registry, build_from_cfg + +PIXEL_SAMPLERS = Registry('pixel sampler') + + +def build_pixel_sampler(cfg, **default_args): + """Build pixel sampler for segmentation map.""" + return build_from_cfg(cfg, PIXEL_SAMPLERS, default_args) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/__init__.py new file mode 100644 index 0000000..332b242 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/__init__.py @@ -0,0 +1,4 @@ +from .base_pixel_sampler import BasePixelSampler +from .ohem_pixel_sampler import OHEMPixelSampler + +__all__ = ['BasePixelSampler', 'OHEMPixelSampler'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/base_pixel_sampler.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/base_pixel_sampler.py new file mode 100644 index 0000000..b75b156 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/base_pixel_sampler.py @@ -0,0 +1,12 @@ +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/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/ohem_pixel_sampler.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/ohem_pixel_sampler.py new file mode 100644 index 0000000..88bb10d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/seg/sampler/ohem_pixel_sampler.py @@ -0,0 +1,76 @@ +import torch +import torch.nn.functional as F + +from ..builder import PIXEL_SAMPLERS +from .base_pixel_sampler import BasePixelSampler + + +@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(OHEMPixelSampler, self).__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: + losses = self.context.loss_decode( + 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/comparison_models/ControlNet/annotator/uniformer/mmseg/core/utils/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/utils/__init__.py new file mode 100644 index 0000000..f2678b3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/utils/__init__.py @@ -0,0 +1,3 @@ +from .misc import add_prefix + +__all__ = ['add_prefix'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/core/utils/misc.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/utils/misc.py new file mode 100644 index 0000000..eb862a8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/core/utils/misc.py @@ -0,0 +1,17 @@ +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 diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/__init__.py new file mode 100644 index 0000000..ebeaef4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/__init__.py @@ -0,0 +1,19 @@ +from .ade import ADE20KDataset +from .builder import DATASETS, PIPELINES, build_dataloader, build_dataset +from .chase_db1 import ChaseDB1Dataset +from .cityscapes import CityscapesDataset +from .custom import CustomDataset +from .dataset_wrappers import ConcatDataset, RepeatDataset +from .drive import DRIVEDataset +from .hrf import HRFDataset +from .pascal_context import PascalContextDataset, PascalContextDataset59 +from .stare import STAREDataset +from .voc import PascalVOCDataset + +__all__ = [ + 'CustomDataset', 'build_dataloader', 'ConcatDataset', 'RepeatDataset', + 'DATASETS', 'build_dataset', 'PIPELINES', 'CityscapesDataset', + 'PascalVOCDataset', 'ADE20KDataset', 'PascalContextDataset', + 'PascalContextDataset59', 'ChaseDB1Dataset', 'DRIVEDataset', 'HRFDataset', + 'STAREDataset' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/ade.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/ade.py new file mode 100644 index 0000000..5913e43 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/ade.py @@ -0,0 +1,84 @@ +from .builder import DATASETS +from .custom import CustomDataset + + +@DATASETS.register_module() +class ADE20KDataset(CustomDataset): + """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'. + """ + 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, **kwargs): + super(ADE20KDataset, self).__init__( + img_suffix='.jpg', + seg_map_suffix='.png', + reduce_zero_label=True, + **kwargs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/builder.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/builder.py new file mode 100644 index 0000000..0798b14 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/builder.py @@ -0,0 +1,169 @@ +import copy +import platform +import random +from functools import partial + +import numpy as np +from annotator.uniformer.mmcv.parallel import collate +from annotator.uniformer.mmcv.runner import get_dist_info +from annotator.uniformer.mmcv.utils import Registry, build_from_cfg +from annotator.uniformer.mmcv.utils.parrots_wrapper import DataLoader, PoolDataLoader +from torch.utils.data import DistributedSampler + +if platform.system() != 'Windows': + # https://github.com/pytorch/pytorch/issues/973 + import resource + rlimit = resource.getrlimit(resource.RLIMIT_NOFILE) + hard_limit = rlimit[1] + soft_limit = min(4096, hard_limit) + resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit)) + +DATASETS = Registry('dataset') +PIPELINES = Registry('pipeline') + + +def _concat_dataset(cfg, default_args=None): + """Build :obj:`ConcatDataset by.""" + from .dataset_wrappers import ConcatDataset + img_dir = cfg['img_dir'] + ann_dir = cfg.get('ann_dir', None) + split = cfg.get('split', None) + num_img_dir = len(img_dir) if isinstance(img_dir, (list, tuple)) else 1 + if ann_dir is not None: + num_ann_dir = len(ann_dir) if isinstance(ann_dir, (list, tuple)) else 1 + else: + num_ann_dir = 0 + if split is not None: + num_split = len(split) if isinstance(split, (list, tuple)) else 1 + else: + num_split = 0 + if num_img_dir > 1: + assert num_img_dir == num_ann_dir or num_ann_dir == 0 + assert num_img_dir == num_split or num_split == 0 + else: + assert num_split == num_ann_dir or num_ann_dir <= 1 + num_dset = max(num_split, num_img_dir) + + datasets = [] + for i in range(num_dset): + data_cfg = copy.deepcopy(cfg) + if isinstance(img_dir, (list, tuple)): + data_cfg['img_dir'] = img_dir[i] + if isinstance(ann_dir, (list, tuple)): + data_cfg['ann_dir'] = ann_dir[i] + if isinstance(split, (list, tuple)): + data_cfg['split'] = split[i] + datasets.append(build_dataset(data_cfg, default_args)) + + return ConcatDataset(datasets) + + +def build_dataset(cfg, default_args=None): + """Build datasets.""" + from .dataset_wrappers import ConcatDataset, RepeatDataset + if isinstance(cfg, (list, tuple)): + dataset = ConcatDataset([build_dataset(c, default_args) for c in cfg]) + elif cfg['type'] == 'RepeatDataset': + dataset = RepeatDataset( + build_dataset(cfg['dataset'], default_args), cfg['times']) + elif isinstance(cfg.get('img_dir'), (list, tuple)) or isinstance( + cfg.get('split', None), (list, tuple)): + dataset = _concat_dataset(cfg, default_args) + else: + dataset = build_from_cfg(cfg, DATASETS, default_args) + + return dataset + + +def build_dataloader(dataset, + samples_per_gpu, + workers_per_gpu, + num_gpus=1, + dist=True, + shuffle=True, + seed=None, + drop_last=False, + pin_memory=True, + dataloader_type='PoolDataLoader', + **kwargs): + """Build PyTorch DataLoader. + + In distributed training, each GPU/process has a dataloader. + In non-distributed training, there is only one dataloader for all GPUs. + + Args: + dataset (Dataset): A PyTorch dataset. + samples_per_gpu (int): Number of training samples on each GPU, i.e., + batch size of each GPU. + workers_per_gpu (int): How many subprocesses to use for data loading + for each GPU. + num_gpus (int): Number of GPUs. Only used in non-distributed training. + dist (bool): Distributed training/test or not. Default: True. + shuffle (bool): Whether to shuffle the data at every epoch. + Default: True. + seed (int | None): Seed to be used. Default: None. + drop_last (bool): Whether to drop the last incomplete batch in epoch. + Default: False + pin_memory (bool): Whether to use pin_memory in DataLoader. + Default: True + dataloader_type (str): Type of dataloader. Default: 'PoolDataLoader' + kwargs: any keyword argument to be used to initialize DataLoader + + Returns: + DataLoader: A PyTorch dataloader. + """ + rank, world_size = get_dist_info() + if dist: + sampler = DistributedSampler( + dataset, world_size, rank, shuffle=shuffle) + shuffle = False + batch_size = samples_per_gpu + num_workers = workers_per_gpu + else: + sampler = None + batch_size = num_gpus * samples_per_gpu + num_workers = num_gpus * workers_per_gpu + + init_fn = partial( + worker_init_fn, num_workers=num_workers, rank=rank, + seed=seed) if seed is not None else None + + assert dataloader_type in ( + 'DataLoader', + 'PoolDataLoader'), f'unsupported dataloader {dataloader_type}' + + if dataloader_type == 'PoolDataLoader': + dataloader = PoolDataLoader + elif dataloader_type == 'DataLoader': + dataloader = DataLoader + + data_loader = dataloader( + dataset, + batch_size=batch_size, + sampler=sampler, + num_workers=num_workers, + collate_fn=partial(collate, samples_per_gpu=samples_per_gpu), + pin_memory=pin_memory, + shuffle=shuffle, + worker_init_fn=init_fn, + drop_last=drop_last, + **kwargs) + + return data_loader + + +def worker_init_fn(worker_id, num_workers, rank, seed): + """Worker init func for dataloader. + + The seed of each worker equals to num_worker * rank + worker_id + user_seed + + Args: + worker_id (int): Worker id. + num_workers (int): Number of workers. + rank (int): The rank of current process. + seed (int): The random seed to use. + """ + + worker_seed = num_workers * rank + worker_id + seed + np.random.seed(worker_seed) + random.seed(worker_seed) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/chase_db1.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/chase_db1.py new file mode 100644 index 0000000..8bc29be --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/chase_db1.py @@ -0,0 +1,27 @@ +import os.path as osp + +from .builder import DATASETS +from .custom import CustomDataset + + +@DATASETS.register_module() +class ChaseDB1Dataset(CustomDataset): + """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'. + """ + + CLASSES = ('background', 'vessel') + + PALETTE = [[120, 120, 120], [6, 230, 230]] + + def __init__(self, **kwargs): + super(ChaseDB1Dataset, self).__init__( + img_suffix='.png', + seg_map_suffix='_1stHO.png', + reduce_zero_label=False, + **kwargs) + assert osp.exists(self.img_dir) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/cityscapes.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/cityscapes.py new file mode 100644 index 0000000..81e47a9 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/cityscapes.py @@ -0,0 +1,217 @@ +import os.path as osp +import tempfile + +import annotator.uniformer.mmcv as mmcv +import numpy as np +from annotator.uniformer.mmcv.utils import print_log +from PIL import Image + +from .builder import DATASETS +from .custom import CustomDataset + + +@DATASETS.register_module() +class CityscapesDataset(CustomDataset): + """Cityscapes dataset. + + The ``img_suffix`` is fixed to '_leftImg8bit.png' and ``seg_map_suffix`` is + fixed to '_gtFine_labelTrainIds.png' for Cityscapes dataset. + """ + + 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, **kwargs): + super(CityscapesDataset, self).__init__( + img_suffix='_leftImg8bit.png', + seg_map_suffix='_gtFine_labelTrainIds.png', + **kwargs) + + @staticmethod + def _convert_to_label_id(result): + """Convert trainId to id for cityscapes.""" + if isinstance(result, str): + result = np.load(result) + import cityscapesscripts.helpers.labels as CSLabels + result_copy = result.copy() + for trainId, label in CSLabels.trainId2label.items(): + result_copy[result == trainId] = label.id + + return result_copy + + def results2img(self, results, imgfile_prefix, to_label_id): + """Write the segmentation results to images. + + Args: + results (list[list | tuple | ndarray]): Testing results of the + dataset. + imgfile_prefix (str): The filename prefix of the png files. + If the prefix is "somepath/xxx", + the png files will be named "somepath/xxx.png". + to_label_id (bool): whether convert output to label_id for + submission + + Returns: + list[str: str]: result txt files which contains corresponding + semantic segmentation images. + """ + mmcv.mkdir_or_exist(imgfile_prefix) + result_files = [] + prog_bar = mmcv.ProgressBar(len(self)) + for idx in range(len(self)): + result = results[idx] + if to_label_id: + result = self._convert_to_label_id(result) + filename = self.img_infos[idx]['filename'] + basename = osp.splitext(osp.basename(filename))[0] + + png_filename = osp.join(imgfile_prefix, f'{basename}.png') + + output = Image.fromarray(result.astype(np.uint8)).convert('P') + import cityscapesscripts.helpers.labels as CSLabels + palette = np.zeros((len(CSLabels.id2label), 3), dtype=np.uint8) + for label_id, label in CSLabels.id2label.items(): + palette[label_id] = label.color + + output.putpalette(palette) + output.save(png_filename) + result_files.append(png_filename) + prog_bar.update() + + return result_files + + def format_results(self, results, imgfile_prefix=None, to_label_id=True): + """Format the results into dir (standard format for Cityscapes + evaluation). + + Args: + results (list): Testing results of the dataset. + imgfile_prefix (str | None): The prefix of images files. It + includes the file path and the prefix of filename, e.g., + "a/b/prefix". If not specified, a temp file will be created. + Default: None. + to_label_id (bool): whether convert output to label_id for + submission. Default: False + + Returns: + tuple: (result_files, tmp_dir), result_files is a list containing + the image paths, tmp_dir is the temporal directory created + for saving json/png files when img_prefix is not specified. + """ + + assert isinstance(results, list), 'results must be a list' + assert len(results) == len(self), ( + 'The length of results is not equal to the dataset len: ' + f'{len(results)} != {len(self)}') + + if imgfile_prefix is None: + tmp_dir = tempfile.TemporaryDirectory() + imgfile_prefix = tmp_dir.name + else: + tmp_dir = None + result_files = self.results2img(results, imgfile_prefix, to_label_id) + + return result_files, tmp_dir + + def evaluate(self, + results, + metric='mIoU', + logger=None, + imgfile_prefix=None, + efficient_test=False): + """Evaluation in Cityscapes/default protocol. + + Args: + results (list): Testing results of the dataset. + metric (str | list[str]): Metrics to be evaluated. + logger (logging.Logger | None | str): Logger used for printing + related information during evaluation. Default: None. + imgfile_prefix (str | None): The prefix of output image file, + for cityscapes evaluation only. It includes the file path and + the prefix of filename, e.g., "a/b/prefix". + If results are evaluated with cityscapes protocol, it would be + the prefix of output png files. The output files would be + png images under folder "a/b/prefix/xxx.png", where "xxx" is + the image name of cityscapes. If not specified, a temp file + will be created for evaluation. + Default: None. + + Returns: + dict[str, float]: Cityscapes/default metrics. + """ + + eval_results = dict() + metrics = metric.copy() if isinstance(metric, list) else [metric] + if 'cityscapes' in metrics: + eval_results.update( + self._evaluate_cityscapes(results, logger, imgfile_prefix)) + metrics.remove('cityscapes') + if len(metrics) > 0: + eval_results.update( + super(CityscapesDataset, + self).evaluate(results, metrics, logger, efficient_test)) + + return eval_results + + def _evaluate_cityscapes(self, results, logger, imgfile_prefix): + """Evaluation in Cityscapes protocol. + + Args: + results (list): Testing results of the dataset. + logger (logging.Logger | str | None): Logger used for printing + related information during evaluation. Default: None. + imgfile_prefix (str | None): The prefix of output image file + + Returns: + dict[str: float]: Cityscapes evaluation results. + """ + try: + import cityscapesscripts.evaluation.evalPixelLevelSemanticLabeling as CSEval # noqa + except ImportError: + raise ImportError('Please run "pip install cityscapesscripts" to ' + 'install cityscapesscripts first.') + msg = 'Evaluating in Cityscapes style' + if logger is None: + msg = '\n' + msg + print_log(msg, logger=logger) + + result_files, tmp_dir = self.format_results(results, imgfile_prefix) + + if tmp_dir is None: + result_dir = imgfile_prefix + else: + result_dir = tmp_dir.name + + eval_results = dict() + print_log(f'Evaluating results under {result_dir} ...', logger=logger) + + CSEval.args.evalInstLevelScore = True + CSEval.args.predictionPath = osp.abspath(result_dir) + CSEval.args.evalPixelAccuracy = True + CSEval.args.JSONOutput = False + + seg_map_list = [] + pred_list = [] + + # when evaluating with official cityscapesscripts, + # **_gtFine_labelIds.png is used + for seg_map in mmcv.scandir( + self.ann_dir, 'gtFine_labelIds.png', recursive=True): + seg_map_list.append(osp.join(self.ann_dir, seg_map)) + pred_list.append(CSEval.getPrediction(CSEval.args, seg_map)) + + eval_results.update( + CSEval.evaluateImgLists(pred_list, seg_map_list, CSEval.args)) + + if tmp_dir is not None: + tmp_dir.cleanup() + + return eval_results diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/custom.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/custom.py new file mode 100644 index 0000000..d8eb2a7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/custom.py @@ -0,0 +1,400 @@ +import os +import os.path as osp +from collections import OrderedDict +from functools import reduce + +import annotator.uniformer.mmcv as mmcv +import numpy as np +from annotator.uniformer.mmcv.utils import print_log +from prettytable import PrettyTable +from torch.utils.data import Dataset + +from annotator.uniformer.mmseg.core import eval_metrics +from annotator.uniformer.mmseg.utils import get_root_logger +from .builder import DATASETS +from .pipelines import Compose + + +@DATASETS.register_module() +class CustomDataset(Dataset): + """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 CustomDataset 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/tutorials/new_dataset.md`` for more details. + + + Args: + pipeline (list[dict]): Processing pipeline + img_dir (str): Path to image directory + img_suffix (str): Suffix of images. Default: '.jpg' + ann_dir (str, optional): Path to annotation directory. Default: None + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + split (str, optional): Split txt file. If split is specified, only + file with suffix in the splits will be loaded. Otherwise, all + images in img_dir/ann_dir will be loaded. Default: None + data_root (str, optional): Data root for img_dir/ann_dir. Default: + None. + test_mode (bool): If test_mode=True, gt wouldn't be loaded. + ignore_index (int): The label index to be ignored. Default: 255 + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default: False + classes (str | Sequence[str], optional): Specify classes to load. + If is None, ``cls.CLASSES`` will be used. Default: None. + palette (Sequence[Sequence[int]]] | np.ndarray | None): + The palette of segmentation map. If None is given, and + self.PALETTE is None, random palette will be generated. + Default: None + """ + + CLASSES = None + + PALETTE = None + + def __init__(self, + pipeline, + img_dir, + img_suffix='.jpg', + ann_dir=None, + seg_map_suffix='.png', + split=None, + data_root=None, + test_mode=False, + ignore_index=255, + reduce_zero_label=False, + classes=None, + palette=None): + self.pipeline = Compose(pipeline) + self.img_dir = img_dir + self.img_suffix = img_suffix + self.ann_dir = ann_dir + self.seg_map_suffix = seg_map_suffix + self.split = split + self.data_root = data_root + self.test_mode = test_mode + self.ignore_index = ignore_index + self.reduce_zero_label = reduce_zero_label + self.label_map = None + self.CLASSES, self.PALETTE = self.get_classes_and_palette( + classes, palette) + + # join paths if data_root is specified + if self.data_root is not None: + if not osp.isabs(self.img_dir): + self.img_dir = osp.join(self.data_root, self.img_dir) + if not (self.ann_dir is None or osp.isabs(self.ann_dir)): + self.ann_dir = osp.join(self.data_root, self.ann_dir) + if not (self.split is None or osp.isabs(self.split)): + self.split = osp.join(self.data_root, self.split) + + # load annotations + self.img_infos = self.load_annotations(self.img_dir, self.img_suffix, + self.ann_dir, + self.seg_map_suffix, self.split) + + def __len__(self): + """Total number of samples of data.""" + return len(self.img_infos) + + def load_annotations(self, img_dir, img_suffix, ann_dir, seg_map_suffix, + split): + """Load annotation from directory. + + Args: + img_dir (str): Path to image directory + img_suffix (str): Suffix of images. + ann_dir (str|None): Path to annotation directory. + seg_map_suffix (str|None): Suffix of segmentation maps. + split (str|None): Split txt file. If split is specified, only file + with suffix in the splits will be loaded. Otherwise, all images + in img_dir/ann_dir will be loaded. Default: None + + Returns: + list[dict]: All image info of dataset. + """ + + img_infos = [] + if split is not None: + with open(split) as f: + for line in f: + img_name = line.strip() + img_info = dict(filename=img_name + img_suffix) + if ann_dir is not None: + seg_map = img_name + seg_map_suffix + img_info['ann'] = dict(seg_map=seg_map) + img_infos.append(img_info) + else: + for img in mmcv.scandir(img_dir, img_suffix, recursive=True): + img_info = dict(filename=img) + if ann_dir is not None: + seg_map = img.replace(img_suffix, seg_map_suffix) + img_info['ann'] = dict(seg_map=seg_map) + img_infos.append(img_info) + + print_log(f'Loaded {len(img_infos)} images', logger=get_root_logger()) + return img_infos + + def get_ann_info(self, idx): + """Get annotation by index. + + Args: + idx (int): Index of data. + + Returns: + dict: Annotation info of specified index. + """ + + return self.img_infos[idx]['ann'] + + def pre_pipeline(self, results): + """Prepare results dict for pipeline.""" + results['seg_fields'] = [] + results['img_prefix'] = self.img_dir + results['seg_prefix'] = self.ann_dir + if self.custom_classes: + results['label_map'] = self.label_map + + def __getitem__(self, idx): + """Get training/test data after pipeline. + + Args: + idx (int): Index of data. + + Returns: + dict: Training/test data (with annotation if `test_mode` is set + False). + """ + + if self.test_mode: + return self.prepare_test_img(idx) + else: + return self.prepare_train_img(idx) + + def prepare_train_img(self, idx): + """Get training data and annotations after pipeline. + + Args: + idx (int): Index of data. + + Returns: + dict: Training data and annotation after pipeline with new keys + introduced by pipeline. + """ + + img_info = self.img_infos[idx] + ann_info = self.get_ann_info(idx) + results = dict(img_info=img_info, ann_info=ann_info) + self.pre_pipeline(results) + return self.pipeline(results) + + def prepare_test_img(self, idx): + """Get testing data after pipeline. + + Args: + idx (int): Index of data. + + Returns: + dict: Testing data after pipeline with new keys introduced by + pipeline. + """ + + img_info = self.img_infos[idx] + results = dict(img_info=img_info) + self.pre_pipeline(results) + return self.pipeline(results) + + def format_results(self, results, **kwargs): + """Place holder to format result to dataset specific output.""" + + def get_gt_seg_maps(self, efficient_test=False): + """Get ground truth segmentation maps for evaluation.""" + gt_seg_maps = [] + for img_info in self.img_infos: + seg_map = osp.join(self.ann_dir, img_info['ann']['seg_map']) + if efficient_test: + gt_seg_map = seg_map + else: + gt_seg_map = mmcv.imread( + seg_map, flag='unchanged', backend='pillow') + gt_seg_maps.append(gt_seg_map) + return gt_seg_maps + + def get_classes_and_palette(self, classes=None, palette=None): + """Get class names of current dataset. + + Args: + classes (Sequence[str] | str | None): If classes is None, use + default CLASSES defined by builtin dataset. If classes is a + string, take it as a file name. The file contains the name of + classes where each line contains one class name. If classes is + a tuple or list, override the CLASSES defined by the dataset. + palette (Sequence[Sequence[int]]] | np.ndarray | None): + The palette of segmentation map. If None is given, random + palette will be generated. Default: None + """ + if classes is None: + self.custom_classes = False + return self.CLASSES, self.PALETTE + + self.custom_classes = True + if isinstance(classes, str): + # take it as a file path + class_names = mmcv.list_from_file(classes) + elif isinstance(classes, (tuple, list)): + class_names = classes + else: + raise ValueError(f'Unsupported type {type(classes)} of classes.') + + if self.CLASSES: + if not set(classes).issubset(self.CLASSES): + raise ValueError('classes is not a subset of CLASSES.') + + # dictionary, its keys are the old label ids and its values + # are the new label ids. + # used for changing pixel labels in load_annotations. + self.label_map = {} + for i, c in enumerate(self.CLASSES): + if c not in class_names: + self.label_map[i] = -1 + else: + self.label_map[i] = classes.index(c) + + palette = self.get_palette_for_custom_classes(class_names, palette) + + return class_names, palette + + def get_palette_for_custom_classes(self, class_names, palette=None): + + if self.label_map is not None: + # return subset of palette + palette = [] + for old_id, new_id in sorted( + self.label_map.items(), key=lambda x: x[1]): + if new_id != -1: + palette.append(self.PALETTE[old_id]) + palette = type(self.PALETTE)(palette) + + elif palette is None: + if self.PALETTE is None: + palette = np.random.randint(0, 255, size=(len(class_names), 3)) + else: + palette = self.PALETTE + + return palette + + def evaluate(self, + results, + metric='mIoU', + logger=None, + efficient_test=False, + **kwargs): + """Evaluate the dataset. + + Args: + results (list): Testing results of the dataset. + metric (str | list[str]): Metrics to be evaluated. 'mIoU', + 'mDice' and 'mFscore' are supported. + logger (logging.Logger | None | str): Logger used for printing + related information during evaluation. Default: None. + + Returns: + dict[str, float]: Default metrics. + """ + + if isinstance(metric, str): + metric = [metric] + allowed_metrics = ['mIoU', 'mDice', 'mFscore'] + if not set(metric).issubset(set(allowed_metrics)): + raise KeyError('metric {} is not supported'.format(metric)) + eval_results = {} + gt_seg_maps = self.get_gt_seg_maps(efficient_test) + if self.CLASSES is None: + num_classes = len( + reduce(np.union1d, [np.unique(_) for _ in gt_seg_maps])) + else: + num_classes = len(self.CLASSES) + ret_metrics = eval_metrics( + results, + gt_seg_maps, + num_classes, + self.ignore_index, + metric, + label_map=self.label_map, + reduce_zero_label=self.reduce_zero_label) + + if self.CLASSES is None: + class_names = tuple(range(num_classes)) + else: + class_names = self.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() + }) + + # 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) + + # for logger + class_table_data = PrettyTable() + for key, val in ret_metrics_class.items(): + class_table_data.add_column(key, val) + + summary_table_data = PrettyTable() + for key, val in ret_metrics_summary.items(): + if key == 'aAcc': + summary_table_data.add_column(key, [val]) + else: + summary_table_data.add_column('m' + key, [val]) + + print_log('per class results:', logger) + print_log('\n' + class_table_data.get_string(), logger=logger) + print_log('Summary:', logger) + print_log('\n' + summary_table_data.get_string(), logger=logger) + + # each metric dict + for key, value in ret_metrics_summary.items(): + if key == 'aAcc': + eval_results[key] = value / 100.0 + else: + eval_results['m' + key] = value / 100.0 + + ret_metrics_class.pop('Class', None) + for key, value in ret_metrics_class.items(): + eval_results.update({ + key + '.' + str(name): value[idx] / 100.0 + for idx, name in enumerate(class_names) + }) + + if mmcv.is_list_of(results, str): + for file_name in results: + os.remove(file_name) + return eval_results diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/dataset_wrappers.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/dataset_wrappers.py new file mode 100644 index 0000000..d6a5e95 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/dataset_wrappers.py @@ -0,0 +1,50 @@ +from torch.utils.data.dataset import ConcatDataset as _ConcatDataset + +from .builder import DATASETS + + +@DATASETS.register_module() +class ConcatDataset(_ConcatDataset): + """A wrapper of concatenated dataset. + + Same as :obj:`torch.utils.data.dataset.ConcatDataset`, but + concat the group flag for image aspect ratio. + + Args: + datasets (list[:obj:`Dataset`]): A list of datasets. + """ + + def __init__(self, datasets): + super(ConcatDataset, self).__init__(datasets) + self.CLASSES = datasets[0].CLASSES + self.PALETTE = datasets[0].PALETTE + + +@DATASETS.register_module() +class RepeatDataset(object): + """A wrapper of repeated dataset. + + The length of repeated dataset will be `times` larger than the original + dataset. This is useful when the data loading time is long but the dataset + is small. Using RepeatDataset can reduce the data loading time between + epochs. + + Args: + dataset (:obj:`Dataset`): The dataset to be repeated. + times (int): Repeat times. + """ + + def __init__(self, dataset, times): + self.dataset = dataset + self.times = times + self.CLASSES = dataset.CLASSES + self.PALETTE = dataset.PALETTE + self._ori_len = len(self.dataset) + + def __getitem__(self, idx): + """Get item from original dataset.""" + return self.dataset[idx % self._ori_len] + + def __len__(self): + """The length is multiplied by ``times``""" + return self.times * self._ori_len diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/drive.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/drive.py new file mode 100644 index 0000000..3cbfda8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/drive.py @@ -0,0 +1,27 @@ +import os.path as osp + +from .builder import DATASETS +from .custom import CustomDataset + + +@DATASETS.register_module() +class DRIVEDataset(CustomDataset): + """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'. + """ + + CLASSES = ('background', 'vessel') + + PALETTE = [[120, 120, 120], [6, 230, 230]] + + def __init__(self, **kwargs): + super(DRIVEDataset, self).__init__( + img_suffix='.png', + seg_map_suffix='_manual1.png', + reduce_zero_label=False, + **kwargs) + assert osp.exists(self.img_dir) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/hrf.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/hrf.py new file mode 100644 index 0000000..923203b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/hrf.py @@ -0,0 +1,27 @@ +import os.path as osp + +from .builder import DATASETS +from .custom import CustomDataset + + +@DATASETS.register_module() +class HRFDataset(CustomDataset): + """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'. + """ + + CLASSES = ('background', 'vessel') + + PALETTE = [[120, 120, 120], [6, 230, 230]] + + def __init__(self, **kwargs): + super(HRFDataset, self).__init__( + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) + assert osp.exists(self.img_dir) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pascal_context.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pascal_context.py new file mode 100644 index 0000000..541a63c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pascal_context.py @@ -0,0 +1,103 @@ +import os.path as osp + +from .builder import DATASETS +from .custom import CustomDataset + + +@DATASETS.register_module() +class PascalContextDataset(CustomDataset): + """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: + split (str): Split txt file for PascalContext. + """ + + 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, split, **kwargs): + super(PascalContextDataset, self).__init__( + img_suffix='.jpg', + seg_map_suffix='.png', + split=split, + reduce_zero_label=False, + **kwargs) + assert osp.exists(self.img_dir) and self.split is not None + + +@DATASETS.register_module() +class PascalContextDataset59(CustomDataset): + """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: + split (str): Split txt file for PascalContext. + """ + + 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, split, **kwargs): + super(PascalContextDataset59, self).__init__( + img_suffix='.jpg', + seg_map_suffix='.png', + split=split, + reduce_zero_label=True, + **kwargs) + assert osp.exists(self.img_dir) and self.split is not None diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/__init__.py new file mode 100644 index 0000000..8b9046b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/__init__.py @@ -0,0 +1,16 @@ +from .compose import Compose +from .formating import (Collect, ImageToTensor, ToDataContainer, ToTensor, + Transpose, to_tensor) +from .loading import LoadAnnotations, LoadImageFromFile +from .test_time_aug import MultiScaleFlipAug +from .transforms import (CLAHE, AdjustGamma, Normalize, Pad, + PhotoMetricDistortion, RandomCrop, RandomFlip, + RandomRotate, Rerange, Resize, RGB2Gray, SegRescale) + +__all__ = [ + 'Compose', 'to_tensor', 'ToTensor', 'ImageToTensor', 'ToDataContainer', + 'Transpose', 'Collect', 'LoadAnnotations', 'LoadImageFromFile', + 'MultiScaleFlipAug', 'Resize', 'RandomFlip', 'Pad', 'RandomCrop', + 'Normalize', 'SegRescale', 'PhotoMetricDistortion', 'RandomRotate', + 'AdjustGamma', 'CLAHE', 'Rerange', 'RGB2Gray' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/compose.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/compose.py new file mode 100644 index 0000000..cbfcbb9 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/compose.py @@ -0,0 +1,51 @@ +import collections + +from annotator.uniformer.mmcv.utils import build_from_cfg + +from ..builder import PIPELINES + + +@PIPELINES.register_module() +class Compose(object): + """Compose multiple transforms sequentially. + + Args: + transforms (Sequence[dict | callable]): Sequence of transform object or + config dict to be composed. + """ + + def __init__(self, transforms): + assert isinstance(transforms, collections.abc.Sequence) + self.transforms = [] + for transform in transforms: + if isinstance(transform, dict): + transform = build_from_cfg(transform, PIPELINES) + self.transforms.append(transform) + elif callable(transform): + self.transforms.append(transform) + else: + raise TypeError('transform must be callable or a dict') + + def __call__(self, data): + """Call function to apply transforms sequentially. + + Args: + data (dict): A result dict contains the data to transform. + + Returns: + dict: Transformed data. + """ + + for t in self.transforms: + data = t(data) + if data is None: + return None + return data + + def __repr__(self): + format_string = self.__class__.__name__ + '(' + for t in self.transforms: + format_string += '\n' + format_string += f' {t}' + format_string += '\n)' + return format_string diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/formating.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/formating.py new file mode 100644 index 0000000..97db85f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/formating.py @@ -0,0 +1,288 @@ +from collections.abc import Sequence + +import annotator.uniformer.mmcv as mmcv +import numpy as np +import torch +from annotator.uniformer.mmcv.parallel import DataContainer as DC + +from ..builder import PIPELINES + + +def to_tensor(data): + """Convert objects of various python types to :obj:`torch.Tensor`. + + Supported types are: :class:`numpy.ndarray`, :class:`torch.Tensor`, + :class:`Sequence`, :class:`int` and :class:`float`. + + Args: + data (torch.Tensor | numpy.ndarray | Sequence | int | float): Data to + be converted. + """ + + if isinstance(data, torch.Tensor): + return data + elif isinstance(data, np.ndarray): + return torch.from_numpy(data) + elif isinstance(data, Sequence) and not mmcv.is_str(data): + return torch.tensor(data) + elif isinstance(data, int): + return torch.LongTensor([data]) + elif isinstance(data, float): + return torch.FloatTensor([data]) + else: + raise TypeError(f'type {type(data)} cannot be converted to tensor.') + + +@PIPELINES.register_module() +class ToTensor(object): + """Convert some results to :obj:`torch.Tensor` by given keys. + + Args: + keys (Sequence[str]): Keys that need to be converted to Tensor. + """ + + def __init__(self, keys): + self.keys = keys + + def __call__(self, results): + """Call function to convert data in results to :obj:`torch.Tensor`. + + Args: + results (dict): Result dict contains the data to convert. + + Returns: + dict: The result dict contains the data converted + to :obj:`torch.Tensor`. + """ + + for key in self.keys: + results[key] = to_tensor(results[key]) + return results + + def __repr__(self): + return self.__class__.__name__ + f'(keys={self.keys})' + + +@PIPELINES.register_module() +class ImageToTensor(object): + """Convert image to :obj:`torch.Tensor` by given keys. + + The dimension order of input image is (H, W, C). The pipeline will convert + it to (C, H, W). If only 2 dimension (H, W) is given, the output would be + (1, H, W). + + Args: + keys (Sequence[str]): Key of images to be converted to Tensor. + """ + + def __init__(self, keys): + self.keys = keys + + def __call__(self, results): + """Call function to convert image in results to :obj:`torch.Tensor` and + transpose the channel order. + + Args: + results (dict): Result dict contains the image data to convert. + + Returns: + dict: The result dict contains the image converted + to :obj:`torch.Tensor` and transposed to (C, H, W) order. + """ + + for key in self.keys: + img = results[key] + if len(img.shape) < 3: + img = np.expand_dims(img, -1) + results[key] = to_tensor(img.transpose(2, 0, 1)) + return results + + def __repr__(self): + return self.__class__.__name__ + f'(keys={self.keys})' + + +@PIPELINES.register_module() +class Transpose(object): + """Transpose some results by given keys. + + Args: + keys (Sequence[str]): Keys of results to be transposed. + order (Sequence[int]): Order of transpose. + """ + + def __init__(self, keys, order): + self.keys = keys + self.order = order + + def __call__(self, results): + """Call function to convert image in results to :obj:`torch.Tensor` and + transpose the channel order. + + Args: + results (dict): Result dict contains the image data to convert. + + Returns: + dict: The result dict contains the image converted + to :obj:`torch.Tensor` and transposed to (C, H, W) order. + """ + + for key in self.keys: + results[key] = results[key].transpose(self.order) + return results + + def __repr__(self): + return self.__class__.__name__ + \ + f'(keys={self.keys}, order={self.order})' + + +@PIPELINES.register_module() +class ToDataContainer(object): + """Convert results to :obj:`mmcv.DataContainer` by given fields. + + Args: + fields (Sequence[dict]): Each field is a dict like + ``dict(key='xxx', **kwargs)``. The ``key`` in result will + be converted to :obj:`mmcv.DataContainer` with ``**kwargs``. + Default: ``(dict(key='img', stack=True), + dict(key='gt_semantic_seg'))``. + """ + + def __init__(self, + fields=(dict(key='img', + stack=True), dict(key='gt_semantic_seg'))): + self.fields = fields + + def __call__(self, results): + """Call function to convert data in results to + :obj:`mmcv.DataContainer`. + + Args: + results (dict): Result dict contains the data to convert. + + Returns: + dict: The result dict contains the data converted to + :obj:`mmcv.DataContainer`. + """ + + for field in self.fields: + field = field.copy() + key = field.pop('key') + results[key] = DC(results[key], **field) + return results + + def __repr__(self): + return self.__class__.__name__ + f'(fields={self.fields})' + + +@PIPELINES.register_module() +class DefaultFormatBundle(object): + """Default formatting bundle. + + It simplifies the pipeline of formatting common fields, including "img" + and "gt_semantic_seg". These fields are formatted as follows. + + - img: (1)transpose, (2)to tensor, (3)to DataContainer (stack=True) + - gt_semantic_seg: (1)unsqueeze dim-0 (2)to tensor, + (3)to DataContainer (stack=True) + """ + + def __call__(self, results): + """Call function to transform and format common fields in results. + + Args: + results (dict): Result dict contains the data to convert. + + Returns: + dict: The result dict contains the data that is formatted with + default bundle. + """ + + if 'img' in results: + img = results['img'] + if len(img.shape) < 3: + img = np.expand_dims(img, -1) + img = np.ascontiguousarray(img.transpose(2, 0, 1)) + results['img'] = DC(to_tensor(img), stack=True) + if 'gt_semantic_seg' in results: + # convert to long + results['gt_semantic_seg'] = DC( + to_tensor(results['gt_semantic_seg'][None, + ...].astype(np.int64)), + stack=True) + return results + + def __repr__(self): + return self.__class__.__name__ + + +@PIPELINES.register_module() +class Collect(object): + """Collect data from the loader relevant to the specific task. + + This is usually the last stage of the data loader pipeline. Typically keys + is set to some subset of "img", "gt_semantic_seg". + + The "img_meta" item is always populated. The contents of the "img_meta" + dictionary depends on "meta_keys". By default this includes: + + - "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. + + - "scale_factor": a float indicating the preprocessing scale + + - "flip": a boolean indicating if image flip transform was used + + - "filename": path to the image file + + - "ori_shape": original shape of the image as a tuple (h, w, c) + + - "pad_shape": image shape after padding + + - "img_norm_cfg": a dict of normalization information: + - mean - per channel mean subtraction + - std - per channel std divisor + - to_rgb - bool indicating if bgr was converted to rgb + + Args: + keys (Sequence[str]): Keys of results to be collected in ``data``. + meta_keys (Sequence[str], optional): Meta keys to be converted to + ``mmcv.DataContainer`` and collected in ``data[img_metas]``. + Default: ``('filename', 'ori_filename', 'ori_shape', 'img_shape', + 'pad_shape', 'scale_factor', 'flip', 'flip_direction', + 'img_norm_cfg')`` + """ + + def __init__(self, + keys, + meta_keys=('filename', 'ori_filename', 'ori_shape', + 'img_shape', 'pad_shape', 'scale_factor', 'flip', + 'flip_direction', 'img_norm_cfg')): + self.keys = keys + self.meta_keys = meta_keys + + def __call__(self, results): + """Call function to collect keys in results. The keys in ``meta_keys`` + will be converted to :obj:mmcv.DataContainer. + + Args: + results (dict): Result dict contains the data to collect. + + Returns: + dict: The result dict contains the following keys + - keys in``self.keys`` + - ``img_metas`` + """ + + data = {} + img_meta = {} + for key in self.meta_keys: + img_meta[key] = results[key] + data['img_metas'] = DC(img_meta, cpu_only=True) + for key in self.keys: + data[key] = results[key] + return data + + def __repr__(self): + return self.__class__.__name__ + \ + f'(keys={self.keys}, meta_keys={self.meta_keys})' diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/loading.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/loading.py new file mode 100644 index 0000000..d3692ae --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/loading.py @@ -0,0 +1,153 @@ +import os.path as osp + +import annotator.uniformer.mmcv as mmcv +import numpy as np + +from ..builder import PIPELINES + + +@PIPELINES.register_module() +class LoadImageFromFile(object): + """Load an image from file. + + Required keys are "img_prefix" and "img_info" (a dict that must contain the + key "filename"). Added or updated keys are "filename", "img", "img_shape", + "ori_shape" (same as `img_shape`), "pad_shape" (same as `img_shape`), + "scale_factor" (1.0) and "img_norm_cfg" (means=0 and stds=1). + + 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. + color_type (str): The flag argument for :func:`mmcv.imfrombytes`. + Defaults to 'color'. + file_client_args (dict): Arguments to instantiate a FileClient. + See :class:`mmcv.fileio.FileClient` for details. + Defaults to ``dict(backend='disk')``. + imdecode_backend (str): Backend for :func:`mmcv.imdecode`. Default: + 'cv2' + """ + + def __init__(self, + to_float32=False, + color_type='color', + file_client_args=dict(backend='disk'), + imdecode_backend='cv2'): + self.to_float32 = to_float32 + self.color_type = color_type + self.file_client_args = file_client_args.copy() + self.file_client = None + self.imdecode_backend = imdecode_backend + + def __call__(self, results): + """Call functions to load image and get image meta information. + + Args: + results (dict): Result dict from :obj:`mmseg.CustomDataset`. + + Returns: + dict: The dict contains loaded image and meta information. + """ + + if self.file_client is None: + self.file_client = mmcv.FileClient(**self.file_client_args) + + if results.get('img_prefix') is not None: + filename = osp.join(results['img_prefix'], + results['img_info']['filename']) + else: + filename = results['img_info']['filename'] + img_bytes = self.file_client.get(filename) + img = mmcv.imfrombytes( + img_bytes, flag=self.color_type, backend=self.imdecode_backend) + if self.to_float32: + img = img.astype(np.float32) + + results['filename'] = filename + results['ori_filename'] = results['img_info']['filename'] + 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 + num_channels = 1 if len(img.shape) < 3 else img.shape[2] + results['img_norm_cfg'] = dict( + mean=np.zeros(num_channels, dtype=np.float32), + std=np.ones(num_channels, dtype=np.float32), + to_rgb=False) + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(to_float32={self.to_float32},' + repr_str += f"color_type='{self.color_type}'," + repr_str += f"imdecode_backend='{self.imdecode_backend}')" + return repr_str + + +@PIPELINES.register_module() +class LoadAnnotations(object): + """Load annotations for semantic segmentation. + + Args: + reduce_zero_label (bool): Whether reduce all label value by 1. + Usually used for datasets where 0 is background label. + Default: False. + file_client_args (dict): Arguments to instantiate a FileClient. + See :class:`mmcv.fileio.FileClient` for details. + Defaults to ``dict(backend='disk')``. + imdecode_backend (str): Backend for :func:`mmcv.imdecode`. Default: + 'pillow' + """ + + def __init__(self, + reduce_zero_label=False, + file_client_args=dict(backend='disk'), + imdecode_backend='pillow'): + self.reduce_zero_label = reduce_zero_label + self.file_client_args = file_client_args.copy() + self.file_client = None + self.imdecode_backend = imdecode_backend + + def __call__(self, results): + """Call function to load multiple types annotations. + + Args: + results (dict): Result dict from :obj:`mmseg.CustomDataset`. + + Returns: + dict: The dict contains loaded semantic segmentation annotations. + """ + + if self.file_client is None: + self.file_client = mmcv.FileClient(**self.file_client_args) + + if results.get('seg_prefix', None) is not None: + filename = osp.join(results['seg_prefix'], + results['ann_info']['seg_map']) + else: + filename = results['ann_info']['seg_map'] + img_bytes = self.file_client.get(filename) + gt_semantic_seg = mmcv.imfrombytes( + img_bytes, flag='unchanged', + backend=self.imdecode_backend).squeeze().astype(np.uint8) + # modify if custom classes + if results.get('label_map', None) is not None: + for old_id, new_id in results['label_map'].items(): + gt_semantic_seg[gt_semantic_seg == old_id] = new_id + # 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 + results['gt_semantic_seg'] = gt_semantic_seg + results['seg_fields'].append('gt_semantic_seg') + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(reduce_zero_label={self.reduce_zero_label},' + repr_str += f"imdecode_backend='{self.imdecode_backend}')" + return repr_str diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/test_time_aug.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/test_time_aug.py new file mode 100644 index 0000000..6a1611a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/test_time_aug.py @@ -0,0 +1,133 @@ +import warnings + +import annotator.uniformer.mmcv as mmcv + +from ..builder import PIPELINES +from .compose import Compose + + +@PIPELINES.register_module() +class MultiScaleFlipAug(object): + """Test-time augmentation with multiple scales and flipping. + + An example configuration is as followed: + + .. code-block:: + + img_scale=(2048, 1024), + img_ratios=[0.5, 1.0], + flip=True, + transforms=[ + dict(type='Resize', keep_ratio=True), + dict(type='RandomFlip'), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='ImageToTensor', keys=['img']), + dict(type='Collect', keys=['img']), + ] + + After MultiScaleFLipAug with above configuration, the results are wrapped + into lists of the same length as followed: + + .. code-block:: + + dict( + img=[...], + img_shape=[...], + scale=[(1024, 512), (1024, 512), (2048, 1024), (2048, 1024)] + flip=[False, True, False, True] + ... + ) + + Args: + transforms (list[dict]): Transforms to apply in each augmentation. + img_scale (None | tuple | list[tuple]): Images scales for resizing. + img_ratios (float | list[float]): Image ratios for resizing + flip (bool): Whether apply flip augmentation. Default: False. + flip_direction (str | list[str]): Flip augmentation directions, + options are "horizontal" and "vertical". If flip_direction is list, + multiple flip augmentations will be applied. + It has no effect when flip == False. Default: "horizontal". + """ + + def __init__(self, + transforms, + img_scale, + img_ratios=None, + flip=False, + flip_direction='horizontal'): + self.transforms = Compose(transforms) + if img_ratios is not None: + img_ratios = img_ratios if isinstance(img_ratios, + list) else [img_ratios] + assert mmcv.is_list_of(img_ratios, float) + if img_scale is None: + # mode 1: given img_scale=None and a range of image ratio + self.img_scale = None + assert mmcv.is_list_of(img_ratios, float) + elif isinstance(img_scale, tuple) and mmcv.is_list_of( + img_ratios, float): + assert len(img_scale) == 2 + # mode 2: given a scale and a range of image ratio + self.img_scale = [(int(img_scale[0] * ratio), + int(img_scale[1] * ratio)) + for ratio in img_ratios] + else: + # mode 3: given multiple scales + self.img_scale = img_scale if isinstance(img_scale, + list) else [img_scale] + assert mmcv.is_list_of(self.img_scale, tuple) or self.img_scale is None + self.flip = flip + self.img_ratios = img_ratios + self.flip_direction = flip_direction if isinstance( + flip_direction, list) else [flip_direction] + assert mmcv.is_list_of(self.flip_direction, str) + if not self.flip and self.flip_direction != ['horizontal']: + warnings.warn( + 'flip_direction has no effect when flip is set to False') + if (self.flip + and not any([t['type'] == 'RandomFlip' for t in transforms])): + warnings.warn( + 'flip has no effect when RandomFlip is not in transforms') + + def __call__(self, results): + """Call function to apply test time augment transforms on results. + + Args: + results (dict): Result dict contains the data to transform. + + Returns: + dict[str: list]: The augmented data, where each value is wrapped + into a list. + """ + + aug_data = [] + if self.img_scale is None and mmcv.is_list_of(self.img_ratios, float): + h, w = results['img'].shape[:2] + img_scale = [(int(w * ratio), int(h * ratio)) + for ratio in self.img_ratios] + else: + img_scale = self.img_scale + flip_aug = [False, True] if self.flip else [False] + for scale in img_scale: + for flip in flip_aug: + for direction in self.flip_direction: + _results = results.copy() + _results['scale'] = scale + _results['flip'] = flip + _results['flip_direction'] = direction + data = self.transforms(_results) + aug_data.append(data) + # list of dict to dict of list + aug_data_dict = {key: [] for key in aug_data[0]} + for data in aug_data: + for key, val in data.items(): + aug_data_dict[key].append(val) + return aug_data_dict + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(transforms={self.transforms}, ' + repr_str += f'img_scale={self.img_scale}, flip={self.flip})' + repr_str += f'flip_direction={self.flip_direction}' + return repr_str diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/transforms.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/transforms.py new file mode 100644 index 0000000..94e869b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/pipelines/transforms.py @@ -0,0 +1,889 @@ +import annotator.uniformer.mmcv as mmcv +import numpy as np +from annotator.uniformer.mmcv.utils import deprecated_api_warning, is_tuple_of +from numpy import random + +from ..builder import PIPELINES + + +@PIPELINES.register_module() +class Resize(object): + """Resize images & seg. + + This transform resizes the input image to some scale. If the input dict + contains the key "scale", then the scale in the input dict is used, + otherwise the specified scale in the init method is used. + + ``img_scale`` can be None, a tuple (single-scale) or a list of tuple + (multi-scale). There are 4 multiscale modes: + + - ``ratio_range is not None``: + 1. When img_scale is None, img_scale is the shape of image in results + (img_scale = results['img'].shape[:2]) and the image is resized based + on the original size. (mode 1) + 2. When img_scale is a tuple (single-scale), randomly sample a ratio from + the ratio range and multiply it with the image scale. (mode 2) + + - ``ratio_range is None and multiscale_mode == "range"``: randomly sample a + scale from the a range. (mode 3) + + - ``ratio_range is None and multiscale_mode == "value"``: randomly sample a + scale from multiple scales. (mode 4) + + Args: + img_scale (tuple or list[tuple]): Images scales for resizing. + multiscale_mode (str): Either "range" or "value". + ratio_range (tuple[float]): (min_ratio, max_ratio) + keep_ratio (bool): Whether to keep the aspect ratio when resizing the + image. + """ + + def __init__(self, + img_scale=None, + multiscale_mode='range', + ratio_range=None, + keep_ratio=True): + if img_scale is None: + self.img_scale = None + else: + if isinstance(img_scale, list): + self.img_scale = img_scale + else: + self.img_scale = [img_scale] + assert mmcv.is_list_of(self.img_scale, tuple) + + if ratio_range is not None: + # mode 1: given img_scale=None and a range of image ratio + # mode 2: given a scale and a range of image ratio + assert self.img_scale is None or len(self.img_scale) == 1 + else: + # mode 3 and 4: given multiple scales or a range of scales + assert multiscale_mode in ['value', 'range'] + + self.multiscale_mode = multiscale_mode + self.ratio_range = ratio_range + self.keep_ratio = keep_ratio + + @staticmethod + def random_select(img_scales): + """Randomly select an img_scale from given candidates. + + Args: + img_scales (list[tuple]): Images scales for selection. + + Returns: + (tuple, int): Returns a tuple ``(img_scale, scale_dix)``, + where ``img_scale`` is the selected image scale and + ``scale_idx`` is the selected index in the given candidates. + """ + + assert mmcv.is_list_of(img_scales, tuple) + scale_idx = np.random.randint(len(img_scales)) + img_scale = img_scales[scale_idx] + return img_scale, scale_idx + + @staticmethod + def random_sample(img_scales): + """Randomly sample an img_scale when ``multiscale_mode=='range'``. + + Args: + img_scales (list[tuple]): Images scale range for sampling. + There must be two tuples in img_scales, which specify the lower + and upper bound of image scales. + + Returns: + (tuple, None): Returns a tuple ``(img_scale, None)``, where + ``img_scale`` is sampled scale and None is just a placeholder + to be consistent with :func:`random_select`. + """ + + assert mmcv.is_list_of(img_scales, tuple) and len(img_scales) == 2 + img_scale_long = [max(s) for s in img_scales] + img_scale_short = [min(s) for s in img_scales] + long_edge = np.random.randint( + min(img_scale_long), + max(img_scale_long) + 1) + short_edge = np.random.randint( + min(img_scale_short), + max(img_scale_short) + 1) + img_scale = (long_edge, short_edge) + return img_scale, None + + @staticmethod + def random_sample_ratio(img_scale, ratio_range): + """Randomly sample an img_scale when ``ratio_range`` is specified. + + A ratio will be randomly sampled from the range specified by + ``ratio_range``. Then it would be multiplied with ``img_scale`` to + generate sampled scale. + + Args: + img_scale (tuple): Images scale base to multiply with ratio. + ratio_range (tuple[float]): The minimum and maximum ratio to scale + the ``img_scale``. + + Returns: + (tuple, None): Returns a tuple ``(scale, None)``, where + ``scale`` is sampled ratio multiplied with ``img_scale`` and + None is just a placeholder to be consistent with + :func:`random_select`. + """ + + assert isinstance(img_scale, tuple) and len(img_scale) == 2 + min_ratio, max_ratio = ratio_range + assert min_ratio <= max_ratio + ratio = np.random.random_sample() * (max_ratio - min_ratio) + min_ratio + scale = int(img_scale[0] * ratio), int(img_scale[1] * ratio) + return scale, None + + def _random_scale(self, results): + """Randomly sample an img_scale according to ``ratio_range`` and + ``multiscale_mode``. + + If ``ratio_range`` is specified, a ratio will be sampled and be + multiplied with ``img_scale``. + If multiple scales are specified by ``img_scale``, a scale will be + sampled according to ``multiscale_mode``. + Otherwise, single scale will be used. + + Args: + results (dict): Result dict from :obj:`dataset`. + + Returns: + dict: Two new keys 'scale` and 'scale_idx` are added into + ``results``, which would be used by subsequent pipelines. + """ + + if self.ratio_range is not None: + if self.img_scale is None: + h, w = results['img'].shape[:2] + scale, scale_idx = self.random_sample_ratio((w, h), + self.ratio_range) + else: + scale, scale_idx = self.random_sample_ratio( + self.img_scale[0], self.ratio_range) + elif len(self.img_scale) == 1: + scale, scale_idx = self.img_scale[0], 0 + elif self.multiscale_mode == 'range': + scale, scale_idx = self.random_sample(self.img_scale) + elif self.multiscale_mode == 'value': + scale, scale_idx = self.random_select(self.img_scale) + else: + raise NotImplementedError + + results['scale'] = scale + results['scale_idx'] = scale_idx + + def _resize_img(self, results): + """Resize images with ``results['scale']``.""" + if self.keep_ratio: + img, scale_factor = mmcv.imrescale( + results['img'], results['scale'], return_scale=True) + # the w_scale and h_scale has minor difference + # a real fix should be done in the mmcv.imrescale in the future + new_h, new_w = img.shape[:2] + h, w = results['img'].shape[:2] + w_scale = new_w / w + h_scale = new_h / h + else: + img, w_scale, h_scale = mmcv.imresize( + results['img'], results['scale'], return_scale=True) + scale_factor = np.array([w_scale, h_scale, w_scale, h_scale], + dtype=np.float32) + results['img'] = img + results['img_shape'] = img.shape + results['pad_shape'] = img.shape # in case that there is no padding + results['scale_factor'] = scale_factor + results['keep_ratio'] = self.keep_ratio + + def _resize_seg(self, results): + """Resize semantic segmentation map with ``results['scale']``.""" + for key in results.get('seg_fields', []): + if self.keep_ratio: + gt_seg = mmcv.imrescale( + results[key], results['scale'], interpolation='nearest') + else: + gt_seg = mmcv.imresize( + results[key], results['scale'], interpolation='nearest') + results[key] = gt_seg + + def __call__(self, results): + """Call function to resize images, bounding boxes, masks, semantic + segmentation map. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Resized results, 'img_shape', 'pad_shape', 'scale_factor', + 'keep_ratio' keys are added into result dict. + """ + + if 'scale' not in results: + self._random_scale(results) + self._resize_img(results) + self._resize_seg(results) + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += (f'(img_scale={self.img_scale}, ' + f'multiscale_mode={self.multiscale_mode}, ' + f'ratio_range={self.ratio_range}, ' + f'keep_ratio={self.keep_ratio})') + return repr_str + + +@PIPELINES.register_module() +class RandomFlip(object): + """Flip the image & seg. + + If the input dict contains the key "flip", then the flag will be used, + otherwise it will be randomly decided by a ratio specified in the init + method. + + Args: + prob (float, optional): The flipping probability. Default: None. + direction(str, optional): The flipping direction. Options are + 'horizontal' and 'vertical'. Default: 'horizontal'. + """ + + @deprecated_api_warning({'flip_ratio': 'prob'}, cls_name='RandomFlip') + def __init__(self, prob=None, direction='horizontal'): + self.prob = prob + self.direction = direction + if prob is not None: + assert prob >= 0 and prob <= 1 + assert direction in ['horizontal', 'vertical'] + + def __call__(self, results): + """Call function to flip bounding boxes, masks, semantic segmentation + maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Flipped results, 'flip', 'flip_direction' keys are added into + result dict. + """ + + if 'flip' not in results: + flip = True if np.random.rand() < self.prob else False + results['flip'] = flip + if 'flip_direction' not in results: + results['flip_direction'] = self.direction + if results['flip']: + # flip image + results['img'] = mmcv.imflip( + results['img'], direction=results['flip_direction']) + + # flip segs + for key in results.get('seg_fields', []): + # use copy() to make numpy stride positive + results[key] = mmcv.imflip( + results[key], direction=results['flip_direction']).copy() + return results + + def __repr__(self): + return self.__class__.__name__ + f'(prob={self.prob})' + + +@PIPELINES.register_module() +class Pad(object): + """Pad the image & mask. + + There are two padding modes: (1) pad to a fixed size and (2) pad to the + minimum size that is divisible by some number. + Added keys are "pad_shape", "pad_fixed_size", "pad_size_divisor", + + Args: + 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. + """ + + def __init__(self, + size=None, + size_divisor=None, + pad_val=0, + seg_pad_val=255): + self.size = size + self.size_divisor = size_divisor + self.pad_val = pad_val + self.seg_pad_val = seg_pad_val + # only one of size and size_divisor should be valid + assert size is not None or size_divisor is not None + assert size is None or size_divisor is None + + def _pad_img(self, results): + """Pad images according to ``self.size``.""" + if self.size is not None: + padded_img = mmcv.impad( + results['img'], shape=self.size, pad_val=self.pad_val) + elif self.size_divisor is not None: + padded_img = mmcv.impad_to_multiple( + results['img'], self.size_divisor, pad_val=self.pad_val) + results['img'] = padded_img + results['pad_shape'] = padded_img.shape + results['pad_fixed_size'] = self.size + results['pad_size_divisor'] = self.size_divisor + + def _pad_seg(self, results): + """Pad masks according to ``results['pad_shape']``.""" + for key in results.get('seg_fields', []): + results[key] = mmcv.impad( + results[key], + shape=results['pad_shape'][:2], + pad_val=self.seg_pad_val) + + def __call__(self, results): + """Call function to pad images, masks, 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'(size={self.size}, size_divisor={self.size_divisor}, ' \ + f'pad_val={self.pad_val})' + return repr_str + + +@PIPELINES.register_module() +class Normalize(object): + """Normalize the image. + + Added key is "img_norm_cfg". + + Args: + mean (sequence): Mean values of 3 channels. + std (sequence): Std values of 3 channels. + to_rgb (bool): Whether to convert the image from BGR to RGB, + default is true. + """ + + def __init__(self, mean, std, to_rgb=True): + self.mean = np.array(mean, dtype=np.float32) + self.std = np.array(std, dtype=np.float32) + self.to_rgb = to_rgb + + def __call__(self, results): + """Call function to normalize images. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Normalized results, 'img_norm_cfg' key is added into + result dict. + """ + + results['img'] = mmcv.imnormalize(results['img'], self.mean, self.std, + self.to_rgb) + results['img_norm_cfg'] = dict( + mean=self.mean, std=self.std, to_rgb=self.to_rgb) + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(mean={self.mean}, std={self.std}, to_rgb=' \ + f'{self.to_rgb})' + return repr_str + + +@PIPELINES.register_module() +class Rerange(object): + """Rerange the image pixel value. + + 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 __call__(self, results): + """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 + + +@PIPELINES.register_module() +class CLAHE(object): + """Use CLAHE method to process the image. + + See `ZUIDERVELD,K. Contrast Limited Adaptive Histogram Equalization[J]. + Graphics Gems, 1994:474-485.` for more information. + + 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 __call__(self, results): + """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 + + +@PIPELINES.register_module() +class RandomCrop(object): + """Random crop the image & seg. + + Args: + crop_size (tuple): Expected size after cropping, (h, w). + cat_max_ratio (float): The maximum ratio that single category could + occupy. + """ + + def __init__(self, crop_size, cat_max_ratio=1., ignore_index=255): + 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 + + def get_crop_bbox(self, img): + """Randomly get a crop bounding box.""" + 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 + + def crop(self, img, crop_bbox): + """Crop from ``img``""" + crop_y1, crop_y2, crop_x1, crop_x2 = crop_bbox + img = img[crop_y1:crop_y2, crop_x1:crop_x2, ...] + return img + + def __call__(self, results): + """Call 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.get_crop_bbox(img) + if self.cat_max_ratio < 1.: + # Repeat 10 times + for _ in range(10): + seg_temp = self.crop(results['gt_semantic_seg'], 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 = self.get_crop_bbox(img) + + # crop the image + img = self.crop(img, crop_bbox) + img_shape = img.shape + results['img'] = img + results['img_shape'] = img_shape + + # crop semantic seg + for key in results.get('seg_fields', []): + results[key] = self.crop(results[key], crop_bbox) + + return results + + def __repr__(self): + return self.__class__.__name__ + f'(crop_size={self.crop_size})' + + +@PIPELINES.register_module() +class RandomRotate(object): + """Rotate the image & seg. + + 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 + + def __call__(self, results): + """Call function to rotate image, semantic segmentation maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Rotated results. + """ + + rotate = True if np.random.rand() < self.prob else False + degree = np.random.uniform(min(*self.degree), max(*self.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 + + +@PIPELINES.register_module() +class RGB2Gray(object): + """Convert RGB image to grayscale image. + + 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 __call__(self, results): + """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 + + +@PIPELINES.register_module() +class AdjustGamma(object): + """Using gamma correction to process the image. + + 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 __call__(self, results): + """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})' + + +@PIPELINES.register_module() +class SegRescale(object): + """Rescale semantic segmentation maps. + + Args: + scale_factor (float): The scale factor of the final output. + """ + + def __init__(self, scale_factor=1): + self.scale_factor = scale_factor + + def __call__(self, results): + """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})' + + +@PIPELINES.register_module() +class PhotoMetricDistortion(object): + """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) + + 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=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=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, alpha=1, beta=0): + """Multiple with alpha and add beat with clip.""" + img = img.astype(np.float32) * alpha + beta + img = np.clip(img, 0, 255) + return img.astype(np.uint8) + + def brightness(self, img): + """Brightness distortion.""" + if random.randint(2): + return self.convert( + img, + beta=random.uniform(-self.brightness_delta, + self.brightness_delta)) + return img + + def contrast(self, img): + """Contrast distortion.""" + if random.randint(2): + return self.convert( + img, + alpha=random.uniform(self.contrast_lower, self.contrast_upper)) + return img + + def saturation(self, img): + """Saturation distortion.""" + 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): + """Hue distortion.""" + 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 __call__(self, results): + """Call 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 diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/stare.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/stare.py new file mode 100644 index 0000000..cbd14e0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/stare.py @@ -0,0 +1,27 @@ +import os.path as osp + +from .builder import DATASETS +from .custom import CustomDataset + + +@DATASETS.register_module() +class STAREDataset(CustomDataset): + """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'. + """ + + CLASSES = ('background', 'vessel') + + PALETTE = [[120, 120, 120], [6, 230, 230]] + + def __init__(self, **kwargs): + super(STAREDataset, self).__init__( + img_suffix='.png', + seg_map_suffix='.ah.png', + reduce_zero_label=False, + **kwargs) + assert osp.exists(self.img_dir) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/voc.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/voc.py new file mode 100644 index 0000000..a885520 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/datasets/voc.py @@ -0,0 +1,29 @@ +import os.path as osp + +from .builder import DATASETS +from .custom import CustomDataset + + +@DATASETS.register_module() +class PascalVOCDataset(CustomDataset): + """Pascal VOC dataset. + + Args: + split (str): Split txt file for Pascal VOC. + """ + + 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, split, **kwargs): + super(PascalVOCDataset, self).__init__( + img_suffix='.jpg', seg_map_suffix='.png', split=split, **kwargs) + assert osp.exists(self.img_dir) and self.split is not None diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/__init__.py new file mode 100644 index 0000000..3cf93f8 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/__init__.py @@ -0,0 +1,12 @@ +from .backbones import * # noqa: F401,F403 +from .builder import (BACKBONES, HEADS, LOSSES, SEGMENTORS, build_backbone, + build_head, build_loss, build_segmentor) +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 + +__all__ = [ + 'BACKBONES', 'HEADS', 'LOSSES', 'SEGMENTORS', 'build_backbone', + 'build_head', 'build_loss', 'build_segmentor' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/__init__.py new file mode 100644 index 0000000..8339983 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/__init__.py @@ -0,0 +1,17 @@ +from .cgnet import CGNet +# from .fast_scnn import FastSCNN +from .hrnet import HRNet +from .mobilenet_v2 import MobileNetV2 +from .mobilenet_v3 import MobileNetV3 +from .resnest import ResNeSt +from .resnet import ResNet, ResNetV1c, ResNetV1d +from .resnext import ResNeXt +from .unet import UNet +from .vit import VisionTransformer +from .uniformer import UniFormer + +__all__ = [ + 'ResNet', 'ResNetV1c', 'ResNetV1d', 'ResNeXt', 'HRNet', + 'ResNeSt', 'MobileNetV2', 'UNet', 'CGNet', 'MobileNetV3', + 'VisionTransformer', 'UniFormer' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/cgnet.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/cgnet.py new file mode 100644 index 0000000..f8bca44 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/cgnet.py @@ -0,0 +1,367 @@ +import torch +import torch.nn as nn +import torch.utils.checkpoint as cp +from annotator.uniformer.mmcv.cnn import (ConvModule, build_conv_layer, build_norm_layer, + constant_init, kaiming_init) +from annotator.uniformer.mmcv.runner import load_checkpoint +from annotator.uniformer.mmcv.utils.parrots_wrapper import _BatchNorm + +from annotator.uniformer.mmseg.utils import get_root_logger +from ..builder import BACKBONES + + +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(GlobalContextExtractor, self).__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(ContextGuidedBlock, self).__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(InputInjection, self).__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 + + +@BACKBONES.register_module() +class CGNet(nn.Module): + """CGNet backbone. + + A Light-weight Context Guided Network for Semantic Segmentation + arXiv: https://arxiv.org/abs/1811.08201 + + 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. + """ + + 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): + + super(CGNet, self).__init__() + 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 init_weights(self, pretrained=None): + """Initialize the weights in backbone. + + Args: + pretrained (str, optional): Path to pre-trained weights. + Defaults to None. + """ + if isinstance(pretrained, str): + logger = get_root_logger() + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + for m in self.modules(): + if isinstance(m, (nn.Conv2d, nn.Linear)): + kaiming_init(m) + elif isinstance(m, (_BatchNorm, nn.GroupNorm)): + constant_init(m, 1) + elif isinstance(m, nn.PReLU): + constant_init(m, 0) + else: + raise TypeError('pretrained must be a str or None') + + def train(self, mode=True): + """Convert the model into training mode will keeping the normalization + layer freezed.""" + super(CGNet, self).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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/fast_scnn.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/fast_scnn.py new file mode 100644 index 0000000..38c2350 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/fast_scnn.py @@ -0,0 +1,375 @@ +import torch +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import (ConvModule, DepthwiseSeparableConvModule, constant_init, + kaiming_init) +from torch.nn.modules.batchnorm import _BatchNorm + +from annotator.uniformer.mmseg.models.decode_heads.psp_head import PPM +from annotator.uniformer.mmseg.ops import resize +from ..builder import BACKBONES +from ..utils.inverted_residual import InvertedResidual + + +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') + """ + + def __init__(self, + in_channels, + dw_channels, + out_channels, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU')): + super(LearningToDownsample, self).__init__() + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + dw_channels1 = dw_channels[0] + dw_channels2 = dw_channels[1] + + self.conv = ConvModule( + in_channels, + dw_channels1, + 3, + stride=2, + 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) + self.dsconv2 = DepthwiseSeparableConvModule( + dw_channels2, + out_channels, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_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(GlobalFeatureExtractor, self).__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, + 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) + ] + for i in range(1, blocks): + layers.append( + InvertedResidual( + out_channels, + out_channels, + 1, + expand_ratio, + norm_cfg=self.norm_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') + 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, + higher_in_channels, + lower_in_channels, + out_channels, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + align_corners=False): + super(FeatureFusionModule, self).__init__() + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.align_corners = align_corners + self.dwconv = ConvModule( + lower_in_channels, + out_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.conv_lower_res = ConvModule( + out_channels, + out_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=None) + self.conv_higher_res = ConvModule( + higher_in_channels, + out_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=None) + 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) + + +@BACKBONES.register_module() +class FastSCNN(nn.Module): + """Fast-SCNN Backbone. + + 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 + """ + + 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): + + super(FastSCNN, self).__init__() + 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) + 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, + act_cfg=self.act_cfg, + align_corners=self.align_corners) + + def init_weights(self, pretrained=None): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, (_BatchNorm, nn.GroupNorm)): + constant_init(m, 1) + + 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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/hrnet.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/hrnet.py new file mode 100644 index 0000000..331ebf3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/hrnet.py @@ -0,0 +1,555 @@ +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import (build_conv_layer, build_norm_layer, constant_init, + kaiming_init) +from annotator.uniformer.mmcv.runner import load_checkpoint +from annotator.uniformer.mmcv.utils.parrots_wrapper import _BatchNorm + +from annotator.uniformer.mmseg.ops import Upsample, resize +from annotator.uniformer.mmseg.utils import get_root_logger +from ..builder import BACKBONES +from .resnet import BasicBlock, Bottleneck + + +class HRModule(nn.Module): + """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)): + super(HRModule, self).__init__() + 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)) + 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)) + + return nn.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 nn.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 + + +@BACKBONES.register_module() +class HRNet(nn.Module): + """HRNet backbone. + + High-Resolution Representations for Labeling Pixels and Regions + arXiv: https://arxiv.org/abs/1904.04514 + + Args: + extra (dict): detailed configuration for each stage of HRNet. + in_channels (int): Number of input image channels. Normally 3. + conv_cfg (dict): dictionary to construct and config conv layer. + 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 annotator.uniformer.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, + zero_init_residual=False): + super(HRNet, self).__init__() + self.extra = extra + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.norm_eval = norm_eval + self.with_cp = with_cp + self.zero_init_residual = zero_init_residual + + # 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) + + @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 = [] + layers.append( + block( + inplanes, + planes, + stride, + downsample=downsample, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_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)) + + return nn.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 = [] + 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)) + + return nn.Sequential(*hr_modules), in_channels + + def init_weights(self, pretrained=None): + """Initialize the weights in backbone. + + Args: + pretrained (str, optional): Path to pre-trained weights. + Defaults to None. + """ + if isinstance(pretrained, str): + logger = get_root_logger() + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, (_BatchNorm, nn.GroupNorm)): + constant_init(m, 1) + + if self.zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + constant_init(m.norm3, 0) + elif isinstance(m, BasicBlock): + constant_init(m.norm2, 0) + else: + raise TypeError('pretrained must be a str or None') + + 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(HRNet, self).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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/mobilenet_v2.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/mobilenet_v2.py new file mode 100644 index 0000000..ab6b379 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/mobilenet_v2.py @@ -0,0 +1,180 @@ +import logging + +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule, constant_init, kaiming_init +from annotator.uniformer.mmcv.runner import load_checkpoint +from torch.nn.modules.batchnorm import _BatchNorm + +from ..builder import BACKBONES +from ..utils import InvertedResidual, make_divisible + + +@BACKBONES.register_module() +class MobileNetV2(nn.Module): + """MobileNetV2 backbone. + + 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. + """ + + # 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): + super(MobileNetV2, self).__init__() + 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, 8). 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 init_weights(self, pretrained=None): + if isinstance(pretrained, str): + logger = logging.getLogger() + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, (_BatchNorm, nn.GroupNorm)): + constant_init(m, 1) + else: + raise TypeError('pretrained must be a str or None') + + 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(MobileNetV2, self).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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/mobilenet_v3.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/mobilenet_v3.py new file mode 100644 index 0000000..1681740 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/mobilenet_v3.py @@ -0,0 +1,255 @@ +import logging + +import annotator.uniformer.mmcv as mmcv +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule, constant_init, kaiming_init +from annotator.uniformer.mmcv.cnn.bricks import Conv2dAdaptivePadding +from annotator.uniformer.mmcv.runner import load_checkpoint +from torch.nn.modules.batchnorm import _BatchNorm + +from ..builder import BACKBONES +from ..utils import InvertedResidualV3 as InvertedResidual + + +@BACKBONES.register_module() +class MobileNetV3(nn.Module): + """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. + """ + # 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): + super(MobileNetV3, self).__init__() + assert arch in self.arch_settings + assert isinstance(reduction_factor, int) and reduction_factor > 0 + assert mmcv.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 = 'layer{}'.format(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 = 'layer{}'.format(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 init_weights(self, pretrained=None): + if isinstance(pretrained, str): + logger = logging.getLogger() + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, nn.BatchNorm2d): + constant_init(m, 1) + else: + raise TypeError('pretrained must be a str or None') + + 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(MobileNetV3, self).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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnest.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnest.py new file mode 100644 index 0000000..b45a837 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnest.py @@ -0,0 +1,314 @@ +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as cp +from annotator.uniformer.mmcv.cnn import build_conv_layer, build_norm_layer + +from ..builder import BACKBONES +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(SplitAttentionConv2d, self).__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(Bottleneck, self).__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 + + +@BACKBONES.register_module() +class ResNeSt(ResNetV1d): + """ResNeSt backbone. + + 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(ResNeSt, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnet.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnet.py new file mode 100644 index 0000000..4e52bf0 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnet.py @@ -0,0 +1,688 @@ +import torch.nn as nn +import torch.utils.checkpoint as cp +from annotator.uniformer.mmcv.cnn import (build_conv_layer, build_norm_layer, build_plugin_layer, + constant_init, kaiming_init) +from annotator.uniformer.mmcv.runner import load_checkpoint +from annotator.uniformer.mmcv.utils.parrots_wrapper import _BatchNorm + +from annotator.uniformer.mmseg.utils import get_root_logger +from ..builder import BACKBONES +from ..utils import ResLayer + + +class BasicBlock(nn.Module): + """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): + super(BasicBlock, self).__init__() + 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(nn.Module): + """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): + super(Bottleneck, self).__init__() + 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 + + +@BACKBONES.register_module() +class ResNet(nn.Module): + """ResNet backbone. + + 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. + 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. + deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv + avg_down (bool): Use AvgPool instead of stride conv when + downsampling in the bottleneck. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + -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. + 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' + 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. + zero_init_residual (bool): Whether to use zero init for last norm layer + in resblocks to let them behave as identity. + + Example: + >>> from annotator.uniformer.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): + super(ResNet, self).__init__() + if depth not in self.arch_settings: + raise KeyError(f'invalid depth {depth} for resnet') + 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.zero_init_residual = zero_init_residual + 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) + 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 init_weights(self, pretrained=None): + """Initialize the weights in backbone. + + Args: + pretrained (str, optional): Path to pre-trained weights. + Defaults to None. + """ + if isinstance(pretrained, str): + logger = get_root_logger() + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, (_BatchNorm, nn.GroupNorm)): + constant_init(m, 1) + + if self.dcn is not None: + for m in self.modules(): + if isinstance(m, Bottleneck) and hasattr( + m, 'conv2_offset'): + constant_init(m.conv2_offset, 0) + + if self.zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + constant_init(m.norm3, 0) + elif isinstance(m, BasicBlock): + constant_init(m.norm2, 0) + else: + raise TypeError('pretrained must be a str or None') + + 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(ResNet, self).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() + + +@BACKBONES.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. + + References: + .. [1] https://arxiv.org/pdf/1812.01187.pdf + """ + + def __init__(self, **kwargs): + super(ResNetV1c, self).__init__( + deep_stem=True, avg_down=False, **kwargs) + + +@BACKBONES.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(ResNetV1d, self).__init__( + deep_stem=True, avg_down=True, **kwargs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnext.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnext.py new file mode 100644 index 0000000..962249a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/resnext.py @@ -0,0 +1,145 @@ +import math + +from annotator.uniformer.mmcv.cnn import build_conv_layer, build_norm_layer + +from ..builder import BACKBONES +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(Bottleneck, self).__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) + + +@BACKBONES.register_module() +class ResNeXt(ResNet): + """ResNeXt backbone. + + 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 annotator.uniformer.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(ResNeXt, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/unet.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/unet.py new file mode 100644 index 0000000..82caa16 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/unet.py @@ -0,0 +1,429 @@ +import torch.nn as nn +import torch.utils.checkpoint as cp +from annotator.uniformer.mmcv.cnn import (UPSAMPLE_LAYERS, ConvModule, build_activation_layer, + build_norm_layer, constant_init, kaiming_init) +from annotator.uniformer.mmcv.runner import load_checkpoint +from annotator.uniformer.mmcv.utils.parrots_wrapper import _BatchNorm + +from annotator.uniformer.mmseg.utils import get_root_logger +from ..builder import BACKBONES +from ..utils import UpConvBlock + + +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(BasicConvBlock, self).__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 + + +@UPSAMPLE_LAYERS.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(DeconvModule, self).__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 + + +@UPSAMPLE_LAYERS.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(InterpConv, self).__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 = nn.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 + + +@BACKBONES.register_module() +class UNet(nn.Module): + """UNet backbone. + U-Net: Convolutional Networks for Biomedical Image Segmentation. + https://arxiv.org/pdf/1505.04597.pdf + + 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. + + 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): + super(UNet, self).__init__() + 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(UNet, self).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}.' + + def init_weights(self, pretrained=None): + """Initialize the weights in backbone. + + Args: + pretrained (str, optional): Path to pre-trained weights. + Defaults to None. + """ + if isinstance(pretrained, str): + logger = get_root_logger() + load_checkpoint(self, pretrained, strict=False, logger=logger) + elif pretrained is None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, (_BatchNorm, nn.GroupNorm)): + constant_init(m, 1) + else: + raise TypeError('pretrained must be a str or None') diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/uniformer.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/uniformer.py new file mode 100644 index 0000000..0c4bb88 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/uniformer.py @@ -0,0 +1,422 @@ +# -------------------------------------------------------- +# UniFormer +# Copyright (c) 2022 SenseTime X-Lab +# Licensed under The MIT License [see LICENSE for details] +# Written by Kunchang Li +# -------------------------------------------------------- + +from collections import OrderedDict +import math + +from functools import partial +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as checkpoint +import numpy as np +from timm.models.layers import DropPath, to_2tuple, trunc_normal_ + +from annotator.uniformer.mmcv_custom import load_checkpoint +from annotator.uniformer.mmseg.utils import get_root_logger +from ..builder import BACKBONES + + +class Mlp(nn.Module): + def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = nn.Linear(in_features, hidden_features) + self.act = act_layer() + self.fc2 = nn.Linear(hidden_features, out_features) + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class CMlp(nn.Module): + def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.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.act = act_layer() + self.fc2 = nn.Conv2d(hidden_features, out_features, 1) + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class CBlock(nn.Module): + def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0., + drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm): + super().__init__() + self.pos_embed = nn.Conv2d(dim, dim, 3, padding=1, groups=dim) + self.norm1 = nn.BatchNorm2d(dim) + self.conv1 = nn.Conv2d(dim, dim, 1) + self.conv2 = nn.Conv2d(dim, dim, 1) + self.attn = nn.Conv2d(dim, dim, 5, padding=2, groups=dim) + # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here + self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity() + self.norm2 = nn.BatchNorm2d(dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp = CMlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop) + + def forward(self, x): + x = x + self.pos_embed(x) + x = x + self.drop_path(self.conv2(self.attn(self.conv1(self.norm1(x))))) + x = x + self.drop_path(self.mlp(self.norm2(x))) + return x + + +class Attention(nn.Module): + def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.): + super().__init__() + self.num_heads = num_heads + head_dim = dim // num_heads + # NOTE scale factor was wrong in my original version, can set manually to be compat with prev weights + self.scale = qk_scale or head_dim ** -0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = nn.Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + + def forward(self, x): + 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) + q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple) + + attn = (q @ k.transpose(-2, -1)) * self.scale + 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 SABlock(nn.Module): + def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0., + drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm): + super().__init__() + self.pos_embed = nn.Conv2d(dim, dim, 3, padding=1, groups=dim) + self.norm1 = norm_layer(dim) + self.attn = Attention( + dim, + num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale, + attn_drop=attn_drop, proj_drop=drop) + # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here + self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity() + self.norm2 = norm_layer(dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop) + + def forward(self, x): + x = x + self.pos_embed(x) + B, N, H, W = x.shape + x = x.flatten(2).transpose(1, 2) + x = x + self.drop_path(self.attn(self.norm1(x))) + x = x + self.drop_path(self.mlp(self.norm2(x))) + x = x.transpose(1, 2).reshape(B, N, H, W) + return x + + +def window_partition(x, window_size): + """ + Args: + x: (B, H, W, C) + window_size (int): window size + Returns: + windows: (num_windows*B, window_size, window_size, C) + """ + B, H, W, C = x.shape + 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().view(-1, window_size, window_size, C) + return windows + + +def window_reverse(windows, window_size, H, W): + """ + Args: + windows: (num_windows*B, window_size, window_size, C) + window_size (int): Window size + H (int): Height of image + W (int): Width of image + Returns: + x: (B, H, W, C) + """ + 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 + + +class SABlock_Windows(nn.Module): + def __init__(self, dim, num_heads, window_size=14, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0., + drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm): + super().__init__() + self.window_size=window_size + self.pos_embed = nn.Conv2d(dim, dim, 3, padding=1, groups=dim) + self.norm1 = norm_layer(dim) + self.attn = Attention( + dim, + num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale, + attn_drop=attn_drop, proj_drop=drop) + # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here + self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity() + self.norm2 = norm_layer(dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop) + + def forward(self, x): + x = x + self.pos_embed(x) + x = x.permute(0, 2, 3, 1) + B, H, W, C = x.shape + shortcut = x + x = self.norm1(x) + + 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)) + _, Hp, Wp, _ = x.shape + + x_windows = window_partition(x, self.window_size) # nW*B, window_size, window_size, C + x_windows = x_windows.view(-1, self.window_size * self.window_size, C) # nW*B, window_size*window_size, C + + # W-MSA/SW-MSA + attn_windows = self.attn(x_windows) # nW*B, window_size*window_size, C + + # merge windows + attn_windows = attn_windows.view(-1, self.window_size, self.window_size, C) + x = window_reverse(attn_windows, self.window_size, Hp, Wp) # B H' W' C + + # reverse cyclic shift + if pad_r > 0 or pad_b > 0: + x = x[:, :H, :W, :].contiguous() + + x = shortcut + self.drop_path(x) + x = x + self.drop_path(self.mlp(self.norm2(x))) + x = x.permute(0, 3, 1, 2).reshape(B, C, H, W) + return x + + +class PatchEmbed(nn.Module): + """ Image to Patch Embedding + """ + def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768): + super().__init__() + img_size = to_2tuple(img_size) + patch_size = to_2tuple(patch_size) + num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0]) + self.img_size = img_size + self.patch_size = patch_size + self.num_patches = num_patches + self.norm = nn.LayerNorm(embed_dim) + self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) + + def forward(self, x): + B, _, H, W = x.shape + x = self.proj(x) + B, _, H, W = x.shape + x = x.flatten(2).transpose(1, 2) + x = self.norm(x) + x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous() + return x + + +@BACKBONES.register_module() +class UniFormer(nn.Module): + """ Vision Transformer + A PyTorch impl of : `An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale` - + https://arxiv.org/abs/2010.11929 + """ + def __init__(self, layers=[3, 4, 8, 3], img_size=224, in_chans=3, num_classes=80, embed_dim=[64, 128, 320, 512], + head_dim=64, mlp_ratio=4., qkv_bias=True, qk_scale=None, representation_size=None, + drop_rate=0., attn_drop_rate=0., drop_path_rate=0., norm_layer=partial(nn.LayerNorm, eps=1e-6), + pretrained_path=None, use_checkpoint=False, checkpoint_num=[0, 0, 0, 0], + windows=False, hybrid=False, window_size=14): + """ + Args: + layer (list): number of block in each layer + img_size (int, tuple): input image size + in_chans (int): number of input channels + num_classes (int): number of classes for classification head + embed_dim (int): embedding dimension + head_dim (int): dimension of attention heads + mlp_ratio (int): ratio of mlp hidden dim to embedding dim + qkv_bias (bool): enable bias for qkv if True + qk_scale (float): override default qk scale of head_dim ** -0.5 if set + representation_size (Optional[int]): enable and set representation layer (pre-logits) to this value if set + drop_rate (float): dropout rate + attn_drop_rate (float): attention dropout rate + drop_path_rate (float): stochastic depth rate + norm_layer (nn.Module): normalization layer + pretrained_path (str): path of pretrained model + use_checkpoint (bool): whether use checkpoint + checkpoint_num (list): index for using checkpoint in every stage + windows (bool): whether use window MHRA + hybrid (bool): whether use hybrid MHRA + window_size (int): size of window (>14) + """ + super().__init__() + self.num_classes = num_classes + self.use_checkpoint = use_checkpoint + self.checkpoint_num = checkpoint_num + self.windows = windows + print(f'Use Checkpoint: {self.use_checkpoint}') + print(f'Checkpoint Number: {self.checkpoint_num}') + self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models + norm_layer = norm_layer or partial(nn.LayerNorm, eps=1e-6) + + self.patch_embed1 = PatchEmbed( + img_size=img_size, patch_size=4, in_chans=in_chans, embed_dim=embed_dim[0]) + self.patch_embed2 = PatchEmbed( + img_size=img_size // 4, patch_size=2, in_chans=embed_dim[0], embed_dim=embed_dim[1]) + self.patch_embed3 = PatchEmbed( + img_size=img_size // 8, patch_size=2, in_chans=embed_dim[1], embed_dim=embed_dim[2]) + self.patch_embed4 = PatchEmbed( + img_size=img_size // 16, patch_size=2, in_chans=embed_dim[2], embed_dim=embed_dim[3]) + + self.pos_drop = nn.Dropout(p=drop_rate) + dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(layers))] # stochastic depth decay rule + num_heads = [dim // head_dim for dim in embed_dim] + self.blocks1 = nn.ModuleList([ + CBlock( + dim=embed_dim[0], num_heads=num_heads[0], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer) + for i in range(layers[0])]) + self.norm1=norm_layer(embed_dim[0]) + self.blocks2 = nn.ModuleList([ + CBlock( + dim=embed_dim[1], num_heads=num_heads[1], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]], norm_layer=norm_layer) + for i in range(layers[1])]) + self.norm2 = norm_layer(embed_dim[1]) + if self.windows: + print('Use local window for all blocks in stage3') + self.blocks3 = nn.ModuleList([ + SABlock_Windows( + dim=embed_dim[2], num_heads=num_heads[2], window_size=window_size, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]], norm_layer=norm_layer) + for i in range(layers[2])]) + elif hybrid: + print('Use hybrid window for blocks in stage3') + block3 = [] + for i in range(layers[2]): + if (i + 1) % 4 == 0: + block3.append(SABlock( + dim=embed_dim[2], num_heads=num_heads[2], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]], norm_layer=norm_layer)) + else: + block3.append(SABlock_Windows( + dim=embed_dim[2], num_heads=num_heads[2], window_size=window_size, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]], norm_layer=norm_layer)) + self.blocks3 = nn.ModuleList(block3) + else: + print('Use global window for all blocks in stage3') + self.blocks3 = nn.ModuleList([ + SABlock( + dim=embed_dim[2], num_heads=num_heads[2], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]], norm_layer=norm_layer) + for i in range(layers[2])]) + self.norm3 = norm_layer(embed_dim[2]) + self.blocks4 = nn.ModuleList([ + SABlock( + dim=embed_dim[3], num_heads=num_heads[3], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i+layers[0]+layers[1]+layers[2]], norm_layer=norm_layer) + for i in range(layers[3])]) + self.norm4 = norm_layer(embed_dim[3]) + + # Representation layer + if representation_size: + self.num_features = representation_size + self.pre_logits = nn.Sequential(OrderedDict([ + ('fc', nn.Linear(embed_dim, representation_size)), + ('act', nn.Tanh()) + ])) + else: + self.pre_logits = nn.Identity() + + self.apply(self._init_weights) + self.init_weights(pretrained=pretrained_path) + + def init_weights(self, pretrained): + if isinstance(pretrained, str): + logger = get_root_logger() + load_checkpoint(self, pretrained, map_location='cpu', strict=False, logger=logger) + print(f'Load pretrained model from {pretrained}') + def _init_weights(self, 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) + + @torch.jit.ignore + def no_weight_decay(self): + return {'pos_embed', 'cls_token'} + + def get_classifier(self): + return self.head + + def reset_classifier(self, num_classes, global_pool=''): + self.num_classes = num_classes + self.head = nn.Linear(self.embed_dim, num_classes) if num_classes > 0 else nn.Identity() + + def forward_features(self, x): + out = [] + x = self.patch_embed1(x) + x = self.pos_drop(x) + for i, blk in enumerate(self.blocks1): + if self.use_checkpoint and i < self.checkpoint_num[0]: + x = checkpoint.checkpoint(blk, x) + else: + x = blk(x) + x_out = self.norm1(x.permute(0, 2, 3, 1)) + out.append(x_out.permute(0, 3, 1, 2).contiguous()) + x = self.patch_embed2(x) + for i, blk in enumerate(self.blocks2): + if self.use_checkpoint and i < self.checkpoint_num[1]: + x = checkpoint.checkpoint(blk, x) + else: + x = blk(x) + x_out = self.norm2(x.permute(0, 2, 3, 1)) + out.append(x_out.permute(0, 3, 1, 2).contiguous()) + x = self.patch_embed3(x) + for i, blk in enumerate(self.blocks3): + if self.use_checkpoint and i < self.checkpoint_num[2]: + x = checkpoint.checkpoint(blk, x) + else: + x = blk(x) + x_out = self.norm3(x.permute(0, 2, 3, 1)) + out.append(x_out.permute(0, 3, 1, 2).contiguous()) + x = self.patch_embed4(x) + for i, blk in enumerate(self.blocks4): + if self.use_checkpoint and i < self.checkpoint_num[3]: + x = checkpoint.checkpoint(blk, x) + else: + x = blk(x) + x_out = self.norm4(x.permute(0, 2, 3, 1)) + out.append(x_out.permute(0, 3, 1, 2).contiguous()) + return tuple(out) + + def forward(self, x): + x = self.forward_features(x) + return x diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/vit.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/vit.py new file mode 100644 index 0000000..59e4479 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/backbones/vit.py @@ -0,0 +1,459 @@ +"""Modified from https://github.com/rwightman/pytorch-image- +models/blob/master/timm/models/vision_transformer.py.""" + +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as cp +from annotator.uniformer.mmcv.cnn import (Conv2d, Linear, build_activation_layer, build_norm_layer, + constant_init, kaiming_init, normal_init) +from annotator.uniformer.mmcv.runner import _load_checkpoint +from annotator.uniformer.mmcv.utils.parrots_wrapper import _BatchNorm + +from annotator.uniformer.mmseg.utils import get_root_logger +from ..builder import BACKBONES +from ..utils import DropPath, trunc_normal_ + + +class Mlp(nn.Module): + """MLP layer for Encoder block. + + Args: + in_features(int): Input dimension for the first fully + connected layer. + hidden_features(int): Output dimension for the first fully + connected layer. + out_features(int): Output dementsion for the second fully + connected layer. + act_cfg(dict): Config dict for activation layer. + Default: dict(type='GELU'). + drop(float): Drop rate for the dropout layer. Dropout rate has + to be between 0 and 1. Default: 0. + """ + + def __init__(self, + in_features, + hidden_features=None, + out_features=None, + act_cfg=dict(type='GELU'), + drop=0.): + super(Mlp, self).__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = Linear(in_features, hidden_features) + self.act = build_activation_layer(act_cfg) + self.fc2 = Linear(hidden_features, out_features) + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class Attention(nn.Module): + """Attention layer for Encoder block. + + Args: + dim (int): Dimension for the input vector. + num_heads (int): Number of parallel attention heads. + qkv_bias (bool): Enable bias for qkv if True. Default: False. + qk_scale (float): Override default qk scale of head_dim ** -0.5 if set. + attn_drop (float): Drop rate for attention output weights. + Default: 0. + proj_drop (float): Drop rate for output weights. Default: 0. + """ + + def __init__(self, + dim, + num_heads=8, + qkv_bias=False, + qk_scale=None, + attn_drop=0., + proj_drop=0.): + super(Attention, self).__init__() + self.num_heads = num_heads + head_dim = dim // num_heads + self.scale = qk_scale or head_dim**-0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + + def forward(self, x): + 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) + q, k, v = qkv[0], qkv[1], qkv[2] + + attn = (q @ k.transpose(-2, -1)) * self.scale + 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 Block(nn.Module): + """Implements encoder block with residual connection. + + Args: + dim (int): The feature dimension. + num_heads (int): Number of parallel attention heads. + mlp_ratio (int): Ratio of mlp hidden dim to embedding dim. + qk_scale (float): Override default qk scale of head_dim ** -0.5 if set. + drop (float): Drop rate for mlp output weights. Default: 0. + attn_drop (float): Drop rate for attention output weights. + Default: 0. + proj_drop (float): Drop rate for attn layer output weights. + Default: 0. + drop_path (float): Drop rate for paths of model. + Default: 0. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN', requires_grad=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, + dim, + num_heads, + mlp_ratio=4, + qkv_bias=False, + qk_scale=None, + drop=0., + attn_drop=0., + proj_drop=0., + drop_path=0., + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN', eps=1e-6), + with_cp=False): + super(Block, self).__init__() + self.with_cp = with_cp + _, self.norm1 = build_norm_layer(norm_cfg, dim) + self.attn = Attention(dim, num_heads, qkv_bias, qk_scale, attn_drop, + proj_drop) + self.drop_path = DropPath( + drop_path) if drop_path > 0. else nn.Identity() + _, self.norm2 = build_norm_layer(norm_cfg, dim) + 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, x): + + def _inner_forward(x): + out = x + self.drop_path(self.attn(self.norm1(x))) + out = out + self.drop_path(self.mlp(self.norm2(out))) + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + return out + + +class PatchEmbed(nn.Module): + """Image to Patch Embedding. + + Args: + img_size (int | tuple): Input image size. + default: 224. + patch_size (int): Width and height for a patch. + default: 16. + in_channels (int): Input channels for images. Default: 3. + embed_dim (int): The embedding dimension. Default: 768. + """ + + def __init__(self, + img_size=224, + patch_size=16, + in_channels=3, + embed_dim=768): + super(PatchEmbed, self).__init__() + if isinstance(img_size, int): + self.img_size = (img_size, img_size) + elif isinstance(img_size, tuple): + self.img_size = img_size + else: + raise TypeError('img_size must be type of int or tuple') + h, w = self.img_size + self.patch_size = (patch_size, patch_size) + self.num_patches = (h // patch_size) * (w // patch_size) + self.proj = Conv2d( + in_channels, embed_dim, kernel_size=patch_size, stride=patch_size) + + def forward(self, x): + return self.proj(x).flatten(2).transpose(1, 2) + + +@BACKBONES.register_module() +class VisionTransformer(nn.Module): + """Vision transformer backbone. + + A PyTorch impl of : `An Image is Worth 16x16 Words: Transformers for + Image Recognition at Scale` - https://arxiv.org/abs/2010.11929 + + Args: + img_size (tuple): input image size. Default: (224, 224). + patch_size (int, tuple): patch size. Default: 16. + in_channels (int): number of input channels. Default: 3. + embed_dim (int): embedding dimension. Default: 768. + depth (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. + qkv_bias (bool): enable bias for qkv if True. Default: True. + qk_scale (float): override default qk scale of head_dim ** -0.5 if set. + drop_rate (float): dropout rate. Default: 0. + attn_drop_rate (float): attention dropout rate. Default: 0. + drop_path_rate (float): Rate of DropPath. Default: 0. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN', eps=1e-6, requires_grad=True). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='GELU'). + 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. + 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. + with_cls_token (bool): If concatenating class token into image tokens + as transformer input. 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, + img_size=(224, 224), + patch_size=16, + in_channels=3, + embed_dim=768, + depth=12, + num_heads=12, + mlp_ratio=4, + out_indices=11, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + norm_cfg=dict(type='LN', eps=1e-6, requires_grad=True), + act_cfg=dict(type='GELU'), + norm_eval=False, + final_norm=False, + with_cls_token=True, + interpolate_mode='bicubic', + with_cp=False): + super(VisionTransformer, self).__init__() + self.img_size = img_size + self.patch_size = patch_size + self.features = self.embed_dim = embed_dim + self.patch_embed = PatchEmbed( + img_size=img_size, + patch_size=patch_size, + in_channels=in_channels, + embed_dim=embed_dim) + + self.with_cls_token = with_cls_token + self.cls_token = nn.Parameter(torch.zeros(1, 1, self.embed_dim)) + self.pos_embed = nn.Parameter( + torch.zeros(1, self.patch_embed.num_patches + 1, embed_dim)) + self.pos_drop = nn.Dropout(p=drop_rate) + + if isinstance(out_indices, int): + 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, depth) + ] # stochastic depth decay rule + self.blocks = nn.ModuleList([ + Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=dpr[i], + attn_drop=attn_drop_rate, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + with_cp=with_cp) for i in range(depth) + ]) + + self.interpolate_mode = interpolate_mode + self.final_norm = final_norm + if final_norm: + _, self.norm = build_norm_layer(norm_cfg, embed_dim) + + self.norm_eval = norm_eval + self.with_cp = with_cp + + def init_weights(self, pretrained=None): + if isinstance(pretrained, str): + logger = get_root_logger() + checkpoint = _load_checkpoint(pretrained, logger=logger) + 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: + logger.info(msg=f'Resize the pos_embed shape from \ +{state_dict["pos_embed"].shape} to {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, w), (pos_size, pos_size), + self.patch_size, self.interpolate_mode) + + self.load_state_dict(state_dict, False) + + elif pretrained is None: + # 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, Linear): + trunc_normal_(m.weight, std=.02) + if m.bias is not None: + if 'mlp' in n: + normal_init(m.bias, std=1e-6) + else: + constant_init(m.bias, 0) + elif isinstance(m, Conv2d): + kaiming_init(m.weight, mode='fan_in') + if m.bias is not None: + constant_init(m.bias, 0) + elif isinstance(m, (_BatchNorm, nn.GroupNorm, nn.LayerNorm)): + constant_init(m.bias, 0) + constant_init(m.weight, 1.0) + else: + raise TypeError('pretrained must be a str or None') + + def _pos_embeding(self, img, patched_img, pos_embed): + """Positiong embeding method. + + Resize the pos_embed, if the input image size doesn't match + the training size. + Args: + img (torch.Tensor): The inference image tensor, the shape + must be [B, C, H, W]. + patched_img (torch.Tensor): The patched image, it should be + shape of [B, L1, C]. + 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, img.shape[2:], + (pos_h, pos_w), self.patch_size, + self.interpolate_mode) + return self.pos_drop(patched_img + pos_embed) + + @staticmethod + def resize_pos_embed(pos_embed, input_shpae, pos_shape, patch_size, mode): + """Resize pos_embed weights. + + Resize pos_embed using bicubic interpolate method. + Args: + pos_embed (torch.Tensor): pos_embed weights. + input_shpae (tuple): Tuple for (input_h, intput_w). + pos_shape (tuple): Tuple for (pos_h, pos_w). + patch_size (int): Patch size. + 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]' + input_h, input_w = input_shpae + 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 = F.interpolate( + pos_embed_weight, + size=[input_h // patch_size, input_w // patch_size], + 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 = self.patch_embed(inputs) + + cls_tokens = self.cls_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, x), dim=1) + x = self._pos_embeding(inputs, x, self.pos_embed) + + if not self.with_cls_token: + # Remove class token for transformer input + x = x[:, 1:] + + outs = [] + for i, blk in enumerate(self.blocks): + x = blk(x) + if i == len(self.blocks) - 1: + if self.final_norm: + x = self.norm(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, inputs.shape[2] // self.patch_size, + inputs.shape[3] // self.patch_size, + C).permute(0, 3, 1, 2) + outs.append(out) + + return tuple(outs) + + def train(self, mode=True): + super(VisionTransformer, self).train(mode) + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, nn.LayerNorm): + m.eval() diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/builder.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/builder.py new file mode 100644 index 0000000..1f5b971 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/builder.py @@ -0,0 +1,46 @@ +import warnings + +from annotator.uniformer.mmcv.cnn import MODELS as MMCV_MODELS +from annotator.uniformer.mmcv.utils import Registry + +MODELS = Registry('models', parent=MMCV_MODELS) + +BACKBONES = MODELS +NECKS = MODELS +HEADS = MODELS +LOSSES = MODELS +SEGMENTORS = MODELS + + +def build_backbone(cfg): + """Build backbone.""" + return BACKBONES.build(cfg) + + +def build_neck(cfg): + """Build neck.""" + return NECKS.build(cfg) + + +def build_head(cfg): + """Build head.""" + return HEADS.build(cfg) + + +def build_loss(cfg): + """Build loss.""" + 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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/__init__.py new file mode 100644 index 0000000..ac66d3c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/__init__.py @@ -0,0 +1,28 @@ +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 .dm_head import DMHead +from .dnl_head import DNLHead +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 .lraspp_head import LRASPPHead +from .nl_head import NLHead +from .ocr_head import OCRHead +# from .point_head import PointHead +from .psa_head import PSAHead +from .psp_head import PSPHead +from .sep_aspp_head import DepthwiseSeparableASPPHead +from .sep_fcn_head import DepthwiseSeparableFCNHead +from .uper_head import UPerHead + +__all__ = [ + 'FCNHead', 'PSPHead', 'ASPPHead', 'PSAHead', 'NLHead', 'GCHead', 'CCHead', + 'UPerHead', 'DepthwiseSeparableASPPHead', 'ANNHead', 'DAHead', 'OCRHead', + 'EncHead', 'DepthwiseSeparableFCNHead', 'FPNHead', 'EMAHead', 'DNLHead', + 'APCHead', 'DMHead', 'LRASPPHead' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ann_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ann_head.py new file mode 100644 index 0000000..30aaacc --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ann_head.py @@ -0,0 +1,245 @@ +import torch +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule + +from ..builder import HEADS +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(PPMConcat, self).__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(SelfAttentionBlock, self).__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(AFNB, self).__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(APNB, self).__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 + + +@HEADS.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(ANNHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/apc_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/apc_head.py new file mode 100644 index 0000000..c7038bd --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/apc_head.py @@ -0,0 +1,158 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +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(ACM, self).__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 + + +@HEADS.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(APCHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/aspp_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/aspp_head.py new file mode 100644 index 0000000..aa914b5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/aspp_head.py @@ -0,0 +1,107 @@ +import torch +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +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(ASPPModule, self).__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 + + +@HEADS.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(ASPPHead, self).__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(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) + output = self.cls_seg(output) + return output diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/cascade_decode_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/cascade_decode_head.py new file mode 100644 index 0000000..d02122c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/cascade_decode_head.py @@ -0,0 +1,57 @@ +from abc import ABCMeta, abstractmethod + +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(BaseCascadeDecodeHead, self).__init__(*args, **kwargs) + + @abstractmethod + def forward(self, inputs, prev_output): + """Placeholder of forward function.""" + pass + + def forward_train(self, inputs, prev_output, img_metas, gt_semantic_seg, + train_cfg): + """Forward function for training. + 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`. + gt_semantic_seg (Tensor): Semantic segmentation masks + used if the architecture supports semantic segmentation task. + train_cfg (dict): The training config. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + seg_logits = self.forward(inputs, prev_output) + losses = self.losses(seg_logits, gt_semantic_seg) + + return losses + + def forward_test(self, inputs, prev_output, img_metas, test_cfg): + """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. + """ + return self.forward(inputs, prev_output) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/cc_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/cc_head.py new file mode 100644 index 0000000..5b9abb4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/cc_head.py @@ -0,0 +1,42 @@ +import torch + +from ..builder import HEADS +from .fcn_head import FCNHead + +try: + from annotator.uniformer.mmcv.ops import CrissCrossAttention +except ModuleNotFoundError: + CrissCrossAttention = None + + +@HEADS.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(CCHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/da_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/da_head.py new file mode 100644 index 0000000..5cd49fc --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/da_head.py @@ -0,0 +1,178 @@ +import torch +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule, Scale +from torch import nn + +from annotator.uniformer.mmseg.core import add_prefix +from ..builder import HEADS +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(PAM, self).__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(PAM, self).forward(x, x) + + out = self.gamma(out) + x + return out + + +class CAM(nn.Module): + """Channel Attention Module (CAM)""" + + def __init__(self): + super(CAM, self).__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 + + +@HEADS.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(DAHead, self).__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 forward_test(self, inputs, img_metas, test_cfg): + """Forward function for testing, only ``pam_cam`` is used.""" + return self.forward(inputs)[0] + + def losses(self, seg_logit, seg_label): + """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(DAHead, self).losses(pam_cam_seg_logit, seg_label), + 'pam_cam')) + loss.update( + add_prefix( + super(DAHead, self).losses(pam_seg_logit, seg_label), 'pam')) + loss.update( + add_prefix( + super(DAHead, self).losses(cam_seg_logit, seg_label), 'cam')) + return loss diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/decode_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/decode_head.py new file mode 100644 index 0000000..88a661b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/decode_head.py @@ -0,0 +1,234 @@ +from abc import ABCMeta, abstractmethod + +import torch +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import normal_init +from annotator.uniformer.mmcv.runner import auto_fp16, force_fp32 + +from annotator.uniformer.mmseg.core import build_pixel_sampler +from annotator.uniformer.mmseg.ops import resize +from ..builder import build_loss +from ..losses import accuracy + + +class BaseDecodeHead(nn.Module, metaclass=ABCMeta): + """Base class for BaseDecodeHead. + + Args: + in_channels (int|Sequence[int]): Input channels. + channels (int): Channels after modules, before conv_seg. + num_classes (int): Number of classes. + 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): Config of decode loss. + 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. + """ + + def __init__(self, + in_channels, + channels, + *, + num_classes, + 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): + super(BaseDecodeHead, self).__init__() + self._init_inputs(in_channels, in_index, input_transform) + self.channels = channels + self.num_classes = num_classes + 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.loss_decode = build_loss(loss_decode) + self.ignore_index = ignore_index + self.align_corners = align_corners + if sampler is not None: + self.sampler = build_pixel_sampler(sampler, context=self) + else: + self.sampler = None + + self.conv_seg = nn.Conv2d(channels, num_classes, kernel_size=1) + if dropout_ratio > 0: + self.dropout = nn.Dropout2d(dropout_ratio) + else: + self.dropout = None + self.fp16_enabled = False + + 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 init_weights(self): + """Initialize weights of classification layer.""" + normal_init(self.conv_seg, mean=0, std=0.01) + + 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 + + @auto_fp16() + @abstractmethod + def forward(self, inputs): + """Placeholder of forward function.""" + pass + + def forward_train(self, inputs, img_metas, gt_semantic_seg, train_cfg): + """Forward function for training. + Args: + inputs (list[Tensor]): List of multi-level img features. + 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`. + gt_semantic_seg (Tensor): Semantic segmentation masks + used if the architecture supports semantic segmentation task. + train_cfg (dict): The training config. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + seg_logits = self.forward(inputs) + losses = self.losses(seg_logits, gt_semantic_seg) + return losses + + def forward_test(self, inputs, img_metas, test_cfg): + """Forward function for testing. + + Args: + inputs (list[Tensor]): List of multi-level img features. + 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. + """ + return self.forward(inputs) + + 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 + + @force_fp32(apply_to=('seg_logit', )) + def losses(self, seg_logit, seg_label): + """Compute segmentation loss.""" + loss = dict() + seg_logit = resize( + input=seg_logit, + 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) + loss['loss_seg'] = self.loss_decode( + 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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/dm_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/dm_head.py new file mode 100644 index 0000000..19c9639 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/dm_head.py @@ -0,0 +1,140 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule, build_activation_layer, build_norm_layer + +from ..builder import HEADS +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(DCM, self).__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 + + +@HEADS.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(DMHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/dnl_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/dnl_head.py new file mode 100644 index 0000000..333280c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/dnl_head.py @@ -0,0 +1,131 @@ +import torch +from annotator.uniformer.mmcv.cnn import NonLocal2d +from torch import nn + +from ..builder import HEADS +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 /= theta_x.shape[-1]**0.5 + pairwise_weight /= self.temperature + 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 + + +@HEADS.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(DNLHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ema_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ema_head.py new file mode 100644 index 0000000..12267cb --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ema_head.py @@ -0,0 +1,168 @@ +import math + +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule + +from ..builder import HEADS +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(EMAModule, self).__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 + + +@HEADS.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(EMAHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/enc_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/enc_head.py new file mode 100644 index 0000000..da57af6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/enc_head.py @@ -0,0 +1,187 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule, build_norm_layer + +from annotator.uniformer.mmseg.ops import Encoding, resize +from ..builder import HEADS, build_loss +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(EncModule, self).__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 + + +@HEADS.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(EncHead, self).__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 = build_loss(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 forward_test(self, inputs, img_metas, test_cfg): + """Forward function for testing, ignore se_loss.""" + if self.use_se_loss: + return self.forward(inputs)[0] + else: + return self.forward(inputs) + + @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 losses(self, seg_logit, seg_label): + """Compute segmentation and semantic encoding loss.""" + seg_logit, se_seg_logit = seg_logit + loss = dict() + loss.update(super(EncHead, self).losses(seg_logit, seg_label)) + 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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/fcn_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/fcn_head.py new file mode 100644 index 0000000..edb32c2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/fcn_head.py @@ -0,0 +1,81 @@ +import torch +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule + +from ..builder import HEADS +from .decode_head import BaseDecodeHead + + +@HEADS.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(FCNHead, self).__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(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + output = self.convs(x) + if self.concat_input: + output = self.conv_cat(torch.cat([x, output], dim=1)) + output = self.cls_seg(output) + return output diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/fpn_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/fpn_head.py new file mode 100644 index 0000000..1241c55 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/fpn_head.py @@ -0,0 +1,68 @@ +import numpy as np +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +from .decode_head import BaseDecodeHead + + +@HEADS.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(FPNHead, self).__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( + nn.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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/gc_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/gc_head.py new file mode 100644 index 0000000..7074124 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/gc_head.py @@ -0,0 +1,47 @@ +import torch +from annotator.uniformer.mmcv.cnn import ContextBlock + +from ..builder import HEADS +from .fcn_head import FCNHead + + +@HEADS.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(GCHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/lraspp_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/lraspp_head.py new file mode 100644 index 0000000..69bf320 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/lraspp_head.py @@ -0,0 +1,90 @@ +import torch +import torch.nn as nn +from annotator.uniformer.mmcv import is_tuple_of +from annotator.uniformer.mmcv.cnn import ConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +from .decode_head import BaseDecodeHead + + +@HEADS.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(LRASPPHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/nl_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/nl_head.py new file mode 100644 index 0000000..3eee424 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/nl_head.py @@ -0,0 +1,49 @@ +import torch +from annotator.uniformer.mmcv.cnn import NonLocal2d + +from ..builder import HEADS +from .fcn_head import FCNHead + + +@HEADS.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(NLHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ocr_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ocr_head.py new file mode 100644 index 0000000..715852e --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/ocr_head.py @@ -0,0 +1,127 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +from ..utils import SelfAttentionBlock as _SelfAttentionBlock +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(SpatialGatherModule, self).__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(ObjectAttentionBlock, self).__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(ObjectAttentionBlock, + self).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 + + +@HEADS.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(OCRHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/point_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/point_head.py new file mode 100644 index 0000000..3342aa2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/point_head.py @@ -0,0 +1,349 @@ +# 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 annotator.uniformer.mmcv.cnn import ConvModule, normal_init +from annotator.uniformer.mmcv.ops import point_sample + +from annotator.uniformer.mmseg.models.builder import HEADS +from annotator.uniformer.mmseg.ops import resize +from ..losses import accuracy +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) + + +@HEADS.register_module() +class PointHead(BaseCascadeDecodeHead): + """A mask point head use in PointRend. + + ``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(PointHead, self).__init__( + input_transform='multiple_select', + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **kwargs) + + 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 init_weights(self): + """Initialize weights of classification layer.""" + normal_init(self.fc_seg, std=0.001) + + 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 forward_train(self, inputs, prev_output, img_metas, gt_semantic_seg, + train_cfg): + """Forward function for training. + 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`. + gt_semantic_seg (Tensor): Semantic segmentation masks + used if the architecture supports semantic segmentation task. + 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) + point_label = point_sample( + gt_semantic_seg.float(), + points, + mode='nearest', + align_corners=self.align_corners) + point_label = point_label.squeeze(1).long() + + losses = self.losses(point_logits, point_label) + + return losses + + def forward_test(self, inputs, prev_output, img_metas, test_cfg): + """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 refined_seg_logits + + def losses(self, point_logits, point_label): + """Compute segmentation loss.""" + loss = dict() + loss['loss_point'] = self.loss_decode( + point_logits, point_label, ignore_index=self.ignore_index) + loss['acc_point'] = accuracy(point_logits, point_label) + 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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/psa_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/psa_head.py new file mode 100644 index 0000000..480dbd1 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/psa_head.py @@ -0,0 +1,196 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +from .decode_head import BaseDecodeHead + +try: + from annotator.uniformer.mmcv.ops import PSAMask +except ModuleNotFoundError: + PSAMask = None + + +@HEADS.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(PSAHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/psp_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/psp_head.py new file mode 100644 index 0000000..b5f1e71 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/psp_head.py @@ -0,0 +1,101 @@ +import torch +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +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): + super(PPM, self).__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))) + + 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 + + +@HEADS.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(PSPHead, self).__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(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + psp_outs = [x] + psp_outs.extend(self.psp_modules(x)) + psp_outs = torch.cat(psp_outs, dim=1) + output = self.bottleneck(psp_outs) + output = self.cls_seg(output) + return output diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/sep_aspp_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/sep_aspp_head.py new file mode 100644 index 0000000..3339a7a --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/sep_aspp_head.py @@ -0,0 +1,101 @@ +import torch +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule, DepthwiseSeparableConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +from .aspp_head import ASPPHead, ASPPModule + + +class DepthwiseSeparableASPPModule(ASPPModule): + """Atrous Spatial Pyramid Pooling (ASPP) Module with depthwise separable + conv.""" + + def __init__(self, **kwargs): + super(DepthwiseSeparableASPPModule, self).__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) + + +@HEADS.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(DepthwiseSeparableASPPHead, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/sep_fcn_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/sep_fcn_head.py new file mode 100644 index 0000000..a098614 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/sep_fcn_head.py @@ -0,0 +1,51 @@ +from annotator.uniformer.mmcv.cnn import DepthwiseSeparableConvModule + +from ..builder import HEADS +from .fcn_head import FCNHead + + +@HEADS.register_module() +class DepthwiseSeparableFCNHead(FCNHead): + """Depthwise-Separable Fully Convolutional Network for Semantic + Segmentation. + + This head is implemented according to Fast-SCNN paper. + 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. + """ + + def __init__(self, **kwargs): + super(DepthwiseSeparableFCNHead, self).__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) + 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) + + 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) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/uper_head.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/uper_head.py new file mode 100644 index 0000000..9e1301b --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/decode_heads/uper_head.py @@ -0,0 +1,126 @@ +import torch +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule + +from annotator.uniformer.mmseg.ops import resize +from ..builder import HEADS +from .decode_head import BaseDecodeHead +from .psp_head import PPM + + +@HEADS.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(UPerHead, self).__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(self, inputs): + """Forward function.""" + + 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] += 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) + output = self.fpn_bottleneck(fpn_outs) + output = self.cls_seg(output) + return output diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/__init__.py new file mode 100644 index 0000000..beca720 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/__init__.py @@ -0,0 +1,12 @@ +from .accuracy import Accuracy, accuracy +from .cross_entropy_loss import (CrossEntropyLoss, binary_cross_entropy, + cross_entropy, mask_cross_entropy) +from .dice_loss import DiceLoss +from .lovasz_loss import LovaszLoss +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' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/accuracy.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/accuracy.py new file mode 100644 index 0000000..c0fd2e7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/accuracy.py @@ -0,0 +1,78 @@ +import torch.nn as nn + + +def accuracy(pred, target, topk=1, thresh=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, , ...) + 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() + res = [] + for k in topk: + correct_k = correct[:k].reshape(-1).float().sum(0, keepdim=True) + res.append(correct_k.mul_(100.0 / target.numel())) + return res[0] if return_single else res + + +class Accuracy(nn.Module): + """Accuracy calculation module.""" + + def __init__(self, topk=(1, ), thresh=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 + + 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) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/cross_entropy_loss.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/cross_entropy_loss.py new file mode 100644 index 0000000..42c0790 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/cross_entropy_loss.py @@ -0,0 +1,198 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from ..builder import LOSSES +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): + """The wrapper function for :func:`F.cross_entropy`""" + # 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 + 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 *= valid_mask + + return bin_labels, bin_label_weights + + +def binary_cross_entropy(pred, + label, + weight=None, + reduction='mean', + avg_factor=None, + class_weight=None, + ignore_index=255): + """Calculate the binary CrossEntropy loss. + + 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. + 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 | None): The label index to be ignored. Default: 255 + + Returns: + torch.Tensor: The calculated loss + """ + 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' + label, weight = _expand_onehot_labels(label, weight, pred.shape, + ignore_index) + + # weighted element-wise losses + if weight is not None: + weight = weight.float() + 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): + """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] + + +@LOSSES.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. + """ + + def __init__(self, + use_sigmoid=False, + use_mask=False, + reduction='mean', + class_weight=None, + loss_weight=1.0): + super(CrossEntropyLoss, self).__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) + + 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 + + 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 + loss_cls = self.loss_weight * self.cls_criterion( + cls_score, + label, + weight, + class_weight=class_weight, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss_cls diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/dice_loss.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/dice_loss.py new file mode 100644 index 0000000..27a77b9 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/dice_loss.py @@ -0,0 +1,119 @@ +"""Modified from https://github.com/LikeLy-Journey/SegmenTron/blob/master/ +segmentron/solver/loss.py (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 dice_loss(pred, + target, + valid_mask, + smooth=1, + exponent=2, + 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: + dice_loss = binary_dice_loss( + pred[:, i], + target[..., i], + valid_mask=valid_mask, + smooth=smooth, + exponent=exponent) + if class_weight is not None: + dice_loss *= class_weight[i] + total_loss += dice_loss + return total_loss / num_classes + + +@weighted_loss +def binary_dice_loss(pred, target, valid_mask, smooth=1, exponent=2, **kwards): + 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) + + num = torch.sum(torch.mul(pred, target) * valid_mask, dim=1) * 2 + smooth + den = torch.sum(pred.pow(exponent) + target.pow(exponent), dim=1) + smooth + + return 1 - num / den + + +@LOSSES.register_module() +class DiceLoss(nn.Module): + """DiceLoss. + + This loss is proposed in `V-Net: Fully Convolutional Neural Networks for + Volumetric Medical Image Segmentation `_. + + Args: + loss_type (str, optional): Binary or multi-class loss. + Default: 'multi_class'. Options are "binary" and "multi_class". + smooth (float): A float number to smooth loss, and avoid NaN error. + Default: 1 + exponent (float): An float number to calculate denominator + value: \\sum{x^exponent} + \\sum{y^exponent}. Default: 2. + 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. Default to 1.0. + ignore_index (int | None): The label index to be ignored. Default: 255. + """ + + def __init__(self, + smooth=1, + exponent=2, + reduction='mean', + class_weight=None, + loss_weight=1.0, + ignore_index=255, + **kwards): + super(DiceLoss, self).__init__() + self.smooth = smooth + self.exponent = exponent + self.reduction = reduction + self.class_weight = get_class_weight(class_weight) + self.loss_weight = loss_weight + self.ignore_index = ignore_index + + def forward(self, + pred, + target, + avg_factor=None, + reduction_override=None, + **kwards): + 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 = 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 * dice_loss( + pred, + one_hot_target, + valid_mask=valid_mask, + reduction=reduction, + avg_factor=avg_factor, + smooth=self.smooth, + exponent=self.exponent, + class_weight=class_weight, + ignore_index=self.ignore_index) + return loss diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/lovasz_loss.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/lovasz_loss.py new file mode 100644 index 0000000..6badb67 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/lovasz_loss.py @@ -0,0 +1,303 @@ +"""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 annotator.uniformer.mmcv as mmcv +import torch +import torch.nn as nn +import torch.nn.functional as F + +from ..builder import LOSSES +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 + + +@LOSSES.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. + """ + + def __init__(self, + loss_type='multi_class', + classes='present', + per_image=False, + reduction='mean', + class_weight=None, + loss_weight=1.0): + super(LovaszLoss, self).__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 mmcv.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) + + 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 diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/utils.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/utils.py new file mode 100644 index 0000000..85aec9f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/losses/utils.py @@ -0,0 +1,121 @@ +import functools + +import annotator.uniformer.mmcv as mmcv +import numpy as np +import torch.nn.functional as F + + +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 = mmcv.load(class_weight) + + return class_weight + + +def reduce_loss(loss, reduction): + """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): + """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): Avarage 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': + loss = loss.sum() / avg_factor + # 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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/__init__.py new file mode 100644 index 0000000..9b9d3d5 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/__init__.py @@ -0,0 +1,4 @@ +from .fpn import FPN +from .multilevel_neck import MultiLevelNeck + +__all__ = ['FPN', 'MultiLevelNeck'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/fpn.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/fpn.py new file mode 100644 index 0000000..a53b2a6 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/fpn.py @@ -0,0 +1,212 @@ +import torch.nn as nn +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule, xavier_init + +from ..builder import NECKS + + +@NECKS.register_module() +class FPN(nn.Module): + """Feature Pyramid Network. + + This is an implementation of - Feature Pyramid Networks for Object + Detection (https://arxiv.org/abs/1612.03144) + + 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 (str): Config dict for activation layer in ConvModule. + Default: None. + upsample_cfg (dict): Config dict for interpolate layer. + Default: `dict(mode='nearest')` + + 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')): + super(FPN, self).__init__() + 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) + + # 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) + + # 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] += F.interpolate(laterals[i], + **self.upsample_cfg) + else: + prev_shape = laterals[i - 1].shape[2:] + laterals[i - 1] += F.interpolate( + 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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/multilevel_neck.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/multilevel_neck.py new file mode 100644 index 0000000..766144d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/necks/multilevel_neck.py @@ -0,0 +1,70 @@ +import torch.nn as nn +import torch.nn.functional as F +from annotator.uniformer.mmcv.cnn import ConvModule + +from ..builder import NECKS + + +@NECKS.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[int]): Scale factors for each input feature map. + 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(MultiLevelNeck, self).__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)) + + def forward(self, inputs): + assert len(inputs) == len(self.in_channels) + print(inputs[0].shape) + 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 = F.interpolate( + inputs[i], scale_factor=self.scales[i], mode='bilinear') + outs.append(self.convs[i](x_resize)) + return tuple(outs) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/__init__.py new file mode 100644 index 0000000..dca2f09 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/__init__.py @@ -0,0 +1,5 @@ +from .base import BaseSegmentor +from .cascade_encoder_decoder import CascadeEncoderDecoder +from .encoder_decoder import EncoderDecoder + +__all__ = ['BaseSegmentor', 'EncoderDecoder', 'CascadeEncoderDecoder'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/base.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/base.py new file mode 100644 index 0000000..172fc63 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/base.py @@ -0,0 +1,273 @@ +import logging +import warnings +from abc import ABCMeta, abstractmethod +from collections import OrderedDict + +import annotator.uniformer.mmcv as mmcv +import numpy as np +import torch +import torch.distributed as dist +import torch.nn as nn +from annotator.uniformer.mmcv.runner import auto_fp16 + + +class BaseSegmentor(nn.Module): + """Base class for segmentors.""" + + __metaclass__ = ABCMeta + + def __init__(self): + super(BaseSegmentor, self).__init__() + self.fp16_enabled = False + + @property + def with_neck(self): + """bool: whether the segmentor has neck""" + return hasattr(self, 'neck') and self.neck is not None + + @property + def with_auxiliary_head(self): + """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: whether the segmentor has decode head""" + return hasattr(self, 'decode_head') and self.decode_head is not None + + @abstractmethod + def extract_feat(self, imgs): + """Placeholder for extract features from images.""" + pass + + @abstractmethod + def encode_decode(self, img, img_metas): + """Placeholder for encode images with backbone and decode into a + semantic segmentation map of the same size as input.""" + pass + + @abstractmethod + def forward_train(self, imgs, img_metas, **kwargs): + """Placeholder for Forward function for training.""" + pass + + @abstractmethod + def simple_test(self, img, img_meta, **kwargs): + """Placeholder for single image test.""" + pass + + @abstractmethod + def aug_test(self, imgs, img_metas, **kwargs): + """Placeholder for augmentation test.""" + pass + + def init_weights(self, pretrained=None): + """Initialize the weights in segmentor. + + Args: + pretrained (str, optional): Path to pre-trained weights. + Defaults to None. + """ + if pretrained is not None: + logger = logging.getLogger() + logger.info(f'load model from: {pretrained}') + + def forward_test(self, imgs, img_metas, **kwargs): + """ + Args: + imgs (List[Tensor]): the outer list indicates test-time + augmentations and inner Tensor should have a shape NxCxHxW, + which contains all images in the batch. + img_metas (List[List[dict]]): the outer list indicates test-time + augs (multiscale, flip, etc.) and the inner list indicates + images in a batch. + """ + for var, name in [(imgs, 'imgs'), (img_metas, 'img_metas')]: + if not isinstance(var, list): + raise TypeError(f'{name} must be a list, but got ' + f'{type(var)}') + + num_augs = len(imgs) + if num_augs != len(img_metas): + raise ValueError(f'num of augmentations ({len(imgs)}) != ' + f'num of image meta ({len(img_metas)})') + # all images in the same aug batch all of the same ori_shape and pad + # shape + for img_meta in img_metas: + ori_shapes = [_['ori_shape'] for _ in img_meta] + assert all(shape == ori_shapes[0] for shape in ori_shapes) + img_shapes = [_['img_shape'] for _ in img_meta] + assert all(shape == img_shapes[0] for shape in img_shapes) + pad_shapes = [_['pad_shape'] for _ in img_meta] + assert all(shape == pad_shapes[0] for shape in pad_shapes) + + if num_augs == 1: + return self.simple_test(imgs[0], img_metas[0], **kwargs) + else: + return self.aug_test(imgs, img_metas, **kwargs) + + @auto_fp16(apply_to=('img', )) + def forward(self, img, img_metas, return_loss=True, **kwargs): + """Calls either :func:`forward_train` or :func:`forward_test` depending + on whether ``return_loss`` is ``True``. + + Note this setting will change the expected inputs. When + ``return_loss=True``, img and img_meta are single-nested (i.e. Tensor + and List[dict]), and when ``resturn_loss=False``, img and img_meta + should be double nested (i.e. List[Tensor], List[List[dict]]), with + the outer list indicating test time augmentations. + """ + if return_loss: + return self.forward_train(img, img_metas, **kwargs) + else: + return self.forward_test(img, img_metas, **kwargs) + + def train_step(self, data_batch, optimizer, **kwargs): + """The iteration step during training. + + This method defines an iteration step during training, except for the + back propagation and optimizer updating, which are done in an optimizer + hook. Note that in some complicated cases or models, the whole process + including back propagation and optimizer updating is also defined in + this method, such as GAN. + + Args: + data (dict): The output of dataloader. + optimizer (:obj:`torch.optim.Optimizer` | dict): The optimizer of + runner is passed to ``train_step()``. This argument is unused + and reserved. + + Returns: + dict: It should contain at least 3 keys: ``loss``, ``log_vars``, + ``num_samples``. + ``loss`` is a tensor for back propagation, which can be a + weighted sum of multiple losses. + ``log_vars`` contains all the variables to be sent to the + logger. + ``num_samples`` indicates the batch size (when the model is + DDP, it means the batch size on each GPU), which is used for + averaging the logs. + """ + losses = self(**data_batch) + loss, log_vars = self._parse_losses(losses) + + outputs = dict( + loss=loss, + log_vars=log_vars, + num_samples=len(data_batch['img_metas'])) + + return outputs + + def val_step(self, data_batch, **kwargs): + """The iteration step during validation. + + This method shares the same signature as :func:`train_step`, but used + during val epochs. Note that the evaluation after training epochs is + not implemented with this method, but an evaluation hook. + """ + output = self(**data_batch, **kwargs) + return output + + @staticmethod + def _parse_losses(losses): + """Parse the raw outputs (losses) of the network. + + Args: + losses (dict): Raw output of the network, which usually contain + losses and other necessary information. + + Returns: + tuple[Tensor, dict]: (loss, log_vars), loss is the loss tensor + which may be a weighted sum of all losses, log_vars contains + all the variables to be sent to the logger. + """ + log_vars = OrderedDict() + for loss_name, loss_value in losses.items(): + if isinstance(loss_value, torch.Tensor): + log_vars[loss_name] = loss_value.mean() + elif isinstance(loss_value, list): + log_vars[loss_name] = sum(_loss.mean() for _loss in loss_value) + else: + raise TypeError( + f'{loss_name} is not a tensor or list of tensors') + + loss = sum(_value for _key, _value in log_vars.items() + if 'loss' in _key) + + log_vars['loss'] = loss + for loss_name, loss_value in log_vars.items(): + # reduce loss when distributed training + if dist.is_available() and dist.is_initialized(): + loss_value = loss_value.data.clone() + dist.all_reduce(loss_value.div_(dist.get_world_size())) + log_vars[loss_name] = loss_value.item() + + return loss, log_vars + + def show_result(self, + img, + result, + palette=None, + win_name='', + show=False, + wait_time=0, + out_file=None, + opacity=0.5): + """Draw `result` over `img`. + + Args: + img (str or Tensor): The image to be displayed. + result (Tensor): The semantic segmentation results to draw over + `img`. + palette (list[list[int]]] | np.ndarray | None): The palette of + segmentation map. If None is given, random palette will be + generated. Default: None + win_name (str): The window name. + wait_time (int): Value of waitKey param. + Default: 0. + show (bool): Whether to show the image. + Default: False. + out_file (str or None): The filename to write the image. + Default: None. + opacity(float): Opacity of painted segmentation map. + Default 0.5. + Must be in (0, 1] range. + Returns: + img (Tensor): Only if not `show` or `out_file` + """ + img = mmcv.imread(img) + img = img.copy() + seg = result[0] + if palette is None: + if self.PALETTE is None: + palette = np.random.randint( + 0, 255, size=(len(self.CLASSES), 3)) + else: + palette = self.PALETTE + palette = np.array(palette) + assert palette.shape[0] == len(self.CLASSES) + assert palette.shape[1] == 3 + assert len(palette.shape) == 2 + assert 0 < opacity <= 1.0 + 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 * (1 - opacity) + color_seg * opacity + img = img.astype(np.uint8) + # if out_file specified, do not show image in window + if out_file is not None: + show = False + + if show: + mmcv.imshow(img, win_name, wait_time) + if out_file is not None: + mmcv.imwrite(img, out_file) + + if not (show or out_file): + warnings.warn('show==False and out_file is not specified, only ' + 'result image will be returned') + return img diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/cascade_encoder_decoder.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/cascade_encoder_decoder.py new file mode 100644 index 0000000..873957d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/cascade_encoder_decoder.py @@ -0,0 +1,98 @@ +from torch import nn + +from annotator.uniformer.mmseg.core import add_prefix +from annotator.uniformer.mmseg.ops import resize +from .. import builder +from ..builder import SEGMENTORS +from .encoder_decoder import EncoderDecoder + + +@SEGMENTORS.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. + """ + + def __init__(self, + num_stages, + backbone, + decode_head, + neck=None, + auxiliary_head=None, + train_cfg=None, + test_cfg=None, + pretrained=None): + self.num_stages = num_stages + super(CascadeEncoderDecoder, self).__init__( + backbone=backbone, + decode_head=decode_head, + neck=neck, + auxiliary_head=auxiliary_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + pretrained=pretrained) + + def _init_decode_head(self, decode_head): + """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(builder.build_head(decode_head[i])) + self.align_corners = self.decode_head[-1].align_corners + self.num_classes = self.decode_head[-1].num_classes + + def init_weights(self, pretrained=None): + """Initialize the weights in backbone and heads. + + Args: + pretrained (str, optional): Path to pre-trained weights. + Defaults to None. + """ + self.backbone.init_weights(pretrained=pretrained) + for i in range(self.num_stages): + self.decode_head[i].init_weights() + if self.with_auxiliary_head: + if isinstance(self.auxiliary_head, nn.ModuleList): + for aux_head in self.auxiliary_head: + aux_head.init_weights() + else: + self.auxiliary_head.init_weights() + + def encode_decode(self, img, img_metas): + """Encode images with backbone and decode into a semantic segmentation + map of the same size as input.""" + x = self.extract_feat(img) + out = self.decode_head[0].forward_test(x, img_metas, self.test_cfg) + for i in range(1, self.num_stages): + out = self.decode_head[i].forward_test(x, out, img_metas, + self.test_cfg) + out = resize( + input=out, + size=img.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + return out + + def _decode_head_forward_train(self, x, img_metas, gt_semantic_seg): + """Run forward function and calculate loss for decode head in + training.""" + losses = dict() + + loss_decode = self.decode_head[0].forward_train( + x, img_metas, gt_semantic_seg, self.train_cfg) + + losses.update(add_prefix(loss_decode, 'decode_0')) + + for i in range(1, self.num_stages): + # forward test again, maybe unnecessary for most methods. + prev_outputs = self.decode_head[i - 1].forward_test( + x, img_metas, self.test_cfg) + loss_decode = self.decode_head[i].forward_train( + x, prev_outputs, img_metas, gt_semantic_seg, self.train_cfg) + losses.update(add_prefix(loss_decode, f'decode_{i}')) + + return losses diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/encoder_decoder.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/encoder_decoder.py new file mode 100644 index 0000000..98392ac --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/segmentors/encoder_decoder.py @@ -0,0 +1,298 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from annotator.uniformer.mmseg.core import add_prefix +from annotator.uniformer.mmseg.ops import resize +from .. import builder +from ..builder import SEGMENTORS +from .base import BaseSegmentor + + +@SEGMENTORS.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. + """ + + def __init__(self, + backbone, + decode_head, + neck=None, + auxiliary_head=None, + train_cfg=None, + test_cfg=None, + pretrained=None): + super(EncoderDecoder, self).__init__() + self.backbone = builder.build_backbone(backbone) + if neck is not None: + self.neck = builder.build_neck(neck) + self._init_decode_head(decode_head) + self._init_auxiliary_head(auxiliary_head) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + self.init_weights(pretrained=pretrained) + + assert self.with_decode_head + + def _init_decode_head(self, decode_head): + """Initialize ``decode_head``""" + self.decode_head = builder.build_head(decode_head) + self.align_corners = self.decode_head.align_corners + self.num_classes = self.decode_head.num_classes + + def _init_auxiliary_head(self, auxiliary_head): + """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(builder.build_head(head_cfg)) + else: + self.auxiliary_head = builder.build_head(auxiliary_head) + + def init_weights(self, pretrained=None): + """Initialize the weights in backbone and heads. + + Args: + pretrained (str, optional): Path to pre-trained weights. + Defaults to None. + """ + + super(EncoderDecoder, self).init_weights(pretrained) + self.backbone.init_weights(pretrained=pretrained) + self.decode_head.init_weights() + if self.with_auxiliary_head: + if isinstance(self.auxiliary_head, nn.ModuleList): + for aux_head in self.auxiliary_head: + aux_head.init_weights() + else: + self.auxiliary_head.init_weights() + + def extract_feat(self, img): + """Extract features from images.""" + x = self.backbone(img) + if self.with_neck: + x = self.neck(x) + return x + + def encode_decode(self, img, img_metas): + """Encode images with backbone and decode into a semantic segmentation + map of the same size as input.""" + x = self.extract_feat(img) + out = self._decode_head_forward_test(x, img_metas) + out = resize( + input=out, + size=img.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + return out + + def _decode_head_forward_train(self, x, img_metas, gt_semantic_seg): + """Run forward function and calculate loss for decode head in + training.""" + losses = dict() + loss_decode = self.decode_head.forward_train(x, img_metas, + gt_semantic_seg, + self.train_cfg) + + losses.update(add_prefix(loss_decode, 'decode')) + return losses + + def _decode_head_forward_test(self, x, img_metas): + """Run forward function and calculate loss for decode head in + inference.""" + seg_logits = self.decode_head.forward_test(x, img_metas, self.test_cfg) + return seg_logits + + def _auxiliary_head_forward_train(self, x, img_metas, gt_semantic_seg): + """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.forward_train(x, img_metas, + gt_semantic_seg, + self.train_cfg) + losses.update(add_prefix(loss_aux, f'aux_{idx}')) + else: + loss_aux = self.auxiliary_head.forward_train( + x, img_metas, gt_semantic_seg, self.train_cfg) + losses.update(add_prefix(loss_aux, 'aux')) + + return losses + + def forward_dummy(self, img): + """Dummy forward function.""" + seg_logit = self.encode_decode(img, None) + + return seg_logit + + def forward_train(self, img, img_metas, gt_semantic_seg): + """Forward function for training. + + Args: + img (Tensor): Input images. + 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`. + gt_semantic_seg (Tensor): Semantic segmentation masks + used if the architecture supports semantic segmentation task. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + + x = self.extract_feat(img) + + losses = dict() + + loss_decode = self._decode_head_forward_train(x, img_metas, + gt_semantic_seg) + losses.update(loss_decode) + + if self.with_auxiliary_head: + loss_aux = self._auxiliary_head_forward_train( + x, img_metas, gt_semantic_seg) + losses.update(loss_aux) + + return losses + + # TODO refactor + def slide_inference(self, img, img_meta, rescale): + """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. + """ + + h_stride, w_stride = self.test_cfg.stride + h_crop, w_crop = self.test_cfg.crop_size + batch_size, _, h_img, w_img = img.size() + num_classes = self.num_classes + 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 = img.new_zeros((batch_size, num_classes, h_img, w_img)) + count_mat = img.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 = img[:, :, y1:y2, x1:x2] + crop_seg_logit = self.encode_decode(crop_img, img_meta) + 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 + if torch.onnx.is_in_onnx_export(): + # cast count_mat to constant while exporting to ONNX + count_mat = torch.from_numpy( + count_mat.cpu().detach().numpy()).to(device=img.device) + preds = preds / count_mat + if rescale: + preds = resize( + preds, + size=img_meta[0]['ori_shape'][:2], + mode='bilinear', + align_corners=self.align_corners, + warning=False) + return preds + + def whole_inference(self, img, img_meta, rescale): + """Inference with full image.""" + + seg_logit = self.encode_decode(img, img_meta) + if rescale: + # support dynamic shape for onnx + if torch.onnx.is_in_onnx_export(): + size = img.shape[2:] + else: + size = img_meta[0]['ori_shape'][:2] + seg_logit = resize( + seg_logit, + size=size, + mode='bilinear', + align_corners=self.align_corners, + warning=False) + + return seg_logit + + def inference(self, img, img_meta, rescale): + """Inference with slide/whole style. + + Args: + img (Tensor): The input image of shape (N, 3, H, W). + img_meta (dict): 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`. + rescale (bool): Whether rescale back to original shape. + + Returns: + Tensor: The output segmentation map. + """ + + assert self.test_cfg.mode in ['slide', 'whole'] + ori_shape = img_meta[0]['ori_shape'] + assert all(_['ori_shape'] == ori_shape for _ in img_meta) + if self.test_cfg.mode == 'slide': + seg_logit = self.slide_inference(img, img_meta, rescale) + else: + seg_logit = self.whole_inference(img, img_meta, rescale) + output = F.softmax(seg_logit, dim=1) + flip = img_meta[0]['flip'] + if flip: + flip_direction = img_meta[0]['flip_direction'] + assert flip_direction in ['horizontal', 'vertical'] + if flip_direction == 'horizontal': + output = output.flip(dims=(3, )) + elif flip_direction == 'vertical': + output = output.flip(dims=(2, )) + + return output + + def simple_test(self, img, img_meta, rescale=True): + """Simple test with single image.""" + seg_logit = self.inference(img, img_meta, rescale) + seg_pred = seg_logit.argmax(dim=1) + if torch.onnx.is_in_onnx_export(): + # our inference backend only support 4D output + seg_pred = seg_pred.unsqueeze(0) + return seg_pred + seg_pred = seg_pred.cpu().numpy() + # unravel batch dim + seg_pred = list(seg_pred) + return seg_pred + + def aug_test(self, imgs, 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(imgs[0], img_metas[0], rescale) + for i in range(1, len(imgs)): + cur_seg_logit = self.inference(imgs[i], img_metas[i], rescale) + seg_logit += cur_seg_logit + seg_logit /= len(imgs) + seg_pred = seg_logit.argmax(dim=1) + seg_pred = seg_pred.cpu().numpy() + # unravel batch dim + seg_pred = list(seg_pred) + return seg_pred diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/__init__.py new file mode 100644 index 0000000..3d3bdd3 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/__init__.py @@ -0,0 +1,13 @@ +from .drop import DropPath +from .inverted_residual import InvertedResidual, InvertedResidualV3 +from .make_divisible import make_divisible +from .res_layer import ResLayer +from .se_layer import SELayer +from .self_attention_block import SelfAttentionBlock +from .up_conv_block import UpConvBlock +from .weight_init import trunc_normal_ + +__all__ = [ + 'ResLayer', 'SelfAttentionBlock', 'make_divisible', 'InvertedResidual', + 'UpConvBlock', 'InvertedResidualV3', 'SELayer', 'DropPath', 'trunc_normal_' +] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/drop.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/drop.py new file mode 100644 index 0000000..4520b0f --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/drop.py @@ -0,0 +1,31 @@ +"""Modified from https://github.com/rwightman/pytorch-image- +models/blob/master/timm/models/layers/drop.py.""" + +import torch +from torch import nn + + +class DropPath(nn.Module): + """Drop paths (Stochastic Depth) per sample (when applied in main path of + residual blocks). + + Args: + drop_prob (float): Drop rate for paths of model. Dropout rate has + to be between 0 and 1. Default: 0. + """ + + def __init__(self, drop_prob=0.): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + self.keep_prob = 1 - drop_prob + + def forward(self, x): + if self.drop_prob == 0. or not self.training: + return x + shape = (x.shape[0], ) + (1, ) * ( + x.ndim - 1) # work with diff dim tensors, not just 2D ConvNets + random_tensor = self.keep_prob + torch.rand( + shape, dtype=x.dtype, device=x.device) + random_tensor.floor_() # binarize + output = x.div(self.keep_prob) * random_tensor + return output diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/inverted_residual.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/inverted_residual.py new file mode 100644 index 0000000..53b8fcd --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/inverted_residual.py @@ -0,0 +1,208 @@ +from annotator.uniformer.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): + super(InvertedResidual, self).__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)) + 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), + ConvModule( + in_channels=hidden_dim, + out_channels=out_channels, + kernel_size=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + ]) + 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(InvertedResidualV3, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/make_divisible.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/make_divisible.py new file mode 100644 index 0000000..75ad756 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/make_divisible.py @@ -0,0 +1,27 @@ +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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/res_layer.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/res_layer.py new file mode 100644 index 0000000..b2c07b4 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/res_layer.py @@ -0,0 +1,94 @@ +from annotator.uniformer.mmcv.cnn import build_conv_layer, build_norm_layer +from torch import nn as nn + + +class ResLayer(nn.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(ResLayer, self).__init__(*layers) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/se_layer.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/se_layer.py new file mode 100644 index 0000000..083bd7d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/se_layer.py @@ -0,0 +1,57 @@ +import annotator.uniformer.mmcv as mmcv +import torch.nn as nn +from annotator.uniformer.mmcv.cnn import ConvModule + +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(SELayer, self).__init__() + if isinstance(act_cfg, dict): + act_cfg = (act_cfg, act_cfg) + assert len(act_cfg) == 2 + assert mmcv.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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/self_attention_block.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/self_attention_block.py new file mode 100644 index 0000000..440c7b7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/self_attention_block.py @@ -0,0 +1,159 @@ +import torch +from annotator.uniformer.mmcv.cnn import ConvModule, 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(SelfAttentionBlock, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/up_conv_block.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/up_conv_block.py new file mode 100644 index 0000000..378469d --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/up_conv_block.py @@ -0,0 +1,101 @@ +import torch +import torch.nn as nn +from annotator.uniformer.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(UpConvBlock, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/weight_init.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/weight_init.py new file mode 100644 index 0000000..38141ba --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/models/utils/weight_init.py @@ -0,0 +1,62 @@ +"""Modified from https://github.com/rwightman/pytorch-image- +models/blob/master/timm/models/layers/drop.py.""" + +import math +import warnings + +import torch + + +def _no_grad_trunc_normal_(tensor, mean, std, a, b): + """Reference: https://people.sc.fsu.edu/~jburkardt/presentations + /truncated_normal.pdf""" + + def norm_cdf(x): + # Computes standard normal cumulative distribution function + return (1. + math.erf(x / math.sqrt(2.))) / 2. + + if (mean < a - 2 * std) or (mean > b + 2 * std): + warnings.warn( + 'mean is more than 2 std from [a, b] in nn.init.trunc_normal_. ' + 'The distribution of values may be incorrect.', + stacklevel=2) + + with torch.no_grad(): + # Values are generated by using a truncated uniform distribution and + # then using the inverse CDF for the normal distribution. + # Get upper and lower cdf values + lower_bound = norm_cdf((a - mean) / std) + upper_bound = norm_cdf((b - mean) / std) + + # Uniformly fill tensor with values from [l, u], then translate to + # [2l-1, 2u-1]. + tensor.uniform_(2 * lower_bound - 1, 2 * upper_bound - 1) + + # Use inverse cdf transform for normal distribution to get truncated + # standard normal + tensor.erfinv_() + + # Transform to proper mean, std + tensor.mul_(std * math.sqrt(2.)) + tensor.add_(mean) + + # Clamp to ensure it's in the proper range + tensor.clamp_(min=a, max=b) + return tensor + + +def trunc_normal_(tensor, mean=0., std=1., a=-2., b=2.): + r"""Fills the input Tensor with values drawn from a truncated + normal distribution. The values are effectively drawn from the + normal distribution :math:`\mathcal{N}(\text{mean}, \text{std}^2)` + with values outside :math:`[a, b]` redrawn until they are within + the bounds. The method used for generating the random values works + best when :math:`a \leq \text{mean} \leq b`. + Args: + tensor (``torch.Tensor``): an n-dimensional `torch.Tensor` + mean (float): the mean of the normal distribution + std (float): the standard deviation of the normal distribution + a (float): the minimum cutoff value + b (float): the maximum cutoff value + """ + return _no_grad_trunc_normal_(tensor, mean, std, a, b) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/__init__.py new file mode 100644 index 0000000..bec51c7 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/__init__.py @@ -0,0 +1,4 @@ +from .encoding import Encoding +from .wrappers import Upsample, resize + +__all__ = ['Upsample', 'resize', 'Encoding'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/encoding.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/encoding.py new file mode 100644 index 0000000..7eb3629 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/encoding.py @@ -0,0 +1,74 @@ +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(Encoding, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/wrappers.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/wrappers.py new file mode 100644 index 0000000..0ed9a0c --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/ops/wrappers.py @@ -0,0 +1,50 @@ +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(Upsample, self).__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/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/__init__.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/__init__.py new file mode 100644 index 0000000..ac489e2 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/__init__.py @@ -0,0 +1,4 @@ +from .collect_env import collect_env +from .logger import get_root_logger + +__all__ = ['get_root_logger', 'collect_env'] diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/collect_env.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/collect_env.py new file mode 100644 index 0000000..65c2134 --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/collect_env.py @@ -0,0 +1,17 @@ +from annotator.uniformer.mmcv.utils import collect_env as collect_base_env +from annotator.uniformer.mmcv.utils import get_git_hash + +import annotator.uniformer.mmseg as 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('{}: {}'.format(name, val)) diff --git a/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/logger.py b/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/logger.py new file mode 100644 index 0000000..4149d9e --- /dev/null +++ b/comparison_models/ControlNet/annotator/uniformer/mmseg/utils/logger.py @@ -0,0 +1,27 @@ +import logging + +from annotator.uniformer.mmcv.utils import get_logger + + +def get_root_logger(log_file=None, log_level=logging.INFO): + """Get the root logger. + + The logger will be initialized if it has not been initialized. By default a + StreamHandler will be added. If `log_file` is specified, a FileHandler will + also be added. The name of the root logger is the top-level package name, + e.g., "mmseg". + + Args: + log_file (str | None): The log filename. If specified, a FileHandler + will be added to the root logger. + log_level (int): The root logger level. Note that only the process of + rank 0 is affected, while other processes will set the level to + "Error" and be silent most of the time. + + Returns: + logging.Logger: The root logger. + """ + + logger = get_logger(name='mmseg', log_file=log_file, log_level=log_level) + + return logger diff --git a/comparison_models/ControlNet/annotator/util.py b/comparison_models/ControlNet/annotator/util.py new file mode 100644 index 0000000..9083164 --- /dev/null +++ b/comparison_models/ControlNet/annotator/util.py @@ -0,0 +1,38 @@ +import numpy as np +import cv2 +import os + + +annotator_ckpts_path = os.path.join(os.path.dirname(__file__), 'ckpts') + + +def HWC3(x): + assert x.dtype == np.uint8 + if x.ndim == 2: + x = x[:, :, None] + assert x.ndim == 3 + H, W, C = x.shape + assert C == 1 or C == 3 or C == 4 + if C == 3: + return x + if C == 1: + return np.concatenate([x, x, x], axis=2) + if C == 4: + color = x[:, :, 0:3].astype(np.float32) + alpha = x[:, :, 3:4].astype(np.float32) / 255.0 + y = color * alpha + 255.0 * (1.0 - alpha) + y = y.clip(0, 255).astype(np.uint8) + return y + + +def resize_image(input_image, resolution): + H, W, C = input_image.shape + H = float(H) + W = float(W) + k = float(resolution) / min(H, W) + H *= k + W *= k + H = int(np.round(H / 64.0)) * 64 + W = int(np.round(W / 64.0)) * 64 + img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4 if k > 1 else cv2.INTER_AREA) + return img diff --git a/comparison_models/ControlNet/cldm/cldm.py b/comparison_models/ControlNet/cldm/cldm.py new file mode 100644 index 0000000..0b3ac7a --- /dev/null +++ b/comparison_models/ControlNet/cldm/cldm.py @@ -0,0 +1,435 @@ +import einops +import torch +import torch as th +import torch.nn as nn + +from ldm.modules.diffusionmodules.util import ( + conv_nd, + linear, + zero_module, + timestep_embedding, +) + +from einops import rearrange, repeat +from torchvision.utils import make_grid +from ldm.modules.attention import SpatialTransformer +from ldm.modules.diffusionmodules.openaimodel import UNetModel, TimestepEmbedSequential, ResBlock, Downsample, AttentionBlock +from ldm.models.diffusion.ddpm import LatentDiffusion +from ldm.util import log_txt_as_img, exists, instantiate_from_config +from ldm.models.diffusion.ddim import DDIMSampler + + +class ControlledUnetModel(UNetModel): + def forward(self, x, timesteps=None, context=None, control=None, only_mid_control=False, **kwargs): + hs = [] + with torch.no_grad(): + t_emb = timestep_embedding(timesteps, self.model_channels, repeat_only=False) + emb = self.time_embed(t_emb) + h = x.type(self.dtype) + for module in self.input_blocks: + h = module(h, emb, context) + hs.append(h) + h = self.middle_block(h, emb, context) + + if control is not None: + h += control.pop() + + for i, module in enumerate(self.output_blocks): + if only_mid_control or control is None: + h = torch.cat([h, hs.pop()], dim=1) + else: + h = torch.cat([h, hs.pop() + control.pop()], dim=1) + h = module(h, emb, context) + + h = h.type(x.dtype) + return self.out(h) + + +class ControlNet(nn.Module): + def __init__( + self, + image_size, + in_channels, + model_channels, + hint_channels, + num_res_blocks, + attention_resolutions, + dropout=0, + channel_mult=(1, 2, 4, 8), + conv_resample=True, + dims=2, + use_checkpoint=False, + use_fp16=False, + num_heads=-1, + num_head_channels=-1, + num_heads_upsample=-1, + use_scale_shift_norm=False, + resblock_updown=False, + use_new_attention_order=False, + use_spatial_transformer=False, # custom transformer support + transformer_depth=1, # custom transformer support + context_dim=None, # custom transformer support + n_embed=None, # custom support for prediction of discrete ids into codebook of first stage vq model + legacy=True, + disable_self_attentions=None, + num_attention_blocks=None, + disable_middle_self_attn=False, + use_linear_in_transformer=False, + ): + super().__init__() + if use_spatial_transformer: + assert context_dim is not None, 'Fool!! You forgot to include the dimension of your cross-attention conditioning...' + + if context_dim is not None: + assert use_spatial_transformer, 'Fool!! You forgot to use the spatial transformer for your cross-attention conditioning...' + from omegaconf.listconfig import ListConfig + if type(context_dim) == ListConfig: + context_dim = list(context_dim) + + if num_heads_upsample == -1: + num_heads_upsample = num_heads + + if num_heads == -1: + assert num_head_channels != -1, 'Either num_heads or num_head_channels has to be set' + + if num_head_channels == -1: + assert num_heads != -1, 'Either num_heads or num_head_channels has to be set' + + self.dims = dims + self.image_size = image_size + self.in_channels = in_channels + self.model_channels = model_channels + if isinstance(num_res_blocks, int): + self.num_res_blocks = len(channel_mult) * [num_res_blocks] + else: + if len(num_res_blocks) != len(channel_mult): + raise ValueError("provide num_res_blocks either as an int (globally constant) or " + "as a list/tuple (per-level) with the same length as channel_mult") + self.num_res_blocks = num_res_blocks + if disable_self_attentions is not None: + # should be a list of booleans, indicating whether to disable self-attention in TransformerBlocks or not + assert len(disable_self_attentions) == len(channel_mult) + if num_attention_blocks is not None: + assert len(num_attention_blocks) == len(self.num_res_blocks) + assert all(map(lambda i: self.num_res_blocks[i] >= num_attention_blocks[i], range(len(num_attention_blocks)))) + print(f"Constructor of UNetModel received num_attention_blocks={num_attention_blocks}. " + f"This option has LESS priority than attention_resolutions {attention_resolutions}, " + f"i.e., in cases where num_attention_blocks[i] > 0 but 2**i not in attention_resolutions, " + f"attention will still not be set.") + + self.attention_resolutions = attention_resolutions + self.dropout = dropout + self.channel_mult = channel_mult + self.conv_resample = conv_resample + self.use_checkpoint = use_checkpoint + self.dtype = th.float16 if use_fp16 else th.float32 + self.num_heads = num_heads + self.num_head_channels = num_head_channels + self.num_heads_upsample = num_heads_upsample + self.predict_codebook_ids = n_embed is not None + + time_embed_dim = model_channels * 4 + self.time_embed = nn.Sequential( + linear(model_channels, time_embed_dim), + nn.SiLU(), + linear(time_embed_dim, time_embed_dim), + ) + + self.input_blocks = nn.ModuleList( + [ + TimestepEmbedSequential( + conv_nd(dims, in_channels, model_channels, 3, padding=1) + ) + ] + ) + self.zero_convs = nn.ModuleList([self.make_zero_conv(model_channels)]) + + self.input_hint_block = TimestepEmbedSequential( + conv_nd(dims, hint_channels, 16, 3, padding=1), + nn.SiLU(), + conv_nd(dims, 16, 16, 3, padding=1), + nn.SiLU(), + conv_nd(dims, 16, 32, 3, padding=1, stride=2), + nn.SiLU(), + conv_nd(dims, 32, 32, 3, padding=1), + nn.SiLU(), + conv_nd(dims, 32, 96, 3, padding=1, stride=2), + nn.SiLU(), + conv_nd(dims, 96, 96, 3, padding=1), + nn.SiLU(), + conv_nd(dims, 96, 256, 3, padding=1, stride=2), + nn.SiLU(), + zero_module(conv_nd(dims, 256, model_channels, 3, padding=1)) + ) + + self._feature_size = model_channels + input_block_chans = [model_channels] + ch = model_channels + ds = 1 + for level, mult in enumerate(channel_mult): + for nr in range(self.num_res_blocks[level]): + layers = [ + ResBlock( + ch, + time_embed_dim, + dropout, + out_channels=mult * model_channels, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ) + ] + ch = mult * model_channels + if ds in attention_resolutions: + if num_head_channels == -1: + dim_head = ch // num_heads + else: + num_heads = ch // num_head_channels + dim_head = num_head_channels + if legacy: + # num_heads = 1 + dim_head = ch // num_heads if use_spatial_transformer else num_head_channels + if exists(disable_self_attentions): + disabled_sa = disable_self_attentions[level] + else: + disabled_sa = False + + if not exists(num_attention_blocks) or nr < num_attention_blocks[level]: + layers.append( + AttentionBlock( + ch, + use_checkpoint=use_checkpoint, + num_heads=num_heads, + num_head_channels=dim_head, + use_new_attention_order=use_new_attention_order, + ) if not use_spatial_transformer else SpatialTransformer( + ch, num_heads, dim_head, depth=transformer_depth, context_dim=context_dim, + disable_self_attn=disabled_sa, use_linear=use_linear_in_transformer, + use_checkpoint=use_checkpoint + ) + ) + self.input_blocks.append(TimestepEmbedSequential(*layers)) + self.zero_convs.append(self.make_zero_conv(ch)) + self._feature_size += ch + input_block_chans.append(ch) + if level != len(channel_mult) - 1: + out_ch = ch + self.input_blocks.append( + TimestepEmbedSequential( + ResBlock( + ch, + time_embed_dim, + dropout, + out_channels=out_ch, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + down=True, + ) + if resblock_updown + else Downsample( + ch, conv_resample, dims=dims, out_channels=out_ch + ) + ) + ) + ch = out_ch + input_block_chans.append(ch) + self.zero_convs.append(self.make_zero_conv(ch)) + ds *= 2 + self._feature_size += ch + + if num_head_channels == -1: + dim_head = ch // num_heads + else: + num_heads = ch // num_head_channels + dim_head = num_head_channels + if legacy: + # num_heads = 1 + dim_head = ch // num_heads if use_spatial_transformer else num_head_channels + self.middle_block = TimestepEmbedSequential( + ResBlock( + ch, + time_embed_dim, + dropout, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ), + AttentionBlock( + ch, + use_checkpoint=use_checkpoint, + num_heads=num_heads, + num_head_channels=dim_head, + use_new_attention_order=use_new_attention_order, + ) if not use_spatial_transformer else SpatialTransformer( # always uses a self-attn + ch, num_heads, dim_head, depth=transformer_depth, context_dim=context_dim, + disable_self_attn=disable_middle_self_attn, use_linear=use_linear_in_transformer, + use_checkpoint=use_checkpoint + ), + ResBlock( + ch, + time_embed_dim, + dropout, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ), + ) + self.middle_block_out = self.make_zero_conv(ch) + self._feature_size += ch + + def make_zero_conv(self, channels): + return TimestepEmbedSequential(zero_module(conv_nd(self.dims, channels, channels, 1, padding=0))) + + def forward(self, x, hint, timesteps, context, **kwargs): + t_emb = timestep_embedding(timesteps, self.model_channels, repeat_only=False) + emb = self.time_embed(t_emb) + + guided_hint = self.input_hint_block(hint, emb, context) + + outs = [] + + h = x.type(self.dtype) + for module, zero_conv in zip(self.input_blocks, self.zero_convs): + if guided_hint is not None: + h = module(h, emb, context) + h += guided_hint + guided_hint = None + else: + h = module(h, emb, context) + outs.append(zero_conv(h, emb, context)) + + h = self.middle_block(h, emb, context) + outs.append(self.middle_block_out(h, emb, context)) + + return outs + + +class ControlLDM(LatentDiffusion): + + def __init__(self, control_stage_config, control_key, only_mid_control, *args, **kwargs): + super().__init__(*args, **kwargs) + self.control_model = instantiate_from_config(control_stage_config) + self.control_key = control_key + self.only_mid_control = only_mid_control + self.control_scales = [1.0] * 13 + + @torch.no_grad() + def get_input(self, batch, k, bs=None, *args, **kwargs): + x, c = super().get_input(batch, self.first_stage_key, *args, **kwargs) + control = batch[self.control_key] + if bs is not None: + control = control[:bs] + control = control.to(self.device) + control = einops.rearrange(control, 'b h w c -> b c h w') + control = control.to(memory_format=torch.contiguous_format).float() + return x, dict(c_crossattn=[c], c_concat=[control]) + + def apply_model(self, x_noisy, t, cond, *args, **kwargs): + assert isinstance(cond, dict) + diffusion_model = self.model.diffusion_model + + cond_txt = torch.cat(cond['c_crossattn'], 1) + + if cond['c_concat'] is None: + eps = diffusion_model(x=x_noisy, timesteps=t, context=cond_txt, control=None, only_mid_control=self.only_mid_control) + else: + control = self.control_model(x=x_noisy, hint=torch.cat(cond['c_concat'], 1), timesteps=t, context=cond_txt) + control = [c * scale for c, scale in zip(control, self.control_scales)] + eps = diffusion_model(x=x_noisy, timesteps=t, context=cond_txt, control=control, only_mid_control=self.only_mid_control) + + return eps + + @torch.no_grad() + def get_unconditional_conditioning(self, N): + return self.get_learned_conditioning([""] * N) + + @torch.no_grad() + def log_images(self, batch, N=4, n_row=2, sample=False, ddim_steps=50, ddim_eta=0.0, return_keys=None, + quantize_denoised=True, inpaint=True, plot_denoise_rows=False, plot_progressive_rows=True, + plot_diffusion_rows=False, unconditional_guidance_scale=9.0, unconditional_guidance_label=None, + use_ema_scope=True, + **kwargs): + use_ddim = ddim_steps is not None + + log = dict() + z, c = self.get_input(batch, self.first_stage_key, bs=N) + c_cat, c = c["c_concat"][0][:N], c["c_crossattn"][0][:N] + N = min(z.shape[0], N) + n_row = min(z.shape[0], n_row) + log["reconstruction"] = self.decode_first_stage(z) + log["control"] = c_cat * 2.0 - 1.0 + log["conditioning"] = log_txt_as_img((512, 512), batch[self.cond_stage_key], size=16) + + if plot_diffusion_rows: + # get diffusion row + diffusion_row = list() + z_start = z[:n_row] + for t in range(self.num_timesteps): + if t % self.log_every_t == 0 or t == self.num_timesteps - 1: + t = repeat(torch.tensor([t]), '1 -> b', b=n_row) + t = t.to(self.device).long() + noise = torch.randn_like(z_start) + z_noisy = self.q_sample(x_start=z_start, t=t, noise=noise) + diffusion_row.append(self.decode_first_stage(z_noisy)) + + diffusion_row = torch.stack(diffusion_row) # n_log_step, n_row, C, H, W + diffusion_grid = rearrange(diffusion_row, 'n b c h w -> b n c h w') + diffusion_grid = rearrange(diffusion_grid, 'b n c h w -> (b n) c h w') + diffusion_grid = make_grid(diffusion_grid, nrow=diffusion_row.shape[0]) + log["diffusion_row"] = diffusion_grid + + if sample: + # get denoise row + samples, z_denoise_row = self.sample_log(cond={"c_concat": [c_cat], "c_crossattn": [c]}, + batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta) + x_samples = self.decode_first_stage(samples) + log["samples"] = x_samples + if plot_denoise_rows: + denoise_grid = self._get_denoise_row_from_list(z_denoise_row) + log["denoise_row"] = denoise_grid + + if unconditional_guidance_scale > 1.0: + uc_cross = self.get_unconditional_conditioning(N) + uc_cat = c_cat # torch.zeros_like(c_cat) + uc_full = {"c_concat": [uc_cat], "c_crossattn": [uc_cross]} + samples_cfg, _ = self.sample_log(cond={"c_concat": [c_cat], "c_crossattn": [c]}, + batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=uc_full, + ) + x_samples_cfg = self.decode_first_stage(samples_cfg) + log[f"samples_cfg_scale_{unconditional_guidance_scale:.2f}"] = x_samples_cfg + + return log + + @torch.no_grad() + def sample_log(self, cond, batch_size, ddim, ddim_steps, **kwargs): + ddim_sampler = DDIMSampler(self) + b, c, h, w = cond["c_concat"][0].shape + shape = (self.channels, h // 8, w // 8) + samples, intermediates = ddim_sampler.sample(ddim_steps, batch_size, shape, cond, verbose=False, **kwargs) + return samples, intermediates + + def configure_optimizers(self): + lr = self.learning_rate + params = list(self.control_model.parameters()) + if not self.sd_locked: + params += list(self.model.diffusion_model.output_blocks.parameters()) + params += list(self.model.diffusion_model.out.parameters()) + opt = torch.optim.AdamW(params, lr=lr) + return opt + + def low_vram_shift(self, is_diffusing): + if is_diffusing: + self.model = self.model.cuda() + self.control_model = self.control_model.cuda() + self.first_stage_model = self.first_stage_model.cpu() + self.cond_stage_model = self.cond_stage_model.cpu() + else: + self.model = self.model.cpu() + self.control_model = self.control_model.cpu() + self.first_stage_model = self.first_stage_model.cuda() + self.cond_stage_model = self.cond_stage_model.cuda() diff --git a/comparison_models/ControlNet/cldm/ddim_hacked.py b/comparison_models/ControlNet/cldm/ddim_hacked.py new file mode 100644 index 0000000..25b1bc9 --- /dev/null +++ b/comparison_models/ControlNet/cldm/ddim_hacked.py @@ -0,0 +1,317 @@ +"""SAMPLING ONLY.""" + +import torch +import numpy as np +from tqdm import tqdm + +from ldm.modules.diffusionmodules.util import make_ddim_sampling_parameters, make_ddim_timesteps, noise_like, extract_into_tensor + + +class DDIMSampler(object): + def __init__(self, model, schedule="linear", **kwargs): + super().__init__() + self.model = model + self.ddpm_num_timesteps = model.num_timesteps + self.schedule = schedule + + def register_buffer(self, name, attr): + if type(attr) == torch.Tensor: + if attr.device != torch.device("cuda"): + attr = attr.to(torch.device("cuda")) + setattr(self, name, attr) + + def make_schedule(self, ddim_num_steps, ddim_discretize="uniform", ddim_eta=0., verbose=True): + self.ddim_timesteps = make_ddim_timesteps(ddim_discr_method=ddim_discretize, num_ddim_timesteps=ddim_num_steps, + num_ddpm_timesteps=self.ddpm_num_timesteps,verbose=verbose) + alphas_cumprod = self.model.alphas_cumprod + assert alphas_cumprod.shape[0] == self.ddpm_num_timesteps, 'alphas have to be defined for each timestep' + to_torch = lambda x: x.clone().detach().to(torch.float32).to(self.model.device) + + self.register_buffer('betas', to_torch(self.model.betas)) + self.register_buffer('alphas_cumprod', to_torch(alphas_cumprod)) + self.register_buffer('alphas_cumprod_prev', to_torch(self.model.alphas_cumprod_prev)) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod.cpu()))) + self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod.cpu()))) + self.register_buffer('log_one_minus_alphas_cumprod', to_torch(np.log(1. - alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recip_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recipm1_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu() - 1))) + + # ddim sampling parameters + ddim_sigmas, ddim_alphas, ddim_alphas_prev = make_ddim_sampling_parameters(alphacums=alphas_cumprod.cpu(), + ddim_timesteps=self.ddim_timesteps, + eta=ddim_eta,verbose=verbose) + self.register_buffer('ddim_sigmas', ddim_sigmas) + self.register_buffer('ddim_alphas', ddim_alphas) + self.register_buffer('ddim_alphas_prev', ddim_alphas_prev) + self.register_buffer('ddim_sqrt_one_minus_alphas', np.sqrt(1. - ddim_alphas)) + sigmas_for_original_sampling_steps = ddim_eta * torch.sqrt( + (1 - self.alphas_cumprod_prev) / (1 - self.alphas_cumprod) * ( + 1 - self.alphas_cumprod / self.alphas_cumprod_prev)) + self.register_buffer('ddim_sigmas_for_original_num_steps', sigmas_for_original_sampling_steps) + + @torch.no_grad() + def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + dynamic_threshold=None, + ucg_schedule=None, + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + ctmp = conditioning[list(conditioning.keys())[0]] + while isinstance(ctmp, list): ctmp = ctmp[0] + cbs = ctmp.shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + + elif isinstance(conditioning, list): + for ctmp in conditioning: + if ctmp.shape[0] != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose) + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + print(f'Data shape for DDIM sampling is {size}, eta {eta}') + + samples, intermediates = self.ddim_sampling(conditioning, size, + callback=callback, + img_callback=img_callback, + quantize_denoised=quantize_x0, + mask=mask, x0=x0, + ddim_use_original_steps=False, + noise_dropout=noise_dropout, + temperature=temperature, + score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + x_T=x_T, + log_every_t=log_every_t, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + dynamic_threshold=dynamic_threshold, + ucg_schedule=ucg_schedule + ) + return samples, intermediates + + @torch.no_grad() + def ddim_sampling(self, cond, shape, + x_T=None, ddim_use_original_steps=False, + callback=None, timesteps=None, quantize_denoised=False, + mask=None, x0=None, img_callback=None, log_every_t=100, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, dynamic_threshold=None, + ucg_schedule=None): + device = self.model.betas.device + b = shape[0] + if x_T is None: + img = torch.randn(shape, device=device) + else: + img = x_T + + if timesteps is None: + timesteps = self.ddpm_num_timesteps if ddim_use_original_steps else self.ddim_timesteps + elif timesteps is not None and not ddim_use_original_steps: + subset_end = int(min(timesteps / self.ddim_timesteps.shape[0], 1) * self.ddim_timesteps.shape[0]) - 1 + timesteps = self.ddim_timesteps[:subset_end] + + intermediates = {'x_inter': [img], 'pred_x0': [img]} + time_range = reversed(range(0,timesteps)) if ddim_use_original_steps else np.flip(timesteps) + total_steps = timesteps if ddim_use_original_steps else timesteps.shape[0] + print(f"Running DDIM Sampling with {total_steps} timesteps") + + iterator = tqdm(time_range, desc='DDIM Sampler', total=total_steps) + + for i, step in enumerate(iterator): + index = total_steps - i - 1 + ts = torch.full((b,), step, device=device, dtype=torch.long) + + if mask is not None: + assert x0 is not None + img_orig = self.model.q_sample(x0, ts) # TODO: deterministic forward pass? + img = img_orig * mask + (1. - mask) * img + + if ucg_schedule is not None: + assert len(ucg_schedule) == len(time_range) + unconditional_guidance_scale = ucg_schedule[i] + + outs = self.p_sample_ddim(img, cond, ts, index=index, use_original_steps=ddim_use_original_steps, + quantize_denoised=quantize_denoised, temperature=temperature, + noise_dropout=noise_dropout, score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + dynamic_threshold=dynamic_threshold) + img, pred_x0 = outs + if callback: callback(i) + if img_callback: img_callback(pred_x0, i) + + if index % log_every_t == 0 or index == total_steps - 1: + intermediates['x_inter'].append(img) + intermediates['pred_x0'].append(pred_x0) + + return img, intermediates + + @torch.no_grad() + def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, + dynamic_threshold=None): + b, *_, device = *x.shape, x.device + + if unconditional_conditioning is None or unconditional_guidance_scale == 1.: + model_output = self.model.apply_model(x, t, c) + else: + model_t = self.model.apply_model(x, t, c) + model_uncond = self.model.apply_model(x, t, unconditional_conditioning) + model_output = model_uncond + unconditional_guidance_scale * (model_t - model_uncond) + + if self.model.parameterization == "v": + e_t = self.model.predict_eps_from_z_and_v(x, t, model_output) + else: + e_t = model_output + + if score_corrector is not None: + assert self.model.parameterization == "eps", 'not implemented' + e_t = score_corrector.modify_score(self.model, e_t, x, t, c, **corrector_kwargs) + + alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas + alphas_prev = self.model.alphas_cumprod_prev if use_original_steps else self.ddim_alphas_prev + sqrt_one_minus_alphas = self.model.sqrt_one_minus_alphas_cumprod if use_original_steps else self.ddim_sqrt_one_minus_alphas + sigmas = self.model.ddim_sigmas_for_original_num_steps if use_original_steps else self.ddim_sigmas + # select parameters corresponding to the currently considered timestep + a_t = torch.full((b, 1, 1, 1), alphas[index], device=device) + a_prev = torch.full((b, 1, 1, 1), alphas_prev[index], device=device) + sigma_t = torch.full((b, 1, 1, 1), sigmas[index], device=device) + sqrt_one_minus_at = torch.full((b, 1, 1, 1), sqrt_one_minus_alphas[index],device=device) + + # current prediction for x_0 + if self.model.parameterization != "v": + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + else: + pred_x0 = self.model.predict_start_from_z_and_v(x, t, model_output) + + if quantize_denoised: + pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0) + + if dynamic_threshold is not None: + raise NotImplementedError() + + # direction pointing to x_t + dir_xt = (1. - a_prev - sigma_t**2).sqrt() * e_t + noise = sigma_t * noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + x_prev = a_prev.sqrt() * pred_x0 + dir_xt + noise + return x_prev, pred_x0 + + @torch.no_grad() + def encode(self, x0, c, t_enc, use_original_steps=False, return_intermediates=None, + unconditional_guidance_scale=1.0, unconditional_conditioning=None, callback=None): + timesteps = np.arange(self.ddpm_num_timesteps) if use_original_steps else self.ddim_timesteps + num_reference_steps = timesteps.shape[0] + + assert t_enc <= num_reference_steps + num_steps = t_enc + + if use_original_steps: + alphas_next = self.alphas_cumprod[:num_steps] + alphas = self.alphas_cumprod_prev[:num_steps] + else: + alphas_next = self.ddim_alphas[:num_steps] + alphas = torch.tensor(self.ddim_alphas_prev[:num_steps]) + + x_next = x0 + intermediates = [] + inter_steps = [] + for i in tqdm(range(num_steps), desc='Encoding Image'): + t = torch.full((x0.shape[0],), timesteps[i], device=self.model.device, dtype=torch.long) + if unconditional_guidance_scale == 1.: + noise_pred = self.model.apply_model(x_next, t, c) + else: + assert unconditional_conditioning is not None + e_t_uncond, noise_pred = torch.chunk( + self.model.apply_model(torch.cat((x_next, x_next)), torch.cat((t, t)), + torch.cat((unconditional_conditioning, c))), 2) + noise_pred = e_t_uncond + unconditional_guidance_scale * (noise_pred - e_t_uncond) + + xt_weighted = (alphas_next[i] / alphas[i]).sqrt() * x_next + weighted_noise_pred = alphas_next[i].sqrt() * ( + (1 / alphas_next[i] - 1).sqrt() - (1 / alphas[i] - 1).sqrt()) * noise_pred + x_next = xt_weighted + weighted_noise_pred + if return_intermediates and i % ( + num_steps // return_intermediates) == 0 and i < num_steps - 1: + intermediates.append(x_next) + inter_steps.append(i) + elif return_intermediates and i >= num_steps - 2: + intermediates.append(x_next) + inter_steps.append(i) + if callback: callback(i) + + out = {'x_encoded': x_next, 'intermediate_steps': inter_steps} + if return_intermediates: + out.update({'intermediates': intermediates}) + return x_next, out + + @torch.no_grad() + def stochastic_encode(self, x0, t, use_original_steps=False, noise=None): + # fast, but does not allow for exact reconstruction + # t serves as an index to gather the correct alphas + if use_original_steps: + sqrt_alphas_cumprod = self.sqrt_alphas_cumprod + sqrt_one_minus_alphas_cumprod = self.sqrt_one_minus_alphas_cumprod + else: + sqrt_alphas_cumprod = torch.sqrt(self.ddim_alphas) + sqrt_one_minus_alphas_cumprod = self.ddim_sqrt_one_minus_alphas + + if noise is None: + noise = torch.randn_like(x0) + return (extract_into_tensor(sqrt_alphas_cumprod, t, x0.shape) * x0 + + extract_into_tensor(sqrt_one_minus_alphas_cumprod, t, x0.shape) * noise) + + @torch.no_grad() + def decode(self, x_latent, cond, t_start, unconditional_guidance_scale=1.0, unconditional_conditioning=None, + use_original_steps=False, callback=None): + + timesteps = np.arange(self.ddpm_num_timesteps) if use_original_steps else self.ddim_timesteps + timesteps = timesteps[:t_start] + + time_range = np.flip(timesteps) + total_steps = timesteps.shape[0] + print(f"Running DDIM Sampling with {total_steps} timesteps") + + iterator = tqdm(time_range, desc='Decoding image', total=total_steps) + x_dec = x_latent + for i, step in enumerate(iterator): + index = total_steps - i - 1 + ts = torch.full((x_latent.shape[0],), step, device=x_latent.device, dtype=torch.long) + x_dec, _ = self.p_sample_ddim(x_dec, cond, ts, index=index, use_original_steps=use_original_steps, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning) + if callback: callback(i) + return x_dec diff --git a/comparison_models/ControlNet/cldm/hack.py b/comparison_models/ControlNet/cldm/hack.py new file mode 100644 index 0000000..454361e --- /dev/null +++ b/comparison_models/ControlNet/cldm/hack.py @@ -0,0 +1,111 @@ +import torch +import einops + +import ldm.modules.encoders.modules +import ldm.modules.attention + +from transformers import logging +from ldm.modules.attention import default + + +def disable_verbosity(): + logging.set_verbosity_error() + print('logging improved.') + return + + +def enable_sliced_attention(): + ldm.modules.attention.CrossAttention.forward = _hacked_sliced_attentin_forward + print('Enabled sliced_attention.') + return + + +def hack_everything(clip_skip=0): + disable_verbosity() + ldm.modules.encoders.modules.FrozenCLIPEmbedder.forward = _hacked_clip_forward + ldm.modules.encoders.modules.FrozenCLIPEmbedder.clip_skip = clip_skip + print('Enabled clip hacks.') + return + + +# Written by Lvmin +def _hacked_clip_forward(self, text): + PAD = self.tokenizer.pad_token_id + EOS = self.tokenizer.eos_token_id + BOS = self.tokenizer.bos_token_id + + def tokenize(t): + return self.tokenizer(t, truncation=False, add_special_tokens=False)["input_ids"] + + def transformer_encode(t): + if self.clip_skip > 1: + rt = self.transformer(input_ids=t, output_hidden_states=True) + return self.transformer.text_model.final_layer_norm(rt.hidden_states[-self.clip_skip]) + else: + return self.transformer(input_ids=t, output_hidden_states=False).last_hidden_state + + def split(x): + return x[75 * 0: 75 * 1], x[75 * 1: 75 * 2], x[75 * 2: 75 * 3] + + def pad(x, p, i): + return x[:i] if len(x) >= i else x + [p] * (i - len(x)) + + raw_tokens_list = tokenize(text) + tokens_list = [] + + for raw_tokens in raw_tokens_list: + raw_tokens_123 = split(raw_tokens) + raw_tokens_123 = [[BOS] + raw_tokens_i + [EOS] for raw_tokens_i in raw_tokens_123] + raw_tokens_123 = [pad(raw_tokens_i, PAD, 77) for raw_tokens_i in raw_tokens_123] + tokens_list.append(raw_tokens_123) + + tokens_list = torch.IntTensor(tokens_list).to(self.device) + + feed = einops.rearrange(tokens_list, 'b f i -> (b f) i') + y = transformer_encode(feed) + z = einops.rearrange(y, '(b f) i c -> b (f i) c', f=3) + + return z + + +# Stolen from https://github.com/basujindal/stable-diffusion/blob/main/optimizedSD/splitAttention.py +def _hacked_sliced_attentin_forward(self, x, context=None, mask=None): + h = self.heads + + q = self.to_q(x) + context = default(context, x) + k = self.to_k(context) + v = self.to_v(context) + del context, x + + q, k, v = map(lambda t: einops.rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v)) + + limit = k.shape[0] + att_step = 1 + q_chunks = list(torch.tensor_split(q, limit // att_step, dim=0)) + k_chunks = list(torch.tensor_split(k, limit // att_step, dim=0)) + v_chunks = list(torch.tensor_split(v, limit // att_step, dim=0)) + + q_chunks.reverse() + k_chunks.reverse() + v_chunks.reverse() + sim = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device) + del k, q, v + for i in range(0, limit, att_step): + q_buffer = q_chunks.pop() + k_buffer = k_chunks.pop() + v_buffer = v_chunks.pop() + sim_buffer = torch.einsum('b i d, b j d -> b i j', q_buffer, k_buffer) * self.scale + + del k_buffer, q_buffer + # attention, what we cannot get enough of, by chunks + + sim_buffer = sim_buffer.softmax(dim=-1) + + sim_buffer = torch.einsum('b i j, b j d -> b i d', sim_buffer, v_buffer) + del v_buffer + sim[i:i + att_step, :, :] = sim_buffer + + del sim_buffer + sim = einops.rearrange(sim, '(b h) n d -> b n (h d)', h=h) + return self.to_out(sim) diff --git a/comparison_models/ControlNet/cldm/logger.py b/comparison_models/ControlNet/cldm/logger.py new file mode 100644 index 0000000..6a88038 --- /dev/null +++ b/comparison_models/ControlNet/cldm/logger.py @@ -0,0 +1,76 @@ +import os + +import numpy as np +import torch +import torchvision +from PIL import Image +from pytorch_lightning.callbacks import Callback +from pytorch_lightning.utilities.distributed import rank_zero_only + + +class ImageLogger(Callback): + def __init__(self, batch_frequency=2000, max_images=4, clamp=True, increase_log_steps=True, + rescale=True, disabled=False, log_on_batch_idx=False, log_first_step=False, + log_images_kwargs=None): + super().__init__() + self.rescale = rescale + self.batch_freq = batch_frequency + self.max_images = max_images + if not increase_log_steps: + self.log_steps = [self.batch_freq] + self.clamp = clamp + self.disabled = disabled + self.log_on_batch_idx = log_on_batch_idx + self.log_images_kwargs = log_images_kwargs if log_images_kwargs else {} + self.log_first_step = log_first_step + + @rank_zero_only + def log_local(self, save_dir, split, images, global_step, current_epoch, batch_idx): + root = os.path.join(save_dir, "image_log", split) + for k in images: + grid = torchvision.utils.make_grid(images[k], nrow=4) + if self.rescale: + grid = (grid + 1.0) / 2.0 # -1,1 -> 0,1; c,h,w + grid = grid.transpose(0, 1).transpose(1, 2).squeeze(-1) + grid = grid.numpy() + grid = (grid * 255).astype(np.uint8) + filename = "{}_gs-{:06}_e-{:06}_b-{:06}.png".format(k, global_step, current_epoch, batch_idx) + path = os.path.join(root, filename) + os.makedirs(os.path.split(path)[0], exist_ok=True) + Image.fromarray(grid).save(path) + + def log_img(self, pl_module, batch, batch_idx, split="train"): + check_idx = batch_idx # if self.log_on_batch_idx else pl_module.global_step + if (self.check_frequency(check_idx) and # batch_idx % self.batch_freq == 0 + hasattr(pl_module, "log_images") and + callable(pl_module.log_images) and + self.max_images > 0): + logger = type(pl_module.logger) + + is_train = pl_module.training + if is_train: + pl_module.eval() + + with torch.no_grad(): + images = pl_module.log_images(batch, split=split, **self.log_images_kwargs) + + for k in images: + N = min(images[k].shape[0], self.max_images) + images[k] = images[k][:N] + if isinstance(images[k], torch.Tensor): + images[k] = images[k].detach().cpu() + if self.clamp: + images[k] = torch.clamp(images[k], -1., 1.) + + self.log_local(pl_module.logger.save_dir, split, images, + pl_module.global_step, pl_module.current_epoch, batch_idx) + + if is_train: + pl_module.train() + + def check_frequency(self, check_idx): + return check_idx % self.batch_freq == 0 + + def on_train_batch_end(self, trainer, pl_module, outputs, batch, batch_idx, dataloader_idx): + if not self.disabled: + self.log_img(pl_module, batch, batch_idx, split="train") diff --git a/comparison_models/ControlNet/cldm/model.py b/comparison_models/ControlNet/cldm/model.py new file mode 100644 index 0000000..fed3c31 --- /dev/null +++ b/comparison_models/ControlNet/cldm/model.py @@ -0,0 +1,28 @@ +import os +import torch + +from omegaconf import OmegaConf +from ldm.util import instantiate_from_config + + +def get_state_dict(d): + return d.get('state_dict', d) + + +def load_state_dict(ckpt_path, location='cpu'): + _, extension = os.path.splitext(ckpt_path) + if extension.lower() == ".safetensors": + import safetensors.torch + state_dict = safetensors.torch.load_file(ckpt_path, device=location) + else: + state_dict = get_state_dict(torch.load(ckpt_path, map_location=torch.device(location))) + state_dict = get_state_dict(state_dict) + print(f'Loaded state_dict from [{ckpt_path}]') + return state_dict + + +def create_model(config_path): + config = OmegaConf.load(config_path) + model = instantiate_from_config(config.model).cpu() + print(f'Loaded model config from [{config_path}]') + return model diff --git a/comparison_models/ControlNet/config.py b/comparison_models/ControlNet/config.py new file mode 100644 index 0000000..e0c738d --- /dev/null +++ b/comparison_models/ControlNet/config.py @@ -0,0 +1 @@ +save_memory = False diff --git a/comparison_models/ControlNet/docs/annotator.md b/comparison_models/ControlNet/docs/annotator.md new file mode 100644 index 0000000..5111678 --- /dev/null +++ b/comparison_models/ControlNet/docs/annotator.md @@ -0,0 +1,49 @@ +# Automatic Annotations + +We provide gradio examples to obtain annotations that are aligned to our pretrained production-ready models. + +Just run + + python gradio_annotator.py + +Since everyone has different habit to organize their datasets, we do not hard code any scripts for batch processing. But "gradio_annotator.py" is written in a super readable way, and modifying it to annotate your images should be easy. + +In the gradio UI of "gradio_annotator.py" we have the following interfaces: + +### Canny Edge + +Be careful about "black edge and white background" or "white edge and black background". + +![p](../github_page/a1.png) + +### HED Edge + +Be careful about "black edge and white background" or "white edge and black background". + +![p](../github_page/a2.png) + +### MLSD Edge + +Be careful about "black edge and white background" or "white edge and black background". + +![p](../github_page/a3.png) + +### MIDAS Depth and Normal + +Be careful about RGB or BGR in normal maps. + +![p](../github_page/a4.png) + +### Openpose + +Be careful about RGB or BGR in pose maps. + +For our production-ready model, the hand pose option is turned off. + +![p](../github_page/a5.png) + +### Uniformer Segmentation + +Be careful about RGB or BGR in segmentation maps. + +![p](../github_page/a6.png) diff --git a/comparison_models/ControlNet/docs/faq.md b/comparison_models/ControlNet/docs/faq.md new file mode 100644 index 0000000..07afd7a --- /dev/null +++ b/comparison_models/ControlNet/docs/faq.md @@ -0,0 +1,21 @@ +# FAQs + +**Q:** If the weight of a conv layer is zero, the gradient will also be zero, and the network will not learn anything. Why "zero convolution" works? + +**A:** This is wrong. Let us consider a very simple + +$$y=wx+b$$ + +and we have + +$$\partial y/\partial w=x, \partial y/\partial x=w, \partial y/\partial b=1$$ + +and if $w=0$ and $x \neq 0$, then + +$$\partial y/\partial w \neq 0, \partial y/\partial x=0, \partial y/\partial b\neq 0$$ + +which means as long as $x \neq 0$, one gradient descent iteration will make $w$ non-zero. Then + +$$\partial y/\partial x\neq 0$$ + +so that the zero convolutions will progressively become a common conv layer with non-zero weights. diff --git a/comparison_models/ControlNet/docs/low_vram.md b/comparison_models/ControlNet/docs/low_vram.md new file mode 100644 index 0000000..784964c --- /dev/null +++ b/comparison_models/ControlNet/docs/low_vram.md @@ -0,0 +1,15 @@ +# Enable Low VRAM Mode + +If you are using 8GB GPU card (or if you want larger batch size), please open "config.py", and then set + +```python +save_memory = True +``` + +This feature is still being tested - not all graphics cards are guaranteed to succeed. + +But it should be neat as I can diffuse at a batch size of 12 now. + +(prompt "man") + +![p](../github_page/ram12.jpg) diff --git a/comparison_models/ControlNet/docs/train.md b/comparison_models/ControlNet/docs/train.md new file mode 100644 index 0000000..fa77392 --- /dev/null +++ b/comparison_models/ControlNet/docs/train.md @@ -0,0 +1,276 @@ +# Train a ControlNet to Control SD + +You are here because you want to control SD in your own way, maybe you have an idea for your perfect research project, and you will annotate some data or have already annotated your own dataset automatically or manually. Herein, the control can be anything that can be converted to images, such as edges, keypoints, segments, etc. + +Before moving on to your own dataset, we highly recommend to first try the toy dataset, Fill50K, as a sanity check. This will help you get a "feeling" for the training. You will know how long it will take for the model to converge and whether your device will be able to complete the training in an acceptable amount of time. And what it "feels" like when the model converges. + +We hope that after you read this page, you will find that training a ControlNet is as easy as (or easier than) training a pix2pix. + +## Step 0 - Design your control + +Let us take a look at a very simple task to control SD to fill color in circles. + +![p](../github_page/t1.png) + +This is simple: we want to control SD to fill a circle with colors, and the prompt contains some description of our target. + +Stable diffusion is trained on billions of images, and it already knows what is "cyan", what is "circle", what is "pink", and what is "background". + +But it does not know the meaning of that "Control Image (Source Image)". Our target is to let it know. + +## Step 1 - Get a dataset + +Just download the Fill50K dataset from [our huggingface page](https://huggingface.co/lllyasviel/ControlNet) (training/fill50k.zip, the file is only 200M!). Make sure that the data is decompressed as + + ControlNet/training/fill50k/prompt.json + ControlNet/training/fill50k/source/X.png + ControlNet/training/fill50k/target/X.png + +In the folder "fill50k/source", you will have 50k images of circle lines. + +![p](../github_page/t2.png) + +In the folder "fill50k/target", you will have 50k images of filled circles. + +![p](../github_page/t3.png) + +In the "fill50k/prompt.json", you will have their filenames and prompts. Each prompt is like "a balabala color circle in some other color background." + +![p](../github_page/t4.png) + +## Step 2 - Load the dataset + +Then you need to write a simple script to read this dataset for pytorch. (In fact we have written it for you in "tutorial_dataset.py".) + +```python +import json +import cv2 +import numpy as np + +from torch.utils.data import Dataset + + +class MyDataset(Dataset): + def __init__(self): + self.data = [] + with open('./training/fill50k/prompt.json', 'rt') as f: + for line in f: + self.data.append(json.loads(line)) + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + item = self.data[idx] + + source_filename = item['source'] + target_filename = item['target'] + prompt = item['prompt'] + + source = cv2.imread('./training/fill50k/' + source_filename) + target = cv2.imread('./training/fill50k/' + target_filename) + + # Do not forget that OpenCV read images in BGR order. + source = cv2.cvtColor(source, cv2.COLOR_BGR2RGB) + target = cv2.cvtColor(target, cv2.COLOR_BGR2RGB) + + # Normalize source images to [0, 1]. + source = source.astype(np.float32) / 255.0 + + # Normalize target images to [-1, 1]. + target = (target.astype(np.float32) / 127.5) - 1.0 + + return dict(jpg=target, txt=prompt, hint=source) + +``` + +This will make your dataset into an array-like object in python. You can test this dataset simply by accessing the array, like this + +```python +from tutorial_dataset import MyDataset + +dataset = MyDataset() +print(len(dataset)) + +item = dataset[1234] +jpg = item['jpg'] +txt = item['txt'] +hint = item['hint'] +print(txt) +print(jpg.shape) +print(hint.shape) + +``` + +The outputs of this simple test on my machine are + + 50000 + burly wood circle with orange background + (512, 512, 3) + (512, 512, 3) + +And this code is in "tutorial_dataset_test.py". + +In this way, the dataset is an array-like object with 50000 items. Each item is a dict with three entry "jpg", "txt", and "hint". The "jpg" is the target image, the "hint" is the control image, and the "txt" is the prompt. + +Do not ask us why we use these three names - this is related to the dark history of a library called LDM. + +## Step 3 - What SD model do you want to control? + +Then you need to decide which Stable Diffusion Model you want to control. In this example, we will just use standard SD1.5. You can download it from the [official page of Stability](https://huggingface.co/runwayml/stable-diffusion-v1-5/tree/main). You want the file ["v1-5-pruned.ckpt"](https://huggingface.co/runwayml/stable-diffusion-v1-5/tree/main). + +(Or ["v2-1_512-ema-pruned.ckpt"](https://huggingface.co/stabilityai/stable-diffusion-2-1-base/tree/main) if you are using SD2.) + +Then you need to attach a control net to the SD model. The architecture is + +![img](../github_page/sd.png) + +Note that all weights inside the ControlNet are also copied from SD so that no layer is trained from scratch, and you are still finetuning the entire model. + +We provide a simple script for you to achieve this easily. If your SD filename is "./models/v1-5-pruned.ckpt" and you want the script to save the processed model (SD+ControlNet) at location "./models/control_sd15_ini.ckpt", you can just run: + + python tool_add_control.py ./models/v1-5-pruned.ckpt ./models/control_sd15_ini.ckpt + +Or if you are using SD2: + + python tool_add_control_sd21.py ./models/v2-1_512-ema-pruned.ckpt ./models/control_sd21_ini.ckpt + +You may also use other filenames as long as the command is "python tool_add_control.py input_path output_path". + +This is the correct output from my machine: + +![img](../github_page/t5.png) + +## Step 4 - Train! + +Happy! We finally come to the most exciting part: training! + +The training code in "tutorial_train.py" is actually surprisingly simple: + +```python +import pytorch_lightning as pl +from torch.utils.data import DataLoader +from tutorial_dataset import MyDataset +from cldm.logger import ImageLogger +from cldm.model import create_model, load_state_dict + + +# Configs +resume_path = './models/control_sd15_ini.ckpt' +batch_size = 4 +logger_freq = 300 +learning_rate = 1e-5 +sd_locked = True +only_mid_control = False + + +# First use cpu to load models. Pytorch Lightning will automatically move it to GPUs. +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict(resume_path, location='cpu')) +model.learning_rate = learning_rate +model.sd_locked = sd_locked +model.only_mid_control = only_mid_control + + +# Misc +dataset = MyDataset() +dataloader = DataLoader(dataset, num_workers=0, batch_size=batch_size, shuffle=True) +logger = ImageLogger(batch_frequency=logger_freq) +trainer = pl.Trainer(gpus=1, precision=32, callbacks=[logger]) + + +# Train! +trainer.fit(model, dataloader) + +``` +(or "tutorial_train_sd21.py" if you are using SD2) + +Thanks to our organized dataset pytorch object and the power of pytorch_lightning, the entire code is just super short. + +Now, you may take a look at [Pytorch Lightning Official DOC](https://lightning.ai/docs/pytorch/stable/api/lightning.pytorch.trainer.trainer.Trainer.html#trainer) to find out how to enable many useful features like gradient accumulation, multiple GPU training, accelerated dataset loading, flexible checkpoint saving, etc. All these only need about one line of code. Great! + +Note that if you find OOM, perhaps you need to enable [Low VRAM mode](low_vram.md), and perhaps you also need to use smaller batch size and gradient accumulation. Or you may also want to use some “advanced” tricks like sliced attention or xformers. For example: + +```python +# Configs +batch_size = 1 + +# Misc +trainer = pl.Trainer(gpus=1, precision=32, callbacks=[logger], accumulate_grad_batches=4) # But this will be 4x slower +``` + +Note that training with 8 GB laptop GPU is challenging. We will need some GPU memory optimization at least as good as automatic1111’s UI. This may require expert modifications to the code. + +### Screenshots + +The training is fast. After 4000 steps (batch size 4, learning rate 1e-5, about 50 minutes on PCIE 40G), the results on my machine (in an output folder "image_log") is + +Control: + +![img](../github_page/t/ip.png) + +Prompt: + +![img](../github_page/t/t.png) + +Prediction: + +![img](../github_page/t/op.png) + +Ground Truth: + +![img](../github_page/t/gt.png) + +Note that the SD's capability is preserved. Even training on this super aligned dataset, it still draws some random textures and those snow decorations. (Besides, note that the ground truth looks a bit modified because it is converted from SD's latent image.) + +Larger batch size and longer training will further improve this. Adequate training will make the filling perfect. + +Of course, training SD to fill circles is meaningless, but this is a successful beginning of your story. + +Let us work together to control large models more and more. + +## Other options + +Beyond standard things, we also provide two important parameters "sd_locked" and "only_mid_control" that you need to know. + +### only_mid_control + +By default, only_mid_control is False. When it is True, you will train the below architecture. + +![img](../github_page/t6.png) + +This can be helpful when your computation power is limited and want to speed up the training, or when you want to facilitate the "global" context learning. Note that sometimes you may pause training, set it to True, resume training, and pause again, and set it again, and resume again. + +If your computation device is good, perhaps you do not need this. But I also know some artists are willing to train a model on their laptop for a month - in that case, perhaps this option can be useful. + +### sd_locked + +By default, sd_locked is True. When it is False, you will train the below architecture. + +![img](../github_page/t7.png) + +This will unlock some layers in SD and you will train them as a whole. + +This option is DANGEROUS! If your dataset is not good enough, this may downgrade the capability of your SD model. + +However, this option is also very useful when you are training on images with some specific style, or when you are training with special datasets (like medical dataset with X-ray images or geographic datasets with lots of Google Maps). You can understand this as simultaneously training the ControlNet and something like a DreamBooth. + +Also, if your dataset is large, you may want to end the training with a few thousands of steps with those layer unlocked. This usually improve the "problem-specific" solutions a little. You may try it yourself to feel the difference. + +Also, if you unlock some original layers, you may want a lower learning rate, like 2e-6. + +## More Consideration: Sudden Converge Phenomenon and Gradient Accumulation + +![img](../github_page/ex1.jpg) + +Because we use zero convolutions, the SD should always be able to predict meaningful images. (If it cannot, the training has already failed.) + +You will always find that at some iterations, the model "suddenly" be able to fit some training conditions. This means that you will get a basically usable model at about 3k to 7k steps (future training will improve it, but that model after the first "sudden converge" should be basically functional). + +Note that 3k to 7k steps is not very large, and you should consider larger batch size rather than more training steps. If you can observe the "sudden converge" at 3k step using batch size 4, then, rather than train it with 300k further steps, a better idea is to use 100× gradient accumulation to re-train that 3k steps with 100× batch size. Note that perhaps we should not do this *too* extremely (perhaps 100x accumulation is too extreme), but you should consider that, since "sudden converge" will *always* happen at that certain point, getting a better converge is more important. + +Because that "sudden converge" always happens, lets say "sudden converge" will happen at 3k step and our money can optimize 90k step, then we have two options: (1) train 3k steps, sudden converge, then train 87k steps. (2) 30x gradient accumulation, train 3k steps (90k real computation steps), then sudden converge. + +In my experiments, (2) is usually better than (1). However, in real cases, perhaps you may need to balance the steps before and after the "sudden converge" on your own to find a balance. The training after "sudden converge" is also important. + +But usually, if your logic batch size is already bigger than 256, then further extending the batch size is not very meaningful. In that case, perhaps a better idea is to train more steps. I tried some "common" logic batch size at 64 or 96 or 128 (by gradient accumulation), it seems that many complicated conditions can be solved very well already. diff --git a/comparison_models/ControlNet/environment.yaml b/comparison_models/ControlNet/environment.yaml new file mode 100644 index 0000000..91463f0 --- /dev/null +++ b/comparison_models/ControlNet/environment.yaml @@ -0,0 +1,35 @@ +name: control +channels: + - pytorch + - defaults +dependencies: + - python=3.8.5 + - pip=20.3 + - cudatoolkit=11.3 + - pytorch=1.12.1 + - torchvision=0.13.1 + - numpy=1.23.1 + - pip: + - gradio==3.16.2 + - albumentations==1.3.0 + - opencv-contrib-python==4.3.0.36 + - imageio==2.9.0 + - imageio-ffmpeg==0.4.2 + - pytorch-lightning==1.5.0 + - omegaconf==2.1.1 + - test-tube>=0.7.5 + - streamlit==1.12.1 + - einops==0.3.0 + - transformers==4.19.2 + - webdataset==0.2.5 + - kornia==0.6 + - open_clip_torch==2.0.2 + - invisible-watermark>=0.1.5 + - streamlit-drawable-canvas==0.8.0 + - torchmetrics==0.6.0 + - timm==0.6.12 + - addict==2.4.0 + - yapf==0.32.0 + - prettytable==3.6.0 + - safetensors==0.2.7 + - basicsr==1.4.2 diff --git a/comparison_models/ControlNet/font/DejaVuSans.ttf b/comparison_models/ControlNet/font/DejaVuSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e5f7eecce43be41ff0703ed99e1553029b849f14 GIT binary patch literal 757076 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

fI);V1&`p^P@z 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%0Dm&v@4taQGLT-zh3S`AeK1 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,GZiO*k^1+ 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


*Toc{%?n7Na{@W#kyM+}-t@ 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�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

9YZ4 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 z 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

C;kIRhg~gM!vAOC`Sn4{$JCXdp2~=e39X=Y38A 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 W{vqoXvb{`%VS%WMs+AypQUKtQ7_{2@A^ytA-f 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%0A~?y&eN-{wPneIq!@VTU2wGtzL+} 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 bzm8AbuVoE9qaMlyfL|Cr6WvPfd(r;^ifYg6 literal 0 HcmV?d00001 diff --git a/comparison_models/ControlNet/ldm/modules/image_degradation/utils_image.py b/comparison_models/ControlNet/ldm/modules/image_degradation/utils_image.py new file mode 100644 index 0000000..0175f15 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/image_degradation/utils_image.py @@ -0,0 +1,916 @@ +import os +import math +import random +import numpy as np +import torch +import cv2 +from torchvision.utils import make_grid +from datetime import datetime +#import matplotlib.pyplot as plt # TODO: check with Dominik, also bsrgan.py vs bsrgan_light.py + + +os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" + + +''' +# -------------------------------------------- +# Kai Zhang (github: https://github.com/cszn) +# 03/Mar/2019 +# -------------------------------------------- +# https://github.com/twhui/SRGAN-pyTorch +# https://github.com/xinntao/BasicSR +# -------------------------------------------- +''' + + +IMG_EXTENSIONS = ['.jpg', '.JPG', '.jpeg', '.JPEG', '.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP', '.tif'] + + +def is_image_file(filename): + return any(filename.endswith(extension) for extension in IMG_EXTENSIONS) + + +def get_timestamp(): + return datetime.now().strftime('%y%m%d-%H%M%S') + + +def imshow(x, title=None, cbar=False, figsize=None): + plt.figure(figsize=figsize) + plt.imshow(np.squeeze(x), interpolation='nearest', cmap='gray') + if title: + plt.title(title) + if cbar: + plt.colorbar() + plt.show() + + +def surf(Z, cmap='rainbow', figsize=None): + plt.figure(figsize=figsize) + ax3 = plt.axes(projection='3d') + + w, h = Z.shape[:2] + xx = np.arange(0,w,1) + yy = np.arange(0,h,1) + X, Y = np.meshgrid(xx, yy) + ax3.plot_surface(X,Y,Z,cmap=cmap) + #ax3.contour(X,Y,Z, zdim='z',offset=-2,cmap=cmap) + plt.show() + + +''' +# -------------------------------------------- +# get image pathes +# -------------------------------------------- +''' + + +def get_image_paths(dataroot): + paths = None # return None if dataroot is None + if dataroot is not None: + paths = sorted(_get_paths_from_images(dataroot)) + return paths + + +def _get_paths_from_images(path): + assert os.path.isdir(path), '{:s} is not a valid directory'.format(path) + images = [] + for dirpath, _, fnames in sorted(os.walk(path)): + for fname in sorted(fnames): + if is_image_file(fname): + img_path = os.path.join(dirpath, fname) + images.append(img_path) + assert images, '{:s} has no valid image file'.format(path) + return images + + +''' +# -------------------------------------------- +# split large images into small images +# -------------------------------------------- +''' + + +def patches_from_image(img, p_size=512, p_overlap=64, p_max=800): + w, h = img.shape[:2] + patches = [] + if w > p_max and h > p_max: + w1 = list(np.arange(0, w-p_size, p_size-p_overlap, dtype=np.int)) + h1 = list(np.arange(0, h-p_size, p_size-p_overlap, dtype=np.int)) + w1.append(w-p_size) + h1.append(h-p_size) +# print(w1) +# print(h1) + for i in w1: + for j in h1: + patches.append(img[i:i+p_size, j:j+p_size,:]) + else: + patches.append(img) + + return patches + + +def imssave(imgs, img_path): + """ + imgs: list, N images of size WxHxC + """ + img_name, ext = os.path.splitext(os.path.basename(img_path)) + + for i, img in enumerate(imgs): + if img.ndim == 3: + img = img[:, :, [2, 1, 0]] + new_path = os.path.join(os.path.dirname(img_path), img_name+str('_s{:04d}'.format(i))+'.png') + cv2.imwrite(new_path, img) + + +def split_imageset(original_dataroot, taget_dataroot, n_channels=3, p_size=800, p_overlap=96, p_max=1000): + """ + split the large images from original_dataroot into small overlapped images with size (p_size)x(p_size), + and save them into taget_dataroot; only the images with larger size than (p_max)x(p_max) + will be splitted. + Args: + original_dataroot: + taget_dataroot: + p_size: size of small images + p_overlap: patch size in training is a good choice + p_max: images with smaller size than (p_max)x(p_max) keep unchanged. + """ + paths = get_image_paths(original_dataroot) + for img_path in paths: + # img_name, ext = os.path.splitext(os.path.basename(img_path)) + img = imread_uint(img_path, n_channels=n_channels) + patches = patches_from_image(img, p_size, p_overlap, p_max) + imssave(patches, os.path.join(taget_dataroot,os.path.basename(img_path))) + #if original_dataroot == taget_dataroot: + #del img_path + +''' +# -------------------------------------------- +# makedir +# -------------------------------------------- +''' + + +def mkdir(path): + if not os.path.exists(path): + os.makedirs(path) + + +def mkdirs(paths): + if isinstance(paths, str): + mkdir(paths) + else: + for path in paths: + mkdir(path) + + +def mkdir_and_rename(path): + if os.path.exists(path): + new_name = path + '_archived_' + get_timestamp() + print('Path already exists. Rename it to [{:s}]'.format(new_name)) + os.rename(path, new_name) + os.makedirs(path) + + +''' +# -------------------------------------------- +# read image from path +# opencv is fast, but read BGR numpy image +# -------------------------------------------- +''' + + +# -------------------------------------------- +# get uint8 image of size HxWxn_channles (RGB) +# -------------------------------------------- +def imread_uint(path, n_channels=3): + # input: path + # output: HxWx3(RGB or GGG), or HxWx1 (G) + if n_channels == 1: + img = cv2.imread(path, 0) # cv2.IMREAD_GRAYSCALE + img = np.expand_dims(img, axis=2) # HxWx1 + elif n_channels == 3: + img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # BGR or G + if img.ndim == 2: + img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) # GGG + else: + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # RGB + return img + + +# -------------------------------------------- +# matlab's imwrite +# -------------------------------------------- +def imsave(img, img_path): + img = np.squeeze(img) + if img.ndim == 3: + img = img[:, :, [2, 1, 0]] + cv2.imwrite(img_path, img) + +def imwrite(img, img_path): + img = np.squeeze(img) + if img.ndim == 3: + img = img[:, :, [2, 1, 0]] + cv2.imwrite(img_path, img) + + + +# -------------------------------------------- +# get single image of size HxWxn_channles (BGR) +# -------------------------------------------- +def read_img(path): + # read image by cv2 + # return: Numpy float32, HWC, BGR, [0,1] + img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_GRAYSCALE + img = img.astype(np.float32) / 255. + if img.ndim == 2: + img = np.expand_dims(img, axis=2) + # some images have 4 channels + if img.shape[2] > 3: + img = img[:, :, :3] + return img + + +''' +# -------------------------------------------- +# image format conversion +# -------------------------------------------- +# numpy(single) <---> numpy(unit) +# numpy(single) <---> tensor +# numpy(unit) <---> tensor +# -------------------------------------------- +''' + + +# -------------------------------------------- +# numpy(single) [0, 1] <---> numpy(unit) +# -------------------------------------------- + + +def uint2single(img): + + return np.float32(img/255.) + + +def single2uint(img): + + return np.uint8((img.clip(0, 1)*255.).round()) + + +def uint162single(img): + + return np.float32(img/65535.) + + +def single2uint16(img): + + return np.uint16((img.clip(0, 1)*65535.).round()) + + +# -------------------------------------------- +# numpy(unit) (HxWxC or HxW) <---> tensor +# -------------------------------------------- + + +# convert uint to 4-dimensional torch tensor +def uint2tensor4(img): + if img.ndim == 2: + img = np.expand_dims(img, axis=2) + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().div(255.).unsqueeze(0) + + +# convert uint to 3-dimensional torch tensor +def uint2tensor3(img): + if img.ndim == 2: + img = np.expand_dims(img, axis=2) + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().div(255.) + + +# convert 2/3/4-dimensional torch tensor to uint +def tensor2uint(img): + img = img.data.squeeze().float().clamp_(0, 1).cpu().numpy() + if img.ndim == 3: + img = np.transpose(img, (1, 2, 0)) + return np.uint8((img*255.0).round()) + + +# -------------------------------------------- +# numpy(single) (HxWxC) <---> tensor +# -------------------------------------------- + + +# convert single (HxWxC) to 3-dimensional torch tensor +def single2tensor3(img): + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float() + + +# convert single (HxWxC) to 4-dimensional torch tensor +def single2tensor4(img): + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().unsqueeze(0) + + +# convert torch tensor to single +def tensor2single(img): + img = img.data.squeeze().float().cpu().numpy() + if img.ndim == 3: + img = np.transpose(img, (1, 2, 0)) + + return img + +# convert torch tensor to single +def tensor2single3(img): + img = img.data.squeeze().float().cpu().numpy() + if img.ndim == 3: + img = np.transpose(img, (1, 2, 0)) + elif img.ndim == 2: + img = np.expand_dims(img, axis=2) + return img + + +def single2tensor5(img): + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1, 3).float().unsqueeze(0) + + +def single32tensor5(img): + return torch.from_numpy(np.ascontiguousarray(img)).float().unsqueeze(0).unsqueeze(0) + + +def single42tensor4(img): + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1, 3).float() + + +# from skimage.io import imread, imsave +def tensor2img(tensor, out_type=np.uint8, min_max=(0, 1)): + ''' + Converts a torch Tensor into an image Numpy array of BGR channel order + Input: 4D(B,(3/1),H,W), 3D(C,H,W), or 2D(H,W), any range, RGB channel order + Output: 3D(H,W,C) or 2D(H,W), [0,255], np.uint8 (default) + ''' + tensor = tensor.squeeze().float().cpu().clamp_(*min_max) # squeeze first, then clamp + tensor = (tensor - min_max[0]) / (min_max[1] - min_max[0]) # to range [0,1] + n_dim = tensor.dim() + if n_dim == 4: + n_img = len(tensor) + img_np = make_grid(tensor, nrow=int(math.sqrt(n_img)), normalize=False).numpy() + img_np = np.transpose(img_np[[2, 1, 0], :, :], (1, 2, 0)) # HWC, BGR + elif n_dim == 3: + img_np = tensor.numpy() + img_np = np.transpose(img_np[[2, 1, 0], :, :], (1, 2, 0)) # HWC, BGR + elif n_dim == 2: + img_np = tensor.numpy() + else: + raise TypeError( + 'Only support 4D, 3D and 2D tensor. But received with dimension: {:d}'.format(n_dim)) + if out_type == np.uint8: + img_np = (img_np * 255.0).round() + # Important. Unlike matlab, numpy.unit8() WILL NOT round by default. + return img_np.astype(out_type) + + +''' +# -------------------------------------------- +# Augmentation, flipe and/or rotate +# -------------------------------------------- +# The following two are enough. +# (1) augmet_img: numpy image of WxHxC or WxH +# (2) augment_img_tensor4: tensor image 1xCxWxH +# -------------------------------------------- +''' + + +def augment_img(img, mode=0): + '''Kai Zhang (github: https://github.com/cszn) + ''' + if mode == 0: + return img + elif mode == 1: + return np.flipud(np.rot90(img)) + elif mode == 2: + return np.flipud(img) + elif mode == 3: + return np.rot90(img, k=3) + elif mode == 4: + return np.flipud(np.rot90(img, k=2)) + elif mode == 5: + return np.rot90(img) + elif mode == 6: + return np.rot90(img, k=2) + elif mode == 7: + return np.flipud(np.rot90(img, k=3)) + + +def augment_img_tensor4(img, mode=0): + '''Kai Zhang (github: https://github.com/cszn) + ''' + if mode == 0: + return img + elif mode == 1: + return img.rot90(1, [2, 3]).flip([2]) + elif mode == 2: + return img.flip([2]) + elif mode == 3: + return img.rot90(3, [2, 3]) + elif mode == 4: + return img.rot90(2, [2, 3]).flip([2]) + elif mode == 5: + return img.rot90(1, [2, 3]) + elif mode == 6: + return img.rot90(2, [2, 3]) + elif mode == 7: + return img.rot90(3, [2, 3]).flip([2]) + + +def augment_img_tensor(img, mode=0): + '''Kai Zhang (github: https://github.com/cszn) + ''' + img_size = img.size() + img_np = img.data.cpu().numpy() + if len(img_size) == 3: + img_np = np.transpose(img_np, (1, 2, 0)) + elif len(img_size) == 4: + img_np = np.transpose(img_np, (2, 3, 1, 0)) + img_np = augment_img(img_np, mode=mode) + img_tensor = torch.from_numpy(np.ascontiguousarray(img_np)) + if len(img_size) == 3: + img_tensor = img_tensor.permute(2, 0, 1) + elif len(img_size) == 4: + img_tensor = img_tensor.permute(3, 2, 0, 1) + + return img_tensor.type_as(img) + + +def augment_img_np3(img, mode=0): + if mode == 0: + return img + elif mode == 1: + return img.transpose(1, 0, 2) + elif mode == 2: + return img[::-1, :, :] + elif mode == 3: + img = img[::-1, :, :] + img = img.transpose(1, 0, 2) + return img + elif mode == 4: + return img[:, ::-1, :] + elif mode == 5: + img = img[:, ::-1, :] + img = img.transpose(1, 0, 2) + return img + elif mode == 6: + img = img[:, ::-1, :] + img = img[::-1, :, :] + return img + elif mode == 7: + img = img[:, ::-1, :] + img = img[::-1, :, :] + img = img.transpose(1, 0, 2) + return img + + +def augment_imgs(img_list, hflip=True, rot=True): + # horizontal flip OR rotate + hflip = hflip and random.random() < 0.5 + vflip = rot and random.random() < 0.5 + rot90 = rot and random.random() < 0.5 + + def _augment(img): + if hflip: + img = img[:, ::-1, :] + if vflip: + img = img[::-1, :, :] + if rot90: + img = img.transpose(1, 0, 2) + return img + + return [_augment(img) for img in img_list] + + +''' +# -------------------------------------------- +# modcrop and shave +# -------------------------------------------- +''' + + +def modcrop(img_in, scale): + # img_in: Numpy, HWC or HW + img = np.copy(img_in) + if img.ndim == 2: + H, W = img.shape + H_r, W_r = H % scale, W % scale + img = img[:H - H_r, :W - W_r] + elif img.ndim == 3: + H, W, C = img.shape + H_r, W_r = H % scale, W % scale + img = img[:H - H_r, :W - W_r, :] + else: + raise ValueError('Wrong img ndim: [{:d}].'.format(img.ndim)) + return img + + +def shave(img_in, border=0): + # img_in: Numpy, HWC or HW + img = np.copy(img_in) + h, w = img.shape[:2] + img = img[border:h-border, border:w-border] + return img + + +''' +# -------------------------------------------- +# image processing process on numpy image +# channel_convert(in_c, tar_type, img_list): +# rgb2ycbcr(img, only_y=True): +# bgr2ycbcr(img, only_y=True): +# ycbcr2rgb(img): +# -------------------------------------------- +''' + + +def rgb2ycbcr(img, only_y=True): + '''same as matlab rgb2ycbcr + only_y: only return Y channel + Input: + uint8, [0, 255] + float, [0, 1] + ''' + in_img_type = img.dtype + img.astype(np.float32) + if in_img_type != np.uint8: + img *= 255. + # convert + if only_y: + rlt = np.dot(img, [65.481, 128.553, 24.966]) / 255.0 + 16.0 + else: + rlt = np.matmul(img, [[65.481, -37.797, 112.0], [128.553, -74.203, -93.786], + [24.966, 112.0, -18.214]]) / 255.0 + [16, 128, 128] + if in_img_type == np.uint8: + rlt = rlt.round() + else: + rlt /= 255. + return rlt.astype(in_img_type) + + +def ycbcr2rgb(img): + '''same as matlab ycbcr2rgb + Input: + uint8, [0, 255] + float, [0, 1] + ''' + in_img_type = img.dtype + img.astype(np.float32) + if in_img_type != np.uint8: + img *= 255. + # convert + rlt = np.matmul(img, [[0.00456621, 0.00456621, 0.00456621], [0, -0.00153632, 0.00791071], + [0.00625893, -0.00318811, 0]]) * 255.0 + [-222.921, 135.576, -276.836] + if in_img_type == np.uint8: + rlt = rlt.round() + else: + rlt /= 255. + return rlt.astype(in_img_type) + + +def bgr2ycbcr(img, only_y=True): + '''bgr version of rgb2ycbcr + only_y: only return Y channel + Input: + uint8, [0, 255] + float, [0, 1] + ''' + in_img_type = img.dtype + img.astype(np.float32) + if in_img_type != np.uint8: + img *= 255. + # convert + if only_y: + rlt = np.dot(img, [24.966, 128.553, 65.481]) / 255.0 + 16.0 + else: + rlt = np.matmul(img, [[24.966, 112.0, -18.214], [128.553, -74.203, -93.786], + [65.481, -37.797, 112.0]]) / 255.0 + [16, 128, 128] + if in_img_type == np.uint8: + rlt = rlt.round() + else: + rlt /= 255. + return rlt.astype(in_img_type) + + +def channel_convert(in_c, tar_type, img_list): + # conversion among BGR, gray and y + if in_c == 3 and tar_type == 'gray': # BGR to gray + gray_list = [cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) for img in img_list] + return [np.expand_dims(img, axis=2) for img in gray_list] + elif in_c == 3 and tar_type == 'y': # BGR to y + y_list = [bgr2ycbcr(img, only_y=True) for img in img_list] + return [np.expand_dims(img, axis=2) for img in y_list] + elif in_c == 1 and tar_type == 'RGB': # gray/y to BGR + return [cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) for img in img_list] + else: + return img_list + + +''' +# -------------------------------------------- +# metric, PSNR and SSIM +# -------------------------------------------- +''' + + +# -------------------------------------------- +# PSNR +# -------------------------------------------- +def calculate_psnr(img1, img2, border=0): + # img1 and img2 have range [0, 255] + #img1 = img1.squeeze() + #img2 = img2.squeeze() + if not img1.shape == img2.shape: + raise ValueError('Input images must have the same dimensions.') + h, w = img1.shape[:2] + img1 = img1[border:h-border, border:w-border] + img2 = img2[border:h-border, border:w-border] + + img1 = img1.astype(np.float64) + img2 = img2.astype(np.float64) + mse = np.mean((img1 - img2)**2) + if mse == 0: + return float('inf') + return 20 * math.log10(255.0 / math.sqrt(mse)) + + +# -------------------------------------------- +# SSIM +# -------------------------------------------- +def calculate_ssim(img1, img2, border=0): + '''calculate SSIM + the same outputs as MATLAB's + img1, img2: [0, 255] + ''' + #img1 = img1.squeeze() + #img2 = img2.squeeze() + if not img1.shape == img2.shape: + raise ValueError('Input images must have the same dimensions.') + h, w = img1.shape[:2] + img1 = img1[border:h-border, border:w-border] + img2 = img2[border:h-border, border:w-border] + + if img1.ndim == 2: + return ssim(img1, img2) + elif img1.ndim == 3: + if img1.shape[2] == 3: + ssims = [] + for i in range(3): + ssims.append(ssim(img1[:,:,i], img2[:,:,i])) + return np.array(ssims).mean() + elif img1.shape[2] == 1: + return ssim(np.squeeze(img1), np.squeeze(img2)) + else: + raise ValueError('Wrong input image dimensions.') + + +def ssim(img1, img2): + C1 = (0.01 * 255)**2 + C2 = (0.03 * 255)**2 + + img1 = img1.astype(np.float64) + img2 = img2.astype(np.float64) + kernel = cv2.getGaussianKernel(11, 1.5) + window = np.outer(kernel, kernel.transpose()) + + mu1 = cv2.filter2D(img1, -1, window)[5:-5, 5:-5] # valid + mu2 = cv2.filter2D(img2, -1, window)[5:-5, 5:-5] + mu1_sq = mu1**2 + mu2_sq = mu2**2 + mu1_mu2 = mu1 * mu2 + sigma1_sq = cv2.filter2D(img1**2, -1, window)[5:-5, 5:-5] - mu1_sq + sigma2_sq = cv2.filter2D(img2**2, -1, window)[5:-5, 5:-5] - mu2_sq + sigma12 = cv2.filter2D(img1 * img2, -1, window)[5:-5, 5:-5] - mu1_mu2 + + ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) * + (sigma1_sq + sigma2_sq + C2)) + return ssim_map.mean() + + +''' +# -------------------------------------------- +# matlab's bicubic imresize (numpy and torch) [0, 1] +# -------------------------------------------- +''' + + +# matlab 'imresize' function, now only support 'bicubic' +def cubic(x): + absx = torch.abs(x) + absx2 = absx**2 + absx3 = absx**3 + return (1.5*absx3 - 2.5*absx2 + 1) * ((absx <= 1).type_as(absx)) + \ + (-0.5*absx3 + 2.5*absx2 - 4*absx + 2) * (((absx > 1)*(absx <= 2)).type_as(absx)) + + +def calculate_weights_indices(in_length, out_length, scale, kernel, kernel_width, antialiasing): + if (scale < 1) and (antialiasing): + # Use a modified kernel to simultaneously interpolate and antialias- larger kernel width + kernel_width = kernel_width / scale + + # Output-space coordinates + x = torch.linspace(1, out_length, out_length) + + # Input-space coordinates. Calculate the inverse mapping such that 0.5 + # in output space maps to 0.5 in input space, and 0.5+scale in output + # space maps to 1.5 in input space. + u = x / scale + 0.5 * (1 - 1 / scale) + + # What is the left-most pixel that can be involved in the computation? + left = torch.floor(u - kernel_width / 2) + + # What is the maximum number of pixels that can be involved in the + # computation? Note: it's OK to use an extra pixel here; if the + # corresponding weights are all zero, it will be eliminated at the end + # of this function. + P = math.ceil(kernel_width) + 2 + + # The indices of the input pixels involved in computing the k-th output + # pixel are in row k of the indices matrix. + indices = left.view(out_length, 1).expand(out_length, P) + torch.linspace(0, P - 1, P).view( + 1, P).expand(out_length, P) + + # The weights used to compute the k-th output pixel are in row k of the + # weights matrix. + distance_to_center = u.view(out_length, 1).expand(out_length, P) - indices + # apply cubic kernel + if (scale < 1) and (antialiasing): + weights = scale * cubic(distance_to_center * scale) + else: + weights = cubic(distance_to_center) + # Normalize the weights matrix so that each row sums to 1. + weights_sum = torch.sum(weights, 1).view(out_length, 1) + weights = weights / weights_sum.expand(out_length, P) + + # If a column in weights is all zero, get rid of it. only consider the first and last column. + weights_zero_tmp = torch.sum((weights == 0), 0) + if not math.isclose(weights_zero_tmp[0], 0, rel_tol=1e-6): + indices = indices.narrow(1, 1, P - 2) + weights = weights.narrow(1, 1, P - 2) + if not math.isclose(weights_zero_tmp[-1], 0, rel_tol=1e-6): + indices = indices.narrow(1, 0, P - 2) + weights = weights.narrow(1, 0, P - 2) + weights = weights.contiguous() + indices = indices.contiguous() + sym_len_s = -indices.min() + 1 + sym_len_e = indices.max() - in_length + indices = indices + sym_len_s - 1 + return weights, indices, int(sym_len_s), int(sym_len_e) + + +# -------------------------------------------- +# imresize for tensor image [0, 1] +# -------------------------------------------- +def imresize(img, scale, antialiasing=True): + # Now the scale should be the same for H and W + # input: img: pytorch tensor, CHW or HW [0,1] + # output: CHW or HW [0,1] w/o round + need_squeeze = True if img.dim() == 2 else False + if need_squeeze: + img.unsqueeze_(0) + in_C, in_H, in_W = img.size() + out_C, out_H, out_W = in_C, math.ceil(in_H * scale), math.ceil(in_W * scale) + kernel_width = 4 + kernel = 'cubic' + + # Return the desired dimension order for performing the resize. The + # strategy is to perform the resize first along the dimension with the + # smallest scale factor. + # Now we do not support this. + + # get weights and indices + weights_H, indices_H, sym_len_Hs, sym_len_He = calculate_weights_indices( + in_H, out_H, scale, kernel, kernel_width, antialiasing) + weights_W, indices_W, sym_len_Ws, sym_len_We = calculate_weights_indices( + in_W, out_W, scale, kernel, kernel_width, antialiasing) + # process H dimension + # symmetric copying + img_aug = torch.FloatTensor(in_C, in_H + sym_len_Hs + sym_len_He, in_W) + img_aug.narrow(1, sym_len_Hs, in_H).copy_(img) + + sym_patch = img[:, :sym_len_Hs, :] + inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(1, inv_idx) + img_aug.narrow(1, 0, sym_len_Hs).copy_(sym_patch_inv) + + sym_patch = img[:, -sym_len_He:, :] + inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(1, inv_idx) + img_aug.narrow(1, sym_len_Hs + in_H, sym_len_He).copy_(sym_patch_inv) + + out_1 = torch.FloatTensor(in_C, out_H, in_W) + kernel_width = weights_H.size(1) + for i in range(out_H): + idx = int(indices_H[i][0]) + for j in range(out_C): + out_1[j, i, :] = img_aug[j, idx:idx + kernel_width, :].transpose(0, 1).mv(weights_H[i]) + + # process W dimension + # symmetric copying + out_1_aug = torch.FloatTensor(in_C, out_H, in_W + sym_len_Ws + sym_len_We) + out_1_aug.narrow(2, sym_len_Ws, in_W).copy_(out_1) + + sym_patch = out_1[:, :, :sym_len_Ws] + inv_idx = torch.arange(sym_patch.size(2) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(2, inv_idx) + out_1_aug.narrow(2, 0, sym_len_Ws).copy_(sym_patch_inv) + + sym_patch = out_1[:, :, -sym_len_We:] + inv_idx = torch.arange(sym_patch.size(2) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(2, inv_idx) + out_1_aug.narrow(2, sym_len_Ws + in_W, sym_len_We).copy_(sym_patch_inv) + + out_2 = torch.FloatTensor(in_C, out_H, out_W) + kernel_width = weights_W.size(1) + for i in range(out_W): + idx = int(indices_W[i][0]) + for j in range(out_C): + out_2[j, :, i] = out_1_aug[j, :, idx:idx + kernel_width].mv(weights_W[i]) + if need_squeeze: + out_2.squeeze_() + return out_2 + + +# -------------------------------------------- +# imresize for numpy image [0, 1] +# -------------------------------------------- +def imresize_np(img, scale, antialiasing=True): + # Now the scale should be the same for H and W + # input: img: Numpy, HWC or HW [0,1] + # output: HWC or HW [0,1] w/o round + img = torch.from_numpy(img) + need_squeeze = True if img.dim() == 2 else False + if need_squeeze: + img.unsqueeze_(2) + + in_H, in_W, in_C = img.size() + out_C, out_H, out_W = in_C, math.ceil(in_H * scale), math.ceil(in_W * scale) + kernel_width = 4 + kernel = 'cubic' + + # Return the desired dimension order for performing the resize. The + # strategy is to perform the resize first along the dimension with the + # smallest scale factor. + # Now we do not support this. + + # get weights and indices + weights_H, indices_H, sym_len_Hs, sym_len_He = calculate_weights_indices( + in_H, out_H, scale, kernel, kernel_width, antialiasing) + weights_W, indices_W, sym_len_Ws, sym_len_We = calculate_weights_indices( + in_W, out_W, scale, kernel, kernel_width, antialiasing) + # process H dimension + # symmetric copying + img_aug = torch.FloatTensor(in_H + sym_len_Hs + sym_len_He, in_W, in_C) + img_aug.narrow(0, sym_len_Hs, in_H).copy_(img) + + sym_patch = img[:sym_len_Hs, :, :] + inv_idx = torch.arange(sym_patch.size(0) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(0, inv_idx) + img_aug.narrow(0, 0, sym_len_Hs).copy_(sym_patch_inv) + + sym_patch = img[-sym_len_He:, :, :] + inv_idx = torch.arange(sym_patch.size(0) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(0, inv_idx) + img_aug.narrow(0, sym_len_Hs + in_H, sym_len_He).copy_(sym_patch_inv) + + out_1 = torch.FloatTensor(out_H, in_W, in_C) + kernel_width = weights_H.size(1) + for i in range(out_H): + idx = int(indices_H[i][0]) + for j in range(out_C): + out_1[i, :, j] = img_aug[idx:idx + kernel_width, :, j].transpose(0, 1).mv(weights_H[i]) + + # process W dimension + # symmetric copying + out_1_aug = torch.FloatTensor(out_H, in_W + sym_len_Ws + sym_len_We, in_C) + out_1_aug.narrow(1, sym_len_Ws, in_W).copy_(out_1) + + sym_patch = out_1[:, :sym_len_Ws, :] + inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(1, inv_idx) + out_1_aug.narrow(1, 0, sym_len_Ws).copy_(sym_patch_inv) + + sym_patch = out_1[:, -sym_len_We:, :] + inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(1, inv_idx) + out_1_aug.narrow(1, sym_len_Ws + in_W, sym_len_We).copy_(sym_patch_inv) + + out_2 = torch.FloatTensor(out_H, out_W, in_C) + kernel_width = weights_W.size(1) + for i in range(out_W): + idx = int(indices_W[i][0]) + for j in range(out_C): + out_2[:, i, j] = out_1_aug[:, idx:idx + kernel_width, j].mv(weights_W[i]) + if need_squeeze: + out_2.squeeze_() + + return out_2.numpy() + + +if __name__ == '__main__': + print('---') +# img = imread_uint('test.bmp', 3) +# img = uint2single(img) +# img_bicubic = imresize_np(img, 1/4) \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/modules/midas/__init__.py b/comparison_models/ControlNet/ldm/modules/midas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/ControlNet/ldm/modules/midas/api.py b/comparison_models/ControlNet/ldm/modules/midas/api.py new file mode 100644 index 0000000..b58ebbf --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/api.py @@ -0,0 +1,170 @@ +# based on https://github.com/isl-org/MiDaS + +import cv2 +import torch +import torch.nn as nn +from torchvision.transforms import Compose + +from ldm.modules.midas.midas.dpt_depth import DPTDepthModel +from ldm.modules.midas.midas.midas_net import MidasNet +from ldm.modules.midas.midas.midas_net_custom import MidasNet_small +from ldm.modules.midas.midas.transforms import Resize, NormalizeImage, PrepareForNet + + +ISL_PATHS = { + "dpt_large": "midas_models/dpt_large-midas-2f21e586.pt", + "dpt_hybrid": "midas_models/dpt_hybrid-midas-501f0c75.pt", + "midas_v21": "", + "midas_v21_small": "", +} + + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +def load_midas_transform(model_type): + # https://github.com/isl-org/MiDaS/blob/master/run.py + # load transform only + if model_type == "dpt_large": # DPT-Large + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid": # DPT-Hybrid + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21": + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + elif model_type == "midas_v21_small": + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + else: + assert False, f"model_type '{model_type}' not implemented, use: --model_type large" + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + return transform + + +def load_model(model_type): + # https://github.com/isl-org/MiDaS/blob/master/run.py + # load network + model_path = ISL_PATHS[model_type] + if model_type == "dpt_large": # DPT-Large + model = DPTDepthModel( + path=model_path, + backbone="vitl16_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid": # DPT-Hybrid + model = DPTDepthModel( + path=model_path, + backbone="vitb_rn50_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21": + model = MidasNet(model_path, non_negative=True) + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + elif model_type == "midas_v21_small": + model = MidasNet_small(model_path, features=64, backbone="efficientnet_lite3", exportable=True, + non_negative=True, blocks={'expand': True}) + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + else: + print(f"model_type '{model_type}' not implemented, use: --model_type large") + assert False + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + return model.eval(), transform + + +class MiDaSInference(nn.Module): + MODEL_TYPES_TORCH_HUB = [ + "DPT_Large", + "DPT_Hybrid", + "MiDaS_small" + ] + MODEL_TYPES_ISL = [ + "dpt_large", + "dpt_hybrid", + "midas_v21", + "midas_v21_small", + ] + + def __init__(self, model_type): + super().__init__() + assert (model_type in self.MODEL_TYPES_ISL) + model, _ = load_model(model_type) + self.model = model + self.model.train = disabled_train + + def forward(self, x): + # x in 0..1 as produced by calling self.transform on a 0..1 float64 numpy array + # NOTE: we expect that the correct transform has been called during dataloading. + with torch.no_grad(): + prediction = self.model(x) + prediction = torch.nn.functional.interpolate( + prediction.unsqueeze(1), + size=x.shape[2:], + mode="bicubic", + align_corners=False, + ) + assert prediction.shape == (x.shape[0], 1, x.shape[2], x.shape[3]) + return prediction + diff --git a/comparison_models/ControlNet/ldm/modules/midas/midas/__init__.py b/comparison_models/ControlNet/ldm/modules/midas/midas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/ControlNet/ldm/modules/midas/midas/base_model.py b/comparison_models/ControlNet/ldm/modules/midas/midas/base_model.py new file mode 100644 index 0000000..5cf4302 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/midas/base_model.py @@ -0,0 +1,16 @@ +import torch + + +class BaseModel(torch.nn.Module): + def load(self, path): + """Load model from file. + + Args: + path (str): file path + """ + parameters = torch.load(path, map_location=torch.device('cpu')) + + if "optimizer" in parameters: + parameters = parameters["model"] + + self.load_state_dict(parameters) diff --git a/comparison_models/ControlNet/ldm/modules/midas/midas/blocks.py b/comparison_models/ControlNet/ldm/modules/midas/midas/blocks.py new file mode 100644 index 0000000..2145d18 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/midas/blocks.py @@ -0,0 +1,342 @@ +import torch +import torch.nn as nn + +from .vit import ( + _make_pretrained_vitb_rn50_384, + _make_pretrained_vitl16_384, + _make_pretrained_vitb16_384, + forward_vit, +) + +def _make_encoder(backbone, features, use_pretrained, groups=1, expand=False, exportable=True, hooks=None, use_vit_only=False, use_readout="ignore",): + if backbone == "vitl16_384": + pretrained = _make_pretrained_vitl16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [256, 512, 1024, 1024], features, groups=groups, expand=expand + ) # ViT-L/16 - 85.0% Top1 (backbone) + elif backbone == "vitb_rn50_384": + pretrained = _make_pretrained_vitb_rn50_384( + use_pretrained, + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) + scratch = _make_scratch( + [256, 512, 768, 768], features, groups=groups, expand=expand + ) # ViT-H/16 - 85.0% Top1 (backbone) + elif backbone == "vitb16_384": + pretrained = _make_pretrained_vitb16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [96, 192, 384, 768], features, groups=groups, expand=expand + ) # ViT-B/16 - 84.6% Top1 (backbone) + elif backbone == "resnext101_wsl": + pretrained = _make_pretrained_resnext101_wsl(use_pretrained) + scratch = _make_scratch([256, 512, 1024, 2048], features, groups=groups, expand=expand) # efficientnet_lite3 + elif backbone == "efficientnet_lite3": + pretrained = _make_pretrained_efficientnet_lite3(use_pretrained, exportable=exportable) + scratch = _make_scratch([32, 48, 136, 384], features, groups=groups, expand=expand) # efficientnet_lite3 + else: + print(f"Backbone '{backbone}' not implemented") + assert False + + return pretrained, scratch + + +def _make_scratch(in_shape, out_shape, groups=1, expand=False): + scratch = nn.Module() + + out_shape1 = out_shape + out_shape2 = out_shape + out_shape3 = out_shape + out_shape4 = out_shape + if expand==True: + out_shape1 = out_shape + out_shape2 = out_shape*2 + out_shape3 = out_shape*4 + out_shape4 = out_shape*8 + + scratch.layer1_rn = nn.Conv2d( + in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer2_rn = nn.Conv2d( + in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer3_rn = nn.Conv2d( + in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer4_rn = nn.Conv2d( + in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + + return scratch + + +def _make_pretrained_efficientnet_lite3(use_pretrained, exportable=False): + efficientnet = torch.hub.load( + "rwightman/gen-efficientnet-pytorch", + "tf_efficientnet_lite3", + pretrained=use_pretrained, + exportable=exportable + ) + return _make_efficientnet_backbone(efficientnet) + + +def _make_efficientnet_backbone(effnet): + pretrained = nn.Module() + + pretrained.layer1 = nn.Sequential( + effnet.conv_stem, effnet.bn1, effnet.act1, *effnet.blocks[0:2] + ) + pretrained.layer2 = nn.Sequential(*effnet.blocks[2:3]) + pretrained.layer3 = nn.Sequential(*effnet.blocks[3:5]) + pretrained.layer4 = nn.Sequential(*effnet.blocks[5:9]) + + return pretrained + + +def _make_resnet_backbone(resnet): + pretrained = nn.Module() + pretrained.layer1 = nn.Sequential( + resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, resnet.layer1 + ) + + pretrained.layer2 = resnet.layer2 + pretrained.layer3 = resnet.layer3 + pretrained.layer4 = resnet.layer4 + + return pretrained + + +def _make_pretrained_resnext101_wsl(use_pretrained): + resnet = torch.hub.load("facebookresearch/WSL-Images", "resnext101_32x8d_wsl") + return _make_resnet_backbone(resnet) + + + +class Interpolate(nn.Module): + """Interpolation module. + """ + + def __init__(self, scale_factor, mode, align_corners=False): + """Init. + + Args: + scale_factor (float): scaling + mode (str): interpolation mode + """ + super(Interpolate, self).__init__() + + self.interp = nn.functional.interpolate + self.scale_factor = scale_factor + self.mode = mode + self.align_corners = align_corners + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: interpolated data + """ + + x = self.interp( + x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners + ) + + return x + + +class ResidualConvUnit(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.relu = nn.ReLU(inplace=True) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + out = self.relu(x) + out = self.conv1(out) + out = self.relu(out) + out = self.conv2(out) + + return out + x + + +class FeatureFusionBlock(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock, self).__init__() + + self.resConfUnit1 = ResidualConvUnit(features) + self.resConfUnit2 = ResidualConvUnit(features) + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + output += self.resConfUnit1(xs[1]) + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=True + ) + + return output + + + + +class ResidualConvUnit_custom(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features, activation, bn): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.bn = bn + + self.groups=1 + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + if self.bn==True: + self.bn1 = nn.BatchNorm2d(features) + self.bn2 = nn.BatchNorm2d(features) + + self.activation = activation + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + + out = self.activation(x) + out = self.conv1(out) + if self.bn==True: + out = self.bn1(out) + + out = self.activation(out) + out = self.conv2(out) + if self.bn==True: + out = self.bn2(out) + + if self.groups > 1: + out = self.conv_merge(out) + + return self.skip_add.add(out, x) + + # return out + x + + +class FeatureFusionBlock_custom(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock_custom, self).__init__() + + self.deconv = deconv + self.align_corners = align_corners + + self.groups=1 + + self.expand = expand + out_features = features + if self.expand==True: + out_features = features//2 + + self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1) + + self.resConfUnit1 = ResidualConvUnit_custom(features, activation, bn) + self.resConfUnit2 = ResidualConvUnit_custom(features, activation, bn) + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + res = self.resConfUnit1(xs[1]) + output = self.skip_add.add(output, res) + # output += res + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=self.align_corners + ) + + output = self.out_conv(output) + + return output + diff --git a/comparison_models/ControlNet/ldm/modules/midas/midas/dpt_depth.py b/comparison_models/ControlNet/ldm/modules/midas/midas/dpt_depth.py new file mode 100644 index 0000000..4e9aab5 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/midas/dpt_depth.py @@ -0,0 +1,109 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from .base_model import BaseModel +from .blocks import ( + FeatureFusionBlock, + FeatureFusionBlock_custom, + Interpolate, + _make_encoder, + forward_vit, +) + + +def _make_fusion_block(features, use_bn): + return FeatureFusionBlock_custom( + features, + nn.ReLU(False), + deconv=False, + bn=use_bn, + expand=False, + align_corners=True, + ) + + +class DPT(BaseModel): + def __init__( + self, + head, + features=256, + backbone="vitb_rn50_384", + readout="project", + channels_last=False, + use_bn=False, + ): + + super(DPT, self).__init__() + + self.channels_last = channels_last + + hooks = { + "vitb_rn50_384": [0, 1, 8, 11], + "vitb16_384": [2, 5, 8, 11], + "vitl16_384": [5, 11, 17, 23], + } + + # Instantiate backbone and reassemble blocks + self.pretrained, self.scratch = _make_encoder( + backbone, + features, + False, # Set to true of you want to train from scratch, uses ImageNet weights + groups=1, + expand=False, + exportable=False, + hooks=hooks[backbone], + use_readout=readout, + ) + + self.scratch.refinenet1 = _make_fusion_block(features, use_bn) + self.scratch.refinenet2 = _make_fusion_block(features, use_bn) + self.scratch.refinenet3 = _make_fusion_block(features, use_bn) + self.scratch.refinenet4 = _make_fusion_block(features, use_bn) + + self.scratch.output_conv = head + + + def forward(self, x): + if self.channels_last == True: + x.contiguous(memory_format=torch.channels_last) + + layer_1, layer_2, layer_3, layer_4 = forward_vit(self.pretrained, x) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return out + + +class DPTDepthModel(DPT): + def __init__(self, path=None, non_negative=True, **kwargs): + features = kwargs["features"] if "features" in kwargs else 256 + + head = nn.Sequential( + nn.Conv2d(features, features // 2, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear", align_corners=True), + nn.Conv2d(features // 2, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + super().__init__(head, **kwargs) + + if path is not None: + self.load(path) + + def forward(self, x): + return super().forward(x).squeeze(dim=1) + diff --git a/comparison_models/ControlNet/ldm/modules/midas/midas/midas_net.py b/comparison_models/ControlNet/ldm/modules/midas/midas/midas_net.py new file mode 100644 index 0000000..8a95497 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/midas/midas_net.py @@ -0,0 +1,76 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, Interpolate, _make_encoder + + +class MidasNet(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=256, non_negative=True): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + print("Loading weights: ", path) + + super(MidasNet, self).__init__() + + use_pretrained = False if path is None else True + + self.pretrained, self.scratch = _make_encoder(backbone="resnext101_wsl", features=features, use_pretrained=use_pretrained) + + self.scratch.refinenet4 = FeatureFusionBlock(features) + self.scratch.refinenet3 = FeatureFusionBlock(features) + self.scratch.refinenet2 = FeatureFusionBlock(features) + self.scratch.refinenet1 = FeatureFusionBlock(features) + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, 128, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(128, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + ) + + if path: + self.load(path) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) diff --git a/comparison_models/ControlNet/ldm/modules/midas/midas/midas_net_custom.py b/comparison_models/ControlNet/ldm/modules/midas/midas/midas_net_custom.py new file mode 100644 index 0000000..50e4acb --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/midas/midas_net_custom.py @@ -0,0 +1,128 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, FeatureFusionBlock_custom, Interpolate, _make_encoder + + +class MidasNet_small(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=64, backbone="efficientnet_lite3", non_negative=True, exportable=True, channels_last=False, align_corners=True, + blocks={'expand': True}): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + print("Loading weights: ", path) + + super(MidasNet_small, self).__init__() + + use_pretrained = False if path else True + + self.channels_last = channels_last + self.blocks = blocks + self.backbone = backbone + + self.groups = 1 + + features1=features + features2=features + features3=features + features4=features + self.expand = False + if "expand" in self.blocks and self.blocks['expand'] == True: + self.expand = True + features1=features + features2=features*2 + features3=features*4 + features4=features*8 + + self.pretrained, self.scratch = _make_encoder(self.backbone, features, use_pretrained, groups=self.groups, expand=self.expand, exportable=exportable) + + self.scratch.activation = nn.ReLU(False) + + self.scratch.refinenet4 = FeatureFusionBlock_custom(features4, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet3 = FeatureFusionBlock_custom(features3, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet2 = FeatureFusionBlock_custom(features2, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet1 = FeatureFusionBlock_custom(features1, self.scratch.activation, deconv=False, bn=False, align_corners=align_corners) + + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, features//2, kernel_size=3, stride=1, padding=1, groups=self.groups), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(features//2, 32, kernel_size=3, stride=1, padding=1), + self.scratch.activation, + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + if path: + self.load(path) + + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + if self.channels_last==True: + print("self.channels_last = ", self.channels_last) + x.contiguous(memory_format=torch.channels_last) + + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) + + + +def fuse_model(m): + prev_previous_type = nn.Identity() + prev_previous_name = '' + previous_type = nn.Identity() + previous_name = '' + for name, module in m.named_modules(): + if prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d and type(module) == nn.ReLU: + # print("FUSED ", prev_previous_name, previous_name, name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name, name], inplace=True) + elif prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d: + # print("FUSED ", prev_previous_name, previous_name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name], inplace=True) + # elif previous_type == nn.Conv2d and type(module) == nn.ReLU: + # print("FUSED ", previous_name, name) + # torch.quantization.fuse_modules(m, [previous_name, name], inplace=True) + + prev_previous_type = previous_type + prev_previous_name = previous_name + previous_type = type(module) + previous_name = name \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/modules/midas/midas/transforms.py b/comparison_models/ControlNet/ldm/modules/midas/midas/transforms.py new file mode 100644 index 0000000..350cbc1 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/midas/transforms.py @@ -0,0 +1,234 @@ +import numpy as np +import cv2 +import math + + +def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA): + """Rezise the sample to ensure the given size. Keeps aspect ratio. + + Args: + sample (dict): sample + size (tuple): image size + + Returns: + tuple: new size + """ + shape = list(sample["disparity"].shape) + + if shape[0] >= size[0] and shape[1] >= size[1]: + return sample + + scale = [0, 0] + scale[0] = size[0] / shape[0] + scale[1] = size[1] / shape[1] + + scale = max(scale) + + shape[0] = math.ceil(scale * shape[0]) + shape[1] = math.ceil(scale * shape[1]) + + # resize + sample["image"] = cv2.resize( + sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method + ) + + sample["disparity"] = cv2.resize( + sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST + ) + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + tuple(shape[::-1]), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return tuple(shape) + + +class Resize(object): + """Resize sample to given size (width, height). + """ + + def __init__( + self, + width, + height, + resize_target=True, + keep_aspect_ratio=False, + ensure_multiple_of=1, + resize_method="lower_bound", + image_interpolation_method=cv2.INTER_AREA, + ): + """Init. + + Args: + width (int): desired output width + height (int): desired output height + resize_target (bool, optional): + True: Resize the full sample (image, mask, target). + False: Resize image only. + Defaults to True. + keep_aspect_ratio (bool, optional): + True: Keep the aspect ratio of the input sample. + Output sample might not have the given width and height, and + resize behaviour depends on the parameter 'resize_method'. + Defaults to False. + ensure_multiple_of (int, optional): + Output width and height is constrained to be multiple of this parameter. + Defaults to 1. + resize_method (str, optional): + "lower_bound": Output will be at least as large as the given size. + "upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.) + "minimal": Scale as least as possible. (Output size might be smaller than given size.) + Defaults to "lower_bound". + """ + self.__width = width + self.__height = height + + self.__resize_target = resize_target + self.__keep_aspect_ratio = keep_aspect_ratio + self.__multiple_of = ensure_multiple_of + self.__resize_method = resize_method + self.__image_interpolation_method = image_interpolation_method + + def constrain_to_multiple_of(self, x, min_val=0, max_val=None): + y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if max_val is not None and y > max_val: + y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if y < min_val: + y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int) + + return y + + def get_size(self, width, height): + # determine new height and width + scale_height = self.__height / height + scale_width = self.__width / width + + if self.__keep_aspect_ratio: + if self.__resize_method == "lower_bound": + # scale such that output size is lower bound + if scale_width > scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "upper_bound": + # scale such that output size is upper bound + if scale_width < scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "minimal": + # scale as least as possbile + if abs(1 - scale_width) < abs(1 - scale_height): + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + else: + raise ValueError( + f"resize_method {self.__resize_method} not implemented" + ) + + if self.__resize_method == "lower_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, min_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, min_val=self.__width + ) + elif self.__resize_method == "upper_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, max_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, max_val=self.__width + ) + elif self.__resize_method == "minimal": + new_height = self.constrain_to_multiple_of(scale_height * height) + new_width = self.constrain_to_multiple_of(scale_width * width) + else: + raise ValueError(f"resize_method {self.__resize_method} not implemented") + + return (new_width, new_height) + + def __call__(self, sample): + width, height = self.get_size( + sample["image"].shape[1], sample["image"].shape[0] + ) + + # resize sample + sample["image"] = cv2.resize( + sample["image"], + (width, height), + interpolation=self.__image_interpolation_method, + ) + + if self.__resize_target: + if "disparity" in sample: + sample["disparity"] = cv2.resize( + sample["disparity"], + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + + if "depth" in sample: + sample["depth"] = cv2.resize( + sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST + ) + + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return sample + + +class NormalizeImage(object): + """Normlize image by given mean and std. + """ + + def __init__(self, mean, std): + self.__mean = mean + self.__std = std + + def __call__(self, sample): + sample["image"] = (sample["image"] - self.__mean) / self.__std + + return sample + + +class PrepareForNet(object): + """Prepare sample for usage as network input. + """ + + def __init__(self): + pass + + def __call__(self, sample): + image = np.transpose(sample["image"], (2, 0, 1)) + sample["image"] = np.ascontiguousarray(image).astype(np.float32) + + if "mask" in sample: + sample["mask"] = sample["mask"].astype(np.float32) + sample["mask"] = np.ascontiguousarray(sample["mask"]) + + if "disparity" in sample: + disparity = sample["disparity"].astype(np.float32) + sample["disparity"] = np.ascontiguousarray(disparity) + + if "depth" in sample: + depth = sample["depth"].astype(np.float32) + sample["depth"] = np.ascontiguousarray(depth) + + return sample diff --git a/comparison_models/ControlNet/ldm/modules/midas/midas/vit.py b/comparison_models/ControlNet/ldm/modules/midas/midas/vit.py new file mode 100644 index 0000000..ea46b1b --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/midas/vit.py @@ -0,0 +1,491 @@ +import torch +import torch.nn as nn +import timm +import types +import math +import torch.nn.functional as F + + +class Slice(nn.Module): + def __init__(self, start_index=1): + super(Slice, self).__init__() + self.start_index = start_index + + def forward(self, x): + return x[:, self.start_index :] + + +class AddReadout(nn.Module): + def __init__(self, start_index=1): + super(AddReadout, self).__init__() + self.start_index = start_index + + def forward(self, x): + if self.start_index == 2: + readout = (x[:, 0] + x[:, 1]) / 2 + else: + readout = x[:, 0] + return x[:, self.start_index :] + readout.unsqueeze(1) + + +class ProjectReadout(nn.Module): + def __init__(self, in_features, start_index=1): + super(ProjectReadout, self).__init__() + self.start_index = start_index + + self.project = nn.Sequential(nn.Linear(2 * in_features, in_features), nn.GELU()) + + def forward(self, x): + readout = x[:, 0].unsqueeze(1).expand_as(x[:, self.start_index :]) + features = torch.cat((x[:, self.start_index :], readout), -1) + + return self.project(features) + + +class Transpose(nn.Module): + def __init__(self, dim0, dim1): + super(Transpose, self).__init__() + self.dim0 = dim0 + self.dim1 = dim1 + + def forward(self, x): + x = x.transpose(self.dim0, self.dim1) + return x + + +def forward_vit(pretrained, x): + b, c, h, w = x.shape + + glob = pretrained.model.forward_flex(x) + + layer_1 = pretrained.activations["1"] + layer_2 = pretrained.activations["2"] + layer_3 = pretrained.activations["3"] + layer_4 = pretrained.activations["4"] + + layer_1 = pretrained.act_postprocess1[0:2](layer_1) + layer_2 = pretrained.act_postprocess2[0:2](layer_2) + layer_3 = pretrained.act_postprocess3[0:2](layer_3) + layer_4 = pretrained.act_postprocess4[0:2](layer_4) + + unflatten = nn.Sequential( + nn.Unflatten( + 2, + torch.Size( + [ + h // pretrained.model.patch_size[1], + w // pretrained.model.patch_size[0], + ] + ), + ) + ) + + if layer_1.ndim == 3: + layer_1 = unflatten(layer_1) + if layer_2.ndim == 3: + layer_2 = unflatten(layer_2) + if layer_3.ndim == 3: + layer_3 = unflatten(layer_3) + if layer_4.ndim == 3: + layer_4 = unflatten(layer_4) + + layer_1 = pretrained.act_postprocess1[3 : len(pretrained.act_postprocess1)](layer_1) + layer_2 = pretrained.act_postprocess2[3 : len(pretrained.act_postprocess2)](layer_2) + layer_3 = pretrained.act_postprocess3[3 : len(pretrained.act_postprocess3)](layer_3) + layer_4 = pretrained.act_postprocess4[3 : len(pretrained.act_postprocess4)](layer_4) + + return layer_1, layer_2, layer_3, layer_4 + + +def _resize_pos_embed(self, posemb, gs_h, gs_w): + posemb_tok, posemb_grid = ( + posemb[:, : self.start_index], + posemb[0, self.start_index :], + ) + + gs_old = int(math.sqrt(len(posemb_grid))) + + posemb_grid = posemb_grid.reshape(1, gs_old, gs_old, -1).permute(0, 3, 1, 2) + posemb_grid = F.interpolate(posemb_grid, size=(gs_h, gs_w), mode="bilinear") + posemb_grid = posemb_grid.permute(0, 2, 3, 1).reshape(1, gs_h * gs_w, -1) + + posemb = torch.cat([posemb_tok, posemb_grid], dim=1) + + return posemb + + +def forward_flex(self, x): + b, c, h, w = x.shape + + pos_embed = self._resize_pos_embed( + self.pos_embed, h // self.patch_size[1], w // self.patch_size[0] + ) + + B = x.shape[0] + + if hasattr(self.patch_embed, "backbone"): + x = self.patch_embed.backbone(x) + if isinstance(x, (list, tuple)): + x = x[-1] # last feature if backbone outputs list/tuple of features + + x = self.patch_embed.proj(x).flatten(2).transpose(1, 2) + + if getattr(self, "dist_token", None) is not None: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + dist_token = self.dist_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, dist_token, x), dim=1) + else: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + x = torch.cat((cls_tokens, x), dim=1) + + x = x + pos_embed + x = self.pos_drop(x) + + for blk in self.blocks: + x = blk(x) + + x = self.norm(x) + + return x + + +activations = {} + + +def get_activation(name): + def hook(model, input, output): + activations[name] = output + + return hook + + +def get_readout_oper(vit_features, features, use_readout, start_index=1): + if use_readout == "ignore": + readout_oper = [Slice(start_index)] * len(features) + elif use_readout == "add": + readout_oper = [AddReadout(start_index)] * len(features) + elif use_readout == "project": + readout_oper = [ + ProjectReadout(vit_features, start_index) for out_feat in features + ] + else: + assert ( + False + ), "wrong operation for readout token, use_readout can be 'ignore', 'add', or 'project'" + + return readout_oper + + +def _make_vit_b16_backbone( + model, + features=[96, 192, 384, 768], + size=[384, 384], + hooks=[2, 5, 8, 11], + vit_features=768, + use_readout="ignore", + start_index=1, +): + pretrained = nn.Module() + + pretrained.model = model + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + # 32, 48, 136, 384 + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitl16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_large_patch16_384", pretrained=pretrained) + + hooks = [5, 11, 17, 23] if hooks == None else hooks + return _make_vit_b16_backbone( + model, + features=[256, 512, 1024, 1024], + hooks=hooks, + vit_features=1024, + use_readout=use_readout, + ) + + +def _make_pretrained_vitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_pretrained_deitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_deit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_pretrained_deitb16_distil_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model( + "vit_deit_base_distilled_patch16_384", pretrained=pretrained + ) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, + features=[96, 192, 384, 768], + hooks=hooks, + use_readout=use_readout, + start_index=2, + ) + + +def _make_vit_b_rn50_backbone( + model, + features=[256, 512, 768, 768], + size=[384, 384], + hooks=[0, 1, 8, 11], + vit_features=768, + use_vit_only=False, + use_readout="ignore", + start_index=1, +): + pretrained = nn.Module() + + pretrained.model = model + + if use_vit_only == True: + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + else: + pretrained.model.patch_embed.backbone.stages[0].register_forward_hook( + get_activation("1") + ) + pretrained.model.patch_embed.backbone.stages[1].register_forward_hook( + get_activation("2") + ) + + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + if use_vit_only == True: + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + else: + pretrained.act_postprocess1 = nn.Sequential( + nn.Identity(), nn.Identity(), nn.Identity() + ) + pretrained.act_postprocess2 = nn.Sequential( + nn.Identity(), nn.Identity(), nn.Identity() + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitb_rn50_384( + pretrained, use_readout="ignore", hooks=None, use_vit_only=False +): + model = timm.create_model("vit_base_resnet50_384", pretrained=pretrained) + + hooks = [0, 1, 8, 11] if hooks == None else hooks + return _make_vit_b_rn50_backbone( + model, + features=[256, 512, 768, 768], + size=[384, 384], + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) diff --git a/comparison_models/ControlNet/ldm/modules/midas/utils.py b/comparison_models/ControlNet/ldm/modules/midas/utils.py new file mode 100644 index 0000000..9a9d3b5 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/midas/utils.py @@ -0,0 +1,189 @@ +"""Utils for monoDepth.""" +import sys +import re +import numpy as np +import cv2 +import torch + + +def read_pfm(path): + """Read pfm file. + + Args: + path (str): path to file + + Returns: + tuple: (data, scale) + """ + with open(path, "rb") as file: + + color = None + width = None + height = None + scale = None + endian = None + + header = file.readline().rstrip() + if header.decode("ascii") == "PF": + color = True + elif header.decode("ascii") == "Pf": + color = False + else: + raise Exception("Not a PFM file: " + path) + + dim_match = re.match(r"^(\d+)\s(\d+)\s$", file.readline().decode("ascii")) + if dim_match: + width, height = list(map(int, dim_match.groups())) + else: + raise Exception("Malformed PFM header.") + + scale = float(file.readline().decode("ascii").rstrip()) + if scale < 0: + # little-endian + endian = "<" + scale = -scale + else: + # big-endian + endian = ">" + + data = np.fromfile(file, endian + "f") + shape = (height, width, 3) if color else (height, width) + + data = np.reshape(data, shape) + data = np.flipud(data) + + return data, scale + + +def write_pfm(path, image, scale=1): + """Write pfm file. + + Args: + path (str): pathto file + image (array): data + scale (int, optional): Scale. Defaults to 1. + """ + + with open(path, "wb") as file: + color = None + + if image.dtype.name != "float32": + raise Exception("Image dtype must be float32.") + + image = np.flipud(image) + + if len(image.shape) == 3 and image.shape[2] == 3: # color image + color = True + elif ( + len(image.shape) == 2 or len(image.shape) == 3 and image.shape[2] == 1 + ): # greyscale + color = False + else: + raise Exception("Image must have H x W x 3, H x W x 1 or H x W dimensions.") + + file.write("PF\n" if color else "Pf\n".encode()) + file.write("%d %d\n".encode() % (image.shape[1], image.shape[0])) + + endian = image.dtype.byteorder + + if endian == "<" or endian == "=" and sys.byteorder == "little": + scale = -scale + + file.write("%f\n".encode() % scale) + + image.tofile(file) + + +def read_image(path): + """Read image and output RGB image (0-1). + + Args: + path (str): path to file + + Returns: + array: RGB image (0-1) + """ + img = cv2.imread(path) + + if img.ndim == 2: + img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) / 255.0 + + return img + + +def resize_image(img): + """Resize image and make it fit for network. + + Args: + img (array): image + + Returns: + tensor: data ready for network + """ + height_orig = img.shape[0] + width_orig = img.shape[1] + + if width_orig > height_orig: + scale = width_orig / 384 + else: + scale = height_orig / 384 + + height = (np.ceil(height_orig / scale / 32) * 32).astype(int) + width = (np.ceil(width_orig / scale / 32) * 32).astype(int) + + img_resized = cv2.resize(img, (width, height), interpolation=cv2.INTER_AREA) + + img_resized = ( + torch.from_numpy(np.transpose(img_resized, (2, 0, 1))).contiguous().float() + ) + img_resized = img_resized.unsqueeze(0) + + return img_resized + + +def resize_depth(depth, width, height): + """Resize depth map and bring to CPU (numpy). + + Args: + depth (tensor): depth + width (int): image width + height (int): image height + + Returns: + array: processed depth + """ + depth = torch.squeeze(depth[0, :, :, :]).to("cpu") + + depth_resized = cv2.resize( + depth.numpy(), (width, height), interpolation=cv2.INTER_CUBIC + ) + + return depth_resized + +def write_depth(path, depth, bits=1): + """Write depth map to pfm and png file. + + Args: + path (str): filepath without extension + depth (array): depth + """ + write_pfm(path + ".pfm", depth.astype(np.float32)) + + depth_min = depth.min() + depth_max = depth.max() + + max_val = (2**(8*bits))-1 + + if depth_max - depth_min > np.finfo("float").eps: + out = max_val * (depth - depth_min) / (depth_max - depth_min) + else: + out = np.zeros(depth.shape, dtype=depth.type) + + if bits == 1: + cv2.imwrite(path + ".png", out.astype("uint8")) + elif bits == 2: + cv2.imwrite(path + ".png", out.astype("uint16")) + + return diff --git a/comparison_models/ControlNet/ldm/util.py b/comparison_models/ControlNet/ldm/util.py new file mode 100644 index 0000000..45cb050 --- /dev/null +++ b/comparison_models/ControlNet/ldm/util.py @@ -0,0 +1,197 @@ +import importlib + +import torch +from torch import optim +import numpy as np + +from inspect import isfunction +from PIL import Image, ImageDraw, ImageFont + + +def log_txt_as_img(wh, xc, size=10): + # wh a tuple of (width, height) + # xc a list of captions to plot + b = len(xc) + txts = list() + for bi in range(b): + txt = Image.new("RGB", wh, color="white") + draw = ImageDraw.Draw(txt) + font = ImageFont.truetype('font/DejaVuSans.ttf', size=size) + nc = int(40 * (wh[0] / 256)) + lines = "\n".join(xc[bi][start:start + nc] for start in range(0, len(xc[bi]), nc)) + + try: + draw.text((0, 0), lines, fill="black", font=font) + except UnicodeEncodeError: + print("Cant encode string for logging. Skipping.") + + txt = np.array(txt).transpose(2, 0, 1) / 127.5 - 1.0 + txts.append(txt) + txts = np.stack(txts) + txts = torch.tensor(txts) + return txts + + +def ismap(x): + if not isinstance(x, torch.Tensor): + return False + return (len(x.shape) == 4) and (x.shape[1] > 3) + + +def isimage(x): + if not isinstance(x,torch.Tensor): + return False + return (len(x.shape) == 4) and (x.shape[1] == 3 or x.shape[1] == 1) + + +def exists(x): + return x is not None + + +def default(val, d): + if exists(val): + return val + return d() if isfunction(d) else d + + +def mean_flat(tensor): + """ + https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/nn.py#L86 + Take the mean over all non-batch dimensions. + """ + return tensor.mean(dim=list(range(1, len(tensor.shape)))) + + +def count_params(model, verbose=False): + total_params = sum(p.numel() for p in model.parameters()) + if verbose: + print(f"{model.__class__.__name__} has {total_params*1.e-6:.2f} M params.") + return total_params + + +def instantiate_from_config(config): + if not "target" in config: + if config == '__is_first_stage__': + return None + elif config == "__is_unconditional__": + return None + raise KeyError("Expected key `target` to instantiate.") + return get_obj_from_str(config["target"])(**config.get("params", dict())) + + +def get_obj_from_str(string, reload=False): + module, cls = string.rsplit(".", 1) + if reload: + module_imp = importlib.import_module(module) + importlib.reload(module_imp) + return getattr(importlib.import_module(module, package=None), cls) + + +class AdamWwithEMAandWings(optim.Optimizer): + # credit to https://gist.github.com/crowsonkb/65f7265353f403714fce3b2595e0b298 + def __init__(self, params, lr=1.e-3, betas=(0.9, 0.999), eps=1.e-8, # TODO: check hyperparameters before using + weight_decay=1.e-2, amsgrad=False, ema_decay=0.9999, # ema decay to match previous code + ema_power=1., param_names=()): + """AdamW that saves EMA versions of the parameters.""" + if not 0.0 <= lr: + raise ValueError("Invalid learning rate: {}".format(lr)) + if not 0.0 <= eps: + raise ValueError("Invalid epsilon value: {}".format(eps)) + if not 0.0 <= betas[0] < 1.0: + raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0])) + if not 0.0 <= betas[1] < 1.0: + raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1])) + if not 0.0 <= weight_decay: + raise ValueError("Invalid weight_decay value: {}".format(weight_decay)) + if not 0.0 <= ema_decay <= 1.0: + raise ValueError("Invalid ema_decay value: {}".format(ema_decay)) + defaults = dict(lr=lr, betas=betas, eps=eps, + weight_decay=weight_decay, amsgrad=amsgrad, ema_decay=ema_decay, + ema_power=ema_power, param_names=param_names) + super().__init__(params, defaults) + + def __setstate__(self, state): + super().__setstate__(state) + for group in self.param_groups: + group.setdefault('amsgrad', False) + + @torch.no_grad() + def step(self, closure=None): + """Performs a single optimization step. + Args: + closure (callable, optional): A closure that reevaluates the model + and returns the loss. + """ + loss = None + if closure is not None: + with torch.enable_grad(): + loss = closure() + + for group in self.param_groups: + params_with_grad = [] + grads = [] + exp_avgs = [] + exp_avg_sqs = [] + ema_params_with_grad = [] + state_sums = [] + max_exp_avg_sqs = [] + state_steps = [] + amsgrad = group['amsgrad'] + beta1, beta2 = group['betas'] + ema_decay = group['ema_decay'] + ema_power = group['ema_power'] + + for p in group['params']: + if p.grad is None: + continue + params_with_grad.append(p) + if p.grad.is_sparse: + raise RuntimeError('AdamW does not support sparse gradients') + grads.append(p.grad) + + state = self.state[p] + + # State initialization + if len(state) == 0: + state['step'] = 0 + # Exponential moving average of gradient values + state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format) + # Exponential moving average of squared gradient values + state['exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format) + if amsgrad: + # Maintains max of all exp. moving avg. of sq. grad. values + state['max_exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format) + # Exponential moving average of parameter values + state['param_exp_avg'] = p.detach().float().clone() + + exp_avgs.append(state['exp_avg']) + exp_avg_sqs.append(state['exp_avg_sq']) + ema_params_with_grad.append(state['param_exp_avg']) + + if amsgrad: + max_exp_avg_sqs.append(state['max_exp_avg_sq']) + + # update the steps for each param group update + state['step'] += 1 + # record the step after step update + state_steps.append(state['step']) + + optim._functional.adamw(params_with_grad, + grads, + exp_avgs, + exp_avg_sqs, + max_exp_avg_sqs, + state_steps, + amsgrad=amsgrad, + beta1=beta1, + beta2=beta2, + lr=group['lr'], + weight_decay=group['weight_decay'], + eps=group['eps'], + maximize=False) + + cur_ema_decay = min(ema_decay, 1 - state['step'] ** -ema_power) + for param, ema_param in zip(params_with_grad, ema_params_with_grad): + ema_param.mul_(cur_ema_decay).add_(param.float(), alpha=1 - cur_ema_decay) + + return loss \ No newline at end of file diff --git a/comparison_models/ControlNet/models/cldm_v15.yaml b/comparison_models/ControlNet/models/cldm_v15.yaml new file mode 100644 index 0000000..fde1825 --- /dev/null +++ b/comparison_models/ControlNet/models/cldm_v15.yaml @@ -0,0 +1,79 @@ +model: + target: cldm.cldm.ControlLDM + params: + linear_start: 0.00085 + linear_end: 0.0120 + num_timesteps_cond: 1 + log_every_t: 200 + timesteps: 1000 + first_stage_key: "jpg" + cond_stage_key: "txt" + control_key: "hint" + image_size: 64 + channels: 4 + cond_stage_trainable: false + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + only_mid_control: False + + control_stage_config: + target: cldm.cldm.ControlNet + params: + image_size: 32 # unused + in_channels: 4 + hint_channels: 3 + 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 + + unet_config: + target: cldm.cldm.ControlledUnetModel + params: + image_size: 32 # unused + 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: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + 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: + target: torch.nn.Identity + + cond_stage_config: + target: ldm.modules.encoders.modules.FrozenCLIPEmbedder diff --git a/comparison_models/ControlNet/models/cldm_v21.yaml b/comparison_models/ControlNet/models/cldm_v21.yaml new file mode 100644 index 0000000..fc65193 --- /dev/null +++ b/comparison_models/ControlNet/models/cldm_v21.yaml @@ -0,0 +1,85 @@ +model: + target: cldm.cldm.ControlLDM + params: + linear_start: 0.00085 + linear_end: 0.0120 + num_timesteps_cond: 1 + log_every_t: 200 + timesteps: 1000 + first_stage_key: "jpg" + cond_stage_key: "txt" + control_key: "hint" + image_size: 64 + channels: 4 + cond_stage_trainable: false + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + only_mid_control: False + + control_stage_config: + target: cldm.cldm.ControlNet + params: + use_checkpoint: True + image_size: 32 # unused + in_channels: 4 + hint_channels: 3 + model_channels: 320 + attention_resolutions: [ 4, 2, 1 ] + num_res_blocks: 2 + channel_mult: [ 1, 2, 4, 4 ] + num_head_channels: 64 # need to fix for flash-attn + use_spatial_transformer: True + use_linear_in_transformer: True + transformer_depth: 1 + context_dim: 1024 + legacy: False + + unet_config: + target: cldm.cldm.ControlledUnetModel + params: + use_checkpoint: True + image_size: 32 # unused + 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_head_channels: 64 # need to fix for flash-attn + use_spatial_transformer: True + use_linear_in_transformer: True + transformer_depth: 1 + context_dim: 1024 + legacy: False + + first_stage_config: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + #attn_type: "vanilla-xformers" + 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: + target: torch.nn.Identity + + cond_stage_config: + target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder + params: + freeze: True + layer: "penultimate" diff --git a/comparison_models/ControlNet/share.py b/comparison_models/ControlNet/share.py new file mode 100644 index 0000000..463af08 --- /dev/null +++ b/comparison_models/ControlNet/share.py @@ -0,0 +1,8 @@ +import config +from cldm.hack import disable_verbosity, enable_sliced_attention + + +disable_verbosity() + +if config.save_memory: + enable_sliced_attention() diff --git a/comparison_models/ControlNet/tool_add_control.py b/comparison_models/ControlNet/tool_add_control.py new file mode 100644 index 0000000..8076b51 --- /dev/null +++ b/comparison_models/ControlNet/tool_add_control.py @@ -0,0 +1,50 @@ +import sys +import os + +assert len(sys.argv) == 3, 'Args are wrong.' + +input_path = sys.argv[1] +output_path = sys.argv[2] + +assert os.path.exists(input_path), 'Input model does not exist.' +assert not os.path.exists(output_path), 'Output filename already exists.' +assert os.path.exists(os.path.dirname(output_path)), 'Output path is not valid.' + +import torch +from share import * +from cldm.model import create_model + + +def get_node_name(name, parent_name): + if len(name) <= len(parent_name): + return False, '' + p = name[:len(parent_name)] + if p != parent_name: + return False, '' + return True, name[len(parent_name):] + + +model = create_model(config_path='./models/cldm_v15.yaml') + +pretrained_weights = torch.load(input_path) +if 'state_dict' in pretrained_weights: + pretrained_weights = pretrained_weights['state_dict'] + +scratch_dict = model.state_dict() + +target_dict = {} +for k in scratch_dict.keys(): + is_control, name = get_node_name(k, 'control_') + if is_control: + copy_k = 'model.diffusion_' + name + else: + copy_k = k + if copy_k in pretrained_weights: + target_dict[k] = pretrained_weights[copy_k].clone() + else: + target_dict[k] = scratch_dict[k].clone() + print(f'These weights are newly added: {k}') + +model.load_state_dict(target_dict, strict=True) +torch.save(model.state_dict(), output_path) +print('Done.') diff --git a/comparison_models/ControlNet/tool_add_control_sd21.py b/comparison_models/ControlNet/tool_add_control_sd21.py new file mode 100644 index 0000000..7c3ac5f --- /dev/null +++ b/comparison_models/ControlNet/tool_add_control_sd21.py @@ -0,0 +1,50 @@ +import sys +import os + +assert len(sys.argv) == 3, 'Args are wrong.' + +input_path = sys.argv[1] +output_path = sys.argv[2] + +assert os.path.exists(input_path), 'Input model does not exist.' +assert not os.path.exists(output_path), 'Output filename already exists.' +assert os.path.exists(os.path.dirname(output_path)), 'Output path is not valid.' + +import torch +from share import * +from cldm.model import create_model + + +def get_node_name(name, parent_name): + if len(name) <= len(parent_name): + return False, '' + p = name[:len(parent_name)] + if p != parent_name: + return False, '' + return True, name[len(parent_name):] + + +model = create_model(config_path='./models/cldm_v21.yaml') + +pretrained_weights = torch.load(input_path) +if 'state_dict' in pretrained_weights: + pretrained_weights = pretrained_weights['state_dict'] + +scratch_dict = model.state_dict() + +target_dict = {} +for k in scratch_dict.keys(): + is_control, name = get_node_name(k, 'control_') + if is_control: + copy_k = 'model.diffusion_' + name + else: + copy_k = k + if copy_k in pretrained_weights: + target_dict[k] = pretrained_weights[copy_k].clone() + else: + target_dict[k] = scratch_dict[k].clone() + print(f'These weights are newly added: {k}') + +model.load_state_dict(target_dict, strict=True) +torch.save(model.state_dict(), output_path) +print('Done.') diff --git a/comparison_models/ControlNet/tool_transfer_control.py b/comparison_models/ControlNet/tool_transfer_control.py new file mode 100644 index 0000000..b84442c --- /dev/null +++ b/comparison_models/ControlNet/tool_transfer_control.py @@ -0,0 +1,59 @@ +path_sd15 = './models/v1-5-pruned.ckpt' +path_sd15_with_control = './models/control_sd15_openpose.pth' +path_input = './models/anything-v3-full.safetensors' +path_output = './models/control_any3_openpose.pth' + + +import os + + +assert os.path.exists(path_sd15), 'Input path_sd15 does not exists!' +assert os.path.exists(path_sd15_with_control), 'Input path_sd15_with_control does not exists!' +assert os.path.exists(path_input), 'Input path_input does not exists!' +assert os.path.exists(os.path.dirname(path_output)), 'Output folder not exists!' + + +import torch +from share import * +from cldm.model import load_state_dict + + +sd15_state_dict = load_state_dict(path_sd15) +sd15_with_control_state_dict = load_state_dict(path_sd15_with_control) +input_state_dict = load_state_dict(path_input) + + +def get_node_name(name, parent_name): + if len(name) <= len(parent_name): + return False, '' + p = name[:len(parent_name)] + if p != parent_name: + return False, '' + return True, name[len(parent_name):] + + +keys = sd15_with_control_state_dict.keys() + +final_state_dict = {} +for key in keys: + is_first_stage, _ = get_node_name(key, 'first_stage_model') + is_cond_stage, _ = get_node_name(key, 'cond_stage_model') + if is_first_stage or is_cond_stage: + final_state_dict[key] = input_state_dict[key] + continue + p = sd15_with_control_state_dict[key] + is_control, node_name = get_node_name(key, 'control_') + if is_control: + sd15_key_name = 'model.diffusion_' + node_name + else: + sd15_key_name = key + if sd15_key_name in input_state_dict: + p_new = p + input_state_dict[sd15_key_name] - sd15_state_dict[sd15_key_name] + # print(f'Offset clone from [{sd15_key_name}] to [{key}]') + else: + p_new = p + # print(f'Direct clone to [{key}]') + final_state_dict[key] = p_new + +torch.save(final_state_dict, path_output) +print('Transferred model saved at ' + path_output) diff --git a/comparison_models/ControlNet/tutorial_dataset.py b/comparison_models/ControlNet/tutorial_dataset.py new file mode 100644 index 0000000..fb327f9 --- /dev/null +++ b/comparison_models/ControlNet/tutorial_dataset.py @@ -0,0 +1,39 @@ +import json +import cv2 +import numpy as np + +from torch.utils.data import Dataset + + +class MyDataset(Dataset): + def __init__(self): + self.data = [] + with open('./training/fill50k/prompt.json', 'rt') as f: + for line in f: + self.data.append(json.loads(line)) + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + item = self.data[idx] + + source_filename = item['source'] + target_filename = item['target'] + prompt = item['prompt'] + + source = cv2.imread('./training/fill50k/' + source_filename) + target = cv2.imread('./training/fill50k/' + target_filename) + + # Do not forget that OpenCV read images in BGR order. + source = cv2.cvtColor(source, cv2.COLOR_BGR2RGB) + target = cv2.cvtColor(target, cv2.COLOR_BGR2RGB) + + # Normalize source images to [0, 1]. + source = source.astype(np.float32) / 255.0 + + # Normalize target images to [-1, 1]. + target = (target.astype(np.float32) / 127.5) - 1.0 + + return dict(jpg=target, txt=prompt, hint=source) + diff --git a/comparison_models/ControlNet/tutorial_dataset_test.py b/comparison_models/ControlNet/tutorial_dataset_test.py new file mode 100644 index 0000000..b0c4c35 --- /dev/null +++ b/comparison_models/ControlNet/tutorial_dataset_test.py @@ -0,0 +1,12 @@ +from tutorial_dataset import MyDataset + +dataset = MyDataset() +print(len(dataset)) + +item = dataset[1234] +jpg = item['jpg'] +txt = item['txt'] +hint = item['hint'] +print(txt) +print(jpg.shape) +print(hint.shape) diff --git a/comparison_models/ControlNet/tutorial_train.py b/comparison_models/ControlNet/tutorial_train.py new file mode 100644 index 0000000..393d7ad --- /dev/null +++ b/comparison_models/ControlNet/tutorial_train.py @@ -0,0 +1,35 @@ +from share import * + +import pytorch_lightning as pl +from torch.utils.data import DataLoader +from tutorial_dataset import MyDataset +from cldm.logger import ImageLogger +from cldm.model import create_model, load_state_dict + + +# Configs +resume_path = './models/control_sd15_ini.ckpt' +batch_size = 4 +logger_freq = 300 +learning_rate = 1e-5 +sd_locked = True +only_mid_control = False + + +# First use cpu to load models. Pytorch Lightning will automatically move it to GPUs. +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict(resume_path, location='cpu')) +model.learning_rate = learning_rate +model.sd_locked = sd_locked +model.only_mid_control = only_mid_control + + +# Misc +dataset = MyDataset() +dataloader = DataLoader(dataset, num_workers=0, batch_size=batch_size, shuffle=True) +logger = ImageLogger(batch_frequency=logger_freq) +trainer = pl.Trainer(gpus=1, precision=32, callbacks=[logger]) + + +# Train! +trainer.fit(model, dataloader) diff --git a/comparison_models/ControlNet/tutorial_train_sd21.py b/comparison_models/ControlNet/tutorial_train_sd21.py new file mode 100644 index 0000000..8bbc148 --- /dev/null +++ b/comparison_models/ControlNet/tutorial_train_sd21.py @@ -0,0 +1,35 @@ +from share import * + +import pytorch_lightning as pl +from torch.utils.data import DataLoader +from tutorial_dataset import MyDataset +from cldm.logger import ImageLogger +from cldm.model import create_model, load_state_dict + + +# Configs +resume_path = './models/control_sd21_ini.ckpt' +batch_size = 4 +logger_freq = 300 +learning_rate = 1e-5 +sd_locked = True +only_mid_control = False + + +# First use cpu to load models. Pytorch Lightning will automatically move it to GPUs. +model = create_model('./models/cldm_v21.yaml').cpu() +model.load_state_dict(load_state_dict(resume_path, location='cpu')) +model.learning_rate = learning_rate +model.sd_locked = sd_locked +model.only_mid_control = only_mid_control + + +# Misc +dataset = MyDataset() +dataloader = DataLoader(dataset, num_workers=0, batch_size=batch_size, shuffle=True) +logger = ImageLogger(batch_frequency=logger_freq) +trainer = pl.Trainer(gpus=1, precision=32, callbacks=[logger]) + + +# Train! +trainer.fit(model, dataloader) diff --git a/comparison_models/T2IAdapter/.gitignore b/comparison_models/T2IAdapter/.gitignore new file mode 100644 index 0000000..ac7b3e4 --- /dev/null +++ b/comparison_models/T2IAdapter/.gitignore @@ -0,0 +1,127 @@ +# ignored folders +tmp/* + +# ignore images in examples folder +examples/*/* + +*.DS_Store +.idea + +# ignored files +version.py + +# ignored files with suffix +# *.html +# *.png +# *.jpeg +# *.jpg +# *.gif +# *.pth +# *.zip + +# template + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/comparison_models/T2IAdapter/LICENSE b/comparison_models/T2IAdapter/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/comparison_models/T2IAdapter/LICENSE @@ -0,0 +1,201 @@ + 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 [yyyy] [name of copyright owner] + + 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/comparison_models/T2IAdapter/README.md b/comparison_models/T2IAdapter/README.md new file mode 100644 index 0000000..ceb4635 --- /dev/null +++ b/comparison_models/T2IAdapter/README.md @@ -0,0 +1,331 @@ +

+ +

+ +
+ + 💥**T2I-Adapter-SDXL:**[![GitHub](https://img.shields.io/badge/-GitHub-181717?style=flat&logo=github&logoColor=white)](https://github.com/TencentARC/T2I-Adapter/tree/XL) +**|** **CoAdapter:**[![Huggingface CoAdapter](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/CoAdapter) **|** **T2I-Adapter:**[![Huggingface T2I-Adapter](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) + + 🎨[**Demos**](docs/examples.md) **|** ⏬[**Download Models**](#-download-models) **|** 💻[**How to Test**](#-how-to-test) **|** 🏰[**Adapter Zoo**](docs/AdapterZoo.md) +
+ +Official implementation of **[T2I-Adapter: Learning Adapters to Dig out More Controllable Ability for Text-to-Image Diffusion Models](https://arxiv.org/abs/2302.08453)**. + +--- + + + +### 🚩 **New Features/Updates** +- ✅ Aug. 21, 2023. We release [T2I-Adapter-SDXL](https://github.com/TencentARC/T2I-Adapter/tree/XL), including sketch, canny, and keypoint. We still use the original recipe (77M parameters, a single inference) to drive [StableDiffusion-XL](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0). Due to the limited computing resources, those adapters still need further improvement. We are collaborating with [HuggingFace](https://huggingface.co/), and a more powerful adapter is in the works. + +- ✅ Jul. 13, 2023. [Stability AI](https://stability.ai/) release [Stable Doodle](https://stability.ai/blog/clipdrop-launches-stable-doodle), a groundbreaking sketch-to-image tool based on T2I-Adapter and [SDXL](https://huggingface.co/stabilityai/stable-diffusion-xl-base-0.9). It makes drawing easier. + +https://user-images.githubusercontent.com/73707470/253800159-c7e12362-1ea1-4b20-a44e-bd6c8d546765.mp4 + +- ✅ Mar. 16, 2023. We add **CoAdapter** (**Co**mposable **Adapter**). The online Huggingface Gadio has been updated [![Huggingface Gradio (CoAdapter)](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/CoAdapter). You can also try the [local gradio demo](app_coadapter.py). +- ✅ Mar. 16, 2023. We have shrunk the git repo with [bfg](https://rtyley.github.io/bfg-repo-cleaner/). If you encounter any issues when pulling or pushing, you can try re-cloning the repository. Sorry for the inconvenience. +- ✅ Mar. 3, 2023. Add a [*color adapter (spatial palette)*](https://huggingface.co/TencentARC/T2I-Adapter/tree/main/models), which has only **17M parameters**. +- ✅ Mar. 3, 2023. Add four new adapters [*style, color, openpose and canny*](https://huggingface.co/TencentARC/T2I-Adapter/tree/main/models). See more info in the **[Adapter Zoo](docs/AdapterZoo.md)**. +- ✅ Feb. 23, 2023. Add the depth adapter [*t2iadapter_depth_sd14v1.pth*](https://huggingface.co/TencentARC/T2I-Adapter/tree/main/models). See more info in the **[Adapter Zoo](docs/AdapterZoo.md)**. +- ✅ Feb. 15, 2023. Release T2I-Adapter. + +--- +
+ + 🔥🔥🔥 Support **CoAdapter** (**Co**mposable **Adapter**).
You can find the details and demos about CoAdapter from [coadapter.md](docs/coadapter.md) + + + +
+ +--- +# Introduction +

+ +

+ +We propose T2I-Adapter, a **simple and small (~70M parameters, ~300M storage space)** network that can provide extra guidance to pre-trained text-to-image models while **freezing** the original large text-to-image models. + +T2I-Adapter aligns internal knowledge in T2I models with external control signals. +We can train various adapters according to different conditions, and achieve rich control and editing effects. + + + +# ⏬ Download Models + +Put the downloaded models in the `T2I-Adapter/models` folder. + +1. You can find the pretrained **T2I-Adapters**, **CoAdapters**, and third party models from . +2. A base SD model is still needed to inference. We recommend to use **Stable Diffusion v1.5**. But please note that the adapters should work well on other SD models which are finetuned from SD-V1.4 or SD-V1.5. You can download these models from HuggingFace or civitai, all the following tested models (e.g., Anything anime model) can be found in there. +3. [Optional] If you want to use mmpose adapter, you need to download the pretrained keypose detection models include [FasterRCNN (human detection)](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth) and [HRNet (pose detection)](https://download.openmmlab.com/mmpose/top_down/hrnet/hrnet_w48_coco_256x192-b9e0b3ab_20200708.pth). + + +# 🔧 Dependencies and Installation + +- Python >= 3.6 (Recommend to use [Anaconda](https://www.anaconda.com/download/#linux) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html)) +- [PyTorch >= 1.4](https://pytorch.org/) +```bash +pip install -r requirements.txt +``` +- If you want to use the full function of keypose-guided generation, you need to install MMPose. For details please refer to . + +# 💻 How to Test + +#### Download Examples (optional) +We provide some [example](https://huggingface.co/TencentARC/T2I-Adapter/tree/main/examples) inputs in huggingface, you can download these examples by: +```bash +python examples/download_examples.py +``` + +#### **🔥🔥🔥Gradio Demo for CoAdapter** +You need to download the pretrained CoAdapters from [huggingface](https://huggingface.co/TencentARC/T2I-Adapter) first, and put them in the `models` folder +```bash +# test for stable diffusion v1.5 +python app_coadapter.py --sd_ckpt models/v1-5-pruned-emaonly.ckpt +``` + + +

+ + + + + + +

+ +#### **Gradio Demo for T2I-Adapters** +```bash +# test for stable diffusion v1.5 +python app.py --sd_ckpt models/v1-5-pruned-emaonly.ckpt +# test for other stable diffusion model, like Anything 4.5 +python app.py --sd_ckpt models/anything-v4.5-pruned-fp16.ckpt --vae_ckpt models/anything-v4.0.vae.pt +``` + +#### **Spatial Palette (Color) Adapter** +```bash +# when input color image +python test_adapter.py --which_cond color --cond_path examples/color/color_0002.png --cond_inp_type color --prompt "A photo of scenery" --sd_ckpt models/v1-5-pruned-emaonly.ckpt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_color_sd14v1.pth --scale 9 +# when input non-color image, obtain the color image is also straightforward, just bicubic downsample to low res and nearst upsample normal res +python test_adapter.py --which_cond color --cond_path examples/sketch/scenery.jpg --cond_inp_type image --prompt "A photo of scenery" --sd_ckpt models/v1-5-pruned-emaonly.ckpt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_color_sd14v1.pth --scale 9 +``` +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) +

+ +

+ + + +#### **Depth Adapter** +```bash +# when input non-depth image +python test_adapter.py --which_cond depth --cond_path examples/depth/sd.png --cond_inp_type image --prompt "Stormtrooper's lecture, best quality, extremely detailed" --sd_ckpt models/v1-5-pruned-emaonly.ckpt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_depth_sd14v1.pth +# when input depth image +python test_adapter.py --which_cond depth --cond_path examples/depth/desk_depth.png --cond_inp_type depth --prompt "desk, best quality, extremely detailed" --sd_ckpt models/v1-5-pruned-emaonly.ckpt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_depth_sd14v1.pth +``` +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) +

+ +

+ +#### **Canny Adapter** +```bash +# when input canny image +python python test_adapter.py --which_cond canny --cond_path examples/canny/toy_canny.png --cond_inp_type canny --prompt "Cute toy, best quality, extremely detailed" --sd_ckpt models/anything-v4.5-pruned-fp16.ckpt --vae_ckpt models/anything-v4.0.vae.pt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_canny_sd14v1.pth +# when input non-canny image +python python test_adapter.py --which_cond canny --cond_path examples/canny/rabbit.png --cond_inp_type image --prompt "A rabbit, best quality, extremely detailed" --sd_ckpt models/sd-v1-4.ckpt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_canny_sd14v1.pth +``` +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) + +

+ +

+ +#### **Sketch Adapter** +```bash +# when input sketch image +python test_adapter.py --which_cond sketch --cond_path examples/sketch/car.png --cond_inp_type sketch --prompt "A car with flying wings" --sd_ckpt models/sd-v1-4.ckpt --resize_short_edge 512 --cond_tau 0.5 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_sketch_sd14v1.pth +# when input non-sketch image +python test_adapter.py --which_cond sketch --cond_path examples/sketch/girl.jpeg --cond_inp_type image --prompt "1girl, masterpiece, high-quality, high-res" --sd_ckpt models/anything-v4.5-pruned-fp16.ckpt --vae_ckpt models/anything-v4.0.vae.pt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_sketch_sd14v1.pth +``` + +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) +

+ +

+ +

+ +

+ + +#### **OpenPose Adapter** +```bash +# when input non-pose image +python test_adapter.py --which_cond openpose --cond_path examples/openpose/iron_man_image.png --cond_inp_type image --prompt "Iron man, high-quality, high-res" --sd_ckpt models/sd-v1-5.ckpt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 1 --adapter_ckpt models/t2iadapter_openpose_sd14v1.pth +# when input openpose image +python test_adapter.py --which_cond openpose --cond_path examples/openpose/iron_man_pose.png --cond_inp_type openpose --prompt "Iron man, high-quality, high-res" --sd_ckpt models/anything-v4.5-pruned-fp16.ckpt --vae_ckpt models/anything-v4.0.vae.pt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 1 --adapter_ckpt models/t2iadapter_openpose_sd14v1.pth +``` + +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) +

+ +

+ + +#### **Keypose Adapter** +```bash +# when input non-pose image +python test_adapter.py --which_cond keypose --cond_path examples/sketch/girl.jpeg --cond_inp_type image --prompt "1girl, masterpiece, high-quality, high-res" --sd_ckpt models/anything-v4.5-pruned-fp16.ckpt --vae_ckpt models/anything-v4.0.vae.pt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 1 --adapter_ckpt models/t2iadapter_keypose_sd14v1.pth + +# when input pose image +python test_adapter.py --which_cond keypose --cond_path examples/keypose/person_keypose.png --cond_inp_type keypose --prompt "astronaut, best quality, extremely detailed" --sd_ckpt models/v1-5-pruned-emaonly.ckpt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_keypose_sd14v1.pth +``` + +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) +

+ +

+ + +#### **Segmentation Adapter** +```bash +# currently, only seg input is supported, if you need image as input, please let us know +python test_adapter.py --which_cond seg --cond_path examples/seg/motor.png --cond_inp_type seg --prompt "A black Honda motorcycle parked in front of a garage, best quality, extremely detailed" --sd_ckpt models/v1-5-pruned-emaonly.ckpt --resize_short_edge 512 --cond_tau 1.0 --cond_weight 1.0 --n_samples 2 --adapter_ckpt models/t2iadapter_seg_sd14v1.pth +``` + +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) +

+ +

+ +#### **Combine multiple Adapters** +```bash +# test depth + keypose +python test_composable_adapters.py --prompt "1girl, computer desk, red chair best quality, extremely detailed" --depth_path examples/depth/desk_depth.png --depth_weight 1.0 --depth_adapter_ckpt experiments/train_depth/models/model_ad_70000.pth --depth_inp_type depth --keypose_path examples/keypose/person_keypose.png --keypose_inp_type keypose --keypose_adapter_ckpt models/t2iadapter_keypose_sd14v1.pth --keypose_weight 1.5 --cond_tau 0.7 --sd_ckpt models/anything-v4.5-pruned-fp16.ckpt --vae_ckpt models/anything-v4.0.vae.pt --n_sample 8 --max_resolution 524288 +# test color + sketch +python test_composable_adapters.py --prompt "A farm, best quality, extremely detailed" --sketch_path examples/sketch/scenery.jpg --sketch_weight 1.0 --sketch_adapter_ckpt models/t2iadapter_sketch_sd14v1.pth --sketch_inp_type image --color_path examples/color/color_0001.png --color_inp_type image --color_adapter_ckpt models/t2iadapter_color_sd14v1.pth --color_weight 1.2 --cond_tau 1.0 --sd_ckpt models/v1-5-pruned-emaonly.ckpt --n_sample 1 --resize_short_edge 512 +# test sketch + style +python test_composable_adapters.py --prompt "car" --sketch_path examples/sketch/car.png --sketch_weight 1.0 --sketch_adapter_ckpt models/t2iadapter_sketch_sd14v1.pth --sketch_inp_type image --style_path examples/style/cyberpunk.png --style_inp_type image --style_adapter_ckpt models/t2iadapter_style_sd14v1.pth --cond_tau 1.0 --sd_ckpt models/v1-5-pruned-emaonly.ckpt --n_sample 1 --resize_short_edge 512 --scale 9 +``` +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/ChongMou/T2I-Adapter) +

+ +

+ +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) +

+ +

+ +[![Huggingface Gradio](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/T2I-Adapter) +

+ +

+ + +## Stable Diffusion + T2I-Adapters (only ~70M parameters, ~300M storage space) + +The following is the detailed structure of a **Stable Diffusion** model with the **T2I-Adapter**. +

+ +

+ + + +# 🚀 Interesting Applications + +### Stable Diffusion results guided with the sketch T2I-Adapter + +The corresponding edge maps are predicted by PiDiNet. The sketch T2I-Adapter can well generalize to other similar sketch types, for example, sketches from the Internet and user scribbles. + +

+ +

+ +### Stable Diffusion results guided with the keypose T2I-Adapter + +The keypose results predicted by the [MMPose](https://github.com/open-mmlab/mmpose). +With the keypose guidance, the keypose T2I-Adapter can also help to generate animals with the same keypose, for example, pandas and tigers. + +

+ +

+ +### T2I-Adapter with Anything-v4.0 + +Once the T2I-Adapter is trained, it can act as a **plug-and-play module** and can be seamlessly integrated into the finetuned diffusion models **without re-training**, for example, Anything-4.0. + +#### ✨ Anything results with the plug-and-play sketch T2I-Adapter (no extra training) + +

+ +

+ +#### Anything results with the plug-and-play keypose T2I-Adapter (no extra training) + +

+ +

+ +### Local editing with the sketch adapter + +When combined with the inpaiting mode of Stable Diffusion, we can realize local editing with user specific guidance. + +#### ✨ Change the head direction of the cat + +

+ +

+ +#### ✨ Add rabbit ears on the head of the Iron Man. + +

+ +

+ +### Combine different concepts with adapter + +Adapter can be used to enhance the SD ability to combine different concepts. + +#### ✨ A car with flying wings. / A doll in the shape of letter ‘A’. + +

+ +

+ +### Sequential editing with the sketch adapter + +We can realize the sequential editing with the adapter guidance. + +

+ +

+ +### Composable Guidance with multiple adapters + +Stable Diffusion results guided with the segmentation and sketch adapters together. + +

+ +

+ +### Cooperating with Low-Rank Adapters for Controllable Multi-Concept Generation +[Mix-of-Show](https://github.com/TencentARC/Mix-of-Show) designed by [Yuchao Gu](https://ycgu.site/), et al. + +

+ +

+ + +# 🤗 Acknowledgements +Thank haofanwang for providing a tutorial of [T2I-Adapter diffusers](https://github.com/haofanwang/T2I-Adapter-for-Diffusers). + +![visitors](https://visitor-badge.glitch.me/badge?page_id=TencentARC/T2I-Adapter) + +Logo materials: [adapter](https://www.flaticon.com/free-icon/adapter_4777242), [lightbulb](https://www.flaticon.com/free-icon/lightbulb_3176369) diff --git a/comparison_models/T2IAdapter/adapters/coadapters.py b/comparison_models/T2IAdapter/adapters/coadapters.py new file mode 100644 index 0000000..a7c79b6 --- /dev/null +++ b/comparison_models/T2IAdapter/adapters/coadapters.py @@ -0,0 +1,134 @@ +import torch +import torch.nn as nn +import numpy as np +from transformers import CLIPVisionModel + +from ldm.models.diffusion.ddpm import LatentDiffusion, disabled_train +from ldm.util import instantiate_from_config +from ldm.modules.extra_condition.midas.api import MiDaSInference +from ldm.modules.extra_condition.model_edge import pidinet +from ldm.inference_base import read_state_dict + + +class CoAdapter(LatentDiffusion): + + def __init__(self, adapter_configs, coadapter_fuser_config, noise_schedule, *args, **kwargs): + super(CoAdapter, self).__init__(*args, **kwargs) + self.adapters = nn.ModuleDict() + for adapter_config in adapter_configs: + cond_name = adapter_config['cond_name'] + self.adapters[cond_name] = instantiate_from_config(adapter_config) + if 'pretrained' in adapter_config: + self.load_pretrained_adapter(cond_name, adapter_config['pretrained']) + self.coadapter_fuser = instantiate_from_config(coadapter_fuser_config) + self.training_adapters = list(self.adapters.keys()) + self.noise_schedule = noise_schedule + + # clip vision model as style model backbone + self.clip_vision_model = CLIPVisionModel.from_pretrained( + 'openai/clip-vit-large-patch14' + ) + self.clip_vision_model = self.clip_vision_model.eval() + self.clip_vision_model.train = disabled_train + for param in self.clip_vision_model.parameters(): + param.requires_grad = False + + # depth model + self.midas_model = MiDaSInference(model_type='dpt_hybrid') + self.midas_model = self.midas_model.eval() + self.midas_model.train = disabled_train + for param in self.midas_model.parameters(): + param.requires_grad = False + + # sketch model + self.sketch_model = pidinet() + ckp = torch.load('models/table5_pidinet.pth', map_location='cpu')['state_dict'] + self.sketch_model.load_state_dict({k.replace('module.', ''): v for k, v in ckp.items()}, strict=True) + self.sketch_model = self.sketch_model.eval() + self.sketch_model.train = disabled_train + for param in self.sketch_model.parameters(): + param.requires_grad = False + + def load_pretrained_adapter(self, cond_name, pretrained_path): + print(f'loading adapter {cond_name} from {pretrained_path}') + state_dict = read_state_dict(pretrained_path) + new_state_dict = {} + for k, v in state_dict.items(): + if k.startswith('adapter.'): + new_state_dict[k[len('adapter.'):]] = v + else: + new_state_dict[k] = v + self.adapters[cond_name].load_state_dict(new_state_dict) + + @torch.inference_mode() + def data_preparation_on_gpu(self, batch): + # style + style = batch['style'].to(self.device) + style = self.clip_vision_model(style)['last_hidden_state'] + batch['style'] = style + + # depth + depth = self.midas_model(batch['jpg']).repeat(1, 3, 1, 1) # jpg range [-1, 1] + for i in range(depth.size(0)): + depth[i] -= torch.min(depth[i]) + depth[i] /= torch.max(depth[i]) + batch['depth'] = depth + + # sketch + edge = 0.5 * batch['jpg'] + 0.5 # [-1, 1] to [0, 1] + edge = self.sketch_model(edge)[-1] + edge = edge > 0.5 + batch['sketch'] = edge.float() + + def get_adapter_features(self, batch, keep_conds): + features = dict() + + for cond_name in keep_conds: + if cond_name in batch: + features[cond_name] = self.adapters[cond_name](batch[cond_name]) + + return features + + def shared_step(self, batch, **kwargs): + for k in self.ucg_training: + p = self.ucg_training[k] + for i in range(len(batch[k])): + if self.ucg_prng.choice(2, p=[1 - p, p]): + if isinstance(batch[k], list): + batch[k][i] = "" + batch['jpg'] = batch['jpg'] * 2 - 1 + x, c = self.get_input(batch, self.first_stage_key) + self.data_preparation_on_gpu(batch) + + p = np.random.rand() + if p < 0.1: + keep_conds = self.training_adapters + elif p < 0.2: + keep_conds = [] + else: + keep = np.random.choice(2, len(self.training_adapters), p=[0.5, 0.5]) + keep_conds = [cond_name for k, cond_name in zip(keep, self.training_adapters) if k == 1] + + features = self.get_adapter_features(batch, keep_conds) + features_adapter, append_to_context = self.coadapter_fuser(features) + + t = self.get_time_with_schedule(self.noise_schedule, x.size(0)) + loss, loss_dict = self(x, c, t=t, features_adapter=features_adapter, append_to_context=append_to_context) + return loss, loss_dict + + def configure_optimizers(self): + lr = self.learning_rate + params = list(self.adapters.parameters()) + list(self.coadapter_fuser.parameters()) + opt = torch.optim.AdamW(params, lr=lr) + return opt + + def on_save_checkpoint(self, checkpoint): + keys = list(checkpoint['state_dict'].keys()) + for key in keys: + if 'adapter' not in key: + del checkpoint['state_dict'][key] + + def on_load_checkpoint(self, checkpoint): + for name in self.state_dict(): + if 'adapter' not in name: + checkpoint['state_dict'][name] = self.state_dict()[name] diff --git a/comparison_models/T2IAdapter/adapters/t2i_adapters_for_canny.py b/comparison_models/T2IAdapter/adapters/t2i_adapters_for_canny.py new file mode 100644 index 0000000..03606b8 --- /dev/null +++ b/comparison_models/T2IAdapter/adapters/t2i_adapters_for_canny.py @@ -0,0 +1,47 @@ +import torch + +from ldm.models.diffusion.ddpm import LatentDiffusion +from ldm.util import instantiate_from_config + + +class T2IAdapterCannyBase(LatentDiffusion): + + def __init__(self, adapter_config, extra_cond_key, noise_schedule, *args, **kwargs): + super(T2IAdapterCannyBase, self).__init__(*args, **kwargs) + self.adapter = instantiate_from_config(adapter_config) + self.extra_cond_key = extra_cond_key + self.noise_schedule = noise_schedule + + def shared_step(self, batch, **kwargs): + for k in self.ucg_training: + p = self.ucg_training[k] + for i in range(len(batch[k])): + if self.ucg_prng.choice(2, p=[1 - p, p]): + if isinstance(batch[k], list): + batch[k][i] = "" + else: + raise NotImplementedError("only text ucg is currently supported") + batch['jpg'] = batch['jpg'] * 2 - 1 + x, c = self.get_input(batch, self.first_stage_key) + extra_cond = super(LatentDiffusion, self).get_input(batch, self.extra_cond_key).to(self.device) + features_adapter = self.adapter(extra_cond) + t = self.get_time_with_schedule(self.noise_schedule, x.size(0)) + loss, loss_dict = self(x, c, t=t, features_adapter=features_adapter) + return loss, loss_dict + + def configure_optimizers(self): + lr = self.learning_rate + params = list(self.adapter.parameters()) + opt = torch.optim.AdamW(params, lr=lr) + return opt + + def on_save_checkpoint(self, checkpoint): + keys = list(checkpoint['state_dict'].keys()) + for key in keys: + if 'adapter' not in key: + del checkpoint['state_dict'][key] + + def on_load_checkpoint(self, checkpoint): + for name in self.state_dict(): + if 'adapter' not in name: + checkpoint['state_dict'][name] = self.state_dict()[name] diff --git a/comparison_models/T2IAdapter/adapters/t2i_adapters_for_style.py b/comparison_models/T2IAdapter/adapters/t2i_adapters_for_style.py new file mode 100644 index 0000000..e8c98ef --- /dev/null +++ b/comparison_models/T2IAdapter/adapters/t2i_adapters_for_style.py @@ -0,0 +1,63 @@ +import torch +import numpy as np +from transformers import CLIPVisionModel + +from ldm.models.diffusion.ddpm import LatentDiffusion, disabled_train +from ldm.util import instantiate_from_config + + +class T2IAdapterStyleV3(LatentDiffusion): + + def __init__(self, adapter_config, extra_cond_key, noise_schedule, *args, **kwargs): + super(T2IAdapterStyleV3, self).__init__(*args, **kwargs) + self.adapter = instantiate_from_config(adapter_config) + self.extra_cond_key = extra_cond_key + self.noise_schedule = noise_schedule + self.clip_vision_model = CLIPVisionModel.from_pretrained( + 'openai/clip-vit-large-patch14' + ) + + self.clip_vision_model = self.clip_vision_model.eval() + self.clip_vision_model.train = disabled_train + for param in self.clip_vision_model.parameters(): + param.requires_grad = False + + + def shared_step(self, batch, **kwargs): + for k in self.ucg_training: + if k == self.extra_cond_key: + continue + p = self.ucg_training[k] + for i in range(len(batch[k])): + if self.ucg_prng.choice(2, p=[1 - p, p]): + if isinstance(batch[k], list): + batch[k][i] = "" + batch['jpg'] = batch['jpg'] * 2 - 1 + x, c = self.get_input(batch, self.first_stage_key) + extra_cond = super(LatentDiffusion, self).get_input(batch, self.extra_cond_key).to(self.device) + extra_cond = self.clip_vision_model(extra_cond)['last_hidden_state'] + features_adapter = self.adapter(extra_cond) + if self.extra_cond_key in self.ucg_training: + idx = np.random.choice(self.adapter.num_token, np.random.randint(1, self.adapter.num_token+1), replace=False) + idx_tensor = torch.from_numpy(idx).to(features_adapter.device) + features_adapter = torch.index_select(features_adapter, 1, idx_tensor) + t = self.get_time_with_schedule(self.noise_schedule, x.size(0)) + loss, loss_dict = self(x, c, t=t, append_to_context=features_adapter) + return loss, loss_dict + + def configure_optimizers(self): + lr = self.learning_rate + params = list(self.adapter.parameters()) + opt = torch.optim.AdamW(params, lr=lr) + return opt + + def on_save_checkpoint(self, checkpoint): + keys = list(checkpoint['state_dict'].keys()) + for key in keys: + if 'adapter' not in key: + del checkpoint['state_dict'][key] + + def on_load_checkpoint(self, checkpoint): + for name in self.state_dict(): + if 'adapter' not in name: + checkpoint['state_dict'][name] = self.state_dict()[name] diff --git a/comparison_models/T2IAdapter/app.py b/comparison_models/T2IAdapter/app.py new file mode 100644 index 0000000..8bbb2ad --- /dev/null +++ b/comparison_models/T2IAdapter/app.py @@ -0,0 +1,178 @@ +# demo inspired by https://huggingface.co/spaces/lambdalabs/image-mixer-demo +import argparse +import copy +import gradio as gr +import torch +from functools import partial +from itertools import chain +from torch import autocast +from pytorch_lightning import seed_everything + +from basicsr.utils import tensor2img +from ldm.inference_base import DEFAULT_NEGATIVE_PROMPT, diffusion_inference, get_adapters, get_sd_models +from ldm.modules.extra_condition import api +from ldm.modules.extra_condition.api import ExtraCondition, get_adapter_feature, get_cond_model + +torch.set_grad_enabled(False) + +supported_cond = [e.name for e in ExtraCondition] + +# config +parser = argparse.ArgumentParser() +parser.add_argument( + '--sd_ckpt', + type=str, + default='models/sd-v1-4.ckpt', + help='path to checkpoint of stable diffusion model, both .ckpt and .safetensor are supported', +) +parser.add_argument( + '--vae_ckpt', + type=str, + default=None, + help='vae checkpoint, anime SD models usually have seperate vae ckpt that need to be loaded', +) +global_opt = parser.parse_args() +global_opt.config = 'configs/stable-diffusion/sd-v1-inference.yaml' +for cond_name in supported_cond: + setattr(global_opt, f'{cond_name}_adapter_ckpt', f'models/t2iadapter_{cond_name}_sd14v1.pth') +global_opt.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +global_opt.max_resolution = 512 * 512 +global_opt.sampler = 'ddim' +global_opt.cond_weight = 1.0 +global_opt.C = 4 +global_opt.f = 8 +global_opt.style_cond_tau = 0.5 + +# stable-diffusion model +sd_model, sampler = get_sd_models(global_opt) +# adapters and models to processing condition inputs +adapters = {} +cond_models = {} + +torch.cuda.empty_cache() + + +def run(*args): + with torch.inference_mode(), \ + sd_model.ema_scope(), \ + autocast('cuda'): + + inps = [] + for i in range(0, len(args) - 8, len(supported_cond)): + inps.append(args[i:i + len(supported_cond)]) + + opt = copy.deepcopy(global_opt) + opt.prompt, opt.neg_prompt, opt.scale, opt.n_samples, opt.seed, opt.steps, opt.resize_short_edge, opt.cond_tau \ + = args[-8:] + + conds = [] + activated_conds = [] + for idx, (b, im1, im2, cond_weight) in enumerate(zip(*inps)): + cond_name = supported_cond[idx] + if b == 'Nothing': + if cond_name in adapters: + adapters[cond_name]['model'] = adapters[cond_name]['model'].cpu() + else: + activated_conds.append(cond_name) + if cond_name in adapters: + adapters[cond_name]['model'] = adapters[cond_name]['model'].to(opt.device) + else: + adapters[cond_name] = get_adapters(opt, getattr(ExtraCondition, cond_name)) + adapters[cond_name]['cond_weight'] = cond_weight + + process_cond_module = getattr(api, f'get_cond_{cond_name}') + + if b == 'Image': + if cond_name not in cond_models: + cond_models[cond_name] = get_cond_model(opt, getattr(ExtraCondition, cond_name)) + conds.append(process_cond_module(opt, im1, 'image', cond_models[cond_name])) + else: + conds.append(process_cond_module(opt, im2, cond_name, None)) + + adapter_features, append_to_context = get_adapter_feature( + conds, [adapters[cond_name] for cond_name in activated_conds]) + + ims = [] + seed_everything(opt.seed) + for _ in range(opt.n_samples): + result = diffusion_inference(opt, sd_model, sampler, adapter_features, append_to_context) + ims.append(tensor2img(result, rgb2bgr=False)) + + # Clear GPU memory cache so less likely to OOM + torch.cuda.empty_cache() + return ims + + +def change_visible(im1, im2, val): + outputs = {} + if val == "Image": + outputs[im1] = gr.update(visible=True) + outputs[im2] = gr.update(visible=False) + elif val == "Nothing": + outputs[im1] = gr.update(visible=False) + outputs[im2] = gr.update(visible=False) + else: + outputs[im1] = gr.update(visible=False) + outputs[im2] = gr.update(visible=True) + return outputs + + +DESCRIPTION = '''# Composable T2I-Adapter +[Paper](https://arxiv.org/abs/2302.08453) [GitHub](https://github.com/TencentARC/T2I-Adapter) + +This gradio demo is for a simple experience of composable T2I-Adapter: +''' +with gr.Blocks(title="T2I-Adapter", css=".gr-box {border-color: #8136e2}") as demo: + gr.Markdown(DESCRIPTION) + + btns = [] + ims1 = [] + ims2 = [] + cond_weights = [] + + with gr.Row(): + for cond_name in supported_cond: + with gr.Box(): + with gr.Column(): + btn1 = gr.Radio( + choices=["Image", cond_name, "Nothing"], + label=f"Input type for {cond_name}", + interactive=True, + value="Nothing", + ) + im1 = gr.Image(source='upload', label="Image", interactive=True, visible=False, type="numpy") + im2 = gr.Image(source='upload', label=cond_name, interactive=True, visible=False, type="numpy") + cond_weight = gr.Slider( + label="Condition weight", minimum=0, maximum=5, step=0.05, value=1, interactive=True) + + fn = partial(change_visible, im1, im2) + btn1.change(fn=fn, inputs=[btn1], outputs=[im1, im2], queue=False) + + btns.append(btn1) + ims1.append(im1) + ims2.append(im2) + cond_weights.append(cond_weight) + + with gr.Column(): + prompt = gr.Textbox(label="Prompt") + neg_prompt = gr.Textbox(label="Negative Prompt", value=DEFAULT_NEGATIVE_PROMPT) + scale = gr.Slider(label="Guidance Scale (Classifier free guidance)", value=7.5, minimum=1, maximum=20, step=0.1) + n_samples = gr.Slider(label="Num samples", value=1, minimum=1, maximum=8, step=1) + seed = gr.Slider(label="Seed", value=42, minimum=0, maximum=10000, step=1) + steps = gr.Slider(label="Steps", value=50, minimum=10, maximum=100, step=1) + resize_short_edge = gr.Slider(label="Image resolution", value=512, minimum=320, maximum=1024, step=1) + cond_tau = gr.Slider( + label="timestamp parameter that determines until which step the adapter is applied", + value=1.0, + minimum=0.1, + maximum=1.0, + step=0.05) + + with gr.Row(): + submit = gr.Button("Generate") + output = gr.Gallery().style(grid=2, height='auto') + + inps = list(chain(btns, ims1, ims2, cond_weights)) + inps.extend([prompt, neg_prompt, scale, n_samples, seed, steps, resize_short_edge, cond_tau]) + submit.click(fn=run, inputs=inps, outputs=[output]) +demo.launch() diff --git a/comparison_models/T2IAdapter/app_coadapter.py b/comparison_models/T2IAdapter/app_coadapter.py new file mode 100644 index 0000000..693bba8 --- /dev/null +++ b/comparison_models/T2IAdapter/app_coadapter.py @@ -0,0 +1,223 @@ +# demo inspired by https://huggingface.co/spaces/lambdalabs/image-mixer-demo +import argparse +import copy +import cv2 +import gradio as gr +import torch +from functools import partial +from itertools import chain +from torch import autocast +from pytorch_lightning import seed_everything + +from basicsr.utils import tensor2img +from ldm.inference_base import DEFAULT_NEGATIVE_PROMPT, diffusion_inference, get_adapters, get_sd_models +from ldm.modules.extra_condition import api +from ldm.modules.extra_condition.api import ExtraCondition, get_cond_model +from ldm.modules.encoders.adapter import CoAdapterFuser + +torch.set_grad_enabled(False) + +supported_cond = ['style', 'sketch', 'color', 'depth', 'canny'] + +# config +parser = argparse.ArgumentParser() +parser.add_argument( + '--sd_ckpt', + type=str, + default='models/v1-5-pruned-emaonly.ckpt', + help='path to checkpoint of stable diffusion model, both .ckpt and .safetensor are supported', +) +parser.add_argument( + '--vae_ckpt', + type=str, + default=None, + help='vae checkpoint, anime SD models usually have seperate vae ckpt that need to be loaded', +) +global_opt = parser.parse_args() +global_opt.config = 'configs/stable-diffusion/sd-v1-inference.yaml' +for cond_name in supported_cond: + setattr(global_opt, f'{cond_name}_adapter_ckpt', f'models/coadapter-{cond_name}-sd15v1.pth') +global_opt.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +global_opt.max_resolution = 512 * 512 +global_opt.sampler = 'ddim' +global_opt.cond_weight = 1.0 +global_opt.C = 4 +global_opt.f = 8 +#TODO: expose style_cond_tau to users +global_opt.style_cond_tau = 1.0 + +# stable-diffusion model +sd_model, sampler = get_sd_models(global_opt) +# adapters and models to processing condition inputs +adapters = {} +cond_models = {} + +torch.cuda.empty_cache() + +# fuser is indispensable +coadapter_fuser = CoAdapterFuser(unet_channels=[320, 640, 1280, 1280], width=768, num_head=8, n_layes=3) +coadapter_fuser.load_state_dict(torch.load(f'models/coadapter-fuser-sd15v1.pth')) +coadapter_fuser = coadapter_fuser.to(global_opt.device) + + +def run(*args): + with torch.inference_mode(), \ + sd_model.ema_scope(), \ + autocast('cuda'): + + inps = [] + for i in range(0, len(args) - 8, len(supported_cond)): + inps.append(args[i:i + len(supported_cond)]) + + opt = copy.deepcopy(global_opt) + opt.prompt, opt.neg_prompt, opt.scale, opt.n_samples, opt.seed, opt.steps, opt.resize_short_edge, opt.cond_tau \ + = args[-8:] + + conds = [] + activated_conds = [] + prev_size = None + for idx, (b, im1, im2, cond_weight) in enumerate(zip(*inps)): + cond_name = supported_cond[idx] + if b == 'Nothing': + if cond_name in adapters: + adapters[cond_name]['model'] = adapters[cond_name]['model'].cpu() + else: + activated_conds.append(cond_name) + if cond_name in adapters: + adapters[cond_name]['model'] = adapters[cond_name]['model'].to(opt.device) + else: + adapters[cond_name] = get_adapters(opt, getattr(ExtraCondition, cond_name)) + adapters[cond_name]['cond_weight'] = cond_weight + + process_cond_module = getattr(api, f'get_cond_{cond_name}') + + if b == 'Image': + if cond_name not in cond_models: + cond_models[cond_name] = get_cond_model(opt, getattr(ExtraCondition, cond_name)) + if prev_size is not None: + image = cv2.resize(im1, prev_size, interpolation=cv2.INTER_LANCZOS4) + else: + image = im1 + conds.append(process_cond_module(opt, image, 'image', cond_models[cond_name])) + if idx != 0 and prev_size is None: # skip style since we only care spatial cond size + h, w = image.shape[:2] + prev_size = (w, h) + else: + if prev_size is not None: + image = cv2.resize(im2, prev_size, interpolation=cv2.INTER_LANCZOS4) + else: + image = im2 + conds.append(process_cond_module(opt, image, cond_name, None)) + if idx != 0 and prev_size is None: # skip style since we only care spatial cond size + h, w = image.shape[:2] + prev_size = (w, h) + + + + features = dict() + for idx, cond_name in enumerate(activated_conds): + cur_feats = adapters[cond_name]['model'](conds[idx]) + if isinstance(cur_feats, list): + for i in range(len(cur_feats)): + cur_feats[i] *= adapters[cond_name]['cond_weight'] + else: + cur_feats *= adapters[cond_name]['cond_weight'] + features[cond_name] = cur_feats + + adapter_features, append_to_context = coadapter_fuser(features) + + output_conds = [] + for cond in conds: + output_conds.append(tensor2img(cond, rgb2bgr=False)) + + ims = [] + seed_everything(opt.seed) + for _ in range(opt.n_samples): + result = diffusion_inference(opt, sd_model, sampler, adapter_features, append_to_context) + ims.append(tensor2img(result, rgb2bgr=False)) + + # Clear GPU memory cache so less likely to OOM + torch.cuda.empty_cache() + return ims, output_conds + + +def change_visible(im1, im2, val): + outputs = {} + if val == "Image": + outputs[im1] = gr.update(visible=True) + outputs[im2] = gr.update(visible=False) + elif val == "Nothing": + outputs[im1] = gr.update(visible=False) + outputs[im2] = gr.update(visible=False) + else: + outputs[im1] = gr.update(visible=False) + outputs[im2] = gr.update(visible=True) + return outputs + + +DESCRIPTION = '''# CoAdapter +[GitHub](https://github.com/TencentARC/T2I-Adapter) [Details](https://github.com/TencentARC/T2I-Adapter/blob/main/docs/coadapter.md) + +This gradio demo is for a simple experience of CoAdapter. Following are some useful tips: +- **Condition weight is important**. If the generated image is not well aligned with the condition, increase the corresponding `Condition weight`. If increasing `Condition weight` is ineffective or degrades image quality, try decreasing the `Condition weight` of other conditions. +- **Start with fewer conditions**. If you plan to use more than two conditions to control the model, it is recommended to start with only one or two conditions. Once you have found the suitable `Condition weight` for existing conditions, gradually append the new conditions. +- It is recommended to use a step size of 0.1 to adjust `Condition weight`. From experience, `Condition weight` will not be less than 0.5 or greater than 1.5 + +''' +with gr.Blocks(title="CoAdapter", css=".gr-box {border-color: #8136e2}") as demo: + gr.Markdown(DESCRIPTION) + + btns = [] + ims1 = [] + ims2 = [] + cond_weights = [] + + with gr.Row(): + for cond_name in supported_cond: + with gr.Box(): + with gr.Column(): + btn1 = gr.Radio( + choices=["Image", cond_name, "Nothing"], + label=f"Input type for {cond_name}", + interactive=True, + value="Nothing", + ) + im1 = gr.Image(source='upload', label="Image", interactive=True, visible=False, type="numpy") + im2 = gr.Image(source='upload', label=cond_name, interactive=True, visible=False, type="numpy") + cond_weight = gr.Slider( + label="Condition weight", minimum=0, maximum=5, step=0.05, value=1, interactive=True) + + fn = partial(change_visible, im1, im2) + btn1.change(fn=fn, inputs=[btn1], outputs=[im1, im2], queue=False) + + btns.append(btn1) + ims1.append(im1) + ims2.append(im2) + cond_weights.append(cond_weight) + + with gr.Column(): + prompt = gr.Textbox(label="Prompt") + + with gr.Accordion('Advanced options', open=False): + neg_prompt = gr.Textbox(label="Negative Prompt", value=DEFAULT_NEGATIVE_PROMPT) + scale = gr.Slider(label="Guidance Scale (Classifier free guidance)", value=7.5, minimum=1, maximum=20, step=0.1) + n_samples = gr.Slider(label="Num samples", value=1, minimum=1, maximum=8, step=1) + seed = gr.Slider(label="Seed", value=42, minimum=0, maximum=10000, step=1) + steps = gr.Slider(label="Steps", value=50, minimum=10, maximum=100, step=1) + resize_short_edge = gr.Slider(label="Image resolution", value=512, minimum=320, maximum=1024, step=1) + cond_tau = gr.Slider( + label="timestamp parameter that determines until which step the adapter is applied", + value=1.0, + minimum=0.1, + maximum=1.0, + step=0.05) + + with gr.Row(): + submit = gr.Button("Generate") + output = gr.Gallery().style(grid=2, height='auto') + cond = gr.Gallery().style(grid=2, height='auto') + + inps = list(chain(btns, ims1, ims2, cond_weights)) + inps.extend([prompt, neg_prompt, scale, n_samples, seed, steps, resize_short_edge, cond_tau]) + submit.click(fn=run, inputs=inps, outputs=[output, cond]) +demo.launch() diff --git a/comparison_models/T2IAdapter/assets/DejaVuSans.ttf b/comparison_models/T2IAdapter/assets/DejaVuSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e5f7eecce43be41ff0703ed99e1553029b849f14 GIT binary patch literal 757076 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

fI);V1&`p^P@z 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


*Toc{%?n7Na{@W#kyM+}-t@ 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�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

k{ZI1CFe0tB> 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%0AkCIj`i%7Br#l;oqDV8V9?w7um6f52njLHb$)>$ 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

XlM*N5W-Ejl^9) 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Ζg^ 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�WQu1NZFHrkoH6sUeBLS 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

rN?63HrfYdc7nCF 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 literal 0 HcmV?d00001 diff --git a/comparison_models/T2IAdapter/assets/logo.png b/comparison_models/T2IAdapter/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..82798c15b5a19e7c76d44bc5e18afad0c7b153ed GIT binary patch literal 12895 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�Dn872Ojj|IJZ*bVC}Bcnu(BIN(;)h~ML@D` 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 M6g1>(WZ!@KKaUU`Hvj+t literal 0 HcmV?d00001 diff --git a/comparison_models/T2IAdapter/assets/logo_coadapter.png b/comparison_models/T2IAdapter/assets/logo_coadapter.png new file mode 100644 index 0000000000000000000000000000000000000000..82d2734d84f3957ef82b95c9ad3f8301070b2540 GIT binary patch literal 59004 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 X;!S^p=ot?Z7OE_-Ay;_s(aZk_J|+VG literal 0 HcmV?d00001 diff --git a/comparison_models/T2IAdapter/configs/mm/faster_rcnn_r50_fpn_coco.py b/comparison_models/T2IAdapter/configs/mm/faster_rcnn_r50_fpn_coco.py new file mode 100644 index 0000000..a9ad952 --- /dev/null +++ b/comparison_models/T2IAdapter/configs/mm/faster_rcnn_r50_fpn_coco.py @@ -0,0 +1,182 @@ +checkpoint_config = dict(interval=1) +# yapf:disable +log_config = dict( + interval=50, + hooks=[ + dict(type='TextLoggerHook'), + # dict(type='TensorboardLoggerHook') + ]) +# yapf:enable +dist_params = dict(backend='nccl') +log_level = 'INFO' +load_from = None +resume_from = None +workflow = [('train', 1)] +# optimizer +optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001) +optimizer_config = dict(grad_clip=None) +# learning policy +lr_config = dict( + policy='step', + warmup='linear', + warmup_iters=500, + warmup_ratio=0.001, + step=[8, 11]) +total_epochs = 12 + +model = dict( + type='FasterRCNN', + pretrained='torchvision://resnet50', + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch'), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + roi_head=dict( + type='StandardRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100) + # soft-nms is also supported for rcnn testing + # e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05) + )) + +dataset_type = 'CocoDataset' +data_root = 'data/coco' +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', with_bbox=True), + dict(type='Resize', img_scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', flip_ratio=0.5), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='DefaultFormatBundle'), + dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(1333, 800), + flip=False, + transforms=[ + dict(type='Resize', keep_ratio=True), + dict(type='RandomFlip'), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='DefaultFormatBundle'), + dict(type='Collect', keys=['img']), + ]) +] +data = dict( + samples_per_gpu=2, + workers_per_gpu=2, + train=dict( + type=dataset_type, + ann_file=f'{data_root}/annotations/instances_train2017.json', + img_prefix=f'{data_root}/train2017/', + pipeline=train_pipeline), + val=dict( + type=dataset_type, + ann_file=f'{data_root}/annotations/instances_val2017.json', + img_prefix=f'{data_root}/val2017/', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + ann_file=f'{data_root}/annotations/instances_val2017.json', + img_prefix=f'{data_root}/val2017/', + pipeline=test_pipeline)) +evaluation = dict(interval=1, metric='bbox') diff --git a/comparison_models/T2IAdapter/configs/mm/hrnet_w48_coco_256x192.py b/comparison_models/T2IAdapter/configs/mm/hrnet_w48_coco_256x192.py new file mode 100644 index 0000000..9755e67 --- /dev/null +++ b/comparison_models/T2IAdapter/configs/mm/hrnet_w48_coco_256x192.py @@ -0,0 +1,169 @@ +# _base_ = [ +# '../../../../_base_/default_runtime.py', +# '../../../../_base_/datasets/coco.py' +# ] +evaluation = dict(interval=10, metric='mAP', save_best='AP') + +optimizer = dict( + type='Adam', + lr=5e-4, +) +optimizer_config = dict(grad_clip=None) +# learning policy +lr_config = dict( + policy='step', + warmup='linear', + warmup_iters=500, + warmup_ratio=0.001, + step=[170, 200]) +total_epochs = 210 +channel_cfg = dict( + num_output_channels=17, + dataset_joints=17, + dataset_channel=[ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + ], + inference_channel=[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + ]) + +# model settings +model = dict( + type='TopDown', + pretrained='https://download.openmmlab.com/mmpose/' + 'pretrain_models/hrnet_w48-8ef0771d.pth', + backbone=dict( + type='HRNet', + in_channels=3, + 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=(48, 96)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(48, 96, 192)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(48, 96, 192, 384))), + ), + keypoint_head=dict( + type='TopdownHeatmapSimpleHead', + in_channels=48, + out_channels=channel_cfg['num_output_channels'], + num_deconv_layers=0, + extra=dict(final_conv_kernel=1, ), + loss_keypoint=dict(type='JointsMSELoss', use_target_weight=True)), + train_cfg=dict(), + test_cfg=dict( + flip_test=True, + post_process='default', + shift_heatmap=True, + modulate_kernel=11)) + +data_cfg = dict( + image_size=[192, 256], + heatmap_size=[48, 64], + num_output_channels=channel_cfg['num_output_channels'], + num_joints=channel_cfg['dataset_joints'], + dataset_channel=channel_cfg['dataset_channel'], + inference_channel=channel_cfg['inference_channel'], + soft_nms=False, + nms_thr=1.0, + oks_thr=0.9, + vis_thr=0.2, + use_gt_bbox=False, + det_bbox_thr=0.0, + bbox_file='data/coco/person_detection_results/' + 'COCO_val2017_detections_AP_H_56_person.json', +) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='TopDownGetBboxCenterScale', padding=1.25), + dict(type='TopDownRandomShiftBboxCenter', shift_factor=0.16, prob=0.3), + dict(type='TopDownRandomFlip', flip_prob=0.5), + dict( + type='TopDownHalfBodyTransform', + num_joints_half_body=8, + prob_half_body=0.3), + dict( + type='TopDownGetRandomScaleRotation', rot_factor=40, scale_factor=0.5), + dict(type='TopDownAffine'), + dict(type='ToTensor'), + dict( + type='NormalizeTensor', + mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + dict(type='TopDownGenerateTarget', sigma=2), + dict( + type='Collect', + keys=['img', 'target', 'target_weight'], + meta_keys=[ + 'image_file', 'joints_3d', 'joints_3d_visible', 'center', 'scale', + 'rotation', 'bbox_score', 'flip_pairs' + ]), +] + +val_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='TopDownGetBboxCenterScale', padding=1.25), + dict(type='TopDownAffine'), + dict(type='ToTensor'), + dict( + type='NormalizeTensor', + mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]), + dict( + type='Collect', + keys=['img'], + meta_keys=[ + 'image_file', 'center', 'scale', 'rotation', 'bbox_score', + 'flip_pairs' + ]), +] + +test_pipeline = val_pipeline + +data_root = 'data/coco' +data = dict( + samples_per_gpu=32, + workers_per_gpu=2, + val_dataloader=dict(samples_per_gpu=32), + test_dataloader=dict(samples_per_gpu=32), + train=dict( + type='TopDownCocoDataset', + ann_file=f'{data_root}/annotations/person_keypoints_train2017.json', + img_prefix=f'{data_root}/train2017/', + data_cfg=data_cfg, + pipeline=train_pipeline, + dataset_info={{_base_.dataset_info}}), + val=dict( + type='TopDownCocoDataset', + ann_file=f'{data_root}/annotations/person_keypoints_val2017.json', + img_prefix=f'{data_root}/val2017/', + data_cfg=data_cfg, + pipeline=val_pipeline, + dataset_info={{_base_.dataset_info}}), + test=dict( + type='TopDownCocoDataset', + ann_file=f'{data_root}/annotations/person_keypoints_val2017.json', + img_prefix=f'{data_root}/val2017/', + data_cfg=data_cfg, + pipeline=test_pipeline, + dataset_info={{_base_.dataset_info}}), +) diff --git a/comparison_models/T2IAdapter/configs/pl_train/coadapter-v1-train.yaml b/comparison_models/T2IAdapter/configs/pl_train/coadapter-v1-train.yaml new file mode 100644 index 0000000..06bb192 --- /dev/null +++ b/comparison_models/T2IAdapter/configs/pl_train/coadapter-v1-train.yaml @@ -0,0 +1,176 @@ +model: + base_learning_rate: 1.0e-05 + target: adapters.coadapters.CoAdapter + params: + adapter_configs: + - target: ldm.modules.encoders.adapter.StyleAdapter + cond_name: style + pretrained: models/t2iadapter_style_sd14v1.pth + params: + width: 1024 + context_dim: 768 + num_head: 8 + n_layes: 3 + num_token: 8 + + - target: ldm.modules.encoders.adapter.Adapter + cond_name: canny + pretrained: models/t2iadapter_canny_sd14v1.pth + params: + cin: 64 + channels: [ 320, 640, 1280, 1280 ] + nums_rb: 2 + ksize: 1 + sk: True + use_conv: False + + - target: ldm.modules.encoders.adapter.Adapter + cond_name: sketch + pretrained: models/t2iadapter_sketch_sd14v1.pth + params: + cin: 64 + channels: [ 320, 640, 1280, 1280 ] + nums_rb: 2 + ksize: 1 + sk: True + use_conv: False + + - target: ldm.modules.encoders.adapter.Adapter_light + cond_name: color + pretrained: models/t2iadapter_color_sd14v1.pth + params: + cin: 192 + channels: [ 320, 640, 1280, 1280 ] + nums_rb: 4 + + - target: ldm.modules.encoders.adapter.Adapter + cond_name: depth + pretrained: models/t2iadapter_depth_sd14v1.pth + params: + cin: 192 + channels: [ 320, 640, 1280, 1280 ] + nums_rb: 2 + ksize: 1 + sk: True + use_conv: False + + coadapter_fuser_config: + target: ldm.modules.encoders.adapter.CoAdapterFuser + params: + unet_channels: [320, 640, 1280, 1280] + width: 768 + num_head: 8 + n_layes: 3 + + noise_schedule: linear + linear_start: 0.00085 + linear_end: 0.0120 + 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 # Note: different from the one we trained before + conditioning_key: crossattn + scale_factor: 0.18215 + use_ema: False + + ucg_training: + txt: 0.5 + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + image_size: 32 # unused + 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: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + 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: + target: torch.nn.Identity + + cond_stage_config: #__is_unconditional__ + target: ldm.modules.encoders.modules.FrozenCLIPEmbedder + params: + version: openai/clip-vit-large-patch14 + +data: + target: ldm.data.dataset_laion.WebDataModuleFromConfig + params: + tar_base: YOUR_DATASET_ROOT # need to change + batch_size: 2 + num_workers: 8 + multinode: True + train: + shards: YOUR_TAR_LIST # need to change + shuffle: 10000 + image_key: jpg + image_transforms: + - target: torchvision.transforms.Resize + params: + size: 512 + interpolation: 3 + - target: torchvision.transforms.RandomCrop + params: + size: 512 + process: + - target: ldm.data.utils.AddStyle + params: + version: openai/clip-vit-large-patch14 + + - target: ldm.data.utils.AddCannyRandomThreshold + params: + low_threshold: 40 + high_threshold: 110 + shift_range: 10 + + - target: ldm.data.utils.AddSpatialPalette + params: + downscale_factor: 64 + + - target: ldm.data.utils.PILtoTensor + +lightning: + find_unused_parameters: False + + modelcheckpoint: + params: + every_n_train_steps: 5000 + save_top_k: -1 + monitor: null + trainer: + benchmark: True + num_sanity_val_steps: 0 + accumulate_grad_batches: 1 + limit_val_batches: 0 \ No newline at end of file diff --git a/comparison_models/T2IAdapter/configs/stable-diffusion/app.yaml b/comparison_models/T2IAdapter/configs/stable-diffusion/app.yaml new file mode 100644 index 0000000..19431de --- /dev/null +++ b/comparison_models/T2IAdapter/configs/stable-diffusion/app.yaml @@ -0,0 +1,87 @@ +name: app +model: + base_learning_rate: 1.0e-04 + target: ldm.models.diffusion.ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + 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 # Note: different from the one we trained before + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + + scheduler_config: # 10000 warmup steps + target: ldm.lr_scheduler.LambdaLinearScheduler + params: + warm_up_steps: [ 10000 ] + cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases + f_start: [ 1.e-6 ] + f_max: [ 1. ] + f_min: [ 1. ] + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + image_size: 32 # unused + 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: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + 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: + target: torch.nn.Identity + + cond_stage_config: + target: ldm.modules.encoders.modules.FrozenCLIPEmbedder + params: + device: 'cuda' + +logger: + print_freq: 100 + save_checkpoint_freq: !!float 1e4 + use_tb_logger: true + wandb: + project: ~ + resume_id: ~ +dist_params: + backend: nccl + port: 29500 +training: + lr: !!float 1e-5 + save_freq: 1e4 \ No newline at end of file diff --git a/comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-inference.yaml b/comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-inference.yaml new file mode 100644 index 0000000..d82c1e1 --- /dev/null +++ b/comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-inference.yaml @@ -0,0 +1,65 @@ +model: + base_learning_rate: 1.0e-04 + target: comparison_models.T2IAdapter.ldm.models.diffusion.ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + 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 # Note: different from the one we trained before + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + + unet_config: + target: comparison_models.T2IAdapter.ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + use_fp16: False + image_size: 32 # unused + 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: + target: comparison_models.T2IAdapter.ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + double_z: true + z_channels: 4 + resolution: 512 + in_channels: 3 + out_ch: 3 + ch: 128 + ch_mult: + - 1 + - 2 + - 4 + - 4 + num_res_blocks: 2 + attn_resolutions: [] + dropout: 0.0 + lossconfig: + target: torch.nn.Identity + + cond_stage_config: + target: comparison_models.T2IAdapter.ldm.modules.encoders.modules.WebUIFrozenCLIPEmebedder + params: + version: openai/clip-vit-large-patch14 + layer: last diff --git a/comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-train.yaml b/comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-train.yaml new file mode 100644 index 0000000..3c22ae7 --- /dev/null +++ b/comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-train.yaml @@ -0,0 +1,86 @@ +model: + base_learning_rate: 1.0e-04 + target: ldm.models.diffusion.ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + 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 # Note: different from the one we trained before + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + + scheduler_config: # 10000 warmup steps + target: ldm.lr_scheduler.LambdaLinearScheduler + params: + warm_up_steps: [ 10000 ] + cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases + f_start: [ 1.e-6 ] + f_max: [ 1. ] + f_min: [ 1. ] + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + image_size: 32 # unused + 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: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + 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: + target: torch.nn.Identity + + cond_stage_config: #__is_unconditional__ + target: ldm.modules.encoders.modules.FrozenCLIPEmbedder + params: + version: openai/clip-vit-large-patch14 + +logger: + print_freq: 100 + save_checkpoint_freq: !!float 1e4 + use_tb_logger: true + wandb: + project: ~ + resume_id: ~ +dist_params: + backend: nccl + port: 29500 +training: + lr: !!float 1e-5 + save_freq: 1e4 \ No newline at end of file diff --git a/comparison_models/T2IAdapter/configs/stable-diffusion/train_keypose.yaml b/comparison_models/T2IAdapter/configs/stable-diffusion/train_keypose.yaml new file mode 100644 index 0000000..cd25843 --- /dev/null +++ b/comparison_models/T2IAdapter/configs/stable-diffusion/train_keypose.yaml @@ -0,0 +1,87 @@ +name: train_keypose +model: + base_learning_rate: 1.0e-04 + target: ldm.models.diffusion.ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + 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 # Note: different from the one we trained before + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + + scheduler_config: # 10000 warmup steps + target: ldm.lr_scheduler.LambdaLinearScheduler + params: + warm_up_steps: [ 10000 ] + cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases + f_start: [ 1.e-6 ] + f_max: [ 1. ] + f_min: [ 1. ] + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + image_size: 32 # unused + 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: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + 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: + target: torch.nn.Identity + + cond_stage_config: #__is_unconditional__ + target: ldm.modules.encoders.modules.FrozenCLIPEmbedder + params: + version: openai/clip-vit-large-patch14 + +logger: + print_freq: 100 + save_checkpoint_freq: !!float 1e4 + use_tb_logger: true + wandb: + project: ~ + resume_id: ~ +dist_params: + backend: nccl + port: 29500 +training: + lr: !!float 1e-5 + save_freq: 1e4 \ No newline at end of file diff --git a/comparison_models/T2IAdapter/configs/stable-diffusion/train_mask.yaml b/comparison_models/T2IAdapter/configs/stable-diffusion/train_mask.yaml new file mode 100644 index 0000000..7ab2981 --- /dev/null +++ b/comparison_models/T2IAdapter/configs/stable-diffusion/train_mask.yaml @@ -0,0 +1,87 @@ +name: train_mask +model: + base_learning_rate: 1.0e-04 + target: ldm.models.diffusion.ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + 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 # Note: different from the one we trained before + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + + scheduler_config: # 10000 warmup steps + target: ldm.lr_scheduler.LambdaLinearScheduler + params: + warm_up_steps: [ 10000 ] + cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases + f_start: [ 1.e-6 ] + f_max: [ 1. ] + f_min: [ 1. ] + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + image_size: 32 # unused + 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: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + 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: + target: torch.nn.Identity + + cond_stage_config: #__is_unconditional__ + target: ldm.modules.encoders.modules.FrozenCLIPEmbedder + params: + version: openai/clip-vit-large-patch14 + +logger: + print_freq: 100 + save_checkpoint_freq: !!float 1e4 + use_tb_logger: true + wandb: + project: ~ + resume_id: ~ +dist_params: + backend: nccl + port: 29500 +training: + lr: !!float 1e-5 + save_freq: 1e4 \ No newline at end of file diff --git a/comparison_models/T2IAdapter/configs/stable-diffusion/train_sketch.yaml b/comparison_models/T2IAdapter/configs/stable-diffusion/train_sketch.yaml new file mode 100644 index 0000000..90d4487 --- /dev/null +++ b/comparison_models/T2IAdapter/configs/stable-diffusion/train_sketch.yaml @@ -0,0 +1,87 @@ +name: train_sketch +model: + base_learning_rate: 1.0e-04 + target: ldm.models.diffusion.ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + 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 # Note: different from the one we trained before + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + + scheduler_config: # 10000 warmup steps + target: ldm.lr_scheduler.LambdaLinearScheduler + params: + warm_up_steps: [ 10000 ] + cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases + f_start: [ 1.e-6 ] + f_max: [ 1. ] + f_min: [ 1. ] + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + image_size: 32 # unused + 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: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + 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: + target: torch.nn.Identity + + cond_stage_config: #__is_unconditional__ + target: ldm.modules.encoders.modules.FrozenCLIPEmbedder + params: + version: openai/clip-vit-large-patch14 + +logger: + print_freq: 100 + save_checkpoint_freq: !!float 1e4 + use_tb_logger: true + wandb: + project: ~ + resume_id: ~ +dist_params: + backend: nccl + port: 29500 +training: + lr: !!float 1e-5 + save_freq: 1e4 \ No newline at end of file diff --git a/comparison_models/T2IAdapter/dist_util.py b/comparison_models/T2IAdapter/dist_util.py new file mode 100644 index 0000000..47441a4 --- /dev/null +++ b/comparison_models/T2IAdapter/dist_util.py @@ -0,0 +1,91 @@ +# Modified from https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/dist_utils.py # noqa: E501 +import functools +import os +import subprocess +import torch +import torch.distributed as dist +import torch.multiprocessing as mp +from torch.nn.parallel import DataParallel, DistributedDataParallel + + +def init_dist(launcher, backend='nccl', **kwargs): + if mp.get_start_method(allow_none=True) is None: + mp.set_start_method('spawn') + if launcher == 'pytorch': + _init_dist_pytorch(backend, **kwargs) + elif launcher == 'slurm': + _init_dist_slurm(backend, **kwargs) + else: + raise ValueError(f'Invalid launcher type: {launcher}') + + +def _init_dist_pytorch(backend, **kwargs): + rank = int(os.environ['RANK']) + num_gpus = torch.cuda.device_count() + torch.cuda.set_device(rank % num_gpus) + dist.init_process_group(backend=backend, **kwargs) + + +def _init_dist_slurm(backend, port=None): + """Initialize slurm distributed training environment. + + If argument ``port`` is not specified, then the master port will be system + environment variable ``MASTER_PORT``. If ``MASTER_PORT`` is not in system + environment variable, then a default port ``29500`` will be used. + + Args: + backend (str): Backend of torch.distributed. + port (int, optional): Master port. Defaults to None. + """ + proc_id = int(os.environ['SLURM_PROCID']) + ntasks = int(os.environ['SLURM_NTASKS']) + node_list = os.environ['SLURM_NODELIST'] + num_gpus = torch.cuda.device_count() + torch.cuda.set_device(proc_id % num_gpus) + addr = subprocess.getoutput(f'scontrol show hostname {node_list} | head -n1') + # specify master port + if port is not None: + os.environ['MASTER_PORT'] = str(port) + elif 'MASTER_PORT' in os.environ: + pass # use MASTER_PORT in the environment variable + else: + # 29500 is torch.distributed default port + os.environ['MASTER_PORT'] = '29500' + os.environ['MASTER_ADDR'] = addr + os.environ['WORLD_SIZE'] = str(ntasks) + os.environ['LOCAL_RANK'] = str(proc_id % num_gpus) + os.environ['RANK'] = str(proc_id) + dist.init_process_group(backend=backend) + + +def get_dist_info(): + if dist.is_available(): + initialized = dist.is_initialized() + else: + initialized = False + if initialized: + rank = dist.get_rank() + world_size = dist.get_world_size() + else: + rank = 0 + world_size = 1 + return rank, world_size + + +def master_only(func): + + @functools.wraps(func) + def wrapper(*args, **kwargs): + rank, _ = get_dist_info() + if rank == 0: + return func(*args, **kwargs) + + return wrapper + +def get_bare_model(net): + """Get bare model, especially under wrapping with + DistributedDataParallel or DataParallel. + """ + if isinstance(net, (DataParallel, DistributedDataParallel)): + net = net.module + return net diff --git a/comparison_models/T2IAdapter/docs/AdapterZoo.md b/comparison_models/T2IAdapter/docs/AdapterZoo.md new file mode 100644 index 0000000..ffdf9a9 --- /dev/null +++ b/comparison_models/T2IAdapter/docs/AdapterZoo.md @@ -0,0 +1,16 @@ +# Adapter Zoo + +You can download the adapters from + +All the following adapters are trained with Stable Diffusion (SD) V1.4, and they can be directly used on custom models as long as they are fine-tuned from the same text-to-image models, such as Anything-4.0 or models on the . + +| Adapter Name | Adapter Description | Demos|Model Parameters| Model Storage | | +| --- | --- |--- |--- |--- |---| +| t2iadapter_color_sd14v1.pth | Spatial color palette → image | [Demos](examples.md#color-adapter-spatial-palette) |18 M | 75 MB | | +| t2iadapter_style_sd14v1.pth | Image style → image | [Demos](examples.md#style-adapter)|| 154MB | Preliminary model. Style adapters with finer controls are on the way| +| t2iadapter_openpose_sd14v1.pth | Openpose → image| [Demos](examples.md#openpose-adapter) |77 M| 309 MB | | +| t2iadapter_canny_sd14v1.pth | Canny edges → image | [Demos](examples.md#canny-adapter-edge )|77 M | 309 MB || +| t2iadapter_sketch_sd14v1.pth | sketch → image ||77 M| 308 MB | | +| t2iadapter_keypose_sd14v1.pth | keypose → image || 77 M| 309 MB | mmpose style | +| t2iadapter_seg_sd14v1.pth | segmentation → image ||77 M| 309 MB || +| t2iadapter_depth_sd14v1.pth | depth maps → image ||77 M | 309 MB | Not the final model, still under training| diff --git a/comparison_models/T2IAdapter/docs/FAQ.md b/comparison_models/T2IAdapter/docs/FAQ.md new file mode 100644 index 0000000..6b34bb1 --- /dev/null +++ b/comparison_models/T2IAdapter/docs/FAQ.md @@ -0,0 +1,5 @@ +# FAQ + +- **Q: The openpose adapter (t2iadapter_openpose_sd14v1) outputs gray-scale images.** + + **A:** You can add `colorful` in the prompt to avoid this problem. diff --git a/comparison_models/T2IAdapter/docs/coadapter.md b/comparison_models/T2IAdapter/docs/coadapter.md new file mode 100644 index 0000000..7af33c9 --- /dev/null +++ b/comparison_models/T2IAdapter/docs/coadapter.md @@ -0,0 +1,50 @@ +

+ +

+ +## Overview + +

+ +

+ +We introduce **CoAdapter** (**Co**mposable **Adapter**) by jointly training T2I-Adapters and an extra fuser. The fuser allows different adapters with various conditions to be aware of each other and synergize to achieve more powerful composability, especially the combination of element-level style and other structural information. + +CoAdapter is inspired by [Composer](https://github.com/damo-vilab/composer). However, instead of training the whole model, it only trains extra light-weight adapters based on T2I-Adapter. But CoAdapter can also show the capability of generating creative images with composibility. Note that the model is still in training and this release is only a preview. + +## Demos + +| Sketch | Canny | Depth | Color (Spatial) | Style | Results | +|:------------------------------------------------------------------------------------------------------------------------------------------|:-----:|:-----------------------------------------------------------------------------------------------------------------------------------------:|:---------------:|--------------------------------------------------------------------------------------------------------------------------------------------|---------| +| image | | | | image | image | +| image | | | | image | image | +| image | | image | | image | image | +| image | | image | | image | image | + + +## Benefits from CoAdapter + +CoAdapter offers two advantages over the original T2I-Adapter. You can try CoAdapter in [![Huggingface CoAdapter](https://img.shields.io/static/v1?label=Demo&message=Huggingface%20Gradio&color=orange)](https://huggingface.co/spaces/Adapter/CoAdapter). + +1. CoAdapter has improved **composability**, especially for the style modality, due to the joint training of multiple adapters. + +| Input 1 | Input 2 | Input3 | Prompt and seed | T2I-Adapter | **CoAdapter** | +| :-----: | :-----: | :-----: |:-----: | :-----: | :-----: | +|image
Canny: 1.0 |image
Style: 1.0 | | "tower"
seed=42 | image | image | +|image
Skecth: 1.0 |image
Style: 1.0 | image
Color: 1.0 | "motorbike"
seed=993 | image | image | +|image
Skecth: 1.0 |image
Style: 1.0 | | "a corgi"
seed=42 | image | image | + +2. The joint training of CoAdapter can also enhance the generation quality of each individual adapter. + TODO + +The above results are based on *coadapter-sd15v1* and *t2iadapter-sd14v1*. + +## Useful Tips +- **Condition weight is important**. If the generated image is not well aligned with the condition, increase the corresponding `Condition weight`. If increasing `Condition weight` is ineffective or degrades image quality, try decreasing the `Condition weight` of other conditions. +- **Start with fewer conditions**. If you plan to use more than two conditions to control the model, it is recommended to start with only one or two conditions. Once you have found the suitable `Condition weight` for existing conditions, gradually append the new conditions. +- It is recommended to use a step size of 0.1 to adjust `Condition weight`. From experience, `Condition weight` will not be less than 0.5 or greater than 1.5 + +## Training +```bash +python train.py -t --base configs/pl_train/coadapter-v1-train.yaml --gpus 0,1,2,3,4,5,6,7 --scale_lr False --num_nodes 1 --sd_finetune_from models/v1-5-pruned-emaonly.ckpt --name coadapter-v1-train --auto_resume +``` diff --git a/comparison_models/T2IAdapter/docs/examples.md b/comparison_models/T2IAdapter/docs/examples.md new file mode 100644 index 0000000..7aa0f1d --- /dev/null +++ b/comparison_models/T2IAdapter/docs/examples.md @@ -0,0 +1,43 @@ +# Demos + +## CoAdapter +

+ + + +

+ +## Style Adapter + +

+ +

+ +## Color Adapter (Spatial Palette) + +

+ +

+ +## Openpose Adapter + +

+ +

+ +## Canny Adapter (Edge) + +

+ +

+ +## Multi-adapters +

+ +

+ +
+ +*T2I adapters naturally support using multiple adapters together.* [Image source](https://twitter.com/toyxyz3/status/1628375164781211648) + +

diff --git a/comparison_models/T2IAdapter/examples/README.md b/comparison_models/T2IAdapter/examples/README.md new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/examples/download_examples.py b/comparison_models/T2IAdapter/examples/download_examples.py new file mode 100644 index 0000000..f841d57 --- /dev/null +++ b/comparison_models/T2IAdapter/examples/download_examples.py @@ -0,0 +1,48 @@ +import os +from tqdm import tqdm + +import torch.hub + +example_list = [ + 'examples/canny/rabbit.png', + 'examples/canny/toy_canny.png', + 'examples/color/color_0000.png', + 'examples/color/color_0001.png', + 'examples/color/color_0002.png', + 'examples/color/color_0003.png', + 'examples/color/color_0004.png', + 'examples/depth/desk_depth.png', + 'examples/depth/sd.png', + 'examples/edit_cat/edge.png', + 'examples/edit_cat/edge_2.png', + 'examples/edit_cat/im.png', + 'examples/edit_cat/mask.png', + 'examples/keypose/iron.png', + 'examples/keypose/person_keypose.png', + 'examples/openpose/iron_man_image.png', + 'examples/openpose/iron_man_pose.png', + 'examples/seg/dinner.png', + 'examples/seg/motor.png', + 'examples/seg_sketch/edge.png', + 'examples/seg_sketch/mask.png', + 'examples/sketch/car.png', + 'examples/sketch/girl.jpeg', + 'examples/sketch/human.png', + 'examples/sketch/scenery.jpg', + 'examples/sketch/scenery2.jpg', + 'examples/style/Bianjing_city_gate.jpeg', + 'examples/style/Claude_Monet,_Impression,_soleil_levant,_1872.jpeg', + 'examples/style/DVwG-hevauxk1601457.jpeg', + 'examples/style/The_Persistence_of_Memory.jpeg', + 'examples/style/Tsunami_by_hokusai_19th_century.jpeg', + 'examples/style/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpeg', + 'examples/style/cyberpunk.png', + 'examples/style/scream.jpeg' +] + +huggingface_root = 'https://huggingface.co/TencentARC/T2I-Adapter/resolve/main' + +for example_path in tqdm(example_list): + if not os.path.exists(example_path): + os.makedirs(os.path.dirname(example_path), exist_ok=True) + torch.hub.download_url_to_file(f'{huggingface_root}/{example_path}', example_path) diff --git a/comparison_models/T2IAdapter/ldm/data/__init__.py b/comparison_models/T2IAdapter/ldm/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/ldm/data/dataset_coco.py b/comparison_models/T2IAdapter/ldm/data/dataset_coco.py new file mode 100644 index 0000000..0b4aa4f --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/data/dataset_coco.py @@ -0,0 +1,36 @@ +import json +import cv2 +import os +from basicsr.utils import img2tensor + + +class dataset_coco_mask_color(): + def __init__(self, path_json, root_path_im, root_path_mask, image_size): + super(dataset_coco_mask_color, self).__init__() + with open(path_json, 'r', encoding='utf-8') as fp: + data = json.load(fp) + data = data['annotations'] + self.files = [] + self.root_path_im = root_path_im + self.root_path_mask = root_path_mask + for file in data: + name = "%012d.png" % file['image_id'] + self.files.append({'name': name, 'sentence': file['caption']}) + + def __getitem__(self, idx): + file = self.files[idx] + name = file['name'] + # print(os.path.join(self.root_path_im, name)) + im = cv2.imread(os.path.join(self.root_path_im, name.replace('.png', '.jpg'))) + im = cv2.resize(im, (512, 512)) + im = img2tensor(im, bgr2rgb=True, float32=True) / 255. + + mask = cv2.imread(os.path.join(self.root_path_mask, name)) # [:,:,0] + mask = cv2.resize(mask, (512, 512)) + mask = img2tensor(mask, bgr2rgb=True, float32=True) / 255. # [0].unsqueeze(0)#/255. + + sentence = file['sentence'] + return {'im': im, 'mask': mask, 'sentence': sentence} + + def __len__(self): + return len(self.files) diff --git a/comparison_models/T2IAdapter/ldm/data/dataset_depth.py b/comparison_models/T2IAdapter/ldm/data/dataset_depth.py new file mode 100644 index 0000000..e3afe28 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/data/dataset_depth.py @@ -0,0 +1,35 @@ +import json +import cv2 +import os +from basicsr.utils import img2tensor + + +class DepthDataset(): + def __init__(self, meta_file): + super(DepthDataset, self).__init__() + + self.files = [] + with open(meta_file, 'r') as f: + lines = f.readlines() + for line in lines: + img_path = line.strip() + depth_img_path = img_path.rsplit('.', 1)[0] + '.depth.png' + txt_path = img_path.rsplit('.', 1)[0] + '.txt' + self.files.append({'img_path': img_path, 'depth_img_path': depth_img_path, 'txt_path': txt_path}) + + def __getitem__(self, idx): + file = self.files[idx] + + im = cv2.imread(file['img_path']) + im = img2tensor(im, bgr2rgb=True, float32=True) / 255. + + depth = cv2.imread(file['depth_img_path']) # [:,:,0] + depth = img2tensor(depth, bgr2rgb=True, float32=True) / 255. # [0].unsqueeze(0)#/255. + + with open(file['txt_path'], 'r') as fs: + sentence = fs.readline().strip() + + return {'im': im, 'depth': depth, 'sentence': sentence} + + def __len__(self): + return len(self.files) diff --git a/comparison_models/T2IAdapter/ldm/data/dataset_laion.py b/comparison_models/T2IAdapter/ldm/data/dataset_laion.py new file mode 100644 index 0000000..bad9c1b --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/data/dataset_laion.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +import numpy as np +import os +import pytorch_lightning as pl +import torch +import webdataset as wds +from torchvision.transforms import transforms + +from ldm.util import instantiate_from_config + + +def dict_collation_fn(samples, combine_tensors=True, combine_scalars=True): + """Take a list of samples (as dictionary) and create a batch, preserving the keys. + If `tensors` is True, `ndarray` objects are combined into + tensor batches. + :param dict samples: list of samples + :param bool tensors: whether to turn lists of ndarrays into a single ndarray + :returns: single sample consisting of a batch + :rtype: dict + """ + keys = set.intersection(*[set(sample.keys()) for sample in samples]) + batched = {key: [] for key in keys} + + for s in samples: + [batched[key].append(s[key]) for key in batched] + + result = {} + for key in batched: + if isinstance(batched[key][0], (int, float)): + if combine_scalars: + result[key] = np.array(list(batched[key])) + elif isinstance(batched[key][0], torch.Tensor): + if combine_tensors: + result[key] = torch.stack(list(batched[key])) + elif isinstance(batched[key][0], np.ndarray): + if combine_tensors: + result[key] = np.array(list(batched[key])) + else: + result[key] = list(batched[key]) + return result + + +class WebDataModuleFromConfig(pl.LightningDataModule): + + def __init__(self, + tar_base, + batch_size, + train=None, + validation=None, + test=None, + num_workers=4, + multinode=True, + min_size=None, + max_pwatermark=1.0, + **kwargs): + super().__init__() + print(f'Setting tar base to {tar_base}') + self.tar_base = tar_base + self.batch_size = batch_size + self.num_workers = num_workers + self.train = train + self.validation = validation + self.test = test + self.multinode = multinode + self.min_size = min_size # filter out very small images + self.max_pwatermark = max_pwatermark # filter out watermarked images + + def make_loader(self, dataset_config): + image_transforms = [instantiate_from_config(tt) for tt in dataset_config.image_transforms] + image_transforms = transforms.Compose(image_transforms) + + process_list = [] + for process_config in dataset_config['process']: + process_list.append(instantiate_from_config(process_config)) + + shuffle = dataset_config.get('shuffle', 0) + shardshuffle = shuffle > 0 + + nodesplitter = wds.shardlists.split_by_node if self.multinode else wds.shardlists.single_node_only + + tars = os.path.join(self.tar_base, dataset_config.shards) + + dset = wds.WebDataset( + tars, nodesplitter=nodesplitter, shardshuffle=shardshuffle, + handler=wds.warn_and_continue).repeat().shuffle(shuffle) + print(f'Loading webdataset with {len(dset.pipeline[0].urls)} shards.') + + dset = ( + dset.select(self.filter_keys).decode('pil', + handler=wds.warn_and_continue).select(self.filter_size).map_dict( + jpg=image_transforms, handler=wds.warn_and_continue)) + for process in process_list: + dset = dset.map(process) + dset = (dset.batched(self.batch_size, partial=False, collation_fn=dict_collation_fn)) + + loader = wds.WebLoader(dset, batch_size=None, shuffle=False, num_workers=self.num_workers) + + return loader + + def filter_size(self, x): + if self.min_size is None: + return True + try: + return x['json']['original_width'] >= self.min_size and x['json']['original_height'] >= self.min_size and x[ + 'json']['pwatermark'] <= self.max_pwatermark + except Exception: + return False + + def filter_keys(self, x): + try: + return ("jpg" in x) and ("txt" in x) + except Exception: + return False + + def train_dataloader(self): + return self.make_loader(self.train) + + def val_dataloader(self): + return None + + def test_dataloader(self): + return None + + +if __name__ == '__main__': + from omegaconf import OmegaConf + + config = OmegaConf.load("configs/pl_train/coadapter-v1-train.yaml") + datamod = WebDataModuleFromConfig(**config["data"]["params"]) + dataloader = datamod.train_dataloader() + + from basicsr.utils import tensor2img + import cv2 + save_root = 'tmp/coadapter' + os.makedirs(save_root, exist_ok=True) + + for idx, batch in enumerate(dataloader): + print(batch.keys()) + print(batch['jpg'].shape, torch.min(batch['jpg']), torch.max(batch['jpg'])) + img = tensor2img(batch['jpg']) + cv2.imwrite(f'{save_root}/{idx:03d}.png', img) + if idx > 20: + break diff --git a/comparison_models/T2IAdapter/ldm/data/dataset_wikiart.py b/comparison_models/T2IAdapter/ldm/data/dataset_wikiart.py new file mode 100644 index 0000000..a7a2de8 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/data/dataset_wikiart.py @@ -0,0 +1,67 @@ +import json +import os.path + +from PIL import Image +from torch.utils.data import DataLoader + +from transformers import CLIPProcessor +from torchvision.transforms import transforms + +import pytorch_lightning as pl + + +class WikiArtDataset(): + def __init__(self, meta_file): + super(WikiArtDataset, self).__init__() + + self.files = [] + with open(meta_file, 'r') as f: + js = json.load(f) + for img_path in js: + img_name = os.path.splitext(os.path.basename(img_path))[0] + caption = img_name.split('_')[-1] + caption = caption.split('-') + j = len(caption) - 1 + while j >= 0: + if not caption[j].isdigit(): + break + j -= 1 + if j < 0: + continue + sentence = ' '.join(caption[:j + 1]) + self.files.append({'img_path': os.path.join('datasets/wikiart', img_path), 'sentence': sentence}) + + version = 'openai/clip-vit-large-patch14' + self.processor = CLIPProcessor.from_pretrained(version) + + self.jpg_transform = transforms.Compose([ + transforms.Resize(512), + transforms.RandomCrop(512), + transforms.ToTensor(), + ]) + + def __getitem__(self, idx): + file = self.files[idx] + + im = Image.open(file['img_path']) + + im_tensor = self.jpg_transform(im) + + clip_im = self.processor(images=im, return_tensors="pt")['pixel_values'][0] + + return {'jpg': im_tensor, 'style': clip_im, 'txt': file['sentence']} + + def __len__(self): + return len(self.files) + + +class WikiArtDataModule(pl.LightningDataModule): + def __init__(self, meta_file, batch_size, num_workers): + super(WikiArtDataModule, self).__init__() + self.train_dataset = WikiArtDataset(meta_file) + self.batch_size = batch_size + self.num_workers = num_workers + + def train_dataloader(self): + return DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=True, num_workers=self.num_workers, + pin_memory=True) diff --git a/comparison_models/T2IAdapter/ldm/data/utils.py b/comparison_models/T2IAdapter/ldm/data/utils.py new file mode 100644 index 0000000..690e37a --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/data/utils.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +import cv2 +import numpy as np +from torchvision.transforms import transforms +from torchvision.transforms.functional import to_tensor +from transformers import CLIPProcessor + +from basicsr.utils import img2tensor + + +class PILtoTensor(object): + def __init__(self): + pass + + def __call__(self, sample): + sample['jpg'] = to_tensor(sample['jpg']) + if 'openpose' in sample: + sample['openpose'] = to_tensor(sample['openpose']) + return sample + + +class AddCannyFreezeThreshold(object): + + def __init__(self, low_threshold=100, high_threshold=200): + self.low_threshold = low_threshold + self.high_threshold = high_threshold + + def __call__(self, sample): + # sample['jpg'] is PIL image + x = sample['jpg'] + img = cv2.cvtColor(np.array(x), cv2.COLOR_RGB2BGR) + canny = cv2.Canny(img, self.low_threshold, self.high_threshold)[..., None] + sample['canny'] = img2tensor(canny, bgr2rgb=True, float32=True) / 255. + return sample + + +class AddCannyRandomThreshold(object): + + def __init__(self, low_threshold=100, high_threshold=200, shift_range=50): + self.low_threshold = low_threshold + self.high_threshold = high_threshold + self.threshold_prng = np.random.RandomState() + self.shift_range = shift_range + + def __call__(self, sample): + # sample['jpg'] is PIL image + x = sample['jpg'] + img = cv2.cvtColor(np.array(x), cv2.COLOR_RGB2BGR) + low_threshold = self.low_threshold + self.threshold_prng.randint(-self.shift_range, self.shift_range) + high_threshold = self.high_threshold + self.threshold_prng.randint(-self.shift_range, self.shift_range) + canny = cv2.Canny(img, low_threshold, high_threshold)[..., None] + sample['canny'] = img2tensor(canny, bgr2rgb=True, float32=True) / 255. + return sample + + +class AddStyle(object): + + def __init__(self, version): + self.processor = CLIPProcessor.from_pretrained(version) + self.pil_to_tensor = transforms.ToTensor() + + def __call__(self, sample): + # sample['jpg'] is PIL image + x = sample['jpg'] + style = self.processor(images=x, return_tensors="pt")['pixel_values'][0] + sample['style'] = style + return sample + + +class AddSpatialPalette(object): + + def __init__(self, downscale_factor=64): + self.downscale_factor = downscale_factor + + def __call__(self, sample): + # sample['jpg'] is PIL image + x = sample['jpg'] + img = cv2.cvtColor(np.array(x), cv2.COLOR_RGB2BGR) + h, w = img.shape[:2] + color = cv2.resize(img, (w // self.downscale_factor, h // self.downscale_factor), interpolation=cv2.INTER_CUBIC) + color = cv2.resize(color, (w, h), interpolation=cv2.INTER_NEAREST) + sample['color'] = img2tensor(color, bgr2rgb=True, float32=True) / 255. + return sample diff --git a/comparison_models/T2IAdapter/ldm/inference_base.py b/comparison_models/T2IAdapter/ldm/inference_base.py new file mode 100644 index 0000000..8eff08e --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/inference_base.py @@ -0,0 +1,300 @@ +import argparse +import torch +from omegaconf import OmegaConf + +from comparison_models.T2IAdapter.ldm.models.diffusion.ddim import DDIMSampler +from comparison_models.T2IAdapter.ldm.models.diffusion.plms import PLMSSampler +from comparison_models.T2IAdapter.ldm.modules.encoders.adapter import Adapter, StyleAdapter, Adapter_light +from comparison_models.T2IAdapter.ldm.modules.extra_condition.api import ExtraCondition +from comparison_models.T2IAdapter.ldm.util import fix_cond_shapes, load_model_from_config, read_state_dict + +DEFAULT_NEGATIVE_PROMPT = 'longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, ' \ + 'fewer digits, cropped, worst quality, low quality' + + +def get_base_argument_parser() -> argparse.ArgumentParser: + """get the base argument parser for inference scripts""" + parser = argparse.ArgumentParser() + parser.add_argument( + '--outdir', + type=str, + help='dir to write results to', + default=None, + ) + + parser.add_argument( + '--prompt', + type=str, + nargs='?', + default=None, + help='positive prompt', + ) + + parser.add_argument( + '--neg_prompt', + type=str, + default=DEFAULT_NEGATIVE_PROMPT, + help='negative prompt', + ) + + parser.add_argument( + '--cond_path', + type=str, + default=None, + help='condition image path', + ) + + parser.add_argument( + '--cond_inp_type', + type=str, + default='image', + help='the type of the input condition image, take depth T2I as example, the input can be raw image, ' + 'which depth will be calculated, or the input can be a directly a depth map image', + ) + + parser.add_argument( + '--sampler', + type=str, + default='ddim', + choices=['ddim', 'plms'], + help='sampling algorithm, currently, only ddim and plms are supported, more are on the way', + ) + + parser.add_argument( + '--steps', + type=int, + default=50, + help='number of sampling steps', + ) + + parser.add_argument( + '--sd_ckpt', + type=str, + default='models/sd-v1-4.ckpt', + help='path to checkpoint of stable diffusion model, both .ckpt and .safetensor are supported', + ) + + parser.add_argument( + '--vae_ckpt', + type=str, + default=None, + help='vae checkpoint, anime SD models usually have seperate vae ckpt that need to be loaded', + ) + + parser.add_argument( + '--adapter_ckpt', + type=str, + default=None, + help='path to checkpoint of adapter', + ) + + parser.add_argument( + '--config', + type=str, + default='configs/stable-diffusion/sd-v1-inference.yaml', + help='path to config which constructs SD model', + ) + + parser.add_argument( + '--max_resolution', + type=float, + default=512 * 512, + help='max image height * width, only for computer with limited vram', + ) + + parser.add_argument( + '--resize_short_edge', + type=int, + default=None, + help='resize short edge of the input image, if this arg is set, max_resolution will not be used', + ) + + parser.add_argument( + '--C', + type=int, + default=4, + help='latent channels', + ) + + parser.add_argument( + '--f', + type=int, + default=8, + help='downsampling factor', + ) + + parser.add_argument( + '--scale', + type=float, + default=7.5, + help='unconditional guidance scale: eps = eps(x, empty) + scale * (eps(x, cond) - eps(x, empty))', + ) + + parser.add_argument( + '--cond_tau', + type=float, + default=1.0, + help='timestamp parameter that determines until which step the adapter is applied, ' + 'similar as Prompt-to-Prompt tau', + ) + + parser.add_argument( + '--style_cond_tau', + type=float, + default=1.0, + help='timestamp parameter that determines until which step the adapter is applied, ' + 'similar as Prompt-to-Prompt tau', + ) + + parser.add_argument( + '--cond_weight', + type=float, + default=1.0, + help='the adapter features are multiplied by the cond_weight. The larger the cond_weight, the more aligned ' + 'the generated image and condition will be, but the generated quality may be reduced', + ) + + parser.add_argument( + '--seed', + type=int, + default=42, + ) + + parser.add_argument( + '--n_samples', + type=int, + default=4, + help='# of samples to generate', + ) + + return parser + + +def get_sd_models(opt): + """ + build stable diffusion model, sampler + """ + # SD + config = OmegaConf.load(f"{opt.config}") + model = load_model_from_config(config, opt.sd_ckpt, opt.vae_ckpt) + sd_model = model.to(opt.device) + + # sampler + if opt.sampler == 'plms': + sampler = PLMSSampler(model) + elif opt.sampler == 'ddim': + sampler = DDIMSampler(model) + else: + raise NotImplementedError + + return sd_model, sampler + + +def get_t2i_adapter_models(opt): + config = OmegaConf.load(f"{opt.config}") + model = load_model_from_config(config, opt.sd_ckpt, opt.vae_ckpt) + adapter_ckpt_path = getattr(opt, f'{opt.which_cond}_adapter_ckpt', None) + if adapter_ckpt_path is None: + adapter_ckpt_path = getattr(opt, 'adapter_ckpt') + adapter_ckpt = read_state_dict(adapter_ckpt_path) + new_state_dict = {} + for k, v in adapter_ckpt.items(): + if not k.startswith('adapter.'): + new_state_dict[f'adapter.{k}'] = v + else: + new_state_dict[k] = v + m, u = model.load_state_dict(new_state_dict, strict=False) + if len(u) > 0: + print(f"unexpected keys in loading adapter ckpt {adapter_ckpt_path}:") + print(u) + + model = model.to(opt.device) + + # sampler + if opt.sampler == 'plms': + sampler = PLMSSampler(model) + elif opt.sampler == 'ddim': + sampler = DDIMSampler(model) + else: + raise NotImplementedError + + return model, sampler + + +def get_cond_ch(cond_type: ExtraCondition): + if cond_type == ExtraCondition.sketch or cond_type == ExtraCondition.canny: + return 1 + return 3 + + +def get_adapters(opt, cond_type: ExtraCondition): + adapter = {} + cond_weight = getattr(opt, f'{cond_type.name}_weight', None) + if cond_weight is None: + cond_weight = getattr(opt, 'cond_weight') + adapter['cond_weight'] = cond_weight + + if cond_type == ExtraCondition.style: + adapter['model'] = StyleAdapter(width=1024, context_dim=768, num_head=8, n_layes=3, num_token=8).to(opt.device) + elif cond_type == ExtraCondition.color: + adapter['model'] = Adapter_light( + cin=64 * get_cond_ch(cond_type), + channels=[320, 640, 1280, 1280], + nums_rb=4).to(opt.device) + else: + adapter['model'] = Adapter( + cin=64 * get_cond_ch(cond_type), + channels=[320, 640, 1280, 1280][:4], + nums_rb=2, + ksize=1, + sk=True, + use_conv=False).to(opt.device) + ckpt_path = getattr(opt, f'{cond_type.name}_adapter_ckpt', None) + if ckpt_path is None: + ckpt_path = getattr(opt, 'adapter_ckpt') + state_dict = read_state_dict(ckpt_path) + new_state_dict = {} + for k, v in state_dict.items(): + if k.startswith('adapter.'): + new_state_dict[k[len('adapter.'):]] = v + else: + new_state_dict[k] = v + + adapter['model'].load_state_dict(new_state_dict) + + return adapter + + +def diffusion_inference(opt, model, sampler, adapter_features, append_to_context=None): + # get text embedding + c = model.get_learned_conditioning([opt.prompt]) + if opt.scale != 1.0: + uc = model.get_learned_conditioning([opt.neg_prompt]) + else: + uc = None + c, uc = fix_cond_shapes(model, c, uc) + + if not hasattr(opt, 'H'): + opt.H = 512 + opt.W = 512 + shape = [opt.C, opt.H // opt.f, opt.W // opt.f] + + samples_latents, _ = sampler.sample( + S=opt.steps, + conditioning=c, + batch_size=1, + shape=shape, + verbose=False, + unconditional_guidance_scale=opt.scale, + unconditional_conditioning=uc, + x_T=None, + features_adapter=adapter_features, + append_to_context=append_to_context, + cond_tau=opt.cond_tau, + style_cond_tau=opt.style_cond_tau, + ) + + x_samples = model.decode_first_stage(samples_latents) + x_samples = torch.clamp((x_samples + 1.0) / 2.0, min=0.0, max=1.0) + + return x_samples diff --git a/comparison_models/T2IAdapter/ldm/lr_scheduler.py b/comparison_models/T2IAdapter/ldm/lr_scheduler.py new file mode 100644 index 0000000..be39da9 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/lr_scheduler.py @@ -0,0 +1,98 @@ +import numpy as np + + +class LambdaWarmUpCosineScheduler: + """ + note: use with a base_lr of 1.0 + """ + def __init__(self, warm_up_steps, lr_min, lr_max, lr_start, max_decay_steps, verbosity_interval=0): + self.lr_warm_up_steps = warm_up_steps + self.lr_start = lr_start + self.lr_min = lr_min + self.lr_max = lr_max + self.lr_max_decay_steps = max_decay_steps + self.last_lr = 0. + self.verbosity_interval = verbosity_interval + + def schedule(self, n, **kwargs): + if self.verbosity_interval > 0: + if n % self.verbosity_interval == 0: print(f"current step: {n}, recent lr-multiplier: {self.last_lr}") + if n < self.lr_warm_up_steps: + lr = (self.lr_max - self.lr_start) / self.lr_warm_up_steps * n + self.lr_start + self.last_lr = lr + return lr + else: + t = (n - self.lr_warm_up_steps) / (self.lr_max_decay_steps - self.lr_warm_up_steps) + t = min(t, 1.0) + lr = self.lr_min + 0.5 * (self.lr_max - self.lr_min) * ( + 1 + np.cos(t * np.pi)) + self.last_lr = lr + return lr + + def __call__(self, n, **kwargs): + return self.schedule(n,**kwargs) + + +class LambdaWarmUpCosineScheduler2: + """ + supports repeated iterations, configurable via lists + note: use with a base_lr of 1.0. + """ + def __init__(self, warm_up_steps, f_min, f_max, f_start, cycle_lengths, verbosity_interval=0): + assert len(warm_up_steps) == len(f_min) == len(f_max) == len(f_start) == len(cycle_lengths) + self.lr_warm_up_steps = warm_up_steps + self.f_start = f_start + self.f_min = f_min + self.f_max = f_max + self.cycle_lengths = cycle_lengths + self.cum_cycles = np.cumsum([0] + list(self.cycle_lengths)) + self.last_f = 0. + self.verbosity_interval = verbosity_interval + + def find_in_interval(self, n): + interval = 0 + for cl in self.cum_cycles[1:]: + if n <= cl: + return interval + interval += 1 + + def schedule(self, n, **kwargs): + cycle = self.find_in_interval(n) + n = n - self.cum_cycles[cycle] + if self.verbosity_interval > 0: + if n % self.verbosity_interval == 0: print(f"current step: {n}, recent lr-multiplier: {self.last_f}, " + f"current cycle {cycle}") + if n < self.lr_warm_up_steps[cycle]: + f = (self.f_max[cycle] - self.f_start[cycle]) / self.lr_warm_up_steps[cycle] * n + self.f_start[cycle] + self.last_f = f + return f + else: + t = (n - self.lr_warm_up_steps[cycle]) / (self.cycle_lengths[cycle] - self.lr_warm_up_steps[cycle]) + t = min(t, 1.0) + f = self.f_min[cycle] + 0.5 * (self.f_max[cycle] - self.f_min[cycle]) * ( + 1 + np.cos(t * np.pi)) + self.last_f = f + return f + + def __call__(self, n, **kwargs): + return self.schedule(n, **kwargs) + + +class LambdaLinearScheduler(LambdaWarmUpCosineScheduler2): + + def schedule(self, n, **kwargs): + cycle = self.find_in_interval(n) + n = n - self.cum_cycles[cycle] + if self.verbosity_interval > 0: + if n % self.verbosity_interval == 0: print(f"current step: {n}, recent lr-multiplier: {self.last_f}, " + f"current cycle {cycle}") + + if n < self.lr_warm_up_steps[cycle]: + f = (self.f_max[cycle] - self.f_start[cycle]) / self.lr_warm_up_steps[cycle] * n + self.f_start[cycle] + self.last_f = f + return f + else: + f = self.f_min[cycle] + (self.f_max[cycle] - self.f_min[cycle]) * (self.cycle_lengths[cycle] - n) / (self.cycle_lengths[cycle]) + self.last_f = f + return f + diff --git a/comparison_models/T2IAdapter/ldm/models/autoencoder.py b/comparison_models/T2IAdapter/ldm/models/autoencoder.py new file mode 100644 index 0000000..e3ff5fe --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/models/autoencoder.py @@ -0,0 +1,211 @@ +import torch +import pytorch_lightning as pl +import torch.nn.functional as F +import torch.nn as nn +from contextlib import contextmanager + +from ldm.modules.diffusionmodules.model import Encoder, Decoder +from ldm.modules.distributions.distributions import DiagonalGaussianDistribution + +from ldm.util import instantiate_from_config +from ldm.modules.ema import LitEma + + +class AutoencoderKL(pl.LightningModule): + def __init__(self, + ddconfig, + lossconfig, + embed_dim, + ckpt_path=None, + ignore_keys=[], + image_key="image", + colorize_nlabels=None, + monitor=None, + ema_decay=None, + learn_logvar=False + ): + super().__init__() + self.learn_logvar = learn_logvar + self.image_key = image_key + self.encoder = Encoder(**ddconfig) + self.decoder = Decoder(**ddconfig) + self.loss = instantiate_from_config(lossconfig) + assert ddconfig["double_z"] + self.quant_conv = torch.nn.Conv2d(2*ddconfig["z_channels"], 2*embed_dim, 1) + self.post_quant_conv = torch.nn.Conv2d(embed_dim, ddconfig["z_channels"], 1) + self.embed_dim = embed_dim + if colorize_nlabels is not None: + assert type(colorize_nlabels)==int + self.register_buffer("colorize", torch.randn(3, colorize_nlabels, 1, 1)) + if monitor is not None: + self.monitor = monitor + + self.use_ema = ema_decay is not None + if self.use_ema: + self.ema_decay = ema_decay + assert 0. < ema_decay < 1. + self.model_ema = LitEma(self, decay=ema_decay) + print(f"Keeping EMAs of {len(list(self.model_ema.buffers()))}.") + + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys) + + def init_from_ckpt(self, path, ignore_keys=list()): + sd = torch.load(path, map_location="cpu")["state_dict"] + keys = list(sd.keys()) + for k in keys: + for ik in ignore_keys: + if k.startswith(ik): + print("Deleting key {} from state_dict.".format(k)) + del sd[k] + self.load_state_dict(sd, strict=False) + print(f"Restored from {path}") + + @contextmanager + def ema_scope(self, context=None): + if self.use_ema: + self.model_ema.store(self.parameters()) + self.model_ema.copy_to(self) + if context is not None: + print(f"{context}: Switched to EMA weights") + try: + yield None + finally: + if self.use_ema: + self.model_ema.restore(self.parameters()) + if context is not None: + print(f"{context}: Restored training weights") + + def on_train_batch_end(self, *args, **kwargs): + if self.use_ema: + self.model_ema(self) + + def encode(self, x): + h = self.encoder(x) + moments = self.quant_conv(h) + posterior = DiagonalGaussianDistribution(moments) + return posterior + + def decode(self, z): + z = self.post_quant_conv(z) + dec = self.decoder(z) + return dec + + def forward(self, input, sample_posterior=True): + posterior = self.encode(input) + if sample_posterior: + z = posterior.sample() + else: + z = posterior.mode() + dec = self.decode(z) + return dec, posterior + + def get_input(self, batch, k): + x = batch[k] + if len(x.shape) == 3: + x = x[..., None] + x = x.permute(0, 3, 1, 2).to(memory_format=torch.contiguous_format).float() + return x + + def training_step(self, batch, batch_idx, optimizer_idx): + inputs = self.get_input(batch, self.image_key) + reconstructions, posterior = self(inputs) + + if optimizer_idx == 0: + # train encoder+decoder+logvar + aeloss, log_dict_ae = self.loss(inputs, reconstructions, posterior, optimizer_idx, self.global_step, + last_layer=self.get_last_layer(), split="train") + self.log("aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=False) + return aeloss + + if optimizer_idx == 1: + # train the discriminator + discloss, log_dict_disc = self.loss(inputs, reconstructions, posterior, optimizer_idx, self.global_step, + last_layer=self.get_last_layer(), split="train") + + self.log("discloss", discloss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=False) + return discloss + + def validation_step(self, batch, batch_idx): + log_dict = self._validation_step(batch, batch_idx) + with self.ema_scope(): + log_dict_ema = self._validation_step(batch, batch_idx, postfix="_ema") + return log_dict + + def _validation_step(self, batch, batch_idx, postfix=""): + inputs = self.get_input(batch, self.image_key) + reconstructions, posterior = self(inputs) + aeloss, log_dict_ae = self.loss(inputs, reconstructions, posterior, 0, self.global_step, + last_layer=self.get_last_layer(), split="val"+postfix) + + discloss, log_dict_disc = self.loss(inputs, reconstructions, posterior, 1, self.global_step, + last_layer=self.get_last_layer(), split="val"+postfix) + + self.log(f"val{postfix}/rec_loss", log_dict_ae[f"val{postfix}/rec_loss"]) + self.log_dict(log_dict_ae) + self.log_dict(log_dict_disc) + return self.log_dict + + def configure_optimizers(self): + lr = self.learning_rate + ae_params_list = list(self.encoder.parameters()) + list(self.decoder.parameters()) + list( + self.quant_conv.parameters()) + list(self.post_quant_conv.parameters()) + if self.learn_logvar: + print(f"{self.__class__.__name__}: Learning logvar") + ae_params_list.append(self.loss.logvar) + opt_ae = torch.optim.Adam(ae_params_list, + lr=lr, betas=(0.5, 0.9)) + opt_disc = torch.optim.Adam(self.loss.discriminator.parameters(), + lr=lr, betas=(0.5, 0.9)) + return [opt_ae, opt_disc], [] + + def get_last_layer(self): + return self.decoder.conv_out.weight + + @torch.no_grad() + def log_images(self, batch, only_inputs=False, log_ema=False, **kwargs): + log = dict() + x = self.get_input(batch, self.image_key) + x = x.to(self.device) + if not only_inputs: + xrec, posterior = self(x) + if x.shape[1] > 3: + # colorize with random projection + assert xrec.shape[1] > 3 + x = self.to_rgb(x) + xrec = self.to_rgb(xrec) + log["samples"] = self.decode(torch.randn_like(posterior.sample())) + log["reconstructions"] = xrec + log["inputs"] = x + return log + + def to_rgb(self, x): + assert self.image_key == "segmentation" + if not hasattr(self, "colorize"): + self.register_buffer("colorize", torch.randn(3, x.shape[1], 1, 1).to(x)) + x = F.conv2d(x, weight=self.colorize) + x = 2.*(x-x.min())/(x.max()-x.min()) - 1. + return x + + +class IdentityFirstStage(nn.Module): + def __init__(self, *args, vq_interface=False, **kwargs): + self.vq_interface = vq_interface + super().__init__() + + def encode(self, x, *args, **kwargs): + return x + + def decode(self, x, *args, **kwargs): + return x + + def quantize(self, x, *args, **kwargs): + if self.vq_interface: + return x, None, [None, None, None] + return x + + def forward(self, x, *args, **kwargs): + return x + diff --git a/comparison_models/T2IAdapter/ldm/models/diffusion/__init__.py b/comparison_models/T2IAdapter/ldm/models/diffusion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/ldm/models/diffusion/ddim.py b/comparison_models/T2IAdapter/ldm/models/diffusion/ddim.py new file mode 100644 index 0000000..6c9d6df --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/models/diffusion/ddim.py @@ -0,0 +1,293 @@ +"""SAMPLING ONLY.""" + +import torch +import numpy as np +from tqdm import tqdm + +from comparison_models.T2IAdapter.ldm.modules.diffusionmodules.util import make_ddim_sampling_parameters, make_ddim_timesteps, noise_like, \ + extract_into_tensor + + +class DDIMSampler(object): + def __init__(self, model, schedule="linear", **kwargs): + super().__init__() + self.model = model + self.ddpm_num_timesteps = model.num_timesteps + self.schedule = schedule + + def register_buffer(self, name, attr): + if type(attr) == torch.Tensor: + if attr.device != torch.device("cuda"): + attr = attr.to(torch.device("cuda")) + setattr(self, name, attr) + + def make_schedule(self, ddim_num_steps, ddim_discretize="uniform", ddim_eta=0., verbose=True): + self.ddim_timesteps = make_ddim_timesteps(ddim_discr_method=ddim_discretize, num_ddim_timesteps=ddim_num_steps, + num_ddpm_timesteps=self.ddpm_num_timesteps, verbose=verbose) + alphas_cumprod = self.model.alphas_cumprod + assert alphas_cumprod.shape[0] == self.ddpm_num_timesteps, 'alphas have to be defined for each timestep' + to_torch = lambda x: x.clone().detach().to(torch.float32).to(self.model.device) + + self.register_buffer('betas', to_torch(self.model.betas)) + self.register_buffer('alphas_cumprod', to_torch(alphas_cumprod)) + self.register_buffer('alphas_cumprod_prev', to_torch(self.model.alphas_cumprod_prev)) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod.cpu()))) + self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod.cpu()))) + self.register_buffer('log_one_minus_alphas_cumprod', to_torch(np.log(1. - alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recip_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recipm1_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu() - 1))) + + # ddim sampling parameters + ddim_sigmas, ddim_alphas, ddim_alphas_prev = make_ddim_sampling_parameters(alphacums=alphas_cumprod.cpu(), + ddim_timesteps=self.ddim_timesteps, + eta=ddim_eta, verbose=verbose) + self.register_buffer('ddim_sigmas', ddim_sigmas) + self.register_buffer('ddim_alphas', ddim_alphas) + self.register_buffer('ddim_alphas_prev', ddim_alphas_prev) + self.register_buffer('ddim_sqrt_one_minus_alphas', np.sqrt(1. - ddim_alphas)) + sigmas_for_original_sampling_steps = ddim_eta * torch.sqrt( + (1 - self.alphas_cumprod_prev) / (1 - self.alphas_cumprod) * ( + 1 - self.alphas_cumprod / self.alphas_cumprod_prev)) + self.register_buffer('ddim_sigmas_for_original_num_steps', sigmas_for_original_sampling_steps) + + @torch.no_grad() + def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + features_adapter=None, + append_to_context=None, + cond_tau=0.4, + style_cond_tau=1.0, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + cbs = conditioning[list(conditioning.keys())[0]].shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose) + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + print(f'Data shape for DDIM sampling is {size}, eta {eta}') + + samples, intermediates = self.ddim_sampling(conditioning, size, + callback=callback, + img_callback=img_callback, + quantize_denoised=quantize_x0, + mask=mask, x0=x0, + ddim_use_original_steps=False, + noise_dropout=noise_dropout, + temperature=temperature, + score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + x_T=x_T, + log_every_t=log_every_t, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + features_adapter=features_adapter, + append_to_context=append_to_context, + cond_tau=cond_tau, + style_cond_tau=style_cond_tau, + ) + return samples, intermediates + + @torch.no_grad() + def ddim_sampling(self, cond, shape, + x_T=None, ddim_use_original_steps=False, + callback=None, timesteps=None, quantize_denoised=False, + mask=None, x0=None, img_callback=None, log_every_t=100, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, features_adapter=None, + append_to_context=None, cond_tau=0.4, style_cond_tau=1.0): + device = self.model.betas.device + b = shape[0] + if x_T is None: + img = torch.randn(shape, device=device) + else: + img = x_T + + if timesteps is None: + timesteps = self.ddpm_num_timesteps if ddim_use_original_steps else self.ddim_timesteps + elif timesteps is not None and not ddim_use_original_steps: + subset_end = int(min(timesteps / self.ddim_timesteps.shape[0], 1) * self.ddim_timesteps.shape[0]) - 1 + timesteps = self.ddim_timesteps[:subset_end] + + intermediates = {'x_inter': [img], 'pred_x0': [img]} + time_range = reversed(range(0, timesteps)) if ddim_use_original_steps else np.flip(timesteps) + total_steps = timesteps if ddim_use_original_steps else timesteps.shape[0] + print(f"Running DDIM Sampling with {total_steps} timesteps") + + iterator = tqdm(time_range, desc='DDIM Sampler', total=total_steps) + + for i, step in enumerate(iterator): + index = total_steps - i - 1 + ts = torch.full((b,), step, device=device, dtype=torch.long) + + if mask is not None: + assert x0 is not None + img_orig = self.model.q_sample(x0, ts) # TODO: deterministic forward pass? + img = img_orig * mask + (1. - mask) * img + + outs = self.p_sample_ddim(img, cond, ts, index=index, use_original_steps=ddim_use_original_steps, + quantize_denoised=quantize_denoised, temperature=temperature, + noise_dropout=noise_dropout, score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + features_adapter=None if index < int( + (1 - cond_tau) * total_steps) else features_adapter, + append_to_context=None if index < int( + (1 - style_cond_tau) * total_steps) else append_to_context, + ) + img, pred_x0 = outs + if callback: callback(i) + if img_callback: img_callback(pred_x0, i) + + if index % log_every_t == 0 or index == total_steps - 1: + intermediates['x_inter'].append(img) + intermediates['pred_x0'].append(pred_x0) + + return img, intermediates + + @torch.no_grad() + def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, features_adapter=None, + append_to_context=None): + b, *_, device = *x.shape, x.device + + if unconditional_conditioning is None or unconditional_guidance_scale == 1.: + if append_to_context is not None: + model_output = self.model.apply_model(x, t, torch.cat([c, append_to_context], dim=1), + features_adapter=features_adapter) + else: + model_output = self.model.apply_model(x, t, c, features_adapter=features_adapter) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t] * 2) + if isinstance(c, dict): + assert isinstance(unconditional_conditioning, dict) + c_in = dict() + for k in c: + if isinstance(c[k], list): + c_in[k] = [torch.cat([ + unconditional_conditioning[k][i], + c[k][i]]) for i in range(len(c[k]))] + else: + c_in[k] = torch.cat([ + unconditional_conditioning[k], + c[k]]) + elif isinstance(c, list): + c_in = list() + assert isinstance(unconditional_conditioning, list) + for i in range(len(c)): + c_in.append(torch.cat([unconditional_conditioning[i], c[i]])) + else: + if append_to_context is not None: + pad_len = append_to_context.size(1) + new_unconditional_conditioning = torch.cat( + [unconditional_conditioning, unconditional_conditioning[:, -pad_len:, :]], dim=1) + new_c = torch.cat([c, append_to_context], dim=1) + c_in = torch.cat([new_unconditional_conditioning, new_c]) + else: + c_in = torch.cat([unconditional_conditioning, c]) + model_uncond, model_t = self.model.apply_model(x_in, t_in, c_in, features_adapter=features_adapter).chunk(2) + model_output = model_uncond + unconditional_guidance_scale * (model_t - model_uncond) + + if self.model.parameterization == "v": + e_t = self.model.predict_eps_from_z_and_v(x, t, model_output) + else: + e_t = model_output + + if score_corrector is not None: + assert self.model.parameterization == "eps", 'not implemented' + e_t = score_corrector.modify_score(self.model, e_t, x, t, c, **corrector_kwargs) + + alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas + alphas_prev = self.model.alphas_cumprod_prev if use_original_steps else self.ddim_alphas_prev + sqrt_one_minus_alphas = self.model.sqrt_one_minus_alphas_cumprod if use_original_steps else self.ddim_sqrt_one_minus_alphas + sigmas = self.model.ddim_sigmas_for_original_num_steps if use_original_steps else self.ddim_sigmas + # select parameters corresponding to the currently considered timestep + a_t = torch.full((b, 1, 1, 1), alphas[index], device=device) + a_prev = torch.full((b, 1, 1, 1), alphas_prev[index], device=device) + sigma_t = torch.full((b, 1, 1, 1), sigmas[index], device=device) + sqrt_one_minus_at = torch.full((b, 1, 1, 1), sqrt_one_minus_alphas[index], device=device) + + # current prediction for x_0 + if self.model.parameterization != "v": + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + else: + pred_x0 = self.model.predict_start_from_z_and_v(x, t, model_output) + + if quantize_denoised: + pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0) + # direction pointing to x_t + dir_xt = (1. - a_prev - sigma_t ** 2).sqrt() * e_t + noise = sigma_t * noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + x_prev = a_prev.sqrt() * pred_x0 + dir_xt + noise + return x_prev, pred_x0 + + @torch.no_grad() + def stochastic_encode(self, x0, t, use_original_steps=False, noise=None): + # fast, but does not allow for exact reconstruction + # t serves as an index to gather the correct alphas + if use_original_steps: + sqrt_alphas_cumprod = self.sqrt_alphas_cumprod + sqrt_one_minus_alphas_cumprod = self.sqrt_one_minus_alphas_cumprod + else: + sqrt_alphas_cumprod = torch.sqrt(self.ddim_alphas) + sqrt_one_minus_alphas_cumprod = self.ddim_sqrt_one_minus_alphas + + if noise is None: + noise = torch.randn_like(x0) + return (extract_into_tensor(sqrt_alphas_cumprod, t, x0.shape) * x0 + + extract_into_tensor(sqrt_one_minus_alphas_cumprod, t, x0.shape) * noise) + + @torch.no_grad() + def decode(self, x_latent, cond, t_start, unconditional_guidance_scale=1.0, unconditional_conditioning=None, + use_original_steps=False): + + timesteps = np.arange(self.ddpm_num_timesteps) if use_original_steps else self.ddim_timesteps + timesteps = timesteps[:t_start] + + time_range = np.flip(timesteps) + total_steps = timesteps.shape[0] + print(f"Running DDIM Sampling with {total_steps} timesteps") + + iterator = tqdm(time_range, desc='Decoding image', total=total_steps) + x_dec = x_latent + for i, step in enumerate(iterator): + index = total_steps - i - 1 + ts = torch.full((x_latent.shape[0],), step, device=x_latent.device, dtype=torch.long) + x_dec, _ = self.p_sample_ddim(x_dec, cond, ts, index=index, use_original_steps=use_original_steps, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning) + return x_dec diff --git a/comparison_models/T2IAdapter/ldm/models/diffusion/ddpm.py b/comparison_models/T2IAdapter/ldm/models/diffusion/ddpm.py new file mode 100644 index 0000000..1dd7d26 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/models/diffusion/ddpm.py @@ -0,0 +1,1329 @@ +""" +wild mixture of +https://github.com/lucidrains/denoising-diffusion-pytorch/blob/7706bdfc6f527f58d33f84b7b522e61e6e3164b3/denoising_diffusion_pytorch/denoising_diffusion_pytorch.py +https://github.com/openai/improved-diffusion/blob/e94489283bb876ac1477d5dd7709bbbd2d9902ce/improved_diffusion/gaussian_diffusion.py +https://github.com/CompVis/taming-transformers +-- merci +""" + +import torch +import torch.nn as nn +import numpy as np +import pytorch_lightning as pl +from torch.optim.lr_scheduler import LambdaLR +from einops import rearrange, repeat +from contextlib import contextmanager, nullcontext +from functools import partial +import itertools +from tqdm import tqdm +from torchvision.utils import make_grid +from pytorch_lightning.utilities.distributed import rank_zero_only +from omegaconf import ListConfig + +from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config +from ldm.modules.ema import LitEma +from ldm.modules.distributions.distributions import normal_kl, DiagonalGaussianDistribution +from ldm.models.autoencoder import IdentityFirstStage, AutoencoderKL +from ldm.modules.diffusionmodules.util import make_beta_schedule, extract_into_tensor, noise_like +from ldm.models.diffusion.ddim import DDIMSampler + + +__conditioning_keys__ = {'concat': 'c_concat', + 'crossattn': 'c_crossattn', + 'adm': 'y'} + + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +def uniform_on_device(r1, r2, shape, device): + return (r1 - r2) * torch.rand(*shape, device=device) + r2 + + +class DDPM(pl.LightningModule): + # classic DDPM with Gaussian diffusion, in image space + def __init__(self, + unet_config, + timesteps=1000, + beta_schedule="linear", + loss_type="l2", + ckpt_path=None, + ignore_keys=[], + load_only_unet=False, + monitor="val/loss", + use_ema=True, + first_stage_key="image", + image_size=256, + channels=3, + log_every_t=100, + clip_denoised=True, + linear_start=1e-4, + linear_end=2e-2, + cosine_s=8e-3, + given_betas=None, + original_elbo_weight=0., + v_posterior=0., # weight for choosing posterior variance as sigma = (1-v) * beta_tilde + v * beta + l_simple_weight=1., + conditioning_key=None, + parameterization="eps", # all assuming fixed variance schedules + scheduler_config=None, + use_positional_encodings=False, + learn_logvar=False, + logvar_init=0., + make_it_fit=False, + ucg_training=None, + reset_ema=False, + reset_num_ema_updates=False, + ): + super().__init__() + assert parameterization in ["eps", "x0", "v"], 'currently only supporting "eps" and "x0" and "v"' + self.parameterization = parameterization + print(f"{self.__class__.__name__}: Running in {self.parameterization}-prediction mode") + self.cond_stage_model = None + self.clip_denoised = clip_denoised + self.log_every_t = log_every_t + self.first_stage_key = first_stage_key + self.image_size = image_size # try conv? + self.channels = channels + self.use_positional_encodings = use_positional_encodings + self.model = DiffusionWrapper(unet_config, conditioning_key) + count_params(self.model, verbose=True) + self.use_ema = use_ema + if self.use_ema: + self.model_ema = LitEma(self.model) + print(f"Keeping EMAs of {len(list(self.model_ema.buffers()))}.") + + self.use_scheduler = scheduler_config is not None + if self.use_scheduler: + self.scheduler_config = scheduler_config + + self.v_posterior = v_posterior + self.original_elbo_weight = original_elbo_weight + self.l_simple_weight = l_simple_weight + + if monitor is not None: + self.monitor = monitor + self.make_it_fit = make_it_fit + if reset_ema: assert exists(ckpt_path) + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys, only_model=load_only_unet) + if reset_ema: + assert self.use_ema + print(f"Resetting ema to pure model weights. This is useful when restoring from an ema-only checkpoint.") + self.model_ema = LitEma(self.model) + if reset_num_ema_updates: + print(" +++++++++++ WARNING: RESETTING NUM_EMA UPDATES TO ZERO +++++++++++ ") + assert self.use_ema + self.model_ema.reset_num_updates() + + self.register_schedule(given_betas=given_betas, beta_schedule=beta_schedule, timesteps=timesteps, + linear_start=linear_start, linear_end=linear_end, cosine_s=cosine_s) + + self.loss_type = loss_type + + self.learn_logvar = learn_logvar + self.logvar = torch.full(fill_value=logvar_init, size=(self.num_timesteps,)) + if self.learn_logvar: + self.logvar = nn.Parameter(self.logvar, requires_grad=True) + + self.ucg_training = ucg_training or dict() + if self.ucg_training: + self.ucg_prng = np.random.RandomState() + + def register_schedule(self, given_betas=None, beta_schedule="linear", timesteps=1000, + linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3): + if exists(given_betas): + betas = given_betas + else: + betas = make_beta_schedule(beta_schedule, timesteps, linear_start=linear_start, linear_end=linear_end, + cosine_s=cosine_s) + alphas = 1. - betas + alphas_cumprod = np.cumprod(alphas, axis=0) + alphas_cumprod_prev = np.append(1., alphas_cumprod[:-1]) + + timesteps, = betas.shape + self.num_timesteps = int(timesteps) + self.linear_start = linear_start + self.linear_end = linear_end + assert alphas_cumprod.shape[0] == self.num_timesteps, 'alphas have to be defined for each timestep' + + to_torch = partial(torch.tensor, dtype=torch.float32) + + self.register_buffer('betas', to_torch(betas)) + self.register_buffer('alphas_cumprod', to_torch(alphas_cumprod)) + self.register_buffer('alphas_cumprod_prev', to_torch(alphas_cumprod_prev)) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod))) + self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod))) + self.register_buffer('log_one_minus_alphas_cumprod', to_torch(np.log(1. - alphas_cumprod))) + self.register_buffer('sqrt_recip_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod))) + self.register_buffer('sqrt_recipm1_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod - 1))) + + # calculations for posterior q(x_{t-1} | x_t, x_0) + posterior_variance = (1 - self.v_posterior) * betas * (1. - alphas_cumprod_prev) / ( + 1. - alphas_cumprod) + self.v_posterior * betas + # above: equal to 1. / (1. / (1. - alpha_cumprod_tm1) + alpha_t / beta_t) + self.register_buffer('posterior_variance', to_torch(posterior_variance)) + # below: log calculation clipped because the posterior variance is 0 at the beginning of the diffusion chain + self.register_buffer('posterior_log_variance_clipped', to_torch(np.log(np.maximum(posterior_variance, 1e-20)))) + self.register_buffer('posterior_mean_coef1', to_torch( + betas * np.sqrt(alphas_cumprod_prev) / (1. - alphas_cumprod))) + self.register_buffer('posterior_mean_coef2', to_torch( + (1. - alphas_cumprod_prev) * np.sqrt(alphas) / (1. - alphas_cumprod))) + + if self.parameterization == "eps": + lvlb_weights = self.betas ** 2 / ( + 2 * self.posterior_variance * to_torch(alphas) * (1 - self.alphas_cumprod)) + elif self.parameterization == "x0": + lvlb_weights = 0.5 * np.sqrt(torch.Tensor(alphas_cumprod)) / (2. * 1 - torch.Tensor(alphas_cumprod)) + elif self.parameterization == "v": + lvlb_weights = torch.ones_like(self.betas ** 2 / ( + 2 * self.posterior_variance * to_torch(alphas) * (1 - self.alphas_cumprod))) + else: + raise NotImplementedError("mu not supported") + lvlb_weights[0] = lvlb_weights[1] + self.register_buffer('lvlb_weights', lvlb_weights, persistent=False) + assert not torch.isnan(self.lvlb_weights).all() + + @contextmanager + def ema_scope(self, context=None): + if self.use_ema: + self.model_ema.store(self.model.parameters()) + self.model_ema.copy_to(self.model) + if context is not None: + print(f"{context}: Switched to EMA weights") + try: + yield None + finally: + if self.use_ema: + self.model_ema.restore(self.model.parameters()) + if context is not None: + print(f"{context}: Restored training weights") + + @torch.no_grad() + def init_from_ckpt(self, path, ignore_keys=list(), only_model=False): + sd = torch.load(path, map_location="cpu") + if "state_dict" in list(sd.keys()): + sd = sd["state_dict"] + keys = list(sd.keys()) + for k in keys: + for ik in ignore_keys: + if k.startswith(ik): + print("Deleting key {} from state_dict.".format(k)) + del sd[k] + if self.make_it_fit: + n_params = len([name for name, _ in + itertools.chain(self.named_parameters(), + self.named_buffers())]) + for name, param in tqdm( + itertools.chain(self.named_parameters(), + self.named_buffers()), + desc="Fitting old weights to new weights", + total=n_params + ): + if not name in sd: + continue + old_shape = sd[name].shape + new_shape = param.shape + assert len(old_shape) == len(new_shape) + if len(new_shape) > 2: + # we only modify first two axes + assert new_shape[2:] == old_shape[2:] + # assumes first axis corresponds to output dim + if not new_shape == old_shape: + new_param = param.clone() + old_param = sd[name] + if len(new_shape) == 1: + for i in range(new_param.shape[0]): + new_param[i] = old_param[i % old_shape[0]] + elif len(new_shape) >= 2: + for i in range(new_param.shape[0]): + for j in range(new_param.shape[1]): + new_param[i, j] = old_param[i % old_shape[0], j % old_shape[1]] + + n_used_old = torch.ones(old_shape[1]) + for j in range(new_param.shape[1]): + n_used_old[j % old_shape[1]] += 1 + n_used_new = torch.zeros(new_shape[1]) + for j in range(new_param.shape[1]): + n_used_new[j] = n_used_old[j % old_shape[1]] + + n_used_new = n_used_new[None, :] + while len(n_used_new.shape) < len(new_shape): + n_used_new = n_used_new.unsqueeze(-1) + new_param /= n_used_new + + sd[name] = new_param + + missing, unexpected = self.load_state_dict(sd, strict=False) if not only_model else self.model.load_state_dict( + sd, strict=False) + print(f"Restored from {path} with {len(missing)} missing and {len(unexpected)} unexpected keys") + if len(missing) > 0: + print(f"Missing Keys:\n {missing}") + if len(unexpected) > 0: + print(f"\nUnexpected Keys:\n {unexpected}") + + def q_mean_variance(self, x_start, t): + """ + Get the distribution q(x_t | x_0). + :param x_start: the [N x C x ...] tensor of noiseless inputs. + :param t: the number of diffusion steps (minus 1). Here, 0 means one step. + :return: A tuple (mean, variance, log_variance), all of x_start's shape. + """ + mean = (extract_into_tensor(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start) + variance = extract_into_tensor(1.0 - self.alphas_cumprod, t, x_start.shape) + log_variance = extract_into_tensor(self.log_one_minus_alphas_cumprod, t, x_start.shape) + return mean, variance, log_variance + + def predict_start_from_noise(self, x_t, t, noise): + return ( + extract_into_tensor(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t - + extract_into_tensor(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) * noise + ) + + def predict_start_from_z_and_v(self, x_t, t, v): + # self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod))) + # self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod))) + return ( + extract_into_tensor(self.sqrt_alphas_cumprod, t, x_t.shape) * x_t - + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_t.shape) * v + ) + + def predict_eps_from_z_and_v(self, x_t, t, v): + return ( + extract_into_tensor(self.sqrt_alphas_cumprod, t, x_t.shape) * v + + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_t.shape) * x_t + ) + + def q_posterior(self, x_start, x_t, t): + posterior_mean = ( + extract_into_tensor(self.posterior_mean_coef1, t, x_t.shape) * x_start + + extract_into_tensor(self.posterior_mean_coef2, t, x_t.shape) * x_t + ) + posterior_variance = extract_into_tensor(self.posterior_variance, t, x_t.shape) + posterior_log_variance_clipped = extract_into_tensor(self.posterior_log_variance_clipped, t, x_t.shape) + return posterior_mean, posterior_variance, posterior_log_variance_clipped + + def p_mean_variance(self, x, t, clip_denoised: bool): + model_out = self.model(x, t) + if self.parameterization == "eps": + x_recon = self.predict_start_from_noise(x, t=t, noise=model_out) + elif self.parameterization == "x0": + x_recon = model_out + if clip_denoised: + x_recon.clamp_(-1., 1.) + + model_mean, posterior_variance, posterior_log_variance = self.q_posterior(x_start=x_recon, x_t=x, t=t) + return model_mean, posterior_variance, posterior_log_variance + + @torch.no_grad() + def p_sample(self, x, t, clip_denoised=True, repeat_noise=False): + b, *_, device = *x.shape, x.device + model_mean, _, model_log_variance = self.p_mean_variance(x=x, t=t, clip_denoised=clip_denoised) + noise = noise_like(x.shape, device, repeat_noise) + # no noise when t == 0 + nonzero_mask = (1 - (t == 0).float()).reshape(b, *((1,) * (len(x.shape) - 1))) + return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise + + @torch.no_grad() + def p_sample_loop(self, shape, return_intermediates=False): + device = self.betas.device + b = shape[0] + img = torch.randn(shape, device=device) + intermediates = [img] + for i in tqdm(reversed(range(0, self.num_timesteps)), desc='Sampling t', total=self.num_timesteps): + img = self.p_sample(img, torch.full((b,), i, device=device, dtype=torch.long), + clip_denoised=self.clip_denoised) + if i % self.log_every_t == 0 or i == self.num_timesteps - 1: + intermediates.append(img) + if return_intermediates: + return img, intermediates + return img + + @torch.no_grad() + def sample(self, batch_size=16, return_intermediates=False): + image_size = self.image_size + channels = self.channels + return self.p_sample_loop((batch_size, channels, image_size, image_size), + return_intermediates=return_intermediates) + + def q_sample(self, x_start, t, noise=None): + noise = default(noise, lambda: torch.randn_like(x_start)) + return (extract_into_tensor(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start + + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape) * noise) + + def get_v(self, x, noise, t): + return ( + extract_into_tensor(self.sqrt_alphas_cumprod, t, x.shape) * noise - + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x.shape) * x + ) + + def get_loss(self, pred, target, mean=True): + if self.loss_type == 'l1': + loss = (target - pred).abs() + if mean: + loss = loss.mean() + elif self.loss_type == 'l2': + if mean: + loss = torch.nn.functional.mse_loss(target, pred) + else: + loss = torch.nn.functional.mse_loss(target, pred, reduction='none') + else: + raise NotImplementedError("unknown loss type '{loss_type}'") + + return loss + + def p_losses(self, x_start, t, noise=None): + noise = default(noise, lambda: torch.randn_like(x_start)) + x_noisy = self.q_sample(x_start=x_start, t=t, noise=noise) + model_out = self.model(x_noisy, t) + + loss_dict = {} + if self.parameterization == "eps": + target = noise + elif self.parameterization == "x0": + target = x_start + elif self.parameterization == "v": + target = self.get_v(x_start, noise, t) + else: + raise NotImplementedError(f"Parameterization {self.parameterization} not yet supported") + + loss = self.get_loss(model_out, target, mean=False).mean(dim=[1, 2, 3]) + + log_prefix = 'train' if self.training else 'val' + + loss_dict.update({f'{log_prefix}/loss_simple': loss.mean()}) + loss_simple = loss.mean() * self.l_simple_weight + + loss_vlb = (self.lvlb_weights[t] * loss).mean() + loss_dict.update({f'{log_prefix}/loss_vlb': loss_vlb}) + + loss = loss_simple + self.original_elbo_weight * loss_vlb + + loss_dict.update({f'{log_prefix}/loss': loss}) + + return loss, loss_dict + + def forward(self, x, *args, **kwargs): + # b, c, h, w, device, img_size, = *x.shape, x.device, self.image_size + # assert h == img_size and w == img_size, f'height and width of image must be {img_size}' + t = torch.randint(0, self.num_timesteps, (x.shape[0],), device=self.device).long() + return self.p_losses(x, t, *args, **kwargs) + + def get_input(self, batch, k): + x = batch[k] + # if len(x.shape) == 3: + # x = x[..., None] + # x = rearrange(x, 'b h w c -> b c h w') + # x = x.to(memory_format=torch.contiguous_format).float() + return x + + def shared_step(self, batch): + x = self.get_input(batch, self.first_stage_key) + loss, loss_dict = self(x) + return loss, loss_dict + + def training_step(self, batch, batch_idx): + loss, loss_dict = self.shared_step(batch) + + self.log_dict(loss_dict, prog_bar=True, + logger=True, on_step=True, on_epoch=True) + + self.log("global_step", self.global_step, + prog_bar=True, logger=True, on_step=True, on_epoch=False) + + if self.use_scheduler: + lr = self.optimizers().param_groups[0]['lr'] + self.log('lr_abs', lr, prog_bar=True, logger=True, on_step=True, on_epoch=False) + + return loss + + @torch.no_grad() + def validation_step(self, batch, batch_idx): + _, loss_dict_no_ema = self.shared_step(batch) + with self.ema_scope(): + _, loss_dict_ema = self.shared_step(batch) + loss_dict_ema = {key + '_ema': loss_dict_ema[key] for key in loss_dict_ema} + self.log_dict(loss_dict_no_ema, prog_bar=False, logger=True, on_step=False, on_epoch=True) + self.log_dict(loss_dict_ema, prog_bar=False, logger=True, on_step=False, on_epoch=True) + + def on_train_batch_end(self, *args, **kwargs): + if self.use_ema: + self.model_ema(self.model) + + def _get_rows_from_list(self, samples): + n_imgs_per_row = len(samples) + denoise_grid = rearrange(samples, 'n b c h w -> b n c h w') + denoise_grid = rearrange(denoise_grid, 'b n c h w -> (b n) c h w') + denoise_grid = make_grid(denoise_grid, nrow=n_imgs_per_row) + return denoise_grid + + @torch.no_grad() + def log_images(self, batch, N=8, n_row=2, sample=True, return_keys=None, **kwargs): + log = dict() + x = self.get_input(batch, self.first_stage_key) + N = min(x.shape[0], N) + n_row = min(x.shape[0], n_row) + x = x.to(self.device)[:N] + log["inputs"] = x + + # get diffusion row + diffusion_row = list() + x_start = x[:n_row] + + for t in range(self.num_timesteps): + if t % self.log_every_t == 0 or t == self.num_timesteps - 1: + t = repeat(torch.tensor([t]), '1 -> b', b=n_row) + t = t.to(self.device).long() + noise = torch.randn_like(x_start) + x_noisy = self.q_sample(x_start=x_start, t=t, noise=noise) + diffusion_row.append(x_noisy) + + log["diffusion_row"] = self._get_rows_from_list(diffusion_row) + + if sample: + # get denoise row + with self.ema_scope("Plotting"): + samples, denoise_row = self.sample(batch_size=N, return_intermediates=True) + + log["samples"] = samples + log["denoise_row"] = self._get_rows_from_list(denoise_row) + + if return_keys: + if np.intersect1d(list(log.keys()), return_keys).shape[0] == 0: + return log + else: + return {key: log[key] for key in return_keys} + return log + + def configure_optimizers(self): + lr = self.learning_rate + params = list(self.model.parameters()) + if self.learn_logvar: + params = params + [self.logvar] + opt = torch.optim.AdamW(params, lr=lr) + return opt + + +class LatentDiffusion(DDPM): + """main class""" + + def __init__(self, + first_stage_config, + cond_stage_config, + num_timesteps_cond=None, + cond_stage_key="image", + cond_stage_trainable=False, + concat_mode=True, + cond_stage_forward=None, + conditioning_key=None, + scale_factor=1.0, + scale_by_std=False, + *args, **kwargs): + self.num_timesteps_cond = default(num_timesteps_cond, 1) + self.scale_by_std = scale_by_std + assert self.num_timesteps_cond <= kwargs['timesteps'] + # for backwards compatibility after implementation of DiffusionWrapper + if conditioning_key is None: + conditioning_key = 'concat' if concat_mode else 'crossattn' + if cond_stage_config == '__is_unconditional__': + conditioning_key = None + ckpt_path = kwargs.pop("ckpt_path", None) + reset_ema = kwargs.pop("reset_ema", False) + reset_num_ema_updates = kwargs.pop("reset_num_ema_updates", False) + ignore_keys = kwargs.pop("ignore_keys", []) + super().__init__(conditioning_key=conditioning_key, *args, **kwargs) + self.concat_mode = concat_mode + self.cond_stage_trainable = cond_stage_trainable + self.cond_stage_key = cond_stage_key + try: + self.num_downs = len(first_stage_config.params.ddconfig.ch_mult) - 1 + except: + self.num_downs = 0 + if not scale_by_std: + self.scale_factor = scale_factor + else: + self.register_buffer('scale_factor', torch.tensor(scale_factor)) + self.instantiate_first_stage(first_stage_config) + self.instantiate_cond_stage(cond_stage_config) + self.cond_stage_forward = cond_stage_forward + self.clip_denoised = False + self.bbox_tokenizer = None + + self.restarted_from_ckpt = False + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys) + self.restarted_from_ckpt = True + if reset_ema: + assert self.use_ema + print( + f"Resetting ema to pure model weights. This is useful when restoring from an ema-only checkpoint.") + self.model_ema = LitEma(self.model) + if reset_num_ema_updates: + print(" +++++++++++ WARNING: RESETTING NUM_EMA UPDATES TO ZERO +++++++++++ ") + assert self.use_ema + self.model_ema.reset_num_updates() + + def make_cond_schedule(self, ): + self.cond_ids = torch.full(size=(self.num_timesteps,), fill_value=self.num_timesteps - 1, dtype=torch.long) + ids = torch.round(torch.linspace(0, self.num_timesteps - 1, self.num_timesteps_cond)).long() + self.cond_ids[:self.num_timesteps_cond] = ids + + def register_schedule(self, + given_betas=None, beta_schedule="linear", timesteps=1000, + linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3): + super().register_schedule(given_betas, beta_schedule, timesteps, linear_start, linear_end, cosine_s) + + self.shorten_cond_schedule = self.num_timesteps_cond > 1 + if self.shorten_cond_schedule: + self.make_cond_schedule() + + def instantiate_first_stage(self, config): + model = instantiate_from_config(config) + self.first_stage_model = model.eval() + self.first_stage_model.train = disabled_train + for param in self.first_stage_model.parameters(): + param.requires_grad = False + + def instantiate_cond_stage(self, config): + if not self.cond_stage_trainable: + if config == "__is_first_stage__": + print("Using first stage also as cond stage.") + self.cond_stage_model = self.first_stage_model + elif config == "__is_unconditional__": + print(f"Training {self.__class__.__name__} as an unconditional model.") + self.cond_stage_model = None + # self.be_unconditional = True + else: + model = instantiate_from_config(config) + self.cond_stage_model = model.eval() + self.cond_stage_model.train = disabled_train + for param in self.cond_stage_model.parameters(): + param.requires_grad = False + else: + assert config != '__is_first_stage__' + assert config != '__is_unconditional__' + model = instantiate_from_config(config) + self.cond_stage_model = model + + def _get_denoise_row_from_list(self, samples, desc='', force_no_decoder_quantization=False): + denoise_row = [] + for zd in tqdm(samples, desc=desc): + denoise_row.append(self.decode_first_stage(zd.to(self.device), + force_not_quantize=force_no_decoder_quantization)) + n_imgs_per_row = len(denoise_row) + denoise_row = torch.stack(denoise_row) # n_log_step, n_row, C, H, W + denoise_grid = rearrange(denoise_row, 'n b c h w -> b n c h w') + denoise_grid = rearrange(denoise_grid, 'b n c h w -> (b n) c h w') + denoise_grid = make_grid(denoise_grid, nrow=n_imgs_per_row) + return denoise_grid + + def get_first_stage_encoding(self, encoder_posterior): + if isinstance(encoder_posterior, DiagonalGaussianDistribution): + z = encoder_posterior.sample() + elif isinstance(encoder_posterior, torch.Tensor): + z = encoder_posterior + else: + raise NotImplementedError(f"encoder_posterior of type '{type(encoder_posterior)}' not yet implemented") + return self.scale_factor * z + + def get_learned_conditioning(self, c): + if self.cond_stage_forward is None: + if hasattr(self.cond_stage_model, 'encode') and callable(self.cond_stage_model.encode): + c = self.cond_stage_model.encode(c) + if isinstance(c, DiagonalGaussianDistribution): + c = c.mode() + else: + c = self.cond_stage_model(c) + else: + assert hasattr(self.cond_stage_model, self.cond_stage_forward) + c = getattr(self.cond_stage_model, self.cond_stage_forward)(c) + return c + + def meshgrid(self, h, w): + y = torch.arange(0, h).view(h, 1, 1).repeat(1, w, 1) + x = torch.arange(0, w).view(1, w, 1).repeat(h, 1, 1) + + arr = torch.cat([y, x], dim=-1) + return arr + + def delta_border(self, h, w): + """ + :param h: height + :param w: width + :return: normalized distance to image border, + wtith min distance = 0 at border and max dist = 0.5 at image center + """ + lower_right_corner = torch.tensor([h - 1, w - 1]).view(1, 1, 2) + arr = self.meshgrid(h, w) / lower_right_corner + dist_left_up = torch.min(arr, dim=-1, keepdims=True)[0] + dist_right_down = torch.min(1 - arr, dim=-1, keepdims=True)[0] + edge_dist = torch.min(torch.cat([dist_left_up, dist_right_down], dim=-1), dim=-1)[0] + return edge_dist + + def get_weighting(self, h, w, Ly, Lx, device): + weighting = self.delta_border(h, w) + weighting = torch.clip(weighting, self.split_input_params["clip_min_weight"], + self.split_input_params["clip_max_weight"], ) + weighting = weighting.view(1, h * w, 1).repeat(1, 1, Ly * Lx).to(device) + + if self.split_input_params["tie_braker"]: + L_weighting = self.delta_border(Ly, Lx) + L_weighting = torch.clip(L_weighting, + self.split_input_params["clip_min_tie_weight"], + self.split_input_params["clip_max_tie_weight"]) + + L_weighting = L_weighting.view(1, 1, Ly * Lx).to(device) + weighting = weighting * L_weighting + return weighting + + def get_fold_unfold(self, x, kernel_size, stride, uf=1, df=1): # todo load once not every time, shorten code + """ + :param x: img of size (bs, c, h, w) + :return: n img crops of size (n, bs, c, kernel_size[0], kernel_size[1]) + """ + bs, nc, h, w = x.shape + + # number of crops in image + Ly = (h - kernel_size[0]) // stride[0] + 1 + Lx = (w - kernel_size[1]) // stride[1] + 1 + + if uf == 1 and df == 1: + fold_params = dict(kernel_size=kernel_size, dilation=1, padding=0, stride=stride) + unfold = torch.nn.Unfold(**fold_params) + + fold = torch.nn.Fold(output_size=x.shape[2:], **fold_params) + + weighting = self.get_weighting(kernel_size[0], kernel_size[1], Ly, Lx, x.device).to(x.dtype) + normalization = fold(weighting).view(1, 1, h, w) # normalizes the overlap + weighting = weighting.view((1, 1, kernel_size[0], kernel_size[1], Ly * Lx)) + + elif uf > 1 and df == 1: + fold_params = dict(kernel_size=kernel_size, dilation=1, padding=0, stride=stride) + unfold = torch.nn.Unfold(**fold_params) + + fold_params2 = dict(kernel_size=(kernel_size[0] * uf, kernel_size[0] * uf), + dilation=1, padding=0, + stride=(stride[0] * uf, stride[1] * uf)) + fold = torch.nn.Fold(output_size=(x.shape[2] * uf, x.shape[3] * uf), **fold_params2) + + weighting = self.get_weighting(kernel_size[0] * uf, kernel_size[1] * uf, Ly, Lx, x.device).to(x.dtype) + normalization = fold(weighting).view(1, 1, h * uf, w * uf) # normalizes the overlap + weighting = weighting.view((1, 1, kernel_size[0] * uf, kernel_size[1] * uf, Ly * Lx)) + + elif df > 1 and uf == 1: + fold_params = dict(kernel_size=kernel_size, dilation=1, padding=0, stride=stride) + unfold = torch.nn.Unfold(**fold_params) + + fold_params2 = dict(kernel_size=(kernel_size[0] // df, kernel_size[0] // df), + dilation=1, padding=0, + stride=(stride[0] // df, stride[1] // df)) + fold = torch.nn.Fold(output_size=(x.shape[2] // df, x.shape[3] // df), **fold_params2) + + weighting = self.get_weighting(kernel_size[0] // df, kernel_size[1] // df, Ly, Lx, x.device).to(x.dtype) + normalization = fold(weighting).view(1, 1, h // df, w // df) # normalizes the overlap + weighting = weighting.view((1, 1, kernel_size[0] // df, kernel_size[1] // df, Ly * Lx)) + + else: + raise NotImplementedError + + return fold, unfold, normalization, weighting + + @torch.no_grad() + def get_input(self, batch, k, return_first_stage_outputs=False, force_c_encode=False, + cond_key=None, return_original_cond=False, bs=None): + x = super().get_input(batch, k) + if bs is not None: + x = x[:bs] + x = x.to(self.device) + encoder_posterior = self.encode_first_stage(x) + z = self.get_first_stage_encoding(encoder_posterior).detach() + + if self.model.conditioning_key is not None: + if cond_key is None: + cond_key = self.cond_stage_key + if cond_key != self.first_stage_key: + if cond_key in ['caption', 'coordinates_bbox', "txt"]: + xc = batch[cond_key] + elif cond_key in ['class_label', 'cls']: + xc = batch + else: + xc = super().get_input(batch, cond_key).to(self.device) + else: + xc = x + if not self.cond_stage_trainable or force_c_encode: + if isinstance(xc, dict) or isinstance(xc, list): + # import pudb; pudb.set_trace() + c = self.get_learned_conditioning(xc) + else: + c = self.get_learned_conditioning(xc.to(self.device)) + else: + c = xc + if bs is not None: + c = c[:bs] + + if self.use_positional_encodings: + pos_x, pos_y = self.compute_latent_shifts(batch) + ckey = __conditioning_keys__[self.model.conditioning_key] + c = {ckey: c, 'pos_x': pos_x, 'pos_y': pos_y} + + else: + c = None + xc = None + if self.use_positional_encodings: + pos_x, pos_y = self.compute_latent_shifts(batch) + c = {'pos_x': pos_x, 'pos_y': pos_y} + out = [z, c] + if return_first_stage_outputs: + xrec = self.decode_first_stage(z) + out.extend([x, xrec]) + if return_original_cond: + out.append(xc) + return out + + @torch.no_grad() + def decode_first_stage(self, z, predict_cids=False, force_not_quantize=False): + if predict_cids: + if z.dim() == 4: + z = torch.argmax(z.exp(), dim=1).long() + z = self.first_stage_model.quantize.get_codebook_entry(z, shape=None) + z = rearrange(z, 'b h w c -> b c h w').contiguous() + + z = 1. / self.scale_factor * z + return self.first_stage_model.decode(z) + + @torch.no_grad() + def encode_first_stage(self, x): + return self.first_stage_model.encode(x) + + def shared_step(self, batch, **kwargs): + x, c = self.get_input(batch, self.first_stage_key) + loss = self(x, c, **kwargs) + return loss + + def get_time_with_schedule(self, scheduler, bs): + if scheduler == 'linear': + t = torch.randint(0, self.num_timesteps, (bs,), device=self.device).long() + elif scheduler == 'cosine': + t = torch.rand((bs, ), device=self.device) + t = torch.cos(torch.pi / 2. * t) * self.num_timesteps + t = t.long() + elif scheduler == 'cubic': + t = torch.rand((bs,), device=self.device) + t = (1 - t ** 3) * self.num_timesteps + t = t.long() + else: + raise NotImplementedError + t = torch.clamp(t, min=0, max=self.num_timesteps-1) + return t + + def forward(self, x, c, *args, **kwargs): + if 't' not in kwargs: + t = torch.randint(0, self.num_timesteps, (x.shape[0], ), device=self.device).long() + else: + t = kwargs.pop('t') + + return self.p_losses(x, c, t, *args, **kwargs) + + def apply_model(self, x_noisy, t, cond, return_ids=False, **kwargs): + if isinstance(cond, dict): + # hybrid case, cond is expected to be a dict + pass + else: + if not isinstance(cond, list): + cond = [cond] + key = 'c_concat' if self.model.conditioning_key == 'concat' else 'c_crossattn' + cond = {key: cond} + + x_recon = self.model(x_noisy, t, **cond, **kwargs) + + if isinstance(x_recon, tuple) and not return_ids: + return x_recon[0] + else: + return x_recon + + def _predict_eps_from_xstart(self, x_t, t, pred_xstart): + return (extract_into_tensor(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t - pred_xstart) / \ + extract_into_tensor(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) + + def _prior_bpd(self, x_start): + """ + Get the prior KL term for the variational lower-bound, measured in + bits-per-dim. + This term can't be optimized, as it only depends on the encoder. + :param x_start: the [N x C x ...] tensor of inputs. + :return: a batch of [N] KL values (in bits), one per batch element. + """ + batch_size = x_start.shape[0] + t = torch.tensor([self.num_timesteps - 1] * batch_size, device=x_start.device) + qt_mean, _, qt_log_variance = self.q_mean_variance(x_start, t) + kl_prior = normal_kl(mean1=qt_mean, logvar1=qt_log_variance, mean2=0.0, logvar2=0.0) + return mean_flat(kl_prior) / np.log(2.0) + + def p_losses(self, x_start, cond, t, noise=None, **kwargs): + noise = default(noise, lambda: torch.randn_like(x_start)) + x_noisy = self.q_sample(x_start=x_start, t=t, noise=noise) + model_output = self.apply_model(x_noisy, t, cond, **kwargs) + + loss_dict = {} + prefix = 'train' if self.training else 'val' + + if self.parameterization == "x0": + target = x_start + elif self.parameterization == "eps": + target = noise + elif self.parameterization == "v": + target = self.get_v(x_start, noise, t) + else: + raise NotImplementedError() + + loss_simple = self.get_loss(model_output, target, mean=False).mean([1, 2, 3]) + loss_dict.update({f'{prefix}/loss_simple': loss_simple.mean()}) + + logvar_t = self.logvar[t].to(self.device) + loss = loss_simple / torch.exp(logvar_t) + logvar_t + # loss = loss_simple / torch.exp(self.logvar) + self.logvar + if self.learn_logvar: + loss_dict.update({f'{prefix}/loss_gamma': loss.mean()}) + loss_dict.update({'logvar': self.logvar.data.mean()}) + + loss = self.l_simple_weight * loss.mean() + + loss_vlb = self.get_loss(model_output, target, mean=False).mean(dim=(1, 2, 3)) + loss_vlb = (self.lvlb_weights[t] * loss_vlb).mean() + loss_dict.update({f'{prefix}/loss_vlb': loss_vlb}) + loss += (self.original_elbo_weight * loss_vlb) + loss_dict.update({f'{prefix}/loss': loss}) + + return loss, loss_dict + + def p_mean_variance(self, x, c, t, clip_denoised: bool, return_codebook_ids=False, quantize_denoised=False, + return_x0=False, score_corrector=None, corrector_kwargs=None): + t_in = t + model_out = self.apply_model(x, t_in, c, return_ids=return_codebook_ids) + + if score_corrector is not None: + assert self.parameterization == "eps" + model_out = score_corrector.modify_score(self, model_out, x, t, c, **corrector_kwargs) + + if return_codebook_ids: + model_out, logits = model_out + + if self.parameterization == "eps": + x_recon = self.predict_start_from_noise(x, t=t, noise=model_out) + elif self.parameterization == "x0": + x_recon = model_out + else: + raise NotImplementedError() + + if clip_denoised: + x_recon.clamp_(-1., 1.) + if quantize_denoised: + x_recon, _, [_, _, indices] = self.first_stage_model.quantize(x_recon) + model_mean, posterior_variance, posterior_log_variance = self.q_posterior(x_start=x_recon, x_t=x, t=t) + if return_codebook_ids: + return model_mean, posterior_variance, posterior_log_variance, logits + elif return_x0: + return model_mean, posterior_variance, posterior_log_variance, x_recon + else: + return model_mean, posterior_variance, posterior_log_variance + + @torch.no_grad() + def p_sample(self, x, c, t, clip_denoised=False, repeat_noise=False, + return_codebook_ids=False, quantize_denoised=False, return_x0=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None): + b, *_, device = *x.shape, x.device + outputs = self.p_mean_variance(x=x, c=c, t=t, clip_denoised=clip_denoised, + return_codebook_ids=return_codebook_ids, + quantize_denoised=quantize_denoised, + return_x0=return_x0, + score_corrector=score_corrector, corrector_kwargs=corrector_kwargs) + if return_codebook_ids: + raise DeprecationWarning("Support dropped.") + model_mean, _, model_log_variance, logits = outputs + elif return_x0: + model_mean, _, model_log_variance, x0 = outputs + else: + model_mean, _, model_log_variance = outputs + + noise = noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + # no noise when t == 0 + nonzero_mask = (1 - (t == 0).float()).reshape(b, *((1,) * (len(x.shape) - 1))) + + if return_codebook_ids: + return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise, logits.argmax(dim=1) + if return_x0: + return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise, x0 + else: + return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise + + @torch.no_grad() + def progressive_denoising(self, cond, shape, verbose=True, callback=None, quantize_denoised=False, + img_callback=None, mask=None, x0=None, temperature=1., noise_dropout=0., + score_corrector=None, corrector_kwargs=None, batch_size=None, x_T=None, start_T=None, + log_every_t=None): + if not log_every_t: + log_every_t = self.log_every_t + timesteps = self.num_timesteps + if batch_size is not None: + b = batch_size if batch_size is not None else shape[0] + shape = [batch_size] + list(shape) + else: + b = batch_size = shape[0] + if x_T is None: + img = torch.randn(shape, device=self.device) + else: + img = x_T + intermediates = [] + if cond is not None: + if isinstance(cond, dict): + cond = {key: cond[key][:batch_size] if not isinstance(cond[key], list) else + list(map(lambda x: x[:batch_size], cond[key])) for key in cond} + else: + cond = [c[:batch_size] for c in cond] if isinstance(cond, list) else cond[:batch_size] + + if start_T is not None: + timesteps = min(timesteps, start_T) + iterator = tqdm(reversed(range(0, timesteps)), desc='Progressive Generation', + total=timesteps) if verbose else reversed( + range(0, timesteps)) + if type(temperature) == float: + temperature = [temperature] * timesteps + + for i in iterator: + ts = torch.full((b,), i, device=self.device, dtype=torch.long) + if self.shorten_cond_schedule: + assert self.model.conditioning_key != 'hybrid' + tc = self.cond_ids[ts].to(cond.device) + cond = self.q_sample(x_start=cond, t=tc, noise=torch.randn_like(cond)) + + img, x0_partial = self.p_sample(img, cond, ts, + clip_denoised=self.clip_denoised, + quantize_denoised=quantize_denoised, return_x0=True, + temperature=temperature[i], noise_dropout=noise_dropout, + score_corrector=score_corrector, corrector_kwargs=corrector_kwargs) + if mask is not None: + assert x0 is not None + img_orig = self.q_sample(x0, ts) + img = img_orig * mask + (1. - mask) * img + + if i % log_every_t == 0 or i == timesteps - 1: + intermediates.append(x0_partial) + if callback: callback(i) + if img_callback: img_callback(img, i) + return img, intermediates + + @torch.no_grad() + def p_sample_loop(self, cond, shape, return_intermediates=False, + x_T=None, verbose=True, callback=None, timesteps=None, quantize_denoised=False, + mask=None, x0=None, img_callback=None, start_T=None, + log_every_t=None): + + if not log_every_t: + log_every_t = self.log_every_t + device = self.betas.device + b = shape[0] + if x_T is None: + img = torch.randn(shape, device=device) + else: + img = x_T + + intermediates = [img] + if timesteps is None: + timesteps = self.num_timesteps + + if start_T is not None: + timesteps = min(timesteps, start_T) + iterator = tqdm(reversed(range(0, timesteps)), desc='Sampling t', total=timesteps) if verbose else reversed( + range(0, timesteps)) + + if mask is not None: + assert x0 is not None + assert x0.shape[2:3] == mask.shape[2:3] # spatial size has to match + + for i in iterator: + ts = torch.full((b,), i, device=device, dtype=torch.long) + if self.shorten_cond_schedule: + assert self.model.conditioning_key != 'hybrid' + tc = self.cond_ids[ts].to(cond.device) + cond = self.q_sample(x_start=cond, t=tc, noise=torch.randn_like(cond)) + + img = self.p_sample(img, cond, ts, + clip_denoised=self.clip_denoised, + quantize_denoised=quantize_denoised) + if mask is not None: + img_orig = self.q_sample(x0, ts) + img = img_orig * mask + (1. - mask) * img + + if i % log_every_t == 0 or i == timesteps - 1: + intermediates.append(img) + if callback: callback(i) + if img_callback: img_callback(img, i) + + if return_intermediates: + return img, intermediates + return img + + @torch.no_grad() + def sample(self, cond, batch_size=16, return_intermediates=False, x_T=None, + verbose=True, timesteps=None, quantize_denoised=False, + mask=None, x0=None, shape=None, **kwargs): + if shape is None: + shape = (batch_size, self.channels, self.image_size, self.image_size) + if cond is not None: + if isinstance(cond, dict): + cond = {key: cond[key][:batch_size] if not isinstance(cond[key], list) else + list(map(lambda x: x[:batch_size], cond[key])) for key in cond} + else: + cond = [c[:batch_size] for c in cond] if isinstance(cond, list) else cond[:batch_size] + return self.p_sample_loop(cond, + shape, + return_intermediates=return_intermediates, x_T=x_T, + verbose=verbose, timesteps=timesteps, quantize_denoised=quantize_denoised, + mask=mask, x0=x0) + + @torch.no_grad() + def sample_log(self, cond, batch_size, ddim, ddim_steps, **kwargs): + if ddim: + ddim_sampler = DDIMSampler(self) + shape = (self.channels, self.image_size, self.image_size) + samples, intermediates = ddim_sampler.sample(ddim_steps, batch_size, + shape, cond, verbose=False, **kwargs) + + else: + samples, intermediates = self.sample(cond=cond, batch_size=batch_size, + return_intermediates=True, **kwargs) + + return samples, intermediates + + @torch.no_grad() + def get_unconditional_conditioning(self, batch_size, null_label=None): + if null_label is not None: + xc = null_label + if isinstance(xc, ListConfig): + xc = list(xc) + if isinstance(xc, dict) or isinstance(xc, list): + c = self.get_learned_conditioning(xc) + else: + if hasattr(xc, "to"): + xc = xc.to(self.device) + c = self.get_learned_conditioning(xc) + else: + if self.cond_stage_key in ["class_label", "cls"]: + xc = self.cond_stage_model.get_unconditional_conditioning(batch_size, device=self.device) + return self.get_learned_conditioning(xc) + else: + raise NotImplementedError("todo") + if isinstance(c, list): # in case the encoder gives us a list + for i in range(len(c)): + c[i] = repeat(c[i], '1 ... -> b ...', b=batch_size).to(self.device) + else: + c = repeat(c, '1 ... -> b ...', b=batch_size).to(self.device) + return c + + @torch.no_grad() + def log_images(self, batch, N=8, n_row=4, sample=True, ddim_steps=50, ddim_eta=0., return_keys=None, + quantize_denoised=True, inpaint=True, plot_denoise_rows=False, plot_progressive_rows=True, + plot_diffusion_rows=True, unconditional_guidance_scale=1., unconditional_guidance_label=None, + use_ema_scope=True, + **kwargs): + ema_scope = self.ema_scope if use_ema_scope else nullcontext + use_ddim = ddim_steps is not None + + log = dict() + z, c, x, xrec, xc = self.get_input(batch, self.first_stage_key, + return_first_stage_outputs=True, + force_c_encode=True, + return_original_cond=True, + bs=N) + N = min(x.shape[0], N) + n_row = min(x.shape[0], n_row) + log["inputs"] = x + log["reconstruction"] = xrec + if self.model.conditioning_key is not None: + if hasattr(self.cond_stage_model, "decode"): + xc = self.cond_stage_model.decode(c) + log["conditioning"] = xc + elif self.cond_stage_key in ["caption", "txt"]: + xc = log_txt_as_img((x.shape[2], x.shape[3]), batch[self.cond_stage_key], size=x.shape[2] // 25) + log["conditioning"] = xc + elif self.cond_stage_key in ['class_label', "cls"]: + try: + xc = log_txt_as_img((x.shape[2], x.shape[3]), batch["human_label"], size=x.shape[2] // 25) + log['conditioning'] = xc + except KeyError: + # probably no "human_label" in batch + pass + elif isimage(xc): + log["conditioning"] = xc + if ismap(xc): + log["original_conditioning"] = self.to_rgb(xc) + + if plot_diffusion_rows: + # get diffusion row + diffusion_row = list() + z_start = z[:n_row] + for t in range(self.num_timesteps): + if t % self.log_every_t == 0 or t == self.num_timesteps - 1: + t = repeat(torch.tensor([t]), '1 -> b', b=n_row) + t = t.to(self.device).long() + noise = torch.randn_like(z_start) + z_noisy = self.q_sample(x_start=z_start, t=t, noise=noise) + diffusion_row.append(self.decode_first_stage(z_noisy)) + + diffusion_row = torch.stack(diffusion_row) # n_log_step, n_row, C, H, W + diffusion_grid = rearrange(diffusion_row, 'n b c h w -> b n c h w') + diffusion_grid = rearrange(diffusion_grid, 'b n c h w -> (b n) c h w') + diffusion_grid = make_grid(diffusion_grid, nrow=diffusion_row.shape[0]) + log["diffusion_row"] = diffusion_grid + + if sample: + # get denoise row + with ema_scope("Sampling"): + samples, z_denoise_row = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta) + # samples, z_denoise_row = self.sample(cond=c, batch_size=N, return_intermediates=True) + x_samples = self.decode_first_stage(samples) + log["samples"] = x_samples + if plot_denoise_rows: + denoise_grid = self._get_denoise_row_from_list(z_denoise_row) + log["denoise_row"] = denoise_grid + + if quantize_denoised and not isinstance(self.first_stage_model, AutoencoderKL) and not isinstance( + self.first_stage_model, IdentityFirstStage): + # also display when quantizing x0 while sampling + with ema_scope("Plotting Quantized Denoised"): + samples, z_denoise_row = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta, + quantize_denoised=True) + # samples, z_denoise_row = self.sample(cond=c, batch_size=N, return_intermediates=True, + # quantize_denoised=True) + x_samples = self.decode_first_stage(samples.to(self.device)) + log["samples_x0_quantized"] = x_samples + + if unconditional_guidance_scale > 1.0: + uc = self.get_unconditional_conditioning(N, unconditional_guidance_label) + if self.model.conditioning_key == "crossattn-adm": + uc = {"c_crossattn": [uc], "c_adm": c["c_adm"]} + with ema_scope("Sampling with classifier-free guidance"): + samples_cfg, _ = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=uc, + ) + x_samples_cfg = self.decode_first_stage(samples_cfg) + log[f"samples_cfg_scale_{unconditional_guidance_scale:.2f}"] = x_samples_cfg + + if inpaint: + # make a simple center square + b, h, w = z.shape[0], z.shape[2], z.shape[3] + mask = torch.ones(N, h, w).to(self.device) + # zeros will be filled in + mask[:, h // 4:3 * h // 4, w // 4:3 * w // 4] = 0. + mask = mask[:, None, ...] + with ema_scope("Plotting Inpaint"): + samples, _ = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, eta=ddim_eta, + ddim_steps=ddim_steps, x0=z[:N], mask=mask) + x_samples = self.decode_first_stage(samples.to(self.device)) + log["samples_inpainting"] = x_samples + log["mask"] = mask + + # outpaint + mask = 1. - mask + with ema_scope("Plotting Outpaint"): + samples, _ = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, eta=ddim_eta, + ddim_steps=ddim_steps, x0=z[:N], mask=mask) + x_samples = self.decode_first_stage(samples.to(self.device)) + log["samples_outpainting"] = x_samples + + if plot_progressive_rows: + with ema_scope("Plotting Progressives"): + img, progressives = self.progressive_denoising(c, + shape=(self.channels, self.image_size, self.image_size), + batch_size=N) + prog_row = self._get_denoise_row_from_list(progressives, desc="Progressive Generation") + log["progressive_row"] = prog_row + + if return_keys: + if np.intersect1d(list(log.keys()), return_keys).shape[0] == 0: + return log + else: + return {key: log[key] for key in return_keys} + return log + + def configure_optimizers(self): + lr = self.learning_rate + params = list(self.model.parameters()) + if self.cond_stage_trainable: + print(f"{self.__class__.__name__}: Also optimizing conditioner params!") + params = params + list(self.cond_stage_model.parameters()) + if self.learn_logvar: + print('Diffusion model optimizing logvar') + params.append(self.logvar) + opt = torch.optim.AdamW(params, lr=lr) + if self.use_scheduler: + assert 'target' in self.scheduler_config + scheduler = instantiate_from_config(self.scheduler_config) + + print("Setting up LambdaLR scheduler...") + scheduler = [ + { + 'scheduler': LambdaLR(opt, lr_lambda=scheduler.schedule), + 'interval': 'step', + 'frequency': 1 + }] + return [opt], scheduler + return opt + + @torch.no_grad() + def to_rgb(self, x): + x = x.float() + if not hasattr(self, "colorize"): + self.colorize = torch.randn(3, x.shape[1], 1, 1).to(x) + x = nn.functional.conv2d(x, weight=self.colorize) + x = 2. * (x - x.min()) / (x.max() - x.min()) - 1. + return x + + +class DiffusionWrapper(pl.LightningModule): + def __init__(self, diff_model_config, conditioning_key): + super().__init__() + self.diffusion_model = instantiate_from_config(diff_model_config) + self.conditioning_key = conditioning_key + assert self.conditioning_key in [None, 'concat', 'crossattn', 'hybrid', 'adm', 'hybrid-adm', 'crossattn-adm'] + + def forward(self, x, t, c_concat: list = None, c_crossattn: list = None, c_adm=None, **kwargs): + if self.conditioning_key is None: + out = self.diffusion_model(x, t, **kwargs) + elif self.conditioning_key == 'concat': + xc = torch.cat([x] + c_concat, dim=1) + out = self.diffusion_model(xc, t, **kwargs) + elif self.conditioning_key == 'crossattn': + cc = torch.cat(c_crossattn, 1) + out = self.diffusion_model(x, t, context=cc, **kwargs) + elif self.conditioning_key == 'hybrid': + xc = torch.cat([x] + c_concat, dim=1) + cc = torch.cat(c_crossattn, 1) + out = self.diffusion_model(xc, t, context=cc, **kwargs) + elif self.conditioning_key == 'hybrid-adm': + assert c_adm is not None + xc = torch.cat([x] + c_concat, dim=1) + cc = torch.cat(c_crossattn, 1) + out = self.diffusion_model(xc, t, context=cc, y=c_adm, **kwargs) + elif self.conditioning_key == 'crossattn-adm': + assert c_adm is not None + cc = torch.cat(c_crossattn, 1) + out = self.diffusion_model(x, t, context=cc, y=c_adm, **kwargs) + elif self.conditioning_key == 'adm': + cc = c_crossattn[0] + out = self.diffusion_model(x, t, y=cc, **kwargs) + else: + raise NotImplementedError() + + return out diff --git a/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/__init__.py b/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/__init__.py new file mode 100644 index 0000000..7427f38 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/__init__.py @@ -0,0 +1 @@ +from .sampler import DPMSolverSampler \ No newline at end of file diff --git a/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/dpm_solver.py b/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/dpm_solver.py new file mode 100644 index 0000000..23ebfeb --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/dpm_solver.py @@ -0,0 +1,1217 @@ +import torch +import torch.nn.functional as F +import math +from tqdm import tqdm + + +class NoiseScheduleVP: + def __init__( + self, + schedule='discrete', + betas=None, + alphas_cumprod=None, + continuous_beta_0=0.1, + continuous_beta_1=20., + ): + """Create a wrapper class for the forward SDE (VP type). + + *** + Update: We support discrete-time diffusion models by implementing a picewise linear interpolation for log_alpha_t. + We recommend to use schedule='discrete' for the discrete-time diffusion models, especially for high-resolution images. + *** + + The forward SDE ensures that the condition distribution q_{t|0}(x_t | x_0) = N ( alpha_t * x_0, sigma_t^2 * I ). + We further define lambda_t = log(alpha_t) - log(sigma_t), which is the half-logSNR (described in the DPM-Solver paper). + Therefore, we implement the functions for computing alpha_t, sigma_t and lambda_t. For t in [0, T], we have: + + log_alpha_t = self.marginal_log_mean_coeff(t) + sigma_t = self.marginal_std(t) + lambda_t = self.marginal_lambda(t) + + Moreover, as lambda(t) is an invertible function, we also support its inverse function: + + t = self.inverse_lambda(lambda_t) + + =============================================================== + + We support both discrete-time DPMs (trained on n = 0, 1, ..., N-1) and continuous-time DPMs (trained on t in [t_0, T]). + + 1. For discrete-time DPMs: + + For discrete-time DPMs trained on n = 0, 1, ..., N-1, we convert the discrete steps to continuous time steps by: + t_i = (i + 1) / N + e.g. for N = 1000, we have t_0 = 1e-3 and T = t_{N-1} = 1. + We solve the corresponding diffusion ODE from time T = 1 to time t_0 = 1e-3. + + Args: + betas: A `torch.Tensor`. The beta array for the discrete-time DPM. (See the original DDPM paper for details) + alphas_cumprod: A `torch.Tensor`. The cumprod alphas for the discrete-time DPM. (See the original DDPM paper for details) + + Note that we always have alphas_cumprod = cumprod(betas). Therefore, we only need to set one of `betas` and `alphas_cumprod`. + + **Important**: Please pay special attention for the args for `alphas_cumprod`: + The `alphas_cumprod` is the \hat{alpha_n} arrays in the notations of DDPM. Specifically, DDPMs assume that + q_{t_n | 0}(x_{t_n} | x_0) = N ( \sqrt{\hat{alpha_n}} * x_0, (1 - \hat{alpha_n}) * I ). + Therefore, the notation \hat{alpha_n} is different from the notation alpha_t in DPM-Solver. In fact, we have + alpha_{t_n} = \sqrt{\hat{alpha_n}}, + and + log(alpha_{t_n}) = 0.5 * log(\hat{alpha_n}). + + + 2. For continuous-time DPMs: + + We support two types of VPSDEs: linear (DDPM) and cosine (improved-DDPM). The hyperparameters for the noise + schedule are the default settings in DDPM and improved-DDPM: + + Args: + beta_min: A `float` number. The smallest beta for the linear schedule. + beta_max: A `float` number. The largest beta for the linear schedule. + cosine_s: A `float` number. The hyperparameter in the cosine schedule. + cosine_beta_max: A `float` number. The hyperparameter in the cosine schedule. + T: A `float` number. The ending time of the forward process. + + =============================================================== + + Args: + schedule: A `str`. The noise schedule of the forward SDE. 'discrete' for discrete-time DPMs, + 'linear' or 'cosine' for continuous-time DPMs. + Returns: + A wrapper object of the forward SDE (VP type). + + =============================================================== + + Example: + + # For discrete-time DPMs, given betas (the beta array for n = 0, 1, ..., N - 1): + >>> ns = NoiseScheduleVP('discrete', betas=betas) + + # For discrete-time DPMs, given alphas_cumprod (the \hat{alpha_n} array for n = 0, 1, ..., N - 1): + >>> ns = NoiseScheduleVP('discrete', alphas_cumprod=alphas_cumprod) + + # For continuous-time DPMs (VPSDE), linear schedule: + >>> ns = NoiseScheduleVP('linear', continuous_beta_0=0.1, continuous_beta_1=20.) + + """ + + if schedule not in ['discrete', 'linear', 'cosine']: + raise ValueError( + "Unsupported noise schedule {}. The schedule needs to be 'discrete' or 'linear' or 'cosine'".format( + schedule)) + + self.schedule = schedule + if schedule == 'discrete': + if betas is not None: + log_alphas = 0.5 * torch.log(1 - betas).cumsum(dim=0) + else: + assert alphas_cumprod is not None + log_alphas = 0.5 * torch.log(alphas_cumprod) + self.total_N = len(log_alphas) + self.T = 1. + self.t_array = torch.linspace(0., 1., self.total_N + 1)[1:].reshape((1, -1)) + self.log_alpha_array = log_alphas.reshape((1, -1,)) + else: + self.total_N = 1000 + self.beta_0 = continuous_beta_0 + self.beta_1 = continuous_beta_1 + self.cosine_s = 0.008 + self.cosine_beta_max = 999. + self.cosine_t_max = math.atan(self.cosine_beta_max * (1. + self.cosine_s) / math.pi) * 2. * ( + 1. + self.cosine_s) / math.pi - self.cosine_s + self.cosine_log_alpha_0 = math.log(math.cos(self.cosine_s / (1. + self.cosine_s) * math.pi / 2.)) + self.schedule = schedule + if schedule == 'cosine': + # For the cosine schedule, T = 1 will have numerical issues. So we manually set the ending time T. + # Note that T = 0.9946 may be not the optimal setting. However, we find it works well. + self.T = 0.9946 + else: + self.T = 1. + + def marginal_log_mean_coeff(self, t): + """ + Compute log(alpha_t) of a given continuous-time label t in [0, T]. + """ + if self.schedule == 'discrete': + return interpolate_fn(t.reshape((-1, 1)), self.t_array.to(t.device), + self.log_alpha_array.to(t.device)).reshape((-1)) + elif self.schedule == 'linear': + return -0.25 * t ** 2 * (self.beta_1 - self.beta_0) - 0.5 * t * self.beta_0 + elif self.schedule == 'cosine': + log_alpha_fn = lambda s: torch.log(torch.cos((s + self.cosine_s) / (1. + self.cosine_s) * math.pi / 2.)) + log_alpha_t = log_alpha_fn(t) - self.cosine_log_alpha_0 + return log_alpha_t + + def marginal_alpha(self, t): + """ + Compute alpha_t of a given continuous-time label t in [0, T]. + """ + return torch.exp(self.marginal_log_mean_coeff(t)) + + def marginal_std(self, t): + """ + Compute sigma_t of a given continuous-time label t in [0, T]. + """ + return torch.sqrt(1. - torch.exp(2. * self.marginal_log_mean_coeff(t))) + + def marginal_lambda(self, t): + """ + Compute lambda_t = log(alpha_t) - log(sigma_t) of a given continuous-time label t in [0, T]. + """ + log_mean_coeff = self.marginal_log_mean_coeff(t) + log_std = 0.5 * torch.log(1. - torch.exp(2. * log_mean_coeff)) + return log_mean_coeff - log_std + + def inverse_lambda(self, lamb): + """ + Compute the continuous-time label t in [0, T] of a given half-logSNR lambda_t. + """ + if self.schedule == 'linear': + tmp = 2. * (self.beta_1 - self.beta_0) * torch.logaddexp(-2. * lamb, torch.zeros((1,)).to(lamb)) + Delta = self.beta_0 ** 2 + tmp + return tmp / (torch.sqrt(Delta) + self.beta_0) / (self.beta_1 - self.beta_0) + elif self.schedule == 'discrete': + log_alpha = -0.5 * torch.logaddexp(torch.zeros((1,)).to(lamb.device), -2. * lamb) + t = interpolate_fn(log_alpha.reshape((-1, 1)), torch.flip(self.log_alpha_array.to(lamb.device), [1]), + torch.flip(self.t_array.to(lamb.device), [1])) + return t.reshape((-1,)) + else: + log_alpha = -0.5 * torch.logaddexp(-2. * lamb, torch.zeros((1,)).to(lamb)) + t_fn = lambda log_alpha_t: torch.arccos(torch.exp(log_alpha_t + self.cosine_log_alpha_0)) * 2. * ( + 1. + self.cosine_s) / math.pi - self.cosine_s + t = t_fn(log_alpha) + return t + + +def model_wrapper( + model, + noise_schedule, + model_type="noise", + model_kwargs={}, + guidance_type="uncond", + condition=None, + unconditional_condition=None, + guidance_scale=1., + classifier_fn=None, + classifier_kwargs={}, +): + """Create a wrapper function for the noise prediction model. + + DPM-Solver needs to solve the continuous-time diffusion ODEs. For DPMs trained on discrete-time labels, we need to + firstly wrap the model function to a noise prediction model that accepts the continuous time as the input. + + We support four types of the diffusion model by setting `model_type`: + + 1. "noise": noise prediction model. (Trained by predicting noise). + + 2. "x_start": data prediction model. (Trained by predicting the data x_0 at time 0). + + 3. "v": velocity prediction model. (Trained by predicting the velocity). + The "v" prediction is derivation detailed in Appendix D of [1], and is used in Imagen-Video [2]. + + [1] Salimans, Tim, and Jonathan Ho. "Progressive distillation for fast sampling of diffusion models." + arXiv preprint arXiv:2202.00512 (2022). + [2] Ho, Jonathan, et al. "Imagen Video: High Definition Video Generation with Diffusion Models." + arXiv preprint arXiv:2210.02303 (2022). + + 4. "score": marginal score function. (Trained by denoising score matching). + Note that the score function and the noise prediction model follows a simple relationship: + ``` + noise(x_t, t) = -sigma_t * score(x_t, t) + ``` + + We support three types of guided sampling by DPMs by setting `guidance_type`: + 1. "uncond": unconditional sampling by DPMs. + The input `model` has the following format: + `` + model(x, t_input, **model_kwargs) -> noise | x_start | v | score + `` + + 2. "classifier": classifier guidance sampling [3] by DPMs and another classifier. + The input `model` has the following format: + `` + model(x, t_input, **model_kwargs) -> noise | x_start | v | score + `` + + The input `classifier_fn` has the following format: + `` + classifier_fn(x, t_input, cond, **classifier_kwargs) -> logits(x, t_input, cond) + `` + + [3] P. Dhariwal and A. Q. Nichol, "Diffusion models beat GANs on image synthesis," + in Advances in Neural Information Processing Systems, vol. 34, 2021, pp. 8780-8794. + + 3. "classifier-free": classifier-free guidance sampling by conditional DPMs. + The input `model` has the following format: + `` + model(x, t_input, cond, **model_kwargs) -> noise | x_start | v | score + `` + And if cond == `unconditional_condition`, the model output is the unconditional DPM output. + + [4] Ho, Jonathan, and Tim Salimans. "Classifier-free diffusion guidance." + arXiv preprint arXiv:2207.12598 (2022). + + + The `t_input` is the time label of the model, which may be discrete-time labels (i.e. 0 to 999) + or continuous-time labels (i.e. epsilon to T). + + We wrap the model function to accept only `x` and `t_continuous` as inputs, and outputs the predicted noise: + `` + def model_fn(x, t_continuous) -> noise: + t_input = get_model_input_time(t_continuous) + return noise_pred(model, x, t_input, **model_kwargs) + `` + where `t_continuous` is the continuous time labels (i.e. epsilon to T). And we use `model_fn` for DPM-Solver. + + =============================================================== + + Args: + model: A diffusion model with the corresponding format described above. + noise_schedule: A noise schedule object, such as NoiseScheduleVP. + model_type: A `str`. The parameterization type of the diffusion model. + "noise" or "x_start" or "v" or "score". + model_kwargs: A `dict`. A dict for the other inputs of the model function. + guidance_type: A `str`. The type of the guidance for sampling. + "uncond" or "classifier" or "classifier-free". + condition: A pytorch tensor. The condition for the guided sampling. + Only used for "classifier" or "classifier-free" guidance type. + unconditional_condition: A pytorch tensor. The condition for the unconditional sampling. + Only used for "classifier-free" guidance type. + guidance_scale: A `float`. The scale for the guided sampling. + classifier_fn: A classifier function. Only used for the classifier guidance. + classifier_kwargs: A `dict`. A dict for the other inputs of the classifier function. + Returns: + A noise prediction model that accepts the noised data and the continuous time as the inputs. + """ + + def get_model_input_time(t_continuous): + """ + Convert the continuous-time `t_continuous` (in [epsilon, T]) to the model input time. + For discrete-time DPMs, we convert `t_continuous` in [1 / N, 1] to `t_input` in [0, 1000 * (N - 1) / N]. + For continuous-time DPMs, we just use `t_continuous`. + """ + if noise_schedule.schedule == 'discrete': + return (t_continuous - 1. / noise_schedule.total_N) * 1000. + else: + return t_continuous + + def noise_pred_fn(x, t_continuous, cond=None): + if t_continuous.reshape((-1,)).shape[0] == 1: + t_continuous = t_continuous.expand((x.shape[0])) + t_input = get_model_input_time(t_continuous) + if cond is None: + output = model(x, t_input, **model_kwargs) + else: + output = model(x, t_input, cond, **model_kwargs) + if model_type == "noise": + return output + elif model_type == "x_start": + alpha_t, sigma_t = noise_schedule.marginal_alpha(t_continuous), noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return (x - expand_dims(alpha_t, dims) * output) / expand_dims(sigma_t, dims) + elif model_type == "v": + alpha_t, sigma_t = noise_schedule.marginal_alpha(t_continuous), noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return expand_dims(alpha_t, dims) * output + expand_dims(sigma_t, dims) * x + elif model_type == "score": + sigma_t = noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return -expand_dims(sigma_t, dims) * output + + def cond_grad_fn(x, t_input): + """ + Compute the gradient of the classifier, i.e. nabla_{x} log p_t(cond | x_t). + """ + with torch.enable_grad(): + x_in = x.detach().requires_grad_(True) + log_prob = classifier_fn(x_in, t_input, condition, **classifier_kwargs) + return torch.autograd.grad(log_prob.sum(), x_in)[0] + + def model_fn(x, t_continuous): + """ + The noise predicition model function that is used for DPM-Solver. + """ + if t_continuous.reshape((-1,)).shape[0] == 1: + t_continuous = t_continuous.expand((x.shape[0])) + if guidance_type == "uncond": + return noise_pred_fn(x, t_continuous) + elif guidance_type == "classifier": + assert classifier_fn is not None + t_input = get_model_input_time(t_continuous) + cond_grad = cond_grad_fn(x, t_input) + sigma_t = noise_schedule.marginal_std(t_continuous) + noise = noise_pred_fn(x, t_continuous) + return noise - guidance_scale * expand_dims(sigma_t, dims=cond_grad.dim()) * cond_grad + elif guidance_type == "classifier-free": + if guidance_scale == 1. or unconditional_condition is None: + return noise_pred_fn(x, t_continuous, cond=condition) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t_continuous] * 2) + c_in = torch.cat([unconditional_condition, condition]) + noise_uncond, noise = noise_pred_fn(x_in, t_in, cond=c_in).chunk(2) + return noise_uncond + guidance_scale * (noise - noise_uncond) + + assert model_type in ["noise", "x_start", "v"] + assert guidance_type in ["uncond", "classifier", "classifier-free"] + return model_fn + + +class DPM_Solver: + def __init__(self, model_fn, noise_schedule, predict_x0=False, thresholding=False, max_val=1.): + """Construct a DPM-Solver. + + We support both the noise prediction model ("predicting epsilon") and the data prediction model ("predicting x0"). + If `predict_x0` is False, we use the solver for the noise prediction model (DPM-Solver). + If `predict_x0` is True, we use the solver for the data prediction model (DPM-Solver++). + In such case, we further support the "dynamic thresholding" in [1] when `thresholding` is True. + The "dynamic thresholding" can greatly improve the sample quality for pixel-space DPMs with large guidance scales. + + Args: + model_fn: A noise prediction model function which accepts the continuous-time input (t in [epsilon, T]): + `` + def model_fn(x, t_continuous): + return noise + `` + noise_schedule: A noise schedule object, such as NoiseScheduleVP. + predict_x0: A `bool`. If true, use the data prediction model; else, use the noise prediction model. + thresholding: A `bool`. Valid when `predict_x0` is True. Whether to use the "dynamic thresholding" in [1]. + max_val: A `float`. Valid when both `predict_x0` and `thresholding` are True. The max value for thresholding. + + [1] Chitwan Saharia, William Chan, Saurabh Saxena, Lala Li, Jay Whang, Emily Denton, Seyed Kamyar Seyed Ghasemipour, Burcu Karagol Ayan, S Sara Mahdavi, Rapha Gontijo Lopes, et al. Photorealistic text-to-image diffusion models with deep language understanding. arXiv preprint arXiv:2205.11487, 2022b. + """ + self.model = model_fn + self.noise_schedule = noise_schedule + self.predict_x0 = predict_x0 + self.thresholding = thresholding + self.max_val = max_val + + def noise_prediction_fn(self, x, t): + """ + Return the noise prediction model. + """ + return self.model(x, t) + + def data_prediction_fn(self, x, t): + """ + Return the data prediction model (with thresholding). + """ + noise = self.noise_prediction_fn(x, t) + dims = x.dim() + alpha_t, sigma_t = self.noise_schedule.marginal_alpha(t), self.noise_schedule.marginal_std(t) + x0 = (x - expand_dims(sigma_t, dims) * noise) / expand_dims(alpha_t, dims) + if self.thresholding: + p = 0.995 # A hyperparameter in the paper of "Imagen" [1]. + s = torch.quantile(torch.abs(x0).reshape((x0.shape[0], -1)), p, dim=1) + s = expand_dims(torch.maximum(s, self.max_val * torch.ones_like(s).to(s.device)), dims) + x0 = torch.clamp(x0, -s, s) / s + return x0 + + def model_fn(self, x, t): + """ + Convert the model to the noise prediction model or the data prediction model. + """ + if self.predict_x0: + return self.data_prediction_fn(x, t) + else: + return self.noise_prediction_fn(x, t) + + def get_time_steps(self, skip_type, t_T, t_0, N, device): + """Compute the intermediate time steps for sampling. + + Args: + skip_type: A `str`. The type for the spacing of the time steps. We support three types: + - 'logSNR': uniform logSNR for the time steps. + - 'time_uniform': uniform time for the time steps. (**Recommended for high-resolutional data**.) + - 'time_quadratic': quadratic time for the time steps. (Used in DDIM for low-resolutional data.) + t_T: A `float`. The starting time of the sampling (default is T). + t_0: A `float`. The ending time of the sampling (default is epsilon). + N: A `int`. The total number of the spacing of the time steps. + device: A torch device. + Returns: + A pytorch tensor of the time steps, with the shape (N + 1,). + """ + if skip_type == 'logSNR': + lambda_T = self.noise_schedule.marginal_lambda(torch.tensor(t_T).to(device)) + lambda_0 = self.noise_schedule.marginal_lambda(torch.tensor(t_0).to(device)) + logSNR_steps = torch.linspace(lambda_T.cpu().item(), lambda_0.cpu().item(), N + 1).to(device) + return self.noise_schedule.inverse_lambda(logSNR_steps) + elif skip_type == 'time_uniform': + return torch.linspace(t_T, t_0, N + 1).to(device) + elif skip_type == 'time_quadratic': + t_order = 2 + t = torch.linspace(t_T ** (1. / t_order), t_0 ** (1. / t_order), N + 1).pow(t_order).to(device) + return t + else: + raise ValueError( + "Unsupported skip_type {}, need to be 'logSNR' or 'time_uniform' or 'time_quadratic'".format(skip_type)) + + def get_orders_and_timesteps_for_singlestep_solver(self, steps, order, skip_type, t_T, t_0, device): + """ + Get the order of each step for sampling by the singlestep DPM-Solver. + + We combine both DPM-Solver-1,2,3 to use all the function evaluations, which is named as "DPM-Solver-fast". + Given a fixed number of function evaluations by `steps`, the sampling procedure by DPM-Solver-fast is: + - If order == 1: + We take `steps` of DPM-Solver-1 (i.e. DDIM). + - If order == 2: + - Denote K = (steps // 2). We take K or (K + 1) intermediate time steps for sampling. + - If steps % 2 == 0, we use K steps of DPM-Solver-2. + - If steps % 2 == 1, we use K steps of DPM-Solver-2 and 1 step of DPM-Solver-1. + - If order == 3: + - Denote K = (steps // 3 + 1). We take K intermediate time steps for sampling. + - If steps % 3 == 0, we use (K - 2) steps of DPM-Solver-3, and 1 step of DPM-Solver-2 and 1 step of DPM-Solver-1. + - If steps % 3 == 1, we use (K - 1) steps of DPM-Solver-3 and 1 step of DPM-Solver-1. + - If steps % 3 == 2, we use (K - 1) steps of DPM-Solver-3 and 1 step of DPM-Solver-2. + + ============================================ + Args: + order: A `int`. The max order for the solver (2 or 3). + steps: A `int`. The total number of function evaluations (NFE). + skip_type: A `str`. The type for the spacing of the time steps. We support three types: + - 'logSNR': uniform logSNR for the time steps. + - 'time_uniform': uniform time for the time steps. (**Recommended for high-resolutional data**.) + - 'time_quadratic': quadratic time for the time steps. (Used in DDIM for low-resolutional data.) + t_T: A `float`. The starting time of the sampling (default is T). + t_0: A `float`. The ending time of the sampling (default is epsilon). + device: A torch device. + Returns: + orders: A list of the solver order of each step. + """ + if order == 3: + K = steps // 3 + 1 + if steps % 3 == 0: + orders = [3, ] * (K - 2) + [2, 1] + elif steps % 3 == 1: + orders = [3, ] * (K - 1) + [1] + else: + orders = [3, ] * (K - 1) + [2] + elif order == 2: + if steps % 2 == 0: + K = steps // 2 + orders = [2, ] * K + else: + K = steps // 2 + 1 + orders = [2, ] * (K - 1) + [1] + elif order == 1: + K = 1 + orders = [1, ] * steps + else: + raise ValueError("'order' must be '1' or '2' or '3'.") + if skip_type == 'logSNR': + # To reproduce the results in DPM-Solver paper + timesteps_outer = self.get_time_steps(skip_type, t_T, t_0, K, device) + else: + timesteps_outer = self.get_time_steps(skip_type, t_T, t_0, steps, device)[ + torch.cumsum(torch.tensor([0, ] + orders)).to(device)] + return timesteps_outer, orders + + def denoise_to_zero_fn(self, x, s): + """ + Denoise at the final step, which is equivalent to solve the ODE from lambda_s to infty by first-order discretization. + """ + return self.data_prediction_fn(x, s) + + def dpm_solver_first_update(self, x, s, t, model_s=None, return_intermediate=False): + """ + DPM-Solver-1 (equivalent to DDIM) from time `s` to time `t`. + + Args: + x: A pytorch tensor. The initial value at time `s`. + s: A pytorch tensor. The starting time, with the shape (x.shape[0],). + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + model_s: A pytorch tensor. The model function evaluated at time `s`. + If `model_s` is None, we evaluate the model by `x` and `s`; otherwise we directly use it. + return_intermediate: A `bool`. If true, also return the model value at time `s`. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + ns = self.noise_schedule + dims = x.dim() + lambda_s, lambda_t = ns.marginal_lambda(s), ns.marginal_lambda(t) + h = lambda_t - lambda_s + log_alpha_s, log_alpha_t = ns.marginal_log_mean_coeff(s), ns.marginal_log_mean_coeff(t) + sigma_s, sigma_t = ns.marginal_std(s), ns.marginal_std(t) + alpha_t = torch.exp(log_alpha_t) + + if self.predict_x0: + phi_1 = torch.expm1(-h) + if model_s is None: + model_s = self.model_fn(x, s) + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + ) + if return_intermediate: + return x_t, {'model_s': model_s} + else: + return x_t + else: + phi_1 = torch.expm1(h) + if model_s is None: + model_s = self.model_fn(x, s) + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + ) + if return_intermediate: + return x_t, {'model_s': model_s} + else: + return x_t + + def singlestep_dpm_solver_second_update(self, x, s, t, r1=0.5, model_s=None, return_intermediate=False, + solver_type='dpm_solver'): + """ + Singlestep solver DPM-Solver-2 from time `s` to time `t`. + + Args: + x: A pytorch tensor. The initial value at time `s`. + s: A pytorch tensor. The starting time, with the shape (x.shape[0],). + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + r1: A `float`. The hyperparameter of the second-order solver. + model_s: A pytorch tensor. The model function evaluated at time `s`. + If `model_s` is None, we evaluate the model by `x` and `s`; otherwise we directly use it. + return_intermediate: A `bool`. If true, also return the model value at time `s` and `s1` (the intermediate time). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if solver_type not in ['dpm_solver', 'taylor']: + raise ValueError("'solver_type' must be either 'dpm_solver' or 'taylor', got {}".format(solver_type)) + if r1 is None: + r1 = 0.5 + ns = self.noise_schedule + dims = x.dim() + lambda_s, lambda_t = ns.marginal_lambda(s), ns.marginal_lambda(t) + h = lambda_t - lambda_s + lambda_s1 = lambda_s + r1 * h + s1 = ns.inverse_lambda(lambda_s1) + log_alpha_s, log_alpha_s1, log_alpha_t = ns.marginal_log_mean_coeff(s), ns.marginal_log_mean_coeff( + s1), ns.marginal_log_mean_coeff(t) + sigma_s, sigma_s1, sigma_t = ns.marginal_std(s), ns.marginal_std(s1), ns.marginal_std(t) + alpha_s1, alpha_t = torch.exp(log_alpha_s1), torch.exp(log_alpha_t) + + if self.predict_x0: + phi_11 = torch.expm1(-r1 * h) + phi_1 = torch.expm1(-h) + + if model_s is None: + model_s = self.model_fn(x, s) + x_s1 = ( + expand_dims(sigma_s1 / sigma_s, dims) * x + - expand_dims(alpha_s1 * phi_11, dims) * model_s + ) + model_s1 = self.model_fn(x_s1, s1) + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + - (0.5 / r1) * expand_dims(alpha_t * phi_1, dims) * (model_s1 - model_s) + ) + elif solver_type == 'taylor': + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + + (1. / r1) * expand_dims(alpha_t * ((torch.exp(-h) - 1.) / h + 1.), dims) * ( + model_s1 - model_s) + ) + else: + phi_11 = torch.expm1(r1 * h) + phi_1 = torch.expm1(h) + + if model_s is None: + model_s = self.model_fn(x, s) + x_s1 = ( + expand_dims(torch.exp(log_alpha_s1 - log_alpha_s), dims) * x + - expand_dims(sigma_s1 * phi_11, dims) * model_s + ) + model_s1 = self.model_fn(x_s1, s1) + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + - (0.5 / r1) * expand_dims(sigma_t * phi_1, dims) * (model_s1 - model_s) + ) + elif solver_type == 'taylor': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + - (1. / r1) * expand_dims(sigma_t * ((torch.exp(h) - 1.) / h - 1.), dims) * (model_s1 - model_s) + ) + if return_intermediate: + return x_t, {'model_s': model_s, 'model_s1': model_s1} + else: + return x_t + + def singlestep_dpm_solver_third_update(self, x, s, t, r1=1. / 3., r2=2. / 3., model_s=None, model_s1=None, + return_intermediate=False, solver_type='dpm_solver'): + """ + Singlestep solver DPM-Solver-3 from time `s` to time `t`. + + Args: + x: A pytorch tensor. The initial value at time `s`. + s: A pytorch tensor. The starting time, with the shape (x.shape[0],). + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + r1: A `float`. The hyperparameter of the third-order solver. + r2: A `float`. The hyperparameter of the third-order solver. + model_s: A pytorch tensor. The model function evaluated at time `s`. + If `model_s` is None, we evaluate the model by `x` and `s`; otherwise we directly use it. + model_s1: A pytorch tensor. The model function evaluated at time `s1` (the intermediate time given by `r1`). + If `model_s1` is None, we evaluate the model at `s1`; otherwise we directly use it. + return_intermediate: A `bool`. If true, also return the model value at time `s`, `s1` and `s2` (the intermediate times). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if solver_type not in ['dpm_solver', 'taylor']: + raise ValueError("'solver_type' must be either 'dpm_solver' or 'taylor', got {}".format(solver_type)) + if r1 is None: + r1 = 1. / 3. + if r2 is None: + r2 = 2. / 3. + ns = self.noise_schedule + dims = x.dim() + lambda_s, lambda_t = ns.marginal_lambda(s), ns.marginal_lambda(t) + h = lambda_t - lambda_s + lambda_s1 = lambda_s + r1 * h + lambda_s2 = lambda_s + r2 * h + s1 = ns.inverse_lambda(lambda_s1) + s2 = ns.inverse_lambda(lambda_s2) + log_alpha_s, log_alpha_s1, log_alpha_s2, log_alpha_t = ns.marginal_log_mean_coeff( + s), ns.marginal_log_mean_coeff(s1), ns.marginal_log_mean_coeff(s2), ns.marginal_log_mean_coeff(t) + sigma_s, sigma_s1, sigma_s2, sigma_t = ns.marginal_std(s), ns.marginal_std(s1), ns.marginal_std( + s2), ns.marginal_std(t) + alpha_s1, alpha_s2, alpha_t = torch.exp(log_alpha_s1), torch.exp(log_alpha_s2), torch.exp(log_alpha_t) + + if self.predict_x0: + phi_11 = torch.expm1(-r1 * h) + phi_12 = torch.expm1(-r2 * h) + phi_1 = torch.expm1(-h) + phi_22 = torch.expm1(-r2 * h) / (r2 * h) + 1. + phi_2 = phi_1 / h + 1. + phi_3 = phi_2 / h - 0.5 + + if model_s is None: + model_s = self.model_fn(x, s) + if model_s1 is None: + x_s1 = ( + expand_dims(sigma_s1 / sigma_s, dims) * x + - expand_dims(alpha_s1 * phi_11, dims) * model_s + ) + model_s1 = self.model_fn(x_s1, s1) + x_s2 = ( + expand_dims(sigma_s2 / sigma_s, dims) * x + - expand_dims(alpha_s2 * phi_12, dims) * model_s + + r2 / r1 * expand_dims(alpha_s2 * phi_22, dims) * (model_s1 - model_s) + ) + model_s2 = self.model_fn(x_s2, s2) + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + + (1. / r2) * expand_dims(alpha_t * phi_2, dims) * (model_s2 - model_s) + ) + elif solver_type == 'taylor': + D1_0 = (1. / r1) * (model_s1 - model_s) + D1_1 = (1. / r2) * (model_s2 - model_s) + D1 = (r2 * D1_0 - r1 * D1_1) / (r2 - r1) + D2 = 2. * (D1_1 - D1_0) / (r2 - r1) + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + + expand_dims(alpha_t * phi_2, dims) * D1 + - expand_dims(alpha_t * phi_3, dims) * D2 + ) + else: + phi_11 = torch.expm1(r1 * h) + phi_12 = torch.expm1(r2 * h) + phi_1 = torch.expm1(h) + phi_22 = torch.expm1(r2 * h) / (r2 * h) - 1. + phi_2 = phi_1 / h - 1. + phi_3 = phi_2 / h - 0.5 + + if model_s is None: + model_s = self.model_fn(x, s) + if model_s1 is None: + x_s1 = ( + expand_dims(torch.exp(log_alpha_s1 - log_alpha_s), dims) * x + - expand_dims(sigma_s1 * phi_11, dims) * model_s + ) + model_s1 = self.model_fn(x_s1, s1) + x_s2 = ( + expand_dims(torch.exp(log_alpha_s2 - log_alpha_s), dims) * x + - expand_dims(sigma_s2 * phi_12, dims) * model_s + - r2 / r1 * expand_dims(sigma_s2 * phi_22, dims) * (model_s1 - model_s) + ) + model_s2 = self.model_fn(x_s2, s2) + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + - (1. / r2) * expand_dims(sigma_t * phi_2, dims) * (model_s2 - model_s) + ) + elif solver_type == 'taylor': + D1_0 = (1. / r1) * (model_s1 - model_s) + D1_1 = (1. / r2) * (model_s2 - model_s) + D1 = (r2 * D1_0 - r1 * D1_1) / (r2 - r1) + D2 = 2. * (D1_1 - D1_0) / (r2 - r1) + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + - expand_dims(sigma_t * phi_2, dims) * D1 + - expand_dims(sigma_t * phi_3, dims) * D2 + ) + + if return_intermediate: + return x_t, {'model_s': model_s, 'model_s1': model_s1, 'model_s2': model_s2} + else: + return x_t + + def multistep_dpm_solver_second_update(self, x, model_prev_list, t_prev_list, t, solver_type="dpm_solver"): + """ + Multistep solver DPM-Solver-2 from time `t_prev_list[-1]` to time `t`. + + Args: + x: A pytorch tensor. The initial value at time `s`. + model_prev_list: A list of pytorch tensor. The previous computed model values. + t_prev_list: A list of pytorch tensor. The previous times, each time has the shape (x.shape[0],) + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if solver_type not in ['dpm_solver', 'taylor']: + raise ValueError("'solver_type' must be either 'dpm_solver' or 'taylor', got {}".format(solver_type)) + ns = self.noise_schedule + dims = x.dim() + model_prev_1, model_prev_0 = model_prev_list + t_prev_1, t_prev_0 = t_prev_list + lambda_prev_1, lambda_prev_0, lambda_t = ns.marginal_lambda(t_prev_1), ns.marginal_lambda( + t_prev_0), ns.marginal_lambda(t) + log_alpha_prev_0, log_alpha_t = ns.marginal_log_mean_coeff(t_prev_0), ns.marginal_log_mean_coeff(t) + sigma_prev_0, sigma_t = ns.marginal_std(t_prev_0), ns.marginal_std(t) + alpha_t = torch.exp(log_alpha_t) + + h_0 = lambda_prev_0 - lambda_prev_1 + h = lambda_t - lambda_prev_0 + r0 = h_0 / h + D1_0 = expand_dims(1. / r0, dims) * (model_prev_0 - model_prev_1) + if self.predict_x0: + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(sigma_t / sigma_prev_0, dims) * x + - expand_dims(alpha_t * (torch.exp(-h) - 1.), dims) * model_prev_0 + - 0.5 * expand_dims(alpha_t * (torch.exp(-h) - 1.), dims) * D1_0 + ) + elif solver_type == 'taylor': + x_t = ( + expand_dims(sigma_t / sigma_prev_0, dims) * x + - expand_dims(alpha_t * (torch.exp(-h) - 1.), dims) * model_prev_0 + + expand_dims(alpha_t * ((torch.exp(-h) - 1.) / h + 1.), dims) * D1_0 + ) + else: + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dims) * x + - expand_dims(sigma_t * (torch.exp(h) - 1.), dims) * model_prev_0 + - 0.5 * expand_dims(sigma_t * (torch.exp(h) - 1.), dims) * D1_0 + ) + elif solver_type == 'taylor': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dims) * x + - expand_dims(sigma_t * (torch.exp(h) - 1.), dims) * model_prev_0 + - expand_dims(sigma_t * ((torch.exp(h) - 1.) / h - 1.), dims) * D1_0 + ) + return x_t + + def multistep_dpm_solver_third_update(self, x, model_prev_list, t_prev_list, t, solver_type='dpm_solver'): + """ + Multistep solver DPM-Solver-3 from time `t_prev_list[-1]` to time `t`. + + Args: + x: A pytorch tensor. The initial value at time `s`. + model_prev_list: A list of pytorch tensor. The previous computed model values. + t_prev_list: A list of pytorch tensor. The previous times, each time has the shape (x.shape[0],) + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + ns = self.noise_schedule + dims = x.dim() + model_prev_2, model_prev_1, model_prev_0 = model_prev_list + t_prev_2, t_prev_1, t_prev_0 = t_prev_list + lambda_prev_2, lambda_prev_1, lambda_prev_0, lambda_t = ns.marginal_lambda(t_prev_2), ns.marginal_lambda( + t_prev_1), ns.marginal_lambda(t_prev_0), ns.marginal_lambda(t) + log_alpha_prev_0, log_alpha_t = ns.marginal_log_mean_coeff(t_prev_0), ns.marginal_log_mean_coeff(t) + sigma_prev_0, sigma_t = ns.marginal_std(t_prev_0), ns.marginal_std(t) + alpha_t = torch.exp(log_alpha_t) + + h_1 = lambda_prev_1 - lambda_prev_2 + h_0 = lambda_prev_0 - lambda_prev_1 + h = lambda_t - lambda_prev_0 + r0, r1 = h_0 / h, h_1 / h + D1_0 = expand_dims(1. / r0, dims) * (model_prev_0 - model_prev_1) + D1_1 = expand_dims(1. / r1, dims) * (model_prev_1 - model_prev_2) + D1 = D1_0 + expand_dims(r0 / (r0 + r1), dims) * (D1_0 - D1_1) + D2 = expand_dims(1. / (r0 + r1), dims) * (D1_0 - D1_1) + if self.predict_x0: + x_t = ( + expand_dims(sigma_t / sigma_prev_0, dims) * x + - expand_dims(alpha_t * (torch.exp(-h) - 1.), dims) * model_prev_0 + + expand_dims(alpha_t * ((torch.exp(-h) - 1.) / h + 1.), dims) * D1 + - expand_dims(alpha_t * ((torch.exp(-h) - 1. + h) / h ** 2 - 0.5), dims) * D2 + ) + else: + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dims) * x + - expand_dims(sigma_t * (torch.exp(h) - 1.), dims) * model_prev_0 + - expand_dims(sigma_t * ((torch.exp(h) - 1.) / h - 1.), dims) * D1 + - expand_dims(sigma_t * ((torch.exp(h) - 1. - h) / h ** 2 - 0.5), dims) * D2 + ) + return x_t + + def singlestep_dpm_solver_update(self, x, s, t, order, return_intermediate=False, solver_type='dpm_solver', r1=None, + r2=None): + """ + Singlestep DPM-Solver with the order `order` from time `s` to time `t`. + + Args: + x: A pytorch tensor. The initial value at time `s`. + s: A pytorch tensor. The starting time, with the shape (x.shape[0],). + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + order: A `int`. The order of DPM-Solver. We only support order == 1 or 2 or 3. + return_intermediate: A `bool`. If true, also return the model value at time `s`, `s1` and `s2` (the intermediate times). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + r1: A `float`. The hyperparameter of the second-order or third-order solver. + r2: A `float`. The hyperparameter of the third-order solver. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if order == 1: + return self.dpm_solver_first_update(x, s, t, return_intermediate=return_intermediate) + elif order == 2: + return self.singlestep_dpm_solver_second_update(x, s, t, return_intermediate=return_intermediate, + solver_type=solver_type, r1=r1) + elif order == 3: + return self.singlestep_dpm_solver_third_update(x, s, t, return_intermediate=return_intermediate, + solver_type=solver_type, r1=r1, r2=r2) + else: + raise ValueError("Solver order must be 1 or 2 or 3, got {}".format(order)) + + def multistep_dpm_solver_update(self, x, model_prev_list, t_prev_list, t, order, solver_type='dpm_solver'): + """ + Multistep DPM-Solver with the order `order` from time `t_prev_list[-1]` to time `t`. + + Args: + x: A pytorch tensor. The initial value at time `s`. + model_prev_list: A list of pytorch tensor. The previous computed model values. + t_prev_list: A list of pytorch tensor. The previous times, each time has the shape (x.shape[0],) + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + order: A `int`. The order of DPM-Solver. We only support order == 1 or 2 or 3. + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if order == 1: + return self.dpm_solver_first_update(x, t_prev_list[-1], t, model_s=model_prev_list[-1]) + elif order == 2: + return self.multistep_dpm_solver_second_update(x, model_prev_list, t_prev_list, t, solver_type=solver_type) + elif order == 3: + return self.multistep_dpm_solver_third_update(x, model_prev_list, t_prev_list, t, solver_type=solver_type) + else: + raise ValueError("Solver order must be 1 or 2 or 3, got {}".format(order)) + + def dpm_solver_adaptive(self, x, order, t_T, t_0, h_init=0.05, atol=0.0078, rtol=0.05, theta=0.9, t_err=1e-5, + solver_type='dpm_solver'): + """ + The adaptive step size solver based on singlestep DPM-Solver. + + Args: + x: A pytorch tensor. The initial value at time `t_T`. + order: A `int`. The (higher) order of the solver. We only support order == 2 or 3. + t_T: A `float`. The starting time of the sampling (default is T). + t_0: A `float`. The ending time of the sampling (default is epsilon). + h_init: A `float`. The initial step size (for logSNR). + atol: A `float`. The absolute tolerance of the solver. For image data, the default setting is 0.0078, followed [1]. + rtol: A `float`. The relative tolerance of the solver. The default setting is 0.05. + theta: A `float`. The safety hyperparameter for adapting the step size. The default setting is 0.9, followed [1]. + t_err: A `float`. The tolerance for the time. We solve the diffusion ODE until the absolute error between the + current time and `t_0` is less than `t_err`. The default setting is 1e-5. + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_0: A pytorch tensor. The approximated solution at time `t_0`. + + [1] A. Jolicoeur-Martineau, K. Li, R. Piché-Taillefer, T. Kachman, and I. Mitliagkas, "Gotta go fast when generating data with score-based models," arXiv preprint arXiv:2105.14080, 2021. + """ + ns = self.noise_schedule + s = t_T * torch.ones((x.shape[0],)).to(x) + lambda_s = ns.marginal_lambda(s) + lambda_0 = ns.marginal_lambda(t_0 * torch.ones_like(s).to(x)) + h = h_init * torch.ones_like(s).to(x) + x_prev = x + nfe = 0 + if order == 2: + r1 = 0.5 + lower_update = lambda x, s, t: self.dpm_solver_first_update(x, s, t, return_intermediate=True) + higher_update = lambda x, s, t, **kwargs: self.singlestep_dpm_solver_second_update(x, s, t, r1=r1, + solver_type=solver_type, + **kwargs) + elif order == 3: + r1, r2 = 1. / 3., 2. / 3. + lower_update = lambda x, s, t: self.singlestep_dpm_solver_second_update(x, s, t, r1=r1, + return_intermediate=True, + solver_type=solver_type) + higher_update = lambda x, s, t, **kwargs: self.singlestep_dpm_solver_third_update(x, s, t, r1=r1, r2=r2, + solver_type=solver_type, + **kwargs) + else: + raise ValueError("For adaptive step size solver, order must be 2 or 3, got {}".format(order)) + while torch.abs((s - t_0)).mean() > t_err: + t = ns.inverse_lambda(lambda_s + h) + x_lower, lower_noise_kwargs = lower_update(x, s, t) + x_higher = higher_update(x, s, t, **lower_noise_kwargs) + delta = torch.max(torch.ones_like(x).to(x) * atol, rtol * torch.max(torch.abs(x_lower), torch.abs(x_prev))) + norm_fn = lambda v: torch.sqrt(torch.square(v.reshape((v.shape[0], -1))).mean(dim=-1, keepdim=True)) + E = norm_fn((x_higher - x_lower) / delta).max() + if torch.all(E <= 1.): + x = x_higher + s = t + x_prev = x_lower + lambda_s = ns.marginal_lambda(s) + h = torch.min(theta * h * torch.float_power(E, -1. / order).float(), lambda_0 - lambda_s) + nfe += order + print('adaptive solver nfe', nfe) + return x + + def sample(self, x, steps=20, t_start=None, t_end=None, order=3, skip_type='time_uniform', + method='singlestep', lower_order_final=True, denoise_to_zero=False, solver_type='dpm_solver', + atol=0.0078, rtol=0.05, + ): + """ + Compute the sample at time `t_end` by DPM-Solver, given the initial `x` at time `t_start`. + + ===================================================== + + We support the following algorithms for both noise prediction model and data prediction model: + - 'singlestep': + Singlestep DPM-Solver (i.e. "DPM-Solver-fast" in the paper), which combines different orders of singlestep DPM-Solver. + We combine all the singlestep solvers with order <= `order` to use up all the function evaluations (steps). + The total number of function evaluations (NFE) == `steps`. + Given a fixed NFE == `steps`, the sampling procedure is: + - If `order` == 1: + - Denote K = steps. We use K steps of DPM-Solver-1 (i.e. DDIM). + - If `order` == 2: + - Denote K = (steps // 2) + (steps % 2). We take K intermediate time steps for sampling. + - If steps % 2 == 0, we use K steps of singlestep DPM-Solver-2. + - If steps % 2 == 1, we use (K - 1) steps of singlestep DPM-Solver-2 and 1 step of DPM-Solver-1. + - If `order` == 3: + - Denote K = (steps // 3 + 1). We take K intermediate time steps for sampling. + - If steps % 3 == 0, we use (K - 2) steps of singlestep DPM-Solver-3, and 1 step of singlestep DPM-Solver-2 and 1 step of DPM-Solver-1. + - If steps % 3 == 1, we use (K - 1) steps of singlestep DPM-Solver-3 and 1 step of DPM-Solver-1. + - If steps % 3 == 2, we use (K - 1) steps of singlestep DPM-Solver-3 and 1 step of singlestep DPM-Solver-2. + - 'multistep': + Multistep DPM-Solver with the order of `order`. The total number of function evaluations (NFE) == `steps`. + We initialize the first `order` values by lower order multistep solvers. + Given a fixed NFE == `steps`, the sampling procedure is: + Denote K = steps. + - If `order` == 1: + - We use K steps of DPM-Solver-1 (i.e. DDIM). + - If `order` == 2: + - We firstly use 1 step of DPM-Solver-1, then use (K - 1) step of multistep DPM-Solver-2. + - If `order` == 3: + - We firstly use 1 step of DPM-Solver-1, then 1 step of multistep DPM-Solver-2, then (K - 2) step of multistep DPM-Solver-3. + - 'singlestep_fixed': + Fixed order singlestep DPM-Solver (i.e. DPM-Solver-1 or singlestep DPM-Solver-2 or singlestep DPM-Solver-3). + We use singlestep DPM-Solver-`order` for `order`=1 or 2 or 3, with total [`steps` // `order`] * `order` NFE. + - 'adaptive': + Adaptive step size DPM-Solver (i.e. "DPM-Solver-12" and "DPM-Solver-23" in the paper). + We ignore `steps` and use adaptive step size DPM-Solver with a higher order of `order`. + You can adjust the absolute tolerance `atol` and the relative tolerance `rtol` to balance the computatation costs + (NFE) and the sample quality. + - If `order` == 2, we use DPM-Solver-12 which combines DPM-Solver-1 and singlestep DPM-Solver-2. + - If `order` == 3, we use DPM-Solver-23 which combines singlestep DPM-Solver-2 and singlestep DPM-Solver-3. + + ===================================================== + + Some advices for choosing the algorithm: + - For **unconditional sampling** or **guided sampling with small guidance scale** by DPMs: + Use singlestep DPM-Solver ("DPM-Solver-fast" in the paper) with `order = 3`. + e.g. + >>> dpm_solver = DPM_Solver(model_fn, noise_schedule, predict_x0=False) + >>> x_sample = dpm_solver.sample(x, steps=steps, t_start=t_start, t_end=t_end, order=3, + skip_type='time_uniform', method='singlestep') + - For **guided sampling with large guidance scale** by DPMs: + Use multistep DPM-Solver with `predict_x0 = True` and `order = 2`. + e.g. + >>> dpm_solver = DPM_Solver(model_fn, noise_schedule, predict_x0=True) + >>> x_sample = dpm_solver.sample(x, steps=steps, t_start=t_start, t_end=t_end, order=2, + skip_type='time_uniform', method='multistep') + + We support three types of `skip_type`: + - 'logSNR': uniform logSNR for the time steps. **Recommended for low-resolutional images** + - 'time_uniform': uniform time for the time steps. **Recommended for high-resolutional images**. + - 'time_quadratic': quadratic time for the time steps. + + ===================================================== + Args: + x: A pytorch tensor. The initial value at time `t_start` + e.g. if `t_start` == T, then `x` is a sample from the standard normal distribution. + steps: A `int`. The total number of function evaluations (NFE). + t_start: A `float`. The starting time of the sampling. + If `T` is None, we use self.noise_schedule.T (default is 1.0). + t_end: A `float`. The ending time of the sampling. + If `t_end` is None, we use 1. / self.noise_schedule.total_N. + e.g. if total_N == 1000, we have `t_end` == 1e-3. + For discrete-time DPMs: + - We recommend `t_end` == 1. / self.noise_schedule.total_N. + For continuous-time DPMs: + - We recommend `t_end` == 1e-3 when `steps` <= 15; and `t_end` == 1e-4 when `steps` > 15. + order: A `int`. The order of DPM-Solver. + skip_type: A `str`. The type for the spacing of the time steps. 'time_uniform' or 'logSNR' or 'time_quadratic'. + method: A `str`. The method for sampling. 'singlestep' or 'multistep' or 'singlestep_fixed' or 'adaptive'. + denoise_to_zero: A `bool`. Whether to denoise to time 0 at the final step. + Default is `False`. If `denoise_to_zero` is `True`, the total NFE is (`steps` + 1). + + This trick is firstly proposed by DDPM (https://arxiv.org/abs/2006.11239) and + score_sde (https://arxiv.org/abs/2011.13456). Such trick can improve the FID + for diffusion models sampling by diffusion SDEs for low-resolutional images + (such as CIFAR-10). However, we observed that such trick does not matter for + high-resolutional images. As it needs an additional NFE, we do not recommend + it for high-resolutional images. + lower_order_final: A `bool`. Whether to use lower order solvers at the final steps. + Only valid for `method=multistep` and `steps < 15`. We empirically find that + this trick is a key to stabilizing the sampling by DPM-Solver with very few steps + (especially for steps <= 10). So we recommend to set it to be `True`. + solver_type: A `str`. The taylor expansion type for the solver. `dpm_solver` or `taylor`. We recommend `dpm_solver`. + atol: A `float`. The absolute tolerance of the adaptive step size solver. Valid when `method` == 'adaptive'. + rtol: A `float`. The relative tolerance of the adaptive step size solver. Valid when `method` == 'adaptive'. + Returns: + x_end: A pytorch tensor. The approximated solution at time `t_end`. + + """ + t_0 = 1. / self.noise_schedule.total_N if t_end is None else t_end + t_T = self.noise_schedule.T if t_start is None else t_start + device = x.device + if method == 'adaptive': + with torch.no_grad(): + x = self.dpm_solver_adaptive(x, order=order, t_T=t_T, t_0=t_0, atol=atol, rtol=rtol, + solver_type=solver_type) + elif method == 'multistep': + assert steps >= order + timesteps = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=steps, device=device) + assert timesteps.shape[0] - 1 == steps + with torch.no_grad(): + vec_t = timesteps[0].expand((x.shape[0])) + model_prev_list = [self.model_fn(x, vec_t)] + t_prev_list = [vec_t] + # Init the first `order` values by lower order multistep DPM-Solver. + for init_order in tqdm(range(1, order), desc="DPM init order"): + vec_t = timesteps[init_order].expand(x.shape[0]) + x = self.multistep_dpm_solver_update(x, model_prev_list, t_prev_list, vec_t, init_order, + solver_type=solver_type) + model_prev_list.append(self.model_fn(x, vec_t)) + t_prev_list.append(vec_t) + # Compute the remaining values by `order`-th order multistep DPM-Solver. + for step in tqdm(range(order, steps + 1), desc="DPM multistep"): + vec_t = timesteps[step].expand(x.shape[0]) + if lower_order_final and steps < 15: + step_order = min(order, steps + 1 - step) + else: + step_order = order + x = self.multistep_dpm_solver_update(x, model_prev_list, t_prev_list, vec_t, step_order, + solver_type=solver_type) + for i in range(order - 1): + t_prev_list[i] = t_prev_list[i + 1] + model_prev_list[i] = model_prev_list[i + 1] + t_prev_list[-1] = vec_t + # We do not need to evaluate the final model value. + if step < steps: + model_prev_list[-1] = self.model_fn(x, vec_t) + elif method in ['singlestep', 'singlestep_fixed']: + if method == 'singlestep': + timesteps_outer, orders = self.get_orders_and_timesteps_for_singlestep_solver(steps=steps, order=order, + skip_type=skip_type, + t_T=t_T, t_0=t_0, + device=device) + elif method == 'singlestep_fixed': + K = steps // order + orders = [order, ] * K + timesteps_outer = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=K, device=device) + for i, order in enumerate(orders): + t_T_inner, t_0_inner = timesteps_outer[i], timesteps_outer[i + 1] + timesteps_inner = self.get_time_steps(skip_type=skip_type, t_T=t_T_inner.item(), t_0=t_0_inner.item(), + N=order, device=device) + lambda_inner = self.noise_schedule.marginal_lambda(timesteps_inner) + vec_s, vec_t = t_T_inner.tile(x.shape[0]), t_0_inner.tile(x.shape[0]) + h = lambda_inner[-1] - lambda_inner[0] + r1 = None if order <= 1 else (lambda_inner[1] - lambda_inner[0]) / h + r2 = None if order <= 2 else (lambda_inner[2] - lambda_inner[0]) / h + x = self.singlestep_dpm_solver_update(x, vec_s, vec_t, order, solver_type=solver_type, r1=r1, r2=r2) + if denoise_to_zero: + x = self.denoise_to_zero_fn(x, torch.ones((x.shape[0],)).to(device) * t_0) + return x + + +############################################################# +# other utility functions +############################################################# + +def interpolate_fn(x, xp, yp): + """ + A piecewise linear function y = f(x), using xp and yp as keypoints. + We implement f(x) in a differentiable way (i.e. applicable for autograd). + The function f(x) is well-defined for all x-axis. (For x beyond the bounds of xp, we use the outmost points of xp to define the linear function.) + + Args: + x: PyTorch tensor with shape [N, C], where N is the batch size, C is the number of channels (we use C = 1 for DPM-Solver). + xp: PyTorch tensor with shape [C, K], where K is the number of keypoints. + yp: PyTorch tensor with shape [C, K]. + Returns: + The function values f(x), with shape [N, C]. + """ + N, K = x.shape[0], xp.shape[1] + all_x = torch.cat([x.unsqueeze(2), xp.unsqueeze(0).repeat((N, 1, 1))], dim=2) + sorted_all_x, x_indices = torch.sort(all_x, dim=2) + x_idx = torch.argmin(x_indices, dim=2) + cand_start_idx = x_idx - 1 + start_idx = torch.where( + torch.eq(x_idx, 0), + torch.tensor(1, device=x.device), + torch.where( + torch.eq(x_idx, K), torch.tensor(K - 2, device=x.device), cand_start_idx, + ), + ) + end_idx = torch.where(torch.eq(start_idx, cand_start_idx), start_idx + 2, start_idx + 1) + start_x = torch.gather(sorted_all_x, dim=2, index=start_idx.unsqueeze(2)).squeeze(2) + end_x = torch.gather(sorted_all_x, dim=2, index=end_idx.unsqueeze(2)).squeeze(2) + start_idx2 = torch.where( + torch.eq(x_idx, 0), + torch.tensor(0, device=x.device), + torch.where( + torch.eq(x_idx, K), torch.tensor(K - 2, device=x.device), cand_start_idx, + ), + ) + y_positions_expanded = yp.unsqueeze(0).expand(N, -1, -1) + start_y = torch.gather(y_positions_expanded, dim=2, index=start_idx2.unsqueeze(2)).squeeze(2) + end_y = torch.gather(y_positions_expanded, dim=2, index=(start_idx2 + 1).unsqueeze(2)).squeeze(2) + cand = start_y + (x - start_x) * (end_y - start_y) / (end_x - start_x) + return cand + + +def expand_dims(v, dims): + """ + Expand the tensor `v` to the dim `dims`. + + Args: + `v`: a PyTorch tensor with shape [N]. + `dim`: a `int`. + Returns: + a PyTorch tensor with shape [N, 1, 1, ..., 1] and the total dimension is `dims`. + """ + return v[(...,) + (None,) * (dims - 1)] diff --git a/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/sampler.py b/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/sampler.py new file mode 100644 index 0000000..fc2c96b --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/models/diffusion/dpm_solver/sampler.py @@ -0,0 +1,87 @@ +"""SAMPLING ONLY.""" +import torch + +from .dpm_solver import NoiseScheduleVP, model_wrapper, DPM_Solver + + +MODEL_TYPES = { + "eps": "noise", + "v": "v" +} + + +class DPMSolverSampler(object): + def __init__(self, model, **kwargs): + super().__init__() + self.model = model + to_torch = lambda x: x.clone().detach().to(torch.float32).to(model.device) + self.register_buffer('alphas_cumprod', to_torch(model.alphas_cumprod)) + + def register_buffer(self, name, attr): + if type(attr) == torch.Tensor: + if attr.device != torch.device("cuda"): + attr = attr.to(torch.device("cuda")) + setattr(self, name, attr) + + @torch.no_grad() + def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + cbs = conditioning[list(conditioning.keys())[0]].shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + + print(f'Data shape for DPM-Solver sampling is {size}, sampling steps {S}') + + device = self.model.betas.device + if x_T is None: + img = torch.randn(size, device=device) + else: + img = x_T + + ns = NoiseScheduleVP('discrete', alphas_cumprod=self.alphas_cumprod) + + model_fn = model_wrapper( + lambda x, t, c: self.model.apply_model(x, t, c), + ns, + model_type=MODEL_TYPES[self.model.parameterization], + guidance_type="classifier-free", + condition=conditioning, + unconditional_condition=unconditional_conditioning, + guidance_scale=unconditional_guidance_scale, + ) + + dpm_solver = DPM_Solver(model_fn, ns, predict_x0=True, thresholding=False) + x = dpm_solver.sample(img, steps=S, skip_type="time_uniform", method="multistep", order=2, lower_order_final=True) + + return x.to(device), None diff --git a/comparison_models/T2IAdapter/ldm/models/diffusion/plms.py b/comparison_models/T2IAdapter/ldm/models/diffusion/plms.py new file mode 100644 index 0000000..86175ee --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/models/diffusion/plms.py @@ -0,0 +1,243 @@ +"""SAMPLING ONLY.""" + +import torch +import numpy as np +from tqdm import tqdm +from comparison_models.T2IAdapter.ldm.modules.diffusionmodules.util import make_ddim_sampling_parameters, make_ddim_timesteps, noise_like + + +class PLMSSampler(object): + def __init__(self, model, schedule="linear", **kwargs): + super().__init__() + self.model = model + self.ddpm_num_timesteps = model.num_timesteps + self.schedule = schedule + + def register_buffer(self, name, attr): + if type(attr) == torch.Tensor: + if attr.device != torch.device("cuda"): + attr = attr.to(torch.device("cuda")) + setattr(self, name, attr) + + def make_schedule(self, ddim_num_steps, ddim_discretize="uniform", ddim_eta=0., verbose=True): + if ddim_eta != 0: + raise ValueError('ddim_eta must be 0 for PLMS') + self.ddim_timesteps = make_ddim_timesteps(ddim_discr_method=ddim_discretize, num_ddim_timesteps=ddim_num_steps, + num_ddpm_timesteps=self.ddpm_num_timesteps, verbose=verbose) + alphas_cumprod = self.model.alphas_cumprod + assert alphas_cumprod.shape[0] == self.ddpm_num_timesteps, 'alphas have to be defined for each timestep' + to_torch = lambda x: x.clone().detach().to(torch.float32).to(self.model.device) + + self.register_buffer('betas', to_torch(self.model.betas)) + self.register_buffer('alphas_cumprod', to_torch(alphas_cumprod)) + self.register_buffer('alphas_cumprod_prev', to_torch(self.model.alphas_cumprod_prev)) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod.cpu()))) + self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod.cpu()))) + self.register_buffer('log_one_minus_alphas_cumprod', to_torch(np.log(1. - alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recip_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recipm1_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu() - 1))) + + # ddim sampling parameters + ddim_sigmas, ddim_alphas, ddim_alphas_prev = make_ddim_sampling_parameters(alphacums=alphas_cumprod.cpu(), + ddim_timesteps=self.ddim_timesteps, + eta=ddim_eta, verbose=verbose) + self.register_buffer('ddim_sigmas', ddim_sigmas) + self.register_buffer('ddim_alphas', ddim_alphas) + self.register_buffer('ddim_alphas_prev', ddim_alphas_prev) + self.register_buffer('ddim_sqrt_one_minus_alphas', np.sqrt(1. - ddim_alphas)) + sigmas_for_original_sampling_steps = ddim_eta * torch.sqrt( + (1 - self.alphas_cumprod_prev) / (1 - self.alphas_cumprod) * ( + 1 - self.alphas_cumprod / self.alphas_cumprod_prev)) + self.register_buffer('ddim_sigmas_for_original_num_steps', sigmas_for_original_sampling_steps) + + @torch.no_grad() + def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + features_adapter=None, + cond_tau=0.4, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + **kwargs + ): + # print('*'*20,x_T) + # exit(0) + if conditioning is not None: + if isinstance(conditioning, dict): + cbs = conditioning[list(conditioning.keys())[0]].shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose) + C, H, W = shape + size = (batch_size, C, H, W) + print(f'Data shape for PLMS sampling is {size}') + + samples, intermediates = self.plms_sampling(conditioning, size, + callback=callback, + img_callback=img_callback, + quantize_denoised=quantize_x0, + mask=mask, x0=x0, + ddim_use_original_steps=False, + noise_dropout=noise_dropout, + temperature=temperature, + score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + x_T=x_T, + log_every_t=log_every_t, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + features_adapter=features_adapter, + cond_tau=cond_tau + ) + return samples, intermediates + + @torch.no_grad() + def plms_sampling(self, cond, shape, + x_T=None, ddim_use_original_steps=False, + callback=None, timesteps=None, quantize_denoised=False, + mask=None, x0=None, img_callback=None, log_every_t=100, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, features_adapter=None, + cond_tau=0.4): + device = self.model.betas.device + b = shape[0] + if x_T is None: + img = torch.randn(shape, device=device) + else: + img = x_T + if timesteps is None: + timesteps = self.ddpm_num_timesteps if ddim_use_original_steps else self.ddim_timesteps + elif timesteps is not None and not ddim_use_original_steps: + subset_end = int(min(timesteps / self.ddim_timesteps.shape[0], 1) * self.ddim_timesteps.shape[0]) - 1 + timesteps = self.ddim_timesteps[:subset_end] + + intermediates = {'x_inter': [img], 'pred_x0': [img]} + time_range = list(reversed(range(0, timesteps))) if ddim_use_original_steps else np.flip(timesteps) + total_steps = timesteps if ddim_use_original_steps else timesteps.shape[0] + print(f"Running PLMS Sampling with {total_steps} timesteps") + + iterator = tqdm(time_range, desc='PLMS Sampler', total=total_steps) + old_eps = [] + + for i, step in enumerate(iterator): + index = total_steps - i - 1 + ts = torch.full((b,), step, device=device, dtype=torch.long) + ts_next = torch.full((b,), time_range[min(i + 1, len(time_range) - 1)], device=device, dtype=torch.long) + + if mask is not None: # and index>=10: + assert x0 is not None + img_orig = self.model.q_sample(x0, ts) # TODO: deterministic forward pass? + img = img_orig * mask + (1. - mask) * img + + outs = self.p_sample_plms(img, cond, ts, index=index, use_original_steps=ddim_use_original_steps, + quantize_denoised=quantize_denoised, temperature=temperature, + noise_dropout=noise_dropout, score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + old_eps=old_eps, t_next=ts_next, + features_adapter=None if index < int( + (1 - cond_tau) * total_steps) else features_adapter) + + img, pred_x0, e_t = outs + old_eps.append(e_t) + if len(old_eps) >= 4: + old_eps.pop(0) + if callback: callback(i) + if img_callback: img_callback(pred_x0, i) + + if index % log_every_t == 0 or index == total_steps - 1: + intermediates['x_inter'].append(img) + intermediates['pred_x0'].append(pred_x0) + + return img, intermediates + + @torch.no_grad() + def p_sample_plms(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, old_eps=None, t_next=None, + features_adapter=None): + b, *_, device = *x.shape, x.device + + def get_model_output(x, t): + if unconditional_conditioning is None or unconditional_guidance_scale == 1.: + e_t = self.model.apply_model(x, t, c, features_adapter=features_adapter) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t] * 2) + c_in = torch.cat([unconditional_conditioning, c]) + e_t_uncond, e_t = self.model.apply_model(x_in, t_in, c_in, features_adapter=features_adapter).chunk(2) + e_t = e_t_uncond + unconditional_guidance_scale * (e_t - e_t_uncond) + + if score_corrector is not None: + assert self.model.parameterization == "eps" + e_t = score_corrector.modify_score(self.model, e_t, x, t, c, **corrector_kwargs) + + return e_t + + alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas + alphas_prev = self.model.alphas_cumprod_prev if use_original_steps else self.ddim_alphas_prev + sqrt_one_minus_alphas = self.model.sqrt_one_minus_alphas_cumprod if use_original_steps else self.ddim_sqrt_one_minus_alphas + sigmas = self.model.ddim_sigmas_for_original_num_steps if use_original_steps else self.ddim_sigmas + + def get_x_prev_and_pred_x0(e_t, index): + # select parameters corresponding to the currently considered timestep + a_t = torch.full((b, 1, 1, 1), alphas[index], device=device) + a_prev = torch.full((b, 1, 1, 1), alphas_prev[index], device=device) + sigma_t = torch.full((b, 1, 1, 1), sigmas[index], device=device) + sqrt_one_minus_at = torch.full((b, 1, 1, 1), sqrt_one_minus_alphas[index], device=device) + + # current prediction for x_0 + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + if quantize_denoised: + pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0) + # direction pointing to x_t + dir_xt = (1. - a_prev - sigma_t ** 2).sqrt() * e_t + noise = sigma_t * noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + x_prev = a_prev.sqrt() * pred_x0 + dir_xt + noise + return x_prev, pred_x0 + + e_t = get_model_output(x, t) + if len(old_eps) == 0: + # Pseudo Improved Euler (2nd order) + x_prev, pred_x0 = get_x_prev_and_pred_x0(e_t, index) + e_t_next = get_model_output(x_prev, t_next) + e_t_prime = (e_t + e_t_next) / 2 + elif len(old_eps) == 1: + # 2nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (3 * e_t - old_eps[-1]) / 2 + elif len(old_eps) == 2: + # 3nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (23 * e_t - 16 * old_eps[-1] + 5 * old_eps[-2]) / 12 + elif len(old_eps) >= 3: + # 4nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (55 * e_t - 59 * old_eps[-1] + 37 * old_eps[-2] - 9 * old_eps[-3]) / 24 + + x_prev, pred_x0 = get_x_prev_and_pred_x0(e_t_prime, index) + + return x_prev, pred_x0, e_t diff --git a/comparison_models/T2IAdapter/ldm/modules/attention.py b/comparison_models/T2IAdapter/ldm/modules/attention.py new file mode 100644 index 0000000..88a4d47 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/attention.py @@ -0,0 +1,344 @@ +from inspect import isfunction +import math +import torch +import torch.nn.functional as F +from torch import nn, einsum +from einops import rearrange, repeat +from typing import Optional, Any + +from ldm.modules.diffusionmodules.util import checkpoint + + +try: + import xformers + import xformers.ops + XFORMERS_IS_AVAILBLE = True +except: + XFORMERS_IS_AVAILBLE = False + +# CrossAttn precision handling +import os +_ATTN_PRECISION = os.environ.get("ATTN_PRECISION", "fp32") + +if os.environ.get("DISABLE_XFORMERS", "false").lower() == 'true': + XFORMERS_IS_AVAILBLE = False + + +def exists(val): + return val is not None + + +def uniq(arr): + return{el: True for el in arr}.keys() + + +def default(val, d): + if exists(val): + return val + return d() if isfunction(d) else d + + +def max_neg_value(t): + return -torch.finfo(t.dtype).max + + +def init_(tensor): + dim = tensor.shape[-1] + std = 1 / math.sqrt(dim) + tensor.uniform_(-std, std) + return tensor + + +# feedforward +class GEGLU(nn.Module): + def __init__(self, dim_in, dim_out): + super().__init__() + self.proj = nn.Linear(dim_in, dim_out * 2) + + def forward(self, x): + x, gate = self.proj(x).chunk(2, dim=-1) + return x * F.gelu(gate) + + +class FeedForward(nn.Module): + def __init__(self, dim, dim_out=None, mult=4, glu=False, dropout=0.): + super().__init__() + inner_dim = int(dim * mult) + dim_out = default(dim_out, dim) + project_in = nn.Sequential( + nn.Linear(dim, inner_dim), + nn.GELU() + ) if not glu else GEGLU(dim, inner_dim) + + self.net = nn.Sequential( + project_in, + nn.Dropout(dropout), + nn.Linear(inner_dim, dim_out) + ) + + def forward(self, x): + return self.net(x) + + +def zero_module(module): + """ + Zero out the parameters of a module and return it. + """ + for p in module.parameters(): + p.detach().zero_() + return module + + +def Normalize(in_channels): + return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True) + + +class SpatialSelfAttention(nn.Module): + def __init__(self, in_channels): + super().__init__() + self.in_channels = in_channels + + self.norm = Normalize(in_channels) + self.q = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.k = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.v = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.proj_out = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + + def forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + b,c,h,w = q.shape + q = rearrange(q, 'b c h w -> b (h w) c') + k = rearrange(k, 'b c h w -> b c (h w)') + w_ = torch.einsum('bij,bjk->bik', q, k) + + w_ = w_ * (int(c)**(-0.5)) + w_ = torch.nn.functional.softmax(w_, dim=2) + + # attend to values + v = rearrange(v, 'b c h w -> b c (h w)') + w_ = rearrange(w_, 'b i j -> b j i') + h_ = torch.einsum('bij,bjk->bik', v, w_) + h_ = rearrange(h_, 'b c (h w) -> b c h w', h=h) + h_ = self.proj_out(h_) + + return x+h_ + + +class CrossAttention(nn.Module): + def __init__(self, query_dim, context_dim=None, heads=8, dim_head=64, dropout=0.): + super().__init__() + inner_dim = dim_head * heads + context_dim = default(context_dim, query_dim) + + self.scale = dim_head ** -0.5 + self.heads = heads + + self.to_q = nn.Linear(query_dim, inner_dim, bias=False) + self.to_k = nn.Linear(context_dim, inner_dim, bias=False) + self.to_v = nn.Linear(context_dim, inner_dim, bias=False) + + self.to_out = nn.Sequential( + nn.Linear(inner_dim, query_dim), + nn.Dropout(dropout) + ) + + def forward(self, x, context=None, mask=None): + h = self.heads + + q = self.to_q(x) + context = default(context, x) + k = self.to_k(context) + v = self.to_v(context) + + q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v)) + + # force cast to fp32 to avoid overflowing + if _ATTN_PRECISION =="fp32": + with torch.autocast(enabled=False, device_type = 'cuda'): + q, k = q.float(), k.float() + sim = einsum('b i d, b j d -> b i j', q, k) * self.scale + else: + sim = einsum('b i d, b j d -> b i j', q, k) * self.scale + + del q, k + + if exists(mask): + mask = rearrange(mask, 'b ... -> b (...)') + max_neg_value = -torch.finfo(sim.dtype).max + mask = repeat(mask, 'b j -> (b h) () j', h=h) + sim.masked_fill_(~mask, max_neg_value) + + # attention, what we cannot get enough of + sim = sim.softmax(dim=-1) + + out = einsum('b i j, b j d -> b i d', sim, v) + out = rearrange(out, '(b h) n d -> b n (h d)', h=h) + return self.to_out(out) + + +class MemoryEfficientCrossAttention(nn.Module): + # https://github.com/MatthieuTPHR/diffusers/blob/d80b531ff8060ec1ea982b65a1b8df70f73aa67c/src/diffusers/models/attention.py#L223 + def __init__(self, query_dim, context_dim=None, heads=8, dim_head=64, dropout=0.0): + super().__init__() + print(f"Setting up {self.__class__.__name__}. Query dim is {query_dim}, context_dim is {context_dim} and using " + f"{heads} heads.") + inner_dim = dim_head * heads + context_dim = default(context_dim, query_dim) + + self.heads = heads + self.dim_head = dim_head + + self.to_q = nn.Linear(query_dim, inner_dim, bias=False) + self.to_k = nn.Linear(context_dim, inner_dim, bias=False) + self.to_v = nn.Linear(context_dim, inner_dim, bias=False) + + self.to_out = nn.Sequential(nn.Linear(inner_dim, query_dim), nn.Dropout(dropout)) + self.attention_op: Optional[Any] = None + + def forward(self, x, context=None, mask=None): + q = self.to_q(x) + context = default(context, x) + k = self.to_k(context) + v = self.to_v(context) + + b, _, _ = q.shape + q, k, v = map( + lambda t: t.unsqueeze(3) + .reshape(b, t.shape[1], self.heads, self.dim_head) + .permute(0, 2, 1, 3) + .reshape(b * self.heads, t.shape[1], self.dim_head) + .contiguous(), + (q, k, v), + ) + + # actually compute the attention, what we cannot get enough of + out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=None, op=self.attention_op) + + if exists(mask): + raise NotImplementedError + out = ( + out.unsqueeze(0) + .reshape(b, self.heads, out.shape[1], self.dim_head) + .permute(0, 2, 1, 3) + .reshape(b, out.shape[1], self.heads * self.dim_head) + ) + return self.to_out(out) + + +class BasicTransformerBlock(nn.Module): + ATTENTION_MODES = { + "softmax": CrossAttention, # vanilla attention + "softmax-xformers": MemoryEfficientCrossAttention + } + def __init__(self, dim, n_heads, d_head, dropout=0., context_dim=None, gated_ff=True, checkpoint=True, + disable_self_attn=False): + super().__init__() + attn_mode = "softmax-xformers" if XFORMERS_IS_AVAILBLE else "softmax" + assert attn_mode in self.ATTENTION_MODES + attn_cls = self.ATTENTION_MODES[attn_mode] + self.disable_self_attn = disable_self_attn + self.attn1 = attn_cls(query_dim=dim, heads=n_heads, dim_head=d_head, dropout=dropout, + context_dim=context_dim if self.disable_self_attn else None) # is a self-attention if not self.disable_self_attn + self.ff = FeedForward(dim, dropout=dropout, glu=gated_ff) + self.attn2 = attn_cls(query_dim=dim, context_dim=context_dim, + heads=n_heads, dim_head=d_head, dropout=dropout) # is self-attn if context is none + self.norm1 = nn.LayerNorm(dim) + self.norm2 = nn.LayerNorm(dim) + self.norm3 = nn.LayerNorm(dim) + self.checkpoint = checkpoint + + def forward(self, x, context=None): + return checkpoint(self._forward, (x, context), self.parameters(), self.checkpoint) + + def _forward(self, x, context=None): + x = self.attn1(self.norm1(x), context=context if self.disable_self_attn else None) + x + x = self.attn2(self.norm2(x), context=context) + x + x = self.ff(self.norm3(x)) + x + return x + + +class SpatialTransformer(nn.Module): + """ + Transformer block for image-like data. + First, project the input (aka embedding) + and reshape to b, t, d. + Then apply standard transformer action. + Finally, reshape to image + NEW: use_linear for more efficiency instead of the 1x1 convs + """ + def __init__(self, in_channels, n_heads, d_head, + depth=1, dropout=0., context_dim=None, + disable_self_attn=False, use_linear=False, + use_checkpoint=True): + super().__init__() + if exists(context_dim) and not isinstance(context_dim, list): + context_dim = [context_dim] + self.in_channels = in_channels + inner_dim = n_heads * d_head + self.norm = Normalize(in_channels) + if not use_linear: + self.proj_in = nn.Conv2d(in_channels, + inner_dim, + kernel_size=1, + stride=1, + padding=0) + else: + self.proj_in = nn.Linear(in_channels, inner_dim) + + self.transformer_blocks = nn.ModuleList( + [BasicTransformerBlock(inner_dim, n_heads, d_head, dropout=dropout, context_dim=context_dim[d], + disable_self_attn=disable_self_attn, checkpoint=use_checkpoint) + for d in range(depth)] + ) + if not use_linear: + self.proj_out = zero_module(nn.Conv2d(inner_dim, + in_channels, + kernel_size=1, + stride=1, + padding=0)) + else: + self.proj_out = zero_module(nn.Linear(in_channels, inner_dim)) + self.use_linear = use_linear + + def forward(self, x, context=None): + # note: if no context is given, cross-attention defaults to self-attention + if not isinstance(context, list): + context = [context] + b, c, h, w = x.shape + x_in = x + x = self.norm(x) + if not self.use_linear: + x = self.proj_in(x) + x = rearrange(x, 'b c h w -> b (h w) c').contiguous() + if self.use_linear: + x = self.proj_in(x) + for i, block in enumerate(self.transformer_blocks): + x = block(x, context=context[i]) + if self.use_linear: + x = self.proj_out(x) + x = rearrange(x, 'b (h w) c -> b c h w', h=h, w=w).contiguous() + if not self.use_linear: + x = self.proj_out(x) + return x + x_in \ No newline at end of file diff --git a/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/__init__.py b/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/model.py b/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/model.py new file mode 100644 index 0000000..b089eeb --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/model.py @@ -0,0 +1,852 @@ +# pytorch_diffusion + derived encoder decoder +import math +import torch +import torch.nn as nn +import numpy as np +from einops import rearrange +from typing import Optional, Any + +from ldm.modules.attention import MemoryEfficientCrossAttention + +try: + import xformers + import xformers.ops + XFORMERS_IS_AVAILBLE = True +except: + XFORMERS_IS_AVAILBLE = False + print("No module 'xformers'. Proceeding without it.") + + +def get_timestep_embedding(timesteps, embedding_dim): + """ + This matches the implementation in Denoising Diffusion Probabilistic Models: + From Fairseq. + Build sinusoidal embeddings. + This matches the implementation in tensor2tensor, but differs slightly + from the description in Section 3.5 of "Attention Is All You Need". + """ + assert len(timesteps.shape) == 1 + + half_dim = embedding_dim // 2 + emb = math.log(10000) / (half_dim - 1) + emb = torch.exp(torch.arange(half_dim, dtype=torch.float32) * -emb) + emb = emb.to(device=timesteps.device) + emb = timesteps.float()[:, None] * emb[None, :] + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1) + if embedding_dim % 2 == 1: # zero pad + emb = torch.nn.functional.pad(emb, (0,1,0,0)) + return emb + + +def nonlinearity(x): + # swish + return x*torch.sigmoid(x) + + +def Normalize(in_channels, num_groups=32): + return torch.nn.GroupNorm(num_groups=num_groups, num_channels=in_channels, eps=1e-6, affine=True) + + +class Upsample(nn.Module): + def __init__(self, in_channels, with_conv): + super().__init__() + self.with_conv = with_conv + if self.with_conv: + self.conv = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + x = torch.nn.functional.interpolate(x, scale_factor=2.0, mode="nearest") + if self.with_conv: + x = self.conv(x) + return x + + +class Downsample(nn.Module): + def __init__(self, in_channels, with_conv): + super().__init__() + self.with_conv = with_conv + if self.with_conv: + # no asymmetric padding in torch conv, must do it ourselves + self.conv = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=3, + stride=2, + padding=0) + + def forward(self, x): + if self.with_conv: + pad = (0,1,0,1) + x = torch.nn.functional.pad(x, pad, mode="constant", value=0) + x = self.conv(x) + else: + x = torch.nn.functional.avg_pool2d(x, kernel_size=2, stride=2) + return x + + +class ResnetBlock(nn.Module): + def __init__(self, *, in_channels, out_channels=None, conv_shortcut=False, + dropout, temb_channels=512): + super().__init__() + self.in_channels = in_channels + out_channels = in_channels if out_channels is None else out_channels + self.out_channels = out_channels + self.use_conv_shortcut = conv_shortcut + + self.norm1 = Normalize(in_channels) + self.conv1 = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + if temb_channels > 0: + self.temb_proj = torch.nn.Linear(temb_channels, + out_channels) + self.norm2 = Normalize(out_channels) + self.dropout = torch.nn.Dropout(dropout) + self.conv2 = torch.nn.Conv2d(out_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + if self.in_channels != self.out_channels: + if self.use_conv_shortcut: + self.conv_shortcut = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + else: + self.nin_shortcut = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=1, + stride=1, + padding=0) + + def forward(self, x, temb): + h = x + h = self.norm1(h) + h = nonlinearity(h) + h = self.conv1(h) + + if temb is not None: + h = h + self.temb_proj(nonlinearity(temb))[:,:,None,None] + + h = self.norm2(h) + h = nonlinearity(h) + h = self.dropout(h) + h = self.conv2(h) + + if self.in_channels != self.out_channels: + if self.use_conv_shortcut: + x = self.conv_shortcut(x) + else: + x = self.nin_shortcut(x) + + return x+h + + +class AttnBlock(nn.Module): + def __init__(self, in_channels): + super().__init__() + self.in_channels = in_channels + + self.norm = Normalize(in_channels) + self.q = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.k = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.v = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.proj_out = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + + def forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + b,c,h,w = q.shape + q = q.reshape(b,c,h*w) + q = q.permute(0,2,1) # b,hw,c + k = k.reshape(b,c,h*w) # b,c,hw + w_ = torch.bmm(q,k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j] + w_ = w_ * (int(c)**(-0.5)) + w_ = torch.nn.functional.softmax(w_, dim=2) + + # attend to values + v = v.reshape(b,c,h*w) + w_ = w_.permute(0,2,1) # b,hw,hw (first hw of k, second of q) + h_ = torch.bmm(v,w_) # b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j] + h_ = h_.reshape(b,c,h,w) + + h_ = self.proj_out(h_) + + return x+h_ + +class MemoryEfficientAttnBlock(nn.Module): + """ + Uses xformers efficient implementation, + see https://github.com/MatthieuTPHR/diffusers/blob/d80b531ff8060ec1ea982b65a1b8df70f73aa67c/src/diffusers/models/attention.py#L223 + Note: this is a single-head self-attention operation + """ + # + def __init__(self, in_channels): + super().__init__() + self.in_channels = in_channels + + self.norm = Normalize(in_channels) + self.q = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.k = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.v = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.proj_out = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.attention_op: Optional[Any] = None + + def forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + B, C, H, W = q.shape + q, k, v = map(lambda x: rearrange(x, 'b c h w -> b (h w) c'), (q, k, v)) + + q, k, v = map( + lambda t: t.unsqueeze(3) + .reshape(B, t.shape[1], 1, C) + .permute(0, 2, 1, 3) + .reshape(B * 1, t.shape[1], C) + .contiguous(), + (q, k, v), + ) + out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=None, op=self.attention_op) + + out = ( + out.unsqueeze(0) + .reshape(B, 1, out.shape[1], C) + .permute(0, 2, 1, 3) + .reshape(B, out.shape[1], C) + ) + out = rearrange(out, 'b (h w) c -> b c h w', b=B, h=H, w=W, c=C) + out = self.proj_out(out) + return x+out + + +class MemoryEfficientCrossAttentionWrapper(MemoryEfficientCrossAttention): + def forward(self, x, context=None, mask=None): + b, c, h, w = x.shape + x = rearrange(x, 'b c h w -> b (h w) c') + out = super().forward(x, context=context, mask=mask) + out = rearrange(out, 'b (h w) c -> b c h w', h=h, w=w, c=c) + return x + out + + +def make_attn(in_channels, attn_type="vanilla", attn_kwargs=None): + assert attn_type in ["vanilla", "vanilla-xformers", "memory-efficient-cross-attn", "linear", "none"], f'attn_type {attn_type} unknown' + if XFORMERS_IS_AVAILBLE and attn_type == "vanilla": + attn_type = "vanilla-xformers" + print(f"making attention of type '{attn_type}' with {in_channels} in_channels") + if attn_type == "vanilla": + assert attn_kwargs is None + return AttnBlock(in_channels) + elif attn_type == "vanilla-xformers": + print(f"building MemoryEfficientAttnBlock with {in_channels} in_channels...") + return MemoryEfficientAttnBlock(in_channels) + elif type == "memory-efficient-cross-attn": + attn_kwargs["query_dim"] = in_channels + return MemoryEfficientCrossAttentionWrapper(**attn_kwargs) + elif attn_type == "none": + return nn.Identity(in_channels) + else: + raise NotImplementedError() + + +class Model(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, use_timestep=True, use_linear_attn=False, attn_type="vanilla"): + super().__init__() + if use_linear_attn: attn_type = "linear" + self.ch = ch + self.temb_ch = self.ch*4 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + + self.use_timestep = use_timestep + if self.use_timestep: + # timestep embedding + self.temb = nn.Module() + self.temb.dense = nn.ModuleList([ + torch.nn.Linear(self.ch, + self.temb_ch), + torch.nn.Linear(self.temb_ch, + self.temb_ch), + ]) + + # downsampling + self.conv_in = torch.nn.Conv2d(in_channels, + self.ch, + kernel_size=3, + stride=1, + padding=1) + + curr_res = resolution + in_ch_mult = (1,)+tuple(ch_mult) + self.down = nn.ModuleList() + for i_level in range(self.num_resolutions): + block = nn.ModuleList() + attn = nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(make_attn(block_in, attn_type=attn_type)) + down = nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions-1: + down.downsample = Downsample(block_in, resamp_with_conv) + curr_res = curr_res // 2 + self.down.append(down) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = make_attn(block_in, attn_type=attn_type) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # upsampling + self.up = nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = nn.ModuleList() + attn = nn.ModuleList() + block_out = ch*ch_mult[i_level] + skip_in = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks+1): + if i_block == self.num_res_blocks: + skip_in = ch*in_ch_mult[i_level] + block.append(ResnetBlock(in_channels=block_in+skip_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(make_attn(block_in, attn_type=attn_type)) + up = nn.Module() + up.block = block + up.attn = attn + if i_level != 0: + up.upsample = Upsample(block_in, resamp_with_conv) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_ch, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x, t=None, context=None): + #assert x.shape[2] == x.shape[3] == self.resolution + if context is not None: + # assume aligned context, cat along channel axis + x = torch.cat((x, context), dim=1) + if self.use_timestep: + # timestep embedding + assert t is not None + temb = get_timestep_embedding(t, self.ch) + temb = self.temb.dense[0](temb) + temb = nonlinearity(temb) + temb = self.temb.dense[1](temb) + else: + temb = None + + # downsampling + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level].block[i_block](hs[-1], temb) + if len(self.down[i_level].attn) > 0: + h = self.down[i_level].attn[i_block](h) + hs.append(h) + if i_level != self.num_resolutions-1: + hs.append(self.down[i_level].downsample(hs[-1])) + + # middle + h = hs[-1] + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks+1): + h = self.up[i_level].block[i_block]( + torch.cat([h, hs.pop()], dim=1), temb) + if len(self.up[i_level].attn) > 0: + h = self.up[i_level].attn[i_block](h) + if i_level != 0: + h = self.up[i_level].upsample(h) + + # end + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + def get_last_layer(self): + return self.conv_out.weight + + +class Encoder(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, z_channels, double_z=True, use_linear_attn=False, attn_type="vanilla", + **ignore_kwargs): + super().__init__() + if use_linear_attn: attn_type = "linear" + self.ch = ch + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + + # downsampling + self.conv_in = torch.nn.Conv2d(in_channels, + self.ch, + kernel_size=3, + stride=1, + padding=1) + + curr_res = resolution + in_ch_mult = (1,)+tuple(ch_mult) + self.in_ch_mult = in_ch_mult + self.down = nn.ModuleList() + for i_level in range(self.num_resolutions): + block = nn.ModuleList() + attn = nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(make_attn(block_in, attn_type=attn_type)) + down = nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions-1: + down.downsample = Downsample(block_in, resamp_with_conv) + curr_res = curr_res // 2 + self.down.append(down) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = make_attn(block_in, attn_type=attn_type) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + 2*z_channels if double_z else z_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + # timestep embedding + temb = None + + # downsampling + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level].block[i_block](hs[-1], temb) + if len(self.down[i_level].attn) > 0: + h = self.down[i_level].attn[i_block](h) + hs.append(h) + if i_level != self.num_resolutions-1: + hs.append(self.down[i_level].downsample(hs[-1])) + + # middle + h = hs[-1] + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # end + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + +class Decoder(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, z_channels, give_pre_end=False, tanh_out=False, use_linear_attn=False, + attn_type="vanilla", **ignorekwargs): + super().__init__() + if use_linear_attn: attn_type = "linear" + self.ch = ch + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + self.give_pre_end = give_pre_end + self.tanh_out = tanh_out + + # compute in_ch_mult, block_in and curr_res at lowest res + in_ch_mult = (1,)+tuple(ch_mult) + block_in = ch*ch_mult[self.num_resolutions-1] + curr_res = resolution // 2**(self.num_resolutions-1) + self.z_shape = (1,z_channels,curr_res,curr_res) + print("Working with z of shape {} = {} dimensions.".format( + self.z_shape, np.prod(self.z_shape))) + + # z to block_in + self.conv_in = torch.nn.Conv2d(z_channels, + block_in, + kernel_size=3, + stride=1, + padding=1) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = make_attn(block_in, attn_type=attn_type) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # upsampling + self.up = nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = nn.ModuleList() + attn = nn.ModuleList() + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks+1): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(make_attn(block_in, attn_type=attn_type)) + up = nn.Module() + up.block = block + up.attn = attn + if i_level != 0: + up.upsample = Upsample(block_in, resamp_with_conv) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_ch, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, z): + #assert z.shape[1:] == self.z_shape[1:] + self.last_z_shape = z.shape + + # timestep embedding + temb = None + + # z to block_in + h = self.conv_in(z) + + # middle + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks+1): + h = self.up[i_level].block[i_block](h, temb) + if len(self.up[i_level].attn) > 0: + h = self.up[i_level].attn[i_block](h) + if i_level != 0: + h = self.up[i_level].upsample(h) + + # end + if self.give_pre_end: + return h + + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + if self.tanh_out: + h = torch.tanh(h) + return h + + +class SimpleDecoder(nn.Module): + def __init__(self, in_channels, out_channels, *args, **kwargs): + super().__init__() + self.model = nn.ModuleList([nn.Conv2d(in_channels, in_channels, 1), + ResnetBlock(in_channels=in_channels, + out_channels=2 * in_channels, + temb_channels=0, dropout=0.0), + ResnetBlock(in_channels=2 * in_channels, + out_channels=4 * in_channels, + temb_channels=0, dropout=0.0), + ResnetBlock(in_channels=4 * in_channels, + out_channels=2 * in_channels, + temb_channels=0, dropout=0.0), + nn.Conv2d(2*in_channels, in_channels, 1), + Upsample(in_channels, with_conv=True)]) + # end + self.norm_out = Normalize(in_channels) + self.conv_out = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + for i, layer in enumerate(self.model): + if i in [1,2,3]: + x = layer(x, None) + else: + x = layer(x) + + h = self.norm_out(x) + h = nonlinearity(h) + x = self.conv_out(h) + return x + + +class UpsampleDecoder(nn.Module): + def __init__(self, in_channels, out_channels, ch, num_res_blocks, resolution, + ch_mult=(2,2), dropout=0.0): + super().__init__() + # upsampling + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + block_in = in_channels + curr_res = resolution // 2 ** (self.num_resolutions - 1) + self.res_blocks = nn.ModuleList() + self.upsample_blocks = nn.ModuleList() + for i_level in range(self.num_resolutions): + res_block = [] + block_out = ch * ch_mult[i_level] + for i_block in range(self.num_res_blocks + 1): + res_block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + self.res_blocks.append(nn.ModuleList(res_block)) + if i_level != self.num_resolutions - 1: + self.upsample_blocks.append(Upsample(block_in, True)) + curr_res = curr_res * 2 + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + # upsampling + h = x + for k, i_level in enumerate(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks + 1): + h = self.res_blocks[i_level][i_block](h, None) + if i_level != self.num_resolutions - 1: + h = self.upsample_blocks[k](h) + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + +class LatentRescaler(nn.Module): + def __init__(self, factor, in_channels, mid_channels, out_channels, depth=2): + super().__init__() + # residual block, interpolate, residual block + self.factor = factor + self.conv_in = nn.Conv2d(in_channels, + mid_channels, + kernel_size=3, + stride=1, + padding=1) + self.res_block1 = nn.ModuleList([ResnetBlock(in_channels=mid_channels, + out_channels=mid_channels, + temb_channels=0, + dropout=0.0) for _ in range(depth)]) + self.attn = AttnBlock(mid_channels) + self.res_block2 = nn.ModuleList([ResnetBlock(in_channels=mid_channels, + out_channels=mid_channels, + temb_channels=0, + dropout=0.0) for _ in range(depth)]) + + self.conv_out = nn.Conv2d(mid_channels, + out_channels, + kernel_size=1, + ) + + def forward(self, x): + x = self.conv_in(x) + for block in self.res_block1: + x = block(x, None) + x = torch.nn.functional.interpolate(x, size=(int(round(x.shape[2]*self.factor)), int(round(x.shape[3]*self.factor)))) + x = self.attn(x) + for block in self.res_block2: + x = block(x, None) + x = self.conv_out(x) + return x + + +class MergedRescaleEncoder(nn.Module): + def __init__(self, in_channels, ch, resolution, out_ch, num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, + ch_mult=(1,2,4,8), rescale_factor=1.0, rescale_module_depth=1): + super().__init__() + intermediate_chn = ch * ch_mult[-1] + self.encoder = Encoder(in_channels=in_channels, num_res_blocks=num_res_blocks, ch=ch, ch_mult=ch_mult, + z_channels=intermediate_chn, double_z=False, resolution=resolution, + attn_resolutions=attn_resolutions, dropout=dropout, resamp_with_conv=resamp_with_conv, + out_ch=None) + self.rescaler = LatentRescaler(factor=rescale_factor, in_channels=intermediate_chn, + mid_channels=intermediate_chn, out_channels=out_ch, depth=rescale_module_depth) + + def forward(self, x): + x = self.encoder(x) + x = self.rescaler(x) + return x + + +class MergedRescaleDecoder(nn.Module): + def __init__(self, z_channels, out_ch, resolution, num_res_blocks, attn_resolutions, ch, ch_mult=(1,2,4,8), + dropout=0.0, resamp_with_conv=True, rescale_factor=1.0, rescale_module_depth=1): + super().__init__() + tmp_chn = z_channels*ch_mult[-1] + self.decoder = Decoder(out_ch=out_ch, z_channels=tmp_chn, attn_resolutions=attn_resolutions, dropout=dropout, + resamp_with_conv=resamp_with_conv, in_channels=None, num_res_blocks=num_res_blocks, + ch_mult=ch_mult, resolution=resolution, ch=ch) + self.rescaler = LatentRescaler(factor=rescale_factor, in_channels=z_channels, mid_channels=tmp_chn, + out_channels=tmp_chn, depth=rescale_module_depth) + + def forward(self, x): + x = self.rescaler(x) + x = self.decoder(x) + return x + + +class Upsampler(nn.Module): + def __init__(self, in_size, out_size, in_channels, out_channels, ch_mult=2): + super().__init__() + assert out_size >= in_size + num_blocks = int(np.log2(out_size//in_size))+1 + factor_up = 1.+ (out_size % in_size) + print(f"Building {self.__class__.__name__} with in_size: {in_size} --> out_size {out_size} and factor {factor_up}") + self.rescaler = LatentRescaler(factor=factor_up, in_channels=in_channels, mid_channels=2*in_channels, + out_channels=in_channels) + self.decoder = Decoder(out_ch=out_channels, resolution=out_size, z_channels=in_channels, num_res_blocks=2, + attn_resolutions=[], in_channels=None, ch=in_channels, + ch_mult=[ch_mult for _ in range(num_blocks)]) + + def forward(self, x): + x = self.rescaler(x) + x = self.decoder(x) + return x + + +class Resize(nn.Module): + def __init__(self, in_channels=None, learned=False, mode="bilinear"): + super().__init__() + self.with_conv = learned + self.mode = mode + if self.with_conv: + print(f"Note: {self.__class__.__name} uses learned downsampling and will ignore the fixed {mode} mode") + raise NotImplementedError() + assert in_channels is not None + # no asymmetric padding in torch conv, must do it ourselves + self.conv = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=4, + stride=2, + padding=1) + + def forward(self, x, scale_factor=1.0): + if scale_factor==1.0: + return x + else: + x = torch.nn.functional.interpolate(x, mode=self.mode, align_corners=False, scale_factor=scale_factor) + return x diff --git a/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/openaimodel.py b/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/openaimodel.py new file mode 100644 index 0000000..09972d5 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/openaimodel.py @@ -0,0 +1,798 @@ +from abc import abstractmethod +import math +import torch + +import numpy as np +import torch as th +import torch.nn as nn +import torch.nn.functional as F + +from ldm.modules.diffusionmodules.util import ( + checkpoint, + conv_nd, + linear, + avg_pool_nd, + zero_module, + normalization, + timestep_embedding, +) +from ldm.modules.attention import SpatialTransformer +from ldm.util import exists + + +# dummy replace +def convert_module_to_f16(x): + pass + +def convert_module_to_f32(x): + pass + + +## go +class AttentionPool2d(nn.Module): + """ + Adapted from CLIP: https://github.com/openai/CLIP/blob/main/clip/model.py + """ + + def __init__( + self, + spacial_dim: int, + embed_dim: int, + num_heads_channels: int, + output_dim: int = None, + ): + super().__init__() + self.positional_embedding = nn.Parameter(th.randn(embed_dim, spacial_dim ** 2 + 1) / embed_dim ** 0.5) + self.qkv_proj = conv_nd(1, embed_dim, 3 * embed_dim, 1) + self.c_proj = conv_nd(1, embed_dim, output_dim or embed_dim, 1) + self.num_heads = embed_dim // num_heads_channels + self.attention = QKVAttention(self.num_heads) + + def forward(self, x): + b, c, *_spatial = x.shape + x = x.reshape(b, c, -1) # NC(HW) + x = th.cat([x.mean(dim=-1, keepdim=True), x], dim=-1) # NC(HW+1) + x = x + self.positional_embedding[None, :, :].to(x.dtype) # NC(HW+1) + x = self.qkv_proj(x) + x = self.attention(x) + x = self.c_proj(x) + return x[:, :, 0] + + +class TimestepBlock(nn.Module): + """ + Any module where forward() takes timestep embeddings as a second argument. + """ + + @abstractmethod + def forward(self, x, emb): + """ + Apply the module to `x` given `emb` timestep embeddings. + """ + + +class TimestepEmbedSequential(nn.Sequential, TimestepBlock): + """ + A sequential module that passes timestep embeddings to the children that + support it as an extra input. + """ + + def forward(self, x, emb, context=None): + for layer in self: + if isinstance(layer, TimestepBlock): + x = layer(x, emb) + elif isinstance(layer, SpatialTransformer): + x = layer(x, context) + else: + x = layer(x) + return x + + +class Upsample(nn.Module): + """ + An upsampling layer with an optional convolution. + :param channels: channels in the inputs and outputs. + :param use_conv: a bool determining if a convolution is applied. + :param dims: determines if the signal is 1D, 2D, or 3D. If 3D, then + upsampling occurs in the inner-two dimensions. + """ + + def __init__(self, channels, use_conv, dims=2, out_channels=None, padding=1): + super().__init__() + self.channels = channels + self.out_channels = out_channels or channels + self.use_conv = use_conv + self.dims = dims + if use_conv: + self.conv = conv_nd(dims, self.channels, self.out_channels, 3, padding=padding) + + def forward(self, x): + assert x.shape[1] == self.channels + if self.dims == 3: + x = F.interpolate( + x, (x.shape[2], x.shape[3] * 2, x.shape[4] * 2), mode="nearest" + ) + else: + x = F.interpolate(x, scale_factor=2, mode="nearest") + if self.use_conv: + x = self.conv(x) + return x + +class TransposedUpsample(nn.Module): + 'Learned 2x upsampling without padding' + def __init__(self, channels, out_channels=None, ks=5): + super().__init__() + self.channels = channels + self.out_channels = out_channels or channels + + self.up = nn.ConvTranspose2d(self.channels,self.out_channels,kernel_size=ks,stride=2) + + def forward(self,x): + return self.up(x) + + +class Downsample(nn.Module): + """ + A downsampling layer with an optional convolution. + :param channels: channels in the inputs and outputs. + :param use_conv: a bool determining if a convolution is applied. + :param dims: determines if the signal is 1D, 2D, or 3D. If 3D, then + downsampling occurs in the inner-two dimensions. + """ + + def __init__(self, channels, use_conv, dims=2, out_channels=None,padding=1): + super().__init__() + self.channels = channels + self.out_channels = out_channels or channels + self.use_conv = use_conv + self.dims = dims + stride = 2 if dims != 3 else (1, 2, 2) + if use_conv: + self.op = conv_nd( + dims, self.channels, self.out_channels, 3, stride=stride, padding=padding + ) + else: + assert self.channels == self.out_channels + self.op = avg_pool_nd(dims, kernel_size=stride, stride=stride) + + def forward(self, x): + assert x.shape[1] == self.channels + return self.op(x) + + +class ResBlock(TimestepBlock): + """ + A residual block that can optionally change the number of channels. + :param channels: the number of input channels. + :param emb_channels: the number of timestep embedding channels. + :param dropout: the rate of dropout. + :param out_channels: if specified, the number of out channels. + :param use_conv: if True and out_channels is specified, use a spatial + convolution instead of a smaller 1x1 convolution to change the + channels in the skip connection. + :param dims: determines if the signal is 1D, 2D, or 3D. + :param use_checkpoint: if True, use gradient checkpointing on this module. + :param up: if True, use this block for upsampling. + :param down: if True, use this block for downsampling. + """ + + def __init__( + self, + channels, + emb_channels, + dropout, + out_channels=None, + use_conv=False, + use_scale_shift_norm=False, + dims=2, + use_checkpoint=False, + up=False, + down=False, + ): + super().__init__() + self.channels = channels + self.emb_channels = emb_channels + self.dropout = dropout + self.out_channels = out_channels or channels + self.use_conv = use_conv + self.use_checkpoint = use_checkpoint + self.use_scale_shift_norm = use_scale_shift_norm + + self.in_layers = nn.Sequential( + normalization(channels), + nn.SiLU(), + conv_nd(dims, channels, self.out_channels, 3, padding=1), + ) + + self.updown = up or down + + if up: + self.h_upd = Upsample(channels, False, dims) + self.x_upd = Upsample(channels, False, dims) + elif down: + self.h_upd = Downsample(channels, False, dims) + self.x_upd = Downsample(channels, False, dims) + else: + self.h_upd = self.x_upd = nn.Identity() + + self.emb_layers = nn.Sequential( + nn.SiLU(), + linear( + emb_channels, + 2 * self.out_channels if use_scale_shift_norm else self.out_channels, + ), + ) + self.out_layers = nn.Sequential( + normalization(self.out_channels), + nn.SiLU(), + nn.Dropout(p=dropout), + zero_module( + conv_nd(dims, self.out_channels, self.out_channels, 3, padding=1) + ), + ) + + if self.out_channels == channels: + self.skip_connection = nn.Identity() + elif use_conv: + self.skip_connection = conv_nd( + dims, channels, self.out_channels, 3, padding=1 + ) + else: + self.skip_connection = conv_nd(dims, channels, self.out_channels, 1) + + def forward(self, x, emb): + """ + Apply the block to a Tensor, conditioned on a timestep embedding. + :param x: an [N x C x ...] Tensor of features. + :param emb: an [N x emb_channels] Tensor of timestep embeddings. + :return: an [N x C x ...] Tensor of outputs. + """ + return checkpoint( + self._forward, (x, emb), self.parameters(), self.use_checkpoint + ) + + + def _forward(self, x, emb): + if self.updown: + in_rest, in_conv = self.in_layers[:-1], self.in_layers[-1] + h = in_rest(x) + h = self.h_upd(h) + x = self.x_upd(x) + h = in_conv(h) + else: + h = self.in_layers(x) + emb_out = self.emb_layers(emb).type(h.dtype) + while len(emb_out.shape) < len(h.shape): + emb_out = emb_out[..., None] + if self.use_scale_shift_norm: + out_norm, out_rest = self.out_layers[0], self.out_layers[1:] + scale, shift = th.chunk(emb_out, 2, dim=1) + h = out_norm(h) * (1 + scale) + shift + h = out_rest(h) + else: + h = h + emb_out + h = self.out_layers(h) + return self.skip_connection(x) + h + + +class AttentionBlock(nn.Module): + """ + An attention block that allows spatial positions to attend to each other. + Originally ported from here, but adapted to the N-d case. + https://github.com/hojonathanho/diffusion/blob/1e0dceb3b3495bbe19116a5e1b3596cd0706c543/diffusion_tf/models/unet.py#L66. + """ + + def __init__( + self, + channels, + num_heads=1, + num_head_channels=-1, + use_checkpoint=False, + use_new_attention_order=False, + ): + super().__init__() + self.channels = channels + if num_head_channels == -1: + self.num_heads = num_heads + else: + assert ( + channels % num_head_channels == 0 + ), f"q,k,v channels {channels} is not divisible by num_head_channels {num_head_channels}" + self.num_heads = channels // num_head_channels + self.use_checkpoint = use_checkpoint + self.norm = normalization(channels) + self.qkv = conv_nd(1, channels, channels * 3, 1) + if use_new_attention_order: + # split qkv before split heads + self.attention = QKVAttention(self.num_heads) + else: + # split heads before split qkv + self.attention = QKVAttentionLegacy(self.num_heads) + + self.proj_out = zero_module(conv_nd(1, channels, channels, 1)) + + def forward(self, x): + return checkpoint(self._forward, (x,), self.parameters(), True) # TODO: check checkpoint usage, is True # TODO: fix the .half call!!! + #return pt_checkpoint(self._forward, x) # pytorch + + def _forward(self, x): + b, c, *spatial = x.shape + x = x.reshape(b, c, -1) + qkv = self.qkv(self.norm(x)) + h = self.attention(qkv) + h = self.proj_out(h) + return (x + h).reshape(b, c, *spatial) + + +def count_flops_attn(model, _x, y): + """ + A counter for the `thop` package to count the operations in an + attention operation. + Meant to be used like: + macs, params = thop.profile( + model, + inputs=(inputs, timestamps), + custom_ops={QKVAttention: QKVAttention.count_flops}, + ) + """ + b, c, *spatial = y[0].shape + num_spatial = int(np.prod(spatial)) + # We perform two matmuls with the same number of ops. + # The first computes the weight matrix, the second computes + # the combination of the value vectors. + matmul_ops = 2 * b * (num_spatial ** 2) * c + model.total_ops += th.DoubleTensor([matmul_ops]) + + +class QKVAttentionLegacy(nn.Module): + """ + A module which performs QKV attention. Matches legacy QKVAttention + input/ouput heads shaping + """ + + def __init__(self, n_heads): + super().__init__() + self.n_heads = n_heads + + def forward(self, qkv): + """ + Apply QKV attention. + :param qkv: an [N x (H * 3 * C) x T] tensor of Qs, Ks, and Vs. + :return: an [N x (H * C) x T] tensor after attention. + """ + bs, width, length = qkv.shape + assert width % (3 * self.n_heads) == 0 + ch = width // (3 * self.n_heads) + q, k, v = qkv.reshape(bs * self.n_heads, ch * 3, length).split(ch, dim=1) + scale = 1 / math.sqrt(math.sqrt(ch)) + weight = th.einsum( + "bct,bcs->bts", q * scale, k * scale + ) # More stable with f16 than dividing afterwards + weight = th.softmax(weight.float(), dim=-1).type(weight.dtype) + a = th.einsum("bts,bcs->bct", weight, v) + return a.reshape(bs, -1, length) + + @staticmethod + def count_flops(model, _x, y): + return count_flops_attn(model, _x, y) + + +class QKVAttention(nn.Module): + """ + A module which performs QKV attention and splits in a different order. + """ + + def __init__(self, n_heads): + super().__init__() + self.n_heads = n_heads + + def forward(self, qkv): + """ + Apply QKV attention. + :param qkv: an [N x (3 * H * C) x T] tensor of Qs, Ks, and Vs. + :return: an [N x (H * C) x T] tensor after attention. + """ + bs, width, length = qkv.shape + assert width % (3 * self.n_heads) == 0 + ch = width // (3 * self.n_heads) + q, k, v = qkv.chunk(3, dim=1) + scale = 1 / math.sqrt(math.sqrt(ch)) + weight = th.einsum( + "bct,bcs->bts", + (q * scale).view(bs * self.n_heads, ch, length), + (k * scale).view(bs * self.n_heads, ch, length), + ) # More stable with f16 than dividing afterwards + weight = th.softmax(weight.float(), dim=-1).type(weight.dtype) + a = th.einsum("bts,bcs->bct", weight, v.reshape(bs * self.n_heads, ch, length)) + return a.reshape(bs, -1, length) + + @staticmethod + def count_flops(model, _x, y): + return count_flops_attn(model, _x, y) + + +class UNetModel(nn.Module): + """ + The full UNet model with attention and timestep embedding. + :param in_channels: channels in the input Tensor. + :param model_channels: base channel count for the model. + :param out_channels: channels in the output Tensor. + :param num_res_blocks: number of residual blocks per downsample. + :param attention_resolutions: a collection of downsample rates at which + attention will take place. May be a set, list, or tuple. + For example, if this contains 4, then at 4x downsampling, attention + will be used. + :param dropout: the dropout probability. + :param channel_mult: channel multiplier for each level of the UNet. + :param conv_resample: if True, use learned convolutions for upsampling and + downsampling. + :param dims: determines if the signal is 1D, 2D, or 3D. + :param num_classes: if specified (as an int), then this model will be + class-conditional with `num_classes` classes. + :param use_checkpoint: use gradient checkpointing to reduce memory usage. + :param num_heads: the number of attention heads in each attention layer. + :param num_heads_channels: if specified, ignore num_heads and instead use + a fixed channel width per attention head. + :param num_heads_upsample: works with num_heads to set a different number + of heads for upsampling. Deprecated. + :param use_scale_shift_norm: use a FiLM-like conditioning mechanism. + :param resblock_updown: use residual blocks for up/downsampling. + :param use_new_attention_order: use a different attention pattern for potentially + increased efficiency. + """ + + def __init__( + self, + image_size, + in_channels, + model_channels, + out_channels, + num_res_blocks, + attention_resolutions, + dropout=0, + channel_mult=(1, 2, 4, 8), + conv_resample=True, + dims=2, + num_classes=None, + use_checkpoint=False, + use_fp16=False, + num_heads=-1, + num_head_channels=-1, + num_heads_upsample=-1, + use_scale_shift_norm=False, + resblock_updown=False, + use_new_attention_order=False, + use_spatial_transformer=False, # custom transformer support + transformer_depth=1, # custom transformer support + context_dim=None, # custom transformer support + n_embed=None, # custom support for prediction of discrete ids into codebook of first stage vq model + legacy=True, + disable_self_attentions=None, + num_attention_blocks=None, + disable_middle_self_attn=False, + use_linear_in_transformer=False, + ): + super().__init__() + if use_spatial_transformer: + assert context_dim is not None, 'Fool!! You forgot to include the dimension of your cross-attention conditioning...' + + if context_dim is not None: + assert use_spatial_transformer, 'Fool!! You forgot to use the spatial transformer for your cross-attention conditioning...' + from omegaconf.listconfig import ListConfig + if type(context_dim) == ListConfig: + context_dim = list(context_dim) + + if num_heads_upsample == -1: + num_heads_upsample = num_heads + + if num_heads == -1: + assert num_head_channels != -1, 'Either num_heads or num_head_channels has to be set' + + if num_head_channels == -1: + assert num_heads != -1, 'Either num_heads or num_head_channels has to be set' + + self.image_size = image_size + self.in_channels = in_channels + self.model_channels = model_channels + self.out_channels = out_channels + if isinstance(num_res_blocks, int): + self.num_res_blocks = len(channel_mult) * [num_res_blocks] + else: + if len(num_res_blocks) != len(channel_mult): + raise ValueError("provide num_res_blocks either as an int (globally constant) or " + "as a list/tuple (per-level) with the same length as channel_mult") + self.num_res_blocks = num_res_blocks + if disable_self_attentions is not None: + # should be a list of booleans, indicating whether to disable self-attention in TransformerBlocks or not + assert len(disable_self_attentions) == len(channel_mult) + if num_attention_blocks is not None: + assert len(num_attention_blocks) == len(self.num_res_blocks) + assert all(map(lambda i: self.num_res_blocks[i] >= num_attention_blocks[i], range(len(num_attention_blocks)))) + print(f"Constructor of UNetModel received num_attention_blocks={num_attention_blocks}. " + f"This option has LESS priority than attention_resolutions {attention_resolutions}, " + f"i.e., in cases where num_attention_blocks[i] > 0 but 2**i not in attention_resolutions, " + f"attention will still not be set.") + + self.attention_resolutions = attention_resolutions + self.dropout = dropout + self.channel_mult = channel_mult + self.conv_resample = conv_resample + self.num_classes = num_classes + self.use_checkpoint = use_checkpoint + self.dtype = th.float16 if use_fp16 else th.float32 + self.num_heads = num_heads + self.num_head_channels = num_head_channels + self.num_heads_upsample = num_heads_upsample + self.predict_codebook_ids = n_embed is not None + + time_embed_dim = model_channels * 4 + self.time_embed = nn.Sequential( + linear(model_channels, time_embed_dim), + nn.SiLU(), + linear(time_embed_dim, time_embed_dim), + ) + + if self.num_classes is not None: + if isinstance(self.num_classes, int): + self.label_emb = nn.Embedding(num_classes, time_embed_dim) + elif self.num_classes == "continuous": + print("setting up linear c_adm embedding layer") + self.label_emb = nn.Linear(1, time_embed_dim) + else: + raise ValueError() + + self.input_blocks = nn.ModuleList( + [ + TimestepEmbedSequential( + conv_nd(dims, in_channels, model_channels, 3, padding=1) + ) + ] + ) + self._feature_size = model_channels + input_block_chans = [model_channels] + ch = model_channels + ds = 1 + for level, mult in enumerate(channel_mult): + for nr in range(self.num_res_blocks[level]): + layers = [ + ResBlock( + ch, + time_embed_dim, + dropout, + out_channels=mult * model_channels, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ) + ] + ch = mult * model_channels + if ds in attention_resolutions: + if num_head_channels == -1: + dim_head = ch // num_heads + else: + num_heads = ch // num_head_channels + dim_head = num_head_channels + if legacy: + #num_heads = 1 + dim_head = ch // num_heads if use_spatial_transformer else num_head_channels + if exists(disable_self_attentions): + disabled_sa = disable_self_attentions[level] + else: + disabled_sa = False + + if not exists(num_attention_blocks) or nr < num_attention_blocks[level]: + layers.append( + AttentionBlock( + ch, + use_checkpoint=use_checkpoint, + num_heads=num_heads, + num_head_channels=dim_head, + use_new_attention_order=use_new_attention_order, + ) if not use_spatial_transformer else SpatialTransformer( + ch, num_heads, dim_head, depth=transformer_depth, context_dim=context_dim, + disable_self_attn=disabled_sa, use_linear=use_linear_in_transformer, + use_checkpoint=use_checkpoint + ) + ) + self.input_blocks.append(TimestepEmbedSequential(*layers)) + self._feature_size += ch + input_block_chans.append(ch) + if level != len(channel_mult) - 1: + out_ch = ch + self.input_blocks.append( + TimestepEmbedSequential( + ResBlock( + ch, + time_embed_dim, + dropout, + out_channels=out_ch, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + down=True, + ) + if resblock_updown + else Downsample( + ch, conv_resample, dims=dims, out_channels=out_ch + ) + ) + ) + ch = out_ch + input_block_chans.append(ch) + ds *= 2 + self._feature_size += ch + + if num_head_channels == -1: + dim_head = ch // num_heads + else: + num_heads = ch // num_head_channels + dim_head = num_head_channels + if legacy: + #num_heads = 1 + dim_head = ch // num_heads if use_spatial_transformer else num_head_channels + self.middle_block = TimestepEmbedSequential( + ResBlock( + ch, + time_embed_dim, + dropout, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ), + AttentionBlock( + ch, + use_checkpoint=use_checkpoint, + num_heads=num_heads, + num_head_channels=dim_head, + use_new_attention_order=use_new_attention_order, + ) if not use_spatial_transformer else SpatialTransformer( # always uses a self-attn + ch, num_heads, dim_head, depth=transformer_depth, context_dim=context_dim, + disable_self_attn=disable_middle_self_attn, use_linear=use_linear_in_transformer, + use_checkpoint=use_checkpoint + ), + ResBlock( + ch, + time_embed_dim, + dropout, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ), + ) + self._feature_size += ch + + self.output_blocks = nn.ModuleList([]) + for level, mult in list(enumerate(channel_mult))[::-1]: + for i in range(self.num_res_blocks[level] + 1): + ich = input_block_chans.pop() + layers = [ + ResBlock( + ch + ich, + time_embed_dim, + dropout, + out_channels=model_channels * mult, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ) + ] + ch = model_channels * mult + if ds in attention_resolutions: + if num_head_channels == -1: + dim_head = ch // num_heads + else: + num_heads = ch // num_head_channels + dim_head = num_head_channels + if legacy: + #num_heads = 1 + dim_head = ch // num_heads if use_spatial_transformer else num_head_channels + if exists(disable_self_attentions): + disabled_sa = disable_self_attentions[level] + else: + disabled_sa = False + + if not exists(num_attention_blocks) or i < num_attention_blocks[level]: + layers.append( + AttentionBlock( + ch, + use_checkpoint=use_checkpoint, + num_heads=num_heads_upsample, + num_head_channels=dim_head, + use_new_attention_order=use_new_attention_order, + ) if not use_spatial_transformer else SpatialTransformer( + ch, num_heads, dim_head, depth=transformer_depth, context_dim=context_dim, + disable_self_attn=disabled_sa, use_linear=use_linear_in_transformer, + use_checkpoint=use_checkpoint + ) + ) + if level and i == self.num_res_blocks[level]: + out_ch = ch + layers.append( + ResBlock( + ch, + time_embed_dim, + dropout, + out_channels=out_ch, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + up=True, + ) + if resblock_updown + else Upsample(ch, conv_resample, dims=dims, out_channels=out_ch) + ) + ds //= 2 + self.output_blocks.append(TimestepEmbedSequential(*layers)) + self._feature_size += ch + + self.out = nn.Sequential( + normalization(ch), + nn.SiLU(), + zero_module(conv_nd(dims, model_channels, out_channels, 3, padding=1)), + ) + if self.predict_codebook_ids: + self.id_predictor = nn.Sequential( + normalization(ch), + conv_nd(dims, model_channels, n_embed, 1), + #nn.LogSoftmax(dim=1) # change to cross_entropy and produce non-normalized logits + ) + + def convert_to_fp16(self): + """ + Convert the torso of the model to float16. + """ + self.input_blocks.apply(convert_module_to_f16) + self.middle_block.apply(convert_module_to_f16) + self.output_blocks.apply(convert_module_to_f16) + + def convert_to_fp32(self): + """ + Convert the torso of the model to float32. + """ + self.input_blocks.apply(convert_module_to_f32) + self.middle_block.apply(convert_module_to_f32) + self.output_blocks.apply(convert_module_to_f32) + + def forward(self, x, timesteps=None, context=None, y=None, features_adapter=None, append_to_context=None, **kwargs): + """ + Apply the model to an input batch. + :param x: an [N x C x ...] Tensor of inputs. + :param timesteps: a 1-D batch of timesteps. + :param context: conditioning plugged in via crossattn + :param y: an [N] Tensor of labels, if class-conditional. + :return: an [N x C x ...] Tensor of outputs. + """ + assert (y is not None) == ( + self.num_classes is not None + ), "must specify y if and only if the model is class-conditional" + hs = [] + t_emb = timestep_embedding(timesteps, self.model_channels, repeat_only=False) + emb = self.time_embed(t_emb) + + if self.num_classes is not None: + assert y.shape[0] == x.shape[0] + emb = emb + self.label_emb(y) + + h = x.type(self.dtype) + + if append_to_context is not None: + context = torch.cat([context, append_to_context], dim=1) + + adapter_idx = 0 + for id, module in enumerate(self.input_blocks): + h = module(h, emb, context) + if ((id+1)%3 == 0) and features_adapter is not None: + h = h + features_adapter[adapter_idx] + adapter_idx += 1 + hs.append(h) + if features_adapter is not None: + assert len(features_adapter)==adapter_idx, 'Wrong features_adapter' + + h = self.middle_block(h, emb, context) + for module in self.output_blocks: + h = th.cat([h, hs.pop()], dim=1) + h = module(h, emb, context) + h = h.type(x.dtype) + if self.predict_codebook_ids: + return self.id_predictor(h) + else: + return self.out(h) diff --git a/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/util.py b/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/util.py new file mode 100644 index 0000000..cce0a64 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/diffusionmodules/util.py @@ -0,0 +1,270 @@ +# adopted from +# https://github.com/openai/improved-diffusion/blob/main/improved_diffusion/gaussian_diffusion.py +# and +# https://github.com/lucidrains/denoising-diffusion-pytorch/blob/7706bdfc6f527f58d33f84b7b522e61e6e3164b3/denoising_diffusion_pytorch/denoising_diffusion_pytorch.py +# and +# https://github.com/openai/guided-diffusion/blob/0ba878e517b276c45d1195eb29f6f5f72659a05b/guided_diffusion/nn.py +# +# thanks! + + +import os +import math +import torch +import torch.nn as nn +import numpy as np +from einops import repeat + +from comparison_models.T2IAdapter.ldm.util import instantiate_from_config + + +def make_beta_schedule(schedule, n_timestep, linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3): + if schedule == "linear": + betas = ( + torch.linspace(linear_start ** 0.5, linear_end ** 0.5, n_timestep, dtype=torch.float64) ** 2 + ) + + elif schedule == "cosine": + timesteps = ( + torch.arange(n_timestep + 1, dtype=torch.float64) / n_timestep + cosine_s + ) + alphas = timesteps / (1 + cosine_s) * np.pi / 2 + alphas = torch.cos(alphas).pow(2) + alphas = alphas / alphas[0] + betas = 1 - alphas[1:] / alphas[:-1] + betas = np.clip(betas, a_min=0, a_max=0.999) + + elif schedule == "sqrt_linear": + betas = torch.linspace(linear_start, linear_end, n_timestep, dtype=torch.float64) + elif schedule == "sqrt": + betas = torch.linspace(linear_start, linear_end, n_timestep, dtype=torch.float64) ** 0.5 + else: + raise ValueError(f"schedule '{schedule}' unknown.") + return betas.numpy() + + +def make_ddim_timesteps(ddim_discr_method, num_ddim_timesteps, num_ddpm_timesteps, verbose=True): + if ddim_discr_method == 'uniform': + c = num_ddpm_timesteps // num_ddim_timesteps + ddim_timesteps = np.asarray(list(range(0, num_ddpm_timesteps, c))) + elif ddim_discr_method == 'quad': + ddim_timesteps = ((np.linspace(0, np.sqrt(num_ddpm_timesteps * .8), num_ddim_timesteps)) ** 2).astype(int) + else: + raise NotImplementedError(f'There is no ddim discretization method called "{ddim_discr_method}"') + + # assert ddim_timesteps.shape[0] == num_ddim_timesteps + # add one to get the final alpha values right (the ones from first scale to data during sampling) + steps_out = ddim_timesteps + 1 + if verbose: + print(f'Selected timesteps for ddim sampler: {steps_out}') + return steps_out + + +def make_ddim_sampling_parameters(alphacums, ddim_timesteps, eta, verbose=True): + # select alphas for computing the variance schedule + alphas = alphacums[ddim_timesteps] + alphas_prev = np.asarray([alphacums[0]] + alphacums[ddim_timesteps[:-1]].tolist()) + + # according the the formula provided in https://arxiv.org/abs/2010.02502 + sigmas = eta * np.sqrt((1 - alphas_prev) / (1 - alphas) * (1 - alphas / alphas_prev)) + if verbose: + print(f'Selected alphas for ddim sampler: a_t: {alphas}; a_(t-1): {alphas_prev}') + print(f'For the chosen value of eta, which is {eta}, ' + f'this results in the following sigma_t schedule for ddim sampler {sigmas}') + return sigmas, alphas, alphas_prev + + +def betas_for_alpha_bar(num_diffusion_timesteps, alpha_bar, max_beta=0.999): + """ + Create a beta schedule that discretizes the given alpha_t_bar function, + which defines the cumulative product of (1-beta) over time from t = [0,1]. + :param num_diffusion_timesteps: the number of betas to produce. + :param alpha_bar: a lambda that takes an argument t from 0 to 1 and + produces the cumulative product of (1-beta) up to that + part of the diffusion process. + :param max_beta: the maximum beta to use; use values lower than 1 to + prevent singularities. + """ + betas = [] + for i in range(num_diffusion_timesteps): + t1 = i / num_diffusion_timesteps + t2 = (i + 1) / num_diffusion_timesteps + betas.append(min(1 - alpha_bar(t2) / alpha_bar(t1), max_beta)) + return np.array(betas) + + +def extract_into_tensor(a, t, x_shape): + b, *_ = t.shape + out = a.gather(-1, t) + return out.reshape(b, *((1,) * (len(x_shape) - 1))) + + +def checkpoint(func, inputs, params, flag): + """ + Evaluate a function without caching intermediate activations, allowing for + reduced memory at the expense of extra compute in the backward pass. + :param func: the function to evaluate. + :param inputs: the argument sequence to pass to `func`. + :param params: a sequence of parameters `func` depends on but does not + explicitly take as arguments. + :param flag: if False, disable gradient checkpointing. + """ + if flag: + args = tuple(inputs) + tuple(params) + return CheckpointFunction.apply(func, len(inputs), *args) + else: + return func(*inputs) + + +class CheckpointFunction(torch.autograd.Function): + @staticmethod + def forward(ctx, run_function, length, *args): + ctx.run_function = run_function + ctx.input_tensors = list(args[:length]) + ctx.input_params = list(args[length:]) + ctx.gpu_autocast_kwargs = {"enabled": torch.is_autocast_enabled(), + "dtype": torch.get_autocast_gpu_dtype(), + "cache_enabled": torch.is_autocast_cache_enabled()} + with torch.no_grad(): + output_tensors = ctx.run_function(*ctx.input_tensors) + return output_tensors + + @staticmethod + def backward(ctx, *output_grads): + ctx.input_tensors = [x.detach().requires_grad_(True) for x in ctx.input_tensors] + with torch.enable_grad(), \ + torch.cuda.amp.autocast(**ctx.gpu_autocast_kwargs): + # Fixes a bug where the first op in run_function modifies the + # Tensor storage in place, which is not allowed for detach()'d + # Tensors. + shallow_copies = [x.view_as(x) for x in ctx.input_tensors] + output_tensors = ctx.run_function(*shallow_copies) + input_grads = torch.autograd.grad( + output_tensors, + ctx.input_tensors + ctx.input_params, + output_grads, + allow_unused=True, + ) + del ctx.input_tensors + del ctx.input_params + del output_tensors + return (None, None) + input_grads + + +def timestep_embedding(timesteps, dim, max_period=10000, repeat_only=False): + """ + Create sinusoidal timestep embeddings. + :param timesteps: a 1-D Tensor of N indices, one per batch element. + These may be fractional. + :param dim: the dimension of the output. + :param max_period: controls the minimum frequency of the embeddings. + :return: an [N x dim] Tensor of positional embeddings. + """ + if not repeat_only: + half = dim // 2 + freqs = torch.exp( + -math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half + ).to(device=timesteps.device) + args = timesteps[:, None].float() * freqs[None] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + if dim % 2: + embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1) + else: + embedding = repeat(timesteps, 'b -> b d', d=dim) + return embedding + + +def zero_module(module): + """ + Zero out the parameters of a module and return it. + """ + for p in module.parameters(): + p.detach().zero_() + return module + + +def scale_module(module, scale): + """ + Scale the parameters of a module and return it. + """ + for p in module.parameters(): + p.detach().mul_(scale) + return module + + +def mean_flat(tensor): + """ + Take the mean over all non-batch dimensions. + """ + return tensor.mean(dim=list(range(1, len(tensor.shape)))) + + +def normalization(channels): + """ + Make a standard normalization layer. + :param channels: number of input channels. + :return: an nn.Module for normalization. + """ + return GroupNorm32(32, channels) + + +# PyTorch 1.7 has SiLU, but we support PyTorch 1.5. +class SiLU(nn.Module): + def forward(self, x): + return x * torch.sigmoid(x) + + +class GroupNorm32(nn.GroupNorm): + def forward(self, x): + return super().forward(x.float()).type(x.dtype) + +def conv_nd(dims, *args, **kwargs): + """ + Create a 1D, 2D, or 3D convolution module. + """ + if dims == 1: + return nn.Conv1d(*args, **kwargs) + elif dims == 2: + return nn.Conv2d(*args, **kwargs) + elif dims == 3: + return nn.Conv3d(*args, **kwargs) + raise ValueError(f"unsupported dimensions: {dims}") + + +def linear(*args, **kwargs): + """ + Create a linear module. + """ + return nn.Linear(*args, **kwargs) + + +def avg_pool_nd(dims, *args, **kwargs): + """ + Create a 1D, 2D, or 3D average pooling module. + """ + if dims == 1: + return nn.AvgPool1d(*args, **kwargs) + elif dims == 2: + return nn.AvgPool2d(*args, **kwargs) + elif dims == 3: + return nn.AvgPool3d(*args, **kwargs) + raise ValueError(f"unsupported dimensions: {dims}") + + +class HybridConditioner(nn.Module): + + def __init__(self, c_concat_config, c_crossattn_config): + super().__init__() + self.concat_conditioner = instantiate_from_config(c_concat_config) + self.crossattn_conditioner = instantiate_from_config(c_crossattn_config) + + def forward(self, c_concat, c_crossattn): + c_concat = self.concat_conditioner(c_concat) + c_crossattn = self.crossattn_conditioner(c_crossattn) + return {'c_concat': [c_concat], 'c_crossattn': [c_crossattn]} + + +def noise_like(shape, device, repeat=False): + repeat_noise = lambda: torch.randn((1, *shape[1:]), device=device).repeat(shape[0], *((1,) * (len(shape) - 1))) + noise = lambda: torch.randn(shape, device=device) + return repeat_noise() if repeat else noise() \ No newline at end of file diff --git a/comparison_models/T2IAdapter/ldm/modules/distributions/__init__.py b/comparison_models/T2IAdapter/ldm/modules/distributions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/ldm/modules/distributions/distributions.py b/comparison_models/T2IAdapter/ldm/modules/distributions/distributions.py new file mode 100644 index 0000000..f2b8ef9 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/distributions/distributions.py @@ -0,0 +1,92 @@ +import torch +import numpy as np + + +class AbstractDistribution: + def sample(self): + raise NotImplementedError() + + def mode(self): + raise NotImplementedError() + + +class DiracDistribution(AbstractDistribution): + def __init__(self, value): + self.value = value + + def sample(self): + return self.value + + def mode(self): + return self.value + + +class DiagonalGaussianDistribution(object): + def __init__(self, parameters, deterministic=False): + self.parameters = parameters + self.mean, self.logvar = torch.chunk(parameters, 2, dim=1) + self.logvar = torch.clamp(self.logvar, -30.0, 20.0) + self.deterministic = deterministic + self.std = torch.exp(0.5 * self.logvar) + self.var = torch.exp(self.logvar) + if self.deterministic: + self.var = self.std = torch.zeros_like(self.mean).to(device=self.parameters.device) + + def sample(self): + x = self.mean + self.std * torch.randn(self.mean.shape).to(device=self.parameters.device) + return x + + def kl(self, other=None): + if self.deterministic: + return torch.Tensor([0.]) + else: + if other is None: + return 0.5 * torch.sum(torch.pow(self.mean, 2) + + self.var - 1.0 - self.logvar, + dim=[1, 2, 3]) + else: + return 0.5 * torch.sum( + torch.pow(self.mean - other.mean, 2) / other.var + + self.var / other.var - 1.0 - self.logvar + other.logvar, + dim=[1, 2, 3]) + + def nll(self, sample, dims=[1,2,3]): + if self.deterministic: + return torch.Tensor([0.]) + logtwopi = np.log(2.0 * np.pi) + return 0.5 * torch.sum( + logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var, + dim=dims) + + def mode(self): + return self.mean + + +def normal_kl(mean1, logvar1, mean2, logvar2): + """ + source: https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/losses.py#L12 + Compute the KL divergence between two gaussians. + Shapes are automatically broadcasted, so batches can be compared to + scalars, among other use cases. + """ + tensor = None + for obj in (mean1, logvar1, mean2, logvar2): + if isinstance(obj, torch.Tensor): + tensor = obj + break + assert tensor is not None, "at least one argument must be a Tensor" + + # Force variances to be Tensors. Broadcasting helps convert scalars to + # Tensors, but it does not work for torch.exp(). + logvar1, logvar2 = [ + x if isinstance(x, torch.Tensor) else torch.tensor(x).to(tensor) + for x in (logvar1, logvar2) + ] + + return 0.5 * ( + -1.0 + + logvar2 + - logvar1 + + torch.exp(logvar1 - logvar2) + + ((mean1 - mean2) ** 2) * torch.exp(-logvar2) + ) diff --git a/comparison_models/T2IAdapter/ldm/modules/ema.py b/comparison_models/T2IAdapter/ldm/modules/ema.py new file mode 100644 index 0000000..bded250 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/ema.py @@ -0,0 +1,80 @@ +import torch +from torch import nn + + +class LitEma(nn.Module): + def __init__(self, model, decay=0.9999, use_num_upates=True): + super().__init__() + if decay < 0.0 or decay > 1.0: + raise ValueError('Decay must be between 0 and 1') + + self.m_name2s_name = {} + self.register_buffer('decay', torch.tensor(decay, dtype=torch.float32)) + self.register_buffer('num_updates', torch.tensor(0, dtype=torch.int) if use_num_upates + else torch.tensor(-1, dtype=torch.int)) + + for name, p in model.named_parameters(): + if p.requires_grad: + # remove as '.'-character is not allowed in buffers + s_name = name.replace('.', '') + self.m_name2s_name.update({name: s_name}) + self.register_buffer(s_name, p.clone().detach().data) + + self.collected_params = [] + + def reset_num_updates(self): + del self.num_updates + self.register_buffer('num_updates', torch.tensor(0, dtype=torch.int)) + + def forward(self, model): + decay = self.decay + + if self.num_updates >= 0: + self.num_updates += 1 + decay = min(self.decay, (1 + self.num_updates) / (10 + self.num_updates)) + + one_minus_decay = 1.0 - decay + + with torch.no_grad(): + m_param = dict(model.named_parameters()) + shadow_params = dict(self.named_buffers()) + + for key in m_param: + if m_param[key].requires_grad: + sname = self.m_name2s_name[key] + shadow_params[sname] = shadow_params[sname].type_as(m_param[key]) + shadow_params[sname].sub_(one_minus_decay * (shadow_params[sname] - m_param[key])) + else: + assert not key in self.m_name2s_name + + def copy_to(self, model): + m_param = dict(model.named_parameters()) + shadow_params = dict(self.named_buffers()) + for key in m_param: + if m_param[key].requires_grad: + m_param[key].data.copy_(shadow_params[self.m_name2s_name[key]].data) + else: + assert not key in self.m_name2s_name + + def store(self, parameters): + """ + Save the current parameters for restoring later. + Args: + parameters: Iterable of `torch.nn.Parameter`; the parameters to be + temporarily stored. + """ + self.collected_params = [param.clone() for param in parameters] + + def restore(self, parameters): + """ + Restore the parameters stored with the `store` method. + Useful to validate the model with EMA parameters without affecting the + original optimization process. Store the parameters before the + `copy_to` method. After validation (or model saving), use this to + restore the former parameters. + Args: + parameters: Iterable of `torch.nn.Parameter`; the parameters to be + updated with the stored parameters. + """ + for c_param, param in zip(self.collected_params, parameters): + param.data.copy_(c_param.data) diff --git a/comparison_models/T2IAdapter/ldm/modules/encoders/__init__.py b/comparison_models/T2IAdapter/ldm/modules/encoders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/ldm/modules/encoders/adapter.py b/comparison_models/T2IAdapter/ldm/modules/encoders/adapter.py new file mode 100644 index 0000000..c7f9028 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/encoders/adapter.py @@ -0,0 +1,339 @@ +import torch +import torch.nn as nn +from collections import OrderedDict +from comparison_models.T2IAdapter.ldm.modules.extra_condition.api import ExtraCondition +from comparison_models.T2IAdapter.ldm.modules.diffusionmodules.util import zero_module + + +def conv_nd(dims, *args, **kwargs): + """ + Create a 1D, 2D, or 3D convolution module. + """ + if dims == 1: + return nn.Conv1d(*args, **kwargs) + elif dims == 2: + return nn.Conv2d(*args, **kwargs) + elif dims == 3: + return nn.Conv3d(*args, **kwargs) + raise ValueError(f"unsupported dimensions: {dims}") + + +def avg_pool_nd(dims, *args, **kwargs): + """ + Create a 1D, 2D, or 3D average pooling module. + """ + if dims == 1: + return nn.AvgPool1d(*args, **kwargs) + elif dims == 2: + return nn.AvgPool2d(*args, **kwargs) + elif dims == 3: + return nn.AvgPool3d(*args, **kwargs) + raise ValueError(f"unsupported dimensions: {dims}") + + +class Downsample(nn.Module): + """ + A downsampling layer with an optional convolution. + :param channels: channels in the inputs and outputs. + :param use_conv: a bool determining if a convolution is applied. + :param dims: determines if the signal is 1D, 2D, or 3D. If 3D, then + downsampling occurs in the inner-two dimensions. + """ + + def __init__(self, channels, use_conv, dims=2, out_channels=None, padding=1): + super().__init__() + self.channels = channels + self.out_channels = out_channels or channels + self.use_conv = use_conv + self.dims = dims + stride = 2 if dims != 3 else (1, 2, 2) + if use_conv: + self.op = conv_nd( + dims, self.channels, self.out_channels, 3, stride=stride, padding=padding + ) + else: + assert self.channels == self.out_channels + self.op = avg_pool_nd(dims, kernel_size=stride, stride=stride) + + def forward(self, x): + assert x.shape[1] == self.channels + return self.op(x) + + +class ResnetBlock(nn.Module): + def __init__(self, in_c, out_c, down, ksize=3, sk=False, use_conv=True): + super().__init__() + ps = ksize // 2 + if in_c != out_c or sk == False: + self.in_conv = nn.Conv2d(in_c, out_c, ksize, 1, ps) + else: + # print('n_in') + self.in_conv = None + self.block1 = nn.Conv2d(out_c, out_c, 3, 1, 1) + self.act = nn.ReLU() + self.block2 = nn.Conv2d(out_c, out_c, ksize, 1, ps) + if sk == False: + self.skep = nn.Conv2d(out_c, out_c, ksize, 1, ps) + else: + self.skep = None + + self.down = down + if self.down == True: + self.down_opt = Downsample(in_c, use_conv=use_conv) + + def forward(self, x): + if self.down == True: + x = self.down_opt(x) + if self.in_conv is not None: # edit + x = self.in_conv(x) + + h = self.block1(x) + h = self.act(h) + h = self.block2(h) + if self.skep is not None: + return h + self.skep(x) + else: + return h + x + + +class Adapter(nn.Module): + def __init__(self, channels=[320, 640, 1280, 1280], nums_rb=3, cin=64, ksize=3, sk=False, use_conv=True): + super(Adapter, self).__init__() + self.unshuffle = nn.PixelUnshuffle(8) + self.channels = channels + self.nums_rb = nums_rb + self.body = [] + for i in range(len(channels)): + for j in range(nums_rb): + if (i != 0) and (j == 0): + self.body.append( + ResnetBlock(channels[i - 1], channels[i], down=True, ksize=ksize, sk=sk, use_conv=use_conv)) + else: + self.body.append( + ResnetBlock(channels[i], channels[i], down=False, ksize=ksize, sk=sk, use_conv=use_conv)) + self.body = nn.ModuleList(self.body) + self.conv_in = nn.Conv2d(cin, channels[0], 3, 1, 1) + + def forward(self, x): + # unshuffle + x = self.unshuffle(x) + # extract features + features = [] + x = self.conv_in(x) + for i in range(len(self.channels)): + for j in range(self.nums_rb): + idx = i * self.nums_rb + j + x = self.body[idx](x) + features.append(x) + + return features + + +class LayerNorm(nn.LayerNorm): + """Subclass torch's LayerNorm to handle fp16.""" + + def forward(self, x: torch.Tensor): + orig_type = x.dtype + ret = super().forward(x.type(torch.float32)) + return ret.type(orig_type) + + +class QuickGELU(nn.Module): + + def forward(self, x: torch.Tensor): + return x * torch.sigmoid(1.702 * x) + + +class ResidualAttentionBlock(nn.Module): + + 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 + + def attention(self, x: torch.Tensor): + 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): + x = x + self.attention(self.ln_1(x)) + x = x + self.mlp(self.ln_2(x)) + return x + + +class StyleAdapter(nn.Module): + + def __init__(self, width=1024, context_dim=768, num_head=8, n_layes=3, num_token=4): + super().__init__() + + scale = width ** -0.5 + self.transformer_layes = nn.Sequential(*[ResidualAttentionBlock(width, num_head) for _ in range(n_layes)]) + self.num_token = num_token + self.style_embedding = nn.Parameter(torch.randn(1, num_token, width) * scale) + self.ln_post = LayerNorm(width) + self.ln_pre = LayerNorm(width) + self.proj = nn.Parameter(scale * torch.randn(width, context_dim)) + + def forward(self, x): + # x shape [N, HW+1, C] + style_embedding = self.style_embedding + torch.zeros( + (x.shape[0], self.num_token, self.style_embedding.shape[-1]), device=x.device) + x = torch.cat([x, style_embedding], dim=1) + x = self.ln_pre(x) + x = x.permute(1, 0, 2) # NLD -> LND + x = self.transformer_layes(x) + x = x.permute(1, 0, 2) # LND -> NLD + + x = self.ln_post(x[:, -self.num_token:, :]) + x = x @ self.proj + + return x + + +class ResnetBlock_light(nn.Module): + def __init__(self, in_c): + super().__init__() + self.block1 = nn.Conv2d(in_c, in_c, 3, 1, 1) + self.act = nn.ReLU() + self.block2 = nn.Conv2d(in_c, in_c, 3, 1, 1) + + def forward(self, x): + h = self.block1(x) + h = self.act(h) + h = self.block2(h) + + return h + x + + +class extractor(nn.Module): + def __init__(self, in_c, inter_c, out_c, nums_rb, down=False): + super().__init__() + self.in_conv = nn.Conv2d(in_c, inter_c, 1, 1, 0) + self.body = [] + for _ in range(nums_rb): + self.body.append(ResnetBlock_light(inter_c)) + self.body = nn.Sequential(*self.body) + self.out_conv = nn.Conv2d(inter_c, out_c, 1, 1, 0) + self.down = down + if self.down == True: + self.down_opt = Downsample(in_c, use_conv=False) + + def forward(self, x): + if self.down == True: + x = self.down_opt(x) + x = self.in_conv(x) + x = self.body(x) + x = self.out_conv(x) + + return x + + +class Adapter_light(nn.Module): + def __init__(self, channels=[320, 640, 1280, 1280], nums_rb=3, cin=64): + super(Adapter_light, self).__init__() + self.unshuffle = nn.PixelUnshuffle(8) + self.channels = channels + self.nums_rb = nums_rb + self.body = [] + for i in range(len(channels)): + if i == 0: + self.body.append(extractor(in_c=cin, inter_c=channels[i]//4, out_c=channels[i], nums_rb=nums_rb, down=False)) + else: + self.body.append(extractor(in_c=channels[i-1], inter_c=channels[i]//4, out_c=channels[i], nums_rb=nums_rb, down=True)) + self.body = nn.ModuleList(self.body) + + def forward(self, x): + # unshuffle + x = self.unshuffle(x) + # extract features + features = [] + for i in range(len(self.channels)): + x = self.body[i](x) + features.append(x) + + return features + + +class CoAdapterFuser(nn.Module): + def __init__(self, unet_channels=[320, 640, 1280, 1280], width=768, num_head=8, n_layes=3): + super(CoAdapterFuser, self).__init__() + scale = width ** 0.5 + # 16, maybe large enough for the number of adapters? + self.task_embedding = nn.Parameter(scale * torch.randn(16, width)) + self.positional_embedding = nn.Parameter(scale * torch.randn(len(unet_channels), width)) + self.spatial_feat_mapping = nn.ModuleList() + for ch in unet_channels: + self.spatial_feat_mapping.append(nn.Sequential( + nn.SiLU(), + nn.Linear(ch, width), + )) + self.transformer_layes = nn.Sequential(*[ResidualAttentionBlock(width, num_head) for _ in range(n_layes)]) + self.ln_post = LayerNorm(width) + self.ln_pre = LayerNorm(width) + self.spatial_ch_projs = nn.ModuleList() + for ch in unet_channels: + self.spatial_ch_projs.append(zero_module(nn.Linear(width, ch))) + self.seq_proj = nn.Parameter(torch.zeros(width, width)) + + def forward(self, features): + if len(features) == 0: + return None, None + inputs = [] + for cond_name in features.keys(): + task_idx = getattr(ExtraCondition, cond_name).value + if not isinstance(features[cond_name], list): + inputs.append(features[cond_name] + self.task_embedding[task_idx]) + continue + + feat_seq = [] + for idx, feature_map in enumerate(features[cond_name]): + feature_vec = torch.mean(feature_map, dim=(2, 3)) + feature_vec = self.spatial_feat_mapping[idx](feature_vec) + feat_seq.append(feature_vec) + feat_seq = torch.stack(feat_seq, dim=1) # Nx4xC + feat_seq = feat_seq + self.task_embedding[task_idx] + feat_seq = feat_seq + self.positional_embedding + inputs.append(feat_seq) + + x = torch.cat(inputs, dim=1) # NxLxC + x = self.ln_pre(x) + x = x.permute(1, 0, 2) # NLD -> LND + x = self.transformer_layes(x) + x = x.permute(1, 0, 2) # LND -> NLD + x = self.ln_post(x) + + ret_feat_map = None + ret_feat_seq = None + cur_seq_idx = 0 + for cond_name in features.keys(): + if not isinstance(features[cond_name], list): + length = features[cond_name].size(1) + transformed_feature = features[cond_name] * ((x[:, cur_seq_idx:cur_seq_idx+length] @ self.seq_proj) + 1) + if ret_feat_seq is None: + ret_feat_seq = transformed_feature + else: + ret_feat_seq = torch.cat([ret_feat_seq, transformed_feature], dim=1) + cur_seq_idx += length + continue + + length = len(features[cond_name]) + transformed_feature_list = [] + for idx in range(length): + alpha = self.spatial_ch_projs[idx](x[:, cur_seq_idx+idx]) + alpha = alpha.unsqueeze(-1).unsqueeze(-1) + 1 + transformed_feature_list.append(features[cond_name][idx] * alpha) + if ret_feat_map is None: + ret_feat_map = transformed_feature_list + else: + ret_feat_map = list(map(lambda x, y: x + y, ret_feat_map, transformed_feature_list)) + cur_seq_idx += length + + assert cur_seq_idx == x.size(1) + + return ret_feat_map, ret_feat_seq diff --git a/comparison_models/T2IAdapter/ldm/modules/encoders/modules.py b/comparison_models/T2IAdapter/ldm/modules/encoders/modules.py new file mode 100644 index 0000000..d59229a --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/encoders/modules.py @@ -0,0 +1,441 @@ +import torch +import torch.nn as nn +import math +from torch.utils.checkpoint import checkpoint + +from transformers import T5Tokenizer, T5EncoderModel, CLIPTokenizer, CLIPTextModel, CLIPModel + +import open_clip +import re +from ldm.util import default, count_params + + +class AbstractEncoder(nn.Module): + def __init__(self): + super().__init__() + + def encode(self, *args, **kwargs): + raise NotImplementedError + + +class IdentityEncoder(AbstractEncoder): + + def encode(self, x): + return x + + +class ClassEmbedder(nn.Module): + def __init__(self, embed_dim, n_classes=1000, key='class'): + super().__init__() + self.key = key + self.embedding = nn.Embedding(n_classes, embed_dim) + + def forward(self, batch, key=None): + if key is None: + key = self.key + # this is for use in crossattn + c = batch[key][:, None] + c = self.embedding(c) + return c + + +class FrozenT5Embedder(AbstractEncoder): + """Uses the T5 transformer encoder for text""" + def __init__(self, version="google/t5-v1_1-large", device="cuda", max_length=77, freeze=True): # others are google/t5-v1_1-xl and google/t5-v1_1-xxl + super().__init__() + self.tokenizer = T5Tokenizer.from_pretrained(version) + self.transformer = T5EncoderModel.from_pretrained(version) + self.device = device + self.max_length = max_length # TODO: typical value? + if freeze: + self.freeze() + + def freeze(self): + self.transformer = self.transformer.eval() + #self.train = disabled_train + for param in self.parameters(): + param.requires_grad = False + + def forward(self, text): + batch_encoding = self.tokenizer(text, truncation=True, max_length=self.max_length, return_length=True, + return_overflowing_tokens=False, padding="max_length", return_tensors="pt") + tokens = batch_encoding["input_ids"].to(self.device) + outputs = self.transformer(input_ids=tokens) + + z = outputs.last_hidden_state + return z + + def encode(self, text): + return self(text) + + +class FrozenCLIPEmbedder(AbstractEncoder): + """Uses the CLIP transformer encoder for text (from huggingface)""" + def __init__(self, version="openai/clip-vit-large-patch14", device="cuda", max_length=77, + freeze=True, layer="last"): # clip-vit-base-patch32 + super().__init__() + self.tokenizer = CLIPTokenizer.from_pretrained(version) + self.transformer = CLIPModel.from_pretrained(version).text_model + self.device = device + self.max_length = max_length + if freeze: + self.freeze() + self.layer = layer + + def freeze(self): + self.transformer = self.transformer.eval() + for param in self.parameters(): + param.requires_grad = False + + def forward(self, text): + batch_encoding = self.tokenizer(text, truncation=True, max_length=self.max_length, return_length=True, + return_overflowing_tokens=False, padding="max_length", return_tensors="pt") + tokens = batch_encoding["input_ids"].to(self.device) + outputs = self.transformer(input_ids=tokens, output_hidden_states=self.layer != 'last') + + if self.layer == 'penultimate': + z = outputs.hidden_states[-2] + z = self.transformer.final_layer_norm(z) + else: + z = outputs.last_hidden_state + return z + + def encode(self, text): + return self(text) + + +class FrozenOpenCLIPEmbedder(AbstractEncoder): + """ + Uses the OpenCLIP transformer encoder for text + """ + LAYERS = [ + #"pooled", + "last", + "penultimate" + ] + def __init__(self, arch="ViT-H-14", version="laion2b_s32b_b79k", device="cuda", max_length=77, + freeze=True, layer="last"): + super().__init__() + assert layer in self.LAYERS + model, _, _ = open_clip.create_model_and_transforms(arch, device=torch.device('cpu'), pretrained=version) + del model.visual + self.model = model + + self.device = device + self.max_length = max_length + if freeze: + self.freeze() + self.layer = layer + if self.layer == "last": + self.layer_idx = 0 + elif self.layer == "penultimate": + self.layer_idx = 1 + else: + raise NotImplementedError() + + def freeze(self): + self.model = self.model.eval() + for param in self.parameters(): + param.requires_grad = False + + def forward(self, text): + tokens = open_clip.tokenize(text) + z = self.encode_with_transformer(tokens.to(self.device)) + return z + + def encode_with_transformer(self, text): + x = self.model.token_embedding(text) # [batch_size, n_ctx, d_model] + x = x + self.model.positional_embedding + x = x.permute(1, 0, 2) # NLD -> LND + x = self.text_transformer_forward(x, attn_mask=self.model.attn_mask) + x = x.permute(1, 0, 2) # LND -> NLD + x = self.model.ln_final(x) + return x + + def text_transformer_forward(self, x: torch.Tensor, attn_mask = None): + for i, r in enumerate(self.model.transformer.resblocks): + if i == len(self.model.transformer.resblocks) - self.layer_idx: + break + if self.model.transformer.grad_checkpointing and not torch.jit.is_scripting(): + x = checkpoint(r, x, attn_mask) + else: + x = r(x, attn_mask=attn_mask) + return x + + def encode(self, text): + return self(text) + + +class FrozenCLIPT5Encoder(AbstractEncoder): + def __init__(self, clip_version="openai/clip-vit-large-patch14", t5_version="google/t5-v1_1-xl", device="cuda", + clip_max_length=77, t5_max_length=77): + super().__init__() + self.clip_encoder = FrozenCLIPEmbedder(clip_version, device, max_length=clip_max_length) + self.t5_encoder = FrozenT5Embedder(t5_version, device, max_length=t5_max_length) + print(f"{self.clip_encoder.__class__.__name__} has {count_params(self.clip_encoder)*1.e-6:.2f} M parameters, " + f"{self.t5_encoder.__class__.__name__} comes with {count_params(self.t5_encoder)*1.e-6:.2f} M params.") + + def encode(self, text): + return self(text) + + def forward(self, text): + clip_z = self.clip_encoder.encode(text) + t5_z = self.t5_encoder.encode(text) + return [clip_z, t5_z] + + +# code from sd-webui +re_attention = re.compile(r""" +\\\(| +\\\)| +\\\[| +\\]| +\\\\| +\\| +\(| +\[| +:([+-]?[.\d]+)\)| +\)| +]| +[^\\()\[\]:]+| +: +""", re.X) + + +def parse_prompt_attention(text): + """ + Parses a string with attention tokens and returns a list of pairs: text and its associated weight. + Accepted tokens are: + (abc) - increases attention to abc by a multiplier of 1.1 + (abc:3.12) - increases attention to abc by a multiplier of 3.12 + [abc] - decreases attention to abc by a multiplier of 1.1 + \( - literal character '(' + \[ - literal character '[' + \) - literal character ')' + \] - literal character ']' + \\ - literal character '\' + anything else - just text + + >>> parse_prompt_attention('normal text') + [['normal text', 1.0]] + >>> parse_prompt_attention('an (important) word') + [['an ', 1.0], ['important', 1.1], [' word', 1.0]] + >>> parse_prompt_attention('(unbalanced') + [['unbalanced', 1.1]] + >>> parse_prompt_attention('\(literal\]') + [['(literal]', 1.0]] + >>> parse_prompt_attention('(unnecessary)(parens)') + [['unnecessaryparens', 1.1]] + >>> parse_prompt_attention('a (((house:1.3)) [on] a (hill:0.5), sun, (((sky))).') + [['a ', 1.0], + ['house', 1.5730000000000004], + [' ', 1.1], + ['on', 1.0], + [' a ', 1.1], + ['hill', 0.55], + [', sun, ', 1.1], + ['sky', 1.4641000000000006], + ['.', 1.1]] + """ + + res = [] + round_brackets = [] + square_brackets = [] + + round_bracket_multiplier = 1.1 + square_bracket_multiplier = 1 / 1.1 + + def multiply_range(start_position, multiplier): + for p in range(start_position, len(res)): + res[p][1] *= multiplier + + for m in re_attention.finditer(text): + text = m.group(0) + weight = m.group(1) + + if text.startswith('\\'): + res.append([text[1:], 1.0]) + elif text == '(': + round_brackets.append(len(res)) + elif text == '[': + square_brackets.append(len(res)) + elif weight is not None and len(round_brackets) > 0: + multiply_range(round_brackets.pop(), float(weight)) + elif text == ')' and len(round_brackets) > 0: + multiply_range(round_brackets.pop(), round_bracket_multiplier) + elif text == ']' and len(square_brackets) > 0: + multiply_range(square_brackets.pop(), square_bracket_multiplier) + else: + res.append([text, 1.0]) + + for pos in round_brackets: + multiply_range(pos, round_bracket_multiplier) + + for pos in square_brackets: + multiply_range(pos, square_bracket_multiplier) + + if len(res) == 0: + res = [["", 1.0]] + + # merge runs of identical weights + i = 0 + while i + 1 < len(res): + if res[i][1] == res[i + 1][1]: + res[i][0] += res[i + 1][0] + res.pop(i + 1) + else: + i += 1 + + return res + +class WebUIFrozenCLIPEmebedder(AbstractEncoder): + def __init__(self, version="openai/clip-vit-large-patch14", device="cuda", freeze=True, layer="penultimate"): + super(WebUIFrozenCLIPEmebedder, self).__init__() + self.tokenizer = CLIPTokenizer.from_pretrained(version) + self.transformer = CLIPModel.from_pretrained(version).text_model + self.device = device + self.layer = layer + if freeze: + self.freeze() + + self.comma_token = [v for k, v in self.tokenizer.get_vocab().items() if k == ','][0] + self.comma_padding_backtrack = 20 + + def freeze(self): + self.transformer = self.transformer.eval() + for param in self.parameters(): + param.requires_grad = False + + def tokenize(self, texts): + tokenized = self.tokenizer(texts, truncation=False, add_special_tokens=False)["input_ids"] + return tokenized + + def encode_with_transformers(self, tokens): + outputs = self.transformer(input_ids=tokens, output_hidden_states=self.layer!='last') + + if self.layer == 'penultimate': + z = outputs.hidden_states[-2] + z = self.transformer.final_layer_norm(z) + else: + z = outputs.last_hidden_state + + return z + + def tokenize_line(self, line): + parsed = parse_prompt_attention(line) + # print(parsed) + + tokenized = self.tokenize([text for text, _ in parsed]) + + remade_tokens = [] + multipliers = [] + last_comma = -1 + + for tokens, (text, weight) in zip(tokenized, parsed): + i = 0 + while i < len(tokens): + token = tokens[i] + + if token == self.comma_token: + last_comma = len(remade_tokens) + elif self.comma_padding_backtrack != 0 and max(len(remade_tokens), + 1) % 75 == 0 and last_comma != -1 and len( + remade_tokens) - last_comma <= self.comma_padding_backtrack: + last_comma += 1 + reloc_tokens = remade_tokens[last_comma:] + reloc_mults = multipliers[last_comma:] + + remade_tokens = remade_tokens[:last_comma] + length = len(remade_tokens) + + rem = int(math.ceil(length / 75)) * 75 - length + remade_tokens += [self.tokenizer.eos_token_id] * rem + reloc_tokens + multipliers = multipliers[:last_comma] + [1.0] * rem + reloc_mults + + remade_tokens.append(token) + multipliers.append(weight) + i += 1 + + token_count = len(remade_tokens) + prompt_target_length = math.ceil(max(token_count, 1) / 75) * 75 + tokens_to_add = prompt_target_length - len(remade_tokens) + + remade_tokens = remade_tokens + [self.tokenizer.eos_token_id] * tokens_to_add + multipliers = multipliers + [1.0] * tokens_to_add + + return remade_tokens, multipliers, token_count + + def process_text(self, texts): + remade_batch_tokens = [] + token_count = 0 + + cache = {} + batch_multipliers = [] + for line in texts: + if line in cache: + remade_tokens, multipliers = cache[line] + else: + remade_tokens, multipliers, current_token_count = self.tokenize_line(line) + token_count = max(current_token_count, token_count) + + cache[line] = (remade_tokens, multipliers) + + remade_batch_tokens.append(remade_tokens) + batch_multipliers.append(multipliers) + + return batch_multipliers, remade_batch_tokens, token_count + + def process_tokens(self, remade_batch_tokens, batch_multipliers): + remade_batch_tokens = [[self.tokenizer.bos_token_id] + x[:75] + [self.tokenizer.eos_token_id] for x in remade_batch_tokens] + batch_multipliers = [[1.0] + x[:75] + [1.0] for x in batch_multipliers] + + tokens = torch.asarray(remade_batch_tokens).to(self.device) + + z = self.encode_with_transformers(tokens) + + # restoring original mean is likely not correct, but it seems to work well to prevent artifacts that happen otherwise + batch_multipliers_of_same_length = [x + [1.0] * (75 - len(x)) for x in batch_multipliers] + batch_multipliers = torch.asarray(batch_multipliers_of_same_length).to(self.device) + original_mean = z.mean() + z *= batch_multipliers.reshape(batch_multipliers.shape + (1,)).expand(z.shape) + new_mean = z.mean() + z *= original_mean / new_mean + + return z + + def forward(self, text): + batch_multipliers, remade_batch_tokens, token_count = self.process_text(text) + + z = None + i = 0 + while max(map(len, remade_batch_tokens)) != 0: + rem_tokens = [x[75:] for x in remade_batch_tokens] + rem_multipliers = [x[75:] for x in batch_multipliers] + + tokens = [] + multipliers = [] + for j in range(len(remade_batch_tokens)): + if len(remade_batch_tokens[j]) > 0: + tokens.append(remade_batch_tokens[j][:75]) + multipliers.append(batch_multipliers[j][:75]) + else: + tokens.append([self.tokenizer.eos_token_id] * 75) + multipliers.append([1.0] * 75) + + z1 = self.process_tokens(tokens, multipliers) + z = z1 if z is None else torch.cat((z, z1), axis=-2) + + remade_batch_tokens = rem_tokens + batch_multipliers = rem_multipliers + i += 1 + + return z + + def encode(self, text): + return self(text) + + + +if __name__ == "__main__": + model = FrozenCLIPEmbedder() + count_params(model, verbose=True) diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/__init__.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/api.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/api.py new file mode 100644 index 0000000..e71df77 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/api.py @@ -0,0 +1,269 @@ +from enum import Enum, unique + +import cv2 +import torch +from basicsr.utils import img2tensor +from comparison_models.T2IAdapter.ldm.util import resize_numpy_image +from PIL import Image +from torch import autocast + + +@unique +class ExtraCondition(Enum): + sketch = 0 + keypose = 1 + seg = 2 + depth = 3 + canny = 4 + style = 5 + color = 6 + openpose = 7 + + +def get_cond_model(opt, cond_type: ExtraCondition): + if cond_type == ExtraCondition.sketch: + from ldm.modules.extra_condition.model_edge import pidinet + model = pidinet() + ckp = torch.load('models/table5_pidinet.pth', map_location='cpu')['state_dict'] + model.load_state_dict({k.replace('module.', ''): v for k, v in ckp.items()}, strict=True) + model.to(opt.device) + return model + elif cond_type == ExtraCondition.seg: + raise NotImplementedError + elif cond_type == ExtraCondition.keypose: + import mmcv + from mmdet.apis import init_detector + from mmpose.apis import init_pose_model + det_config = 'configs/mm/faster_rcnn_r50_fpn_coco.py' + det_checkpoint = 'models/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth' + pose_config = 'configs/mm/hrnet_w48_coco_256x192.py' + pose_checkpoint = 'models/hrnet_w48_coco_256x192-b9e0b3ab_20200708.pth' + det_config_mmcv = mmcv.Config.fromfile(det_config) + det_model = init_detector(det_config_mmcv, det_checkpoint, device=opt.device) + pose_config_mmcv = mmcv.Config.fromfile(pose_config) + pose_model = init_pose_model(pose_config_mmcv, pose_checkpoint, device=opt.device) + return {'pose_model': pose_model, 'det_model': det_model} + elif cond_type == ExtraCondition.depth: + from ldm.modules.extra_condition.midas.api import MiDaSInference + model = MiDaSInference(model_type='dpt_hybrid').to(opt.device) + return model + elif cond_type == ExtraCondition.canny: + return None + elif cond_type == ExtraCondition.style: + from transformers import CLIPProcessor, CLIPVisionModel + version = 'openai/clip-vit-large-patch14' + processor = CLIPProcessor.from_pretrained(version) + clip_vision_model = CLIPVisionModel.from_pretrained(version).to(opt.device) + return {'processor': processor, 'clip_vision_model': clip_vision_model} + elif cond_type == ExtraCondition.color: + return None + elif cond_type == ExtraCondition.openpose: + from ldm.modules.extra_condition.openpose.api import OpenposeInference + model = OpenposeInference().to(opt.device) + return model + else: + raise NotImplementedError + + +def get_cond_sketch(opt, cond_image, cond_inp_type, cond_model=None): + if isinstance(cond_image, str): + edge = cv2.imread(cond_image) + else: + # for gradio input, pay attention, it's rgb numpy + edge = cv2.cvtColor(cond_image, cv2.COLOR_RGB2BGR) + edge = resize_numpy_image(edge, max_resolution=opt.max_resolution, resize_short_edge=opt.resize_short_edge) + opt.H, opt.W = edge.shape[:2] + if cond_inp_type == 'sketch': + edge = img2tensor(edge)[0].unsqueeze(0).unsqueeze(0) / 255. + edge = edge.to(opt.device) + elif cond_inp_type == 'image': + edge = img2tensor(edge).unsqueeze(0) / 255. + edge = cond_model(edge.to(opt.device))[-1] + else: + raise NotImplementedError + + # edge = 1-edge # for white background + edge = edge > 0.5 + edge = edge.float() + + return edge + + +def get_cond_seg(opt, cond_image, cond_inp_type='image', cond_model=None): + if isinstance(cond_image, str): + seg = cv2.imread(cond_image) + else: + seg = cv2.cvtColor(cond_image, cv2.COLOR_RGB2BGR) + seg = resize_numpy_image(seg, max_resolution=opt.max_resolution, resize_short_edge=opt.resize_short_edge) + opt.H, opt.W = seg.shape[:2] + if cond_inp_type == 'seg': + seg = img2tensor(seg).unsqueeze(0) / 255. + seg = seg.to(opt.device) + else: + raise NotImplementedError + + return seg + + +def get_cond_keypose(opt, cond_image, cond_inp_type='image', cond_model=None): + if isinstance(cond_image, str): + pose = cv2.imread(cond_image) + else: + pose = cv2.cvtColor(cond_image, cv2.COLOR_RGB2BGR) + pose = resize_numpy_image(pose, max_resolution=opt.max_resolution, resize_short_edge=opt.resize_short_edge) + opt.H, opt.W = pose.shape[:2] + if cond_inp_type == 'keypose': + pose = img2tensor(pose).unsqueeze(0) / 255. + pose = pose.to(opt.device) + elif cond_inp_type == 'image': + from ldm.modules.extra_condition.utils import imshow_keypoints + from mmdet.apis import inference_detector + from mmpose.apis import (inference_top_down_pose_model, process_mmdet_results) + + # mmpose seems not compatible with autocast fp16 + with autocast("cuda", dtype=torch.float32): + mmdet_results = inference_detector(cond_model['det_model'], pose) + # keep the person class bounding boxes. + person_results = process_mmdet_results(mmdet_results, 1) + + # optional + return_heatmap = False + dataset = cond_model['pose_model'].cfg.data['test']['type'] + + # e.g. use ('backbone', ) to return backbone feature + output_layer_names = None + pose_results, returned_outputs = inference_top_down_pose_model( + cond_model['pose_model'], + pose, + person_results, + bbox_thr=0.2, + format='xyxy', + dataset=dataset, + dataset_info=None, + return_heatmap=return_heatmap, + outputs=output_layer_names) + + # show the results + pose = imshow_keypoints(pose, pose_results, radius=2, thickness=2) + pose = img2tensor(pose).unsqueeze(0) / 255. + pose = pose.to(opt.device) + else: + raise NotImplementedError + + return pose + + +def get_cond_depth(opt, cond_image, cond_inp_type='image', cond_model=None): + if isinstance(cond_image, str): + depth = cv2.imread(cond_image) + else: + depth = cv2.cvtColor(cond_image, cv2.COLOR_RGB2BGR) + depth = resize_numpy_image(depth, max_resolution=opt.max_resolution, resize_short_edge=opt.resize_short_edge) + opt.H, opt.W = depth.shape[:2] + if cond_inp_type == 'depth': + depth = img2tensor(depth).unsqueeze(0) / 255. + depth = depth.to(opt.device) + elif cond_inp_type == 'image': + depth = img2tensor(depth).unsqueeze(0) / 127.5 - 1.0 + depth = cond_model(depth.to(opt.device)).repeat(1, 3, 1, 1) + depth -= torch.min(depth) + depth /= torch.max(depth) + else: + raise NotImplementedError + + return depth + + +def get_cond_canny(opt, cond_image, cond_inp_type='image', cond_model=None): + if isinstance(cond_image, str): + canny = cv2.imread(cond_image) + else: + canny = cv2.cvtColor(cond_image, cv2.COLOR_RGB2BGR) + canny = resize_numpy_image(canny, max_resolution=opt.max_resolution, resize_short_edge=opt.resize_short_edge) + opt.H, opt.W = canny.shape[:2] + if cond_inp_type == 'canny': + canny = img2tensor(canny)[0:1].unsqueeze(0) / 255. + canny = canny.to(opt.device) + elif cond_inp_type == 'image': + canny = cv2.Canny(canny, 100, 200)[..., None] + canny = img2tensor(canny).unsqueeze(0) / 255. + canny = canny.to(opt.device) + else: + raise NotImplementedError + + return canny + + +def get_cond_style(opt, cond_image, cond_inp_type='image', cond_model=None): + assert cond_inp_type == 'image' + if isinstance(cond_image, str): + style = Image.open(cond_image) + else: + # numpy image to PIL image + style = Image.fromarray(cond_image) + + style_for_clip = cond_model['processor'](images=style, return_tensors="pt")['pixel_values'] + style_feat = cond_model['clip_vision_model'](style_for_clip.to(opt.device))['last_hidden_state'] + + return style_feat + + +def get_cond_color(opt, cond_image, cond_inp_type='image', cond_model=None): + if isinstance(cond_image, str): + color = cv2.imread(cond_image) + else: + color = cv2.cvtColor(cond_image, cv2.COLOR_RGB2BGR) + color = resize_numpy_image(color, max_resolution=opt.max_resolution, resize_short_edge=opt.resize_short_edge) + opt.H, opt.W = color.shape[:2] + if cond_inp_type == 'image': + color = cv2.resize(color, (opt.W//64, opt.H//64), interpolation=cv2.INTER_CUBIC) + color = cv2.resize(color, (opt.W, opt.H), interpolation=cv2.INTER_NEAREST) + color = img2tensor(color).unsqueeze(0) / 255. + color = color.to(opt.device) + return color + + +def get_cond_openpose(opt, cond_image, cond_inp_type='image', cond_model=None): + if isinstance(cond_image, str): + openpose_keypose = cv2.imread(cond_image) + else: + openpose_keypose = cv2.cvtColor(cond_image, cv2.COLOR_RGB2BGR) + openpose_keypose = resize_numpy_image( + openpose_keypose, max_resolution=opt.max_resolution, resize_short_edge=opt.resize_short_edge) + opt.H, opt.W = openpose_keypose.shape[:2] + if cond_inp_type == 'openpose': + openpose_keypose = img2tensor(openpose_keypose).unsqueeze(0) / 255. + openpose_keypose = openpose_keypose.to(opt.device) + elif cond_inp_type == 'image': + with autocast('cuda', dtype=torch.float32): + openpose_keypose = cond_model(openpose_keypose) + openpose_keypose = img2tensor(openpose_keypose).unsqueeze(0) / 255. + openpose_keypose = openpose_keypose.to(opt.device) + + else: + raise NotImplementedError + + return openpose_keypose + + +def get_adapter_feature(inputs, adapters): + ret_feat_map = None + ret_feat_seq = None + if not isinstance(inputs, list): + inputs = [inputs] + adapters = [adapters] + + for input, adapter in zip(inputs, adapters): + cur_feature = adapter['model'](input) + if isinstance(cur_feature, list): + if ret_feat_map is None: + ret_feat_map = list(map(lambda x: x * adapter['cond_weight'], cur_feature)) + else: + ret_feat_map = list(map(lambda x, y: x + y * adapter['cond_weight'], ret_feat_map, cur_feature)) + else: + if ret_feat_seq is None: + ret_feat_seq = cur_feature * adapter['cond_weight'] + else: + ret_feat_seq = torch.cat([ret_feat_seq, cur_feature * adapter['cond_weight']], dim=1) + + return ret_feat_map, ret_feat_seq diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/__init__.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/api.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/api.py new file mode 100644 index 0000000..9a6e194 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/api.py @@ -0,0 +1,175 @@ +# based on https://github.com/isl-org/MiDaS +import os + +import cv2 +import torch +import torch.nn as nn +from torchvision.transforms import Compose + +from ldm.modules.extra_condition.midas.midas.dpt_depth import DPTDepthModel +from ldm.modules.extra_condition.midas.midas.midas_net import MidasNet +from ldm.modules.extra_condition.midas.midas.midas_net_custom import MidasNet_small +from ldm.modules.extra_condition.midas.midas.transforms import Resize, NormalizeImage, PrepareForNet + + +ISL_PATHS = { + "dpt_large": "models/dpt_large-midas-2f21e586.pt", + "dpt_hybrid": "models/dpt_hybrid-midas-501f0c75.pt", + "midas_v21": "", + "midas_v21_small": "", +} + +remote_model_path = "https://github.com/intel-isl/DPT/releases/download/1_0/dpt_hybrid-midas-501f0c75.pt" + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +def load_midas_transform(model_type): + # https://github.com/isl-org/MiDaS/blob/master/run.py + # load transform only + if model_type == "dpt_large": # DPT-Large + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid": # DPT-Hybrid + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21": + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + elif model_type == "midas_v21_small": + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + else: + assert False, f"model_type '{model_type}' not implemented, use: --model_type large" + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + return transform + + +def load_model(model_type): + # https://github.com/isl-org/MiDaS/blob/master/run.py + # load network + model_path = ISL_PATHS[model_type] + if model_type == "dpt_large": # DPT-Large + model = DPTDepthModel( + path=model_path, + backbone="vitl16_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid": # DPT-Hybrid + if not os.path.exists(model_path): + from basicsr.utils.download_util import load_file_from_url + load_file_from_url(remote_model_path, model_dir='models') + + model = DPTDepthModel( + path=model_path, + backbone="vitb_rn50_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21": + model = MidasNet(model_path, non_negative=True) + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + elif model_type == "midas_v21_small": + model = MidasNet_small(model_path, features=64, backbone="efficientnet_lite3", exportable=True, + non_negative=True, blocks={'expand': True}) + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + else: + print(f"model_type '{model_type}' not implemented, use: --model_type large") + assert False + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + return model.eval(), transform + + +class MiDaSInference(nn.Module): + MODEL_TYPES_TORCH_HUB = [ + "DPT_Large", + "DPT_Hybrid", + "MiDaS_small" + ] + MODEL_TYPES_ISL = [ + "dpt_large", + "dpt_hybrid", + "midas_v21", + "midas_v21_small", + ] + + def __init__(self, model_type): + super().__init__() + assert (model_type in self.MODEL_TYPES_ISL) + model, _ = load_model(model_type) + self.model = model + self.model.train = disabled_train + + def forward(self, x): + # x in 0..1 as produced by calling self.transform on a 0..1 float64 numpy array + # NOTE: we expect that the correct transform has been called during dataloading. + with torch.no_grad(): + prediction = self.model(x) + prediction = torch.nn.functional.interpolate( + prediction.unsqueeze(1), + size=x.shape[2:], + mode="bicubic", + align_corners=False, + ) + assert prediction.shape == (x.shape[0], 1, x.shape[2], x.shape[3]) + return prediction diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/__init__.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/base_model.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/base_model.py new file mode 100644 index 0000000..5cf4302 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/base_model.py @@ -0,0 +1,16 @@ +import torch + + +class BaseModel(torch.nn.Module): + def load(self, path): + """Load model from file. + + Args: + path (str): file path + """ + parameters = torch.load(path, map_location=torch.device('cpu')) + + if "optimizer" in parameters: + parameters = parameters["model"] + + self.load_state_dict(parameters) diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/blocks.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/blocks.py new file mode 100644 index 0000000..2145d18 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/blocks.py @@ -0,0 +1,342 @@ +import torch +import torch.nn as nn + +from .vit import ( + _make_pretrained_vitb_rn50_384, + _make_pretrained_vitl16_384, + _make_pretrained_vitb16_384, + forward_vit, +) + +def _make_encoder(backbone, features, use_pretrained, groups=1, expand=False, exportable=True, hooks=None, use_vit_only=False, use_readout="ignore",): + if backbone == "vitl16_384": + pretrained = _make_pretrained_vitl16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [256, 512, 1024, 1024], features, groups=groups, expand=expand + ) # ViT-L/16 - 85.0% Top1 (backbone) + elif backbone == "vitb_rn50_384": + pretrained = _make_pretrained_vitb_rn50_384( + use_pretrained, + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) + scratch = _make_scratch( + [256, 512, 768, 768], features, groups=groups, expand=expand + ) # ViT-H/16 - 85.0% Top1 (backbone) + elif backbone == "vitb16_384": + pretrained = _make_pretrained_vitb16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [96, 192, 384, 768], features, groups=groups, expand=expand + ) # ViT-B/16 - 84.6% Top1 (backbone) + elif backbone == "resnext101_wsl": + pretrained = _make_pretrained_resnext101_wsl(use_pretrained) + scratch = _make_scratch([256, 512, 1024, 2048], features, groups=groups, expand=expand) # efficientnet_lite3 + elif backbone == "efficientnet_lite3": + pretrained = _make_pretrained_efficientnet_lite3(use_pretrained, exportable=exportable) + scratch = _make_scratch([32, 48, 136, 384], features, groups=groups, expand=expand) # efficientnet_lite3 + else: + print(f"Backbone '{backbone}' not implemented") + assert False + + return pretrained, scratch + + +def _make_scratch(in_shape, out_shape, groups=1, expand=False): + scratch = nn.Module() + + out_shape1 = out_shape + out_shape2 = out_shape + out_shape3 = out_shape + out_shape4 = out_shape + if expand==True: + out_shape1 = out_shape + out_shape2 = out_shape*2 + out_shape3 = out_shape*4 + out_shape4 = out_shape*8 + + scratch.layer1_rn = nn.Conv2d( + in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer2_rn = nn.Conv2d( + in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer3_rn = nn.Conv2d( + in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer4_rn = nn.Conv2d( + in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + + return scratch + + +def _make_pretrained_efficientnet_lite3(use_pretrained, exportable=False): + efficientnet = torch.hub.load( + "rwightman/gen-efficientnet-pytorch", + "tf_efficientnet_lite3", + pretrained=use_pretrained, + exportable=exportable + ) + return _make_efficientnet_backbone(efficientnet) + + +def _make_efficientnet_backbone(effnet): + pretrained = nn.Module() + + pretrained.layer1 = nn.Sequential( + effnet.conv_stem, effnet.bn1, effnet.act1, *effnet.blocks[0:2] + ) + pretrained.layer2 = nn.Sequential(*effnet.blocks[2:3]) + pretrained.layer3 = nn.Sequential(*effnet.blocks[3:5]) + pretrained.layer4 = nn.Sequential(*effnet.blocks[5:9]) + + return pretrained + + +def _make_resnet_backbone(resnet): + pretrained = nn.Module() + pretrained.layer1 = nn.Sequential( + resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, resnet.layer1 + ) + + pretrained.layer2 = resnet.layer2 + pretrained.layer3 = resnet.layer3 + pretrained.layer4 = resnet.layer4 + + return pretrained + + +def _make_pretrained_resnext101_wsl(use_pretrained): + resnet = torch.hub.load("facebookresearch/WSL-Images", "resnext101_32x8d_wsl") + return _make_resnet_backbone(resnet) + + + +class Interpolate(nn.Module): + """Interpolation module. + """ + + def __init__(self, scale_factor, mode, align_corners=False): + """Init. + + Args: + scale_factor (float): scaling + mode (str): interpolation mode + """ + super(Interpolate, self).__init__() + + self.interp = nn.functional.interpolate + self.scale_factor = scale_factor + self.mode = mode + self.align_corners = align_corners + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: interpolated data + """ + + x = self.interp( + x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners + ) + + return x + + +class ResidualConvUnit(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.relu = nn.ReLU(inplace=True) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + out = self.relu(x) + out = self.conv1(out) + out = self.relu(out) + out = self.conv2(out) + + return out + x + + +class FeatureFusionBlock(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock, self).__init__() + + self.resConfUnit1 = ResidualConvUnit(features) + self.resConfUnit2 = ResidualConvUnit(features) + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + output += self.resConfUnit1(xs[1]) + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=True + ) + + return output + + + + +class ResidualConvUnit_custom(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features, activation, bn): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.bn = bn + + self.groups=1 + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + if self.bn==True: + self.bn1 = nn.BatchNorm2d(features) + self.bn2 = nn.BatchNorm2d(features) + + self.activation = activation + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + + out = self.activation(x) + out = self.conv1(out) + if self.bn==True: + out = self.bn1(out) + + out = self.activation(out) + out = self.conv2(out) + if self.bn==True: + out = self.bn2(out) + + if self.groups > 1: + out = self.conv_merge(out) + + return self.skip_add.add(out, x) + + # return out + x + + +class FeatureFusionBlock_custom(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock_custom, self).__init__() + + self.deconv = deconv + self.align_corners = align_corners + + self.groups=1 + + self.expand = expand + out_features = features + if self.expand==True: + out_features = features//2 + + self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1) + + self.resConfUnit1 = ResidualConvUnit_custom(features, activation, bn) + self.resConfUnit2 = ResidualConvUnit_custom(features, activation, bn) + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + res = self.resConfUnit1(xs[1]) + output = self.skip_add.add(output, res) + # output += res + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=self.align_corners + ) + + output = self.out_conv(output) + + return output + diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/dpt_depth.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/dpt_depth.py new file mode 100644 index 0000000..4e9aab5 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/dpt_depth.py @@ -0,0 +1,109 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from .base_model import BaseModel +from .blocks import ( + FeatureFusionBlock, + FeatureFusionBlock_custom, + Interpolate, + _make_encoder, + forward_vit, +) + + +def _make_fusion_block(features, use_bn): + return FeatureFusionBlock_custom( + features, + nn.ReLU(False), + deconv=False, + bn=use_bn, + expand=False, + align_corners=True, + ) + + +class DPT(BaseModel): + def __init__( + self, + head, + features=256, + backbone="vitb_rn50_384", + readout="project", + channels_last=False, + use_bn=False, + ): + + super(DPT, self).__init__() + + self.channels_last = channels_last + + hooks = { + "vitb_rn50_384": [0, 1, 8, 11], + "vitb16_384": [2, 5, 8, 11], + "vitl16_384": [5, 11, 17, 23], + } + + # Instantiate backbone and reassemble blocks + self.pretrained, self.scratch = _make_encoder( + backbone, + features, + False, # Set to true of you want to train from scratch, uses ImageNet weights + groups=1, + expand=False, + exportable=False, + hooks=hooks[backbone], + use_readout=readout, + ) + + self.scratch.refinenet1 = _make_fusion_block(features, use_bn) + self.scratch.refinenet2 = _make_fusion_block(features, use_bn) + self.scratch.refinenet3 = _make_fusion_block(features, use_bn) + self.scratch.refinenet4 = _make_fusion_block(features, use_bn) + + self.scratch.output_conv = head + + + def forward(self, x): + if self.channels_last == True: + x.contiguous(memory_format=torch.channels_last) + + layer_1, layer_2, layer_3, layer_4 = forward_vit(self.pretrained, x) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return out + + +class DPTDepthModel(DPT): + def __init__(self, path=None, non_negative=True, **kwargs): + features = kwargs["features"] if "features" in kwargs else 256 + + head = nn.Sequential( + nn.Conv2d(features, features // 2, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear", align_corners=True), + nn.Conv2d(features // 2, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + super().__init__(head, **kwargs) + + if path is not None: + self.load(path) + + def forward(self, x): + return super().forward(x).squeeze(dim=1) + diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/midas_net.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/midas_net.py new file mode 100644 index 0000000..8a95497 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/midas_net.py @@ -0,0 +1,76 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, Interpolate, _make_encoder + + +class MidasNet(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=256, non_negative=True): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + print("Loading weights: ", path) + + super(MidasNet, self).__init__() + + use_pretrained = False if path is None else True + + self.pretrained, self.scratch = _make_encoder(backbone="resnext101_wsl", features=features, use_pretrained=use_pretrained) + + self.scratch.refinenet4 = FeatureFusionBlock(features) + self.scratch.refinenet3 = FeatureFusionBlock(features) + self.scratch.refinenet2 = FeatureFusionBlock(features) + self.scratch.refinenet1 = FeatureFusionBlock(features) + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, 128, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(128, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + ) + + if path: + self.load(path) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/midas_net_custom.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/midas_net_custom.py new file mode 100644 index 0000000..50e4acb --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/midas_net_custom.py @@ -0,0 +1,128 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, FeatureFusionBlock_custom, Interpolate, _make_encoder + + +class MidasNet_small(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=64, backbone="efficientnet_lite3", non_negative=True, exportable=True, channels_last=False, align_corners=True, + blocks={'expand': True}): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + print("Loading weights: ", path) + + super(MidasNet_small, self).__init__() + + use_pretrained = False if path else True + + self.channels_last = channels_last + self.blocks = blocks + self.backbone = backbone + + self.groups = 1 + + features1=features + features2=features + features3=features + features4=features + self.expand = False + if "expand" in self.blocks and self.blocks['expand'] == True: + self.expand = True + features1=features + features2=features*2 + features3=features*4 + features4=features*8 + + self.pretrained, self.scratch = _make_encoder(self.backbone, features, use_pretrained, groups=self.groups, expand=self.expand, exportable=exportable) + + self.scratch.activation = nn.ReLU(False) + + self.scratch.refinenet4 = FeatureFusionBlock_custom(features4, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet3 = FeatureFusionBlock_custom(features3, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet2 = FeatureFusionBlock_custom(features2, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet1 = FeatureFusionBlock_custom(features1, self.scratch.activation, deconv=False, bn=False, align_corners=align_corners) + + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, features//2, kernel_size=3, stride=1, padding=1, groups=self.groups), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(features//2, 32, kernel_size=3, stride=1, padding=1), + self.scratch.activation, + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + if path: + self.load(path) + + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + if self.channels_last==True: + print("self.channels_last = ", self.channels_last) + x.contiguous(memory_format=torch.channels_last) + + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) + + + +def fuse_model(m): + prev_previous_type = nn.Identity() + prev_previous_name = '' + previous_type = nn.Identity() + previous_name = '' + for name, module in m.named_modules(): + if prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d and type(module) == nn.ReLU: + # print("FUSED ", prev_previous_name, previous_name, name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name, name], inplace=True) + elif prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d: + # print("FUSED ", prev_previous_name, previous_name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name], inplace=True) + # elif previous_type == nn.Conv2d and type(module) == nn.ReLU: + # print("FUSED ", previous_name, name) + # torch.quantization.fuse_modules(m, [previous_name, name], inplace=True) + + prev_previous_type = previous_type + prev_previous_name = previous_name + previous_type = type(module) + previous_name = name \ No newline at end of file diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/transforms.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/transforms.py new file mode 100644 index 0000000..350cbc1 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/transforms.py @@ -0,0 +1,234 @@ +import numpy as np +import cv2 +import math + + +def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA): + """Rezise the sample to ensure the given size. Keeps aspect ratio. + + Args: + sample (dict): sample + size (tuple): image size + + Returns: + tuple: new size + """ + shape = list(sample["disparity"].shape) + + if shape[0] >= size[0] and shape[1] >= size[1]: + return sample + + scale = [0, 0] + scale[0] = size[0] / shape[0] + scale[1] = size[1] / shape[1] + + scale = max(scale) + + shape[0] = math.ceil(scale * shape[0]) + shape[1] = math.ceil(scale * shape[1]) + + # resize + sample["image"] = cv2.resize( + sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method + ) + + sample["disparity"] = cv2.resize( + sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST + ) + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + tuple(shape[::-1]), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return tuple(shape) + + +class Resize(object): + """Resize sample to given size (width, height). + """ + + def __init__( + self, + width, + height, + resize_target=True, + keep_aspect_ratio=False, + ensure_multiple_of=1, + resize_method="lower_bound", + image_interpolation_method=cv2.INTER_AREA, + ): + """Init. + + Args: + width (int): desired output width + height (int): desired output height + resize_target (bool, optional): + True: Resize the full sample (image, mask, target). + False: Resize image only. + Defaults to True. + keep_aspect_ratio (bool, optional): + True: Keep the aspect ratio of the input sample. + Output sample might not have the given width and height, and + resize behaviour depends on the parameter 'resize_method'. + Defaults to False. + ensure_multiple_of (int, optional): + Output width and height is constrained to be multiple of this parameter. + Defaults to 1. + resize_method (str, optional): + "lower_bound": Output will be at least as large as the given size. + "upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.) + "minimal": Scale as least as possible. (Output size might be smaller than given size.) + Defaults to "lower_bound". + """ + self.__width = width + self.__height = height + + self.__resize_target = resize_target + self.__keep_aspect_ratio = keep_aspect_ratio + self.__multiple_of = ensure_multiple_of + self.__resize_method = resize_method + self.__image_interpolation_method = image_interpolation_method + + def constrain_to_multiple_of(self, x, min_val=0, max_val=None): + y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if max_val is not None and y > max_val: + y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if y < min_val: + y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int) + + return y + + def get_size(self, width, height): + # determine new height and width + scale_height = self.__height / height + scale_width = self.__width / width + + if self.__keep_aspect_ratio: + if self.__resize_method == "lower_bound": + # scale such that output size is lower bound + if scale_width > scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "upper_bound": + # scale such that output size is upper bound + if scale_width < scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "minimal": + # scale as least as possbile + if abs(1 - scale_width) < abs(1 - scale_height): + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + else: + raise ValueError( + f"resize_method {self.__resize_method} not implemented" + ) + + if self.__resize_method == "lower_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, min_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, min_val=self.__width + ) + elif self.__resize_method == "upper_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, max_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, max_val=self.__width + ) + elif self.__resize_method == "minimal": + new_height = self.constrain_to_multiple_of(scale_height * height) + new_width = self.constrain_to_multiple_of(scale_width * width) + else: + raise ValueError(f"resize_method {self.__resize_method} not implemented") + + return (new_width, new_height) + + def __call__(self, sample): + width, height = self.get_size( + sample["image"].shape[1], sample["image"].shape[0] + ) + + # resize sample + sample["image"] = cv2.resize( + sample["image"], + (width, height), + interpolation=self.__image_interpolation_method, + ) + + if self.__resize_target: + if "disparity" in sample: + sample["disparity"] = cv2.resize( + sample["disparity"], + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + + if "depth" in sample: + sample["depth"] = cv2.resize( + sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST + ) + + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return sample + + +class NormalizeImage(object): + """Normlize image by given mean and std. + """ + + def __init__(self, mean, std): + self.__mean = mean + self.__std = std + + def __call__(self, sample): + sample["image"] = (sample["image"] - self.__mean) / self.__std + + return sample + + +class PrepareForNet(object): + """Prepare sample for usage as network input. + """ + + def __init__(self): + pass + + def __call__(self, sample): + image = np.transpose(sample["image"], (2, 0, 1)) + sample["image"] = np.ascontiguousarray(image).astype(np.float32) + + if "mask" in sample: + sample["mask"] = sample["mask"].astype(np.float32) + sample["mask"] = np.ascontiguousarray(sample["mask"]) + + if "disparity" in sample: + disparity = sample["disparity"].astype(np.float32) + sample["disparity"] = np.ascontiguousarray(disparity) + + if "depth" in sample: + depth = sample["depth"].astype(np.float32) + sample["depth"] = np.ascontiguousarray(depth) + + return sample diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/vit.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/vit.py new file mode 100644 index 0000000..ea46b1b --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/midas/vit.py @@ -0,0 +1,491 @@ +import torch +import torch.nn as nn +import timm +import types +import math +import torch.nn.functional as F + + +class Slice(nn.Module): + def __init__(self, start_index=1): + super(Slice, self).__init__() + self.start_index = start_index + + def forward(self, x): + return x[:, self.start_index :] + + +class AddReadout(nn.Module): + def __init__(self, start_index=1): + super(AddReadout, self).__init__() + self.start_index = start_index + + def forward(self, x): + if self.start_index == 2: + readout = (x[:, 0] + x[:, 1]) / 2 + else: + readout = x[:, 0] + return x[:, self.start_index :] + readout.unsqueeze(1) + + +class ProjectReadout(nn.Module): + def __init__(self, in_features, start_index=1): + super(ProjectReadout, self).__init__() + self.start_index = start_index + + self.project = nn.Sequential(nn.Linear(2 * in_features, in_features), nn.GELU()) + + def forward(self, x): + readout = x[:, 0].unsqueeze(1).expand_as(x[:, self.start_index :]) + features = torch.cat((x[:, self.start_index :], readout), -1) + + return self.project(features) + + +class Transpose(nn.Module): + def __init__(self, dim0, dim1): + super(Transpose, self).__init__() + self.dim0 = dim0 + self.dim1 = dim1 + + def forward(self, x): + x = x.transpose(self.dim0, self.dim1) + return x + + +def forward_vit(pretrained, x): + b, c, h, w = x.shape + + glob = pretrained.model.forward_flex(x) + + layer_1 = pretrained.activations["1"] + layer_2 = pretrained.activations["2"] + layer_3 = pretrained.activations["3"] + layer_4 = pretrained.activations["4"] + + layer_1 = pretrained.act_postprocess1[0:2](layer_1) + layer_2 = pretrained.act_postprocess2[0:2](layer_2) + layer_3 = pretrained.act_postprocess3[0:2](layer_3) + layer_4 = pretrained.act_postprocess4[0:2](layer_4) + + unflatten = nn.Sequential( + nn.Unflatten( + 2, + torch.Size( + [ + h // pretrained.model.patch_size[1], + w // pretrained.model.patch_size[0], + ] + ), + ) + ) + + if layer_1.ndim == 3: + layer_1 = unflatten(layer_1) + if layer_2.ndim == 3: + layer_2 = unflatten(layer_2) + if layer_3.ndim == 3: + layer_3 = unflatten(layer_3) + if layer_4.ndim == 3: + layer_4 = unflatten(layer_4) + + layer_1 = pretrained.act_postprocess1[3 : len(pretrained.act_postprocess1)](layer_1) + layer_2 = pretrained.act_postprocess2[3 : len(pretrained.act_postprocess2)](layer_2) + layer_3 = pretrained.act_postprocess3[3 : len(pretrained.act_postprocess3)](layer_3) + layer_4 = pretrained.act_postprocess4[3 : len(pretrained.act_postprocess4)](layer_4) + + return layer_1, layer_2, layer_3, layer_4 + + +def _resize_pos_embed(self, posemb, gs_h, gs_w): + posemb_tok, posemb_grid = ( + posemb[:, : self.start_index], + posemb[0, self.start_index :], + ) + + gs_old = int(math.sqrt(len(posemb_grid))) + + posemb_grid = posemb_grid.reshape(1, gs_old, gs_old, -1).permute(0, 3, 1, 2) + posemb_grid = F.interpolate(posemb_grid, size=(gs_h, gs_w), mode="bilinear") + posemb_grid = posemb_grid.permute(0, 2, 3, 1).reshape(1, gs_h * gs_w, -1) + + posemb = torch.cat([posemb_tok, posemb_grid], dim=1) + + return posemb + + +def forward_flex(self, x): + b, c, h, w = x.shape + + pos_embed = self._resize_pos_embed( + self.pos_embed, h // self.patch_size[1], w // self.patch_size[0] + ) + + B = x.shape[0] + + if hasattr(self.patch_embed, "backbone"): + x = self.patch_embed.backbone(x) + if isinstance(x, (list, tuple)): + x = x[-1] # last feature if backbone outputs list/tuple of features + + x = self.patch_embed.proj(x).flatten(2).transpose(1, 2) + + if getattr(self, "dist_token", None) is not None: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + dist_token = self.dist_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, dist_token, x), dim=1) + else: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + x = torch.cat((cls_tokens, x), dim=1) + + x = x + pos_embed + x = self.pos_drop(x) + + for blk in self.blocks: + x = blk(x) + + x = self.norm(x) + + return x + + +activations = {} + + +def get_activation(name): + def hook(model, input, output): + activations[name] = output + + return hook + + +def get_readout_oper(vit_features, features, use_readout, start_index=1): + if use_readout == "ignore": + readout_oper = [Slice(start_index)] * len(features) + elif use_readout == "add": + readout_oper = [AddReadout(start_index)] * len(features) + elif use_readout == "project": + readout_oper = [ + ProjectReadout(vit_features, start_index) for out_feat in features + ] + else: + assert ( + False + ), "wrong operation for readout token, use_readout can be 'ignore', 'add', or 'project'" + + return readout_oper + + +def _make_vit_b16_backbone( + model, + features=[96, 192, 384, 768], + size=[384, 384], + hooks=[2, 5, 8, 11], + vit_features=768, + use_readout="ignore", + start_index=1, +): + pretrained = nn.Module() + + pretrained.model = model + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + # 32, 48, 136, 384 + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitl16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_large_patch16_384", pretrained=pretrained) + + hooks = [5, 11, 17, 23] if hooks == None else hooks + return _make_vit_b16_backbone( + model, + features=[256, 512, 1024, 1024], + hooks=hooks, + vit_features=1024, + use_readout=use_readout, + ) + + +def _make_pretrained_vitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_pretrained_deitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_deit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_pretrained_deitb16_distil_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model( + "vit_deit_base_distilled_patch16_384", pretrained=pretrained + ) + + hooks = [2, 5, 8, 11] if hooks == None else hooks + return _make_vit_b16_backbone( + model, + features=[96, 192, 384, 768], + hooks=hooks, + use_readout=use_readout, + start_index=2, + ) + + +def _make_vit_b_rn50_backbone( + model, + features=[256, 512, 768, 768], + size=[384, 384], + hooks=[0, 1, 8, 11], + vit_features=768, + use_vit_only=False, + use_readout="ignore", + start_index=1, +): + pretrained = nn.Module() + + pretrained.model = model + + if use_vit_only == True: + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + else: + pretrained.model.patch_embed.backbone.stages[0].register_forward_hook( + get_activation("1") + ) + pretrained.model.patch_embed.backbone.stages[1].register_forward_hook( + get_activation("2") + ) + + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + if use_vit_only == True: + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + else: + pretrained.act_postprocess1 = nn.Sequential( + nn.Identity(), nn.Identity(), nn.Identity() + ) + pretrained.act_postprocess2 = nn.Sequential( + nn.Identity(), nn.Identity(), nn.Identity() + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitb_rn50_384( + pretrained, use_readout="ignore", hooks=None, use_vit_only=False +): + model = timm.create_model("vit_base_resnet50_384", pretrained=pretrained) + + hooks = [0, 1, 8, 11] if hooks == None else hooks + return _make_vit_b_rn50_backbone( + model, + features=[256, 512, 768, 768], + size=[384, 384], + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/utils.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/utils.py new file mode 100644 index 0000000..9a9d3b5 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/midas/utils.py @@ -0,0 +1,189 @@ +"""Utils for monoDepth.""" +import sys +import re +import numpy as np +import cv2 +import torch + + +def read_pfm(path): + """Read pfm file. + + Args: + path (str): path to file + + Returns: + tuple: (data, scale) + """ + with open(path, "rb") as file: + + color = None + width = None + height = None + scale = None + endian = None + + header = file.readline().rstrip() + if header.decode("ascii") == "PF": + color = True + elif header.decode("ascii") == "Pf": + color = False + else: + raise Exception("Not a PFM file: " + path) + + dim_match = re.match(r"^(\d+)\s(\d+)\s$", file.readline().decode("ascii")) + if dim_match: + width, height = list(map(int, dim_match.groups())) + else: + raise Exception("Malformed PFM header.") + + scale = float(file.readline().decode("ascii").rstrip()) + if scale < 0: + # little-endian + endian = "<" + scale = -scale + else: + # big-endian + endian = ">" + + data = np.fromfile(file, endian + "f") + shape = (height, width, 3) if color else (height, width) + + data = np.reshape(data, shape) + data = np.flipud(data) + + return data, scale + + +def write_pfm(path, image, scale=1): + """Write pfm file. + + Args: + path (str): pathto file + image (array): data + scale (int, optional): Scale. Defaults to 1. + """ + + with open(path, "wb") as file: + color = None + + if image.dtype.name != "float32": + raise Exception("Image dtype must be float32.") + + image = np.flipud(image) + + if len(image.shape) == 3 and image.shape[2] == 3: # color image + color = True + elif ( + len(image.shape) == 2 or len(image.shape) == 3 and image.shape[2] == 1 + ): # greyscale + color = False + else: + raise Exception("Image must have H x W x 3, H x W x 1 or H x W dimensions.") + + file.write("PF\n" if color else "Pf\n".encode()) + file.write("%d %d\n".encode() % (image.shape[1], image.shape[0])) + + endian = image.dtype.byteorder + + if endian == "<" or endian == "=" and sys.byteorder == "little": + scale = -scale + + file.write("%f\n".encode() % scale) + + image.tofile(file) + + +def read_image(path): + """Read image and output RGB image (0-1). + + Args: + path (str): path to file + + Returns: + array: RGB image (0-1) + """ + img = cv2.imread(path) + + if img.ndim == 2: + img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) / 255.0 + + return img + + +def resize_image(img): + """Resize image and make it fit for network. + + Args: + img (array): image + + Returns: + tensor: data ready for network + """ + height_orig = img.shape[0] + width_orig = img.shape[1] + + if width_orig > height_orig: + scale = width_orig / 384 + else: + scale = height_orig / 384 + + height = (np.ceil(height_orig / scale / 32) * 32).astype(int) + width = (np.ceil(width_orig / scale / 32) * 32).astype(int) + + img_resized = cv2.resize(img, (width, height), interpolation=cv2.INTER_AREA) + + img_resized = ( + torch.from_numpy(np.transpose(img_resized, (2, 0, 1))).contiguous().float() + ) + img_resized = img_resized.unsqueeze(0) + + return img_resized + + +def resize_depth(depth, width, height): + """Resize depth map and bring to CPU (numpy). + + Args: + depth (tensor): depth + width (int): image width + height (int): image height + + Returns: + array: processed depth + """ + depth = torch.squeeze(depth[0, :, :, :]).to("cpu") + + depth_resized = cv2.resize( + depth.numpy(), (width, height), interpolation=cv2.INTER_CUBIC + ) + + return depth_resized + +def write_depth(path, depth, bits=1): + """Write depth map to pfm and png file. + + Args: + path (str): filepath without extension + depth (array): depth + """ + write_pfm(path + ".pfm", depth.astype(np.float32)) + + depth_min = depth.min() + depth_max = depth.max() + + max_val = (2**(8*bits))-1 + + if depth_max - depth_min > np.finfo("float").eps: + out = max_val * (depth - depth_min) / (depth_max - depth_min) + else: + out = np.zeros(depth.shape, dtype=depth.type) + + if bits == 1: + cv2.imwrite(path + ".png", out.astype("uint8")) + elif bits == 2: + cv2.imwrite(path + ".png", out.astype("uint16")) + + return diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/model_edge.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/model_edge.py new file mode 100644 index 0000000..5511f1d --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/model_edge.py @@ -0,0 +1,653 @@ +""" +Author: Zhuo Su, Wenzhe Liu +Date: Feb 18, 2021 +""" + +import math + +import cv2 +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from basicsr.utils import img2tensor + +nets = { + 'baseline': { + 'layer0': 'cv', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cv', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cv', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cv', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'c-v15': { + 'layer0': 'cd', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cv', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cv', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cv', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'a-v15': { + 'layer0': 'ad', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cv', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cv', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cv', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'r-v15': { + 'layer0': 'rd', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cv', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cv', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cv', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'cvvv4': { + 'layer0': 'cd', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cd', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cd', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cd', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'avvv4': { + 'layer0': 'ad', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'ad', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'ad', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'ad', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'rvvv4': { + 'layer0': 'rd', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'rd', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'rd', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'rd', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'cccv4': { + 'layer0': 'cd', + 'layer1': 'cd', + 'layer2': 'cd', + 'layer3': 'cv', + 'layer4': 'cd', + 'layer5': 'cd', + 'layer6': 'cd', + 'layer7': 'cv', + 'layer8': 'cd', + 'layer9': 'cd', + 'layer10': 'cd', + 'layer11': 'cv', + 'layer12': 'cd', + 'layer13': 'cd', + 'layer14': 'cd', + 'layer15': 'cv', + }, + 'aaav4': { + 'layer0': 'ad', + 'layer1': 'ad', + 'layer2': 'ad', + 'layer3': 'cv', + 'layer4': 'ad', + 'layer5': 'ad', + 'layer6': 'ad', + 'layer7': 'cv', + 'layer8': 'ad', + 'layer9': 'ad', + 'layer10': 'ad', + 'layer11': 'cv', + 'layer12': 'ad', + 'layer13': 'ad', + 'layer14': 'ad', + 'layer15': 'cv', + }, + 'rrrv4': { + 'layer0': 'rd', + 'layer1': 'rd', + 'layer2': 'rd', + 'layer3': 'cv', + 'layer4': 'rd', + 'layer5': 'rd', + 'layer6': 'rd', + 'layer7': 'cv', + 'layer8': 'rd', + 'layer9': 'rd', + 'layer10': 'rd', + 'layer11': 'cv', + 'layer12': 'rd', + 'layer13': 'rd', + 'layer14': 'rd', + 'layer15': 'cv', + }, + 'c16': { + 'layer0': 'cd', + 'layer1': 'cd', + 'layer2': 'cd', + 'layer3': 'cd', + 'layer4': 'cd', + 'layer5': 'cd', + 'layer6': 'cd', + 'layer7': 'cd', + 'layer8': 'cd', + 'layer9': 'cd', + 'layer10': 'cd', + 'layer11': 'cd', + 'layer12': 'cd', + 'layer13': 'cd', + 'layer14': 'cd', + 'layer15': 'cd', + }, + 'a16': { + 'layer0': 'ad', + 'layer1': 'ad', + 'layer2': 'ad', + 'layer3': 'ad', + 'layer4': 'ad', + 'layer5': 'ad', + 'layer6': 'ad', + 'layer7': 'ad', + 'layer8': 'ad', + 'layer9': 'ad', + 'layer10': 'ad', + 'layer11': 'ad', + 'layer12': 'ad', + 'layer13': 'ad', + 'layer14': 'ad', + 'layer15': 'ad', + }, + 'r16': { + 'layer0': 'rd', + 'layer1': 'rd', + 'layer2': 'rd', + 'layer3': 'rd', + 'layer4': 'rd', + 'layer5': 'rd', + 'layer6': 'rd', + 'layer7': 'rd', + 'layer8': 'rd', + 'layer9': 'rd', + 'layer10': 'rd', + 'layer11': 'rd', + 'layer12': 'rd', + 'layer13': 'rd', + 'layer14': 'rd', + 'layer15': 'rd', + }, + 'carv4': { + 'layer0': 'cd', + 'layer1': 'ad', + 'layer2': 'rd', + 'layer3': 'cv', + 'layer4': 'cd', + 'layer5': 'ad', + 'layer6': 'rd', + 'layer7': 'cv', + 'layer8': 'cd', + 'layer9': 'ad', + 'layer10': 'rd', + 'layer11': 'cv', + 'layer12': 'cd', + 'layer13': 'ad', + 'layer14': 'rd', + 'layer15': 'cv', + }, + } + +def createConvFunc(op_type): + assert op_type in ['cv', 'cd', 'ad', 'rd'], 'unknown op type: %s' % str(op_type) + if op_type == 'cv': + return F.conv2d + + if op_type == 'cd': + def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1): + assert dilation in [1, 2], 'dilation for cd_conv should be in 1 or 2' + assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for cd_conv should be 3x3' + assert padding == dilation, 'padding for cd_conv set wrong' + + weights_c = weights.sum(dim=[2, 3], keepdim=True) + yc = F.conv2d(x, weights_c, stride=stride, padding=0, groups=groups) + y = F.conv2d(x, weights, bias, stride=stride, padding=padding, dilation=dilation, groups=groups) + return y - yc + return func + elif op_type == 'ad': + def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1): + assert dilation in [1, 2], 'dilation for ad_conv should be in 1 or 2' + assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for ad_conv should be 3x3' + assert padding == dilation, 'padding for ad_conv set wrong' + + shape = weights.shape + weights = weights.view(shape[0], shape[1], -1) + weights_conv = (weights - weights[:, :, [3, 0, 1, 6, 4, 2, 7, 8, 5]]).view(shape) # clock-wise + y = F.conv2d(x, weights_conv, bias, stride=stride, padding=padding, dilation=dilation, groups=groups) + return y + return func + elif op_type == 'rd': + def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1): + assert dilation in [1, 2], 'dilation for rd_conv should be in 1 or 2' + assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for rd_conv should be 3x3' + padding = 2 * dilation + + shape = weights.shape + if weights.is_cuda: + buffer = torch.cuda.FloatTensor(shape[0], shape[1], 5 * 5).fill_(0) + else: + buffer = torch.zeros(shape[0], shape[1], 5 * 5) + weights = weights.view(shape[0], shape[1], -1) + buffer[:, :, [0, 2, 4, 10, 14, 20, 22, 24]] = weights[:, :, 1:] + buffer[:, :, [6, 7, 8, 11, 13, 16, 17, 18]] = -weights[:, :, 1:] + buffer[:, :, 12] = 0 + buffer = buffer.view(shape[0], shape[1], 5, 5) + y = F.conv2d(x, buffer, bias, stride=stride, padding=padding, dilation=dilation, groups=groups) + return y + return func + else: + print('impossible to be here unless you force that') + return None + +class Conv2d(nn.Module): + def __init__(self, pdc, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=False): + super(Conv2d, self).__init__() + if in_channels % groups != 0: + raise ValueError('in_channels must be divisible by groups') + if out_channels % groups != 0: + raise ValueError('out_channels must be divisible by groups') + self.in_channels = in_channels + self.out_channels = out_channels + self.kernel_size = kernel_size + self.stride = stride + self.padding = padding + self.dilation = dilation + self.groups = groups + self.weight = nn.Parameter(torch.Tensor(out_channels, in_channels // groups, kernel_size, kernel_size)) + if bias: + self.bias = nn.Parameter(torch.Tensor(out_channels)) + else: + self.register_parameter('bias', None) + self.reset_parameters() + self.pdc = pdc + + def reset_parameters(self): + nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5)) + if self.bias is not None: + fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight) + bound = 1 / math.sqrt(fan_in) + nn.init.uniform_(self.bias, -bound, bound) + + def forward(self, input): + + return self.pdc(input, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups) + +class CSAM(nn.Module): + """ + Compact Spatial Attention Module + """ + def __init__(self, channels): + super(CSAM, self).__init__() + + mid_channels = 4 + self.relu1 = nn.ReLU() + self.conv1 = nn.Conv2d(channels, mid_channels, kernel_size=1, padding=0) + self.conv2 = nn.Conv2d(mid_channels, 1, kernel_size=3, padding=1, bias=False) + self.sigmoid = nn.Sigmoid() + nn.init.constant_(self.conv1.bias, 0) + + def forward(self, x): + y = self.relu1(x) + y = self.conv1(y) + y = self.conv2(y) + y = self.sigmoid(y) + + return x * y + +class CDCM(nn.Module): + """ + Compact Dilation Convolution based Module + """ + def __init__(self, in_channels, out_channels): + super(CDCM, self).__init__() + + self.relu1 = nn.ReLU() + self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, padding=0) + self.conv2_1 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=5, padding=5, bias=False) + self.conv2_2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=7, padding=7, bias=False) + self.conv2_3 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=9, padding=9, bias=False) + self.conv2_4 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=11, padding=11, bias=False) + nn.init.constant_(self.conv1.bias, 0) + + def forward(self, x): + x = self.relu1(x) + x = self.conv1(x) + x1 = self.conv2_1(x) + x2 = self.conv2_2(x) + x3 = self.conv2_3(x) + x4 = self.conv2_4(x) + return x1 + x2 + x3 + x4 + + +class MapReduce(nn.Module): + """ + Reduce feature maps into a single edge map + """ + def __init__(self, channels): + super(MapReduce, self).__init__() + self.conv = nn.Conv2d(channels, 1, kernel_size=1, padding=0) + nn.init.constant_(self.conv.bias, 0) + + def forward(self, x): + return self.conv(x) + + +class PDCBlock(nn.Module): + def __init__(self, pdc, inplane, ouplane, stride=1): + super(PDCBlock, self).__init__() + self.stride=stride + + self.stride=stride + if self.stride > 1: + self.pool = nn.MaxPool2d(kernel_size=2, stride=2) + self.shortcut = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0) + self.conv1 = Conv2d(pdc, inplane, inplane, kernel_size=3, padding=1, groups=inplane, bias=False) + self.relu2 = nn.ReLU() + self.conv2 = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0, bias=False) + + def forward(self, x): + if self.stride > 1: + x = self.pool(x) + y = self.conv1(x) + y = self.relu2(y) + y = self.conv2(y) + if self.stride > 1: + x = self.shortcut(x) + y = y + x + return y + +class PDCBlock_converted(nn.Module): + """ + CPDC, APDC can be converted to vanilla 3x3 convolution + RPDC can be converted to vanilla 5x5 convolution + """ + def __init__(self, pdc, inplane, ouplane, stride=1): + super(PDCBlock_converted, self).__init__() + self.stride=stride + + if self.stride > 1: + self.pool = nn.MaxPool2d(kernel_size=2, stride=2) + self.shortcut = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0) + if pdc == 'rd': + self.conv1 = nn.Conv2d(inplane, inplane, kernel_size=5, padding=2, groups=inplane, bias=False) + else: + self.conv1 = nn.Conv2d(inplane, inplane, kernel_size=3, padding=1, groups=inplane, bias=False) + self.relu2 = nn.ReLU() + self.conv2 = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0, bias=False) + + def forward(self, x): + if self.stride > 1: + x = self.pool(x) + y = self.conv1(x) + y = self.relu2(y) + y = self.conv2(y) + if self.stride > 1: + x = self.shortcut(x) + y = y + x + return y + +class PiDiNet(nn.Module): + def __init__(self, inplane, pdcs, dil=None, sa=False, convert=False): + super(PiDiNet, self).__init__() + self.sa = sa + if dil is not None: + assert isinstance(dil, int), 'dil should be an int' + self.dil = dil + + self.fuseplanes = [] + + self.inplane = inplane + if convert: + if pdcs[0] == 'rd': + init_kernel_size = 5 + init_padding = 2 + else: + init_kernel_size = 3 + init_padding = 1 + self.init_block = nn.Conv2d(3, self.inplane, + kernel_size=init_kernel_size, padding=init_padding, bias=False) + block_class = PDCBlock_converted + else: + self.init_block = Conv2d(pdcs[0], 3, self.inplane, kernel_size=3, padding=1) + block_class = PDCBlock + + self.block1_1 = block_class(pdcs[1], self.inplane, self.inplane) + self.block1_2 = block_class(pdcs[2], self.inplane, self.inplane) + self.block1_3 = block_class(pdcs[3], self.inplane, self.inplane) + self.fuseplanes.append(self.inplane) # C + + inplane = self.inplane + self.inplane = self.inplane * 2 + self.block2_1 = block_class(pdcs[4], inplane, self.inplane, stride=2) + self.block2_2 = block_class(pdcs[5], self.inplane, self.inplane) + self.block2_3 = block_class(pdcs[6], self.inplane, self.inplane) + self.block2_4 = block_class(pdcs[7], self.inplane, self.inplane) + self.fuseplanes.append(self.inplane) # 2C + + inplane = self.inplane + self.inplane = self.inplane * 2 + self.block3_1 = block_class(pdcs[8], inplane, self.inplane, stride=2) + self.block3_2 = block_class(pdcs[9], self.inplane, self.inplane) + self.block3_3 = block_class(pdcs[10], self.inplane, self.inplane) + self.block3_4 = block_class(pdcs[11], self.inplane, self.inplane) + self.fuseplanes.append(self.inplane) # 4C + + self.block4_1 = block_class(pdcs[12], self.inplane, self.inplane, stride=2) + self.block4_2 = block_class(pdcs[13], self.inplane, self.inplane) + self.block4_3 = block_class(pdcs[14], self.inplane, self.inplane) + self.block4_4 = block_class(pdcs[15], self.inplane, self.inplane) + self.fuseplanes.append(self.inplane) # 4C + + self.conv_reduces = nn.ModuleList() + if self.sa and self.dil is not None: + self.attentions = nn.ModuleList() + self.dilations = nn.ModuleList() + for i in range(4): + self.dilations.append(CDCM(self.fuseplanes[i], self.dil)) + self.attentions.append(CSAM(self.dil)) + self.conv_reduces.append(MapReduce(self.dil)) + elif self.sa: + self.attentions = nn.ModuleList() + for i in range(4): + self.attentions.append(CSAM(self.fuseplanes[i])) + self.conv_reduces.append(MapReduce(self.fuseplanes[i])) + elif self.dil is not None: + self.dilations = nn.ModuleList() + for i in range(4): + self.dilations.append(CDCM(self.fuseplanes[i], self.dil)) + self.conv_reduces.append(MapReduce(self.dil)) + else: + for i in range(4): + self.conv_reduces.append(MapReduce(self.fuseplanes[i])) + + self.classifier = nn.Conv2d(4, 1, kernel_size=1) # has bias + nn.init.constant_(self.classifier.weight, 0.25) + nn.init.constant_(self.classifier.bias, 0) + + # print('initialization done') + + def get_weights(self): + conv_weights = [] + bn_weights = [] + relu_weights = [] + for pname, p in self.named_parameters(): + if 'bn' in pname: + bn_weights.append(p) + elif 'relu' in pname: + relu_weights.append(p) + else: + conv_weights.append(p) + + return conv_weights, bn_weights, relu_weights + + def forward(self, x): + H, W = x.size()[2:] + + x = self.init_block(x) + + x1 = self.block1_1(x) + x1 = self.block1_2(x1) + x1 = self.block1_3(x1) + + x2 = self.block2_1(x1) + x2 = self.block2_2(x2) + x2 = self.block2_3(x2) + x2 = self.block2_4(x2) + + x3 = self.block3_1(x2) + x3 = self.block3_2(x3) + x3 = self.block3_3(x3) + x3 = self.block3_4(x3) + + x4 = self.block4_1(x3) + x4 = self.block4_2(x4) + x4 = self.block4_3(x4) + x4 = self.block4_4(x4) + + x_fuses = [] + if self.sa and self.dil is not None: + for i, xi in enumerate([x1, x2, x3, x4]): + x_fuses.append(self.attentions[i](self.dilations[i](xi))) + elif self.sa: + for i, xi in enumerate([x1, x2, x3, x4]): + x_fuses.append(self.attentions[i](xi)) + elif self.dil is not None: + for i, xi in enumerate([x1, x2, x3, x4]): + x_fuses.append(self.dilations[i](xi)) + else: + x_fuses = [x1, x2, x3, x4] + + e1 = self.conv_reduces[0](x_fuses[0]) + e1 = F.interpolate(e1, (H, W), mode="bilinear", align_corners=False) + + e2 = self.conv_reduces[1](x_fuses[1]) + e2 = F.interpolate(e2, (H, W), mode="bilinear", align_corners=False) + + e3 = self.conv_reduces[2](x_fuses[2]) + e3 = F.interpolate(e3, (H, W), mode="bilinear", align_corners=False) + + e4 = self.conv_reduces[3](x_fuses[3]) + e4 = F.interpolate(e4, (H, W), mode="bilinear", align_corners=False) + + outputs = [e1, e2, e3, e4] + + output = self.classifier(torch.cat(outputs, dim=1)) + #if not self.training: + # return torch.sigmoid(output) + + outputs.append(output) + outputs = [torch.sigmoid(r) for r in outputs] + return outputs + +def config_model(model): + model_options = list(nets.keys()) + assert model in model_options, \ + 'unrecognized model, please choose from %s' % str(model_options) + + # print(str(nets[model])) + + pdcs = [] + for i in range(16): + layer_name = 'layer%d' % i + op = nets[model][layer_name] + pdcs.append(createConvFunc(op)) + + return pdcs + +def pidinet(): + pdcs = config_model('carv4') + dil = 24 #if args.dil else None + return PiDiNet(60, pdcs, dil=dil, sa=True) + + +if __name__ == '__main__': + model = pidinet() + ckp = torch.load('table5_pidinet.pth')['state_dict'] + model.load_state_dict({k.replace('module.',''):v for k, v in ckp.items()}) + im = cv2.imread('examples/test_my/cat_v4.png') + im = img2tensor(im).unsqueeze(0)/255. + res = model(im)[-1] + res = res>0.5 + res = res.float() + res = (res[0,0].cpu().data.numpy()*255.).astype(np.uint8) + print(res.shape) + cv2.imwrite('edge.png', res) diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/__init__.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/api.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/api.py new file mode 100644 index 0000000..dbe7a8c --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/api.py @@ -0,0 +1,35 @@ +import numpy as np +import os +import torch.nn as nn + +os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" + +import cv2 +import torch + +from . import util +from .body import Body + +remote_model_path = "https://huggingface.co/TencentARC/T2I-Adapter/blob/main/third-party-models/body_pose_model.pth" + + +class OpenposeInference(nn.Module): + + def __init__(self): + super().__init__() + body_modelpath = os.path.join('models', "body_pose_model.pth") + + if not os.path.exists(body_modelpath): + from basicsr.utils.download_util import load_file_from_url + load_file_from_url(remote_model_path, model_dir='models') + + self.body_estimation = Body(body_modelpath) + + def forward(self, x): + x = x[:, :, ::-1].copy() + with torch.no_grad(): + candidate, subset = self.body_estimation(x) + canvas = np.zeros_like(x) + canvas = util.draw_bodypose(canvas, candidate, subset) + canvas = cv2.cvtColor(canvas, cv2.COLOR_RGB2BGR) + return canvas diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/body.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/body.py new file mode 100644 index 0000000..ecfa8a0 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/body.py @@ -0,0 +1,211 @@ +import cv2 +import math +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +import time +import torch +from scipy.ndimage.filters import gaussian_filter +from torchvision import transforms + +from . import util +from .model import bodypose_model + + +class Body(object): + + def __init__(self, model_path): + self.model = bodypose_model() + if torch.cuda.is_available(): + self.model = self.model.cuda() + print('cuda') + model_dict = util.transfer(self.model, torch.load(model_path)) + self.model.load_state_dict(model_dict) + self.model.eval() + + def __call__(self, oriImg): + # scale_search = [0.5, 1.0, 1.5, 2.0] + scale_search = [0.5] + boxsize = 368 + stride = 8 + padValue = 128 + thre1 = 0.1 + thre2 = 0.05 + multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search] + heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 19)) + paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38)) + + for m in range(len(multiplier)): + scale = multiplier[m] + imageToTest = cv2.resize(oriImg, (0, 0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) + imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue) + im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5 + im = np.ascontiguousarray(im) + + data = torch.from_numpy(im).float() + if torch.cuda.is_available(): + data = data.cuda() + # data = data.permute([2, 0, 1]).unsqueeze(0).float() + with torch.no_grad(): + Mconv7_stage6_L1, Mconv7_stage6_L2 = self.model(data) + Mconv7_stage6_L1 = Mconv7_stage6_L1.cpu().numpy() + Mconv7_stage6_L2 = Mconv7_stage6_L2.cpu().numpy() + + # extract outputs, resize, and remove padding + # heatmap = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[1]].data), (1, 2, 0)) # output 1 is heatmaps + heatmap = np.transpose(np.squeeze(Mconv7_stage6_L2), (1, 2, 0)) # output 1 is heatmaps + heatmap = cv2.resize(heatmap, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC) + heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + heatmap = cv2.resize(heatmap, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC) + + # paf = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[0]].data), (1, 2, 0)) # output 0 is PAFs + paf = np.transpose(np.squeeze(Mconv7_stage6_L1), (1, 2, 0)) # output 0 is PAFs + paf = cv2.resize(paf, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC) + paf = paf[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + paf = cv2.resize(paf, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC) + + heatmap_avg += heatmap_avg + heatmap / len(multiplier) + paf_avg += +paf / len(multiplier) + + all_peaks = [] + peak_counter = 0 + + for part in range(18): + map_ori = heatmap_avg[:, :, part] + one_heatmap = gaussian_filter(map_ori, sigma=3) + + map_left = np.zeros(one_heatmap.shape) + map_left[1:, :] = one_heatmap[:-1, :] + map_right = np.zeros(one_heatmap.shape) + map_right[:-1, :] = one_heatmap[1:, :] + map_up = np.zeros(one_heatmap.shape) + map_up[:, 1:] = one_heatmap[:, :-1] + map_down = np.zeros(one_heatmap.shape) + map_down[:, :-1] = one_heatmap[:, 1:] + + peaks_binary = np.logical_and.reduce((one_heatmap >= map_left, one_heatmap >= map_right, + one_heatmap >= map_up, one_heatmap >= map_down, one_heatmap > thre1)) + peaks = list(zip(np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0])) # note reverse + peaks_with_score = [x + (map_ori[x[1], x[0]], ) for x in peaks] + peak_id = range(peak_counter, peak_counter + len(peaks)) + peaks_with_score_and_id = [peaks_with_score[i] + (peak_id[i], ) for i in range(len(peak_id))] + + all_peaks.append(peaks_with_score_and_id) + peak_counter += len(peaks) + + # find connection in the specified sequence, center 29 is in the position 15 + limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \ + [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \ + [1, 16], [16, 18], [3, 17], [6, 18]] + # the middle joints heatmap correpondence + mapIdx = [[31, 32], [39, 40], [33, 34], [35, 36], [41, 42], [43, 44], [19, 20], [21, 22], \ + [23, 24], [25, 26], [27, 28], [29, 30], [47, 48], [49, 50], [53, 54], [51, 52], \ + [55, 56], [37, 38], [45, 46]] + + connection_all = [] + special_k = [] + mid_num = 10 + + for k in range(len(mapIdx)): + score_mid = paf_avg[:, :, [x - 19 for x in mapIdx[k]]] + candA = all_peaks[limbSeq[k][0] - 1] + candB = all_peaks[limbSeq[k][1] - 1] + nA = len(candA) + nB = len(candB) + indexA, indexB = limbSeq[k] + if (nA != 0 and nB != 0): + connection_candidate = [] + for i in range(nA): + for j in range(nB): + vec = np.subtract(candB[j][:2], candA[i][:2]) + norm = math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]) + norm = max(0.001, norm) + vec = np.divide(vec, norm) + + startend = list(zip(np.linspace(candA[i][0], candB[j][0], num=mid_num), \ + np.linspace(candA[i][1], candB[j][1], num=mid_num))) + + vec_x = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 0] \ + for I in range(len(startend))]) + vec_y = np.array([score_mid[int(round(startend[I][1])), int(round(startend[I][0])), 1] \ + for I in range(len(startend))]) + + score_midpts = np.multiply(vec_x, vec[0]) + np.multiply(vec_y, vec[1]) + score_with_dist_prior = sum(score_midpts) / len(score_midpts) + min( + 0.5 * oriImg.shape[0] / norm - 1, 0) + criterion1 = len(np.nonzero(score_midpts > thre2)[0]) > 0.8 * len(score_midpts) + criterion2 = score_with_dist_prior > 0 + if criterion1 and criterion2: + connection_candidate.append( + [i, j, score_with_dist_prior, score_with_dist_prior + candA[i][2] + candB[j][2]]) + + connection_candidate = sorted(connection_candidate, key=lambda x: x[2], reverse=True) + connection = np.zeros((0, 5)) + for c in range(len(connection_candidate)): + i, j, s = connection_candidate[c][0:3] + if (i not in connection[:, 3] and j not in connection[:, 4]): + connection = np.vstack([connection, [candA[i][3], candB[j][3], s, i, j]]) + if (len(connection) >= min(nA, nB)): + break + + connection_all.append(connection) + else: + special_k.append(k) + connection_all.append([]) + + # last number in each row is the total parts number of that person + # the second last number in each row is the score of the overall configuration + subset = -1 * np.ones((0, 20)) + candidate = np.array([item for sublist in all_peaks for item in sublist]) + + for k in range(len(mapIdx)): + if k not in special_k: + partAs = connection_all[k][:, 0] + partBs = connection_all[k][:, 1] + indexA, indexB = np.array(limbSeq[k]) - 1 + + for i in range(len(connection_all[k])): # = 1:size(temp,1) + found = 0 + subset_idx = [-1, -1] + for j in range(len(subset)): # 1:size(subset,1): + if subset[j][indexA] == partAs[i] or subset[j][indexB] == partBs[i]: + subset_idx[found] = j + found += 1 + + if found == 1: + j = subset_idx[0] + if subset[j][indexB] != partBs[i]: + subset[j][indexB] = partBs[i] + subset[j][-1] += 1 + subset[j][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2] + elif found == 2: # if found 2 and disjoint, merge them + j1, j2 = subset_idx + membership = ((subset[j1] >= 0).astype(int) + (subset[j2] >= 0).astype(int))[:-2] + if len(np.nonzero(membership == 2)[0]) == 0: # merge + subset[j1][:-2] += (subset[j2][:-2] + 1) + subset[j1][-2:] += subset[j2][-2:] + subset[j1][-2] += connection_all[k][i][2] + subset = np.delete(subset, j2, 0) + else: # as like found == 1 + subset[j1][indexB] = partBs[i] + subset[j1][-1] += 1 + subset[j1][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2] + + # if find no partA in the subset, create a new subset + elif not found and k < 17: + row = -1 * np.ones(20) + row[indexA] = partAs[i] + row[indexB] = partBs[i] + row[-1] = 2 + row[-2] = sum(candidate[connection_all[k][i, :2].astype(int), 2]) + connection_all[k][i][2] + subset = np.vstack([subset, row]) + # delete some rows of subset which has few parts occur + deleteIdx = [] + for i in range(len(subset)): + if subset[i][-1] < 4 or subset[i][-2] / subset[i][-1] < 0.4: + deleteIdx.append(i) + subset = np.delete(subset, deleteIdx, axis=0) + + # subset: n*20 array, 0-17 is the index in candidate, 18 is the total score, 19 is the total parts + # candidate: x, y, score, id + return candidate, subset diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/hand.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/hand.py new file mode 100644 index 0000000..1100239 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/hand.py @@ -0,0 +1,77 @@ +import cv2 +import json +import math +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +import time +import torch +from scipy.ndimage.filters import gaussian_filter +from skimage.measure import label + +from . import util +from .model import handpose_model + + +class Hand(object): + + def __init__(self, model_path): + self.model = handpose_model() + if torch.cuda.is_available(): + self.model = self.model.cuda() + print('cuda') + model_dict = util.transfer(self.model, torch.load(model_path)) + self.model.load_state_dict(model_dict) + self.model.eval() + + def __call__(self, oriImg): + scale_search = [0.5, 1.0, 1.5, 2.0] + # scale_search = [0.5] + boxsize = 368 + stride = 8 + padValue = 128 + thre = 0.05 + multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search] + heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 22)) + # paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38)) + + for m in range(len(multiplier)): + scale = multiplier[m] + imageToTest = cv2.resize(oriImg, (0, 0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) + imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue) + im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5 + im = np.ascontiguousarray(im) + + data = torch.from_numpy(im).float() + if torch.cuda.is_available(): + data = data.cuda() + # data = data.permute([2, 0, 1]).unsqueeze(0).float() + with torch.no_grad(): + output = self.model(data).cpu().numpy() + # output = self.model(data).numpy()q + + # extract outputs, resize, and remove padding + heatmap = np.transpose(np.squeeze(output), (1, 2, 0)) # output 1 is heatmaps + heatmap = cv2.resize(heatmap, (0, 0), fx=stride, fy=stride, interpolation=cv2.INTER_CUBIC) + heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + heatmap = cv2.resize(heatmap, (oriImg.shape[1], oriImg.shape[0]), interpolation=cv2.INTER_CUBIC) + + heatmap_avg += heatmap / len(multiplier) + + all_peaks = [] + for part in range(21): + map_ori = heatmap_avg[:, :, part] + one_heatmap = gaussian_filter(map_ori, sigma=3) + binary = np.ascontiguousarray(one_heatmap > thre, dtype=np.uint8) + # 全部小于阈值 + if np.sum(binary) == 0: + all_peaks.append([0, 0]) + continue + label_img, label_numbers = label(binary, return_num=True, connectivity=binary.ndim) + max_index = np.argmax([np.sum(map_ori[label_img == i]) for i in range(1, label_numbers + 1)]) + 1 + label_img[label_img != max_index] = 0 + map_ori[label_img == 0] = 0 + + y, x = util.npmax(map_ori) + all_peaks.append([x, y]) + return np.array(all_peaks) diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/model.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/model.py new file mode 100644 index 0000000..6f5d8eb --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/model.py @@ -0,0 +1,178 @@ +import torch +import torch.nn as nn +from collections import OrderedDict + + +def make_layers(block, no_relu_layers): + layers = [] + for layer_name, v in block.items(): + if 'pool' in layer_name: + layer = nn.MaxPool2d(kernel_size=v[0], stride=v[1], padding=v[2]) + layers.append((layer_name, layer)) + else: + conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], kernel_size=v[2], stride=v[3], padding=v[4]) + layers.append((layer_name, conv2d)) + if layer_name not in no_relu_layers: + layers.append(('relu_' + layer_name, nn.ReLU(inplace=True))) + + return nn.Sequential(OrderedDict(layers)) + + +class bodypose_model(nn.Module): + + def __init__(self): + super(bodypose_model, self).__init__() + + # these layers have no relu layer + no_relu_layers = ['conv5_5_CPM_L1', 'conv5_5_CPM_L2', 'Mconv7_stage2_L1',\ + 'Mconv7_stage2_L2', 'Mconv7_stage3_L1', 'Mconv7_stage3_L2',\ + 'Mconv7_stage4_L1', 'Mconv7_stage4_L2', 'Mconv7_stage5_L1',\ + 'Mconv7_stage5_L2', 'Mconv7_stage6_L1', 'Mconv7_stage6_L1'] + blocks = {} + block0 = OrderedDict([('conv1_1', [3, 64, 3, 1, 1]), ('conv1_2', [64, 64, 3, 1, 1]), ('pool1_stage1', [2, 2, + 0]), + ('conv2_1', [64, 128, 3, 1, 1]), ('conv2_2', [128, 128, 3, 1, 1]), + ('pool2_stage1', [2, 2, 0]), ('conv3_1', [128, 256, 3, 1, 1]), + ('conv3_2', [256, 256, 3, 1, 1]), ('conv3_3', [256, 256, 3, 1, 1]), + ('conv3_4', [256, 256, 3, 1, 1]), ('pool3_stage1', [2, 2, 0]), + ('conv4_1', [256, 512, 3, 1, 1]), ('conv4_2', [512, 512, 3, 1, 1]), + ('conv4_3_CPM', [512, 256, 3, 1, 1]), ('conv4_4_CPM', [256, 128, 3, 1, 1])]) + + # Stage 1 + block1_1 = OrderedDict([('conv5_1_CPM_L1', [128, 128, 3, 1, 1]), ('conv5_2_CPM_L1', [128, 128, 3, 1, 1]), + ('conv5_3_CPM_L1', [128, 128, 3, 1, 1]), ('conv5_4_CPM_L1', [128, 512, 1, 1, 0]), + ('conv5_5_CPM_L1', [512, 38, 1, 1, 0])]) + + block1_2 = OrderedDict([('conv5_1_CPM_L2', [128, 128, 3, 1, 1]), ('conv5_2_CPM_L2', [128, 128, 3, 1, 1]), + ('conv5_3_CPM_L2', [128, 128, 3, 1, 1]), ('conv5_4_CPM_L2', [128, 512, 1, 1, 0]), + ('conv5_5_CPM_L2', [512, 19, 1, 1, 0])]) + blocks['block1_1'] = block1_1 + blocks['block1_2'] = block1_2 + + self.model0 = make_layers(block0, no_relu_layers) + + # Stages 2 - 6 + for i in range(2, 7): + blocks['block%d_1' % i] = OrderedDict([('Mconv1_stage%d_L1' % i, [185, 128, 7, 1, 3]), + ('Mconv2_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d_L1' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d_L1' % i, [128, 38, 1, 1, 0])]) + + blocks['block%d_2' % i] = OrderedDict([('Mconv1_stage%d_L2' % i, [185, 128, 7, 1, 3]), + ('Mconv2_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d_L2' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d_L2' % i, [128, 19, 1, 1, 0])]) + + for k in blocks.keys(): + blocks[k] = make_layers(blocks[k], no_relu_layers) + + self.model1_1 = blocks['block1_1'] + self.model2_1 = blocks['block2_1'] + self.model3_1 = blocks['block3_1'] + self.model4_1 = blocks['block4_1'] + self.model5_1 = blocks['block5_1'] + self.model6_1 = blocks['block6_1'] + + self.model1_2 = blocks['block1_2'] + self.model2_2 = blocks['block2_2'] + self.model3_2 = blocks['block3_2'] + self.model4_2 = blocks['block4_2'] + self.model5_2 = blocks['block5_2'] + self.model6_2 = blocks['block6_2'] + + def forward(self, x): + + out1 = self.model0(x) + + out1_1 = self.model1_1(out1) + out1_2 = self.model1_2(out1) + out2 = torch.cat([out1_1, out1_2, out1], 1) + + out2_1 = self.model2_1(out2) + out2_2 = self.model2_2(out2) + out3 = torch.cat([out2_1, out2_2, out1], 1) + + out3_1 = self.model3_1(out3) + out3_2 = self.model3_2(out3) + out4 = torch.cat([out3_1, out3_2, out1], 1) + + out4_1 = self.model4_1(out4) + out4_2 = self.model4_2(out4) + out5 = torch.cat([out4_1, out4_2, out1], 1) + + out5_1 = self.model5_1(out5) + out5_2 = self.model5_2(out5) + out6 = torch.cat([out5_1, out5_2, out1], 1) + + out6_1 = self.model6_1(out6) + out6_2 = self.model6_2(out6) + + return out6_1, out6_2 + + +class handpose_model(nn.Module): + + def __init__(self): + super(handpose_model, self).__init__() + + # these layers have no relu layer + no_relu_layers = ['conv6_2_CPM', 'Mconv7_stage2', 'Mconv7_stage3',\ + 'Mconv7_stage4', 'Mconv7_stage5', 'Mconv7_stage6'] + # stage 1 + block1_0 = OrderedDict([('conv1_1', [3, 64, 3, 1, 1]), ('conv1_2', [64, 64, 3, 1, 1]), + ('pool1_stage1', [2, 2, 0]), ('conv2_1', [64, 128, 3, 1, 1]), + ('conv2_2', [128, 128, 3, 1, 1]), ('pool2_stage1', [2, 2, 0]), + ('conv3_1', [128, 256, 3, 1, 1]), ('conv3_2', [256, 256, 3, 1, 1]), + ('conv3_3', [256, 256, 3, 1, 1]), ('conv3_4', [256, 256, 3, 1, 1]), + ('pool3_stage1', [2, 2, 0]), ('conv4_1', [256, 512, 3, 1, 1]), + ('conv4_2', [512, 512, 3, 1, 1]), ('conv4_3', [512, 512, 3, 1, 1]), + ('conv4_4', [512, 512, 3, 1, 1]), ('conv5_1', [512, 512, 3, 1, 1]), + ('conv5_2', [512, 512, 3, 1, 1]), ('conv5_3_CPM', [512, 128, 3, 1, 1])]) + + block1_1 = OrderedDict([('conv6_1_CPM', [128, 512, 1, 1, 0]), ('conv6_2_CPM', [512, 22, 1, 1, 0])]) + + blocks = {} + blocks['block1_0'] = block1_0 + blocks['block1_1'] = block1_1 + + # stage 2-6 + for i in range(2, 7): + blocks['block%d' % i] = OrderedDict([('Mconv1_stage%d' % i, [150, 128, 7, 1, 3]), + ('Mconv2_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d' % i, [128, 22, 1, 1, 0])]) + + for k in blocks.keys(): + blocks[k] = make_layers(blocks[k], no_relu_layers) + + self.model1_0 = blocks['block1_0'] + self.model1_1 = blocks['block1_1'] + self.model2 = blocks['block2'] + self.model3 = blocks['block3'] + self.model4 = blocks['block4'] + self.model5 = blocks['block5'] + self.model6 = blocks['block6'] + + def forward(self, x): + out1_0 = self.model1_0(x) + out1_1 = self.model1_1(out1_0) + concat_stage2 = torch.cat([out1_1, out1_0], 1) + out_stage2 = self.model2(concat_stage2) + concat_stage3 = torch.cat([out_stage2, out1_0], 1) + out_stage3 = self.model3(concat_stage3) + concat_stage4 = torch.cat([out_stage3, out1_0], 1) + out_stage4 = self.model4(concat_stage4) + concat_stage5 = torch.cat([out_stage4, out1_0], 1) + out_stage5 = self.model5(concat_stage5) + concat_stage6 = torch.cat([out_stage5, out1_0], 1) + out_stage6 = self.model6(concat_stage6) + return out_stage6 diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/util.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/util.py new file mode 100644 index 0000000..29724d5 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/openpose/util.py @@ -0,0 +1,203 @@ +import math + +import cv2 +import matplotlib +import numpy as np + + +def padRightDownCorner(img, stride, padValue): + h = img.shape[0] + w = img.shape[1] + + pad = 4 * [None] + pad[0] = 0 # up + pad[1] = 0 # left + pad[2] = 0 if (h % stride == 0) else stride - (h % stride) # down + pad[3] = 0 if (w % stride == 0) else stride - (w % stride) # right + + img_padded = img + pad_up = np.tile(img_padded[0:1, :, :] * 0 + padValue, (pad[0], 1, 1)) + img_padded = np.concatenate((pad_up, img_padded), axis=0) + pad_left = np.tile(img_padded[:, 0:1, :] * 0 + padValue, (1, pad[1], 1)) + img_padded = np.concatenate((pad_left, img_padded), axis=1) + pad_down = np.tile(img_padded[-2:-1, :, :] * 0 + padValue, (pad[2], 1, 1)) + img_padded = np.concatenate((img_padded, pad_down), axis=0) + pad_right = np.tile(img_padded[:, -2:-1, :] * 0 + padValue, (1, pad[3], 1)) + img_padded = np.concatenate((img_padded, pad_right), axis=1) + + return img_padded, pad + + +# transfer caffe model to pytorch which will match the layer name +def transfer(model, model_weights): + transfered_model_weights = {} + for weights_name in model.state_dict().keys(): + transfered_model_weights[weights_name] = model_weights['.'.join(weights_name.split('.')[1:])] + return transfered_model_weights + + +# draw the body keypoint and lims +def draw_bodypose(canvas, candidate, subset): + stickwidth = 4 + limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \ + [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \ + [1, 16], [16, 18], [3, 17], [6, 18]] + + colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \ + [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \ + [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]] + for i in range(18): + for n in range(len(subset)): + index = int(subset[n][i]) + if index == -1: + continue + x, y = candidate[index][0:2] + cv2.circle(canvas, (int(x), int(y)), 4, colors[i], thickness=-1) + for i in range(17): + for n in range(len(subset)): + index = subset[n][np.array(limbSeq[i]) - 1] + if -1 in index: + continue + cur_canvas = canvas.copy() + Y = candidate[index.astype(int), 0] + X = candidate[index.astype(int), 1] + mX = np.mean(X) + mY = np.mean(Y) + length = ((X[0] - X[1])**2 + (Y[0] - Y[1])**2)**0.5 + angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1])) + polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1) + cv2.fillConvexPoly(cur_canvas, polygon, colors[i]) + canvas = cv2.addWeighted(canvas, 0.4, cur_canvas, 0.6, 0) + # plt.imsave("preview.jpg", canvas[:, :, [2, 1, 0]]) + # plt.imshow(canvas[:, :, [2, 1, 0]]) + return canvas + + +# image drawed by opencv is not good. +def draw_handpose(canvas, all_hand_peaks, show_number=False): + edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \ + [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]] + + for peaks in all_hand_peaks: + for ie, e in enumerate(edges): + if np.sum(np.all(peaks[e], axis=1) == 0) == 0: + x1, y1 = peaks[e[0]] + x2, y2 = peaks[e[1]] + cv2.line( + canvas, (x1, y1), (x2, y2), + matplotlib.colors.hsv_to_rgb([ie / float(len(edges)), 1.0, 1.0]) * 255, + thickness=2) + + for i, keyponit in enumerate(peaks): + x, y = keyponit + cv2.circle(canvas, (x, y), 4, (0, 0, 255), thickness=-1) + if show_number: + cv2.putText(canvas, str(i), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (0, 0, 0), lineType=cv2.LINE_AA) + return canvas + + +# detect hand according to body pose keypoints +# please refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/src/openpose/hand/handDetector.cpp +def handDetect(candidate, subset, oriImg): + # right hand: wrist 4, elbow 3, shoulder 2 + # left hand: wrist 7, elbow 6, shoulder 5 + ratioWristElbow = 0.33 + detect_result = [] + image_height, image_width = oriImg.shape[0:2] + for person in subset.astype(int): + # if any of three not detected + has_left = np.sum(person[[5, 6, 7]] == -1) == 0 + has_right = np.sum(person[[2, 3, 4]] == -1) == 0 + if not (has_left or has_right): + continue + hands = [] + #left hand + if has_left: + left_shoulder_index, left_elbow_index, left_wrist_index = person[[5, 6, 7]] + x1, y1 = candidate[left_shoulder_index][:2] + x2, y2 = candidate[left_elbow_index][:2] + x3, y3 = candidate[left_wrist_index][:2] + hands.append([x1, y1, x2, y2, x3, y3, True]) + # right hand + if has_right: + right_shoulder_index, right_elbow_index, right_wrist_index = person[[2, 3, 4]] + x1, y1 = candidate[right_shoulder_index][:2] + x2, y2 = candidate[right_elbow_index][:2] + x3, y3 = candidate[right_wrist_index][:2] + hands.append([x1, y1, x2, y2, x3, y3, False]) + + for x1, y1, x2, y2, x3, y3, is_left in hands: + # pos_hand = pos_wrist + ratio * (pos_wrist - pos_elbox) = (1 + ratio) * pos_wrist - ratio * pos_elbox + # handRectangle.x = posePtr[wrist*3] + ratioWristElbow * (posePtr[wrist*3] - posePtr[elbow*3]); + # handRectangle.y = posePtr[wrist*3+1] + ratioWristElbow * (posePtr[wrist*3+1] - posePtr[elbow*3+1]); + # const auto distanceWristElbow = getDistance(poseKeypoints, person, wrist, elbow); + # const auto distanceElbowShoulder = getDistance(poseKeypoints, person, elbow, shoulder); + # handRectangle.width = 1.5f * fastMax(distanceWristElbow, 0.9f * distanceElbowShoulder); + x = x3 + ratioWristElbow * (x3 - x2) + y = y3 + ratioWristElbow * (y3 - y2) + distanceWristElbow = math.sqrt((x3 - x2)**2 + (y3 - y2)**2) + distanceElbowShoulder = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) + width = 1.5 * max(distanceWristElbow, 0.9 * distanceElbowShoulder) + # x-y refers to the center --> offset to topLeft point + # handRectangle.x -= handRectangle.width / 2.f; + # handRectangle.y -= handRectangle.height / 2.f; + x -= width / 2 + y -= width / 2 # width = height + # overflow the image + if x < 0: x = 0 + if y < 0: y = 0 + width1 = width + width2 = width + if x + width > image_width: width1 = image_width - x + if y + width > image_height: width2 = image_height - y + width = min(width1, width2) + # the max hand box value is 20 pixels + if width >= 20: + detect_result.append([int(x), int(y), int(width), is_left]) + ''' + return value: [[x, y, w, True if left hand else False]]. + width=height since the network require squared input. + x, y is the coordinate of top left + ''' + return detect_result + + +# get max index of 2d array +def npmax(array): + arrayindex = array.argmax(1) + arrayvalue = array.max(1) + i = arrayvalue.argmax() + j = arrayindex[i] + return i, j + + +def HWC3(x): + assert x.dtype == np.uint8 + if x.ndim == 2: + x = x[:, :, None] + assert x.ndim == 3 + H, W, C = x.shape + assert C == 1 or C == 3 or C == 4 + if C == 3: + return x + if C == 1: + return np.concatenate([x, x, x], axis=2) + if C == 4: + color = x[:, :, 0:3].astype(np.float32) + alpha = x[:, :, 3:4].astype(np.float32) / 255.0 + y = color * alpha + 255.0 * (1.0 - alpha) + y = y.clip(0, 255).astype(np.uint8) + return y + + +def resize_image(input_image, resolution): + H, W, C = input_image.shape + H = float(H) + W = float(W) + k = float(resolution) / min(H, W) + H *= k + W *= k + H = int(np.round(H / 64.0)) * 64 + W = int(np.round(W / 64.0)) * 64 + img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4 if k > 1 else cv2.INTER_AREA) + return img diff --git a/comparison_models/T2IAdapter/ldm/modules/extra_condition/utils.py b/comparison_models/T2IAdapter/ldm/modules/extra_condition/utils.py new file mode 100644 index 0000000..af6bcb9 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/extra_condition/utils.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +import cv2 +import numpy as np + +skeleton = [[15, 13], [13, 11], [16, 14], [14, 12], [11, 12], [5, 11], [6, 12], [5, 6], [5, 7], [6, 8], [7, 9], [8, 10], + [1, 2], [0, 1], [0, 2], [1, 3], [2, 4], [3, 5], [4, 6]] + +pose_kpt_color = [[51, 153, 255], [51, 153, 255], [51, 153, 255], [51, 153, 255], [51, 153, 255], [0, 255, 0], + [255, 128, 0], [0, 255, 0], [255, 128, 0], [0, 255, 0], [255, 128, 0], [0, 255, 0], [255, 128, 0], + [0, 255, 0], [255, 128, 0], [0, 255, 0], [255, 128, 0]] + +pose_link_color = [[0, 255, 0], [0, 255, 0], [255, 128, 0], [255, 128, 0], + [51, 153, 255], [51, 153, 255], [51, 153, 255], [51, 153, 255], [0, 255, 0], [255, 128, 0], + [0, 255, 0], [255, 128, 0], [51, 153, 255], [51, 153, 255], [51, 153, 255], [51, 153, 255], + [51, 153, 255], [51, 153, 255], [51, 153, 255]] + + +def imshow_keypoints(img, + pose_result, + kpt_score_thr=0.1, + radius=2, + thickness=2): + """Draw keypoints and links on an image. + + Args: + img (ndarry): The image to draw poses on. + pose_result (list[kpts]): The poses to draw. Each element kpts is + a set of K keypoints as an Kx3 numpy.ndarray, where each + keypoint is represented as x, y, score. + kpt_score_thr (float, optional): Minimum score of keypoints + to be shown. Default: 0.3. + thickness (int): Thickness of lines. + """ + + img_h, img_w, _ = img.shape + img = np.zeros(img.shape) + + for idx, kpts in enumerate(pose_result): + if idx > 1: + continue + kpts = kpts['keypoints'] + # print(kpts) + kpts = np.array(kpts, copy=False) + + # draw each point on image + assert len(pose_kpt_color) == len(kpts) + + for kid, kpt in enumerate(kpts): + x_coord, y_coord, kpt_score = int(kpt[0]), int(kpt[1]), kpt[2] + + if kpt_score < kpt_score_thr or pose_kpt_color[kid] is None: + # skip the point that should not be drawn + continue + + color = tuple(int(c) for c in pose_kpt_color[kid]) + cv2.circle(img, (int(x_coord), int(y_coord)), radius, color, -1) + + # draw links + + for sk_id, sk in enumerate(skeleton): + pos1 = (int(kpts[sk[0], 0]), int(kpts[sk[0], 1])) + pos2 = (int(kpts[sk[1], 0]), int(kpts[sk[1], 1])) + + if (pos1[0] <= 0 or pos1[0] >= img_w or pos1[1] <= 0 or pos1[1] >= img_h or pos2[0] <= 0 + or pos2[0] >= img_w or pos2[1] <= 0 or pos2[1] >= img_h or kpts[sk[0], 2] < kpt_score_thr + or kpts[sk[1], 2] < kpt_score_thr or pose_link_color[sk_id] is None): + # skip the link that should not be drawn + continue + color = tuple(int(c) for c in pose_link_color[sk_id]) + cv2.line(img, pos1, pos2, color, thickness=thickness) + + return img diff --git a/comparison_models/T2IAdapter/ldm/modules/image_degradation/__init__.py b/comparison_models/T2IAdapter/ldm/modules/image_degradation/__init__.py new file mode 100644 index 0000000..7836cad --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/image_degradation/__init__.py @@ -0,0 +1,2 @@ +from ldm.modules.image_degradation.bsrgan import degradation_bsrgan_variant as degradation_fn_bsr +from ldm.modules.image_degradation.bsrgan_light import degradation_bsrgan_variant as degradation_fn_bsr_light diff --git a/comparison_models/T2IAdapter/ldm/modules/image_degradation/bsrgan.py b/comparison_models/T2IAdapter/ldm/modules/image_degradation/bsrgan.py new file mode 100644 index 0000000..32ef561 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/image_degradation/bsrgan.py @@ -0,0 +1,730 @@ +# -*- coding: utf-8 -*- +""" +# -------------------------------------------- +# Super-Resolution +# -------------------------------------------- +# +# Kai Zhang (cskaizhang@gmail.com) +# https://github.com/cszn +# From 2019/03--2021/08 +# -------------------------------------------- +""" + +import numpy as np +import cv2 +import torch + +from functools import partial +import random +from scipy import ndimage +import scipy +import scipy.stats as ss +from scipy.interpolate import interp2d +from scipy.linalg import orth +import albumentations + +import ldm.modules.image_degradation.utils_image as util + + +def modcrop_np(img, sf): + ''' + Args: + img: numpy image, WxH or WxHxC + sf: scale factor + Return: + cropped image + ''' + w, h = img.shape[:2] + im = np.copy(img) + return im[:w - w % sf, :h - h % sf, ...] + + +""" +# -------------------------------------------- +# anisotropic Gaussian kernels +# -------------------------------------------- +""" + + +def analytic_kernel(k): + """Calculate the X4 kernel from the X2 kernel (for proof see appendix in paper)""" + k_size = k.shape[0] + # Calculate the big kernels size + big_k = np.zeros((3 * k_size - 2, 3 * k_size - 2)) + # Loop over the small kernel to fill the big one + for r in range(k_size): + for c in range(k_size): + big_k[2 * r:2 * r + k_size, 2 * c:2 * c + k_size] += k[r, c] * k + # Crop the edges of the big kernel to ignore very small values and increase run time of SR + crop = k_size // 2 + cropped_big_k = big_k[crop:-crop, crop:-crop] + # Normalize to 1 + return cropped_big_k / cropped_big_k.sum() + + +def anisotropic_Gaussian(ksize=15, theta=np.pi, l1=6, l2=6): + """ generate an anisotropic Gaussian kernel + Args: + ksize : e.g., 15, kernel size + theta : [0, pi], rotation angle range + l1 : [0.1,50], scaling of eigenvalues + l2 : [0.1,l1], scaling of eigenvalues + If l1 = l2, will get an isotropic Gaussian kernel. + Returns: + k : kernel + """ + + v = np.dot(np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]), np.array([1., 0.])) + V = np.array([[v[0], v[1]], [v[1], -v[0]]]) + D = np.array([[l1, 0], [0, l2]]) + Sigma = np.dot(np.dot(V, D), np.linalg.inv(V)) + k = gm_blur_kernel(mean=[0, 0], cov=Sigma, size=ksize) + + return k + + +def gm_blur_kernel(mean, cov, size=15): + center = size / 2.0 + 0.5 + k = np.zeros([size, size]) + for y in range(size): + for x in range(size): + cy = y - center + 1 + cx = x - center + 1 + k[y, x] = ss.multivariate_normal.pdf([cx, cy], mean=mean, cov=cov) + + k = k / np.sum(k) + return k + + +def shift_pixel(x, sf, upper_left=True): + """shift pixel for super-resolution with different scale factors + Args: + x: WxHxC or WxH + sf: scale factor + upper_left: shift direction + """ + h, w = x.shape[:2] + shift = (sf - 1) * 0.5 + xv, yv = np.arange(0, w, 1.0), np.arange(0, h, 1.0) + if upper_left: + x1 = xv + shift + y1 = yv + shift + else: + x1 = xv - shift + y1 = yv - shift + + x1 = np.clip(x1, 0, w - 1) + y1 = np.clip(y1, 0, h - 1) + + if x.ndim == 2: + x = interp2d(xv, yv, x)(x1, y1) + if x.ndim == 3: + for i in range(x.shape[-1]): + x[:, :, i] = interp2d(xv, yv, x[:, :, i])(x1, y1) + + return x + + +def blur(x, k): + ''' + x: image, NxcxHxW + k: kernel, Nx1xhxw + ''' + n, c = x.shape[:2] + p1, p2 = (k.shape[-2] - 1) // 2, (k.shape[-1] - 1) // 2 + x = torch.nn.functional.pad(x, pad=(p1, p2, p1, p2), mode='replicate') + k = k.repeat(1, c, 1, 1) + k = k.view(-1, 1, k.shape[2], k.shape[3]) + x = x.view(1, -1, x.shape[2], x.shape[3]) + x = torch.nn.functional.conv2d(x, k, bias=None, stride=1, padding=0, groups=n * c) + x = x.view(n, c, x.shape[2], x.shape[3]) + + return x + + +def gen_kernel(k_size=np.array([15, 15]), scale_factor=np.array([4, 4]), min_var=0.6, max_var=10., noise_level=0): + """" + # modified version of https://github.com/assafshocher/BlindSR_dataset_generator + # Kai Zhang + # min_var = 0.175 * sf # variance of the gaussian kernel will be sampled between min_var and max_var + # max_var = 2.5 * sf + """ + # Set random eigen-vals (lambdas) and angle (theta) for COV matrix + lambda_1 = min_var + np.random.rand() * (max_var - min_var) + lambda_2 = min_var + np.random.rand() * (max_var - min_var) + theta = np.random.rand() * np.pi # random theta + noise = -noise_level + np.random.rand(*k_size) * noise_level * 2 + + # Set COV matrix using Lambdas and Theta + LAMBDA = np.diag([lambda_1, lambda_2]) + Q = np.array([[np.cos(theta), -np.sin(theta)], + [np.sin(theta), np.cos(theta)]]) + SIGMA = Q @ LAMBDA @ Q.T + INV_SIGMA = np.linalg.inv(SIGMA)[None, None, :, :] + + # Set expectation position (shifting kernel for aligned image) + MU = k_size // 2 - 0.5 * (scale_factor - 1) # - 0.5 * (scale_factor - k_size % 2) + MU = MU[None, None, :, None] + + # Create meshgrid for Gaussian + [X, Y] = np.meshgrid(range(k_size[0]), range(k_size[1])) + Z = np.stack([X, Y], 2)[:, :, :, None] + + # Calcualte Gaussian for every pixel of the kernel + ZZ = Z - MU + ZZ_t = ZZ.transpose(0, 1, 3, 2) + raw_kernel = np.exp(-0.5 * np.squeeze(ZZ_t @ INV_SIGMA @ ZZ)) * (1 + noise) + + # shift the kernel so it will be centered + # raw_kernel_centered = kernel_shift(raw_kernel, scale_factor) + + # Normalize the kernel and return + # kernel = raw_kernel_centered / np.sum(raw_kernel_centered) + kernel = raw_kernel / np.sum(raw_kernel) + return kernel + + +def fspecial_gaussian(hsize, sigma): + hsize = [hsize, hsize] + siz = [(hsize[0] - 1.0) / 2.0, (hsize[1] - 1.0) / 2.0] + std = sigma + [x, y] = np.meshgrid(np.arange(-siz[1], siz[1] + 1), np.arange(-siz[0], siz[0] + 1)) + arg = -(x * x + y * y) / (2 * std * std) + h = np.exp(arg) + h[h < scipy.finfo(float).eps * h.max()] = 0 + sumh = h.sum() + if sumh != 0: + h = h / sumh + return h + + +def fspecial_laplacian(alpha): + alpha = max([0, min([alpha, 1])]) + h1 = alpha / (alpha + 1) + h2 = (1 - alpha) / (alpha + 1) + h = [[h1, h2, h1], [h2, -4 / (alpha + 1), h2], [h1, h2, h1]] + h = np.array(h) + return h + + +def fspecial(filter_type, *args, **kwargs): + ''' + python code from: + https://github.com/ronaldosena/imagens-medicas-2/blob/40171a6c259edec7827a6693a93955de2bd39e76/Aulas/aula_2_-_uniform_filter/matlab_fspecial.py + ''' + if filter_type == 'gaussian': + return fspecial_gaussian(*args, **kwargs) + if filter_type == 'laplacian': + return fspecial_laplacian(*args, **kwargs) + + +""" +# -------------------------------------------- +# degradation models +# -------------------------------------------- +""" + + +def bicubic_degradation(x, sf=3): + ''' + Args: + x: HxWxC image, [0, 1] + sf: down-scale factor + Return: + bicubicly downsampled LR image + ''' + x = util.imresize_np(x, scale=1 / sf) + return x + + +def srmd_degradation(x, k, sf=3): + ''' blur + bicubic downsampling + Args: + x: HxWxC image, [0, 1] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + Reference: + @inproceedings{zhang2018learning, + title={Learning a single convolutional super-resolution network for multiple degradations}, + author={Zhang, Kai and Zuo, Wangmeng and Zhang, Lei}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + pages={3262--3271}, + year={2018} + } + ''' + x = ndimage.filters.convolve(x, np.expand_dims(k, axis=2), mode='wrap') # 'nearest' | 'mirror' + x = bicubic_degradation(x, sf=sf) + return x + + +def dpsr_degradation(x, k, sf=3): + ''' bicubic downsampling + blur + Args: + x: HxWxC image, [0, 1] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + Reference: + @inproceedings{zhang2019deep, + title={Deep Plug-and-Play Super-Resolution for Arbitrary Blur Kernels}, + author={Zhang, Kai and Zuo, Wangmeng and Zhang, Lei}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + pages={1671--1681}, + year={2019} + } + ''' + x = bicubic_degradation(x, sf=sf) + x = ndimage.filters.convolve(x, np.expand_dims(k, axis=2), mode='wrap') + return x + + +def classical_degradation(x, k, sf=3): + ''' blur + downsampling + Args: + x: HxWxC image, [0, 1]/[0, 255] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + ''' + x = ndimage.filters.convolve(x, np.expand_dims(k, axis=2), mode='wrap') + # x = filters.correlate(x, np.expand_dims(np.flip(k), axis=2)) + st = 0 + return x[st::sf, st::sf, ...] + + +def add_sharpening(img, weight=0.5, radius=50, threshold=10): + """USM sharpening. borrowed from real-ESRGAN + Input image: I; Blurry image: B. + 1. K = I + weight * (I - B) + 2. Mask = 1 if abs(I - B) > threshold, else: 0 + 3. Blur mask: + 4. Out = Mask * K + (1 - Mask) * I + Args: + img (Numpy array): Input image, HWC, BGR; float32, [0, 1]. + weight (float): Sharp weight. Default: 1. + radius (float): Kernel size of Gaussian blur. Default: 50. + threshold (int): + """ + if radius % 2 == 0: + radius += 1 + blur = cv2.GaussianBlur(img, (radius, radius), 0) + residual = img - blur + mask = np.abs(residual) * 255 > threshold + mask = mask.astype('float32') + soft_mask = cv2.GaussianBlur(mask, (radius, radius), 0) + + K = img + weight * residual + K = np.clip(K, 0, 1) + return soft_mask * K + (1 - soft_mask) * img + + +def add_blur(img, sf=4): + wd2 = 4.0 + sf + wd = 2.0 + 0.2 * sf + if random.random() < 0.5: + l1 = wd2 * random.random() + l2 = wd2 * random.random() + k = anisotropic_Gaussian(ksize=2 * random.randint(2, 11) + 3, theta=random.random() * np.pi, l1=l1, l2=l2) + else: + k = fspecial('gaussian', 2 * random.randint(2, 11) + 3, wd * random.random()) + img = ndimage.filters.convolve(img, np.expand_dims(k, axis=2), mode='mirror') + + return img + + +def add_resize(img, sf=4): + rnum = np.random.rand() + if rnum > 0.8: # up + sf1 = random.uniform(1, 2) + elif rnum < 0.7: # down + sf1 = random.uniform(0.5 / sf, 1) + else: + sf1 = 1.0 + img = cv2.resize(img, (int(sf1 * img.shape[1]), int(sf1 * img.shape[0])), interpolation=random.choice([1, 2, 3])) + img = np.clip(img, 0.0, 1.0) + + return img + + +# def add_Gaussian_noise(img, noise_level1=2, noise_level2=25): +# noise_level = random.randint(noise_level1, noise_level2) +# rnum = np.random.rand() +# if rnum > 0.6: # add color Gaussian noise +# img += np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) +# elif rnum < 0.4: # add grayscale Gaussian noise +# img += np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) +# else: # add noise +# L = noise_level2 / 255. +# D = np.diag(np.random.rand(3)) +# U = orth(np.random.rand(3, 3)) +# conv = np.dot(np.dot(np.transpose(U), D), U) +# img += np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) +# img = np.clip(img, 0.0, 1.0) +# return img + +def add_Gaussian_noise(img, noise_level1=2, noise_level2=25): + noise_level = random.randint(noise_level1, noise_level2) + rnum = np.random.rand() + if rnum > 0.6: # add color Gaussian noise + img = img + np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) + elif rnum < 0.4: # add grayscale Gaussian noise + img = img + np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) + else: # add noise + L = noise_level2 / 255. + D = np.diag(np.random.rand(3)) + U = orth(np.random.rand(3, 3)) + conv = np.dot(np.dot(np.transpose(U), D), U) + img = img + np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) + img = np.clip(img, 0.0, 1.0) + return img + + +def add_speckle_noise(img, noise_level1=2, noise_level2=25): + noise_level = random.randint(noise_level1, noise_level2) + img = np.clip(img, 0.0, 1.0) + rnum = random.random() + if rnum > 0.6: + img += img * np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) + elif rnum < 0.4: + img += img * np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) + else: + L = noise_level2 / 255. + D = np.diag(np.random.rand(3)) + U = orth(np.random.rand(3, 3)) + conv = np.dot(np.dot(np.transpose(U), D), U) + img += img * np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) + img = np.clip(img, 0.0, 1.0) + return img + + +def add_Poisson_noise(img): + img = np.clip((img * 255.0).round(), 0, 255) / 255. + vals = 10 ** (2 * random.random() + 2.0) # [2, 4] + if random.random() < 0.5: + img = np.random.poisson(img * vals).astype(np.float32) / vals + else: + img_gray = np.dot(img[..., :3], [0.299, 0.587, 0.114]) + img_gray = np.clip((img_gray * 255.0).round(), 0, 255) / 255. + noise_gray = np.random.poisson(img_gray * vals).astype(np.float32) / vals - img_gray + img += noise_gray[:, :, np.newaxis] + img = np.clip(img, 0.0, 1.0) + return img + + +def add_JPEG_noise(img): + quality_factor = random.randint(30, 95) + img = cv2.cvtColor(util.single2uint(img), cv2.COLOR_RGB2BGR) + result, encimg = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), quality_factor]) + img = cv2.imdecode(encimg, 1) + img = cv2.cvtColor(util.uint2single(img), cv2.COLOR_BGR2RGB) + return img + + +def random_crop(lq, hq, sf=4, lq_patchsize=64): + h, w = lq.shape[:2] + rnd_h = random.randint(0, h - lq_patchsize) + rnd_w = random.randint(0, w - lq_patchsize) + lq = lq[rnd_h:rnd_h + lq_patchsize, rnd_w:rnd_w + lq_patchsize, :] + + rnd_h_H, rnd_w_H = int(rnd_h * sf), int(rnd_w * sf) + hq = hq[rnd_h_H:rnd_h_H + lq_patchsize * sf, rnd_w_H:rnd_w_H + lq_patchsize * sf, :] + return lq, hq + + +def degradation_bsrgan(img, sf=4, lq_patchsize=72, isp_model=None): + """ + This is the degradation model of BSRGAN from the paper + "Designing a Practical Degradation Model for Deep Blind Image Super-Resolution" + ---------- + img: HXWXC, [0, 1], its size should be large than (lq_patchsizexsf)x(lq_patchsizexsf) + sf: scale factor + isp_model: camera ISP model + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + isp_prob, jpeg_prob, scale2_prob = 0.25, 0.9, 0.25 + sf_ori = sf + + h1, w1 = img.shape[:2] + img = img.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = img.shape[:2] + + if h < lq_patchsize * sf or w < lq_patchsize * sf: + raise ValueError(f'img size ({h1}X{w1}) is too small!') + + hq = img.copy() + + if sf == 4 and random.random() < scale2_prob: # downsample1 + if np.random.rand() < 0.5: + img = cv2.resize(img, (int(1 / 2 * img.shape[1]), int(1 / 2 * img.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + img = util.imresize_np(img, 1 / 2, True) + img = np.clip(img, 0.0, 1.0) + sf = 2 + + shuffle_order = random.sample(range(7), 7) + idx1, idx2 = shuffle_order.index(2), shuffle_order.index(3) + if idx1 > idx2: # keep downsample3 last + shuffle_order[idx1], shuffle_order[idx2] = shuffle_order[idx2], shuffle_order[idx1] + + for i in shuffle_order: + + if i == 0: + img = add_blur(img, sf=sf) + + elif i == 1: + img = add_blur(img, sf=sf) + + elif i == 2: + a, b = img.shape[1], img.shape[0] + # downsample2 + if random.random() < 0.75: + sf1 = random.uniform(1, 2 * sf) + img = cv2.resize(img, (int(1 / sf1 * img.shape[1]), int(1 / sf1 * img.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + k = fspecial('gaussian', 25, random.uniform(0.1, 0.6 * sf)) + k_shifted = shift_pixel(k, sf) + k_shifted = k_shifted / k_shifted.sum() # blur with shifted kernel + img = ndimage.filters.convolve(img, np.expand_dims(k_shifted, axis=2), mode='mirror') + img = img[0::sf, 0::sf, ...] # nearest downsampling + img = np.clip(img, 0.0, 1.0) + + elif i == 3: + # downsample3 + img = cv2.resize(img, (int(1 / sf * a), int(1 / sf * b)), interpolation=random.choice([1, 2, 3])) + img = np.clip(img, 0.0, 1.0) + + elif i == 4: + # add Gaussian noise + img = add_Gaussian_noise(img, noise_level1=2, noise_level2=25) + + elif i == 5: + # add JPEG noise + if random.random() < jpeg_prob: + img = add_JPEG_noise(img) + + elif i == 6: + # add processed camera sensor noise + if random.random() < isp_prob and isp_model is not None: + with torch.no_grad(): + img, hq = isp_model.forward(img.copy(), hq) + + # add final JPEG compression noise + img = add_JPEG_noise(img) + + # random crop + img, hq = random_crop(img, hq, sf_ori, lq_patchsize) + + return img, hq + + +# todo no isp_model? +def degradation_bsrgan_variant(image, sf=4, isp_model=None): + """ + This is the degradation model of BSRGAN from the paper + "Designing a Practical Degradation Model for Deep Blind Image Super-Resolution" + ---------- + sf: scale factor + isp_model: camera ISP model + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + image = util.uint2single(image) + isp_prob, jpeg_prob, scale2_prob = 0.25, 0.9, 0.25 + sf_ori = sf + + h1, w1 = image.shape[:2] + image = image.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = image.shape[:2] + + hq = image.copy() + + if sf == 4 and random.random() < scale2_prob: # downsample1 + if np.random.rand() < 0.5: + image = cv2.resize(image, (int(1 / 2 * image.shape[1]), int(1 / 2 * image.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + image = util.imresize_np(image, 1 / 2, True) + image = np.clip(image, 0.0, 1.0) + sf = 2 + + shuffle_order = random.sample(range(7), 7) + idx1, idx2 = shuffle_order.index(2), shuffle_order.index(3) + if idx1 > idx2: # keep downsample3 last + shuffle_order[idx1], shuffle_order[idx2] = shuffle_order[idx2], shuffle_order[idx1] + + for i in shuffle_order: + + if i == 0: + image = add_blur(image, sf=sf) + + elif i == 1: + image = add_blur(image, sf=sf) + + elif i == 2: + a, b = image.shape[1], image.shape[0] + # downsample2 + if random.random() < 0.75: + sf1 = random.uniform(1, 2 * sf) + image = cv2.resize(image, (int(1 / sf1 * image.shape[1]), int(1 / sf1 * image.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + k = fspecial('gaussian', 25, random.uniform(0.1, 0.6 * sf)) + k_shifted = shift_pixel(k, sf) + k_shifted = k_shifted / k_shifted.sum() # blur with shifted kernel + image = ndimage.filters.convolve(image, np.expand_dims(k_shifted, axis=2), mode='mirror') + image = image[0::sf, 0::sf, ...] # nearest downsampling + image = np.clip(image, 0.0, 1.0) + + elif i == 3: + # downsample3 + image = cv2.resize(image, (int(1 / sf * a), int(1 / sf * b)), interpolation=random.choice([1, 2, 3])) + image = np.clip(image, 0.0, 1.0) + + elif i == 4: + # add Gaussian noise + image = add_Gaussian_noise(image, noise_level1=2, noise_level2=25) + + elif i == 5: + # add JPEG noise + if random.random() < jpeg_prob: + image = add_JPEG_noise(image) + + # elif i == 6: + # # add processed camera sensor noise + # if random.random() < isp_prob and isp_model is not None: + # with torch.no_grad(): + # img, hq = isp_model.forward(img.copy(), hq) + + # add final JPEG compression noise + image = add_JPEG_noise(image) + image = util.single2uint(image) + example = {"image":image} + return example + + +# TODO incase there is a pickle error one needs to replace a += x with a = a + x in add_speckle_noise etc... +def degradation_bsrgan_plus(img, sf=4, shuffle_prob=0.5, use_sharp=True, lq_patchsize=64, isp_model=None): + """ + This is an extended degradation model by combining + the degradation models of BSRGAN and Real-ESRGAN + ---------- + img: HXWXC, [0, 1], its size should be large than (lq_patchsizexsf)x(lq_patchsizexsf) + sf: scale factor + use_shuffle: the degradation shuffle + use_sharp: sharpening the img + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + + h1, w1 = img.shape[:2] + img = img.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = img.shape[:2] + + if h < lq_patchsize * sf or w < lq_patchsize * sf: + raise ValueError(f'img size ({h1}X{w1}) is too small!') + + if use_sharp: + img = add_sharpening(img) + hq = img.copy() + + if random.random() < shuffle_prob: + shuffle_order = random.sample(range(13), 13) + else: + shuffle_order = list(range(13)) + # local shuffle for noise, JPEG is always the last one + shuffle_order[2:6] = random.sample(shuffle_order[2:6], len(range(2, 6))) + shuffle_order[9:13] = random.sample(shuffle_order[9:13], len(range(9, 13))) + + poisson_prob, speckle_prob, isp_prob = 0.1, 0.1, 0.1 + + for i in shuffle_order: + if i == 0: + img = add_blur(img, sf=sf) + elif i == 1: + img = add_resize(img, sf=sf) + elif i == 2: + img = add_Gaussian_noise(img, noise_level1=2, noise_level2=25) + elif i == 3: + if random.random() < poisson_prob: + img = add_Poisson_noise(img) + elif i == 4: + if random.random() < speckle_prob: + img = add_speckle_noise(img) + elif i == 5: + if random.random() < isp_prob and isp_model is not None: + with torch.no_grad(): + img, hq = isp_model.forward(img.copy(), hq) + elif i == 6: + img = add_JPEG_noise(img) + elif i == 7: + img = add_blur(img, sf=sf) + elif i == 8: + img = add_resize(img, sf=sf) + elif i == 9: + img = add_Gaussian_noise(img, noise_level1=2, noise_level2=25) + elif i == 10: + if random.random() < poisson_prob: + img = add_Poisson_noise(img) + elif i == 11: + if random.random() < speckle_prob: + img = add_speckle_noise(img) + elif i == 12: + if random.random() < isp_prob and isp_model is not None: + with torch.no_grad(): + img, hq = isp_model.forward(img.copy(), hq) + else: + print('check the shuffle!') + + # resize to desired size + img = cv2.resize(img, (int(1 / sf * hq.shape[1]), int(1 / sf * hq.shape[0])), + interpolation=random.choice([1, 2, 3])) + + # add final JPEG compression noise + img = add_JPEG_noise(img) + + # random crop + img, hq = random_crop(img, hq, sf, lq_patchsize) + + return img, hq + + +if __name__ == '__main__': + print("hey") + img = util.imread_uint('utils/test.png', 3) + print(img) + img = util.uint2single(img) + print(img) + img = img[:448, :448] + h = img.shape[0] // 4 + print("resizing to", h) + sf = 4 + deg_fn = partial(degradation_bsrgan_variant, sf=sf) + for i in range(20): + print(i) + img_lq = deg_fn(img) + print(img_lq) + img_lq_bicubic = albumentations.SmallestMaxSize(max_size=h, interpolation=cv2.INTER_CUBIC)(image=img)["image"] + print(img_lq.shape) + print("bicubic", img_lq_bicubic.shape) + print(img_hq.shape) + lq_nearest = cv2.resize(util.single2uint(img_lq), (int(sf * img_lq.shape[1]), int(sf * img_lq.shape[0])), + interpolation=0) + lq_bicubic_nearest = cv2.resize(util.single2uint(img_lq_bicubic), (int(sf * img_lq.shape[1]), int(sf * img_lq.shape[0])), + interpolation=0) + img_concat = np.concatenate([lq_bicubic_nearest, lq_nearest, util.single2uint(img_hq)], axis=1) + util.imsave(img_concat, str(i) + '.png') + + diff --git a/comparison_models/T2IAdapter/ldm/modules/image_degradation/bsrgan_light.py b/comparison_models/T2IAdapter/ldm/modules/image_degradation/bsrgan_light.py new file mode 100644 index 0000000..808c7f8 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/image_degradation/bsrgan_light.py @@ -0,0 +1,651 @@ +# -*- coding: utf-8 -*- +import numpy as np +import cv2 +import torch + +from functools import partial +import random +from scipy import ndimage +import scipy +import scipy.stats as ss +from scipy.interpolate import interp2d +from scipy.linalg import orth +import albumentations + +import ldm.modules.image_degradation.utils_image as util + +""" +# -------------------------------------------- +# Super-Resolution +# -------------------------------------------- +# +# Kai Zhang (cskaizhang@gmail.com) +# https://github.com/cszn +# From 2019/03--2021/08 +# -------------------------------------------- +""" + +def modcrop_np(img, sf): + ''' + Args: + img: numpy image, WxH or WxHxC + sf: scale factor + Return: + cropped image + ''' + w, h = img.shape[:2] + im = np.copy(img) + return im[:w - w % sf, :h - h % sf, ...] + + +""" +# -------------------------------------------- +# anisotropic Gaussian kernels +# -------------------------------------------- +""" + + +def analytic_kernel(k): + """Calculate the X4 kernel from the X2 kernel (for proof see appendix in paper)""" + k_size = k.shape[0] + # Calculate the big kernels size + big_k = np.zeros((3 * k_size - 2, 3 * k_size - 2)) + # Loop over the small kernel to fill the big one + for r in range(k_size): + for c in range(k_size): + big_k[2 * r:2 * r + k_size, 2 * c:2 * c + k_size] += k[r, c] * k + # Crop the edges of the big kernel to ignore very small values and increase run time of SR + crop = k_size // 2 + cropped_big_k = big_k[crop:-crop, crop:-crop] + # Normalize to 1 + return cropped_big_k / cropped_big_k.sum() + + +def anisotropic_Gaussian(ksize=15, theta=np.pi, l1=6, l2=6): + """ generate an anisotropic Gaussian kernel + Args: + ksize : e.g., 15, kernel size + theta : [0, pi], rotation angle range + l1 : [0.1,50], scaling of eigenvalues + l2 : [0.1,l1], scaling of eigenvalues + If l1 = l2, will get an isotropic Gaussian kernel. + Returns: + k : kernel + """ + + v = np.dot(np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]), np.array([1., 0.])) + V = np.array([[v[0], v[1]], [v[1], -v[0]]]) + D = np.array([[l1, 0], [0, l2]]) + Sigma = np.dot(np.dot(V, D), np.linalg.inv(V)) + k = gm_blur_kernel(mean=[0, 0], cov=Sigma, size=ksize) + + return k + + +def gm_blur_kernel(mean, cov, size=15): + center = size / 2.0 + 0.5 + k = np.zeros([size, size]) + for y in range(size): + for x in range(size): + cy = y - center + 1 + cx = x - center + 1 + k[y, x] = ss.multivariate_normal.pdf([cx, cy], mean=mean, cov=cov) + + k = k / np.sum(k) + return k + + +def shift_pixel(x, sf, upper_left=True): + """shift pixel for super-resolution with different scale factors + Args: + x: WxHxC or WxH + sf: scale factor + upper_left: shift direction + """ + h, w = x.shape[:2] + shift = (sf - 1) * 0.5 + xv, yv = np.arange(0, w, 1.0), np.arange(0, h, 1.0) + if upper_left: + x1 = xv + shift + y1 = yv + shift + else: + x1 = xv - shift + y1 = yv - shift + + x1 = np.clip(x1, 0, w - 1) + y1 = np.clip(y1, 0, h - 1) + + if x.ndim == 2: + x = interp2d(xv, yv, x)(x1, y1) + if x.ndim == 3: + for i in range(x.shape[-1]): + x[:, :, i] = interp2d(xv, yv, x[:, :, i])(x1, y1) + + return x + + +def blur(x, k): + ''' + x: image, NxcxHxW + k: kernel, Nx1xhxw + ''' + n, c = x.shape[:2] + p1, p2 = (k.shape[-2] - 1) // 2, (k.shape[-1] - 1) // 2 + x = torch.nn.functional.pad(x, pad=(p1, p2, p1, p2), mode='replicate') + k = k.repeat(1, c, 1, 1) + k = k.view(-1, 1, k.shape[2], k.shape[3]) + x = x.view(1, -1, x.shape[2], x.shape[3]) + x = torch.nn.functional.conv2d(x, k, bias=None, stride=1, padding=0, groups=n * c) + x = x.view(n, c, x.shape[2], x.shape[3]) + + return x + + +def gen_kernel(k_size=np.array([15, 15]), scale_factor=np.array([4, 4]), min_var=0.6, max_var=10., noise_level=0): + """" + # modified version of https://github.com/assafshocher/BlindSR_dataset_generator + # Kai Zhang + # min_var = 0.175 * sf # variance of the gaussian kernel will be sampled between min_var and max_var + # max_var = 2.5 * sf + """ + # Set random eigen-vals (lambdas) and angle (theta) for COV matrix + lambda_1 = min_var + np.random.rand() * (max_var - min_var) + lambda_2 = min_var + np.random.rand() * (max_var - min_var) + theta = np.random.rand() * np.pi # random theta + noise = -noise_level + np.random.rand(*k_size) * noise_level * 2 + + # Set COV matrix using Lambdas and Theta + LAMBDA = np.diag([lambda_1, lambda_2]) + Q = np.array([[np.cos(theta), -np.sin(theta)], + [np.sin(theta), np.cos(theta)]]) + SIGMA = Q @ LAMBDA @ Q.T + INV_SIGMA = np.linalg.inv(SIGMA)[None, None, :, :] + + # Set expectation position (shifting kernel for aligned image) + MU = k_size // 2 - 0.5 * (scale_factor - 1) # - 0.5 * (scale_factor - k_size % 2) + MU = MU[None, None, :, None] + + # Create meshgrid for Gaussian + [X, Y] = np.meshgrid(range(k_size[0]), range(k_size[1])) + Z = np.stack([X, Y], 2)[:, :, :, None] + + # Calcualte Gaussian for every pixel of the kernel + ZZ = Z - MU + ZZ_t = ZZ.transpose(0, 1, 3, 2) + raw_kernel = np.exp(-0.5 * np.squeeze(ZZ_t @ INV_SIGMA @ ZZ)) * (1 + noise) + + # shift the kernel so it will be centered + # raw_kernel_centered = kernel_shift(raw_kernel, scale_factor) + + # Normalize the kernel and return + # kernel = raw_kernel_centered / np.sum(raw_kernel_centered) + kernel = raw_kernel / np.sum(raw_kernel) + return kernel + + +def fspecial_gaussian(hsize, sigma): + hsize = [hsize, hsize] + siz = [(hsize[0] - 1.0) / 2.0, (hsize[1] - 1.0) / 2.0] + std = sigma + [x, y] = np.meshgrid(np.arange(-siz[1], siz[1] + 1), np.arange(-siz[0], siz[0] + 1)) + arg = -(x * x + y * y) / (2 * std * std) + h = np.exp(arg) + h[h < scipy.finfo(float).eps * h.max()] = 0 + sumh = h.sum() + if sumh != 0: + h = h / sumh + return h + + +def fspecial_laplacian(alpha): + alpha = max([0, min([alpha, 1])]) + h1 = alpha / (alpha + 1) + h2 = (1 - alpha) / (alpha + 1) + h = [[h1, h2, h1], [h2, -4 / (alpha + 1), h2], [h1, h2, h1]] + h = np.array(h) + return h + + +def fspecial(filter_type, *args, **kwargs): + ''' + python code from: + https://github.com/ronaldosena/imagens-medicas-2/blob/40171a6c259edec7827a6693a93955de2bd39e76/Aulas/aula_2_-_uniform_filter/matlab_fspecial.py + ''' + if filter_type == 'gaussian': + return fspecial_gaussian(*args, **kwargs) + if filter_type == 'laplacian': + return fspecial_laplacian(*args, **kwargs) + + +""" +# -------------------------------------------- +# degradation models +# -------------------------------------------- +""" + + +def bicubic_degradation(x, sf=3): + ''' + Args: + x: HxWxC image, [0, 1] + sf: down-scale factor + Return: + bicubicly downsampled LR image + ''' + x = util.imresize_np(x, scale=1 / sf) + return x + + +def srmd_degradation(x, k, sf=3): + ''' blur + bicubic downsampling + Args: + x: HxWxC image, [0, 1] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + Reference: + @inproceedings{zhang2018learning, + title={Learning a single convolutional super-resolution network for multiple degradations}, + author={Zhang, Kai and Zuo, Wangmeng and Zhang, Lei}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + pages={3262--3271}, + year={2018} + } + ''' + x = ndimage.convolve(x, np.expand_dims(k, axis=2), mode='wrap') # 'nearest' | 'mirror' + x = bicubic_degradation(x, sf=sf) + return x + + +def dpsr_degradation(x, k, sf=3): + ''' bicubic downsampling + blur + Args: + x: HxWxC image, [0, 1] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + Reference: + @inproceedings{zhang2019deep, + title={Deep Plug-and-Play Super-Resolution for Arbitrary Blur Kernels}, + author={Zhang, Kai and Zuo, Wangmeng and Zhang, Lei}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + pages={1671--1681}, + year={2019} + } + ''' + x = bicubic_degradation(x, sf=sf) + x = ndimage.convolve(x, np.expand_dims(k, axis=2), mode='wrap') + return x + + +def classical_degradation(x, k, sf=3): + ''' blur + downsampling + Args: + x: HxWxC image, [0, 1]/[0, 255] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + ''' + x = ndimage.convolve(x, np.expand_dims(k, axis=2), mode='wrap') + # x = filters.correlate(x, np.expand_dims(np.flip(k), axis=2)) + st = 0 + return x[st::sf, st::sf, ...] + + +def add_sharpening(img, weight=0.5, radius=50, threshold=10): + """USM sharpening. borrowed from real-ESRGAN + Input image: I; Blurry image: B. + 1. K = I + weight * (I - B) + 2. Mask = 1 if abs(I - B) > threshold, else: 0 + 3. Blur mask: + 4. Out = Mask * K + (1 - Mask) * I + Args: + img (Numpy array): Input image, HWC, BGR; float32, [0, 1]. + weight (float): Sharp weight. Default: 1. + radius (float): Kernel size of Gaussian blur. Default: 50. + threshold (int): + """ + if radius % 2 == 0: + radius += 1 + blur = cv2.GaussianBlur(img, (radius, radius), 0) + residual = img - blur + mask = np.abs(residual) * 255 > threshold + mask = mask.astype('float32') + soft_mask = cv2.GaussianBlur(mask, (radius, radius), 0) + + K = img + weight * residual + K = np.clip(K, 0, 1) + return soft_mask * K + (1 - soft_mask) * img + + +def add_blur(img, sf=4): + wd2 = 4.0 + sf + wd = 2.0 + 0.2 * sf + + wd2 = wd2/4 + wd = wd/4 + + if random.random() < 0.5: + l1 = wd2 * random.random() + l2 = wd2 * random.random() + k = anisotropic_Gaussian(ksize=random.randint(2, 11) + 3, theta=random.random() * np.pi, l1=l1, l2=l2) + else: + k = fspecial('gaussian', random.randint(2, 4) + 3, wd * random.random()) + img = ndimage.convolve(img, np.expand_dims(k, axis=2), mode='mirror') + + return img + + +def add_resize(img, sf=4): + rnum = np.random.rand() + if rnum > 0.8: # up + sf1 = random.uniform(1, 2) + elif rnum < 0.7: # down + sf1 = random.uniform(0.5 / sf, 1) + else: + sf1 = 1.0 + img = cv2.resize(img, (int(sf1 * img.shape[1]), int(sf1 * img.shape[0])), interpolation=random.choice([1, 2, 3])) + img = np.clip(img, 0.0, 1.0) + + return img + + +# def add_Gaussian_noise(img, noise_level1=2, noise_level2=25): +# noise_level = random.randint(noise_level1, noise_level2) +# rnum = np.random.rand() +# if rnum > 0.6: # add color Gaussian noise +# img += np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) +# elif rnum < 0.4: # add grayscale Gaussian noise +# img += np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) +# else: # add noise +# L = noise_level2 / 255. +# D = np.diag(np.random.rand(3)) +# U = orth(np.random.rand(3, 3)) +# conv = np.dot(np.dot(np.transpose(U), D), U) +# img += np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) +# img = np.clip(img, 0.0, 1.0) +# return img + +def add_Gaussian_noise(img, noise_level1=2, noise_level2=25): + noise_level = random.randint(noise_level1, noise_level2) + rnum = np.random.rand() + if rnum > 0.6: # add color Gaussian noise + img = img + np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) + elif rnum < 0.4: # add grayscale Gaussian noise + img = img + np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) + else: # add noise + L = noise_level2 / 255. + D = np.diag(np.random.rand(3)) + U = orth(np.random.rand(3, 3)) + conv = np.dot(np.dot(np.transpose(U), D), U) + img = img + np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) + img = np.clip(img, 0.0, 1.0) + return img + + +def add_speckle_noise(img, noise_level1=2, noise_level2=25): + noise_level = random.randint(noise_level1, noise_level2) + img = np.clip(img, 0.0, 1.0) + rnum = random.random() + if rnum > 0.6: + img += img * np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) + elif rnum < 0.4: + img += img * np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) + else: + L = noise_level2 / 255. + D = np.diag(np.random.rand(3)) + U = orth(np.random.rand(3, 3)) + conv = np.dot(np.dot(np.transpose(U), D), U) + img += img * np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) + img = np.clip(img, 0.0, 1.0) + return img + + +def add_Poisson_noise(img): + img = np.clip((img * 255.0).round(), 0, 255) / 255. + vals = 10 ** (2 * random.random() + 2.0) # [2, 4] + if random.random() < 0.5: + img = np.random.poisson(img * vals).astype(np.float32) / vals + else: + img_gray = np.dot(img[..., :3], [0.299, 0.587, 0.114]) + img_gray = np.clip((img_gray * 255.0).round(), 0, 255) / 255. + noise_gray = np.random.poisson(img_gray * vals).astype(np.float32) / vals - img_gray + img += noise_gray[:, :, np.newaxis] + img = np.clip(img, 0.0, 1.0) + return img + + +def add_JPEG_noise(img): + quality_factor = random.randint(80, 95) + img = cv2.cvtColor(util.single2uint(img), cv2.COLOR_RGB2BGR) + result, encimg = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), quality_factor]) + img = cv2.imdecode(encimg, 1) + img = cv2.cvtColor(util.uint2single(img), cv2.COLOR_BGR2RGB) + return img + + +def random_crop(lq, hq, sf=4, lq_patchsize=64): + h, w = lq.shape[:2] + rnd_h = random.randint(0, h - lq_patchsize) + rnd_w = random.randint(0, w - lq_patchsize) + lq = lq[rnd_h:rnd_h + lq_patchsize, rnd_w:rnd_w + lq_patchsize, :] + + rnd_h_H, rnd_w_H = int(rnd_h * sf), int(rnd_w * sf) + hq = hq[rnd_h_H:rnd_h_H + lq_patchsize * sf, rnd_w_H:rnd_w_H + lq_patchsize * sf, :] + return lq, hq + + +def degradation_bsrgan(img, sf=4, lq_patchsize=72, isp_model=None): + """ + This is the degradation model of BSRGAN from the paper + "Designing a Practical Degradation Model for Deep Blind Image Super-Resolution" + ---------- + img: HXWXC, [0, 1], its size should be large than (lq_patchsizexsf)x(lq_patchsizexsf) + sf: scale factor + isp_model: camera ISP model + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + isp_prob, jpeg_prob, scale2_prob = 0.25, 0.9, 0.25 + sf_ori = sf + + h1, w1 = img.shape[:2] + img = img.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = img.shape[:2] + + if h < lq_patchsize * sf or w < lq_patchsize * sf: + raise ValueError(f'img size ({h1}X{w1}) is too small!') + + hq = img.copy() + + if sf == 4 and random.random() < scale2_prob: # downsample1 + if np.random.rand() < 0.5: + img = cv2.resize(img, (int(1 / 2 * img.shape[1]), int(1 / 2 * img.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + img = util.imresize_np(img, 1 / 2, True) + img = np.clip(img, 0.0, 1.0) + sf = 2 + + shuffle_order = random.sample(range(7), 7) + idx1, idx2 = shuffle_order.index(2), shuffle_order.index(3) + if idx1 > idx2: # keep downsample3 last + shuffle_order[idx1], shuffle_order[idx2] = shuffle_order[idx2], shuffle_order[idx1] + + for i in shuffle_order: + + if i == 0: + img = add_blur(img, sf=sf) + + elif i == 1: + img = add_blur(img, sf=sf) + + elif i == 2: + a, b = img.shape[1], img.shape[0] + # downsample2 + if random.random() < 0.75: + sf1 = random.uniform(1, 2 * sf) + img = cv2.resize(img, (int(1 / sf1 * img.shape[1]), int(1 / sf1 * img.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + k = fspecial('gaussian', 25, random.uniform(0.1, 0.6 * sf)) + k_shifted = shift_pixel(k, sf) + k_shifted = k_shifted / k_shifted.sum() # blur with shifted kernel + img = ndimage.convolve(img, np.expand_dims(k_shifted, axis=2), mode='mirror') + img = img[0::sf, 0::sf, ...] # nearest downsampling + img = np.clip(img, 0.0, 1.0) + + elif i == 3: + # downsample3 + img = cv2.resize(img, (int(1 / sf * a), int(1 / sf * b)), interpolation=random.choice([1, 2, 3])) + img = np.clip(img, 0.0, 1.0) + + elif i == 4: + # add Gaussian noise + img = add_Gaussian_noise(img, noise_level1=2, noise_level2=8) + + elif i == 5: + # add JPEG noise + if random.random() < jpeg_prob: + img = add_JPEG_noise(img) + + elif i == 6: + # add processed camera sensor noise + if random.random() < isp_prob and isp_model is not None: + with torch.no_grad(): + img, hq = isp_model.forward(img.copy(), hq) + + # add final JPEG compression noise + img = add_JPEG_noise(img) + + # random crop + img, hq = random_crop(img, hq, sf_ori, lq_patchsize) + + return img, hq + + +# todo no isp_model? +def degradation_bsrgan_variant(image, sf=4, isp_model=None, up=False): + """ + This is the degradation model of BSRGAN from the paper + "Designing a Practical Degradation Model for Deep Blind Image Super-Resolution" + ---------- + sf: scale factor + isp_model: camera ISP model + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + image = util.uint2single(image) + isp_prob, jpeg_prob, scale2_prob = 0.25, 0.9, 0.25 + sf_ori = sf + + h1, w1 = image.shape[:2] + image = image.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = image.shape[:2] + + hq = image.copy() + + if sf == 4 and random.random() < scale2_prob: # downsample1 + if np.random.rand() < 0.5: + image = cv2.resize(image, (int(1 / 2 * image.shape[1]), int(1 / 2 * image.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + image = util.imresize_np(image, 1 / 2, True) + image = np.clip(image, 0.0, 1.0) + sf = 2 + + shuffle_order = random.sample(range(7), 7) + idx1, idx2 = shuffle_order.index(2), shuffle_order.index(3) + if idx1 > idx2: # keep downsample3 last + shuffle_order[idx1], shuffle_order[idx2] = shuffle_order[idx2], shuffle_order[idx1] + + for i in shuffle_order: + + if i == 0: + image = add_blur(image, sf=sf) + + # elif i == 1: + # image = add_blur(image, sf=sf) + + if i == 0: + pass + + elif i == 2: + a, b = image.shape[1], image.shape[0] + # downsample2 + if random.random() < 0.8: + sf1 = random.uniform(1, 2 * sf) + image = cv2.resize(image, (int(1 / sf1 * image.shape[1]), int(1 / sf1 * image.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + k = fspecial('gaussian', 25, random.uniform(0.1, 0.6 * sf)) + k_shifted = shift_pixel(k, sf) + k_shifted = k_shifted / k_shifted.sum() # blur with shifted kernel + image = ndimage.convolve(image, np.expand_dims(k_shifted, axis=2), mode='mirror') + image = image[0::sf, 0::sf, ...] # nearest downsampling + + image = np.clip(image, 0.0, 1.0) + + elif i == 3: + # downsample3 + image = cv2.resize(image, (int(1 / sf * a), int(1 / sf * b)), interpolation=random.choice([1, 2, 3])) + image = np.clip(image, 0.0, 1.0) + + elif i == 4: + # add Gaussian noise + image = add_Gaussian_noise(image, noise_level1=1, noise_level2=2) + + elif i == 5: + # add JPEG noise + if random.random() < jpeg_prob: + image = add_JPEG_noise(image) + # + # elif i == 6: + # # add processed camera sensor noise + # if random.random() < isp_prob and isp_model is not None: + # with torch.no_grad(): + # img, hq = isp_model.forward(img.copy(), hq) + + # add final JPEG compression noise + image = add_JPEG_noise(image) + image = util.single2uint(image) + if up: + image = cv2.resize(image, (w1, h1), interpolation=cv2.INTER_CUBIC) # todo: random, as above? want to condition on it then + example = {"image": image} + return example + + + + +if __name__ == '__main__': + print("hey") + img = util.imread_uint('utils/test.png', 3) + img = img[:448, :448] + h = img.shape[0] // 4 + print("resizing to", h) + sf = 4 + deg_fn = partial(degradation_bsrgan_variant, sf=sf) + for i in range(20): + print(i) + img_hq = img + img_lq = deg_fn(img)["image"] + img_hq, img_lq = util.uint2single(img_hq), util.uint2single(img_lq) + print(img_lq) + img_lq_bicubic = albumentations.SmallestMaxSize(max_size=h, interpolation=cv2.INTER_CUBIC)(image=img_hq)["image"] + print(img_lq.shape) + print("bicubic", img_lq_bicubic.shape) + print(img_hq.shape) + lq_nearest = cv2.resize(util.single2uint(img_lq), (int(sf * img_lq.shape[1]), int(sf * img_lq.shape[0])), + interpolation=0) + lq_bicubic_nearest = cv2.resize(util.single2uint(img_lq_bicubic), + (int(sf * img_lq.shape[1]), int(sf * img_lq.shape[0])), + interpolation=0) + img_concat = np.concatenate([lq_bicubic_nearest, lq_nearest, util.single2uint(img_hq)], axis=1) + util.imsave(img_concat, str(i) + '.png') diff --git a/comparison_models/T2IAdapter/ldm/modules/image_degradation/utils/test.png b/comparison_models/T2IAdapter/ldm/modules/image_degradation/utils/test.png new file mode 100644 index 0000000000000000000000000000000000000000..4249b43de0f22707758d13c240268a401642f6e6 GIT binary patch literal 441072 zcmWh!c|6nqAO8$7B{n3LV`kK(93v(n=FF9&gWOr7x#ec=DLIy6$XOP(=y2x<5$5{3 zs+mc-V`-Qp{Pz3DAA5K__ISMae!rgQE7jW4_~_x2hXDXMYHEV90RS#N006atxj3JE zF4jW;AOJAMT(%1vnml1{bTxP?g+DiynQo9o!I6N_%E*vbgZuO|L|mjk7P zI+d=K`&W>AKZIh#!o$NOBX`NMJA*)>jW^|y3Q#;Aq4n&kr^~q#OBBtfvCT(8H#W{9o?KF0OXT!$_mv{Kc%5DquBFg3b@sO7_q?^dupWPXl z54e1i%uFqg$z=NZ`PI>IX={rkWUC^bXM^*czmHU$U0g`pQ7yUKjc+^zLamVJ`t&iC zhXDc@z;14{=4mUN9YVU<+VqJhq?`3MyZ|P+*|}Zzzq~wlF8)L?v){TxVRY055O3&vbrg{ zA{o<(b&h;RX>9lo!|;7Uqfqe5%F4|tQh4Ef-*!PDFMfB=nY|a|vb(S<<#G>;$qqX2 zIe;GfzRJ$OsO?f{*~dj#N(O_&niw&AvlF|Go5O4z(*ri6szhcjMxh^?P*8(MDie??6!N&){dv4x%IdQ+0(SPrz81#ezRI<%+xlBmx>e#T6 zUq7hrDyIByUXJI@r^JW(+`^n|0)2ph+o1p$0O!!J-dAZDp@>Hi=#!fPK;CSaCn+CZSTJ0g!<}JmE`;e5Cp(i=ACVn zB_^PtC~nSu#5ZmKw0!9DQ-eUj&+$%Uey#fQ60p2dp@#vyGPgUkqaQj<4;mnkq!R4< z>0nSsT}EGEo)t@b(3Uh8K9?OV;3idhuuhvts2cgzpt(RGK#DQZZ((n1ihdE6u>jy# zeGPt!1cma2s@ogNa|Qa_;wYcVy~Rb&)3N_T$+2w4TKG<0y~D(KvR1Cp1}_5BlREYl z?>K>@efNTET9Ev0!oIJP54PB})&n6njk2EAfA?iq^ozsjoRPZ$-Fuq%Az8T?dr&4J zSr9Ab0gvr8|hg#PRPNJDi*8$MoBXp|R<~5E&U6`0(0U>wh5lkAQ$IP>&=ijvyI# zQ)1@f@Xt9OJwA9KpS-+0CNMPdr&O>%+(=Ikh6VmLF$Zb2b=Ud@+PW8ZYagl1g}ck3 z_yG9_Kl_|+B1~=6)ls2bXKXK5JNPjBjjA}0S7O*=Ogq(lq#!VmHANHemFTXi_};?Q z;)N4_)pH^5h{?F~`FDrw$jAVPPa|wrY|I)M%-t6D)WJGgm+o7qdAQr_Dz6!G&DYip zJMQo>XoUW=gyV*V{1)TMb6I7)Zh1;=)M}Eu`w|bjoKo;jTG9o9ME-o(6?T!?o<;L0zbKwDO9L*ayGU~X@-c8024k|S-(`b>%6F?fQo489W-9&-+-!H-tS@S~D7)(emDeqNfUd4%5MoCwY7A%P;gVN*-QiV5V%)Acg zGI4HRwacrSgw3LE7!`Sbc)ETAXia=^S2;v z{nYX35JwABdK)s8$}%?*Oa`YWrS2|dv>O5G(-`p$Kmw3?@o$B)G2CDeHHE{!(L)3< z!FTv<4G0e1-Q2&gLa1*hmSg{A9K2=kPsHv`nD#oeX&VnP#IM2iyL~A_jM#%q@TpR( z@YXlW&j`6;jM_Js*SG5%ub)x~6RcY|qwS>tCRBTS-6V#d-F z8*KTw19N4|js9uRam^hLS9k#{{q~(ATa6%<-z~fYysr7aHhES>Ru#T5G}TxQ0H}F{ zE%JaFyOok{n20yL428BqGjsc2*I5EYk<-GLdHh{@M%@gaK)`LI{Q}Pl#M_`>K0yI0 ziI58Vc&&;)^(KTtCO5zYIxqh&cM2;O;=8ZxpLRBJl*(MC7uY{~ciQM&tzur#6{6(x zqkwYA^$@p0G7+&+VlKclXQ|lUGnxev}0M9+aM5dipA{kGc>L?eyROxZFEvh0F4Bx-;UoyoB+(Z!(VuCERE9huC#1EW%2;_IfrHa}9 z1+K*l5KIbIz(iESDV3(UZ?L&+#A>*|baTEpQ=Pvl|It*pvc0WjWu*baf^+*HU;J?O zCm~YwBwwgJk33349ple^+a0Q5%gRQfM4+(QTZFJ+;?(yR3OF5L({PLn7_(G+^%sdI z$QLR`19I~pnUNIrIm*jFc;zmjGrTZW?zqy(2PSPVhUO#p+`$Jq8`ywxnRFH#^l>siWIkV0qf@ zJ_<8ghg;wO_fLE9N{!Y%^AS5U5MF%Lh)Hv1OifXLN9nknw}Qjr9%&Atp}FOp7b{dp zqime?Y-PV??rJL`<=}QW>^E}^#wIX@&1N^(dO8D>w;WG(nt*AzQ_+67pt=lcT`DWv zhU-T(Z9IfROE+0l)cook%7bXT-p<-C2pS*uIknvQv_iSG0?s8v;*Lkn1bm}|Tm=sO zDG)(5?21P_V@++!-RC@<94QobG=s1eb)GV&!YeX+tGuGq*p3~Y_ExcPHc+cb>4iD? zWjQuI5%VRjIrM;Qw-&_3Wnwm>mip(a+hm;b?62wF+Kh5Iyq$U*Tj-YNE7;BzKQx?@ z=gl+-`!G%f!}Ig=RAji~E`Mm$dtPqR+3q`MnV6o)84b*XpA2$A?7tt~Ax=IN17$DWwjh?vbm`D5{&R02=->sPXIk0W^ziEd?F0>N?xkfJvJ ztEtSKI}tIP(eF!mfF&bfo;)8;GOZ5viC(`j^Imm@d#wL5v_JReF+dzY16IWVu43E| zD<96yrDOHpVAZJ5+`EN=K0`*=N4l?CrDY->4W}wU#OR(V^H+lp7Yo_f#R0~;eA8H} zJ~dHuRAT6A_>F7+L8$8!&2^n>=WKgTYfk7D&f8((0q@=Q2 z|BMdL^9|3-q5ea|nL}gHfI@lbWjIE>qr2L}^|}wGyZe}iK=CVYzZ&)hqtgh4Dl3`+ zg3ZIJ-y@{U*g8htVJ4GQML89g3a_Rn4^RB+RD|qI_5+iXmCEKe4}S0fzjih&n{x_4 zFaVx)oBNYnlV3<0=i;J*n3s~@mnGfi#kcl7U3D$bfZ4BRnTcVpAeb=8L@ zafoGeiv=r6t0>Hs(nLx%8R&WKN4un~g8880JHd{oK}u?_vG;bRV>FANDiyV=+8{lh zCWdz-n#OT^e|{uD4!s%KjOaMa{h*r6q1AqM`IW1?EfgPV?^X02tS}S~HLVQRdS*#R zaoF=6`*SbMgDi>mI9laN0$4?{@3${yr81iFO6#?w=Um@xRCt6L(sccZmM?8*yKjCY z2DfWwzPd?gGny*%RwJWhTbUtzdSh{5YT7j6CEF3VTZ==cR*rusg)4ju&gJ4#J_66J zgurZYC&iWE5S3EdcD32@2Nhaht;b3zY-=p~nr^`&~KOwC)?=({PcHe+msfS)ZUv%!1m8g0a64$exY8oud6U=|uFbO}S~V zq#gn_ys@$};Sw7i9XVFwz2t2w3{RVKctz0wG=livL*ECA$_HxjVR(UHlm@pyHy@yW zX+W2U2SZ4K+{^tQ=aex8YBTQ_17^>a&2l6&Zr7ky{r+HNNLeWbBJf?L11ZHK1-+6khzS}Vq-VcLd$q~>8ryhb&aKGV27$KBl z?O{i{{~fY4Pt3OIMWgZQtKVy`8^Yii|4@5rFi};eqDioZFVW*d8x%O0I9NH@h~1Ii zkHo6lhT7Wm5NKBY-Qpf+pl~=!5|4(#1;w!jxt{`nX+8U8t;uF~7j-a)9DXy`Yhi&> z@knoyA1xOJ6L}B=YlBx%MZh1%Nj5|QJuEO?*=vqjm=k_{&5R%FLkSS&4YtI*_%;31 zF2so)UKlvg%r35oU{cieMcpLJ@>h0slJg#A|LW-DTZwkmK;_SGFLb0jFj}LwZG854 zpJ1GVk3&=c>s4HC+~1`6O&eicT4N+VqPDgIoacg8nlp-ra?#2=I9iwZZcEYN{K%qq zS6HiaQDGtQV`T-$VB-zQcNIjmVDK)$bFT6M0iDCa$x#Qxtw6NyrJ_2VK_};*YKtt% zIT=c<)W_BaHzyi_3ryyn#jQ@Zq z%tvh zsfK;^UoMNJ9L8YYdjx(i(bQVwv_+7{K|`P zp5Eg_GaTAwCQ6P^klUIu!ra{P zl_%p$&zd4nwVwwBDAsH!X&@!!H>F?B&deQphClOFrQP^a^erz~DWDKhWl&Q?zX#zf zyA#JJa=C5t)6K0Nj#$3Jl5ZatYOkiRo#0 z`ujDD3`aR|gyqw_?qaAhdS(JmUS5z8kTz^|3YVsmD<^M=P*c|z#|R<0T)V#^I2tIBy-*WzAAkOo=WMdgdZIt<^sH`jsNmWi(ecDV_J zCNct!)RMJVOzIknX4K-!G;2WA-!U$ni4)l56v-sqGE-rlc@#-!J6QG20ChBrZt-aR z?$E;R6E)nQ7PtYjw%g?%;iDpf>kqxWqrK>kRsEwkxo-1ibaSwZs$I;PY;gUP7vgL0 z+aF>!LuFJNE~;2oL>+XHGm3Pc*i1Py_SaqZUq?UBHVQ@Ao@$@$-WuT?VovKnuIac} z$}BIO)5N#}o;yB4Rv$OE9(J;9LQo+qHS_DIF}0;3jq?6}$@KO)-c_toCm@*aTB#DI z5>#!A$wqvR(@$&{ekUSkgy8?WGK6l?`(BKXE@;p=82Zm6G{k2pK4Hu|CLK4|?@XL{N~S{r^rQMsSkIsBja9B zdYzg4^%WO&oeEnP_3U%sKgA!6zsLyIBt7N^q45dAS+aR&Ww>5i=LK>7@qNR0B$@D1 z1)JY^c~r-E;)i|Y@=*x_1TQteud)mifp6$Ysn+ExJWIIG4g8sMWU8OkP^;n221am>)XP->-Ky6SCag zNXjk12eL9jnMod#SK8qS5~)YhkO<*;gj9F^2QK}=PRy0)YLjdT{3K@th)YRR zKg<{8%!v}n+|LkjIRZZ7~uC6X$ z;nw=Posa$4@d~o(-ZzgtI57-Ak zqz~3~qj%QVLR)uFK-tawD1da+&!WFJx{1CzqIOAFmm7w92rk{6O3-R%Fnm_Z8*z>} z9HVY|V?6Tsk8ELBBdukHLjZ6%Ay8puc|k_dNq%TQVBT*>H?PTV|95W{-;#lS1HK$n zg2rt8=av`+Ip(XQwtp6YxqaC5PF_e>S%ttM@8g74zFyWN;B9(?^5%Yfu~()X4TBM- zo$+5CHEN3Uy(zTXjA0wgcH#ARq)}ApvPwL51b$4>cZX zI9i!4qP%E-C6q5OBy(Pr?66GNF17^s@Yl=Q_-|ltUzmaEAi@A_`Td23(Ttc$b5IsO zf;lJbQA&zCtND0IXPn|;D-6e&5!K(HdhC8`H66FE^7`7nNH?*^pPvl(>Rq!|=bA6L zo%i4FSj5O(1p)>Wg#2Ekaa>G;?*~&inynGbs)}K=n1KU8ZzrWj$HC0dhKtAlx;md4 zyO|@0R+k&cPHI&}H!~(2nH_WtkKt(cED(JYpPJnn1q76chQ53L3u|)5++>t)ed&8= z*cmRHD@d6VNZiFEj`$Qf`bGBb+*jK}Dn^W2I>%I5K#ZoRBUV4?c{x(zgr(b|ZP{VH zvm9Tgz_NLR@<=N<4LT?&E4i*vPcqPuv`h@>z;i#$J*A03g~EPfuu^ys8d}1Q#(yW| z2#fJZYk`q!PZPn4oxz#1<=#ewms{i=HlbKaYP2VgWPT1O5zK$i8r;@V%1UvtZcs3uNSMKL;CSd;p zeAsGaH1dE|bRdye(7fvLwU*Lc*EhQzrIUYmLD{cvd490F%+rTK{SF2MugTX_@xQtSwR~v~ust7Tm75Z1Rq^ zYeor$Gf+;_O>eo_9_mC8ukeEc)~$D2j!J@uB8Boavbj|rCYE0q&``f(T3)d}T-VtB zV|iMCVUAL>(o&-Xhyxavw&I7ZRBS}~F}Jyb7A{O`zd*d8vJ%ZH>X<<}Q!~>ugWFLz zGyiO?Ebr24R@Jj0woFL@!E%|eQaoZjq8g#&7t*pUS>bu7;Y(#z>>A%DH`u{_@VWFK z9U=9LU@w{VB1kbOM~h!L3C4wbVrYlKT0Kiz9qCT%q0o^SKh#f zU$`$_gwoT-+uK{H17|RK<%`Vyd0j5o>}&r1dI+H?RXP4Q`z{LdiTiQ@T=_Wvprmw2Z45H6&4q24rIUt8RRa;Io;Cm=|e^f~8Lk?hc2D^Gv;D<^)IosB< zEQ9Z_SZ;qnnd{K=j-NvuJX^V(+_n+4xESBIyfY0ipn42gPIlYWxmKyXtcV***E58Hq%{_<*Ce_{!ZG z^~;pZyUDD{5CpDrsOVr$-`zrEAE3AyH7vx4zV5h8ImeRdAK=8Evw`6ejj%tBzOg$a zMGihWWY%mTClo!!btqYEXRG=(j?%p#X0NPS*f$b{Od>hFsuk2hiO z9v$Y0O%CwWtjK0 zHVAfx!4bkmIx!BGEb(KRnLH=_Ch|!o5U$VFU=u-zuCg#M4Uzh(xkmoQFQV1_0CoYzVSvNA75yQn@oA8SD__2 zLt1C^O&u*H4QhC1Ui8qtG^jxaA)DAeR9D9#_veXS;wo=R7aN*7w8;l^u{#D#NvNP~ z!DYLvAN+!T#M+Cs_Pc}e#c$>S@#tfcxQj9((%fQ~zs&Z><&sW7fleyua>|!8Je@JU zXF6(C%%2#I#8HmYPhIeY0a=LZR})=0$2^zYy0fYzp#-x6i2(ZI%JN3v{IQZ-1LSbx zi1yp(Dz4{kO|R7@>*b6Pla_1q8cC{LDTM;oH3{*D@+|~h!C%B1&CK=u2<6V> zF2?tg!XG4YNa$1NCt=k4%AlFqkDU_VLLe}N4434Eh-D8AYxp1<`f#=Xvd4^)J}X?O z$SR~NvZ?L@_$uApSo`7Hs#Ku_5R5qu|5kVIfg=Yf8rOBY!~>{@K5{|MYrLsx-0f&^ zXYcOpbGX^{F(GN4OOrWTU9k27+tCYQ0%yo0NdJcMp4H8rot@3i@yLVq#gP;tX)~mi zl@(C^h8;Fwp^gbyjnR5G!*X~!qIQl@6}!(Wirw3o7WCZ=&z|_W!baSTJd;|f1 zk^QoBO{-?y^JaOt+Z-pzq{KD!v$T!w%oPN^yzujk_A|?QR?n@2zw^3xh#b48>-fFp z&CN}*2N?xHZAaXQO$;V56d4;EYt>Nv7@U7|z|h{9Iq}Nb&((KfDB@Ik5E6OXUFU_i zT^;V3f9*Z&1D*zxfr>h*>3l&7Wwkk}T<^xH9o`V};+DLzR#boDFR2Lh&i!ghk>vl+ zA_<*N)hD^+1f^6#7(&B9ombQT(a#tcCXraNsUj*0`VdFHu21Ne^f&`ceyNyDEF++!@}JHKEkK%*<+f>{lOqyn zJc*p`e*XW*zZkspch+a9>*~OKxTz`ND&RDs?jHg#lvjzYtl5~NKZ1}sy^a%;lK)%| ztYUHZO;UbbC28NQndbG+<>FsE)3YWi<0==jYvjadH~mBH@N2bwRbHOO>2$$LSv4g= zJkJ+_u1@sZCYE@#<6dp66VuO8(jutNoS&6QjcRhJdi?FgivHg;=iqz1w;!}cwNm`5 z?3$ZY zF}e?pNej{G*BdgXEvK6Z^15yn{{gkNExIgd1^c^YLBz%#B9~1*Qv1{_cBQ!3*+E8~ z1w>NUND^VU#n`+{99MWJlvewQ;NVjk(R>Yym@8nl-~ekg_qmgq0H9zhO=@_A9h|4unbOF}n5RW(?k1s6#P$&)A9&}ft?Z~8bvFz_@wR0>r5fSBb#k*n<2?~=Y2vE6z33do$N!y~btY!|Vd>V9F-z@-z z@oKKnw?v$6Wlxm?vyorELe!=ws@t9kR= zyUf;5_7EE`6}sqhART+y=LUGN#jWUSFt?@}YvF-ZEntgMKdL1NQT%H-nfi4ULZ9qO zzmaUM8a@Xfxd{6~Dx^U!Id>*+YQ`HRJOG@IO|Hc;lWds4OX(Y2 zu)MtVG`;EKB@Z5@-&DmCQNk`)I^iS+k^V*ibk*Y1v)qixstqkISR)KPS1?JLSOua5 zf+nV9OF;w)>y(OFgF6wffIBE!%Q=094}hClEl8qsJtH%_g+X(|LsK(xD8GZ zOpMl}sGGux71`NAFE{#mg}EBg0q#xK6b12*F+)ZLX;pqz zKwGDq&!e=W>>xTjy2?Z}V&{x7^2Pl8eD*?Ai@9wgujH*O1yIl;_{zE@rG^vVFFffI zUwbW&%<1za<>*8(B_#&u$$`j?3(&h_-Qp4c`VARE;jIEb!_QaPYckEbJkm|(vE7EL1mpFU(()@41 zMWq_W<(6{<=!q=4Opg8+BpLA=#c3+~weIhP=RE`u zdKQ)=XA$k-eG6Ly%teq%Nf0q} zY2gCqzs10a2rZ>~Qj*Wbze<>|=8>m%os)=e8hoc*kv`Wk*HQAwaD@gv8=<1-&Tk-At7 zxzv7AFv|Iyx8uSD=-+*gVmNOb64!R{P86>YR6tb98O951r~l5Bl@3{cxv-ijDsvoSP%T)a z{Infv<@O)F@n%Ya%zKt+jN3K;6@Q*P_#~n0nIuip4{Q6=&!Zw42Y+*D%RV6xp8BdP z;LnGG)`P9ZzfmzU;ikwsElw-MnbGpJfM|_u7?b+i*z_G#2p( zzktob@edHGGG%AqiM#3JQX{YgM3nP>8rBtXxt z?@*nqieEyp+Pnb>e8iN^?#5Ny{o_SVF!mTIwEd zVNG%<%O;m|ad{juP6c^3a!965e_vEn zbCVs6jiRCL%47pLR-JA#IYjx{%)}52L}gptcqGhN;odbn$KqLe|_5Y)~JmT z3Z?c!ul69z9lN};nob@u9P6&`n~f*1mlX<*s?RH$js{oJMn+!z`bcLQbaV2!`g9#4 z!fgQgY>+&%%?ba9BDt#-PrLV`AVI7ZoOdPIGxW&dBPC=u<1aD8QTZ~r^~7lUpD_lwElgI3#V7i^hoR5u6SPRfiLqH zehPbPug-hO*6L>9dGC&;`{5Bg`zg$Fxl`hh+tf}-y|2^qf_F!wMkru>%C{day=HDM zWs1%4V1r!+V(%L_)!ihWm`*Inb|Vd);<=vpNjTjki!l;>Qj z!YTfj6tDd}HH_J68;9wA5fA%!s}l4BJb{w(Z4Rhs*qObmd&@Y z|Cy!6YTYh6pp7d$hDtT6Y7}$N@w|5fWCKGbB%&k=ee~deG(QSJ`m=IBQMGxGU;6K| zgk*o)((WXy#4fJN&v5TfB7JgetE0Hw$_)P*x8PGl!cj7}t6% zh$9MCI$Fv&UiDA8|LJfzN-0@RShj0MgV9JZvc=!zCe% z#0a~=6&lPvg*D{hwjSku+wTI7iVK39j()vn$*GBz-wj0h`_xpVd)^EjVAE=RclI}4 zop`ylcb_(~yZAR)>)eQ%$otdWDdTw{F+JG%7rzQ-%z$a}J@Lhz>V!lIO-=V>+{L!6 zlIfBFy{}7+b@z2#_Wx+a{@d?naz;q<#~51eR!G`Z#L=^+q`8s6{dGF|?oG&Dh1p;S zPFbGe?6TbQ`PRnla!%buonn;Ev!t6LxoD{#y-R9=~+SA3Qc{QQa*G-77iYYU^X+}T!-GA`%ItURE`+*4{T-PPqimDr45Cnr)|iO!aNaiB#`lQp z>T{aU)5Hl2S_?08U-Bd?>nvBEtsUwC##!KIFVHQ!Gte^( zK|aWl_TH8KHep~SeL}#SSE~FT4E*aF1!P6EB_<&gfSu%2SMlEeBATmwdbZzD8>r9K zc3k5NZcv(Aofyuo&QlPy(dSyMPqd&A>jop7i|O@Wwcd^|M_ z(165SSlgm_^du{v>z!$z&V~73=Wd(ICkWWem^Kisdn-2fTAcfh)3yXn2ztDNx4|ZE zQ)fo(=DrPQ;YkPy?_Z|B5XW7=F4eMYSIz=l;KvXy_eA5%Jv|^W(o~Q-)KBt6KYJRU zM{ZDLsVXHF1l=q*EiY*DW}Jl1s?OfZMbGjOpnA^BIu=1l&kwb@5KiWUyX15psGq3R zstpOk+i(gbR#wM}or)NVHPuy1s@v-0?8#<61L4;K0Z-NX)%we7?zg%)R(bbQi7d52 zPJXdsLXDprNF32_ZEa;wR4FMb4Js)CQt&N3njNPUwz9D?X4ju>yT3Xj)VYrAv6~y` z@LM$5=I`z`!x$L@ z7`t~R5v`nJ{Zz+PJ#!c8cqpvl)|}^k-C!tRcCUF_v;d&=BD)|fj5fXzQ&ofhI9uSd z^uFx=D?PFM{|%3>C_7;-0qbT{cXc0{bxp-DPb5pNVYkH(D`hw;3E|bYp*!5c$~@m% z&Dj1O<}+L<1wG0U<)RR~(KJ^u8nIEX!z=ti^>4?bBC$TvJxR7uZw1dtg}~%`woO_# zQ?~YlwUUe$Bbt+i|D)Ppy0jmV@%BHD=Tq#H5%4WKBWrw_zAFlPUXB#YX#p|i?l{Lu< zA#!*MYR+c!_uq1))NtDr+8~KUfBC~HzUy<#N*rX2Xwr9IS^P%rRrwO+`5@ zMN*a|*WzuSh?JIZN#WW1Kcs ztD|6(JM&30<=dL=sc4jWhRTlkYcm5VSeU?L^&0y$aDP9gNNI3zd9T)&z3cGllY|V{ zuRjZiP8cE{e#!o;t(4Qp8X2)gzQ{Hgjk)4xiGj`OM6|ZJWGxC5j)=ZKrjlbLv2ed> zipj1J#qI6wHP?vAyN5EPO$JUwF}I(pq~%(YZDan}cYlLoP3K(O|NKyRq$|{tNFv`o z95YKReOzJAuoGUjOmtH`GEgz@VD_La$oVNpkuqBk_BnjDs>*L-*%22~SWcdwZ{68* zc{X_3U#MZag*l?Ox6f|nWRVqYvutPQLg=tLgTa_QXCF`aC-~-o)fMFD$X6Ca4JjE zWzVUKtD0SeHfM@4iy| zaZ}SkVNdCUPTZI#-p=h4$JK{O|Bf9^*%;92TkQ zmH8U1)hpczHoA%)B0=M*7EeBbQ^nc$Ff7Ub z=_k|~0fhNo+QcBo)LY(Yxh}T-N_YPUbAN@gx0Vrm<0;zA$2_jYDs?R48BrXj! zmB|MI8?Tp?TqYfXYmyo-UX;%?oC_CR^Jj9ao_VEg^`gLv+&5Ceev4B!n*ZfF*O9eJ z$%y>7>g8d;#s6!S=XSC274B)~c{q|BZrNE)Uvg#&KDAB9>7_(>s9U3SYgOxiLKSW= zVc-R4u(#U%4u37M8BijRcsfo@u&X#*P~{#smJ>)JLvZuVV%WCJy(@tSVn_U{9w0@~8blJ*eIC6}lPb9h-4y?Zr_@wrlZBKx zWajF%oZ0N4ikg_cotS24dUG}>&Xk{SWZNk753>HP{p`-Hd!B7WoN`pWBvUG?sy#L_ zF%jZqAYh6SykXW*#SWp7k>u=N?cuCMpK{Hvg)-TCNo2aAO<)4<;Y$XFP`T63eFT6u zrC_iQj?Csd2k2XB&~2~MOSR`PLd%61GX+nDj5ocGK2@AaQsvT-pBWSp%Oq%8aLNXz zV>9y^(Q>=a#u#xDw`Pey5&Qy2srvt!=U)sGb_-_IQZ{zhc5^s^=*Wm_^3-O?E8I(q zAWK`LndTKwl1|i4J^i{~ky&_z4)pO7%m{?!m=g|>Om2zyw+)tc;N!yo^0^iMC}&um zhC8&iKlNFyJou|@ka;%a+t?$5^jmqNu<+lv-5{GnP0Pz|#MABy=7*d!$C6|0nV@o@`HxGH<6{~nk- z-$`N|K6t>ZGb$Ue`@_|C`FYIw2nC1wcc6OJncAuSzsnnqtGw$?oZtF->~3A`Mhc_< zN>;E04o}5om8St>_B~lA=EKdtxz}Xz$L3~d zwe_Tdl23HyUC>jV^_PQ`7&|DPxiLh6w#TKc1E~bj(G+R)Exl=H;nS)9YH68$)^D5c zw^wUPJQsCGv|?V8YNx(vsn);$t_LK1S#Mu6QN1E!TT(#y0$hB2d?qJQz8!(|l=}L} z9t*elqWPN7GuXsS2JrwN{F>-yH20H=tXe~yI^a3yA+ETp1RzV z=H=c0I;qFW!ak+a^sf!ag)u!0=T`Mch@2Asq4(lOhAVt_cKfHDWwh5Td%Dd`P7aI3 z+73i31-Y3eetQOS^Or>ma(r{X|Q>1-(Y;1iOMsEtoNGB#obi`aRQbvybt}{)vrPE)vV)Hm zKe+-Dz;kYj$sv#)xAM#Hra|q#?e1QLRX8wldF31fK!s|~(#B=kgIbs=gGe#I{}<3H zE5J1$&N637X4-S(=o>?3Nc5oX-I|q&<^LjsQm#4nJZ`G=E)gv!V8Lg{xDp+N`J3&RmR8vzD;@<( z$1VAxA!#K-^LUe9^y~U8GaZXTs_;djNIz&J^yzuAfIolsGgKm$>vp5p?>BKeuK5)$ z95EUbfo=D@D~q*E98r6inKxA%LaQ4#`U0PsX>3A(5^=bi3+g{_JUit7dVu@5rQDOw zhE;a8jF!H1S(Ch;yTf@75y~cO7h%D$V1_zWG7QHTS7Hb$>&*fTtxpt-1$btgG02n=evMl6&G(Q2ZiT z4fIfPTb6yH@i*kPQT4AM4&46LVnKYoX`&0o7j-6iuz??jMGF&Tul5N*x|GX)x1GFv z!x=iXqkO4Y+bqoup)B{6C-s@I9@pUX)KWbqdYThDA8>Y$H>>uyQbuMKQ~JjVU=T?k zS2}E!7=OM}N2Kv+(w|HL`-@LUID1B%r1i_4&~?Or5yp5O-sI>)(cDyzs$*OPbpBaA zu9Pn`fn{!@ZYp!)z4`#~x8tsubSb($K!eBsoQ#XHaNgWqQ&kz_i3Mx>Q^OTL$3VvN zCMnx9`G3X=2z2C3HAE;M`OVLv8A zL25qjnM*Qr3vK`Em7HjawM5F@xA&wvN2Oged)PTonQ~}-e6Mb0Glpq;TY;QC;7ipc z^(?$S-`+p=sr-K&opn@`|NF*AH*A0i(j$j}G>j5qgtU~TG)gx}hs5X*$$@~*Y&z8P}}^mBM(6!^$FMq-Ti^YIk9?i+vD)I zrB|05(mG^NHw>=E=MO>z4aF&4hf1o>e2NZqvFo;9`&0V{>Tp46C7e)e42f@0aFSX< zDRsIU)J7YWsz(Yb{LNbul|lhAp>DvB`r!Tj@-WLXR4bi}3y)a$0Vwbo&{J0~<+$7c znYQ1LiOWbYJZUU=_AJL+8&Ft*Us8+=8aSlQ26e5S`$&IC&uPd3T*C_sHDk0-7J~q} zDYs1TYoojMzj$@HmcBDOMOe!|ce`lQuWbkR1j`Bi#Z-u@9LGZ8EkRWwYyOD9&``Lg zVCdVN!ue7q4Ook&ClmywIW_PSWEU1{;t(n(7={;LE&;FD)j|4CDXvQfzH3dZkI3H1 zL}meo?mK^suXmLzRqsfTfp13*+DK@aYs{VDl=u~+>eeg0MijNOc6wzbyXj9v|EHvz zyCce{_qXqJFs3G)J7OP8QQrF>vM0;7?hXNiE%Aiq*WNJ)E9>|B4zWuA%%ZXflCyVT zne-pjViA{z_`m})PR@w}bhhwI%vmIL21y*IY6ZeV&nQ9KQPue9HRt&KGeZIv}6$$&)}4FW#S&GISW+ z=a-~Fzk!BGGA%99h9hueR6yPdR|&m8eRO?JJX{%>%yjT@gk&>mS#cDN!_&@%Pw{UM zWpGG~<6GynVY%Wy1(MBI~2g*9N zve2uDAX9hM%BfQxEZ`@rt10X07K9?fQk6d()fE_!;>L4DN<(!Oe}znF)+Mc(Ssvpf zvYDWwGao?DIG#i&=Wc=p1?A(n*{S2`B<0C5C+gjhmB_c``D%U322{_Td^m-ovXNAL zXK5IpH<>Fv`9=TjJ8gHgyh|1}*Ve)A(cXRxWcBMp`_ENf&sl?|s68TkiPzbhMZI3^Jn?kl)@} zswidvZ+!;P>S|4;k(sEB#1owvAUoLlyXk@IuI}ZJAfD&9QYa9AJn9~9nn?l#kgcEH&zVjh?|`H9p27&*b&K*4=76h!ywvucOM8 zwU60!$rd66f?~ruFmR9x;7mt1e(euQTsrjYS`o+nfs^g{iVoymdlLvG0|{O-_YudH zpG&mn!o8)R9BkVc=mAl(keV3-M7r7QpJk)(pYb-`8PmdD%2(W%fE(`EE-?_sGR_=W z0i-xzhzJm9{#m^kThny&>M@ONycQihO%f@AG>a}ZE_*B`*Hmw6dOYz{!g^gZjl=>K zBsl23az@V3^tyF=hKAqebS#c0mVd0nUyLX23;v6lRaJDG+&Vt9Is(wPT7F$NHLa?W zTTjzhI9e?zslvFv$szxK!5?!2o&5`^0fn0tMkwGP(Ot-Qv)S*xa8G{y7eW?E9NM2F zBZS8x%cMykPJiMV9&>tW_L4<}f=EgH1Mg22RX2JmsTLa5SC6TQH;|FmM@YXD$Dbf8 zw zJRwnGb|xkApODgIP*jl#j)(INB_(1Ezn}IX8t;qs4duez%^SJ?%u^&=o)YIqtbH$N z3`PH*(~4ETcX7fxqjC6{%R>#CB@!mJfZg+g%hhF^B=+HvVHOjA)A4g#m0P4C=P=^V zzC8L+*<0pMRp-0&CtaG}_i^^G=$^+>jI=7aaKBrWe%L1N$Fj{erI181RU)u*En!3uvZx_=`517fkA8Wu(i1UXUw5#Kc+d*{xx4vzMZB zDh~ZpTZZBy@<6s@#cw@gti5{wE;J=c`cxXHa9~VqQ0n6(Y>R%vYXU&_EM0^Qp?Lfc z&@?tuV=SuKj^A$X?)=)G?EKH|281?jazbc%Z+kwivQI01-`uo? zELAHiz%fREE;+P|6=^ZSUkxa>Cwsb(c63Yg7}xVk48RLY2mDkezgA20)|_0^78Ek#gr0MQ4z*%2 zs~{n+XA0gLoZaETT+F^vGeEge(2t*7?(Y&)h@en&)yr6u+r~ z0^2hA68%&{tgj!b)p2pYEk2=a-t5ZW15ewUkiX%b6Y5sx#`YOMC=e=+4Wc8q+2UbS zKrlqd#gk9>P(FQe;<8fv8|!u5H~IALzKk^!MfJTfEixh{T>SJ@XBP+yYMX}>73{I7 zKAic~*~(gBS@#8S8{tm~w&NY3sXZrP0~wBQ!YL~NI|bF~pdBKaxEnUUJ~g=OHmGE= z65Bxit|-s!C5Qk`_xp+-pJaU5yLWz{{<6B?U}C2?5hDWE;#mX{3$<0zul z!Sj`W*+|$kZ`s&rlIF|oKr5!^AH+vy_H}c4Fx*^sDJG>-4AES?@x(8?WsO_J0h8FCUGo1<` zK4&-dGfe4n{HQ;Dulx6K~dhb$zHJ(Ed zjErQe3-d#}`N##|yW1t;mdANo({+E5^6zg7`*iXHAwT@Jf@0qJE77(KNiFpGYn9 z%Kc+giry>VVCj^OZ?m` zK7BcGrf8dvK~YtLo9!1sOV|#u{+VH)%dLO2m1Sx2cdL)8^pV}~ru)R~(uyzhX8Smb z#0hB{{ZDDAA!PraTq^w}A9|*(?Xj4?UPnO>3-$`fccW#0;*he#E#?lP+)sv#pMZvc z4xFC){#7gd(|1fvxE@|t2>}VshQC$Y$5Ft6Yo4797n8k|%N>xOu`N}^6}#oGQn*}v zc)K!`^)c-BNbCW5)r`k$qRWl6iGhA{g|{c}>qO&wL+T<#WPBoxto<=8-c5K{TttKl zD&C)?G!2^WLfalYjSxf#|J+E^D=0yw5p9j>na4i@)iY|&WH81tWfWen#2ASw zNq9)ji^JL2g>a~|`Tl?yx?^l`W^jdyP3RNg5_$b^iPi}>1Y=#@n}RH=<|F32gPF9R zEe8#q<8miY@xog6 z|F*A4xQXSwiOF0RDW*i5b$bq*ARONDh%73bfRM?TEJ;C2LR>?n4*NWuyLtfG&z}EJI@Vm z8NO7OW&oi=sTimT^e~9APaU>i-Zue&O|o9U{JXW#b-VQ>Y_;)lZ|~2UkI^|WImVhE z2g_%P4A_x?Nunw+ejTg5F5uWb$vyR70?Kp#*rmft=?^JSo^u+|_X~>(C;ZaWE~8T#JocVWSIm)Z zc@D`$W~65Qg9ZyP7x*qm+~X*oU{*C zHYYg1s`Of2p#iV8XJYMhxL>xf9e>JAh&*fpU_Pt46Eg;X4&u=lu2sJ7N7YXJQ6SjR zN`^8bwi3o}t@4ONx>%`{jyPQgN;q8ZVEbn38&38l_M7i5;J#g=dse9DbxI`OiA63L~qG9!vp zdVSU}BUGP#_GHEUM9zv*+}R=9SYIgFvDb>K{?awGp+zcHBoC({iPZ2Rs7IIs`b89p zIO#_Z<1ocknxh@1ZU!X1O`$P6t18rhhfP(fSoQ-T|KFbMaS5}P=g|~KUrs;|N61kq zxmk(`nXo)XVv^muATeV_MyE8E2e#^(4&n5pB?Ifh(ymLd%%V!$^4Q{~%RTLQyh0|Wt|Lvxn)I4w`@ZhBOS7P!k!AoUU zP3CM7r9bPtc}S6tgWx{ia7x+BMJgQL`|QKtB~{QWEIV5s*VrchaQb@+8BW9Jfx*ju z5#n>wH#jJ>`P1~wh;iiYg~gS!qm)?~F>YESBdkpv`JSQ5}@iRVlz z<-&uza&KylK>BdZY*QrZ*$EYzz3V$V1A?esU_FfzV!*PxWKXAMX zkiuDs;p_5)5qRUH6&Z>M*Rxi4SJvn1>h;&sx$LC8UxWic6K{)XkwNEv%wy)!%BdiB zQVs2v4C>c!XnnUA6Zlp7`?sxZ5#WsEB9LbLnCO$TRWs-D6;9>G?*l!@mJ9T&V5@?% zfZTLWhd9lDLi6OzZq|G7dBzL*3)e|53&AWDknA#9I0uBLy^cInn0+n}ck@uV#70COC>k@;c%GnE3byXf3J}X;M#_+9+ zJy22WCkD*!(zE|1P2aq!3}K=vilp+O_%c_R;x+}D>Rx%y%tihdlCYrw?*lx-aV3|Y zLVl+V-y(1*6+^p2(hM2i&)BNnG&WCzx|2sQ6yBu}vxrH`+;VsHNb*$z`Go^qm8BoWZzxc9=;FVscykpm!q2ZDo%K6WoQhKN-9 z+B_=7qD>wGL`*aI2w}4(0glS#5+bougxYyP6rb}?s20@7XL76dC|HX-V;bdwE79@g zRQxRO?D7EJfWbUHAml8BGndR}oZdnLZ!d0F-a+vZ-p++g7nRGDTJ+Q?sm zaj7*o$8l{QKxzcNJjY&%d|=Y_ON`SO_)ia5K1bjQGQPA@exN;I(tr`g`#zGNX3@CX$`u? zB&SqZIy(!cuMW@3n0Zx|Q<@D9N;Xgu}6JTIL)sGxk&WhT39bH>kJ^!dBn zHp}2f1%Cub=tdz)HaT(0AlDv~$gG)Pt7ek;oZ5K1MoatBZg>@A2pAxqt$bM^9PXoq zOWAU&=sJwG=&H0Fxi8#>EM3C3;9T6)6GyU|ao*7Gy7xj*vnUPRT$w-v3i02>UKs)F z#4?_uAjOd}wQ>qjDr&EgYX$eAzErp>6#p_d5dxjL@N~2(<;IUe`j8JVCJDXmyb@_M8-wqCMkfZAs!yyn&nRG<=fj*vzQjm8EPMcZUjzE z^qv$Dqc3*Ceu=uE3MJv}8+T2l9Cj-2yX?pbd^4x$Dr+iAq{t8OP8mgT*v=jbKgTx& zpE9Lz+2I!!k;aX<6aWqo07shT8Ae{qO0Y7o}qvI%ouX*|rW|Ahi~uK@2IO~mr=&ch|( zrx86`FGQnYPsgba*9p*L-soJO2OL!(kOSJ^*qU#v9hJ(aVY8w4Rpbf6!0V`ENap%> z3wRmgT|ThNgi1(06}fPqvrAhSYv`%)g&Y=3~)YHa^M0OztQ## zJw-hPGJ*#29Z`JP8G3cQ71$B4Ca4_Sc~oOdj=$LGY68$`ArU#tAxjrGtw~B>drC6? zx!%)DJ3TdUpzPDg3B5lp)5&_x**+JtVkAo&^FmvZE|i!C4S{POIcIJN}@68g1y`oQDM;IwiOEe@fV$MZk8 z|Fih6Y3mAkNc!+dN-kZRJ+Jtc=sN2&@>%)s_M?WHQ5Kr>)L%(Wpn4( ztENrUD-pi^6NSQrO%6wxMj%GnX`bEijvbu(ES%=32;a}25tQ5^qT$J+My+TB@@56+ zSn#jWUhw}Sl?DJak{l*wt149;hqh~j^z4H_SG8i*nZPePIuDiNUc}`DrHGI7K>@QQ zLiXBf+qZ)wlCLtrwPU_OUt2R=Z7fYyv7ZwB0oJL}9kX%aidKetC?tSXZ`tk>rYUV# zEdK`*ry8TR#%7Ij`GAql$IfGh&l=i-K3jl5Pc#vy9og`mTjL>LvT0Ii!NhCOUx2J6 z#%w?bQMqa#@XCd|NVC80)&urvjRGx7&WE9vae6tNye9z#VC!4}bsL>t(HIhz^J=@| zOUyWMt6p_mKmo`DAxTlr%Ah&nZn=JuqTrlSgeI=y1Isla%1#A8I1qiB>6+_AI1Z=N zAzX6^x2nYHuGdX|4)x_eLW_5)&5ClIpPlGZz8NvCf$`0!+x#2jFEK?Nv{ue& z`Z1&QtuMb&zPqii?6MHy=OR4M;W!G~Bw&t*H5p#=A4yIDpxly#exADUr7N)9ux!F) z{5kE5HFjh10r>471+%c{em9f7P=h@_qUIlJwIz+ zoX}AKx8c>c#x5*s^5$oXL0REhr?ux=V@WZ_7gv-aphBVitUnvTSkPY{n@J5?8P4zSNWKX5 z?FTTjze*Pvg&w~aszsSg#Rmr?`pbVy&;Hc(^OqD;LfDAC#G}}VXHy}~vU7;_z4Udq zYz#d#N+Qa;rZ4^M;MON#x0tx7BC1a$;!B=6&7WoP^^aGPzT^M<>yoT7YgjS7I?A=7 z(1H?8N6AjZvXl2McuY$<(Y*idrBuaGx+wHnXD8@Ol6lv&cJ{iz#924%C55in#Y;6m z3%8Xs5`(T0))|+Q)P-$jBR8F1aCY@|(Zf0qV-x9Ox^Wl)b!mV=9NhY0JyEDp^}O0C ztL*i2>cp7b^HSA2@~Lm(&EcizE4%`uux~eQ0eE`cM2f8IY;MbKO%~I3_`stYvna>?SvUDA%--)p^$!iSU~;G2n}|e* z_D{sLYIh7|^%3{{-;iG~IyyQ^GJvan&VaN72+5}E(bd@{(~ZS?^UkgaG&3|bTPG*R z*eVm#Lo{cYQXOE*>1^q01+T>5;t2qc2>p9HgwjW% zP1f%YUEhoXer|HmX{ZJO^)yL0uL06iZ53KGU-;w7;<6ETxd7z(Q%lvm7Bh2s5mI^y z-jA!fGC~7-kJZV?h~^ zmIyLn-j;nJ=Fj=aLZb+~C89M0K#?1P4Dl99U2yE5W&Qns&od>S(?l7ZuZ)dl8Ed1q zMxTg2uBvZsYmMH+VX$+c7c{{KM}&PP=p|qiV#DR&pAq1o9n(Db(f?p_<@!2qTv9aX zq2ZR|_$?|*ZDfoF!g9p2v0YOsf6cFLV1umo{)IG&q>`6ntHgYnHxR?83KxzUuU$Fz zV<$kgn+x`mD_|saciTE=zd6xln#ONfS!hlN3EAbNBB={Gd{%R^uCOy2f-UoYTPcjH z93`JYSh0W|8+B5vzgMNKdYWU0!JSdNkf~RX+P*}U%sF&a!PqEXG;s&8Q}N#--!JTQzeZ+)~#wTxnprZ`G3SFAG0KJ5zhlk4$?@1+@D-=k<~(V`gdhS(p?8!YzMoSoHXgZDq~y^}|IS|! zr!bX>4J7=A+!g&>795weZ5dl(U;4^Y?yhv=KMs0+g(F42yY0T=Og86_4WO}oW`Jl@&O%J;*cQ>h7wq^$kr+|VyUf|YjK^~Pne^SF(+r$u(M#BL`z zvEsjg^wpcTHW_DBmgHK~?>%}v1*B)!nkA2rLS4~#kfk$PJQmzqt?I$gwKM&Ah#s(F z_qa>m)vmb5;6P%m@xI2e0aHem*NM;DkdS~tlsC`@5Eu}GNhll7$?={*TBXHUEMWA~ zgm&7EB~3oVte&0;bIYir{AC-Ess7;xEzhgwjdoh3b|4nfgve=CF#XVr2a%Vs(imgs z@fL84XZx(4=DO1eY(@;Dr$h`Z9YoLDgjJ<$R0zbd6|c73jjtXEY{LP9a!+nU^}Y=` z$k?f2;B!EHT+ZU)Y>9T%3!#|WuN@5mMNP6(# z1|SE$AfMJeaaMju>cQ2_$15oj);s#PTFY+ThD^N=IIH=W+uGm`#HJ0~38h2@$pUbAec z$7WiYKS2A}qzlhn9J^|a;`Rw`z8eaxG`W7Di~6d<3u;(1KAT*VWt+ZM7GD!lok)Dq z*}~quE|FKX|NfKxZ$(gDT6~5X2f;(RdV}iKXu)VBWsP}iHmUw_B>pZFJE%%ZA$I!} z1t>lWe?4<9OWHIBa;#tyR~V=6Qx_wx{`f-mnK%{IgS1lOiP*vP7SaWW&Pixe&j77W z?MeKS^#a^dc)5Ko8T&S8(zakwHlen>(8_*c%JAEsZ}9lxhF=q7G0o>}X=o|~Qi16a znJwIP9=G16#q03NynTtVm_k=*J&U~+!*rm4<>0zWOG1K6_ch}?Qh^WO1Y1hjeu{K| zf4b01P&i>i%L27oIL{kbdFkyzqhIy=Dwt(xI;d;KMN!?Ho+OH3I1!cW-9P5*hNLxL z*j{If=ggcBAAy&4kMpXtkP=zBnVRMSB_*2K7fV3~y4Hx={vP-w{NW4X;c==yU3Com zV9?}PY4-{_BU`(sC0>qONO~KLAP@RPPp^%^>2=?Ll{H!2;8l7+MI#~%#n`Fjr|6Kb3Jra)fYC78vYlThPqe8` z1Q-gmByJjbapQwMCvL#o0fY*_zoB09Bh)6^i~v0ENqO=TDd^Q|E3N#U4iIiVi-DWUXldjt6X zZUTe9LJ$aRxFwM5YlvuySd7|W>*hmiihr5F#UImOZVMH~_mZF4A zf>_$U`y2p&LfOp7XO((Mix7742AHJ9d52h=QfcRH{LmF_S9(T}J zcN+^?8_IrFV9C-I%rKNTT$!8Usm%>A&ih5u! znTE_DkRo2t!h2_es4;p|x@SrG@nQ27VKWU&3~F|?JYz@UN;rkDfIff(#wM#lN@VQvrKFGEe~HuldsA1rlX8e5f)?70JtEY+VOWvlkf{ zQSl}J_s7g9N6F$jMbyN$A}7daik6mye&3`T3!(TY|53!cl+B^+@fxt=GW%yu-UEW?8Wt`LUm~B@* z?!hC4n=M4dd)aOqIjPVtEsuzt{`QJ0zS|NpQFzk+&D@io&@F+sa{p%5m+z5&StTYnDq=)NKqz_h^lf`f#~c@{LNi0% zcaAqO69Ror77nEC^nAHE6+Lp<=00LI=9U(dA*&(4g?Hl6cHH{P7%N-h>R%*P-t9;!QHGpcgBCTFCycV=ER!xt8u9+rAk!D5Pl0Qzcxaf_|P9U+KVTHAJ{ z1XDQ{8HMwXD&E-Z0iABQOCxStw3+j!RKeuK2hTVS#SdK*1xnt^Ck=`mUvol%s+uth zh_@ip*ja`}haG=sxR}DZqUXw*-uUn7sI8!ha)*DPgBtAcvdwq)&Hqm3pd-p_WJc`V zqG`qL`1t5z=}va1?-Yeyb`gOlvR~YUin=6@TG>|T*OV9_)M1ZEW&(b=N#3j^n`C^M z%iS?`0vbOy-&|AFI90nDJ7W%PtCrCi^LTGT#Bn}rOhJyBE8jO?$2Ml0c&@BLa<6EqCEO?=npCZ=&AkrvD5}*o3zW)Q zhq+47O*S&H;PtjTqGkSHue*^SD?goX{n>m~Sqv^T`>?#+Q;gWCOWs6doSFddF}Q5O z(`D~J&kD-X5Nd%UaQ$j@gcs7XiF-7aa6c>apK3#tai?qdx;lB!`RhcjpGcETIg0M$ zbv@s~GnI_NR}9%BM69w^AgS|Y5HQpkIB4XlsP_KnZRDlCPA&CNVeTE9z$;CoN<+F= z+?4?l>+yX8+w7ksX+QVc=T7PiE=H6=6G~*?v02%VXnDC(c1J9`-ZV+JQ601R-5idO zj{}`2JJQD^L`ILiL*4JdL8$FM*}U=y zW-dD&-Q z4e~=g`le#RW92sVgk6Dub2(^17USe-1}b**d?}YMd*_A~x7TIa0qQyDvsZ85P5?*h z^6tptDY+bI_J@=61UyBfdQ)r?F?$}e;M*sZt)G$Bb8zN4VKF!=mLxoQb0aw;)><;A zOZ@7A>6|I4KLlh$?qDu6zB!7ub^eNGew7ltfG2&DtfvWcResC#r0`q70O|qWiKX9ygr!`q}JNww{-ocTURC=9Y-|%or4HcpQQh-qA$DfY0clYF39O$M%hG2u;2(*$p_x z$!K9u=b+tM@3`!VN1PNWZ+lW(8%i^!z$bfcybaakh6NaPAQ1zB;HuaCH$vx4L#Y?U`C6(6o^lduu|H?7a*;5?cJY2g3wpcw2hU4H=ODK}hsV zWl8E5x}2@ZjNd1#lo?c$Y}oh*ffF+j1U4}EJS*bdrYZHRUil0E1#v>PRe&2-cHzhB zL2K;Yy?-r?B8~{cAxd{d~?&b zsViw^FxqFrn*-q+&a0rWq|yyBw%T!=X+!?-B_XNu5U=5b)L{zvOTF8mJwAvo=>pS*BZAWa@gX+!IakXVcbG99#mXi% z@b%Z?OQzRlgb>Sv!aYXeU7ek?Ml}%Ejx;kt~lNP3-6=c3sca7|i)iS2_u{4%V*crdc(umC$Oq z`CW9dB$tg6#5FFtYRY-!m68=zwRoVDz6TApsN1rOD175(zYw91nELf?_0xH~M9}o3 zXZ0&?HRO~*+=B;Q>hB(ws=#{3XQx(!Y+u)^I~y8T_lJ-P3kNC__o#o$A6PXTj*P6l z#Ce;;Toe0z;T-0RHK2_Bp9+XjcVz%&Uu|uj2g~y9%L0%2lal#$Icmy~<7J~~ib!Ej z(3@h5HCM?H;^&4>HnY9A=k*dTvOp1_N-P1aiB1tjkRV4=MCB>;0gy(WMCIeG`FbEU z(yB@yZ4yBq^7&2`O_EJLG~W3<)^2&##}a*8UO6h3PQDYu-mU^-onNMHj10uG%r$%` z258%=8Lu;13vw)9y%O96TwHF!b17@f%Wjf+w4W;5+uQjmVwH2)b5CRk!ykXoWr9qJ zCDp{f#7`7X=ZNj^P0D*cG?wMq3g8Gw?F&SqrSx%AZyJE<`}l@_vy{~dT@(Ax!a$x7 z%DJPC{>DdbFI*wIQV`zYgWNvNyhL~{PW+|8&i!bD0lsneQDb2$AO9l zhURaPjS26!@}LVC5-4xZK=ZSNc%#y+Pr4BvFWPz8tku&}73SCjcDmuLC=MR>c~8{n ztSN_ryDMS@Ow5Ff(;AL+D+#w;@Qau5gyNd-=n+7+b2VTkLIpa(@;bb7ym*kD?5t-_ z1Z)qGyO)xEHODt$fAWCn!~WVqOhIHDD&?akrDcKT#LhI{%8JWcSC|^?+~Q%}a%$+m ztge92kO1j+7E6{`v(>d_anCaI9=N?Su17T=^JBv_YIBFxz+I@7E~4_=BT!ZSBk@!p z-_OP}q=vS4m1v%>Lp_g;*y;vJ5I>>*KD9ws%t-BW^bc>Yn%>_1s|%Ja$V%q}8*=&Z z-~7^9&yAaRGSab>AfFFO@qF-yk?v^b6ji+H?SNGm34|SbN`#1yh&5f~KVlI77}R{) zi*d2HzZv!h_Q5%VE0@w6)+^#7QCg7x17U1P!XCBmethIH{$6uGRsavFW-!dg@<;v+ zRS2;seWU)!jBHsohw4l=#NweIakU)>{!QdAQ#9D6TyD9Udp2_T^1+5QA zfiV=)eB$*x-XxOx(pqO&w259kUkAhZ-JVX^R}Ao^-o#1@mtgn>f~SC)72FH3duL|e zcl>?n&~;8LTslrTNTOY)GyxxUYg;i+VX#GJjJ?X<5P zjjab;^Bc>?!yg2(UJ6GQ@`>-r?rfeKJ99;~wcUUft3DXAO(tm-4PY|$s)Rl!51|@( z>a(63FvHh^AR9k&`PgTFXzyqU1_;ZM3`WdY(;pqLxipzoCz<8_{?BRRXo6naVhv(b zfl==W#D(uPpV~7ScADNKAmPvn@5a!lgY=3_5@v=0A#%Veq<=qtnv8;qxe){G2><{f zsBGZc_=*mmtX=`~rH|=k)q5J1;V0R|UJB@zjpItTJIfAjEgc==)w<5(GRN(bZBGpI zy)RbR4lXR#XkNJ5GYyF*M7FL&h9Lmh;``0_w6?^}4UadN{3oxS`OKW30{8}d+X%}m z+s9WPB_GhvRA$qU)Bf{dW#^0dDjkpWN+5=|2ksP|breV-(FOl?@Wu4n+qr676Ff#u z3icE*O;~^HS*2K?TRSFQUe3w3A5lR{O4brKLf^Nw*x-V=u|OJpA({MO(j9ah2kJ)O zH%L?hyha%=qE17UXM}_!NrD5Rb;66fGe()kB&mk`%*xtD4*`|Li$U%)b}0qNWl}tm zlh#riIy&^+&3gXQ`HKHq$4%baYS`sPHCbol6}D{Q>FwXs8SJzCt}yJ;#f4iJt6pMW zCsvrZ`$~k>(sEn&y;6SJ=rdh7<*g%BJEkrhYN zb?`u0WxYFMBF_7!E`b?rMr_;V*8S;rT|NDudEdHyY40QUUQ}7xlaFNqzx6&U1_uT^ zE$bmK;%CyE-jx^}w^NDj?46(VCN;HLkWYJPhz{a`uv#ZQ(d$6-Y9{@=OPnvleRFS~prKD1p4U$wk`4d_N@YNaYbhx%OJ1$(dtw`Wc@{gf2 z;=?f+^G;{-QV(rvC8Nrt!2ES38GKOTXuuw4v;-ua$~^1O=|LHKZJi11**Rb~5LPeePpm34zw|ujDP9*SP+4Tocs2$EB#p}yKBqzPhK1=U#d3&F@EXSg{Bk; z_@BQZ0NJQt6h@t0YzRQXE%d!tUOA=kw`)`#44HHlkFDZLb$5)S^U6J(OU9rs1#~fn zgb!1ZX8C_yE{{WYTYsV2P^w{uZ*oN6L%41_C8uik36DE|?{>(!j{!*S$<3{w?I{&_ z3Pb?zA(Ojz#^26!K4(zRapBC!L=FHBJqo|7nqYmc-<40sEn=UDCLa}?XrSO!j zv}g@M`?&P&aR;@!DoipUvjlp3D@Ex~Y>MGo#h;GfSrDI&_r2qgW}z&0+Iu&V=DmW& zerjQ$xY1hRdSK;%Q1HrqsH%Z&>7?uOWP(_nISzjNoVXcHoF;4VT$s2iee~+B>_==nrkAKWe9>Sn4etHnz>bW#Wmh)46kK zz)aC?_`Q{5w4I9W?)^+}Q&u^VCO&WR+te2N<8a2WDFOEV+|`buDtbn20zL%x%M*Zf z2E6@yvY|vOyc67lg4BA-pUn#8ox9}UX{xwf`>hXCuUsC>~$9fcxuNxE9t%8`UXy_c#@wis2WX;CQ>^OW< z_;e<~n%8=WK&SWdOE8_$Oue#+1W(n*e~|xPzMa;t+mCm_5#LbHi#l)F=$+tEd~kbx zh{@wACQME8-()K6PNysb^?y0A>c=5%sEuso<}-J;f3x^#K4z7MEFCxJTmo0Bs#st_ zkCaU%e$;8G`4^wUF6aYhcG(myLMrW5z>vYH&KPr26?+48qPwqlwP^H^V6hu#?)UdY z|0bW_>JEhbyK@gczh5~F&0{JwP*jbO_AU7prz1Fc7y54@>@;s@CVS`4GQMe!j%st; z4bQ({A3K?zg#A5z$VQX|B0wT4aIKW`&8)wFo+ADGg@oT%8qdnL{=W;Oz03_djg>TC zwTH^Fe5B2!Xj+3=xGC7Ic5!zWe~;eY64?KGP8Dn~jb^R(hm z)mJWGBjIHqL!dm7QJXYI*{WUs}oT zxa5@`I>=1e!df&c_P>P%y6g|4)+e8ORM562!}edUn{sr*=$(~ZH9R!* z=%(O5Or1(JsqydpsjabRD#2ZaE)KovzPK-Y8m6}8<-f9~_^jwOe}1KaTS@Ry$lv$$D-GPEBX-mkjzp ziq1Qp>i>`8myjgxwMoX6zS$|6H(O-8_O(Kk9T%6(WZcZi%te$vQo8mC*<8uqWL%NN zm7D#0|L&hXdPw))&wHHLInTq^=ghI=7y92=RC=8+XJhks9ex&@XN6Aqz!1x!cZVWb zJ&*jH6>6%Ftk%T+`Kea&E-2GJ@9oq!yiROkJo{F-Xtw13#(y64SGJcr|?;AKdIwRq3U^WH=1ibv8nheb1f z4Owc-<>;^TKA~4;x6yvyJ49N=l~yLlYIp;hH~wjlP&x_yA9M1aKjwpPA{46ve1UX zsOR0KXSdm2x|U}QOb1Ey&y`(%#PayEwRA&LOO`3e$bnma>g`;KjyI|owFWEr@U`6) z_)B%j+cFfUE~4)*1G3NH)GbXd zvz{1fQKkawVv2}ZX;3HtTobaOPe$CQrJJ7$ttzRugDf}Cb8~~!@d*nWbQZOR)z7+1 zCnY5Ta0k%8#v7LBo506FmK$c9drcID*MWQZwkNK8^l-Je3o2Inl}qB?Ud)old%Ol@ z2`3XbJ@jpHZeig^LP;v}tj>Tmd4Uo(sp7h;`7ga`*DtE|52EU%aZN`ROE5+;{hqW&^`x z?8dhU0kQX!p@Bw^YQCst3vj0YVu-VHWR)%!q3G?%z-3Xls9kiwde+U4bv3?k#!rO2 z2LmBp{`aXqm1qw-6W8*)uT|L{*qNcv#>FE!f??E^Z#PwT7Uxa?Lho$bYr#vVH0_zJ zE{L7(?wl{j*eNQK=YckR^cRdtFgDywg{!De)cab|$f0BbUdJEOdKn{G@2ZkisYKgH z)_hOadU${HEW9fr+@UcgK4*&)rx7Czi&<;G%&pB%;1i^ay;jdqD7qqZd&#e+-j>O2 z?oG(Z5hK**&Gm7=*Djq0t|j*B;ZevVRv#*=yWM}dq8~E9$#S0Y%S0mACf-nvAx$E) z9CbaTS}QSB5Y4Y;l@r~p6t0y$qmuuY7G%+4kY3_|g%z_s1ohlkMfLGUbBd$6PvyBb3kp& z9soYN*J57Zei&J?E>C=uQ=$hC$Bw7hjsxweY_2%b8;AX-Ji_6CT|PLFj(jrnuXRU9 zESR?2`b}7#;7qE^&+V_%Vmv2x| z&Eigv_y6(N`o%RuzY&42QF#)?K*B=u;kV(@M<w(`ZYr?t6;wmRGRins{60mBwK(Y) z@L$M7klT%^jghqIfimH_FUYp$xweMm^0t$0uP~DRMo8b`+U{E0VO`k2PTo-N;-fzY zol1wZas}fapf!}5N*NU2ZrBDgEUC!%>zUi5l zCwPlIwLM~1M&904cdZnA4r-QcOmUFvDFeP4mcqtc*S1@6YP?tw7XVmi$$VW9AwH>+{E@aWG}2j2xw=Qlbxd*B!m#wR1t z>eQdNZR^J;W)Mk0i9*z&XeIqy$YKE!3B?1eEh`iCW-h&H*ErQb6o6PpAdui~77v#g zV>*BO-o`7_gBx&XXJ>XsMuvo)qJkzPqt}t=)bCp0fHEP;UPg<9=0JhoE{@}>okoUB zIr2msC3+j}&RZp}rGB~Vqr3lnp5dL+T40X&X+^jP$fMywNx=xHdMb1N*fhh z5DL5<-+DY(f~%)TRNq|UF2Rbge-f94J6LAk<(q2Q$oY?zh=9FWL1PnNX-UeG|E#Zn zI6tb}S!{d2P()fA?dbszCZkfwGm~)g4)56}x$St!Yw=2UE1s_7$;}Z36G0S>kHzFSG@Z^J`+bo;&8&qLKYiz-(8 zGdl5d%8fS8-{(O_Z?M{KaO+r7`-Cp`?Ah%&*K&L+<=dwD?uPtvRocW7ymQ~x^gLn& zCJ`qfqF-$hBMWPY&mbNCdeNZb=equsc3tVANM_)hJd4agzo~GPCTtgv|D1aq&E{EW zWs1N3ka@}!?p(b9wg}y%zyJQ-?8q4C!#%aL%{>Ti;`FBp0d4kN;jcPl>d5#pq>mG! zp%MD(=0D{T8d0`nWQNgTqj}IiN(7!YG$0Q{J*zmJbJVuy`LAa6len!ZS|}k4k&cWW z>OPz!m+mwL=K26b`@lCZ9|G9WoJHJw?QO3V;Lw$|-C_ogIsfh43l|+>g**GSTZ?tH zv(RE64m2andg&o}{BbH5u)=wBImWlg^z;oaQR*`oH;5V97};{{Qu@|5qsJIBXEqBq0opJ@Fq&RJ{
@|jq>bjDN8Lpqi zU{?rPAEd$K(>XMhQ1*FdU2gQv8-Do8TCiMRDHS-ILi$q*;AcGNEWrP6n+D+kym20;_LDkVXnK$$_+fJb_+!=`a zFUZT=vvq_h(AV>GcUS1^QjW}Y(XC0kL3c+Ag-PLeclFdKScR1P4v$LFgiSp$J(X)C zVfq)u!iVr~*4immRF_`#czZiCS>FuY!WQYMg{*0Am^XXh3)_&NDt(ZhaLYNCUF|hn zH^RD8IAeF?nbLrvlbu!39qVBkx52hOCiB~HVUo{TI- zei=w~=jAe{P3dKXurC}QvrsZcxb&(+O2%mj0NL;-fG6ze&@l`#zpy|%O&fFHNI;Vo zrJb`kr;coUsW>wV{f3MqaQAsMX{k@By(VE3O)dAAe;f6clI+0 zR8Z%6dIFo(4o0RarVcZkv-M1M!_~eDsiWqrNE4rlE;oHYUbej^b^2#uG|3=FBFVrB zVRY@Dw2D)uFwZoM>84KBh=yNu3mue_`PMrUpZ@0u@4Bh)cpQ0dU?^V^FPmSsRvX}! zoZGp2fB5@-h^=XFNx73!m9~T_{=v~^-KV!>I>s-ynl7-Kzux$(T9YFp7gMHQ&q-qu zTznJstkfmE=@JG4&vamqXyp*qlfy6SV_X+pA&Y)Cv>zqQwXmf+eHB(bym?@nFEzAq zymW!d(!#Uy2F7Kstn3Kd*I-soxo`7<4$pQyk|vZ(({m`DuGXNjHOl?uQ`nTZvyOnN ziZA~^@(ws^yW{DG$gxp|Yf(cq35{PTVl}AZu$Zbe(3uF*1;EOA>lZobI6K|j9cd-D`U=`T zkV*8BORB7u!C)8}caA&*?r~c=LVQ<^sj9YpvaG~xGEgEUsXCNTpE_{W@Xf&|Cr~Ps zG4CURkU9XbuwwVYo3SypUzQ=xoo;Uf6{mVS6oV8rKJ@ShAV114nqHDlnjM4MRD}X@v4?z zE`BR{aR;eQwV}305D+g{xcZ5N)2NpmCb{dMd+aKhzg7|`NH{Dgh!yfXK3$L+fc!Zm zJ=U4sC9EMc4-eM;n`Xz&+}sl9qzv5XXG3;^SpSGyeF4V1$ll7A7GG{ppiqv^6Z#3v zP4n(U^`8Pk+qwWSpD|J_q* zh=c=NqQ?BKkUxN1{QBj)n4xej{1{GzPoAju2eQijjQ7OO9{Y7yϐ}ewmE<1P{om13ZIR;da-v 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

9YZ4 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 z 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

C;kIRhg~gM!vAOC`Sn4{$JCXdp2~=e39X=Y38A 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 W{vqoXvb{`%VS%WMs+AypQUKtQ7_{2@A^ytA-f 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%0A~?y&eN-{wPneIq!@VTU2wGtzL+} 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 bzm8AbuVoE9qaMlyfL|Cr6WvPfd(r;^ifYg6 literal 0 HcmV?d00001 diff --git a/comparison_models/T2IAdapter/ldm/modules/image_degradation/utils_image.py b/comparison_models/T2IAdapter/ldm/modules/image_degradation/utils_image.py new file mode 100644 index 0000000..0175f15 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/modules/image_degradation/utils_image.py @@ -0,0 +1,916 @@ +import os +import math +import random +import numpy as np +import torch +import cv2 +from torchvision.utils import make_grid +from datetime import datetime +#import matplotlib.pyplot as plt # TODO: check with Dominik, also bsrgan.py vs bsrgan_light.py + + +os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" + + +''' +# -------------------------------------------- +# Kai Zhang (github: https://github.com/cszn) +# 03/Mar/2019 +# -------------------------------------------- +# https://github.com/twhui/SRGAN-pyTorch +# https://github.com/xinntao/BasicSR +# -------------------------------------------- +''' + + +IMG_EXTENSIONS = ['.jpg', '.JPG', '.jpeg', '.JPEG', '.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP', '.tif'] + + +def is_image_file(filename): + return any(filename.endswith(extension) for extension in IMG_EXTENSIONS) + + +def get_timestamp(): + return datetime.now().strftime('%y%m%d-%H%M%S') + + +def imshow(x, title=None, cbar=False, figsize=None): + plt.figure(figsize=figsize) + plt.imshow(np.squeeze(x), interpolation='nearest', cmap='gray') + if title: + plt.title(title) + if cbar: + plt.colorbar() + plt.show() + + +def surf(Z, cmap='rainbow', figsize=None): + plt.figure(figsize=figsize) + ax3 = plt.axes(projection='3d') + + w, h = Z.shape[:2] + xx = np.arange(0,w,1) + yy = np.arange(0,h,1) + X, Y = np.meshgrid(xx, yy) + ax3.plot_surface(X,Y,Z,cmap=cmap) + #ax3.contour(X,Y,Z, zdim='z',offset=-2,cmap=cmap) + plt.show() + + +''' +# -------------------------------------------- +# get image pathes +# -------------------------------------------- +''' + + +def get_image_paths(dataroot): + paths = None # return None if dataroot is None + if dataroot is not None: + paths = sorted(_get_paths_from_images(dataroot)) + return paths + + +def _get_paths_from_images(path): + assert os.path.isdir(path), '{:s} is not a valid directory'.format(path) + images = [] + for dirpath, _, fnames in sorted(os.walk(path)): + for fname in sorted(fnames): + if is_image_file(fname): + img_path = os.path.join(dirpath, fname) + images.append(img_path) + assert images, '{:s} has no valid image file'.format(path) + return images + + +''' +# -------------------------------------------- +# split large images into small images +# -------------------------------------------- +''' + + +def patches_from_image(img, p_size=512, p_overlap=64, p_max=800): + w, h = img.shape[:2] + patches = [] + if w > p_max and h > p_max: + w1 = list(np.arange(0, w-p_size, p_size-p_overlap, dtype=np.int)) + h1 = list(np.arange(0, h-p_size, p_size-p_overlap, dtype=np.int)) + w1.append(w-p_size) + h1.append(h-p_size) +# print(w1) +# print(h1) + for i in w1: + for j in h1: + patches.append(img[i:i+p_size, j:j+p_size,:]) + else: + patches.append(img) + + return patches + + +def imssave(imgs, img_path): + """ + imgs: list, N images of size WxHxC + """ + img_name, ext = os.path.splitext(os.path.basename(img_path)) + + for i, img in enumerate(imgs): + if img.ndim == 3: + img = img[:, :, [2, 1, 0]] + new_path = os.path.join(os.path.dirname(img_path), img_name+str('_s{:04d}'.format(i))+'.png') + cv2.imwrite(new_path, img) + + +def split_imageset(original_dataroot, taget_dataroot, n_channels=3, p_size=800, p_overlap=96, p_max=1000): + """ + split the large images from original_dataroot into small overlapped images with size (p_size)x(p_size), + and save them into taget_dataroot; only the images with larger size than (p_max)x(p_max) + will be splitted. + Args: + original_dataroot: + taget_dataroot: + p_size: size of small images + p_overlap: patch size in training is a good choice + p_max: images with smaller size than (p_max)x(p_max) keep unchanged. + """ + paths = get_image_paths(original_dataroot) + for img_path in paths: + # img_name, ext = os.path.splitext(os.path.basename(img_path)) + img = imread_uint(img_path, n_channels=n_channels) + patches = patches_from_image(img, p_size, p_overlap, p_max) + imssave(patches, os.path.join(taget_dataroot,os.path.basename(img_path))) + #if original_dataroot == taget_dataroot: + #del img_path + +''' +# -------------------------------------------- +# makedir +# -------------------------------------------- +''' + + +def mkdir(path): + if not os.path.exists(path): + os.makedirs(path) + + +def mkdirs(paths): + if isinstance(paths, str): + mkdir(paths) + else: + for path in paths: + mkdir(path) + + +def mkdir_and_rename(path): + if os.path.exists(path): + new_name = path + '_archived_' + get_timestamp() + print('Path already exists. Rename it to [{:s}]'.format(new_name)) + os.rename(path, new_name) + os.makedirs(path) + + +''' +# -------------------------------------------- +# read image from path +# opencv is fast, but read BGR numpy image +# -------------------------------------------- +''' + + +# -------------------------------------------- +# get uint8 image of size HxWxn_channles (RGB) +# -------------------------------------------- +def imread_uint(path, n_channels=3): + # input: path + # output: HxWx3(RGB or GGG), or HxWx1 (G) + if n_channels == 1: + img = cv2.imread(path, 0) # cv2.IMREAD_GRAYSCALE + img = np.expand_dims(img, axis=2) # HxWx1 + elif n_channels == 3: + img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # BGR or G + if img.ndim == 2: + img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) # GGG + else: + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # RGB + return img + + +# -------------------------------------------- +# matlab's imwrite +# -------------------------------------------- +def imsave(img, img_path): + img = np.squeeze(img) + if img.ndim == 3: + img = img[:, :, [2, 1, 0]] + cv2.imwrite(img_path, img) + +def imwrite(img, img_path): + img = np.squeeze(img) + if img.ndim == 3: + img = img[:, :, [2, 1, 0]] + cv2.imwrite(img_path, img) + + + +# -------------------------------------------- +# get single image of size HxWxn_channles (BGR) +# -------------------------------------------- +def read_img(path): + # read image by cv2 + # return: Numpy float32, HWC, BGR, [0,1] + img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_GRAYSCALE + img = img.astype(np.float32) / 255. + if img.ndim == 2: + img = np.expand_dims(img, axis=2) + # some images have 4 channels + if img.shape[2] > 3: + img = img[:, :, :3] + return img + + +''' +# -------------------------------------------- +# image format conversion +# -------------------------------------------- +# numpy(single) <---> numpy(unit) +# numpy(single) <---> tensor +# numpy(unit) <---> tensor +# -------------------------------------------- +''' + + +# -------------------------------------------- +# numpy(single) [0, 1] <---> numpy(unit) +# -------------------------------------------- + + +def uint2single(img): + + return np.float32(img/255.) + + +def single2uint(img): + + return np.uint8((img.clip(0, 1)*255.).round()) + + +def uint162single(img): + + return np.float32(img/65535.) + + +def single2uint16(img): + + return np.uint16((img.clip(0, 1)*65535.).round()) + + +# -------------------------------------------- +# numpy(unit) (HxWxC or HxW) <---> tensor +# -------------------------------------------- + + +# convert uint to 4-dimensional torch tensor +def uint2tensor4(img): + if img.ndim == 2: + img = np.expand_dims(img, axis=2) + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().div(255.).unsqueeze(0) + + +# convert uint to 3-dimensional torch tensor +def uint2tensor3(img): + if img.ndim == 2: + img = np.expand_dims(img, axis=2) + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().div(255.) + + +# convert 2/3/4-dimensional torch tensor to uint +def tensor2uint(img): + img = img.data.squeeze().float().clamp_(0, 1).cpu().numpy() + if img.ndim == 3: + img = np.transpose(img, (1, 2, 0)) + return np.uint8((img*255.0).round()) + + +# -------------------------------------------- +# numpy(single) (HxWxC) <---> tensor +# -------------------------------------------- + + +# convert single (HxWxC) to 3-dimensional torch tensor +def single2tensor3(img): + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float() + + +# convert single (HxWxC) to 4-dimensional torch tensor +def single2tensor4(img): + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float().unsqueeze(0) + + +# convert torch tensor to single +def tensor2single(img): + img = img.data.squeeze().float().cpu().numpy() + if img.ndim == 3: + img = np.transpose(img, (1, 2, 0)) + + return img + +# convert torch tensor to single +def tensor2single3(img): + img = img.data.squeeze().float().cpu().numpy() + if img.ndim == 3: + img = np.transpose(img, (1, 2, 0)) + elif img.ndim == 2: + img = np.expand_dims(img, axis=2) + return img + + +def single2tensor5(img): + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1, 3).float().unsqueeze(0) + + +def single32tensor5(img): + return torch.from_numpy(np.ascontiguousarray(img)).float().unsqueeze(0).unsqueeze(0) + + +def single42tensor4(img): + return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1, 3).float() + + +# from skimage.io import imread, imsave +def tensor2img(tensor, out_type=np.uint8, min_max=(0, 1)): + ''' + Converts a torch Tensor into an image Numpy array of BGR channel order + Input: 4D(B,(3/1),H,W), 3D(C,H,W), or 2D(H,W), any range, RGB channel order + Output: 3D(H,W,C) or 2D(H,W), [0,255], np.uint8 (default) + ''' + tensor = tensor.squeeze().float().cpu().clamp_(*min_max) # squeeze first, then clamp + tensor = (tensor - min_max[0]) / (min_max[1] - min_max[0]) # to range [0,1] + n_dim = tensor.dim() + if n_dim == 4: + n_img = len(tensor) + img_np = make_grid(tensor, nrow=int(math.sqrt(n_img)), normalize=False).numpy() + img_np = np.transpose(img_np[[2, 1, 0], :, :], (1, 2, 0)) # HWC, BGR + elif n_dim == 3: + img_np = tensor.numpy() + img_np = np.transpose(img_np[[2, 1, 0], :, :], (1, 2, 0)) # HWC, BGR + elif n_dim == 2: + img_np = tensor.numpy() + else: + raise TypeError( + 'Only support 4D, 3D and 2D tensor. But received with dimension: {:d}'.format(n_dim)) + if out_type == np.uint8: + img_np = (img_np * 255.0).round() + # Important. Unlike matlab, numpy.unit8() WILL NOT round by default. + return img_np.astype(out_type) + + +''' +# -------------------------------------------- +# Augmentation, flipe and/or rotate +# -------------------------------------------- +# The following two are enough. +# (1) augmet_img: numpy image of WxHxC or WxH +# (2) augment_img_tensor4: tensor image 1xCxWxH +# -------------------------------------------- +''' + + +def augment_img(img, mode=0): + '''Kai Zhang (github: https://github.com/cszn) + ''' + if mode == 0: + return img + elif mode == 1: + return np.flipud(np.rot90(img)) + elif mode == 2: + return np.flipud(img) + elif mode == 3: + return np.rot90(img, k=3) + elif mode == 4: + return np.flipud(np.rot90(img, k=2)) + elif mode == 5: + return np.rot90(img) + elif mode == 6: + return np.rot90(img, k=2) + elif mode == 7: + return np.flipud(np.rot90(img, k=3)) + + +def augment_img_tensor4(img, mode=0): + '''Kai Zhang (github: https://github.com/cszn) + ''' + if mode == 0: + return img + elif mode == 1: + return img.rot90(1, [2, 3]).flip([2]) + elif mode == 2: + return img.flip([2]) + elif mode == 3: + return img.rot90(3, [2, 3]) + elif mode == 4: + return img.rot90(2, [2, 3]).flip([2]) + elif mode == 5: + return img.rot90(1, [2, 3]) + elif mode == 6: + return img.rot90(2, [2, 3]) + elif mode == 7: + return img.rot90(3, [2, 3]).flip([2]) + + +def augment_img_tensor(img, mode=0): + '''Kai Zhang (github: https://github.com/cszn) + ''' + img_size = img.size() + img_np = img.data.cpu().numpy() + if len(img_size) == 3: + img_np = np.transpose(img_np, (1, 2, 0)) + elif len(img_size) == 4: + img_np = np.transpose(img_np, (2, 3, 1, 0)) + img_np = augment_img(img_np, mode=mode) + img_tensor = torch.from_numpy(np.ascontiguousarray(img_np)) + if len(img_size) == 3: + img_tensor = img_tensor.permute(2, 0, 1) + elif len(img_size) == 4: + img_tensor = img_tensor.permute(3, 2, 0, 1) + + return img_tensor.type_as(img) + + +def augment_img_np3(img, mode=0): + if mode == 0: + return img + elif mode == 1: + return img.transpose(1, 0, 2) + elif mode == 2: + return img[::-1, :, :] + elif mode == 3: + img = img[::-1, :, :] + img = img.transpose(1, 0, 2) + return img + elif mode == 4: + return img[:, ::-1, :] + elif mode == 5: + img = img[:, ::-1, :] + img = img.transpose(1, 0, 2) + return img + elif mode == 6: + img = img[:, ::-1, :] + img = img[::-1, :, :] + return img + elif mode == 7: + img = img[:, ::-1, :] + img = img[::-1, :, :] + img = img.transpose(1, 0, 2) + return img + + +def augment_imgs(img_list, hflip=True, rot=True): + # horizontal flip OR rotate + hflip = hflip and random.random() < 0.5 + vflip = rot and random.random() < 0.5 + rot90 = rot and random.random() < 0.5 + + def _augment(img): + if hflip: + img = img[:, ::-1, :] + if vflip: + img = img[::-1, :, :] + if rot90: + img = img.transpose(1, 0, 2) + return img + + return [_augment(img) for img in img_list] + + +''' +# -------------------------------------------- +# modcrop and shave +# -------------------------------------------- +''' + + +def modcrop(img_in, scale): + # img_in: Numpy, HWC or HW + img = np.copy(img_in) + if img.ndim == 2: + H, W = img.shape + H_r, W_r = H % scale, W % scale + img = img[:H - H_r, :W - W_r] + elif img.ndim == 3: + H, W, C = img.shape + H_r, W_r = H % scale, W % scale + img = img[:H - H_r, :W - W_r, :] + else: + raise ValueError('Wrong img ndim: [{:d}].'.format(img.ndim)) + return img + + +def shave(img_in, border=0): + # img_in: Numpy, HWC or HW + img = np.copy(img_in) + h, w = img.shape[:2] + img = img[border:h-border, border:w-border] + return img + + +''' +# -------------------------------------------- +# image processing process on numpy image +# channel_convert(in_c, tar_type, img_list): +# rgb2ycbcr(img, only_y=True): +# bgr2ycbcr(img, only_y=True): +# ycbcr2rgb(img): +# -------------------------------------------- +''' + + +def rgb2ycbcr(img, only_y=True): + '''same as matlab rgb2ycbcr + only_y: only return Y channel + Input: + uint8, [0, 255] + float, [0, 1] + ''' + in_img_type = img.dtype + img.astype(np.float32) + if in_img_type != np.uint8: + img *= 255. + # convert + if only_y: + rlt = np.dot(img, [65.481, 128.553, 24.966]) / 255.0 + 16.0 + else: + rlt = np.matmul(img, [[65.481, -37.797, 112.0], [128.553, -74.203, -93.786], + [24.966, 112.0, -18.214]]) / 255.0 + [16, 128, 128] + if in_img_type == np.uint8: + rlt = rlt.round() + else: + rlt /= 255. + return rlt.astype(in_img_type) + + +def ycbcr2rgb(img): + '''same as matlab ycbcr2rgb + Input: + uint8, [0, 255] + float, [0, 1] + ''' + in_img_type = img.dtype + img.astype(np.float32) + if in_img_type != np.uint8: + img *= 255. + # convert + rlt = np.matmul(img, [[0.00456621, 0.00456621, 0.00456621], [0, -0.00153632, 0.00791071], + [0.00625893, -0.00318811, 0]]) * 255.0 + [-222.921, 135.576, -276.836] + if in_img_type == np.uint8: + rlt = rlt.round() + else: + rlt /= 255. + return rlt.astype(in_img_type) + + +def bgr2ycbcr(img, only_y=True): + '''bgr version of rgb2ycbcr + only_y: only return Y channel + Input: + uint8, [0, 255] + float, [0, 1] + ''' + in_img_type = img.dtype + img.astype(np.float32) + if in_img_type != np.uint8: + img *= 255. + # convert + if only_y: + rlt = np.dot(img, [24.966, 128.553, 65.481]) / 255.0 + 16.0 + else: + rlt = np.matmul(img, [[24.966, 112.0, -18.214], [128.553, -74.203, -93.786], + [65.481, -37.797, 112.0]]) / 255.0 + [16, 128, 128] + if in_img_type == np.uint8: + rlt = rlt.round() + else: + rlt /= 255. + return rlt.astype(in_img_type) + + +def channel_convert(in_c, tar_type, img_list): + # conversion among BGR, gray and y + if in_c == 3 and tar_type == 'gray': # BGR to gray + gray_list = [cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) for img in img_list] + return [np.expand_dims(img, axis=2) for img in gray_list] + elif in_c == 3 and tar_type == 'y': # BGR to y + y_list = [bgr2ycbcr(img, only_y=True) for img in img_list] + return [np.expand_dims(img, axis=2) for img in y_list] + elif in_c == 1 and tar_type == 'RGB': # gray/y to BGR + return [cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) for img in img_list] + else: + return img_list + + +''' +# -------------------------------------------- +# metric, PSNR and SSIM +# -------------------------------------------- +''' + + +# -------------------------------------------- +# PSNR +# -------------------------------------------- +def calculate_psnr(img1, img2, border=0): + # img1 and img2 have range [0, 255] + #img1 = img1.squeeze() + #img2 = img2.squeeze() + if not img1.shape == img2.shape: + raise ValueError('Input images must have the same dimensions.') + h, w = img1.shape[:2] + img1 = img1[border:h-border, border:w-border] + img2 = img2[border:h-border, border:w-border] + + img1 = img1.astype(np.float64) + img2 = img2.astype(np.float64) + mse = np.mean((img1 - img2)**2) + if mse == 0: + return float('inf') + return 20 * math.log10(255.0 / math.sqrt(mse)) + + +# -------------------------------------------- +# SSIM +# -------------------------------------------- +def calculate_ssim(img1, img2, border=0): + '''calculate SSIM + the same outputs as MATLAB's + img1, img2: [0, 255] + ''' + #img1 = img1.squeeze() + #img2 = img2.squeeze() + if not img1.shape == img2.shape: + raise ValueError('Input images must have the same dimensions.') + h, w = img1.shape[:2] + img1 = img1[border:h-border, border:w-border] + img2 = img2[border:h-border, border:w-border] + + if img1.ndim == 2: + return ssim(img1, img2) + elif img1.ndim == 3: + if img1.shape[2] == 3: + ssims = [] + for i in range(3): + ssims.append(ssim(img1[:,:,i], img2[:,:,i])) + return np.array(ssims).mean() + elif img1.shape[2] == 1: + return ssim(np.squeeze(img1), np.squeeze(img2)) + else: + raise ValueError('Wrong input image dimensions.') + + +def ssim(img1, img2): + C1 = (0.01 * 255)**2 + C2 = (0.03 * 255)**2 + + img1 = img1.astype(np.float64) + img2 = img2.astype(np.float64) + kernel = cv2.getGaussianKernel(11, 1.5) + window = np.outer(kernel, kernel.transpose()) + + mu1 = cv2.filter2D(img1, -1, window)[5:-5, 5:-5] # valid + mu2 = cv2.filter2D(img2, -1, window)[5:-5, 5:-5] + mu1_sq = mu1**2 + mu2_sq = mu2**2 + mu1_mu2 = mu1 * mu2 + sigma1_sq = cv2.filter2D(img1**2, -1, window)[5:-5, 5:-5] - mu1_sq + sigma2_sq = cv2.filter2D(img2**2, -1, window)[5:-5, 5:-5] - mu2_sq + sigma12 = cv2.filter2D(img1 * img2, -1, window)[5:-5, 5:-5] - mu1_mu2 + + ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) * + (sigma1_sq + sigma2_sq + C2)) + return ssim_map.mean() + + +''' +# -------------------------------------------- +# matlab's bicubic imresize (numpy and torch) [0, 1] +# -------------------------------------------- +''' + + +# matlab 'imresize' function, now only support 'bicubic' +def cubic(x): + absx = torch.abs(x) + absx2 = absx**2 + absx3 = absx**3 + return (1.5*absx3 - 2.5*absx2 + 1) * ((absx <= 1).type_as(absx)) + \ + (-0.5*absx3 + 2.5*absx2 - 4*absx + 2) * (((absx > 1)*(absx <= 2)).type_as(absx)) + + +def calculate_weights_indices(in_length, out_length, scale, kernel, kernel_width, antialiasing): + if (scale < 1) and (antialiasing): + # Use a modified kernel to simultaneously interpolate and antialias- larger kernel width + kernel_width = kernel_width / scale + + # Output-space coordinates + x = torch.linspace(1, out_length, out_length) + + # Input-space coordinates. Calculate the inverse mapping such that 0.5 + # in output space maps to 0.5 in input space, and 0.5+scale in output + # space maps to 1.5 in input space. + u = x / scale + 0.5 * (1 - 1 / scale) + + # What is the left-most pixel that can be involved in the computation? + left = torch.floor(u - kernel_width / 2) + + # What is the maximum number of pixels that can be involved in the + # computation? Note: it's OK to use an extra pixel here; if the + # corresponding weights are all zero, it will be eliminated at the end + # of this function. + P = math.ceil(kernel_width) + 2 + + # The indices of the input pixels involved in computing the k-th output + # pixel are in row k of the indices matrix. + indices = left.view(out_length, 1).expand(out_length, P) + torch.linspace(0, P - 1, P).view( + 1, P).expand(out_length, P) + + # The weights used to compute the k-th output pixel are in row k of the + # weights matrix. + distance_to_center = u.view(out_length, 1).expand(out_length, P) - indices + # apply cubic kernel + if (scale < 1) and (antialiasing): + weights = scale * cubic(distance_to_center * scale) + else: + weights = cubic(distance_to_center) + # Normalize the weights matrix so that each row sums to 1. + weights_sum = torch.sum(weights, 1).view(out_length, 1) + weights = weights / weights_sum.expand(out_length, P) + + # If a column in weights is all zero, get rid of it. only consider the first and last column. + weights_zero_tmp = torch.sum((weights == 0), 0) + if not math.isclose(weights_zero_tmp[0], 0, rel_tol=1e-6): + indices = indices.narrow(1, 1, P - 2) + weights = weights.narrow(1, 1, P - 2) + if not math.isclose(weights_zero_tmp[-1], 0, rel_tol=1e-6): + indices = indices.narrow(1, 0, P - 2) + weights = weights.narrow(1, 0, P - 2) + weights = weights.contiguous() + indices = indices.contiguous() + sym_len_s = -indices.min() + 1 + sym_len_e = indices.max() - in_length + indices = indices + sym_len_s - 1 + return weights, indices, int(sym_len_s), int(sym_len_e) + + +# -------------------------------------------- +# imresize for tensor image [0, 1] +# -------------------------------------------- +def imresize(img, scale, antialiasing=True): + # Now the scale should be the same for H and W + # input: img: pytorch tensor, CHW or HW [0,1] + # output: CHW or HW [0,1] w/o round + need_squeeze = True if img.dim() == 2 else False + if need_squeeze: + img.unsqueeze_(0) + in_C, in_H, in_W = img.size() + out_C, out_H, out_W = in_C, math.ceil(in_H * scale), math.ceil(in_W * scale) + kernel_width = 4 + kernel = 'cubic' + + # Return the desired dimension order for performing the resize. The + # strategy is to perform the resize first along the dimension with the + # smallest scale factor. + # Now we do not support this. + + # get weights and indices + weights_H, indices_H, sym_len_Hs, sym_len_He = calculate_weights_indices( + in_H, out_H, scale, kernel, kernel_width, antialiasing) + weights_W, indices_W, sym_len_Ws, sym_len_We = calculate_weights_indices( + in_W, out_W, scale, kernel, kernel_width, antialiasing) + # process H dimension + # symmetric copying + img_aug = torch.FloatTensor(in_C, in_H + sym_len_Hs + sym_len_He, in_W) + img_aug.narrow(1, sym_len_Hs, in_H).copy_(img) + + sym_patch = img[:, :sym_len_Hs, :] + inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(1, inv_idx) + img_aug.narrow(1, 0, sym_len_Hs).copy_(sym_patch_inv) + + sym_patch = img[:, -sym_len_He:, :] + inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(1, inv_idx) + img_aug.narrow(1, sym_len_Hs + in_H, sym_len_He).copy_(sym_patch_inv) + + out_1 = torch.FloatTensor(in_C, out_H, in_W) + kernel_width = weights_H.size(1) + for i in range(out_H): + idx = int(indices_H[i][0]) + for j in range(out_C): + out_1[j, i, :] = img_aug[j, idx:idx + kernel_width, :].transpose(0, 1).mv(weights_H[i]) + + # process W dimension + # symmetric copying + out_1_aug = torch.FloatTensor(in_C, out_H, in_W + sym_len_Ws + sym_len_We) + out_1_aug.narrow(2, sym_len_Ws, in_W).copy_(out_1) + + sym_patch = out_1[:, :, :sym_len_Ws] + inv_idx = torch.arange(sym_patch.size(2) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(2, inv_idx) + out_1_aug.narrow(2, 0, sym_len_Ws).copy_(sym_patch_inv) + + sym_patch = out_1[:, :, -sym_len_We:] + inv_idx = torch.arange(sym_patch.size(2) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(2, inv_idx) + out_1_aug.narrow(2, sym_len_Ws + in_W, sym_len_We).copy_(sym_patch_inv) + + out_2 = torch.FloatTensor(in_C, out_H, out_W) + kernel_width = weights_W.size(1) + for i in range(out_W): + idx = int(indices_W[i][0]) + for j in range(out_C): + out_2[j, :, i] = out_1_aug[j, :, idx:idx + kernel_width].mv(weights_W[i]) + if need_squeeze: + out_2.squeeze_() + return out_2 + + +# -------------------------------------------- +# imresize for numpy image [0, 1] +# -------------------------------------------- +def imresize_np(img, scale, antialiasing=True): + # Now the scale should be the same for H and W + # input: img: Numpy, HWC or HW [0,1] + # output: HWC or HW [0,1] w/o round + img = torch.from_numpy(img) + need_squeeze = True if img.dim() == 2 else False + if need_squeeze: + img.unsqueeze_(2) + + in_H, in_W, in_C = img.size() + out_C, out_H, out_W = in_C, math.ceil(in_H * scale), math.ceil(in_W * scale) + kernel_width = 4 + kernel = 'cubic' + + # Return the desired dimension order for performing the resize. The + # strategy is to perform the resize first along the dimension with the + # smallest scale factor. + # Now we do not support this. + + # get weights and indices + weights_H, indices_H, sym_len_Hs, sym_len_He = calculate_weights_indices( + in_H, out_H, scale, kernel, kernel_width, antialiasing) + weights_W, indices_W, sym_len_Ws, sym_len_We = calculate_weights_indices( + in_W, out_W, scale, kernel, kernel_width, antialiasing) + # process H dimension + # symmetric copying + img_aug = torch.FloatTensor(in_H + sym_len_Hs + sym_len_He, in_W, in_C) + img_aug.narrow(0, sym_len_Hs, in_H).copy_(img) + + sym_patch = img[:sym_len_Hs, :, :] + inv_idx = torch.arange(sym_patch.size(0) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(0, inv_idx) + img_aug.narrow(0, 0, sym_len_Hs).copy_(sym_patch_inv) + + sym_patch = img[-sym_len_He:, :, :] + inv_idx = torch.arange(sym_patch.size(0) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(0, inv_idx) + img_aug.narrow(0, sym_len_Hs + in_H, sym_len_He).copy_(sym_patch_inv) + + out_1 = torch.FloatTensor(out_H, in_W, in_C) + kernel_width = weights_H.size(1) + for i in range(out_H): + idx = int(indices_H[i][0]) + for j in range(out_C): + out_1[i, :, j] = img_aug[idx:idx + kernel_width, :, j].transpose(0, 1).mv(weights_H[i]) + + # process W dimension + # symmetric copying + out_1_aug = torch.FloatTensor(out_H, in_W + sym_len_Ws + sym_len_We, in_C) + out_1_aug.narrow(1, sym_len_Ws, in_W).copy_(out_1) + + sym_patch = out_1[:, :sym_len_Ws, :] + inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(1, inv_idx) + out_1_aug.narrow(1, 0, sym_len_Ws).copy_(sym_patch_inv) + + sym_patch = out_1[:, -sym_len_We:, :] + inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long() + sym_patch_inv = sym_patch.index_select(1, inv_idx) + out_1_aug.narrow(1, sym_len_Ws + in_W, sym_len_We).copy_(sym_patch_inv) + + out_2 = torch.FloatTensor(out_H, out_W, in_C) + kernel_width = weights_W.size(1) + for i in range(out_W): + idx = int(indices_W[i][0]) + for j in range(out_C): + out_2[:, i, j] = out_1_aug[:, idx:idx + kernel_width, j].mv(weights_W[i]) + if need_squeeze: + out_2.squeeze_() + + return out_2.numpy() + + +if __name__ == '__main__': + print('---') +# img = imread_uint('test.bmp', 3) +# img = uint2single(img) +# img_bicubic = imresize_np(img, 1/4) \ No newline at end of file diff --git a/comparison_models/T2IAdapter/ldm/util.py b/comparison_models/T2IAdapter/ldm/util.py new file mode 100644 index 0000000..dc9e3c4 --- /dev/null +++ b/comparison_models/T2IAdapter/ldm/util.py @@ -0,0 +1,200 @@ +import importlib +import math + +import cv2 +import torch +import numpy as np + +import os +from safetensors.torch import load_file + +from inspect import isfunction +from PIL import Image, ImageDraw, ImageFont + + +def log_txt_as_img(wh, xc, size=10): + # wh a tuple of (width, height) + # xc a list of captions to plot + b = len(xc) + txts = list() + for bi in range(b): + txt = Image.new("RGB", wh, color="white") + draw = ImageDraw.Draw(txt) + font = ImageFont.truetype('assets/DejaVuSans.ttf', size=size) + nc = int(40 * (wh[0] / 256)) + lines = "\n".join(xc[bi][start:start + nc] for start in range(0, len(xc[bi]), nc)) + + try: + draw.text((0, 0), lines, fill="black", font=font) + except UnicodeEncodeError: + print("Cant encode string for logging. Skipping.") + + txt = np.array(txt).transpose(2, 0, 1) / 127.5 - 1.0 + txts.append(txt) + txts = np.stack(txts) + txts = torch.tensor(txts) + return txts + + +def ismap(x): + if not isinstance(x, torch.Tensor): + return False + return (len(x.shape) == 4) and (x.shape[1] > 3) + + +def isimage(x): + if not isinstance(x, torch.Tensor): + return False + return (len(x.shape) == 4) and (x.shape[1] == 3 or x.shape[1] == 1) + + +def exists(x): + return x is not None + + +def default(val, d): + if exists(val): + return val + return d() if isfunction(d) else d + + +def mean_flat(tensor): + """ + https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/nn.py#L86 + Take the mean over all non-batch dimensions. + """ + return tensor.mean(dim=list(range(1, len(tensor.shape)))) + + +def count_params(model, verbose=False): + total_params = sum(p.numel() for p in model.parameters()) + if verbose: + print(f"{model.__class__.__name__} has {total_params * 1.e-6:.2f} M params.") + return total_params + + +def instantiate_from_config(config): + if not "target" in config: + if config == '__is_first_stage__': + return None + elif config == "__is_unconditional__": + return None + raise KeyError("Expected key `target` to instantiate.") + return get_obj_from_str(config["target"])(**config.get("params", dict())) + + +def get_obj_from_str(string, reload=False): + module, cls = string.rsplit(".", 1) + if reload: + module_imp = importlib.import_module(module) + importlib.reload(module_imp) + return getattr(importlib.import_module(module, package=None), cls) + + +checkpoint_dict_replacements = { + 'cond_stage_model.transformer.text_model.embeddings.': 'cond_stage_model.transformer.embeddings.', + 'cond_stage_model.transformer.text_model.encoder.': 'cond_stage_model.transformer.encoder.', + 'cond_stage_model.transformer.text_model.final_layer_norm.': 'cond_stage_model.transformer.final_layer_norm.', +} + + +def transform_checkpoint_dict_key(k): + for text, replacement in checkpoint_dict_replacements.items(): + if k.startswith(text): + k = replacement + k[len(text):] + + return k + + +def get_state_dict_from_checkpoint(pl_sd): + pl_sd = pl_sd.pop("state_dict", pl_sd) + pl_sd.pop("state_dict", None) + + sd = {} + for k, v in pl_sd.items(): + new_key = transform_checkpoint_dict_key(k) + + if new_key is not None: + sd[new_key] = v + + pl_sd.clear() + pl_sd.update(sd) + + return pl_sd + + +def read_state_dict(checkpoint_file, print_global_state=False): + _, extension = os.path.splitext(checkpoint_file) + if extension.lower() == ".safetensors": + pl_sd = load_file(checkpoint_file, device='cpu') + else: + pl_sd = torch.load(checkpoint_file, map_location='cpu') + + if print_global_state and "global_step" in pl_sd: + print(f"Global Step: {pl_sd['global_step']}") + + sd = get_state_dict_from_checkpoint(pl_sd) + return sd + + +def load_model_from_config(config, ckpt, vae_ckpt=None, verbose=False): + print(f"Loading model from {ckpt}") + sd = read_state_dict(ckpt) + model = instantiate_from_config(config.model) + m, u = model.load_state_dict(sd, strict=False) + if len(m) > 0 and verbose: + print("missing keys:") + print(m) + if len(u) > 0 and verbose: + print("unexpected keys:") + print(u) + + if 'anything' in ckpt.lower() and vae_ckpt is None: + vae_ckpt = 'models/anything-v4.0.vae.pt' + + if vae_ckpt is not None and vae_ckpt != 'None': + print(f"Loading vae model from {vae_ckpt}") + vae_sd = torch.load(vae_ckpt, map_location="cpu") + if "global_step" in vae_sd: + print(f"Global Step: {vae_sd['global_step']}") + sd = vae_sd["state_dict"] + m, u = model.first_stage_model.load_state_dict(sd, strict=False) + if len(m) > 0 and verbose: + print("missing keys:") + print(m) + if len(u) > 0 and verbose: + print("unexpected keys:") + print(u) + + model.cuda() + model.eval() + return model + + +def resize_numpy_image(image, max_resolution=512 * 512, resize_short_edge=None): + h, w = image.shape[:2] + if resize_short_edge is not None: + k = resize_short_edge / min(h, w) + else: + k = max_resolution / (h * w) + k = k**0.5 + h = int(np.round(h * k / 64)) * 64 + w = int(np.round(w * k / 64)) * 64 + image = cv2.resize(image, (w, h), interpolation=cv2.INTER_LANCZOS4) + return image + + +# make uc and prompt shapes match via padding for long prompts +null_cond = None + +def fix_cond_shapes(model, prompt_condition, uc): + if uc is None: + return prompt_condition, uc + global null_cond + if null_cond is None: + null_cond = model.get_learned_conditioning([""]) + while prompt_condition.shape[1] > uc.shape[1]: + uc = torch.cat((uc, null_cond.repeat((uc.shape[0], 1, 1))), axis=1) + while prompt_condition.shape[1] < uc.shape[1]: + prompt_condition = torch.cat((prompt_condition, null_cond.repeat((prompt_condition.shape[0], 1, 1))), axis=1) + return prompt_condition, uc diff --git a/comparison_models/T2IAdapter/models/README.md b/comparison_models/T2IAdapter/models/README.md new file mode 100644 index 0000000..b81e99e --- /dev/null +++ b/comparison_models/T2IAdapter/models/README.md @@ -0,0 +1,6 @@ +You can manually download the models from +- [T2I-Adapter v1](https://huggingface.co/TencentARC/T2I-Adapter/tree/main/models) +- [CoAdapter Preview version](https://huggingface.co/TencentARC/T2I-Adapter/tree/main/models) +- [third-party-models](https://huggingface.co/TencentARC/T2I-Adapter/tree/main/third-party-models) + +and put them into `models` folder \ No newline at end of file diff --git a/comparison_models/T2IAdapter/requirements.txt b/comparison_models/T2IAdapter/requirements.txt new file mode 100644 index 0000000..c2f3d5e --- /dev/null +++ b/comparison_models/T2IAdapter/requirements.txt @@ -0,0 +1,18 @@ +transformers==4.19.2 +diffusers==0.11.1 +invisible_watermark==0.1.5 +basicsr==1.4.2 +einops==0.6.0 +omegaconf==2.3.0 +pytorch_lightning==1.5.9 +gradio +opencv-python +pudb +imageio +imageio-ffmpeg +k-diffusion +webdataset +open-clip-torch +kornia +safetensors +timm diff --git a/comparison_models/T2IAdapter/test_adapter.py b/comparison_models/T2IAdapter/test_adapter.py new file mode 100644 index 0000000..aa8f7ae --- /dev/null +++ b/comparison_models/T2IAdapter/test_adapter.py @@ -0,0 +1,80 @@ +import os + +import cv2 +import torch +from basicsr.utils import tensor2img +from pytorch_lightning import seed_everything +from torch import autocast + +from ldm.inference_base import (diffusion_inference, get_adapters, get_base_argument_parser, get_sd_models) +from ldm.modules.extra_condition import api +from ldm.modules.extra_condition.api import (ExtraCondition, get_adapter_feature, get_cond_model) + +torch.set_grad_enabled(False) + + +def main(): + supported_cond = [e.name for e in ExtraCondition] + parser = get_base_argument_parser() + parser.add_argument( + '--which_cond', + type=str, + required=True, + choices=supported_cond, + help='which condition modality you want to test', + ) + opt = parser.parse_args() + which_cond = opt.which_cond + if opt.outdir is None: + opt.outdir = f'outputs/test-{which_cond}' + os.makedirs(opt.outdir, exist_ok=True) + if opt.resize_short_edge is None: + print(f"you don't specify the resize_shot_edge, so the maximum resolution is set to {opt.max_resolution}") + opt.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") + + # support two test mode: single image test, and batch test (through a txt file) + if opt.prompt.endswith('.txt'): + assert opt.prompt.endswith('.txt') + image_paths = [] + prompts = [] + with open(opt.prompt, 'r') as f: + lines = f.readlines() + for line in lines: + line = line.strip() + image_paths.append(line.split('; ')[0]) + prompts.append(line.split('; ')[1]) + else: + image_paths = [opt.cond_path] + prompts = [opt.prompt] + print(image_paths) + + # prepare models + sd_model, sampler = get_sd_models(opt) + adapter = get_adapters(opt, getattr(ExtraCondition, which_cond)) + cond_model = None + if opt.cond_inp_type == 'image': + cond_model = get_cond_model(opt, getattr(ExtraCondition, which_cond)) + + process_cond_module = getattr(api, f'get_cond_{which_cond}') + + # inference + with torch.inference_mode(), \ + sd_model.ema_scope(), \ + autocast('cuda'): + for test_idx, (cond_path, prompt) in enumerate(zip(image_paths, prompts)): + seed_everything(opt.seed) + for v_idx in range(opt.n_samples): + # seed_everything(opt.seed+v_idx+test_idx) + cond = process_cond_module(opt, cond_path, opt.cond_inp_type, cond_model) + + base_count = len(os.listdir(opt.outdir)) // 2 + cv2.imwrite(os.path.join(opt.outdir, f'{base_count:05}_{which_cond}.png'), tensor2img(cond)) + + adapter_features, append_to_context = get_adapter_feature(cond, adapter) + opt.prompt = prompt + result = diffusion_inference(opt, sd_model, sampler, adapter_features, append_to_context) + cv2.imwrite(os.path.join(opt.outdir, f'{base_count:05}_result.png'), tensor2img(result)) + + +if __name__ == '__main__': + main() diff --git a/comparison_models/T2IAdapter/test_composable_adapters.py b/comparison_models/T2IAdapter/test_composable_adapters.py new file mode 100644 index 0000000..7e814e9 --- /dev/null +++ b/comparison_models/T2IAdapter/test_composable_adapters.py @@ -0,0 +1,101 @@ +import cv2 +import os +import torch +from pytorch_lightning import seed_everything +from torch import autocast + +from basicsr.utils import tensor2img +from ldm.inference_base import diffusion_inference, get_adapters, get_base_argument_parser, get_sd_models +from ldm.modules.extra_condition import api +from ldm.modules.extra_condition.api import ExtraCondition, get_adapter_feature, get_cond_model + +torch.set_grad_enabled(False) + + +def main(): + supported_cond = [e.name for e in ExtraCondition] + parser = get_base_argument_parser() + for cond_name in supported_cond: + parser.add_argument( + f'--{cond_name}_path', + type=str, + default=None, + help=f'condition image path for {cond_name}', + ) + parser.add_argument( + f'--{cond_name}_inp_type', + type=str, + default='image', + help=f'the type of the input condition image, can be image or {cond_name}', + choices=['image', cond_name], + ) + parser.add_argument( + f'--{cond_name}_adapter_ckpt', + type=str, + default=None, + help=f'path to checkpoint of the {cond_name} adapter, ' + f'if {cond_name}_path is not None, this should not be None too', + ) + parser.add_argument( + f'--{cond_name}_weight', + type=float, + default=1.0, + help=f'the {cond_name} adapter features are multiplied by the {cond_name}_weight and then summed up together', + ) + opt = parser.parse_args() + + # process argument + activated_conds = [] + cond_paths = [] + adapter_ckpts = [] + for cond_name in supported_cond: + if getattr(opt, f'{cond_name}_path') is None: + continue + assert getattr(opt, f'{cond_name}_adapter_ckpt') is not None, f'you should specify the {cond_name}_adapter_ckpt' + activated_conds.append(cond_name) + cond_paths.append(getattr(opt, f'{cond_name}_path')) + adapter_ckpts.append(getattr(opt, f'{cond_name}_adapter_ckpt')) + assert len(activated_conds) != 0, 'you did not input any condition' + + if opt.outdir is None: + opt.outdir = f'outputs/test-composable-adapters' + os.makedirs(opt.outdir, exist_ok=True) + if opt.resize_short_edge is None: + print(f"you don't specify the resize_shot_edge, so the maximum resolution is set to {opt.max_resolution}") + opt.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") + + # prepare models + adapters = [] + cond_models = [] + cond_inp_types = [] + process_cond_modules = [] + for cond_name in activated_conds: + adapters.append(get_adapters(opt, getattr(ExtraCondition, cond_name))) + cond_inp_type = getattr(opt, f'{cond_name}_inp_type', 'image') + if cond_inp_type == 'image': + cond_models.append(get_cond_model(opt, getattr(ExtraCondition, cond_name))) + else: + cond_models.append(None) + cond_inp_types.append(cond_inp_type) + process_cond_modules.append(getattr(api, f'get_cond_{cond_name}')) + sd_model, sampler = get_sd_models(opt) + + # inference + with torch.inference_mode(), \ + sd_model.ema_scope(), \ + autocast('cuda'): + seed_everything(opt.seed) + conds = [] + for cond_idx, cond_name in enumerate(activated_conds): + conds.append(process_cond_modules[cond_idx]( + opt, cond_paths[cond_idx], cond_inp_types[cond_idx], cond_models[cond_idx], + )) + adapter_features, append_to_context = get_adapter_feature(conds, adapters) + for v_idx in range(opt.n_samples): + result = diffusion_inference(opt, sd_model, sampler, adapter_features, append_to_context) + base_count = len(os.listdir(opt.outdir)) + cv2.imwrite(os.path.join(opt.outdir, f'{base_count:05}_result.png'), tensor2img(result)) + + +if __name__ == '__main__': + main() diff --git a/comparison_models/T2IAdapter/train.py b/comparison_models/T2IAdapter/train.py new file mode 100644 index 0000000..0d8acc2 --- /dev/null +++ b/comparison_models/T2IAdapter/train.py @@ -0,0 +1,524 @@ +import argparse +import datetime +import glob +import os +import pytorch_lightning as pl +import sys +import time +import torch +from omegaconf import OmegaConf +from packaging import version +from pytorch_lightning import seed_everything +from pytorch_lightning.callbacks import Callback, LearningRateMonitor +from pytorch_lightning.trainer import Trainer +from pytorch_lightning.utilities import rank_zero_info +from pytorch_lightning.utilities.distributed import rank_zero_only + +from ldm.util import instantiate_from_config, read_state_dict + +import warnings + +warnings.filterwarnings("ignore", ".*Trying to infer the `batch_size` from an ambiguous collection.*") + + +@rank_zero_only +def rank_zero_print(*args): + print(*args) + + +def get_parser(**parser_kwargs): + + def str2bool(v): + if isinstance(v, bool): + return v + if v.lower() in ("yes", "true", "t", "y", "1"): + return True + elif v.lower() in ("no", "false", "f", "n", "0"): + return False + else: + raise argparse.ArgumentTypeError("Boolean value expected.") + + parser = argparse.ArgumentParser(**parser_kwargs) + parser.add_argument( + "--sd_finetune_from", + type=str, + nargs="?", + required=True, + help="path to stable diffusion checkpoint to load SD model state from", + ) + parser.add_argument( + "--adapter_finetune_from", + type=str, + nargs="?", + default="", + help="path to stable diffusion checkpoint to load adapter model state from", + ) + parser.add_argument( + "--coadapter_finetune_from", + type=str, + nargs="?", + default="", + help="path to stable diffusion checkpoint to load CoAdapter state from", + ) + parser.add_argument( + "-n", + "--name", + type=str, + const=True, + default="", + nargs="?", + help="postfix for logdir", + ) + parser.add_argument( + "-r", + "--resume", + type=str, + const=True, + default="", + nargs="?", + help="resume from logdir or checkpoint in logdir", + ) + parser.add_argument( + "-b", + "--base", + nargs="*", + metavar="base_config.yaml", + help="paths to base configs. Loaded from left-to-right. " + "Parameters can be overwritten or added with command-line options of the form `--key value`.", + default=list(), + ) + parser.add_argument( + "-t", + "--train", + type=str2bool, + const=True, + default=False, + nargs="?", + help="train", + ) + parser.add_argument( + "--no-test", + type=str2bool, + const=True, + default=False, + nargs="?", + help="disable test", + ) + parser.add_argument("-p", "--project", help="name of new or path to existing project") + parser.add_argument( + "-d", + "--debug", + type=str2bool, + nargs="?", + const=True, + default=False, + help="enable post-mortem debugging", + ) + parser.add_argument( + "-s", + "--seed", + type=int, + default=23, + help="seed for seed_everything", + ) + parser.add_argument( + "-f", + "--postfix", + type=str, + default="", + help="post-postfix for default name", + ) + parser.add_argument( + "-l", + "--logdir", + type=str, + default="logs", + help="directory for logging dat shit", + ) + parser.add_argument( + "--scale_lr", + type=str2bool, + nargs="?", + const=True, + default=True, + help="scale base-lr by ngpu * batch_size * n_accumulate", + ) + parser.add_argument( + "--auto_resume", + type=str2bool, + nargs="?", + const=True, + default=False, + help="auto resume similar like basicsr", + ) + + return parser + + +def nondefault_trainer_args(opt): + parser = argparse.ArgumentParser() + parser = Trainer.add_argparse_args(parser) + args = parser.parse_args([]) + return sorted(k for k in vars(args) if getattr(opt, k) != getattr(args, k)) + + +class SetupCallback(Callback): + + def __init__(self, resume, now, logdir, ckptdir, cfgdir, config, lightning_config, cli_config, debug): + super().__init__() + self.resume = resume + self.now = now + self.logdir = logdir + self.ckptdir = ckptdir + self.cfgdir = cfgdir + self.config = config + self.lightning_config = lightning_config + self.cli_config = cli_config + self.debug = debug + + def on_keyboard_interrupt(self, trainer, pl_module): + if not self.debug and trainer.global_rank == 0: + rank_zero_print("Summoning checkpoint.") + ckpt_path = os.path.join(self.ckptdir, "last.ckpt") + trainer.save_checkpoint(ckpt_path) + + def on_fit_start(self, trainer, pl_module): + if trainer.global_rank == 0: + # Create logdirs and save configs + os.makedirs(self.logdir, exist_ok=True) + os.makedirs(self.ckptdir, exist_ok=True) + os.makedirs(self.cfgdir, exist_ok=True) + + if "callbacks" in self.lightning_config: + if 'metrics_over_trainsteps_checkpoint' in self.lightning_config['callbacks']: + os.makedirs(os.path.join(self.ckptdir, 'trainstep_checkpoints'), exist_ok=True) + rank_zero_print("Project config") + rank_zero_print(OmegaConf.to_yaml(self.config)) + OmegaConf.save(self.config, os.path.join(self.cfgdir, "{}-project.yaml".format(self.now))) + + rank_zero_print("Lightning config") + rank_zero_print(OmegaConf.to_yaml(self.lightning_config)) + OmegaConf.save( + OmegaConf.create({"lightning": self.lightning_config}), + os.path.join(self.cfgdir, "{}-lightning.yaml".format(self.now))) + + rank_zero_print("cli config") + OmegaConf.save( + OmegaConf.create({"cli": self.cli_config}), os.path.join(self.cfgdir, "{}-cli.yaml".format(self.now))) + + +class CUDACallback(Callback): + # see https://github.com/SeanNaren/minGPT/blob/master/mingpt/callback.py + def on_train_start(self, trainer, pl_module): + # Reset the memory use counter + torch.cuda.reset_peak_memory_stats(trainer.root_gpu) + torch.cuda.synchronize(trainer.root_gpu) + self.start_time = time.time() + + def on_save_checkpoint(self, trainer, pl_module, checkpoint): + torch.cuda.synchronize(trainer.root_gpu) + max_memory = torch.cuda.max_memory_allocated(trainer.root_gpu) / 2 ** 20 + epoch_time = time.time() - self.start_time + + try: + max_memory = trainer.training_type_plugin.reduce(max_memory) + epoch_time = trainer.training_type_plugin.reduce(epoch_time) + + rank_zero_info(f"Average checkpoint generating time: {epoch_time:.2f} seconds") + rank_zero_info(f"Average Peak memory {max_memory:.2f}MiB") + except AttributeError: + pass + + +def load_pretrained_ckp(model, ckpt, name): + rank_zero_print(f"Load {name} state from {ckpt}") + state_dict = read_state_dict(ckpt) + if name == 'adapter': + # old version didn't use pytorch lightning, so handle compatible here + new_state_dict = {} + for k, v in state_dict.items(): + if not k.startswith('adapter.'): + new_state_dict[f'adapter.{k}'] = v + if len(new_state_dict) != 0: + rank_zero_print('Take care that you are using old version adapter as pretrained model') + assert len(state_dict) == len(new_state_dict) + state_dict.clear() + state_dict.update(new_state_dict) + m, u = model.load_state_dict(state_dict, strict=False) + if len(m) > 0: + rank_zero_print(f"missing keys for {name}:") + rank_zero_print(m) + if len(u) > 0: + rank_zero_print(f"unexpected keys for {name}:") + rank_zero_print(u) + + +if __name__ == "__main__": + + now = datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S") + + # add cwd for convenience and to make classes in this file available when + # running as `python train.py` + # (in particular `train.DataModuleFromConfig`) + sys.path.append(os.getcwd()) + + parser = get_parser() + parser = Trainer.add_argparse_args(parser) + + opt, unknown = parser.parse_known_args() + if opt.name and opt.resume: + raise ValueError("-n/--name and -r/--resume cannot be specified both." + "If you want to resume training in a new log folder, " + "use -n/--name in combination with --resume_from_checkpoint") + if opt.resume: + if not os.path.exists(opt.resume): + raise ValueError("Cannot find {}".format(opt.resume)) + if os.path.isfile(opt.resume): + paths = opt.resume.split("/") + logdir = "/".join(paths[:-2]) + ckpt = opt.resume + else: + assert os.path.isdir(opt.resume), opt.resume + logdir = opt.resume.rstrip("/") + ckpt = os.path.join(logdir, "checkpoints", "last.ckpt") + + opt.resume_from_checkpoint = ckpt + base_configs = sorted(glob.glob(os.path.join(logdir, "configs/*.yaml"))) + opt.base = base_configs + opt.base + _tmp = logdir.split("/") + nowname = _tmp[-1] + else: + if opt.name: + name = opt.name + elif opt.base: + cfg_fname = os.path.split(opt.base[0])[-1] + cfg_name = os.path.splitext(cfg_fname)[0] + name = cfg_name + else: + raise ValueError + nowname = name + if opt.postfix != '': + nowname = name + opt.postfix + logdir = os.path.join(opt.logdir, nowname) + if os.path.isdir(logdir): + if not opt.auto_resume: + rename_logdir = logdir.strip('/') + '_archived_' + now + os.rename(logdir, rename_logdir) + else: + ckpt = os.path.join(logdir, "checkpoints", "last.ckpt") + # base_configs = sorted(glob.glob(os.path.join(logdir, "configs/*.yaml"))) + # opt.base = base_configs + opt.base + if os.path.isfile(ckpt): + opt.resume_from_checkpoint = ckpt + + ckptdir = os.path.join(logdir, "checkpoints") + cfgdir = os.path.join(logdir, "configs") + seed_everything(opt.seed) + + try: + # init and save configs + configs = [OmegaConf.load(cfg) for cfg in opt.base] + cli = OmegaConf.from_dotlist(unknown) + config = OmegaConf.merge(*configs, cli) + lightning_config = config.pop("lightning", OmegaConf.create()) + # merge trainer cli with config + trainer_config = lightning_config.get("trainer", OmegaConf.create()) + # default to ddp + trainer_config["accelerator"] = "ddp" + for k in nondefault_trainer_args(opt): + trainer_config[k] = getattr(opt, k) + if "gpus" not in trainer_config: + del trainer_config["accelerator"] + cpu = True + else: + gpuinfo = trainer_config["gpus"] + rank_zero_print(f"Running on GPUs {gpuinfo}") + cpu = False + trainer_opt = argparse.Namespace(**trainer_config) + lightning_config.trainer = trainer_config + + # model + model = instantiate_from_config(config.model) + model.cpu() + + load_pretrained_ckp(model, opt.sd_finetune_from, 'stable diffusion') + if not opt.adapter_finetune_from == "": + load_pretrained_ckp(model, opt.adapter_finetune_from, 'adapter') + if not opt.coadapter_finetune_from == "": + load_pretrained_ckp(model, opt.coadapter_finetune_from, 'coadapter') + + # trainer and callbacks + trainer_kwargs = dict() + + # default logger configs + default_logger_cfgs = { + "wandb": { + "target": "pytorch_lightning.loggers.WandbLogger", + "params": { + "name": nowname, + "save_dir": logdir, + "offline": opt.debug, + "id": nowname, + } + }, + "csvlogger": { + "target": "pytorch_lightning.loggers.CSVLogger", + "params": { + "name": "csvlogger", + "save_dir": logdir, + } + }, + } + default_logger_cfg = default_logger_cfgs["csvlogger"] + if "logger" in lightning_config: + logger_cfg = lightning_config.logger + else: + logger_cfg = OmegaConf.create() + logger_cfg = OmegaConf.merge(default_logger_cfg, logger_cfg) + trainer_kwargs["logger"] = instantiate_from_config(logger_cfg) + + # modelcheckpoint - use TrainResult/EvalResult(checkpoint_on=metric) to + # specify which metric is used to determine best models + default_modelckpt_cfg = { + "target": "pytorch_lightning.callbacks.ModelCheckpoint", + "params": { + "dirpath": ckptdir, + "filename": "{step:09}", + "verbose": True, + "save_last": True, + } + } + if hasattr(model, "monitor"): + rank_zero_print(f"Monitoring {model.monitor} as checkpoint metric.") + default_modelckpt_cfg["params"]["monitor"] = model.monitor + default_modelckpt_cfg["params"]["save_top_k"] = 3 + + if "modelcheckpoint" in lightning_config: + modelckpt_cfg = lightning_config.modelcheckpoint + else: + modelckpt_cfg = OmegaConf.create() + modelckpt_cfg = OmegaConf.merge(default_modelckpt_cfg, modelckpt_cfg) + rank_zero_print(f"Merged modelckpt-cfg: \n{modelckpt_cfg}") + if version.parse(pl.__version__) < version.parse('1.4.0'): + trainer_kwargs["checkpoint_callback"] = instantiate_from_config(modelckpt_cfg) + + # add callback which sets up log directory + cli_config = OmegaConf.create() + for k in vars(opt): + cli_config[k] = getattr(opt, k) + default_callbacks_cfg = { + "setup_callback": { + "target": "train.SetupCallback", + "params": { + "resume": opt.resume, + "now": now, + "logdir": logdir, + "ckptdir": ckptdir, + "cfgdir": cfgdir, + "config": config, + "lightning_config": lightning_config, + "cli_config": cli_config, + "debug": opt.debug, + } + }, + "learning_rate_logger": { + "target": "train.LearningRateMonitor", + "params": { + "logging_interval": "step", + # "log_momentum": True + } + }, + "cuda_callback": { + "target": "train.CUDACallback" + }, + } + if version.parse(pl.__version__) >= version.parse('1.4.0'): + default_callbacks_cfg.update({'checkpoint_callback': modelckpt_cfg}) + + if "callbacks" in lightning_config: + callbacks_cfg = lightning_config.callbacks + else: + callbacks_cfg = OmegaConf.create() + + callbacks_cfg = OmegaConf.merge(default_callbacks_cfg, callbacks_cfg) + if 'ignore_keys_callback' in callbacks_cfg and hasattr(trainer_opt, 'resume_from_checkpoint'): + callbacks_cfg.ignore_keys_callback.params['ckpt_path'] = trainer_opt.resume_from_checkpoint + elif 'ignore_keys_callback' in callbacks_cfg: + del callbacks_cfg['ignore_keys_callback'] + + trainer_kwargs["callbacks"] = list(instantiate_from_config(callbacks_cfg[k]) for k in callbacks_cfg) + + trainer = Trainer.from_argparse_args(trainer_opt, **trainer_kwargs) + trainer.logdir = logdir + + # data + data = instantiate_from_config(config.data) + + # configure learning rate + bs, base_lr = config.data.params.batch_size, config.model.base_learning_rate + if not cpu: + ngpu = len(lightning_config.trainer.gpus.strip(",").split(',')) + else: + ngpu = 1 + if 'accumulate_grad_batches' in lightning_config.trainer: + accumulate_grad_batches = lightning_config.trainer.accumulate_grad_batches + else: + accumulate_grad_batches = 1 + rank_zero_print(f"accumulate_grad_batches = {accumulate_grad_batches}") + lightning_config.trainer.accumulate_grad_batches = accumulate_grad_batches + if opt.scale_lr: + model.learning_rate = accumulate_grad_batches * ngpu * bs * base_lr + rank_zero_print("Setting learning rate to {:.2e} = {} (accumulate_grad_batches) * {} " + "(num_gpus) * {} (batchsize) * {:.2e} (base_lr)".format(model.learning_rate, + accumulate_grad_batches, ngpu, bs, + base_lr)) + else: + model.learning_rate = base_lr + rank_zero_print("++++ NOT USING LR SCALING ++++") + rank_zero_print(f"Setting learning rate to {model.learning_rate:.2e}") + + # allow checkpointing via USR1 + def melk(*args, **kwargs): + # run all checkpoint hooks + if trainer.global_rank == 0: + rank_zero_print("Summoning checkpoint.") + ckpt_path = os.path.join(ckptdir, "last.ckpt") + trainer.save_checkpoint(ckpt_path) + + def divein(*args, **kwargs): + if trainer.global_rank == 0: + import pudb + pudb.set_trace() + + import signal + + signal.signal(signal.SIGUSR1, melk) + signal.signal(signal.SIGUSR2, divein) + + # run + if opt.train: + try: + trainer.fit(model, data) + except Exception: + if not opt.debug: + melk() + raise + if not opt.no_test and not trainer.interrupted: + trainer.test(model, data) + except Exception: + if opt.debug and trainer.global_rank == 0: + try: + import pudb as debugger + except ImportError: + import pdb as debugger + debugger.post_mortem() + raise + finally: + # move newly created debug project to debug_runs + if opt.debug and not opt.resume and trainer.global_rank == 0: + dst, name = os.path.split(logdir) + dst = os.path.join(dst, "debug_runs", name) + os.makedirs(os.path.split(dst)[0], exist_ok=True) + os.rename(logdir, dst) + if trainer.global_rank == 0: + rank_zero_print(trainer.profiler.summary()) diff --git a/comparison_models/T2IAdapter/train_depth.py b/comparison_models/T2IAdapter/train_depth.py new file mode 100644 index 0000000..af9a203 --- /dev/null +++ b/comparison_models/T2IAdapter/train_depth.py @@ -0,0 +1,281 @@ +import argparse +import logging +import os +import os.path as osp +import torch +from basicsr.utils import (get_env_info, get_root_logger, get_time_str, + scandir) +from basicsr.utils.options import copy_opt_file, dict2str +from omegaconf import OmegaConf + +from ldm.data.dataset_depth import DepthDataset +from basicsr.utils.dist_util import get_dist_info, init_dist, master_only +from ldm.modules.encoders.adapter import Adapter +from ldm.util import load_model_from_config + + +@master_only +def mkdir_and_rename(path): + """mkdirs. If path exists, rename it with timestamp and create a new one. + + Args: + path (str): Folder path. + """ + if osp.exists(path): + new_name = path + '_archived_' + get_time_str() + print(f'Path already exists. Rename it to {new_name}', flush=True) + os.rename(path, new_name) + os.makedirs(path, exist_ok=True) + os.makedirs(osp.join(path, 'models')) + os.makedirs(osp.join(path, 'training_states')) + os.makedirs(osp.join(path, 'visualization')) + + +def load_resume_state(opt): + resume_state_path = None + if opt.auto_resume: + state_path = osp.join('experiments', opt.name, 'training_states') + if osp.isdir(state_path): + states = list(scandir(state_path, suffix='state', recursive=False, full_path=False)) + if len(states) != 0: + states = [float(v.split('.state')[0]) for v in states] + resume_state_path = osp.join(state_path, f'{max(states):.0f}.state') + opt.resume_state_path = resume_state_path + + if resume_state_path is None: + resume_state = None + else: + device_id = torch.cuda.current_device() + resume_state = torch.load(resume_state_path, map_location=lambda storage, loc: storage.cuda(device_id)) + return resume_state + + +def parsr_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--bsize", + type=int, + default=8, + ) + parser.add_argument( + "--epochs", + type=int, + default=10000, + ) + parser.add_argument( + "--num_workers", + type=int, + default=8, + ) + parser.add_argument( + "--plms", + action='store_true', + help="use plms sampling", + ) + parser.add_argument( + "--auto_resume", + action='store_true', + help="use plms sampling", + ) + parser.add_argument( + "--ckpt", + type=str, + default="models/sd-v1-4.ckpt", + help="path to checkpoint of model", + ) + parser.add_argument( + "--config", + type=str, + default="configs/stable-diffusion/sd-v1-train.yaml", + help="path to config which constructs model", + ) + parser.add_argument( + "--name", + type=str, + default="train_depth", + help="experiment name", + ) + parser.add_argument( + "--print_fq", + type=int, + default=100, + help="path to config which constructs model", + ) + parser.add_argument( + "--H", + type=int, + default=512, + help="image height, in pixel space", + ) + parser.add_argument( + "--W", + type=int, + default=512, + help="image width, in pixel space", + ) + parser.add_argument( + "--C", + type=int, + default=4, + help="latent channels", + ) + parser.add_argument( + "--f", + type=int, + default=8, + help="downsampling factor", + ) + parser.add_argument( + "--sample_steps", + type=int, + default=50, + help="number of ddim sampling steps", + ) + parser.add_argument( + "--n_samples", + type=int, + default=1, + help="how many samples to produce for each given prompt. A.k.a. batch size", + ) + parser.add_argument( + "--scale", + type=float, + default=7.5, + help="unconditional guidance scale: eps = eps(x, empty) + scale * (eps(x, cond) - eps(x, empty))", + ) + parser.add_argument( + "--gpus", + default=[0, 1, 2, 3], + help="gpu idx", + ) + parser.add_argument( + '--local_rank', + default=0, + type=int, + help='node rank for distributed training' + ) + parser.add_argument( + '--launcher', + default='pytorch', + type=str, + help='node rank for distributed training' + ) + opt = parser.parse_args() + return opt + + +def main(): + opt = parsr_args() + config = OmegaConf.load(f"{opt.config}") + + # distributed setting + init_dist(opt.launcher) + torch.backends.cudnn.benchmark = True + device = 'cuda' + torch.cuda.set_device(opt.local_rank) + + # dataset + train_dataset = DepthDataset('datasets/laion_depth_meta_v1.txt') + train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset) + train_dataloader = torch.utils.data.DataLoader( + train_dataset, + batch_size=opt.bsize, + shuffle=(train_sampler is None), + num_workers=opt.num_workers, + pin_memory=True, + sampler=train_sampler) + + # stable diffusion + model = load_model_from_config(config, f"{opt.ckpt}").to(device) + + # depth encoder + model_ad = Adapter(cin=3 * 64, channels=[320, 640, 1280, 1280][:4], nums_rb=2, ksize=1, sk=True, use_conv=False).to( + device) + + # to gpus + model_ad = torch.nn.parallel.DistributedDataParallel( + model_ad, + device_ids=[opt.local_rank], + output_device=opt.local_rank) + model = torch.nn.parallel.DistributedDataParallel( + model, + device_ids=[opt.local_rank], + output_device=opt.local_rank) + + # optimizer + params = list(model_ad.parameters()) + optimizer = torch.optim.AdamW(params, lr=config['training']['lr']) + + experiments_root = osp.join('experiments', opt.name) + + # resume state + resume_state = load_resume_state(opt) + if resume_state is None: + mkdir_and_rename(experiments_root) + start_epoch = 0 + current_iter = 0 + # WARNING: should not use get_root_logger in the above codes, including the called functions + # Otherwise the logger will not be properly initialized + log_file = osp.join(experiments_root, f"train_{opt.name}_{get_time_str()}.log") + logger = get_root_logger(logger_name='basicsr', log_level=logging.INFO, log_file=log_file) + logger.info(get_env_info()) + logger.info(dict2str(config)) + else: + # WARNING: should not use get_root_logger in the above codes, including the called functions + # Otherwise the logger will not be properly initialized + log_file = osp.join(experiments_root, f"train_{opt.name}_{get_time_str()}.log") + logger = get_root_logger(logger_name='basicsr', log_level=logging.INFO, log_file=log_file) + logger.info(get_env_info()) + logger.info(dict2str(config)) + resume_optimizers = resume_state['optimizers'] + optimizer.load_state_dict(resume_optimizers) + logger.info(f"Resuming training from epoch: {resume_state['epoch']}, " f"iter: {resume_state['iter']}.") + start_epoch = resume_state['epoch'] + current_iter = resume_state['iter'] + + # copy the yml file to the experiment root + copy_opt_file(opt.config, experiments_root) + + # training + logger.info(f'Start training from epoch: {start_epoch}, iter: {current_iter}') + for epoch in range(start_epoch, opt.epochs): + train_dataloader.sampler.set_epoch(epoch) + # train + for _, data in enumerate(train_dataloader): + current_iter += 1 + with torch.no_grad(): + c = model.module.get_learned_conditioning(data['sentence']) + z = model.module.encode_first_stage((data['im'] * 2 - 1.).to(device)) + z = model.module.get_first_stage_encoding(z) + + optimizer.zero_grad() + model.zero_grad() + features_adapter = model_ad(data['depth'].to(device)) + l_pixel, loss_dict = model(z, c=c, features_adapter=features_adapter) + l_pixel.backward() + optimizer.step() + + if (current_iter + 1) % opt.print_fq == 0: + logger.info(loss_dict) + + # save checkpoint + rank, _ = get_dist_info() + if (rank == 0) and ((current_iter + 1) % config['training']['save_freq'] == 0): + save_filename = f'model_ad_{current_iter + 1}.pth' + save_path = os.path.join(experiments_root, 'models', save_filename) + save_dict = {} + state_dict = model_ad.state_dict() + for key, param in state_dict.items(): + if key.startswith('module.'): # remove unnecessary 'module.' + key = key[7:] + save_dict[key] = param.cpu() + torch.save(save_dict, save_path) + # save state + state = {'epoch': epoch, 'iter': current_iter + 1, 'optimizers': optimizer.state_dict()} + save_filename = f'{current_iter + 1}.state' + save_path = os.path.join(experiments_root, 'training_states', save_filename) + torch.save(state, save_path) + + +if __name__ == '__main__': + main() diff --git a/comparison_models/T2IAdapter/train_seg.py b/comparison_models/T2IAdapter/train_seg.py new file mode 100644 index 0000000..82ed072 --- /dev/null +++ b/comparison_models/T2IAdapter/train_seg.py @@ -0,0 +1,372 @@ +import cv2 +import torch +import os +from basicsr.utils import img2tensor, tensor2img, scandir, get_time_str, get_root_logger, get_env_info +from ldm.data.dataset_coco import dataset_coco_mask_color +import argparse +from ldm.models.diffusion.ddim import DDIMSampler +from ldm.models.diffusion.plms import PLMSSampler +from ldm.models.diffusion.dpm_solver import DPMSolverSampler +from omegaconf import OmegaConf +from ldm.util import instantiate_from_config +from ldm.modules.encoders.adapter import Adapter +from PIL import Image +import numpy as np +import torch.nn as nn +import matplotlib.pyplot as plt +import time +import os.path as osp +from basicsr.utils.options import copy_opt_file, dict2str +import logging +from dist_util import init_dist, master_only, get_bare_model, get_dist_info + +def load_model_from_config(config, ckpt, verbose=False): + print(f"Loading model from {ckpt}") + pl_sd = torch.load(ckpt, map_location="cpu") + if "global_step" in pl_sd: + print(f"Global Step: {pl_sd['global_step']}") + sd = pl_sd["state_dict"] + model = instantiate_from_config(config.model) + m, u = model.load_state_dict(sd, strict=False) + if len(m) > 0 and verbose: + print("missing keys:") + print(m) + if len(u) > 0 and verbose: + print("unexpected keys:") + print(u) + + model.cuda() + model.eval() + return model + +@master_only +def mkdir_and_rename(path): + """mkdirs. If path exists, rename it with timestamp and create a new one. + + Args: + path (str): Folder path. + """ + if osp.exists(path): + new_name = path + '_archived_' + get_time_str() + print(f'Path already exists. Rename it to {new_name}', flush=True) + os.rename(path, new_name) + os.makedirs(path, exist_ok=True) + os.makedirs(osp.join(experiments_root, 'models')) + os.makedirs(osp.join(experiments_root, 'training_states')) + os.makedirs(osp.join(experiments_root, 'visualization')) + +def load_resume_state(opt): + resume_state_path = None + if opt.auto_resume: + state_path = osp.join('experiments', opt.name, 'training_states') + if osp.isdir(state_path): + states = list(scandir(state_path, suffix='state', recursive=False, full_path=False)) + if len(states) != 0: + states = [float(v.split('.state')[0]) for v in states] + resume_state_path = osp.join(state_path, f'{max(states):.0f}.state') + opt.resume_state_path = resume_state_path + # else: + # if opt['path'].get('resume_state'): + # resume_state_path = opt['path']['resume_state'] + + if resume_state_path is None: + resume_state = None + else: + device_id = torch.cuda.current_device() + resume_state = torch.load(resume_state_path, map_location=lambda storage, loc: storage.cuda(device_id)) + # check_resume(opt, resume_state['iter']) + return resume_state + +parser = argparse.ArgumentParser() +parser.add_argument( + "--bsize", + type=int, + default=8, + help="the prompt to render" +) +parser.add_argument( + "--epochs", + type=int, + default=10000, + help="the prompt to render" +) +parser.add_argument( + "--num_workers", + type=int, + default=8, + help="the prompt to render" +) +parser.add_argument( + "--use_shuffle", + type=bool, + default=True, + help="the prompt to render" +) +parser.add_argument( + "--dpm_solver", + action='store_true', + help="use dpm_solver sampling", +) +parser.add_argument( + "--plms", + action='store_true', + help="use plms sampling", +) +parser.add_argument( + "--auto_resume", + action='store_true', + help="use plms sampling", +) +parser.add_argument( + "--ckpt", + type=str, + default="ckp/sd-v1-4.ckpt", + help="path to checkpoint of model", +) +parser.add_argument( + "--config", + type=str, + default="configs/stable-diffusion/train_mask.yaml", + help="path to config which constructs model", +) +parser.add_argument( + "--print_fq", + type=int, + default=100, + help="path to config which constructs model", +) +parser.add_argument( + "--H", + type=int, + default=512, + help="image height, in pixel space", +) +parser.add_argument( + "--W", + type=int, + default=512, + help="image width, in pixel space", +) +parser.add_argument( + "--C", + type=int, + default=4, + help="latent channels", +) +parser.add_argument( + "--f", + type=int, + default=8, + help="downsampling factor", +) +parser.add_argument( + "--ddim_steps", + type=int, + default=50, + help="number of ddim sampling steps", +) +parser.add_argument( + "--n_samples", + type=int, + default=1, + help="how many samples to produce for each given prompt. A.k.a. batch size", +) +parser.add_argument( + "--ddim_eta", + type=float, + default=0.0, + help="ddim eta (eta=0.0 corresponds to deterministic sampling", +) +parser.add_argument( + "--scale", + type=float, + default=7.5, + help="unconditional guidance scale: eps = eps(x, empty) + scale * (eps(x, cond) - eps(x, empty))", +) +parser.add_argument( + "--gpus", + default=[0,1,2,3], + help="gpu idx", +) +parser.add_argument( + '--local_rank', + default=0, + type=int, + help='node rank for distributed training' +) +parser.add_argument( + '--launcher', + default='pytorch', + type=str, + help='node rank for distributed training' +) +opt = parser.parse_args() + +if __name__ == '__main__': + config = OmegaConf.load(f"{opt.config}") + opt.name = config['name'] + + # distributed setting + init_dist(opt.launcher) + torch.backends.cudnn.benchmark = True + device='cuda' + torch.cuda.set_device(opt.local_rank) + + # dataset + path_json_train = 'coco_stuff/mask/annotations/captions_train2017.json' + path_json_val = 'coco_stuff/mask/annotations/captions_val2017.json' + train_dataset = dataset_coco_mask_color(path_json_train, + root_path_im='coco/train2017', + root_path_mask='coco_stuff/mask/train2017_color', + image_size=512 + ) + train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset) + val_dataset = dataset_coco_mask_color(path_json_val, + root_path_im='coco/val2017', + root_path_mask='coco_stuff/mask/val2017_color', + image_size=512 + ) + train_dataloader = torch.utils.data.DataLoader( + train_dataset, + batch_size=opt.bsize, + shuffle=(train_sampler is None), + num_workers=opt.num_workers, + pin_memory=True, + sampler=train_sampler) + val_dataloader = torch.utils.data.DataLoader( + val_dataset, + batch_size=1, + shuffle=False, + num_workers=1, + pin_memory=False) + + # stable diffusion + model = load_model_from_config(config, f"{opt.ckpt}").to(device) + + # sketch encoder + model_ad = Adapter(cin=int(3*64), channels=[320, 640, 1280, 1280][:4], nums_rb=2, ksize=1, sk=True, use_conv=False).to(device) + + + # to gpus + model_ad = torch.nn.parallel.DistributedDataParallel( + model_ad, + device_ids=[opt.local_rank], + output_device=opt.local_rank) + model = torch.nn.parallel.DistributedDataParallel( + model, + device_ids=[opt.local_rank], + output_device=opt.local_rank) + # device_ids=[torch.cuda.current_device()]) + + # optimizer + params = list(model_ad.parameters()) + optimizer = torch.optim.AdamW(params, lr=config['training']['lr']) + + experiments_root = osp.join('experiments', opt.name) + + # resume state + resume_state = load_resume_state(opt) + if resume_state is None: + mkdir_and_rename(experiments_root) + start_epoch = 0 + current_iter = 0 + # WARNING: should not use get_root_logger in the above codes, including the called functions + # Otherwise the logger will not be properly initialized + log_file = osp.join(experiments_root, f"train_{opt.name}_{get_time_str()}.log") + logger = get_root_logger(logger_name='basicsr', log_level=logging.INFO, log_file=log_file) + logger.info(get_env_info()) + logger.info(dict2str(config)) + else: + # WARNING: should not use get_root_logger in the above codes, including the called functions + # Otherwise the logger will not be properly initialized + log_file = osp.join(experiments_root, f"train_{opt.name}_{get_time_str()}.log") + logger = get_root_logger(logger_name='basicsr', log_level=logging.INFO, log_file=log_file) + logger.info(get_env_info()) + logger.info(dict2str(config)) + resume_optimizers = resume_state['optimizers'] + optimizer.load_state_dict(resume_optimizers) + logger.info(f"Resuming training from epoch: {resume_state['epoch']}, " f"iter: {resume_state['iter']}.") + start_epoch = resume_state['epoch'] + current_iter = resume_state['iter'] + + # copy the yml file to the experiment root + copy_opt_file(opt.config, experiments_root) + + # training + logger.info(f'Start training from epoch: {start_epoch}, iter: {current_iter}') + for epoch in range(start_epoch, opt.epochs): + train_dataloader.sampler.set_epoch(epoch) + # train + for _, data in enumerate(train_dataloader): + current_iter += 1 + with torch.no_grad(): + c = model.module.get_learned_conditioning(data['sentence']) + z = model.module.encode_first_stage((data['im']*2-1.).cuda(non_blocking=True)) + z = model.module.get_first_stage_encoding(z) + + mask = data['mask'] + optimizer.zero_grad() + model.zero_grad() + features_adapter = model_ad(mask) + l_pixel, loss_dict = model(z, c=c, features_adapter = features_adapter) + l_pixel.backward() + optimizer.step() + + if (current_iter+1)%opt.print_fq == 0: + logger.info(loss_dict) + + # save checkpoint + rank, _ = get_dist_info() + if (rank==0) and ((current_iter+1)%config['training']['save_freq'] == 0): + save_filename = f'model_ad_{current_iter+1}.pth' + save_path = os.path.join(experiments_root, 'models', save_filename) + save_dict = {} + model_ad_bare = get_bare_model(model_ad) + state_dict = model_ad_bare.state_dict() + for key, param in state_dict.items(): + if key.startswith('module.'): # remove unnecessary 'module.' + key = key[7:] + save_dict[key] = param.cpu() + torch.save(save_dict, save_path) + # save state + state = {'epoch': epoch, 'iter': current_iter+1, 'optimizers': optimizer.state_dict()} + save_filename = f'{current_iter+1}.state' + save_path = os.path.join(experiments_root, 'training_states', save_filename) + torch.save(state, save_path) + + # val + rank, _ = get_dist_info() + if rank==0: + for data in val_dataloader: + with torch.no_grad(): + if opt.dpm_solver: + sampler = DPMSolverSampler(model.module) + elif opt.plms: + sampler = PLMSSampler(model.module) + else: + sampler = DDIMSampler(model.module) + c = model.module.get_learned_conditioning(data['sentence']) + mask = data['mask'] + im_mask = tensor2img(mask) + cv2.imwrite(os.path.join(experiments_root, 'visualization', 'mask_%04d.png'%epoch), im_mask) + features_adapter = model_ad(mask) + shape = [opt.C, opt.H // opt.f, opt.W // opt.f] + samples_ddim, _ = sampler.sample(S=opt.ddim_steps, + conditioning=c, + batch_size=opt.n_samples, + shape=shape, + verbose=False, + unconditional_guidance_scale=opt.scale, + unconditional_conditioning=model.module.get_learned_conditioning(opt.n_samples * [""]), + eta=opt.ddim_eta, + x_T=None, + features_adapter=features_adapter) + x_samples_ddim = model.module.decode_first_stage(samples_ddim) + x_samples_ddim = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0) + x_samples_ddim = x_samples_ddim.cpu().permute(0, 2, 3, 1).numpy() + for id_sample, x_sample in enumerate(x_samples_ddim): + x_sample = 255.*x_sample + img = x_sample.astype(np.uint8) + img = cv2.putText(img.copy(), data['sentence'][0], (10,30), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) + cv2.imwrite(os.path.join(experiments_root, 'visualization', 'sample_e%04d_s%04d.png'%(epoch, id_sample)), img[:,:,::-1]) + break diff --git a/comparison_models/T2IAdapter/train_sketch.py b/comparison_models/T2IAdapter/train_sketch.py new file mode 100644 index 0000000..6b89406 --- /dev/null +++ b/comparison_models/T2IAdapter/train_sketch.py @@ -0,0 +1,399 @@ +import argparse +import logging +import os +import os.path as osp +import time + +import cv2 +import matplotlib.pyplot as plt +import numpy as np +import torch +import torch.nn as nn +from basicsr.utils import (get_env_info, get_root_logger, get_time_str, + img2tensor, scandir, tensor2img) +from basicsr.utils.options import copy_opt_file, dict2str +from omegaconf import OmegaConf +from PIL import Image + +from ldm.data.dataset_coco import dataset_coco_mask_color +from dist_util import get_bare_model, get_dist_info, init_dist, master_only +from ldm.models.diffusion.ddim import DDIMSampler +from ldm.models.diffusion.dpm_solver import DPMSolverSampler +from ldm.models.diffusion.plms import PLMSSampler +from ldm.modules.encoders.adapter import Adapter +from ldm.util import instantiate_from_config +from ldm.modules.extra_condition.model_edge import pidinet + + +def load_model_from_config(config, ckpt, verbose=False): + print(f"Loading model from {ckpt}") + pl_sd = torch.load(ckpt, map_location="cpu") + if "global_step" in pl_sd: + print(f"Global Step: {pl_sd['global_step']}") + sd = pl_sd["state_dict"] + model = instantiate_from_config(config.model) + m, u = model.load_state_dict(sd, strict=False) + if len(m) > 0 and verbose: + print("missing keys:") + print(m) + if len(u) > 0 and verbose: + print("unexpected keys:") + print(u) + + model.cuda() + model.eval() + return model + +@master_only +def mkdir_and_rename(path): + """mkdirs. If path exists, rename it with timestamp and create a new one. + + Args: + path (str): Folder path. + """ + if osp.exists(path): + new_name = path + '_archived_' + get_time_str() + print(f'Path already exists. Rename it to {new_name}', flush=True) + os.rename(path, new_name) + os.makedirs(path, exist_ok=True) + os.makedirs(osp.join(experiments_root, 'models')) + os.makedirs(osp.join(experiments_root, 'training_states')) + os.makedirs(osp.join(experiments_root, 'visualization')) + +def load_resume_state(opt): + resume_state_path = None + if opt.auto_resume: + state_path = osp.join('experiments', opt.name, 'training_states') + if osp.isdir(state_path): + states = list(scandir(state_path, suffix='state', recursive=False, full_path=False)) + if len(states) != 0: + states = [float(v.split('.state')[0]) for v in states] + resume_state_path = osp.join(state_path, f'{max(states):.0f}.state') + opt.resume_state_path = resume_state_path + # else: + # if opt['path'].get('resume_state'): + # resume_state_path = opt['path']['resume_state'] + + if resume_state_path is None: + resume_state = None + else: + device_id = torch.cuda.current_device() + resume_state = torch.load(resume_state_path, map_location=lambda storage, loc: storage.cuda(device_id)) + # check_resume(opt, resume_state['iter']) + return resume_state + +parser = argparse.ArgumentParser() +parser.add_argument( + "--bsize", + type=int, + default=8, + help="the prompt to render" +) +parser.add_argument( + "--epochs", + type=int, + default=10000, + help="the prompt to render" +) +parser.add_argument( + "--num_workers", + type=int, + default=8, + help="the prompt to render" +) +parser.add_argument( + "--use_shuffle", + type=bool, + default=True, + help="the prompt to render" +) +parser.add_argument( + "--dpm_solver", + action='store_true', + help="use dpm_solver sampling", +) +parser.add_argument( + "--plms", + action='store_true', + help="use plms sampling", +) +parser.add_argument( + "--auto_resume", + action='store_true', + help="use plms sampling", +) +parser.add_argument( + "--ckpt", + type=str, + default="models/sd-v1-4.ckpt", + help="path to checkpoint of model", +) +parser.add_argument( + "--config", + type=str, + default="configs/stable-diffusion/train_sketch.yaml", + help="path to config which constructs model", +) +parser.add_argument( + "--print_fq", + type=int, + default=100, + help="path to config which constructs model", +) +parser.add_argument( + "--H", + type=int, + default=512, + help="image height, in pixel space", +) +parser.add_argument( + "--W", + type=int, + default=512, + help="image width, in pixel space", +) +parser.add_argument( + "--C", + type=int, + default=4, + help="latent channels", +) +parser.add_argument( + "--f", + type=int, + default=8, + help="downsampling factor", +) +parser.add_argument( + "--ddim_steps", + type=int, + default=50, + help="number of ddim sampling steps", +) +parser.add_argument( + "--n_samples", + type=int, + default=1, + help="how many samples to produce for each given prompt. A.k.a. batch size", +) +parser.add_argument( + "--ddim_eta", + type=float, + default=0.0, + help="ddim eta (eta=0.0 corresponds to deterministic sampling", +) +parser.add_argument( + "--scale", + type=float, + default=7.5, + help="unconditional guidance scale: eps = eps(x, empty) + scale * (eps(x, cond) - eps(x, empty))", +) +parser.add_argument( + "--gpus", + default=[0,1,2,3], + help="gpu idx", +) +parser.add_argument( + '--local_rank', + default=0, + type=int, + help='node rank for distributed training' +) +parser.add_argument( + '--launcher', + default='pytorch', + type=str, + help='node rank for distributed training' +) +parser.add_argument( + '--l_cond', + default=4, + type=int, + help='number of scales' +) +opt = parser.parse_args() + +if __name__ == '__main__': + config = OmegaConf.load(f"{opt.config}") + opt.name = config['name'] + + # distributed setting + init_dist(opt.launcher) + torch.backends.cudnn.benchmark = True + device='cuda' + torch.cuda.set_device(opt.local_rank) + + # dataset + path_json_train = 'coco_stuff/mask/annotations/captions_train2017.json' + path_json_val = 'coco_stuff/mask/annotations/captions_val2017.json' + train_dataset = dataset_coco_mask_color(path_json_train, + root_path_im='coco/train2017', + root_path_mask='coco_stuff/mask/train2017_color', + image_size=512 + ) + train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset) + val_dataset = dataset_coco_mask_color(path_json_val, + root_path_im='coco/val2017', + root_path_mask='coco_stuff/mask/val2017_color', + image_size=512 + ) + train_dataloader = torch.utils.data.DataLoader( + train_dataset, + batch_size=opt.bsize, + shuffle=(train_sampler is None), + num_workers=opt.num_workers, + pin_memory=True, + sampler=train_sampler) + val_dataloader = torch.utils.data.DataLoader( + val_dataset, + batch_size=1, + shuffle=False, + num_workers=1, + pin_memory=False) + + # edge_generator + net_G = pidinet() + ckp = torch.load('models/table5_pidinet.pth', map_location='cpu')['state_dict'] + net_G.load_state_dict({k.replace('module.',''):v for k, v in ckp.items()}) + net_G.cuda() + + # stable diffusion + model = load_model_from_config(config, f"{opt.ckpt}").to(device) + + # sketch encoder + model_ad = Adapter(channels=[320, 640, 1280, 1280][:4], nums_rb=2, ksize=1, sk=True, use_conv=False).to(device) + + # to gpus + model_ad = torch.nn.parallel.DistributedDataParallel( + model_ad, + device_ids=[opt.local_rank], + output_device=opt.local_rank) + model = torch.nn.parallel.DistributedDataParallel( + model, + device_ids=[opt.local_rank], + output_device=opt.local_rank) + # device_ids=[torch.cuda.current_device()]) + net_G = torch.nn.parallel.DistributedDataParallel( + net_G, + device_ids=[opt.local_rank], + output_device=opt.local_rank) + # device_ids=[torch.cuda.current_device()]) + + # optimizer + params = list(model_ad.parameters()) + optimizer = torch.optim.AdamW(params, lr=config['training']['lr']) + + experiments_root = osp.join('experiments', opt.name) + + # resume state + resume_state = load_resume_state(opt) + if resume_state is None: + mkdir_and_rename(experiments_root) + start_epoch = 0 + current_iter = 0 + # WARNING: should not use get_root_logger in the above codes, including the called functions + # Otherwise the logger will not be properly initialized + log_file = osp.join(experiments_root, f"train_{opt.name}_{get_time_str()}.log") + logger = get_root_logger(logger_name='basicsr', log_level=logging.INFO, log_file=log_file) + logger.info(get_env_info()) + logger.info(dict2str(config)) + else: + # WARNING: should not use get_root_logger in the above codes, including the called functions + # Otherwise the logger will not be properly initialized + log_file = osp.join(experiments_root, f"train_{opt.name}_{get_time_str()}.log") + logger = get_root_logger(logger_name='basicsr', log_level=logging.INFO, log_file=log_file) + logger.info(get_env_info()) + logger.info(dict2str(config)) + resume_optimizers = resume_state['optimizers'] + optimizer.load_state_dict(resume_optimizers) + logger.info(f"Resuming training from epoch: {resume_state['epoch']}, " f"iter: {resume_state['iter']}.") + start_epoch = resume_state['epoch'] + current_iter = resume_state['iter'] + + # copy the yml file to the experiment root + copy_opt_file(opt.config, experiments_root) + + + # training + logger.info(f'Start training from epoch: {start_epoch}, iter: {current_iter}') + for epoch in range(start_epoch, opt.epochs): + train_dataloader.sampler.set_epoch(epoch) + # train + for _, data in enumerate(train_dataloader): + current_iter += 1 + with torch.no_grad(): + edge = net_G(data['im'].cuda(non_blocking=True))[-1] + edge = edge>0.5 + edge = edge.float() + c = model.module.get_learned_conditioning(data['sentence']) + z = model.module.encode_first_stage((data['im']*2-1.).cuda(non_blocking=True)) + z = model.module.get_first_stage_encoding(z) + + optimizer.zero_grad() + model.zero_grad() + features_adapter = model_ad(edge) + l_pixel, loss_dict = model(z, c=c, features_adapter = features_adapter) + l_pixel.backward() + optimizer.step() + + if (current_iter+1)%opt.print_fq == 0: + logger.info(loss_dict) + + # save checkpoint + rank, _ = get_dist_info() + if (rank==0) and ((current_iter+1)%config['training']['save_freq'] == 0): + save_filename = f'model_ad_{current_iter+1}.pth' + save_path = os.path.join(experiments_root, 'models', save_filename) + save_dict = {} + model_ad_bare = get_bare_model(model_ad) + state_dict = model_ad_bare.state_dict() + for key, param in state_dict.items(): + if key.startswith('module.'): # remove unnecessary 'module.' + key = key[7:] + save_dict[key] = param.cpu() + torch.save(save_dict, save_path) + # save state + state = {'epoch': epoch, 'iter': current_iter+1, 'optimizers': optimizer.state_dict()} + save_filename = f'{current_iter+1}.state' + save_path = os.path.join(experiments_root, 'training_states', save_filename) + torch.save(state, save_path) + + # val + rank, _ = get_dist_info() + if rank==0: + for data in val_dataloader: + with torch.no_grad(): + if opt.dpm_solver: + sampler = DPMSolverSampler(model.module) + elif opt.plms: + sampler = PLMSSampler(model.module) + else: + sampler = DDIMSampler(model.module) + print(data['im'].shape) + c = model.module.get_learned_conditioning(data['sentence']) + edge = net_G(data['im'].cuda(non_blocking=True))[-1] + edge = edge>0.5 + edge = edge.float() + im_edge = tensor2img(edge) + cv2.imwrite(os.path.join(experiments_root, 'visualization', 'edge_%04d.png'%epoch), im_edge) + features_adapter = model_ad(edge) + shape = [opt.C, opt.H // opt.f, opt.W // opt.f] + samples_ddim, _ = sampler.sample(S=opt.ddim_steps, + conditioning=c, + batch_size=opt.n_samples, + shape=shape, + verbose=False, + unconditional_guidance_scale=opt.scale, + unconditional_conditioning=model.module.get_learned_conditioning(opt.n_samples * [""]), + eta=opt.ddim_eta, + x_T=None, + features_adapter=features_adapter) + x_samples_ddim = model.module.decode_first_stage(samples_ddim) + x_samples_ddim = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0) + x_samples_ddim = x_samples_ddim.cpu().permute(0, 2, 3, 1).numpy() + for id_sample, x_sample in enumerate(x_samples_ddim): + x_sample = 255.*x_sample + img = x_sample.astype(np.uint8) + img = cv2.putText(img.copy(), data['sentence'][0], (10,30), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) + cv2.imwrite(os.path.join(experiments_root, 'visualization', 'sample_e%04d_s%04d.png'%(epoch, id_sample)), img[:,:,::-1]) + break diff --git a/configs/humansd/humansd-inference.yaml b/configs/humansd/humansd-inference.yaml index 238c551..03654ce 100644 --- a/configs/humansd/humansd-inference.yaml +++ b/configs/humansd/humansd-inference.yaml @@ -22,7 +22,7 @@ model: target: ldm.modules.diffusionmodules.openaimodel.UNetModel params: use_checkpoint: True - use_fp16: True + use_fp16: False image_size: 32 # unused in_channels: 8 out_channels: 4 diff --git a/requirements.txt b/requirements.txt index 23b4acb..294a736 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,4 +16,5 @@ gradio==3.13.2 kornia==0.6 invisible-watermark>=0.1.5 streamlit-drawable-canvas==0.8.0 +safetensors==0.3.3 -e . diff --git a/scripts/gradio/pose2img.py b/scripts/gradio/pose2img.py index 634b686..3c807bc 100644 --- a/scripts/gradio/pose2img.py +++ b/scripts/gradio/pose2img.py @@ -506,11 +506,6 @@ def __init__(self) -> None: run_button.click(fn=predict, inputs=[ comparison_model, load_image_type, input_image, prompt, added_prompt, ddim_steps, detection_thresh, num_samples, scale, seed, eta, strength, negative_prompt], outputs=[gallery,controlnet_gallery,t2i_gallery]) - - - - - block.launch(share=True) if __name__=="__main__": diff --git a/scripts/pose2img.py b/scripts/pose2img.py new file mode 100644 index 0000000..4c41cf4 --- /dev/null +++ b/scripts/pose2img.py @@ -0,0 +1,552 @@ +import torch +from pytorch_lightning import seed_everything + +import gradio as gr + +import argparse +import sys +import numpy as np +import math +from einops import repeat, rearrange +import os +import time + +import cv2 +import seaborn as sns +from PIL import Image + + +# HumanSD +from omegaconf import OmegaConf +from ldm.util import instantiate_from_config +from ldm.models.diffusion.ddim import DDIMSampler as humansd_DDIMSampler +from imwatermark import WatermarkEncoder +from scripts.txt2img import put_watermark + +# pose detector +from mmpose.apis import inference_bottom_up_pose_model, init_pose_model + + +DEVICE="cuda" if torch.cuda.is_available() else "cpu" +IMAGE_RESOLUTION=512 + +def resize_image(input_image, resolution): + H, W, C = input_image.shape + H = float(H) + W = float(W) + k = float(resolution) / min(H, W) + H *= k + W *= k + H = int(np.round(H / 64.0)) * 64 + W = int(np.round(W / 64.0)) * 64 + img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4 if k > 1 else cv2.INTER_AREA) + return img + +def draw_humansd_skeleton(image, present_pose,mmpose_detection_thresh): + humansd_skeleton=[ + [0,0,1], + [1,0,2], + [2,1,3], + [3,2,4], + [4,3,5], + [5,4,6], + [6,5,7], + [7,6,8], + [8,7,9], + [9,8,10], + [10,5,11], + [11,6,12], + [12,11,13], + [13,12,14], + [14,13,15], + [15,14,16], + ] + humansd_skeleton_width=10 + humansd_color=sns.color_palette("hls", len(humansd_skeleton)) + + def plot_kpts(img_draw, kpts, color, edgs,width): + for idx, kpta, kptb in edgs: + if kpts[kpta,2]>mmpose_detection_thresh and \ + kpts[kptb,2]>mmpose_detection_thresh : + line_color = tuple([int(255*color_i) for color_i in color[idx]]) + + cv2.line(img_draw, (int(kpts[kpta,0]),int(kpts[kpta,1])), (int(kpts[kptb,0]),int(kpts[kptb,1])), line_color,width) + cv2.circle(img_draw, (int(kpts[kpta,0]),int(kpts[kpta,1])), width//2, line_color, -1) + cv2.circle(img_draw, (int(kpts[kptb,0]),int(kpts[kptb,1])), width//2, line_color, -1) + + + pose_image = np.zeros_like(image) + for person_i in range(len(present_pose)): + if np.sum(present_pose[person_i]["keypoints"])>0: + plot_kpts(pose_image, present_pose[person_i]["keypoints"],humansd_color,humansd_skeleton,humansd_skeleton_width) + + return pose_image + + +def draw_controlnet_skeleton(image, pose,mmpose_detection_thresh): + H, W, C = image.shape + canvas = np.zeros((H, W, C)) + + for pose_i in range(len(pose)): + present_pose=pose[pose_i]["keypoints"] + candidate=[ + [present_pose[0,0],present_pose[0,1],present_pose[0,2],0], + [(present_pose[6,0]+present_pose[5,0])/2,(present_pose[6,1]+present_pose[5,1])/2,(present_pose[6,2]+present_pose[5,2])/2,1] if present_pose[6,2]>mmpose_detection_thresh and present_pose[5,2]>mmpose_detection_thresh else [-1,-1,0,1], + [present_pose[6,0],present_pose[6,1],present_pose[6,2],2], + [present_pose[8,0],present_pose[8,1],present_pose[8,2],3], + [present_pose[10,0],present_pose[10,1],present_pose[10,2],4], + [present_pose[5,0],present_pose[5,1],present_pose[5,2],5], + [present_pose[7,0],present_pose[7,1],present_pose[7,2],6], + [present_pose[9,0],present_pose[9,1],present_pose[9,2],7], + [present_pose[12,0],present_pose[12,1],present_pose[12,2],8], + [present_pose[14,0],present_pose[14,1],present_pose[14,2],9], + [present_pose[16,0],present_pose[16,1],present_pose[16,2],10], + [present_pose[11,0],present_pose[11,1],present_pose[11,2],11], + [present_pose[13,0],present_pose[13,1],present_pose[13,2],12], + [present_pose[15,0],present_pose[15,1],present_pose[15,2],13], + [present_pose[2,0],present_pose[2,1],present_pose[2,2],14], + [present_pose[1,0],present_pose[1,1],present_pose[1,2],15], + [present_pose[4,0],present_pose[4,1],present_pose[4,2],16], + [present_pose[3,0],present_pose[3,1],present_pose[3,2],17], + ] + stickwidth = 4 + limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \ + [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \ + [1, 16], [16, 18], [3, 17], [6, 18]] + + colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \ + [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \ + [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]] + + for i in range(17): + if candidate[limbSeq[i][0]-1][2]>mmpose_detection_thresh and candidate[limbSeq[i][1]-1][2]>mmpose_detection_thresh: + Y=[candidate[limbSeq[i][1]-1][0],candidate[limbSeq[i][0]-1][0]] + X=[candidate[limbSeq[i][1]-1][1],candidate[limbSeq[i][0]-1][1]] + + mX = np.mean(X) + mY = np.mean(Y) + length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5 + angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1])) + polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1) + cur_canvas = canvas.copy() + cv2.fillConvexPoly(cur_canvas, polygon, colors[i]) + canvas = cv2.addWeighted(canvas, 0.4, cur_canvas, 0.6, 0) + + for i in range(18): + if candidate[i][2]>mmpose_detection_thresh: + x, y = candidate[i][0:2] + cv2.circle(canvas, (int(x), int(y)), 4, colors[i], thickness=-1) + + return canvas + +def make_batch_sd( + image, + pose_image, + txt, + device, + num_samples=1, +): + batch={ + "jpg":(torch.from_numpy(image).to(dtype=torch.float32) / 255 *2 - 1.0), + "pose_img": (torch.from_numpy(pose_image).to(dtype=torch.float32) / 255 *2 - 1.0), + "txt": num_samples * [txt], + } + + batch["pose_img"] = rearrange(batch["pose_img"], 'h w c -> 1 c h w') + batch["pose_img"] = repeat(batch["pose_img"].to(device=device), + "1 ... -> n ...", n=num_samples) + + batch["jpg"] = rearrange(batch["jpg"], 'h w c -> 1 c h w') + batch["jpg"] = repeat(batch["jpg"].to(device=device), + "1 ... -> n ...", n=num_samples) + return batch + + +def paint_humansd(humansd_sampler, image, pose_image, prompt, t_enc, seed, scale, device, num_samples=1, callback=None, + do_full_sample=False,negative_prompt="longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality"): + model = humansd_sampler.model + seed_everything(seed) + + print("Creating invisible watermark encoder (see https://github.com/ShieldMnt/invisible-watermark)...") + wm = "HumanSD" + wm_encoder = WatermarkEncoder() + wm_encoder.set_watermark('bytes', wm.encode('utf-8')) + + with torch.no_grad(): + batch = make_batch_sd( + image,pose_image, txt=prompt, device=device, num_samples=num_samples) + z = model.get_first_stage_encoding(model.encode_first_stage( + batch[model.first_stage_key])) # move to latent space + c = model.cond_stage_model.encode(batch["txt"]) + c_cat = list() + for ck in model.concat_keys: + cc = batch[ck] + if len(cc.shape) == 3: + cc = cc[..., None] + cc = cc.to(memory_format=torch.contiguous_format).float() + cc = model.get_first_stage_encoding(model.encode_first_stage(cc)) + c_cat.append(cc) + + c_cat = torch.cat(c_cat, dim=1) + # cond + cond = {"c_concat": [c_cat], "c_crossattn": [c]} + + # uncond cond + uc_cross = model.get_unconditional_conditioning(num_samples, negative_prompt) + uc_full = {"c_concat": [c_cat], "c_crossattn": [uc_cross]} + if not do_full_sample: + # encode (scaled latent) + z_enc = humansd_sampler.stochastic_encode( + z, torch.tensor([t_enc] * num_samples).to(model.device)) + else: + z_enc = torch.randn_like(z) + # decode it + samples = humansd_sampler.decode(z_enc, cond, t_enc, unconditional_guidance_scale=scale, + unconditional_conditioning=uc_full, callback=callback) + x_samples_ddim = model.decode_first_stage(samples) + result = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0) + result = result.cpu().numpy().transpose(0, 2, 3, 1) * 255 + return [pose_image.astype(np.uint8)] + [put_watermark(Image.fromarray(img.astype(np.uint8)), wm_encoder) for img in result] + + + + +def paint_controlnet(controlnet_sampler, pose_image,prompt, a_prompt, n_prompt, num_samples, ddim_steps, guess_mode, strength, scale, seed, eta): + + with torch.no_grad(): + H, W, C = pose_image.shape + + control = torch.from_numpy(pose_image.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = rearrange(control, 'b h w c -> b c h w').clone() + + seed_everything(seed) + + cond = {"c_concat": [control], "c_crossattn": [controlnet_model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [controlnet_model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + strength=1 + controlnet_model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) + # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + + samples, intermediates = controlnet_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + + x_samples = controlnet_model.decode_first_stage(samples) + x_samples = (rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [Image.fromarray(x_samples[i].astype(np.uint8)) for i in range(num_samples)] + return [pose_image.astype(np.uint8)] + results + +def paint_t2i(t2i_pose_image,num_samples,prompt,added_prompt,negative_prompt,ddim_steps,scale,seed): + from comparison_models.T2IAdapter.ldm.inference_base import diffusion_inference as t2i_diffusion_inference + from comparison_models.T2IAdapter.ldm.modules.extra_condition.api import get_adapter_feature as get_t2i_adapter_feature + + H, W, C = t2i_pose_image.shape + + t2i_control = torch.from_numpy(t2i_pose_image.copy()).float().cuda() / 255.0 + t2i_control = torch.stack([t2i_control for _ in range(1)], dim=0) + t2i_control = rearrange(t2i_control, 'b h w c -> b c h w').clone() + + t2i_opt.prompt=prompt + ", "+added_prompt + t2i_opt.neg_prompt=negative_prompt + t2i_opt.steps=ddim_steps + t2i_opt.max_resolution=IMAGE_RESOLUTION*IMAGE_RESOLUTION + t2i_opt.scale=scale + t2i_opt.seed=seed + t2i_opt.n_samples=num_samples + t2i_opt.style_cond_tau=1.0 + t2i_opt.cond_tau=1.0 + t2i_opt.H=H + t2i_opt.W=W + + result= [t2i_pose_image.astype(np.uint8)] + + for idx in range(t2i_opt.n_samples): + + adapter_features, append_to_context = get_t2i_adapter_feature(t2i_control, t2i_adapter) + + x_samples = t2i_diffusion_inference(t2i_opt, t2i_sd_model, t2i_sampler, adapter_features, append_to_context) + x_samples = (rearrange(x_samples, 'b c h w -> b h w c') * 255.).cpu().numpy().clip(0, 255).astype(np.uint8) + + x_samples = [Image.fromarray(x_samples[i].astype(np.uint8)) for i in range(1)] + result= result + x_samples + + return result + +def predict(args): + image = np.zeros((args.image_H, args.image_W, 3)) + image = resize_image(image,IMAGE_RESOLUTION) # resize to integer multiple of 32 + + humansd_result=[] + controlnet_result=[] + t2i_result=[] + + mmpose_results=np.load(args.pose_file,allow_pickle=True)["arr_0"] + mmpose_filtered_results=[] + for mmpose_result in mmpose_results: + if mmpose_result["score"]>args.detection_thresh: + mmpose_filtered_results.append(mmpose_result) + + humansd_pose_image=draw_humansd_skeleton(image,mmpose_filtered_results,args.detection_thresh) + if args.controlnet: + controlnet_pose_image=draw_controlnet_skeleton(image,mmpose_filtered_results,args.detection_thresh) + if args.t2i: + t2i_pose_image=draw_controlnet_skeleton(image,mmpose_filtered_results,args.detection_thresh) + + # humansd + humansd_sampler.make_schedule(args.ddim_steps, ddim_eta=args.eta, verbose=True) + do_full_sample = args.strength == 1. + + t_enc = min(int(args.strength * args.ddim_steps), args.ddim_steps-1) + humansd_result = paint_humansd( + humansd_sampler=humansd_sampler, + image=image, + pose_image=humansd_pose_image, + prompt=args.prompt + ", "+args.added_prompt, + t_enc=t_enc, + seed=args.seed, + scale=args.scale, + num_samples=args.num_samples, + callback=None, + do_full_sample=do_full_sample, + device=DEVICE, + negative_prompt=args.negative_prompt + ) + + if args.controlnet: + controlnet_result = paint_controlnet( + controlnet_sampler=controlnet_sampler, + pose_image=controlnet_pose_image, + prompt=args.prompt, + a_prompt=args.added_prompt, + n_prompt=args.negative_prompt, + num_samples=args.num_samples, + ddim_steps=args.ddim_steps, + guess_mode=False, + strength=args.strength, + scale=args.scale, + seed=args.seed, + eta=args.eta) + + if args.t2i: + t2i_result=paint_t2i( + t2i_pose_image=t2i_pose_image, + num_samples=args.num_samples, + prompt=args.prompt, + added_prompt=args.added_prompt, + negative_prompt=args.negative_prompt, + ddim_steps=args.ddim_steps, + scale=args.scale, + seed=args.seed) + + print(f"Images are save in {args.save_path}") + if not os.path.exists(args.save_path): + os.makedirs(args.save_path) + + + save_name=time.strftime("%Y-%m-%d-%H_%M_%S.jpg", time.localtime(time.time())) + if args.controlnet: + if args.t2i: + save_image=np.concatenate((np.concatenate(humansd_result,0),np.concatenate(controlnet_result,0),np.concatenate(t2i_result,0)),1) + else: + save_image=np.concatenate((np.concatenate(humansd_result,0),np.concatenate(controlnet_result,0)),1) + elif args.t2i: + save_image=np.concatenate((np.concatenate(humansd_result,0),np.concatenate(t2i_result,0)),1) + else: + save_image=np.concatenate(humansd_result,0) + + save_image=save_image[...,[2,1,0]] + + cv2.imwrite(os.path.join(args.save_path,save_name),save_image) + + return humansd_result,controlnet_result,t2i_result + + + + +def main(args): + if args.controlnet: + sys.path.append("comparison_models/ControlNet") + from comparison_models.ControlNet.cldm.model import create_model as create_controlnet_model , load_state_dict as load_controlnet_state_dict + from comparison_models.ControlNet.cldm.ddim_hacked import DDIMSampler as controlnet_DDIMSampler + CONTROLNET_MODEL_CONFIG='comparison_models/ControlNet/models/cldm_v15.yaml' + CONTROLNET_BASE_MODEL_CKPT='humansd_data/checkpoints/control_sd15_openpose.pth' + CONTROLNET_MODEL_CKPT='humansd_data/checkpoints/v1-5-pruned.ckpt' + global controlnet_sampler + global controlnet_model + controlnet_model = create_controlnet_model(CONTROLNET_MODEL_CONFIG).cpu() + controlnet_model.load_state_dict(load_controlnet_state_dict(CONTROLNET_BASE_MODEL_CKPT,location=DEVICE), strict=False) + controlnet_model.load_state_dict(load_controlnet_state_dict(CONTROLNET_MODEL_CKPT,location=DEVICE), strict=False) + controlnet_model = controlnet_model.to(DEVICE) + controlnet_sampler = controlnet_DDIMSampler(controlnet_model) + if args.t2i: + # t2i + sys.path.append("comparison_models/T2IAdapter") + from comparison_models.T2IAdapter.ldm.inference_base import get_adapters as get_t2i_adapters, get_sd_models as get_t2i_sd_models + from comparison_models.T2IAdapter.ldm.modules.extra_condition.api import ExtraCondition as t2i_ExtraCondition + + global t2i_sd_model + global t2i_sampler + global t2i_adapter + global t2i_opt + class T2I_OPT(): + def __init__(self) -> None: + self.which_cond="openpose" + self.sd_ckpt="humansd_data/checkpoints/v1-5-pruned.ckpt" + self.adapter_ckpt="humansd_data/checkpoints/t2iadapter_openpose_sd14v1.pth" + self.config="comparison_models/T2IAdapter/configs/stable-diffusion/sd-v1-inference.yaml" + self.vae_ckpt=None + self.device=DEVICE + self.cond_weight=1.0 + self.sampler='ddim' + self.C=4 + self.f=8 + + t2i_opt=T2I_OPT() + + t2i_sd_model, t2i_sampler = get_t2i_sd_models(t2i_opt) + t2i_adapter = get_t2i_adapters(t2i_opt, getattr(t2i_ExtraCondition, t2i_opt.which_cond)) + + global humansd_sampler + humansd_config=OmegaConf.load(args.humansd_config) + humansd_model = instantiate_from_config(humansd_config.model) + humansd_model.load_state_dict(torch.load(args.humansd_checkpoint)["state_dict"], strict=False) + humansd_model = humansd_model.to(DEVICE) + humansd_sampler = humansd_DDIMSampler(humansd_model) + + global mmpose_model + mmpose_model=init_pose_model(args.mmpose_config, args.mmpose_checkpoint, device=DEVICE) + + fn=predict(args) + + + + +if __name__=="__main__": + print("You are running the demo of HumanSD ........") + parser = argparse.ArgumentParser() + parser.add_argument( + "--prompt", + type=str, + help="the prompt" + ) + parser.add_argument( + "--negative_prompt", + type=str, + default="longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality", + help="the negative prompt" + ) + parser.add_argument( + "--added_prompt", + type=str, + default="detailed background, best quality, extremely detailed", + help="the added prompt" + ) + parser.add_argument( + "--pose_file", + type=str, + help="the pose npz file in MMPose format" + ) + parser.add_argument( + "--save_path", + type=str, + default="outputs/visualization_results", + help="the image save path" + ) + parser.add_argument( + "--image_H", + type=int, + default=512, + help="number of samples" + ) + parser.add_argument( + "--image_W", + type=int, + default=512, + help="number of samples" + ) + parser.add_argument( + "--num_samples", + type=int, + default=1, + help="number of samples" + ) + parser.add_argument( + "--ddim_steps", + type=int, + default=50, + help="ddim step number" + ) + parser.add_argument( + "--detection_thresh", + type=float, + default=0.05, + help="detection threshold" + ) + parser.add_argument( + "--scale", + type=float, + default=10.0, + help="guidance scale" + ) + parser.add_argument( + "--strength", + type=float, + default=1.0, + help="pose strength" + ) + parser.add_argument( + "--seed", + type=int, + default=1234, + help="random seed" + ) + parser.add_argument( + "--eta", + type=float, + default=0.0, + help="eta" + ) + parser.add_argument( + "--humansd_config", + type=str, + default="configs/humansd/humansd-inference.yaml", + help="the config file of HumanSD" + ) + parser.add_argument( + "--humansd_checkpoint", + type=str, + default="humansd_data/checkpoints/humansd-v1.ckpt", + help="the checkpoint of HumanSD" + ) + parser.add_argument( + "--mmpose_config", + type=str, + default="humansd_data/models/mmpose/configs/body/2d_kpt_sview_rgb_img/associative_embedding/coco/higherhrnet_w48_coco_512x512_udp.py", + help="the config file of human pose estimator" + ) + parser.add_argument( + "--mmpose_checkpoint", + type=str, + default="humansd_data/checkpoints/higherhrnet_w48_coco_512x512_udp.pth", + help="the checkpoint of human pose estimator", + ) + + parser.add_argument( + "--controlnet", + action='store_true', + help="generate images from ControlNet", + ) + parser.add_argument( + "--t2i", + action='store_true', + help="generate images from T2I-Adapter", + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/utils/download_data.py b/utils/download_data.py new file mode 100644 index 0000000..80076ac --- /dev/null +++ b/utils/download_data.py @@ -0,0 +1,35 @@ +from pandas import read_parquet +import os +import requests + +BASE_DIR="humansd_data/datasets/LaionAesthetics" + +for idx in range(0,287): + data = read_parquet(f"{BASE_DIR}/images/{str(idx).zfill(5)}.parquet") + + for i in range(len(data)): + try: + url=data["url"][i] + key=data["key"][i] + image_path=os.path.join(BASE_DIR,"images",str(idx).zfill(5),key)+'.jpg' + + if not os.path.exists(os.path.dirname(image_path)): + os.makedirs(os.path.dirname(image_path)) + + if os.path.exists(image_path): + print(f"Image {key}.jpg already exists!") + continue + + r = requests.get(url) + + if r.status_code!=200: + print(f"Error! Unable to download image {key}.jpg") + continue + + with open(image_path, 'wb') as f: + f.write(r.content) + + print(f"Sucessfully download image {key}.jpg") + except: + print(f"Error! Unable to download image {key}.jpg") + continue \ No newline at end of file

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 z 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%0AB@BHT&cQkbmz7PVNBb1+jy)pbF4`eQ+n) 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%0Dm&v@4taQGLT-zh3S`AeK1 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,GZiO*k^1+ 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 b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [255 - detected_map] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Canny Edge Maps") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + low_threshold = gr.Slider(label="Canny low threshold", minimum=1, maximum=255, value=100, step=1) + high_threshold = gr.Slider(label="Canny high threshold", minimum=1, maximum=255, value=200, step=1) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, ddim_steps, guess_mode, strength, scale, seed, eta, low_threshold, high_threshold] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_depth2image.py b/comparison_models/ControlNet/gradio_depth2image.py new file mode 100644 index 0000000..ee67899 --- /dev/null +++ b/comparison_models/ControlNet/gradio_depth2image.py @@ -0,0 +1,98 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from annotator.midas import MidasDetector +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +apply_midas = MidasDetector() + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_depth.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta): + with torch.no_grad(): + input_image = HWC3(input_image) + detected_map, _ = apply_midas(resize_image(input_image, detect_resolution)) + detected_map = HWC3(detected_map) + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [detected_map] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Depth Maps") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + detect_resolution = gr.Slider(label="Depth Resolution", minimum=128, maximum=1024, value=384, step=1) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_fake_scribble2image.py b/comparison_models/ControlNet/gradio_fake_scribble2image.py new file mode 100644 index 0000000..a7cd375 --- /dev/null +++ b/comparison_models/ControlNet/gradio_fake_scribble2image.py @@ -0,0 +1,102 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from annotator.hed import HEDdetector, nms +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +apply_hed = HEDdetector() + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_scribble.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta): + with torch.no_grad(): + input_image = HWC3(input_image) + detected_map = apply_hed(resize_image(input_image, detect_resolution)) + detected_map = HWC3(detected_map) + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + detected_map = nms(detected_map, 127, 3.0) + detected_map = cv2.GaussianBlur(detected_map, (0, 0), 3.0) + detected_map[detected_map > 4] = 255 + detected_map[detected_map < 255] = 0 + + control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [255 - detected_map] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Fake Scribble Maps") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + detect_resolution = gr.Slider(label="HED Resolution", minimum=128, maximum=1024, value=512, step=1) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_hed2image.py b/comparison_models/ControlNet/gradio_hed2image.py new file mode 100644 index 0000000..1ceff67 --- /dev/null +++ b/comparison_models/ControlNet/gradio_hed2image.py @@ -0,0 +1,98 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from annotator.hed import HEDdetector +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +apply_hed = HEDdetector() + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_hed.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta): + with torch.no_grad(): + input_image = HWC3(input_image) + detected_map = apply_hed(resize_image(input_image, detect_resolution)) + detected_map = HWC3(detected_map) + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [detected_map] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with HED Maps") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + detect_resolution = gr.Slider(label="HED Resolution", minimum=128, maximum=1024, value=512, step=1) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_hough2image.py b/comparison_models/ControlNet/gradio_hough2image.py new file mode 100644 index 0000000..6095eeb --- /dev/null +++ b/comparison_models/ControlNet/gradio_hough2image.py @@ -0,0 +1,100 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from annotator.mlsd import MLSDdetector +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +apply_mlsd = MLSDdetector() + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_mlsd.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta, value_threshold, distance_threshold): + with torch.no_grad(): + input_image = HWC3(input_image) + detected_map = apply_mlsd(resize_image(input_image, detect_resolution), value_threshold, distance_threshold) + detected_map = HWC3(detected_map) + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_NEAREST) + + control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [255 - cv2.dilate(detected_map, np.ones(shape=(3, 3), dtype=np.uint8), iterations=1)] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Hough Line Maps") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + detect_resolution = gr.Slider(label="Hough Resolution", minimum=128, maximum=1024, value=512, step=1) + value_threshold = gr.Slider(label="Hough value threshold (MLSD)", minimum=0.01, maximum=2.0, value=0.1, step=0.01) + distance_threshold = gr.Slider(label="Hough distance threshold (MLSD)", minimum=0.01, maximum=20.0, value=0.1, step=0.01) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta, value_threshold, distance_threshold] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_normal2image.py b/comparison_models/ControlNet/gradio_normal2image.py new file mode 100644 index 0000000..30aea2f --- /dev/null +++ b/comparison_models/ControlNet/gradio_normal2image.py @@ -0,0 +1,99 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from annotator.midas import MidasDetector +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +apply_midas = MidasDetector() + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_normal.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta, bg_threshold): + with torch.no_grad(): + input_image = HWC3(input_image) + _, detected_map = apply_midas(resize_image(input_image, detect_resolution), bg_th=bg_threshold) + detected_map = HWC3(detected_map) + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + control = torch.from_numpy(detected_map[:, :, ::-1].copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [detected_map] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Normal Maps") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + detect_resolution = gr.Slider(label="Normal Resolution", minimum=128, maximum=1024, value=384, step=1) + bg_threshold = gr.Slider(label="Normal background threshold", minimum=0.0, maximum=1.0, value=0.4, step=0.01) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta, bg_threshold] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_pose2image.py b/comparison_models/ControlNet/gradio_pose2image.py new file mode 100644 index 0000000..700973b --- /dev/null +++ b/comparison_models/ControlNet/gradio_pose2image.py @@ -0,0 +1,98 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from annotator.openpose import OpenposeDetector +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +apply_openpose = OpenposeDetector() + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_openpose.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta): + with torch.no_grad(): + input_image = HWC3(input_image) + detected_map, _ = apply_openpose(resize_image(input_image, detect_resolution)) + detected_map = HWC3(detected_map) + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_NEAREST) + + control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [detected_map] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Human Pose") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + detect_resolution = gr.Slider(label="OpenPose Resolution", minimum=128, maximum=1024, value=512, step=1) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_scribble2image.py b/comparison_models/ControlNet/gradio_scribble2image.py new file mode 100644 index 0000000..8abbc25 --- /dev/null +++ b/comparison_models/ControlNet/gradio_scribble2image.py @@ -0,0 +1,92 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_scribble.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, ddim_steps, guess_mode, strength, scale, seed, eta): + with torch.no_grad(): + img = resize_image(HWC3(input_image), image_resolution) + H, W, C = img.shape + + detected_map = np.zeros_like(img, dtype=np.uint8) + detected_map[np.min(img, axis=2) < 127] = 255 + + control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [255 - detected_map] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Scribble Maps") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, ddim_steps, guess_mode, strength, scale, seed, eta] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_scribble2image_interactive.py b/comparison_models/ControlNet/gradio_scribble2image_interactive.py new file mode 100644 index 0000000..7308bcc --- /dev/null +++ b/comparison_models/ControlNet/gradio_scribble2image_interactive.py @@ -0,0 +1,102 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_scribble.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, ddim_steps, guess_mode, strength, scale, seed, eta): + with torch.no_grad(): + img = resize_image(HWC3(input_image['mask'][:, :, 0]), image_resolution) + H, W, C = img.shape + + detected_map = np.zeros_like(img, dtype=np.uint8) + detected_map[np.min(img, axis=2) > 127] = 255 + + control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [255 - detected_map] + results + + +def create_canvas(w, h): + return np.zeros(shape=(h, w, 3), dtype=np.uint8) + 255 + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Interactive Scribbles") + with gr.Row(): + with gr.Column(): + canvas_width = gr.Slider(label="Canvas Width", minimum=256, maximum=1024, value=512, step=1) + canvas_height = gr.Slider(label="Canvas Height", minimum=256, maximum=1024, value=512, step=1) + create_button = gr.Button(label="Start", value='Open drawing canvas!') + input_image = gr.Image(source='upload', type='numpy', tool='sketch') + gr.Markdown(value='Do not forget to change your brush width to make it thinner. (Gradio do not allow developers to set brush width so you need to do it manually.) ' + 'Just click on the small pencil icon in the upper right corner of the above block.') + create_button.click(fn=create_canvas, inputs=[canvas_width, canvas_height], outputs=[input_image]) + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, ddim_steps, guess_mode, strength, scale, seed, eta] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/gradio_seg2image.py b/comparison_models/ControlNet/gradio_seg2image.py new file mode 100644 index 0000000..c3854dc --- /dev/null +++ b/comparison_models/ControlNet/gradio_seg2image.py @@ -0,0 +1,97 @@ +from share import * +import config + +import cv2 +import einops +import gradio as gr +import numpy as np +import torch +import random + +from pytorch_lightning import seed_everything +from annotator.util import resize_image, HWC3 +from annotator.uniformer import UniformerDetector +from cldm.model import create_model, load_state_dict +from cldm.ddim_hacked import DDIMSampler + + +apply_uniformer = UniformerDetector() + +model = create_model('./models/cldm_v15.yaml').cpu() +model.load_state_dict(load_state_dict('./models/control_sd15_seg.pth', location='cuda')) +model = model.cuda() +ddim_sampler = DDIMSampler(model) + + +def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta): + with torch.no_grad(): + input_image = HWC3(input_image) + detected_map = apply_uniformer(resize_image(input_image, detect_resolution)) + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_NEAREST) + + control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0 + control = torch.stack([control for _ in range(num_samples)], dim=0) + control = einops.rearrange(control, 'b h w c -> b c h w').clone() + + if seed == -1: + seed = random.randint(0, 65535) + seed_everything(seed) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + cond = {"c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + ', ' + a_prompt] * num_samples)]} + un_cond = {"c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]} + shape = (4, H // 8, W // 8) + + if config.save_memory: + model.low_vram_shift(is_diffusing=True) + + model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13) # Magic number. IDK why. Perhaps because 0.825**12<0.01 but 0.826**12>0.01 + samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples, + shape, cond, verbose=False, eta=eta, + unconditional_guidance_scale=scale, + unconditional_conditioning=un_cond) + + if config.save_memory: + model.low_vram_shift(is_diffusing=False) + + x_samples = model.decode_first_stage(samples) + x_samples = (einops.rearrange(x_samples, 'b c h w -> b h w c') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8) + + results = [x_samples[i] for i in range(num_samples)] + return [detected_map] + results + + +block = gr.Blocks().queue() +with block: + with gr.Row(): + gr.Markdown("## Control Stable Diffusion with Segmentation Maps") + with gr.Row(): + with gr.Column(): + input_image = gr.Image(source='upload', type="numpy") + prompt = gr.Textbox(label="Prompt") + run_button = gr.Button(label="Run") + with gr.Accordion("Advanced options", open=False): + num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1) + image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64) + strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01) + guess_mode = gr.Checkbox(label='Guess Mode', value=False) + detect_resolution = gr.Slider(label="Segmentation Resolution", minimum=128, maximum=1024, value=512, step=1) + ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1) + scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1) + seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True) + eta = gr.Number(label="eta (DDIM)", value=0.0) + a_prompt = gr.Textbox(label="Added Prompt", value='best quality, extremely detailed') + n_prompt = gr.Textbox(label="Negative Prompt", + value='longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality') + with gr.Column(): + result_gallery = gr.Gallery(label='Output', show_label=False, elem_id="gallery").style(grid=2, height='auto') + ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, detect_resolution, ddim_steps, guess_mode, strength, scale, seed, eta] + run_button.click(fn=process, inputs=ips, outputs=[result_gallery]) + + +block.launch(server_name='0.0.0.0') diff --git a/comparison_models/ControlNet/ldm/data/__init__.py b/comparison_models/ControlNet/ldm/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/ControlNet/ldm/data/util.py b/comparison_models/ControlNet/ldm/data/util.py new file mode 100644 index 0000000..5b60ceb --- /dev/null +++ b/comparison_models/ControlNet/ldm/data/util.py @@ -0,0 +1,24 @@ +import torch + +from ldm.modules.midas.api import load_midas_transform + + +class AddMiDaS(object): + def __init__(self, model_type): + super().__init__() + self.transform = load_midas_transform(model_type) + + def pt2np(self, x): + x = ((x + 1.0) * .5).detach().cpu().numpy() + return x + + def np2pt(self, x): + x = torch.from_numpy(x) * 2 - 1. + return x + + def __call__(self, sample): + # sample['jpg'] is tensor hwc in [-1, 1] at this point + x = self.pt2np(sample['jpg']) + x = self.transform({"image": x})["image"] + sample['midas_in'] = x + return sample \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/models/autoencoder.py b/comparison_models/ControlNet/ldm/models/autoencoder.py new file mode 100644 index 0000000..d122549 --- /dev/null +++ b/comparison_models/ControlNet/ldm/models/autoencoder.py @@ -0,0 +1,219 @@ +import torch +import pytorch_lightning as pl +import torch.nn.functional as F +from contextlib import contextmanager + +from ldm.modules.diffusionmodules.model import Encoder, Decoder +from ldm.modules.distributions.distributions import DiagonalGaussianDistribution + +from ldm.util import instantiate_from_config +from ldm.modules.ema import LitEma + + +class AutoencoderKL(pl.LightningModule): + def __init__(self, + ddconfig, + lossconfig, + embed_dim, + ckpt_path=None, + ignore_keys=[], + image_key="image", + colorize_nlabels=None, + monitor=None, + ema_decay=None, + learn_logvar=False + ): + super().__init__() + self.learn_logvar = learn_logvar + self.image_key = image_key + self.encoder = Encoder(**ddconfig) + self.decoder = Decoder(**ddconfig) + self.loss = instantiate_from_config(lossconfig) + assert ddconfig["double_z"] + self.quant_conv = torch.nn.Conv2d(2*ddconfig["z_channels"], 2*embed_dim, 1) + self.post_quant_conv = torch.nn.Conv2d(embed_dim, ddconfig["z_channels"], 1) + self.embed_dim = embed_dim + if colorize_nlabels is not None: + assert type(colorize_nlabels)==int + self.register_buffer("colorize", torch.randn(3, colorize_nlabels, 1, 1)) + if monitor is not None: + self.monitor = monitor + + self.use_ema = ema_decay is not None + if self.use_ema: + self.ema_decay = ema_decay + assert 0. < ema_decay < 1. + self.model_ema = LitEma(self, decay=ema_decay) + print(f"Keeping EMAs of {len(list(self.model_ema.buffers()))}.") + + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys) + + def init_from_ckpt(self, path, ignore_keys=list()): + sd = torch.load(path, map_location="cpu")["state_dict"] + keys = list(sd.keys()) + for k in keys: + for ik in ignore_keys: + if k.startswith(ik): + print("Deleting key {} from state_dict.".format(k)) + del sd[k] + self.load_state_dict(sd, strict=False) + print(f"Restored from {path}") + + @contextmanager + def ema_scope(self, context=None): + if self.use_ema: + self.model_ema.store(self.parameters()) + self.model_ema.copy_to(self) + if context is not None: + print(f"{context}: Switched to EMA weights") + try: + yield None + finally: + if self.use_ema: + self.model_ema.restore(self.parameters()) + if context is not None: + print(f"{context}: Restored training weights") + + def on_train_batch_end(self, *args, **kwargs): + if self.use_ema: + self.model_ema(self) + + def encode(self, x): + h = self.encoder(x) + moments = self.quant_conv(h) + posterior = DiagonalGaussianDistribution(moments) + return posterior + + def decode(self, z): + z = self.post_quant_conv(z) + dec = self.decoder(z) + return dec + + def forward(self, input, sample_posterior=True): + posterior = self.encode(input) + if sample_posterior: + z = posterior.sample() + else: + z = posterior.mode() + dec = self.decode(z) + return dec, posterior + + def get_input(self, batch, k): + x = batch[k] + if len(x.shape) == 3: + x = x[..., None] + x = x.permute(0, 3, 1, 2).to(memory_format=torch.contiguous_format).float() + return x + + def training_step(self, batch, batch_idx, optimizer_idx): + inputs = self.get_input(batch, self.image_key) + reconstructions, posterior = self(inputs) + + if optimizer_idx == 0: + # train encoder+decoder+logvar + aeloss, log_dict_ae = self.loss(inputs, reconstructions, posterior, optimizer_idx, self.global_step, + last_layer=self.get_last_layer(), split="train") + self.log("aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=False) + return aeloss + + if optimizer_idx == 1: + # train the discriminator + discloss, log_dict_disc = self.loss(inputs, reconstructions, posterior, optimizer_idx, self.global_step, + last_layer=self.get_last_layer(), split="train") + + self.log("discloss", discloss, prog_bar=True, logger=True, on_step=True, on_epoch=True) + self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=False) + return discloss + + def validation_step(self, batch, batch_idx): + log_dict = self._validation_step(batch, batch_idx) + with self.ema_scope(): + log_dict_ema = self._validation_step(batch, batch_idx, postfix="_ema") + return log_dict + + def _validation_step(self, batch, batch_idx, postfix=""): + inputs = self.get_input(batch, self.image_key) + reconstructions, posterior = self(inputs) + aeloss, log_dict_ae = self.loss(inputs, reconstructions, posterior, 0, self.global_step, + last_layer=self.get_last_layer(), split="val"+postfix) + + discloss, log_dict_disc = self.loss(inputs, reconstructions, posterior, 1, self.global_step, + last_layer=self.get_last_layer(), split="val"+postfix) + + self.log(f"val{postfix}/rec_loss", log_dict_ae[f"val{postfix}/rec_loss"]) + self.log_dict(log_dict_ae) + self.log_dict(log_dict_disc) + return self.log_dict + + def configure_optimizers(self): + lr = self.learning_rate + ae_params_list = list(self.encoder.parameters()) + list(self.decoder.parameters()) + list( + self.quant_conv.parameters()) + list(self.post_quant_conv.parameters()) + if self.learn_logvar: + print(f"{self.__class__.__name__}: Learning logvar") + ae_params_list.append(self.loss.logvar) + opt_ae = torch.optim.Adam(ae_params_list, + lr=lr, betas=(0.5, 0.9)) + opt_disc = torch.optim.Adam(self.loss.discriminator.parameters(), + lr=lr, betas=(0.5, 0.9)) + return [opt_ae, opt_disc], [] + + def get_last_layer(self): + return self.decoder.conv_out.weight + + @torch.no_grad() + def log_images(self, batch, only_inputs=False, log_ema=False, **kwargs): + log = dict() + x = self.get_input(batch, self.image_key) + x = x.to(self.device) + if not only_inputs: + xrec, posterior = self(x) + if x.shape[1] > 3: + # colorize with random projection + assert xrec.shape[1] > 3 + x = self.to_rgb(x) + xrec = self.to_rgb(xrec) + log["samples"] = self.decode(torch.randn_like(posterior.sample())) + log["reconstructions"] = xrec + if log_ema or self.use_ema: + with self.ema_scope(): + xrec_ema, posterior_ema = self(x) + if x.shape[1] > 3: + # colorize with random projection + assert xrec_ema.shape[1] > 3 + xrec_ema = self.to_rgb(xrec_ema) + log["samples_ema"] = self.decode(torch.randn_like(posterior_ema.sample())) + log["reconstructions_ema"] = xrec_ema + log["inputs"] = x + return log + + def to_rgb(self, x): + assert self.image_key == "segmentation" + if not hasattr(self, "colorize"): + self.register_buffer("colorize", torch.randn(3, x.shape[1], 1, 1).to(x)) + x = F.conv2d(x, weight=self.colorize) + x = 2.*(x-x.min())/(x.max()-x.min()) - 1. + return x + + +class IdentityFirstStage(torch.nn.Module): + def __init__(self, *args, vq_interface=False, **kwargs): + self.vq_interface = vq_interface + super().__init__() + + def encode(self, x, *args, **kwargs): + return x + + def decode(self, x, *args, **kwargs): + return x + + def quantize(self, x, *args, **kwargs): + if self.vq_interface: + return x, None, [None, None, None] + return x + + def forward(self, x, *args, **kwargs): + return x + diff --git a/comparison_models/ControlNet/ldm/models/diffusion/__init__.py b/comparison_models/ControlNet/ldm/models/diffusion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/ControlNet/ldm/models/diffusion/ddim.py b/comparison_models/ControlNet/ldm/models/diffusion/ddim.py new file mode 100644 index 0000000..27ead0e --- /dev/null +++ b/comparison_models/ControlNet/ldm/models/diffusion/ddim.py @@ -0,0 +1,336 @@ +"""SAMPLING ONLY.""" + +import torch +import numpy as np +from tqdm import tqdm + +from ldm.modules.diffusionmodules.util import make_ddim_sampling_parameters, make_ddim_timesteps, noise_like, extract_into_tensor + + +class DDIMSampler(object): + def __init__(self, model, schedule="linear", **kwargs): + super().__init__() + self.model = model + self.ddpm_num_timesteps = model.num_timesteps + self.schedule = schedule + + def register_buffer(self, name, attr): + if type(attr) == torch.Tensor: + if attr.device != torch.device("cuda"): + attr = attr.to(torch.device("cuda")) + setattr(self, name, attr) + + def make_schedule(self, ddim_num_steps, ddim_discretize="uniform", ddim_eta=0., verbose=True): + self.ddim_timesteps = make_ddim_timesteps(ddim_discr_method=ddim_discretize, num_ddim_timesteps=ddim_num_steps, + num_ddpm_timesteps=self.ddpm_num_timesteps,verbose=verbose) + alphas_cumprod = self.model.alphas_cumprod + assert alphas_cumprod.shape[0] == self.ddpm_num_timesteps, 'alphas have to be defined for each timestep' + to_torch = lambda x: x.clone().detach().to(torch.float32).to(self.model.device) + + self.register_buffer('betas', to_torch(self.model.betas)) + self.register_buffer('alphas_cumprod', to_torch(alphas_cumprod)) + self.register_buffer('alphas_cumprod_prev', to_torch(self.model.alphas_cumprod_prev)) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod.cpu()))) + self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod.cpu()))) + self.register_buffer('log_one_minus_alphas_cumprod', to_torch(np.log(1. - alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recip_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recipm1_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu() - 1))) + + # ddim sampling parameters + ddim_sigmas, ddim_alphas, ddim_alphas_prev = make_ddim_sampling_parameters(alphacums=alphas_cumprod.cpu(), + ddim_timesteps=self.ddim_timesteps, + eta=ddim_eta,verbose=verbose) + self.register_buffer('ddim_sigmas', ddim_sigmas) + self.register_buffer('ddim_alphas', ddim_alphas) + self.register_buffer('ddim_alphas_prev', ddim_alphas_prev) + self.register_buffer('ddim_sqrt_one_minus_alphas', np.sqrt(1. - ddim_alphas)) + sigmas_for_original_sampling_steps = ddim_eta * torch.sqrt( + (1 - self.alphas_cumprod_prev) / (1 - self.alphas_cumprod) * ( + 1 - self.alphas_cumprod / self.alphas_cumprod_prev)) + self.register_buffer('ddim_sigmas_for_original_num_steps', sigmas_for_original_sampling_steps) + + @torch.no_grad() + def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + dynamic_threshold=None, + ucg_schedule=None, + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + ctmp = conditioning[list(conditioning.keys())[0]] + while isinstance(ctmp, list): ctmp = ctmp[0] + cbs = ctmp.shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + + elif isinstance(conditioning, list): + for ctmp in conditioning: + if ctmp.shape[0] != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose) + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + print(f'Data shape for DDIM sampling is {size}, eta {eta}') + + samples, intermediates = self.ddim_sampling(conditioning, size, + callback=callback, + img_callback=img_callback, + quantize_denoised=quantize_x0, + mask=mask, x0=x0, + ddim_use_original_steps=False, + noise_dropout=noise_dropout, + temperature=temperature, + score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + x_T=x_T, + log_every_t=log_every_t, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + dynamic_threshold=dynamic_threshold, + ucg_schedule=ucg_schedule + ) + return samples, intermediates + + @torch.no_grad() + def ddim_sampling(self, cond, shape, + x_T=None, ddim_use_original_steps=False, + callback=None, timesteps=None, quantize_denoised=False, + mask=None, x0=None, img_callback=None, log_every_t=100, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, dynamic_threshold=None, + ucg_schedule=None): + device = self.model.betas.device + b = shape[0] + if x_T is None: + img = torch.randn(shape, device=device) + else: + img = x_T + + if timesteps is None: + timesteps = self.ddpm_num_timesteps if ddim_use_original_steps else self.ddim_timesteps + elif timesteps is not None and not ddim_use_original_steps: + subset_end = int(min(timesteps / self.ddim_timesteps.shape[0], 1) * self.ddim_timesteps.shape[0]) - 1 + timesteps = self.ddim_timesteps[:subset_end] + + intermediates = {'x_inter': [img], 'pred_x0': [img]} + time_range = reversed(range(0,timesteps)) if ddim_use_original_steps else np.flip(timesteps) + total_steps = timesteps if ddim_use_original_steps else timesteps.shape[0] + print(f"Running DDIM Sampling with {total_steps} timesteps") + + iterator = tqdm(time_range, desc='DDIM Sampler', total=total_steps) + + for i, step in enumerate(iterator): + index = total_steps - i - 1 + ts = torch.full((b,), step, device=device, dtype=torch.long) + + if mask is not None: + assert x0 is not None + img_orig = self.model.q_sample(x0, ts) # TODO: deterministic forward pass? + img = img_orig * mask + (1. - mask) * img + + if ucg_schedule is not None: + assert len(ucg_schedule) == len(time_range) + unconditional_guidance_scale = ucg_schedule[i] + + outs = self.p_sample_ddim(img, cond, ts, index=index, use_original_steps=ddim_use_original_steps, + quantize_denoised=quantize_denoised, temperature=temperature, + noise_dropout=noise_dropout, score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + dynamic_threshold=dynamic_threshold) + img, pred_x0 = outs + if callback: callback(i) + if img_callback: img_callback(pred_x0, i) + + if index % log_every_t == 0 or index == total_steps - 1: + intermediates['x_inter'].append(img) + intermediates['pred_x0'].append(pred_x0) + + return img, intermediates + + @torch.no_grad() + def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, + dynamic_threshold=None): + b, *_, device = *x.shape, x.device + + if unconditional_conditioning is None or unconditional_guidance_scale == 1.: + model_output = self.model.apply_model(x, t, c) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t] * 2) + if isinstance(c, dict): + assert isinstance(unconditional_conditioning, dict) + c_in = dict() + for k in c: + if isinstance(c[k], list): + c_in[k] = [torch.cat([ + unconditional_conditioning[k][i], + c[k][i]]) for i in range(len(c[k]))] + else: + c_in[k] = torch.cat([ + unconditional_conditioning[k], + c[k]]) + elif isinstance(c, list): + c_in = list() + assert isinstance(unconditional_conditioning, list) + for i in range(len(c)): + c_in.append(torch.cat([unconditional_conditioning[i], c[i]])) + else: + c_in = torch.cat([unconditional_conditioning, c]) + model_uncond, model_t = self.model.apply_model(x_in, t_in, c_in).chunk(2) + model_output = model_uncond + unconditional_guidance_scale * (model_t - model_uncond) + + if self.model.parameterization == "v": + e_t = self.model.predict_eps_from_z_and_v(x, t, model_output) + else: + e_t = model_output + + if score_corrector is not None: + assert self.model.parameterization == "eps", 'not implemented' + e_t = score_corrector.modify_score(self.model, e_t, x, t, c, **corrector_kwargs) + + alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas + alphas_prev = self.model.alphas_cumprod_prev if use_original_steps else self.ddim_alphas_prev + sqrt_one_minus_alphas = self.model.sqrt_one_minus_alphas_cumprod if use_original_steps else self.ddim_sqrt_one_minus_alphas + sigmas = self.model.ddim_sigmas_for_original_num_steps if use_original_steps else self.ddim_sigmas + # select parameters corresponding to the currently considered timestep + a_t = torch.full((b, 1, 1, 1), alphas[index], device=device) + a_prev = torch.full((b, 1, 1, 1), alphas_prev[index], device=device) + sigma_t = torch.full((b, 1, 1, 1), sigmas[index], device=device) + sqrt_one_minus_at = torch.full((b, 1, 1, 1), sqrt_one_minus_alphas[index],device=device) + + # current prediction for x_0 + if self.model.parameterization != "v": + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + else: + pred_x0 = self.model.predict_start_from_z_and_v(x, t, model_output) + + if quantize_denoised: + pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0) + + if dynamic_threshold is not None: + raise NotImplementedError() + + # direction pointing to x_t + dir_xt = (1. - a_prev - sigma_t**2).sqrt() * e_t + noise = sigma_t * noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + x_prev = a_prev.sqrt() * pred_x0 + dir_xt + noise + return x_prev, pred_x0 + + @torch.no_grad() + def encode(self, x0, c, t_enc, use_original_steps=False, return_intermediates=None, + unconditional_guidance_scale=1.0, unconditional_conditioning=None, callback=None): + num_reference_steps = self.ddpm_num_timesteps if use_original_steps else self.ddim_timesteps.shape[0] + + assert t_enc <= num_reference_steps + num_steps = t_enc + + if use_original_steps: + alphas_next = self.alphas_cumprod[:num_steps] + alphas = self.alphas_cumprod_prev[:num_steps] + else: + alphas_next = self.ddim_alphas[:num_steps] + alphas = torch.tensor(self.ddim_alphas_prev[:num_steps]) + + x_next = x0 + intermediates = [] + inter_steps = [] + for i in tqdm(range(num_steps), desc='Encoding Image'): + t = torch.full((x0.shape[0],), i, device=self.model.device, dtype=torch.long) + if unconditional_guidance_scale == 1.: + noise_pred = self.model.apply_model(x_next, t, c) + else: + assert unconditional_conditioning is not None + e_t_uncond, noise_pred = torch.chunk( + self.model.apply_model(torch.cat((x_next, x_next)), torch.cat((t, t)), + torch.cat((unconditional_conditioning, c))), 2) + noise_pred = e_t_uncond + unconditional_guidance_scale * (noise_pred - e_t_uncond) + + xt_weighted = (alphas_next[i] / alphas[i]).sqrt() * x_next + weighted_noise_pred = alphas_next[i].sqrt() * ( + (1 / alphas_next[i] - 1).sqrt() - (1 / alphas[i] - 1).sqrt()) * noise_pred + x_next = xt_weighted + weighted_noise_pred + if return_intermediates and i % ( + num_steps // return_intermediates) == 0 and i < num_steps - 1: + intermediates.append(x_next) + inter_steps.append(i) + elif return_intermediates and i >= num_steps - 2: + intermediates.append(x_next) + inter_steps.append(i) + if callback: callback(i) + + out = {'x_encoded': x_next, 'intermediate_steps': inter_steps} + if return_intermediates: + out.update({'intermediates': intermediates}) + return x_next, out + + @torch.no_grad() + def stochastic_encode(self, x0, t, use_original_steps=False, noise=None): + # fast, but does not allow for exact reconstruction + # t serves as an index to gather the correct alphas + if use_original_steps: + sqrt_alphas_cumprod = self.sqrt_alphas_cumprod + sqrt_one_minus_alphas_cumprod = self.sqrt_one_minus_alphas_cumprod + else: + sqrt_alphas_cumprod = torch.sqrt(self.ddim_alphas) + sqrt_one_minus_alphas_cumprod = self.ddim_sqrt_one_minus_alphas + + if noise is None: + noise = torch.randn_like(x0) + return (extract_into_tensor(sqrt_alphas_cumprod, t, x0.shape) * x0 + + extract_into_tensor(sqrt_one_minus_alphas_cumprod, t, x0.shape) * noise) + + @torch.no_grad() + def decode(self, x_latent, cond, t_start, unconditional_guidance_scale=1.0, unconditional_conditioning=None, + use_original_steps=False, callback=None): + + timesteps = np.arange(self.ddpm_num_timesteps) if use_original_steps else self.ddim_timesteps + timesteps = timesteps[:t_start] + + time_range = np.flip(timesteps) + total_steps = timesteps.shape[0] + print(f"Running DDIM Sampling with {total_steps} timesteps") + + iterator = tqdm(time_range, desc='Decoding image', total=total_steps) + x_dec = x_latent + for i, step in enumerate(iterator): + index = total_steps - i - 1 + ts = torch.full((x_latent.shape[0],), step, device=x_latent.device, dtype=torch.long) + x_dec, _ = self.p_sample_ddim(x_dec, cond, ts, index=index, use_original_steps=use_original_steps, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning) + if callback: callback(i) + return x_dec \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/models/diffusion/ddpm.py b/comparison_models/ControlNet/ldm/models/diffusion/ddpm.py new file mode 100644 index 0000000..f71a44a --- /dev/null +++ b/comparison_models/ControlNet/ldm/models/diffusion/ddpm.py @@ -0,0 +1,1797 @@ +""" +wild mixture of +https://github.com/lucidrains/denoising-diffusion-pytorch/blob/7706bdfc6f527f58d33f84b7b522e61e6e3164b3/denoising_diffusion_pytorch/denoising_diffusion_pytorch.py +https://github.com/openai/improved-diffusion/blob/e94489283bb876ac1477d5dd7709bbbd2d9902ce/improved_diffusion/gaussian_diffusion.py +https://github.com/CompVis/taming-transformers +-- merci +""" + +import torch +import torch.nn as nn +import numpy as np +import pytorch_lightning as pl +from torch.optim.lr_scheduler import LambdaLR +from einops import rearrange, repeat +from contextlib import contextmanager, nullcontext +from functools import partial +import itertools +from tqdm import tqdm +from torchvision.utils import make_grid +from pytorch_lightning.utilities.distributed import rank_zero_only +from omegaconf import ListConfig + +from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config +from ldm.modules.ema import LitEma +from ldm.modules.distributions.distributions import normal_kl, DiagonalGaussianDistribution +from ldm.models.autoencoder import IdentityFirstStage, AutoencoderKL +from ldm.modules.diffusionmodules.util import make_beta_schedule, extract_into_tensor, noise_like +from ldm.models.diffusion.ddim import DDIMSampler + + +__conditioning_keys__ = {'concat': 'c_concat', + 'crossattn': 'c_crossattn', + 'adm': 'y'} + + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +def uniform_on_device(r1, r2, shape, device): + return (r1 - r2) * torch.rand(*shape, device=device) + r2 + + +class DDPM(pl.LightningModule): + # classic DDPM with Gaussian diffusion, in image space + def __init__(self, + unet_config, + timesteps=1000, + beta_schedule="linear", + loss_type="l2", + ckpt_path=None, + ignore_keys=[], + load_only_unet=False, + monitor="val/loss", + use_ema=True, + first_stage_key="image", + image_size=256, + channels=3, + log_every_t=100, + clip_denoised=True, + linear_start=1e-4, + linear_end=2e-2, + cosine_s=8e-3, + given_betas=None, + original_elbo_weight=0., + v_posterior=0., # weight for choosing posterior variance as sigma = (1-v) * beta_tilde + v * beta + l_simple_weight=1., + conditioning_key=None, + parameterization="eps", # all assuming fixed variance schedules + scheduler_config=None, + use_positional_encodings=False, + learn_logvar=False, + logvar_init=0., + make_it_fit=False, + ucg_training=None, + reset_ema=False, + reset_num_ema_updates=False, + ): + super().__init__() + assert parameterization in ["eps", "x0", "v"], 'currently only supporting "eps" and "x0" and "v"' + self.parameterization = parameterization + print(f"{self.__class__.__name__}: Running in {self.parameterization}-prediction mode") + self.cond_stage_model = None + self.clip_denoised = clip_denoised + self.log_every_t = log_every_t + self.first_stage_key = first_stage_key + self.image_size = image_size # try conv? + self.channels = channels + self.use_positional_encodings = use_positional_encodings + self.model = DiffusionWrapper(unet_config, conditioning_key) + count_params(self.model, verbose=True) + self.use_ema = use_ema + if self.use_ema: + self.model_ema = LitEma(self.model) + print(f"Keeping EMAs of {len(list(self.model_ema.buffers()))}.") + + self.use_scheduler = scheduler_config is not None + if self.use_scheduler: + self.scheduler_config = scheduler_config + + self.v_posterior = v_posterior + self.original_elbo_weight = original_elbo_weight + self.l_simple_weight = l_simple_weight + + if monitor is not None: + self.monitor = monitor + self.make_it_fit = make_it_fit + if reset_ema: assert exists(ckpt_path) + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys, only_model=load_only_unet) + if reset_ema: + assert self.use_ema + print(f"Resetting ema to pure model weights. This is useful when restoring from an ema-only checkpoint.") + self.model_ema = LitEma(self.model) + if reset_num_ema_updates: + print(" +++++++++++ WARNING: RESETTING NUM_EMA UPDATES TO ZERO +++++++++++ ") + assert self.use_ema + self.model_ema.reset_num_updates() + + self.register_schedule(given_betas=given_betas, beta_schedule=beta_schedule, timesteps=timesteps, + linear_start=linear_start, linear_end=linear_end, cosine_s=cosine_s) + + self.loss_type = loss_type + + self.learn_logvar = learn_logvar + logvar = torch.full(fill_value=logvar_init, size=(self.num_timesteps,)) + if self.learn_logvar: + self.logvar = nn.Parameter(self.logvar, requires_grad=True) + else: + self.register_buffer('logvar', logvar) + + self.ucg_training = ucg_training or dict() + if self.ucg_training: + self.ucg_prng = np.random.RandomState() + + def register_schedule(self, given_betas=None, beta_schedule="linear", timesteps=1000, + linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3): + if exists(given_betas): + betas = given_betas + else: + betas = make_beta_schedule(beta_schedule, timesteps, linear_start=linear_start, linear_end=linear_end, + cosine_s=cosine_s) + alphas = 1. - betas + alphas_cumprod = np.cumprod(alphas, axis=0) + alphas_cumprod_prev = np.append(1., alphas_cumprod[:-1]) + + timesteps, = betas.shape + self.num_timesteps = int(timesteps) + self.linear_start = linear_start + self.linear_end = linear_end + assert alphas_cumprod.shape[0] == self.num_timesteps, 'alphas have to be defined for each timestep' + + to_torch = partial(torch.tensor, dtype=torch.float32) + + self.register_buffer('betas', to_torch(betas)) + self.register_buffer('alphas_cumprod', to_torch(alphas_cumprod)) + self.register_buffer('alphas_cumprod_prev', to_torch(alphas_cumprod_prev)) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod))) + self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod))) + self.register_buffer('log_one_minus_alphas_cumprod', to_torch(np.log(1. - alphas_cumprod))) + self.register_buffer('sqrt_recip_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod))) + self.register_buffer('sqrt_recipm1_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod - 1))) + + # calculations for posterior q(x_{t-1} | x_t, x_0) + posterior_variance = (1 - self.v_posterior) * betas * (1. - alphas_cumprod_prev) / ( + 1. - alphas_cumprod) + self.v_posterior * betas + # above: equal to 1. / (1. / (1. - alpha_cumprod_tm1) + alpha_t / beta_t) + self.register_buffer('posterior_variance', to_torch(posterior_variance)) + # below: log calculation clipped because the posterior variance is 0 at the beginning of the diffusion chain + self.register_buffer('posterior_log_variance_clipped', to_torch(np.log(np.maximum(posterior_variance, 1e-20)))) + self.register_buffer('posterior_mean_coef1', to_torch( + betas * np.sqrt(alphas_cumprod_prev) / (1. - alphas_cumprod))) + self.register_buffer('posterior_mean_coef2', to_torch( + (1. - alphas_cumprod_prev) * np.sqrt(alphas) / (1. - alphas_cumprod))) + + if self.parameterization == "eps": + lvlb_weights = self.betas ** 2 / ( + 2 * self.posterior_variance * to_torch(alphas) * (1 - self.alphas_cumprod)) + elif self.parameterization == "x0": + lvlb_weights = 0.5 * np.sqrt(torch.Tensor(alphas_cumprod)) / (2. * 1 - torch.Tensor(alphas_cumprod)) + elif self.parameterization == "v": + lvlb_weights = torch.ones_like(self.betas ** 2 / ( + 2 * self.posterior_variance * to_torch(alphas) * (1 - self.alphas_cumprod))) + else: + raise NotImplementedError("mu not supported") + lvlb_weights[0] = lvlb_weights[1] + self.register_buffer('lvlb_weights', lvlb_weights, persistent=False) + assert not torch.isnan(self.lvlb_weights).all() + + @contextmanager + def ema_scope(self, context=None): + if self.use_ema: + self.model_ema.store(self.model.parameters()) + self.model_ema.copy_to(self.model) + if context is not None: + print(f"{context}: Switched to EMA weights") + try: + yield None + finally: + if self.use_ema: + self.model_ema.restore(self.model.parameters()) + if context is not None: + print(f"{context}: Restored training weights") + + @torch.no_grad() + def init_from_ckpt(self, path, ignore_keys=list(), only_model=False): + sd = torch.load(path, map_location="cpu") + if "state_dict" in list(sd.keys()): + sd = sd["state_dict"] + keys = list(sd.keys()) + for k in keys: + for ik in ignore_keys: + if k.startswith(ik): + print("Deleting key {} from state_dict.".format(k)) + del sd[k] + if self.make_it_fit: + n_params = len([name for name, _ in + itertools.chain(self.named_parameters(), + self.named_buffers())]) + for name, param in tqdm( + itertools.chain(self.named_parameters(), + self.named_buffers()), + desc="Fitting old weights to new weights", + total=n_params + ): + if not name in sd: + continue + old_shape = sd[name].shape + new_shape = param.shape + assert len(old_shape) == len(new_shape) + if len(new_shape) > 2: + # we only modify first two axes + assert new_shape[2:] == old_shape[2:] + # assumes first axis corresponds to output dim + if not new_shape == old_shape: + new_param = param.clone() + old_param = sd[name] + if len(new_shape) == 1: + for i in range(new_param.shape[0]): + new_param[i] = old_param[i % old_shape[0]] + elif len(new_shape) >= 2: + for i in range(new_param.shape[0]): + for j in range(new_param.shape[1]): + new_param[i, j] = old_param[i % old_shape[0], j % old_shape[1]] + + n_used_old = torch.ones(old_shape[1]) + for j in range(new_param.shape[1]): + n_used_old[j % old_shape[1]] += 1 + n_used_new = torch.zeros(new_shape[1]) + for j in range(new_param.shape[1]): + n_used_new[j] = n_used_old[j % old_shape[1]] + + n_used_new = n_used_new[None, :] + while len(n_used_new.shape) < len(new_shape): + n_used_new = n_used_new.unsqueeze(-1) + new_param /= n_used_new + + sd[name] = new_param + + missing, unexpected = self.load_state_dict(sd, strict=False) if not only_model else self.model.load_state_dict( + sd, strict=False) + print(f"Restored from {path} with {len(missing)} missing and {len(unexpected)} unexpected keys") + if len(missing) > 0: + print(f"Missing Keys:\n {missing}") + if len(unexpected) > 0: + print(f"\nUnexpected Keys:\n {unexpected}") + + def q_mean_variance(self, x_start, t): + """ + Get the distribution q(x_t | x_0). + :param x_start: the [N x C x ...] tensor of noiseless inputs. + :param t: the number of diffusion steps (minus 1). Here, 0 means one step. + :return: A tuple (mean, variance, log_variance), all of x_start's shape. + """ + mean = (extract_into_tensor(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start) + variance = extract_into_tensor(1.0 - self.alphas_cumprod, t, x_start.shape) + log_variance = extract_into_tensor(self.log_one_minus_alphas_cumprod, t, x_start.shape) + return mean, variance, log_variance + + def predict_start_from_noise(self, x_t, t, noise): + return ( + extract_into_tensor(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t - + extract_into_tensor(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) * noise + ) + + def predict_start_from_z_and_v(self, x_t, t, v): + # self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod))) + # self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod))) + return ( + extract_into_tensor(self.sqrt_alphas_cumprod, t, x_t.shape) * x_t - + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_t.shape) * v + ) + + def predict_eps_from_z_and_v(self, x_t, t, v): + return ( + extract_into_tensor(self.sqrt_alphas_cumprod, t, x_t.shape) * v + + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_t.shape) * x_t + ) + + def q_posterior(self, x_start, x_t, t): + posterior_mean = ( + extract_into_tensor(self.posterior_mean_coef1, t, x_t.shape) * x_start + + extract_into_tensor(self.posterior_mean_coef2, t, x_t.shape) * x_t + ) + posterior_variance = extract_into_tensor(self.posterior_variance, t, x_t.shape) + posterior_log_variance_clipped = extract_into_tensor(self.posterior_log_variance_clipped, t, x_t.shape) + return posterior_mean, posterior_variance, posterior_log_variance_clipped + + def p_mean_variance(self, x, t, clip_denoised: bool): + model_out = self.model(x, t) + if self.parameterization == "eps": + x_recon = self.predict_start_from_noise(x, t=t, noise=model_out) + elif self.parameterization == "x0": + x_recon = model_out + if clip_denoised: + x_recon.clamp_(-1., 1.) + + model_mean, posterior_variance, posterior_log_variance = self.q_posterior(x_start=x_recon, x_t=x, t=t) + return model_mean, posterior_variance, posterior_log_variance + + @torch.no_grad() + def p_sample(self, x, t, clip_denoised=True, repeat_noise=False): + b, *_, device = *x.shape, x.device + model_mean, _, model_log_variance = self.p_mean_variance(x=x, t=t, clip_denoised=clip_denoised) + noise = noise_like(x.shape, device, repeat_noise) + # no noise when t == 0 + nonzero_mask = (1 - (t == 0).float()).reshape(b, *((1,) * (len(x.shape) - 1))) + return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise + + @torch.no_grad() + def p_sample_loop(self, shape, return_intermediates=False): + device = self.betas.device + b = shape[0] + img = torch.randn(shape, device=device) + intermediates = [img] + for i in tqdm(reversed(range(0, self.num_timesteps)), desc='Sampling t', total=self.num_timesteps): + img = self.p_sample(img, torch.full((b,), i, device=device, dtype=torch.long), + clip_denoised=self.clip_denoised) + if i % self.log_every_t == 0 or i == self.num_timesteps - 1: + intermediates.append(img) + if return_intermediates: + return img, intermediates + return img + + @torch.no_grad() + def sample(self, batch_size=16, return_intermediates=False): + image_size = self.image_size + channels = self.channels + return self.p_sample_loop((batch_size, channels, image_size, image_size), + return_intermediates=return_intermediates) + + def q_sample(self, x_start, t, noise=None): + noise = default(noise, lambda: torch.randn_like(x_start)) + return (extract_into_tensor(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start + + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape) * noise) + + def get_v(self, x, noise, t): + return ( + extract_into_tensor(self.sqrt_alphas_cumprod, t, x.shape) * noise - + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x.shape) * x + ) + + def get_loss(self, pred, target, mean=True): + if self.loss_type == 'l1': + loss = (target - pred).abs() + if mean: + loss = loss.mean() + elif self.loss_type == 'l2': + if mean: + loss = torch.nn.functional.mse_loss(target, pred) + else: + loss = torch.nn.functional.mse_loss(target, pred, reduction='none') + else: + raise NotImplementedError("unknown loss type '{loss_type}'") + + return loss + + def p_losses(self, x_start, t, noise=None): + noise = default(noise, lambda: torch.randn_like(x_start)) + x_noisy = self.q_sample(x_start=x_start, t=t, noise=noise) + model_out = self.model(x_noisy, t) + + loss_dict = {} + if self.parameterization == "eps": + target = noise + elif self.parameterization == "x0": + target = x_start + elif self.parameterization == "v": + target = self.get_v(x_start, noise, t) + else: + raise NotImplementedError(f"Parameterization {self.parameterization} not yet supported") + + loss = self.get_loss(model_out, target, mean=False).mean(dim=[1, 2, 3]) + + log_prefix = 'train' if self.training else 'val' + + loss_dict.update({f'{log_prefix}/loss_simple': loss.mean()}) + loss_simple = loss.mean() * self.l_simple_weight + + loss_vlb = (self.lvlb_weights[t] * loss).mean() + loss_dict.update({f'{log_prefix}/loss_vlb': loss_vlb}) + + loss = loss_simple + self.original_elbo_weight * loss_vlb + + loss_dict.update({f'{log_prefix}/loss': loss}) + + return loss, loss_dict + + def forward(self, x, *args, **kwargs): + # b, c, h, w, device, img_size, = *x.shape, x.device, self.image_size + # assert h == img_size and w == img_size, f'height and width of image must be {img_size}' + t = torch.randint(0, self.num_timesteps, (x.shape[0],), device=self.device).long() + return self.p_losses(x, t, *args, **kwargs) + + def get_input(self, batch, k): + x = batch[k] + if len(x.shape) == 3: + x = x[..., None] + x = rearrange(x, 'b h w c -> b c h w') + x = x.to(memory_format=torch.contiguous_format).float() + return x + + def shared_step(self, batch): + x = self.get_input(batch, self.first_stage_key) + loss, loss_dict = self(x) + return loss, loss_dict + + def training_step(self, batch, batch_idx): + for k in self.ucg_training: + p = self.ucg_training[k]["p"] + val = self.ucg_training[k]["val"] + if val is None: + val = "" + for i in range(len(batch[k])): + if self.ucg_prng.choice(2, p=[1 - p, p]): + batch[k][i] = val + + loss, loss_dict = self.shared_step(batch) + + self.log_dict(loss_dict, prog_bar=True, + logger=True, on_step=True, on_epoch=True) + + self.log("global_step", self.global_step, + prog_bar=True, logger=True, on_step=True, on_epoch=False) + + if self.use_scheduler: + lr = self.optimizers().param_groups[0]['lr'] + self.log('lr_abs', lr, prog_bar=True, logger=True, on_step=True, on_epoch=False) + + return loss + + @torch.no_grad() + def validation_step(self, batch, batch_idx): + _, loss_dict_no_ema = self.shared_step(batch) + with self.ema_scope(): + _, loss_dict_ema = self.shared_step(batch) + loss_dict_ema = {key + '_ema': loss_dict_ema[key] for key in loss_dict_ema} + self.log_dict(loss_dict_no_ema, prog_bar=False, logger=True, on_step=False, on_epoch=True) + self.log_dict(loss_dict_ema, prog_bar=False, logger=True, on_step=False, on_epoch=True) + + def on_train_batch_end(self, *args, **kwargs): + if self.use_ema: + self.model_ema(self.model) + + def _get_rows_from_list(self, samples): + n_imgs_per_row = len(samples) + denoise_grid = rearrange(samples, 'n b c h w -> b n c h w') + denoise_grid = rearrange(denoise_grid, 'b n c h w -> (b n) c h w') + denoise_grid = make_grid(denoise_grid, nrow=n_imgs_per_row) + return denoise_grid + + @torch.no_grad() + def log_images(self, batch, N=8, n_row=2, sample=True, return_keys=None, **kwargs): + log = dict() + x = self.get_input(batch, self.first_stage_key) + N = min(x.shape[0], N) + n_row = min(x.shape[0], n_row) + x = x.to(self.device)[:N] + log["inputs"] = x + + # get diffusion row + diffusion_row = list() + x_start = x[:n_row] + + for t in range(self.num_timesteps): + if t % self.log_every_t == 0 or t == self.num_timesteps - 1: + t = repeat(torch.tensor([t]), '1 -> b', b=n_row) + t = t.to(self.device).long() + noise = torch.randn_like(x_start) + x_noisy = self.q_sample(x_start=x_start, t=t, noise=noise) + diffusion_row.append(x_noisy) + + log["diffusion_row"] = self._get_rows_from_list(diffusion_row) + + if sample: + # get denoise row + with self.ema_scope("Plotting"): + samples, denoise_row = self.sample(batch_size=N, return_intermediates=True) + + log["samples"] = samples + log["denoise_row"] = self._get_rows_from_list(denoise_row) + + if return_keys: + if np.intersect1d(list(log.keys()), return_keys).shape[0] == 0: + return log + else: + return {key: log[key] for key in return_keys} + return log + + def configure_optimizers(self): + lr = self.learning_rate + params = list(self.model.parameters()) + if self.learn_logvar: + params = params + [self.logvar] + opt = torch.optim.AdamW(params, lr=lr) + return opt + + +class LatentDiffusion(DDPM): + """main class""" + + def __init__(self, + first_stage_config, + cond_stage_config, + num_timesteps_cond=None, + cond_stage_key="image", + cond_stage_trainable=False, + concat_mode=True, + cond_stage_forward=None, + conditioning_key=None, + scale_factor=1.0, + scale_by_std=False, + force_null_conditioning=False, + *args, **kwargs): + self.force_null_conditioning = force_null_conditioning + self.num_timesteps_cond = default(num_timesteps_cond, 1) + self.scale_by_std = scale_by_std + assert self.num_timesteps_cond <= kwargs['timesteps'] + # for backwards compatibility after implementation of DiffusionWrapper + if conditioning_key is None: + conditioning_key = 'concat' if concat_mode else 'crossattn' + if cond_stage_config == '__is_unconditional__' and not self.force_null_conditioning: + conditioning_key = None + ckpt_path = kwargs.pop("ckpt_path", None) + reset_ema = kwargs.pop("reset_ema", False) + reset_num_ema_updates = kwargs.pop("reset_num_ema_updates", False) + ignore_keys = kwargs.pop("ignore_keys", []) + super().__init__(conditioning_key=conditioning_key, *args, **kwargs) + self.concat_mode = concat_mode + self.cond_stage_trainable = cond_stage_trainable + self.cond_stage_key = cond_stage_key + try: + self.num_downs = len(first_stage_config.params.ddconfig.ch_mult) - 1 + except: + self.num_downs = 0 + if not scale_by_std: + self.scale_factor = scale_factor + else: + self.register_buffer('scale_factor', torch.tensor(scale_factor)) + self.instantiate_first_stage(first_stage_config) + self.instantiate_cond_stage(cond_stage_config) + self.cond_stage_forward = cond_stage_forward + self.clip_denoised = False + self.bbox_tokenizer = None + + self.restarted_from_ckpt = False + if ckpt_path is not None: + self.init_from_ckpt(ckpt_path, ignore_keys) + self.restarted_from_ckpt = True + if reset_ema: + assert self.use_ema + print( + f"Resetting ema to pure model weights. This is useful when restoring from an ema-only checkpoint.") + self.model_ema = LitEma(self.model) + if reset_num_ema_updates: + print(" +++++++++++ WARNING: RESETTING NUM_EMA UPDATES TO ZERO +++++++++++ ") + assert self.use_ema + self.model_ema.reset_num_updates() + + def make_cond_schedule(self, ): + self.cond_ids = torch.full(size=(self.num_timesteps,), fill_value=self.num_timesteps - 1, dtype=torch.long) + ids = torch.round(torch.linspace(0, self.num_timesteps - 1, self.num_timesteps_cond)).long() + self.cond_ids[:self.num_timesteps_cond] = ids + + @rank_zero_only + @torch.no_grad() + def on_train_batch_start(self, batch, batch_idx, dataloader_idx): + # only for very first batch + if self.scale_by_std and self.current_epoch == 0 and self.global_step == 0 and batch_idx == 0 and not self.restarted_from_ckpt: + assert self.scale_factor == 1., 'rather not use custom rescaling and std-rescaling simultaneously' + # set rescale weight to 1./std of encodings + print("### USING STD-RESCALING ###") + x = super().get_input(batch, self.first_stage_key) + x = x.to(self.device) + encoder_posterior = self.encode_first_stage(x) + z = self.get_first_stage_encoding(encoder_posterior).detach() + del self.scale_factor + self.register_buffer('scale_factor', 1. / z.flatten().std()) + print(f"setting self.scale_factor to {self.scale_factor}") + print("### USING STD-RESCALING ###") + + def register_schedule(self, + given_betas=None, beta_schedule="linear", timesteps=1000, + linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3): + super().register_schedule(given_betas, beta_schedule, timesteps, linear_start, linear_end, cosine_s) + + self.shorten_cond_schedule = self.num_timesteps_cond > 1 + if self.shorten_cond_schedule: + self.make_cond_schedule() + + def instantiate_first_stage(self, config): + model = instantiate_from_config(config) + self.first_stage_model = model.eval() + self.first_stage_model.train = disabled_train + for param in self.first_stage_model.parameters(): + param.requires_grad = False + + def instantiate_cond_stage(self, config): + if not self.cond_stage_trainable: + if config == "__is_first_stage__": + print("Using first stage also as cond stage.") + self.cond_stage_model = self.first_stage_model + elif config == "__is_unconditional__": + print(f"Training {self.__class__.__name__} as an unconditional model.") + self.cond_stage_model = None + # self.be_unconditional = True + else: + model = instantiate_from_config(config) + self.cond_stage_model = model.eval() + self.cond_stage_model.train = disabled_train + for param in self.cond_stage_model.parameters(): + param.requires_grad = False + else: + assert config != '__is_first_stage__' + assert config != '__is_unconditional__' + model = instantiate_from_config(config) + self.cond_stage_model = model + + def _get_denoise_row_from_list(self, samples, desc='', force_no_decoder_quantization=False): + denoise_row = [] + for zd in tqdm(samples, desc=desc): + denoise_row.append(self.decode_first_stage(zd.to(self.device), + force_not_quantize=force_no_decoder_quantization)) + n_imgs_per_row = len(denoise_row) + denoise_row = torch.stack(denoise_row) # n_log_step, n_row, C, H, W + denoise_grid = rearrange(denoise_row, 'n b c h w -> b n c h w') + denoise_grid = rearrange(denoise_grid, 'b n c h w -> (b n) c h w') + denoise_grid = make_grid(denoise_grid, nrow=n_imgs_per_row) + return denoise_grid + + def get_first_stage_encoding(self, encoder_posterior): + if isinstance(encoder_posterior, DiagonalGaussianDistribution): + z = encoder_posterior.sample() + elif isinstance(encoder_posterior, torch.Tensor): + z = encoder_posterior + else: + raise NotImplementedError(f"encoder_posterior of type '{type(encoder_posterior)}' not yet implemented") + return self.scale_factor * z + + def get_learned_conditioning(self, c): + if self.cond_stage_forward is None: + if hasattr(self.cond_stage_model, 'encode') and callable(self.cond_stage_model.encode): + c = self.cond_stage_model.encode(c) + if isinstance(c, DiagonalGaussianDistribution): + c = c.mode() + else: + c = self.cond_stage_model(c) + else: + assert hasattr(self.cond_stage_model, self.cond_stage_forward) + c = getattr(self.cond_stage_model, self.cond_stage_forward)(c) + return c + + def meshgrid(self, h, w): + y = torch.arange(0, h).view(h, 1, 1).repeat(1, w, 1) + x = torch.arange(0, w).view(1, w, 1).repeat(h, 1, 1) + + arr = torch.cat([y, x], dim=-1) + return arr + + def delta_border(self, h, w): + """ + :param h: height + :param w: width + :return: normalized distance to image border, + wtith min distance = 0 at border and max dist = 0.5 at image center + """ + lower_right_corner = torch.tensor([h - 1, w - 1]).view(1, 1, 2) + arr = self.meshgrid(h, w) / lower_right_corner + dist_left_up = torch.min(arr, dim=-1, keepdims=True)[0] + dist_right_down = torch.min(1 - arr, dim=-1, keepdims=True)[0] + edge_dist = torch.min(torch.cat([dist_left_up, dist_right_down], dim=-1), dim=-1)[0] + return edge_dist + + def get_weighting(self, h, w, Ly, Lx, device): + weighting = self.delta_border(h, w) + weighting = torch.clip(weighting, self.split_input_params["clip_min_weight"], + self.split_input_params["clip_max_weight"], ) + weighting = weighting.view(1, h * w, 1).repeat(1, 1, Ly * Lx).to(device) + + if self.split_input_params["tie_braker"]: + L_weighting = self.delta_border(Ly, Lx) + L_weighting = torch.clip(L_weighting, + self.split_input_params["clip_min_tie_weight"], + self.split_input_params["clip_max_tie_weight"]) + + L_weighting = L_weighting.view(1, 1, Ly * Lx).to(device) + weighting = weighting * L_weighting + return weighting + + def get_fold_unfold(self, x, kernel_size, stride, uf=1, df=1): # todo load once not every time, shorten code + """ + :param x: img of size (bs, c, h, w) + :return: n img crops of size (n, bs, c, kernel_size[0], kernel_size[1]) + """ + bs, nc, h, w = x.shape + + # number of crops in image + Ly = (h - kernel_size[0]) // stride[0] + 1 + Lx = (w - kernel_size[1]) // stride[1] + 1 + + if uf == 1 and df == 1: + fold_params = dict(kernel_size=kernel_size, dilation=1, padding=0, stride=stride) + unfold = torch.nn.Unfold(**fold_params) + + fold = torch.nn.Fold(output_size=x.shape[2:], **fold_params) + + weighting = self.get_weighting(kernel_size[0], kernel_size[1], Ly, Lx, x.device).to(x.dtype) + normalization = fold(weighting).view(1, 1, h, w) # normalizes the overlap + weighting = weighting.view((1, 1, kernel_size[0], kernel_size[1], Ly * Lx)) + + elif uf > 1 and df == 1: + fold_params = dict(kernel_size=kernel_size, dilation=1, padding=0, stride=stride) + unfold = torch.nn.Unfold(**fold_params) + + fold_params2 = dict(kernel_size=(kernel_size[0] * uf, kernel_size[0] * uf), + dilation=1, padding=0, + stride=(stride[0] * uf, stride[1] * uf)) + fold = torch.nn.Fold(output_size=(x.shape[2] * uf, x.shape[3] * uf), **fold_params2) + + weighting = self.get_weighting(kernel_size[0] * uf, kernel_size[1] * uf, Ly, Lx, x.device).to(x.dtype) + normalization = fold(weighting).view(1, 1, h * uf, w * uf) # normalizes the overlap + weighting = weighting.view((1, 1, kernel_size[0] * uf, kernel_size[1] * uf, Ly * Lx)) + + elif df > 1 and uf == 1: + fold_params = dict(kernel_size=kernel_size, dilation=1, padding=0, stride=stride) + unfold = torch.nn.Unfold(**fold_params) + + fold_params2 = dict(kernel_size=(kernel_size[0] // df, kernel_size[0] // df), + dilation=1, padding=0, + stride=(stride[0] // df, stride[1] // df)) + fold = torch.nn.Fold(output_size=(x.shape[2] // df, x.shape[3] // df), **fold_params2) + + weighting = self.get_weighting(kernel_size[0] // df, kernel_size[1] // df, Ly, Lx, x.device).to(x.dtype) + normalization = fold(weighting).view(1, 1, h // df, w // df) # normalizes the overlap + weighting = weighting.view((1, 1, kernel_size[0] // df, kernel_size[1] // df, Ly * Lx)) + + else: + raise NotImplementedError + + return fold, unfold, normalization, weighting + + @torch.no_grad() + def get_input(self, batch, k, return_first_stage_outputs=False, force_c_encode=False, + cond_key=None, return_original_cond=False, bs=None, return_x=False): + x = super().get_input(batch, k) + if bs is not None: + x = x[:bs] + x = x.to(self.device) + encoder_posterior = self.encode_first_stage(x) + z = self.get_first_stage_encoding(encoder_posterior).detach() + + if self.model.conditioning_key is not None and not self.force_null_conditioning: + if cond_key is None: + cond_key = self.cond_stage_key + if cond_key != self.first_stage_key: + if cond_key in ['caption', 'coordinates_bbox', "txt"]: + xc = batch[cond_key] + elif cond_key in ['class_label', 'cls']: + xc = batch + else: + xc = super().get_input(batch, cond_key).to(self.device) + else: + xc = x + if not self.cond_stage_trainable or force_c_encode: + if isinstance(xc, dict) or isinstance(xc, list): + c = self.get_learned_conditioning(xc) + else: + c = self.get_learned_conditioning(xc.to(self.device)) + else: + c = xc + if bs is not None: + c = c[:bs] + + if self.use_positional_encodings: + pos_x, pos_y = self.compute_latent_shifts(batch) + ckey = __conditioning_keys__[self.model.conditioning_key] + c = {ckey: c, 'pos_x': pos_x, 'pos_y': pos_y} + + else: + c = None + xc = None + if self.use_positional_encodings: + pos_x, pos_y = self.compute_latent_shifts(batch) + c = {'pos_x': pos_x, 'pos_y': pos_y} + out = [z, c] + if return_first_stage_outputs: + xrec = self.decode_first_stage(z) + out.extend([x, xrec]) + if return_x: + out.extend([x]) + if return_original_cond: + out.append(xc) + return out + + @torch.no_grad() + def decode_first_stage(self, z, predict_cids=False, force_not_quantize=False): + if predict_cids: + if z.dim() == 4: + z = torch.argmax(z.exp(), dim=1).long() + z = self.first_stage_model.quantize.get_codebook_entry(z, shape=None) + z = rearrange(z, 'b h w c -> b c h w').contiguous() + + z = 1. / self.scale_factor * z + return self.first_stage_model.decode(z) + + @torch.no_grad() + def encode_first_stage(self, x): + return self.first_stage_model.encode(x) + + def shared_step(self, batch, **kwargs): + x, c = self.get_input(batch, self.first_stage_key) + loss = self(x, c) + return loss + + def forward(self, x, c, *args, **kwargs): + t = torch.randint(0, self.num_timesteps, (x.shape[0],), device=self.device).long() + if self.model.conditioning_key is not None: + assert c is not None + if self.cond_stage_trainable: + c = self.get_learned_conditioning(c) + if self.shorten_cond_schedule: # TODO: drop this option + tc = self.cond_ids[t].to(self.device) + c = self.q_sample(x_start=c, t=tc, noise=torch.randn_like(c.float())) + return self.p_losses(x, c, t, *args, **kwargs) + + def apply_model(self, x_noisy, t, cond, return_ids=False): + if isinstance(cond, dict): + # hybrid case, cond is expected to be a dict + pass + else: + if not isinstance(cond, list): + cond = [cond] + key = 'c_concat' if self.model.conditioning_key == 'concat' else 'c_crossattn' + cond = {key: cond} + + x_recon = self.model(x_noisy, t, **cond) + + if isinstance(x_recon, tuple) and not return_ids: + return x_recon[0] + else: + return x_recon + + def _predict_eps_from_xstart(self, x_t, t, pred_xstart): + return (extract_into_tensor(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t - pred_xstart) / \ + extract_into_tensor(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) + + def _prior_bpd(self, x_start): + """ + Get the prior KL term for the variational lower-bound, measured in + bits-per-dim. + This term can't be optimized, as it only depends on the encoder. + :param x_start: the [N x C x ...] tensor of inputs. + :return: a batch of [N] KL values (in bits), one per batch element. + """ + batch_size = x_start.shape[0] + t = torch.tensor([self.num_timesteps - 1] * batch_size, device=x_start.device) + qt_mean, _, qt_log_variance = self.q_mean_variance(x_start, t) + kl_prior = normal_kl(mean1=qt_mean, logvar1=qt_log_variance, mean2=0.0, logvar2=0.0) + return mean_flat(kl_prior) / np.log(2.0) + + def p_losses(self, x_start, cond, t, noise=None): + noise = default(noise, lambda: torch.randn_like(x_start)) + x_noisy = self.q_sample(x_start=x_start, t=t, noise=noise) + model_output = self.apply_model(x_noisy, t, cond) + + loss_dict = {} + prefix = 'train' if self.training else 'val' + + if self.parameterization == "x0": + target = x_start + elif self.parameterization == "eps": + target = noise + elif self.parameterization == "v": + target = self.get_v(x_start, noise, t) + else: + raise NotImplementedError() + + loss_simple = self.get_loss(model_output, target, mean=False).mean([1, 2, 3]) + loss_dict.update({f'{prefix}/loss_simple': loss_simple.mean()}) + + logvar_t = self.logvar[t].to(self.device) + loss = loss_simple / torch.exp(logvar_t) + logvar_t + # loss = loss_simple / torch.exp(self.logvar) + self.logvar + if self.learn_logvar: + loss_dict.update({f'{prefix}/loss_gamma': loss.mean()}) + loss_dict.update({'logvar': self.logvar.data.mean()}) + + loss = self.l_simple_weight * loss.mean() + + loss_vlb = self.get_loss(model_output, target, mean=False).mean(dim=(1, 2, 3)) + loss_vlb = (self.lvlb_weights[t] * loss_vlb).mean() + loss_dict.update({f'{prefix}/loss_vlb': loss_vlb}) + loss += (self.original_elbo_weight * loss_vlb) + loss_dict.update({f'{prefix}/loss': loss}) + + return loss, loss_dict + + def p_mean_variance(self, x, c, t, clip_denoised: bool, return_codebook_ids=False, quantize_denoised=False, + return_x0=False, score_corrector=None, corrector_kwargs=None): + t_in = t + model_out = self.apply_model(x, t_in, c, return_ids=return_codebook_ids) + + if score_corrector is not None: + assert self.parameterization == "eps" + model_out = score_corrector.modify_score(self, model_out, x, t, c, **corrector_kwargs) + + if return_codebook_ids: + model_out, logits = model_out + + if self.parameterization == "eps": + x_recon = self.predict_start_from_noise(x, t=t, noise=model_out) + elif self.parameterization == "x0": + x_recon = model_out + else: + raise NotImplementedError() + + if clip_denoised: + x_recon.clamp_(-1., 1.) + if quantize_denoised: + x_recon, _, [_, _, indices] = self.first_stage_model.quantize(x_recon) + model_mean, posterior_variance, posterior_log_variance = self.q_posterior(x_start=x_recon, x_t=x, t=t) + if return_codebook_ids: + return model_mean, posterior_variance, posterior_log_variance, logits + elif return_x0: + return model_mean, posterior_variance, posterior_log_variance, x_recon + else: + return model_mean, posterior_variance, posterior_log_variance + + @torch.no_grad() + def p_sample(self, x, c, t, clip_denoised=False, repeat_noise=False, + return_codebook_ids=False, quantize_denoised=False, return_x0=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None): + b, *_, device = *x.shape, x.device + outputs = self.p_mean_variance(x=x, c=c, t=t, clip_denoised=clip_denoised, + return_codebook_ids=return_codebook_ids, + quantize_denoised=quantize_denoised, + return_x0=return_x0, + score_corrector=score_corrector, corrector_kwargs=corrector_kwargs) + if return_codebook_ids: + raise DeprecationWarning("Support dropped.") + model_mean, _, model_log_variance, logits = outputs + elif return_x0: + model_mean, _, model_log_variance, x0 = outputs + else: + model_mean, _, model_log_variance = outputs + + noise = noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + # no noise when t == 0 + nonzero_mask = (1 - (t == 0).float()).reshape(b, *((1,) * (len(x.shape) - 1))) + + if return_codebook_ids: + return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise, logits.argmax(dim=1) + if return_x0: + return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise, x0 + else: + return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise + + @torch.no_grad() + def progressive_denoising(self, cond, shape, verbose=True, callback=None, quantize_denoised=False, + img_callback=None, mask=None, x0=None, temperature=1., noise_dropout=0., + score_corrector=None, corrector_kwargs=None, batch_size=None, x_T=None, start_T=None, + log_every_t=None): + if not log_every_t: + log_every_t = self.log_every_t + timesteps = self.num_timesteps + if batch_size is not None: + b = batch_size if batch_size is not None else shape[0] + shape = [batch_size] + list(shape) + else: + b = batch_size = shape[0] + if x_T is None: + img = torch.randn(shape, device=self.device) + else: + img = x_T + intermediates = [] + if cond is not None: + if isinstance(cond, dict): + cond = {key: cond[key][:batch_size] if not isinstance(cond[key], list) else + list(map(lambda x: x[:batch_size], cond[key])) for key in cond} + else: + cond = [c[:batch_size] for c in cond] if isinstance(cond, list) else cond[:batch_size] + + if start_T is not None: + timesteps = min(timesteps, start_T) + iterator = tqdm(reversed(range(0, timesteps)), desc='Progressive Generation', + total=timesteps) if verbose else reversed( + range(0, timesteps)) + if type(temperature) == float: + temperature = [temperature] * timesteps + + for i in iterator: + ts = torch.full((b,), i, device=self.device, dtype=torch.long) + if self.shorten_cond_schedule: + assert self.model.conditioning_key != 'hybrid' + tc = self.cond_ids[ts].to(cond.device) + cond = self.q_sample(x_start=cond, t=tc, noise=torch.randn_like(cond)) + + img, x0_partial = self.p_sample(img, cond, ts, + clip_denoised=self.clip_denoised, + quantize_denoised=quantize_denoised, return_x0=True, + temperature=temperature[i], noise_dropout=noise_dropout, + score_corrector=score_corrector, corrector_kwargs=corrector_kwargs) + if mask is not None: + assert x0 is not None + img_orig = self.q_sample(x0, ts) + img = img_orig * mask + (1. - mask) * img + + if i % log_every_t == 0 or i == timesteps - 1: + intermediates.append(x0_partial) + if callback: callback(i) + if img_callback: img_callback(img, i) + return img, intermediates + + @torch.no_grad() + def p_sample_loop(self, cond, shape, return_intermediates=False, + x_T=None, verbose=True, callback=None, timesteps=None, quantize_denoised=False, + mask=None, x0=None, img_callback=None, start_T=None, + log_every_t=None): + + if not log_every_t: + log_every_t = self.log_every_t + device = self.betas.device + b = shape[0] + if x_T is None: + img = torch.randn(shape, device=device) + else: + img = x_T + + intermediates = [img] + if timesteps is None: + timesteps = self.num_timesteps + + if start_T is not None: + timesteps = min(timesteps, start_T) + iterator = tqdm(reversed(range(0, timesteps)), desc='Sampling t', total=timesteps) if verbose else reversed( + range(0, timesteps)) + + if mask is not None: + assert x0 is not None + assert x0.shape[2:3] == mask.shape[2:3] # spatial size has to match + + for i in iterator: + ts = torch.full((b,), i, device=device, dtype=torch.long) + if self.shorten_cond_schedule: + assert self.model.conditioning_key != 'hybrid' + tc = self.cond_ids[ts].to(cond.device) + cond = self.q_sample(x_start=cond, t=tc, noise=torch.randn_like(cond)) + + img = self.p_sample(img, cond, ts, + clip_denoised=self.clip_denoised, + quantize_denoised=quantize_denoised) + if mask is not None: + img_orig = self.q_sample(x0, ts) + img = img_orig * mask + (1. - mask) * img + + if i % log_every_t == 0 or i == timesteps - 1: + intermediates.append(img) + if callback: callback(i) + if img_callback: img_callback(img, i) + + if return_intermediates: + return img, intermediates + return img + + @torch.no_grad() + def sample(self, cond, batch_size=16, return_intermediates=False, x_T=None, + verbose=True, timesteps=None, quantize_denoised=False, + mask=None, x0=None, shape=None, **kwargs): + if shape is None: + shape = (batch_size, self.channels, self.image_size, self.image_size) + if cond is not None: + if isinstance(cond, dict): + cond = {key: cond[key][:batch_size] if not isinstance(cond[key], list) else + list(map(lambda x: x[:batch_size], cond[key])) for key in cond} + else: + cond = [c[:batch_size] for c in cond] if isinstance(cond, list) else cond[:batch_size] + return self.p_sample_loop(cond, + shape, + return_intermediates=return_intermediates, x_T=x_T, + verbose=verbose, timesteps=timesteps, quantize_denoised=quantize_denoised, + mask=mask, x0=x0) + + @torch.no_grad() + def sample_log(self, cond, batch_size, ddim, ddim_steps, **kwargs): + if ddim: + ddim_sampler = DDIMSampler(self) + shape = (self.channels, self.image_size, self.image_size) + samples, intermediates = ddim_sampler.sample(ddim_steps, batch_size, + shape, cond, verbose=False, **kwargs) + + else: + samples, intermediates = self.sample(cond=cond, batch_size=batch_size, + return_intermediates=True, **kwargs) + + return samples, intermediates + + @torch.no_grad() + def get_unconditional_conditioning(self, batch_size, null_label=None): + if null_label is not None: + xc = null_label + if isinstance(xc, ListConfig): + xc = list(xc) + if isinstance(xc, dict) or isinstance(xc, list): + c = self.get_learned_conditioning(xc) + else: + if hasattr(xc, "to"): + xc = xc.to(self.device) + c = self.get_learned_conditioning(xc) + else: + if self.cond_stage_key in ["class_label", "cls"]: + xc = self.cond_stage_model.get_unconditional_conditioning(batch_size, device=self.device) + return self.get_learned_conditioning(xc) + else: + raise NotImplementedError("todo") + if isinstance(c, list): # in case the encoder gives us a list + for i in range(len(c)): + c[i] = repeat(c[i], '1 ... -> b ...', b=batch_size).to(self.device) + else: + c = repeat(c, '1 ... -> b ...', b=batch_size).to(self.device) + return c + + @torch.no_grad() + def log_images(self, batch, N=8, n_row=4, sample=True, ddim_steps=50, ddim_eta=0., return_keys=None, + quantize_denoised=True, inpaint=True, plot_denoise_rows=False, plot_progressive_rows=True, + plot_diffusion_rows=True, unconditional_guidance_scale=1., unconditional_guidance_label=None, + use_ema_scope=True, + **kwargs): + ema_scope = self.ema_scope if use_ema_scope else nullcontext + use_ddim = ddim_steps is not None + + log = dict() + z, c, x, xrec, xc = self.get_input(batch, self.first_stage_key, + return_first_stage_outputs=True, + force_c_encode=True, + return_original_cond=True, + bs=N) + N = min(x.shape[0], N) + n_row = min(x.shape[0], n_row) + log["inputs"] = x + log["reconstruction"] = xrec + if self.model.conditioning_key is not None: + if hasattr(self.cond_stage_model, "decode"): + xc = self.cond_stage_model.decode(c) + log["conditioning"] = xc + elif self.cond_stage_key in ["caption", "txt"]: + xc = log_txt_as_img((x.shape[2], x.shape[3]), batch[self.cond_stage_key], size=x.shape[2] // 25) + log["conditioning"] = xc + elif self.cond_stage_key in ['class_label', "cls"]: + try: + xc = log_txt_as_img((x.shape[2], x.shape[3]), batch["human_label"], size=x.shape[2] // 25) + log['conditioning'] = xc + except KeyError: + # probably no "human_label" in batch + pass + elif isimage(xc): + log["conditioning"] = xc + if ismap(xc): + log["original_conditioning"] = self.to_rgb(xc) + + if plot_diffusion_rows: + # get diffusion row + diffusion_row = list() + z_start = z[:n_row] + for t in range(self.num_timesteps): + if t % self.log_every_t == 0 or t == self.num_timesteps - 1: + t = repeat(torch.tensor([t]), '1 -> b', b=n_row) + t = t.to(self.device).long() + noise = torch.randn_like(z_start) + z_noisy = self.q_sample(x_start=z_start, t=t, noise=noise) + diffusion_row.append(self.decode_first_stage(z_noisy)) + + diffusion_row = torch.stack(diffusion_row) # n_log_step, n_row, C, H, W + diffusion_grid = rearrange(diffusion_row, 'n b c h w -> b n c h w') + diffusion_grid = rearrange(diffusion_grid, 'b n c h w -> (b n) c h w') + diffusion_grid = make_grid(diffusion_grid, nrow=diffusion_row.shape[0]) + log["diffusion_row"] = diffusion_grid + + if sample: + # get denoise row + with ema_scope("Sampling"): + samples, z_denoise_row = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta) + # samples, z_denoise_row = self.sample(cond=c, batch_size=N, return_intermediates=True) + x_samples = self.decode_first_stage(samples) + log["samples"] = x_samples + if plot_denoise_rows: + denoise_grid = self._get_denoise_row_from_list(z_denoise_row) + log["denoise_row"] = denoise_grid + + if quantize_denoised and not isinstance(self.first_stage_model, AutoencoderKL) and not isinstance( + self.first_stage_model, IdentityFirstStage): + # also display when quantizing x0 while sampling + with ema_scope("Plotting Quantized Denoised"): + samples, z_denoise_row = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta, + quantize_denoised=True) + # samples, z_denoise_row = self.sample(cond=c, batch_size=N, return_intermediates=True, + # quantize_denoised=True) + x_samples = self.decode_first_stage(samples.to(self.device)) + log["samples_x0_quantized"] = x_samples + + if unconditional_guidance_scale > 1.0: + uc = self.get_unconditional_conditioning(N, unconditional_guidance_label) + if self.model.conditioning_key == "crossattn-adm": + uc = {"c_crossattn": [uc], "c_adm": c["c_adm"]} + with ema_scope("Sampling with classifier-free guidance"): + samples_cfg, _ = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=uc, + ) + x_samples_cfg = self.decode_first_stage(samples_cfg) + log[f"samples_cfg_scale_{unconditional_guidance_scale:.2f}"] = x_samples_cfg + + if inpaint: + # make a simple center square + b, h, w = z.shape[0], z.shape[2], z.shape[3] + mask = torch.ones(N, h, w).to(self.device) + # zeros will be filled in + mask[:, h // 4:3 * h // 4, w // 4:3 * w // 4] = 0. + mask = mask[:, None, ...] + with ema_scope("Plotting Inpaint"): + samples, _ = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, eta=ddim_eta, + ddim_steps=ddim_steps, x0=z[:N], mask=mask) + x_samples = self.decode_first_stage(samples.to(self.device)) + log["samples_inpainting"] = x_samples + log["mask"] = mask + + # outpaint + mask = 1. - mask + with ema_scope("Plotting Outpaint"): + samples, _ = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, eta=ddim_eta, + ddim_steps=ddim_steps, x0=z[:N], mask=mask) + x_samples = self.decode_first_stage(samples.to(self.device)) + log["samples_outpainting"] = x_samples + + if plot_progressive_rows: + with ema_scope("Plotting Progressives"): + img, progressives = self.progressive_denoising(c, + shape=(self.channels, self.image_size, self.image_size), + batch_size=N) + prog_row = self._get_denoise_row_from_list(progressives, desc="Progressive Generation") + log["progressive_row"] = prog_row + + if return_keys: + if np.intersect1d(list(log.keys()), return_keys).shape[0] == 0: + return log + else: + return {key: log[key] for key in return_keys} + return log + + def configure_optimizers(self): + lr = self.learning_rate + params = list(self.model.parameters()) + if self.cond_stage_trainable: + print(f"{self.__class__.__name__}: Also optimizing conditioner params!") + params = params + list(self.cond_stage_model.parameters()) + if self.learn_logvar: + print('Diffusion model optimizing logvar') + params.append(self.logvar) + opt = torch.optim.AdamW(params, lr=lr) + if self.use_scheduler: + assert 'target' in self.scheduler_config + scheduler = instantiate_from_config(self.scheduler_config) + + print("Setting up LambdaLR scheduler...") + scheduler = [ + { + 'scheduler': LambdaLR(opt, lr_lambda=scheduler.schedule), + 'interval': 'step', + 'frequency': 1 + }] + return [opt], scheduler + return opt + + @torch.no_grad() + def to_rgb(self, x): + x = x.float() + if not hasattr(self, "colorize"): + self.colorize = torch.randn(3, x.shape[1], 1, 1).to(x) + x = nn.functional.conv2d(x, weight=self.colorize) + x = 2. * (x - x.min()) / (x.max() - x.min()) - 1. + return x + + +class DiffusionWrapper(pl.LightningModule): + def __init__(self, diff_model_config, conditioning_key): + super().__init__() + self.sequential_cross_attn = diff_model_config.pop("sequential_crossattn", False) + self.diffusion_model = instantiate_from_config(diff_model_config) + self.conditioning_key = conditioning_key + assert self.conditioning_key in [None, 'concat', 'crossattn', 'hybrid', 'adm', 'hybrid-adm', 'crossattn-adm'] + + def forward(self, x, t, c_concat: list = None, c_crossattn: list = None, c_adm=None): + if self.conditioning_key is None: + out = self.diffusion_model(x, t) + elif self.conditioning_key == 'concat': + xc = torch.cat([x] + c_concat, dim=1) + out = self.diffusion_model(xc, t) + elif self.conditioning_key == 'crossattn': + if not self.sequential_cross_attn: + cc = torch.cat(c_crossattn, 1) + else: + cc = c_crossattn + out = self.diffusion_model(x, t, context=cc) + elif self.conditioning_key == 'hybrid': + xc = torch.cat([x] + c_concat, dim=1) + cc = torch.cat(c_crossattn, 1) + out = self.diffusion_model(xc, t, context=cc) + elif self.conditioning_key == 'hybrid-adm': + assert c_adm is not None + xc = torch.cat([x] + c_concat, dim=1) + cc = torch.cat(c_crossattn, 1) + out = self.diffusion_model(xc, t, context=cc, y=c_adm) + elif self.conditioning_key == 'crossattn-adm': + assert c_adm is not None + cc = torch.cat(c_crossattn, 1) + out = self.diffusion_model(x, t, context=cc, y=c_adm) + elif self.conditioning_key == 'adm': + cc = c_crossattn[0] + out = self.diffusion_model(x, t, y=cc) + else: + raise NotImplementedError() + + return out + + +class LatentUpscaleDiffusion(LatentDiffusion): + def __init__(self, *args, low_scale_config, low_scale_key="LR", noise_level_key=None, **kwargs): + super().__init__(*args, **kwargs) + # assumes that neither the cond_stage nor the low_scale_model contain trainable params + assert not self.cond_stage_trainable + self.instantiate_low_stage(low_scale_config) + self.low_scale_key = low_scale_key + self.noise_level_key = noise_level_key + + def instantiate_low_stage(self, config): + model = instantiate_from_config(config) + self.low_scale_model = model.eval() + self.low_scale_model.train = disabled_train + for param in self.low_scale_model.parameters(): + param.requires_grad = False + + @torch.no_grad() + def get_input(self, batch, k, cond_key=None, bs=None, log_mode=False): + if not log_mode: + z, c = super().get_input(batch, k, force_c_encode=True, bs=bs) + else: + z, c, x, xrec, xc = super().get_input(batch, self.first_stage_key, return_first_stage_outputs=True, + force_c_encode=True, return_original_cond=True, bs=bs) + x_low = batch[self.low_scale_key][:bs] + x_low = rearrange(x_low, 'b h w c -> b c h w') + x_low = x_low.to(memory_format=torch.contiguous_format).float() + zx, noise_level = self.low_scale_model(x_low) + if self.noise_level_key is not None: + # get noise level from batch instead, e.g. when extracting a custom noise level for bsr + raise NotImplementedError('TODO') + + all_conds = {"c_concat": [zx], "c_crossattn": [c], "c_adm": noise_level} + if log_mode: + # TODO: maybe disable if too expensive + x_low_rec = self.low_scale_model.decode(zx) + return z, all_conds, x, xrec, xc, x_low, x_low_rec, noise_level + return z, all_conds + + @torch.no_grad() + def log_images(self, batch, N=8, n_row=4, sample=True, ddim_steps=200, ddim_eta=1., return_keys=None, + plot_denoise_rows=False, plot_progressive_rows=True, plot_diffusion_rows=True, + unconditional_guidance_scale=1., unconditional_guidance_label=None, use_ema_scope=True, + **kwargs): + ema_scope = self.ema_scope if use_ema_scope else nullcontext + use_ddim = ddim_steps is not None + + log = dict() + z, c, x, xrec, xc, x_low, x_low_rec, noise_level = self.get_input(batch, self.first_stage_key, bs=N, + log_mode=True) + N = min(x.shape[0], N) + n_row = min(x.shape[0], n_row) + log["inputs"] = x + log["reconstruction"] = xrec + log["x_lr"] = x_low + log[f"x_lr_rec_@noise_levels{'-'.join(map(lambda x: str(x), list(noise_level.cpu().numpy())))}"] = x_low_rec + if self.model.conditioning_key is not None: + if hasattr(self.cond_stage_model, "decode"): + xc = self.cond_stage_model.decode(c) + log["conditioning"] = xc + elif self.cond_stage_key in ["caption", "txt"]: + xc = log_txt_as_img((x.shape[2], x.shape[3]), batch[self.cond_stage_key], size=x.shape[2] // 25) + log["conditioning"] = xc + elif self.cond_stage_key in ['class_label', 'cls']: + xc = log_txt_as_img((x.shape[2], x.shape[3]), batch["human_label"], size=x.shape[2] // 25) + log['conditioning'] = xc + elif isimage(xc): + log["conditioning"] = xc + if ismap(xc): + log["original_conditioning"] = self.to_rgb(xc) + + if plot_diffusion_rows: + # get diffusion row + diffusion_row = list() + z_start = z[:n_row] + for t in range(self.num_timesteps): + if t % self.log_every_t == 0 or t == self.num_timesteps - 1: + t = repeat(torch.tensor([t]), '1 -> b', b=n_row) + t = t.to(self.device).long() + noise = torch.randn_like(z_start) + z_noisy = self.q_sample(x_start=z_start, t=t, noise=noise) + diffusion_row.append(self.decode_first_stage(z_noisy)) + + diffusion_row = torch.stack(diffusion_row) # n_log_step, n_row, C, H, W + diffusion_grid = rearrange(diffusion_row, 'n b c h w -> b n c h w') + diffusion_grid = rearrange(diffusion_grid, 'b n c h w -> (b n) c h w') + diffusion_grid = make_grid(diffusion_grid, nrow=diffusion_row.shape[0]) + log["diffusion_row"] = diffusion_grid + + if sample: + # get denoise row + with ema_scope("Sampling"): + samples, z_denoise_row = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta) + # samples, z_denoise_row = self.sample(cond=c, batch_size=N, return_intermediates=True) + x_samples = self.decode_first_stage(samples) + log["samples"] = x_samples + if plot_denoise_rows: + denoise_grid = self._get_denoise_row_from_list(z_denoise_row) + log["denoise_row"] = denoise_grid + + if unconditional_guidance_scale > 1.0: + uc_tmp = self.get_unconditional_conditioning(N, unconditional_guidance_label) + # TODO explore better "unconditional" choices for the other keys + # maybe guide away from empty text label and highest noise level and maximally degraded zx? + uc = dict() + for k in c: + if k == "c_crossattn": + assert isinstance(c[k], list) and len(c[k]) == 1 + uc[k] = [uc_tmp] + elif k == "c_adm": # todo: only run with text-based guidance? + assert isinstance(c[k], torch.Tensor) + #uc[k] = torch.ones_like(c[k]) * self.low_scale_model.max_noise_level + uc[k] = c[k] + elif isinstance(c[k], list): + uc[k] = [c[k][i] for i in range(len(c[k]))] + else: + uc[k] = c[k] + + with ema_scope("Sampling with classifier-free guidance"): + samples_cfg, _ = self.sample_log(cond=c, batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=uc, + ) + x_samples_cfg = self.decode_first_stage(samples_cfg) + log[f"samples_cfg_scale_{unconditional_guidance_scale:.2f}"] = x_samples_cfg + + if plot_progressive_rows: + with ema_scope("Plotting Progressives"): + img, progressives = self.progressive_denoising(c, + shape=(self.channels, self.image_size, self.image_size), + batch_size=N) + prog_row = self._get_denoise_row_from_list(progressives, desc="Progressive Generation") + log["progressive_row"] = prog_row + + return log + + +class LatentFinetuneDiffusion(LatentDiffusion): + """ + Basis for different finetunas, such as inpainting or depth2image + To disable finetuning mode, set finetune_keys to None + """ + + def __init__(self, + concat_keys: tuple, + finetune_keys=("model.diffusion_model.input_blocks.0.0.weight", + "model_ema.diffusion_modelinput_blocks00weight" + ), + keep_finetune_dims=4, + # if model was trained without concat mode before and we would like to keep these channels + c_concat_log_start=None, # to log reconstruction of c_concat codes + c_concat_log_end=None, + *args, **kwargs + ): + ckpt_path = kwargs.pop("ckpt_path", None) + ignore_keys = kwargs.pop("ignore_keys", list()) + super().__init__(*args, **kwargs) + self.finetune_keys = finetune_keys + self.concat_keys = concat_keys + self.keep_dims = keep_finetune_dims + self.c_concat_log_start = c_concat_log_start + self.c_concat_log_end = c_concat_log_end + if exists(self.finetune_keys): assert exists(ckpt_path), 'can only finetune from a given checkpoint' + if exists(ckpt_path): + self.init_from_ckpt(ckpt_path, ignore_keys) + + def init_from_ckpt(self, path, ignore_keys=list(), only_model=False): + sd = torch.load(path, map_location="cpu") + if "state_dict" in list(sd.keys()): + sd = sd["state_dict"] + keys = list(sd.keys()) + for k in keys: + for ik in ignore_keys: + if k.startswith(ik): + print("Deleting key {} from state_dict.".format(k)) + del sd[k] + + # make it explicit, finetune by including extra input channels + if exists(self.finetune_keys) and k in self.finetune_keys: + new_entry = None + for name, param in self.named_parameters(): + if name in self.finetune_keys: + print( + f"modifying key '{name}' and keeping its original {self.keep_dims} (channels) dimensions only") + new_entry = torch.zeros_like(param) # zero init + assert exists(new_entry), 'did not find matching parameter to modify' + new_entry[:, :self.keep_dims, ...] = sd[k] + sd[k] = new_entry + + missing, unexpected = self.load_state_dict(sd, strict=False) if not only_model else self.model.load_state_dict( + sd, strict=False) + print(f"Restored from {path} with {len(missing)} missing and {len(unexpected)} unexpected keys") + if len(missing) > 0: + print(f"Missing Keys: {missing}") + if len(unexpected) > 0: + print(f"Unexpected Keys: {unexpected}") + + @torch.no_grad() + def log_images(self, batch, N=8, n_row=4, sample=True, ddim_steps=200, ddim_eta=1., return_keys=None, + quantize_denoised=True, inpaint=True, plot_denoise_rows=False, plot_progressive_rows=True, + plot_diffusion_rows=True, unconditional_guidance_scale=1., unconditional_guidance_label=None, + use_ema_scope=True, + **kwargs): + ema_scope = self.ema_scope if use_ema_scope else nullcontext + use_ddim = ddim_steps is not None + + log = dict() + z, c, x, xrec, xc = self.get_input(batch, self.first_stage_key, bs=N, return_first_stage_outputs=True) + c_cat, c = c["c_concat"][0], c["c_crossattn"][0] + N = min(x.shape[0], N) + n_row = min(x.shape[0], n_row) + log["inputs"] = x + log["reconstruction"] = xrec + if self.model.conditioning_key is not None: + if hasattr(self.cond_stage_model, "decode"): + xc = self.cond_stage_model.decode(c) + log["conditioning"] = xc + elif self.cond_stage_key in ["caption", "txt"]: + xc = log_txt_as_img((x.shape[2], x.shape[3]), batch[self.cond_stage_key], size=x.shape[2] // 25) + log["conditioning"] = xc + elif self.cond_stage_key in ['class_label', 'cls']: + xc = log_txt_as_img((x.shape[2], x.shape[3]), batch["human_label"], size=x.shape[2] // 25) + log['conditioning'] = xc + elif isimage(xc): + log["conditioning"] = xc + if ismap(xc): + log["original_conditioning"] = self.to_rgb(xc) + + if not (self.c_concat_log_start is None and self.c_concat_log_end is None): + log["c_concat_decoded"] = self.decode_first_stage(c_cat[:, self.c_concat_log_start:self.c_concat_log_end]) + + if plot_diffusion_rows: + # get diffusion row + diffusion_row = list() + z_start = z[:n_row] + for t in range(self.num_timesteps): + if t % self.log_every_t == 0 or t == self.num_timesteps - 1: + t = repeat(torch.tensor([t]), '1 -> b', b=n_row) + t = t.to(self.device).long() + noise = torch.randn_like(z_start) + z_noisy = self.q_sample(x_start=z_start, t=t, noise=noise) + diffusion_row.append(self.decode_first_stage(z_noisy)) + + diffusion_row = torch.stack(diffusion_row) # n_log_step, n_row, C, H, W + diffusion_grid = rearrange(diffusion_row, 'n b c h w -> b n c h w') + diffusion_grid = rearrange(diffusion_grid, 'b n c h w -> (b n) c h w') + diffusion_grid = make_grid(diffusion_grid, nrow=diffusion_row.shape[0]) + log["diffusion_row"] = diffusion_grid + + if sample: + # get denoise row + with ema_scope("Sampling"): + samples, z_denoise_row = self.sample_log(cond={"c_concat": [c_cat], "c_crossattn": [c]}, + batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta) + # samples, z_denoise_row = self.sample(cond=c, batch_size=N, return_intermediates=True) + x_samples = self.decode_first_stage(samples) + log["samples"] = x_samples + if plot_denoise_rows: + denoise_grid = self._get_denoise_row_from_list(z_denoise_row) + log["denoise_row"] = denoise_grid + + if unconditional_guidance_scale > 1.0: + uc_cross = self.get_unconditional_conditioning(N, unconditional_guidance_label) + uc_cat = c_cat + uc_full = {"c_concat": [uc_cat], "c_crossattn": [uc_cross]} + with ema_scope("Sampling with classifier-free guidance"): + samples_cfg, _ = self.sample_log(cond={"c_concat": [c_cat], "c_crossattn": [c]}, + batch_size=N, ddim=use_ddim, + ddim_steps=ddim_steps, eta=ddim_eta, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=uc_full, + ) + x_samples_cfg = self.decode_first_stage(samples_cfg) + log[f"samples_cfg_scale_{unconditional_guidance_scale:.2f}"] = x_samples_cfg + + return log + + +class LatentInpaintDiffusion(LatentFinetuneDiffusion): + """ + can either run as pure inpainting model (only concat mode) or with mixed conditionings, + e.g. mask as concat and text via cross-attn. + To disable finetuning mode, set finetune_keys to None + """ + + def __init__(self, + concat_keys=("mask", "masked_image"), + masked_image_key="masked_image", + *args, **kwargs + ): + super().__init__(concat_keys, *args, **kwargs) + self.masked_image_key = masked_image_key + assert self.masked_image_key in concat_keys + + @torch.no_grad() + def get_input(self, batch, k, cond_key=None, bs=None, return_first_stage_outputs=False): + # note: restricted to non-trainable encoders currently + assert not self.cond_stage_trainable, 'trainable cond stages not yet supported for inpainting' + z, c, x, xrec, xc = super().get_input(batch, self.first_stage_key, return_first_stage_outputs=True, + force_c_encode=True, return_original_cond=True, bs=bs) + + assert exists(self.concat_keys) + c_cat = list() + for ck in self.concat_keys: + cc = rearrange(batch[ck], 'b h w c -> b c h w').to(memory_format=torch.contiguous_format).float() + if bs is not None: + cc = cc[:bs] + cc = cc.to(self.device) + bchw = z.shape + if ck != self.masked_image_key: + cc = torch.nn.functional.interpolate(cc, size=bchw[-2:]) + else: + cc = self.get_first_stage_encoding(self.encode_first_stage(cc)) + c_cat.append(cc) + c_cat = torch.cat(c_cat, dim=1) + all_conds = {"c_concat": [c_cat], "c_crossattn": [c]} + if return_first_stage_outputs: + return z, all_conds, x, xrec, xc + return z, all_conds + + @torch.no_grad() + def log_images(self, *args, **kwargs): + log = super(LatentInpaintDiffusion, self).log_images(*args, **kwargs) + log["masked_image"] = rearrange(args[0]["masked_image"], + 'b h w c -> b c h w').to(memory_format=torch.contiguous_format).float() + return log + + +class LatentDepth2ImageDiffusion(LatentFinetuneDiffusion): + """ + condition on monocular depth estimation + """ + + def __init__(self, depth_stage_config, concat_keys=("midas_in",), *args, **kwargs): + super().__init__(concat_keys=concat_keys, *args, **kwargs) + self.depth_model = instantiate_from_config(depth_stage_config) + self.depth_stage_key = concat_keys[0] + + @torch.no_grad() + def get_input(self, batch, k, cond_key=None, bs=None, return_first_stage_outputs=False): + # note: restricted to non-trainable encoders currently + assert not self.cond_stage_trainable, 'trainable cond stages not yet supported for depth2img' + z, c, x, xrec, xc = super().get_input(batch, self.first_stage_key, return_first_stage_outputs=True, + force_c_encode=True, return_original_cond=True, bs=bs) + + assert exists(self.concat_keys) + assert len(self.concat_keys) == 1 + c_cat = list() + for ck in self.concat_keys: + cc = batch[ck] + if bs is not None: + cc = cc[:bs] + cc = cc.to(self.device) + cc = self.depth_model(cc) + cc = torch.nn.functional.interpolate( + cc, + size=z.shape[2:], + mode="bicubic", + align_corners=False, + ) + + depth_min, depth_max = torch.amin(cc, dim=[1, 2, 3], keepdim=True), torch.amax(cc, dim=[1, 2, 3], + keepdim=True) + cc = 2. * (cc - depth_min) / (depth_max - depth_min + 0.001) - 1. + c_cat.append(cc) + c_cat = torch.cat(c_cat, dim=1) + all_conds = {"c_concat": [c_cat], "c_crossattn": [c]} + if return_first_stage_outputs: + return z, all_conds, x, xrec, xc + return z, all_conds + + @torch.no_grad() + def log_images(self, *args, **kwargs): + log = super().log_images(*args, **kwargs) + depth = self.depth_model(args[0][self.depth_stage_key]) + depth_min, depth_max = torch.amin(depth, dim=[1, 2, 3], keepdim=True), \ + torch.amax(depth, dim=[1, 2, 3], keepdim=True) + log["depth"] = 2. * (depth - depth_min) / (depth_max - depth_min) - 1. + return log + + +class LatentUpscaleFinetuneDiffusion(LatentFinetuneDiffusion): + """ + condition on low-res image (and optionally on some spatial noise augmentation) + """ + def __init__(self, concat_keys=("lr",), reshuffle_patch_size=None, + low_scale_config=None, low_scale_key=None, *args, **kwargs): + super().__init__(concat_keys=concat_keys, *args, **kwargs) + self.reshuffle_patch_size = reshuffle_patch_size + self.low_scale_model = None + if low_scale_config is not None: + print("Initializing a low-scale model") + assert exists(low_scale_key) + self.instantiate_low_stage(low_scale_config) + self.low_scale_key = low_scale_key + + def instantiate_low_stage(self, config): + model = instantiate_from_config(config) + self.low_scale_model = model.eval() + self.low_scale_model.train = disabled_train + for param in self.low_scale_model.parameters(): + param.requires_grad = False + + @torch.no_grad() + def get_input(self, batch, k, cond_key=None, bs=None, return_first_stage_outputs=False): + # note: restricted to non-trainable encoders currently + assert not self.cond_stage_trainable, 'trainable cond stages not yet supported for upscaling-ft' + z, c, x, xrec, xc = super().get_input(batch, self.first_stage_key, return_first_stage_outputs=True, + force_c_encode=True, return_original_cond=True, bs=bs) + + assert exists(self.concat_keys) + assert len(self.concat_keys) == 1 + # optionally make spatial noise_level here + c_cat = list() + noise_level = None + for ck in self.concat_keys: + cc = batch[ck] + cc = rearrange(cc, 'b h w c -> b c h w') + if exists(self.reshuffle_patch_size): + assert isinstance(self.reshuffle_patch_size, int) + cc = rearrange(cc, 'b c (p1 h) (p2 w) -> b (p1 p2 c) h w', + p1=self.reshuffle_patch_size, p2=self.reshuffle_patch_size) + if bs is not None: + cc = cc[:bs] + cc = cc.to(self.device) + if exists(self.low_scale_model) and ck == self.low_scale_key: + cc, noise_level = self.low_scale_model(cc) + c_cat.append(cc) + c_cat = torch.cat(c_cat, dim=1) + if exists(noise_level): + all_conds = {"c_concat": [c_cat], "c_crossattn": [c], "c_adm": noise_level} + else: + all_conds = {"c_concat": [c_cat], "c_crossattn": [c]} + if return_first_stage_outputs: + return z, all_conds, x, xrec, xc + return z, all_conds + + @torch.no_grad() + def log_images(self, *args, **kwargs): + log = super().log_images(*args, **kwargs) + log["lr"] = rearrange(args[0]["lr"], 'b h w c -> b c h w') + return log diff --git a/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/__init__.py b/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/__init__.py new file mode 100644 index 0000000..7427f38 --- /dev/null +++ b/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/__init__.py @@ -0,0 +1 @@ +from .sampler import DPMSolverSampler \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/dpm_solver.py b/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/dpm_solver.py new file mode 100644 index 0000000..095e5ba --- /dev/null +++ b/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/dpm_solver.py @@ -0,0 +1,1154 @@ +import torch +import torch.nn.functional as F +import math +from tqdm import tqdm + + +class NoiseScheduleVP: + def __init__( + self, + schedule='discrete', + betas=None, + alphas_cumprod=None, + continuous_beta_0=0.1, + continuous_beta_1=20., + ): + """Create a wrapper class for the forward SDE (VP type). + *** + Update: We support discrete-time diffusion models by implementing a picewise linear interpolation for log_alpha_t. + We recommend to use schedule='discrete' for the discrete-time diffusion models, especially for high-resolution images. + *** + The forward SDE ensures that the condition distribution q_{t|0}(x_t | x_0) = N ( alpha_t * x_0, sigma_t^2 * I ). + We further define lambda_t = log(alpha_t) - log(sigma_t), which is the half-logSNR (described in the DPM-Solver paper). + Therefore, we implement the functions for computing alpha_t, sigma_t and lambda_t. For t in [0, T], we have: + log_alpha_t = self.marginal_log_mean_coeff(t) + sigma_t = self.marginal_std(t) + lambda_t = self.marginal_lambda(t) + Moreover, as lambda(t) is an invertible function, we also support its inverse function: + t = self.inverse_lambda(lambda_t) + =============================================================== + We support both discrete-time DPMs (trained on n = 0, 1, ..., N-1) and continuous-time DPMs (trained on t in [t_0, T]). + 1. For discrete-time DPMs: + For discrete-time DPMs trained on n = 0, 1, ..., N-1, we convert the discrete steps to continuous time steps by: + t_i = (i + 1) / N + e.g. for N = 1000, we have t_0 = 1e-3 and T = t_{N-1} = 1. + We solve the corresponding diffusion ODE from time T = 1 to time t_0 = 1e-3. + Args: + betas: A `torch.Tensor`. The beta array for the discrete-time DPM. (See the original DDPM paper for details) + alphas_cumprod: A `torch.Tensor`. The cumprod alphas for the discrete-time DPM. (See the original DDPM paper for details) + Note that we always have alphas_cumprod = cumprod(betas). Therefore, we only need to set one of `betas` and `alphas_cumprod`. + **Important**: Please pay special attention for the args for `alphas_cumprod`: + The `alphas_cumprod` is the \hat{alpha_n} arrays in the notations of DDPM. Specifically, DDPMs assume that + q_{t_n | 0}(x_{t_n} | x_0) = N ( \sqrt{\hat{alpha_n}} * x_0, (1 - \hat{alpha_n}) * I ). + Therefore, the notation \hat{alpha_n} is different from the notation alpha_t in DPM-Solver. In fact, we have + alpha_{t_n} = \sqrt{\hat{alpha_n}}, + and + log(alpha_{t_n}) = 0.5 * log(\hat{alpha_n}). + 2. For continuous-time DPMs: + We support two types of VPSDEs: linear (DDPM) and cosine (improved-DDPM). The hyperparameters for the noise + schedule are the default settings in DDPM and improved-DDPM: + Args: + beta_min: A `float` number. The smallest beta for the linear schedule. + beta_max: A `float` number. The largest beta for the linear schedule. + cosine_s: A `float` number. The hyperparameter in the cosine schedule. + cosine_beta_max: A `float` number. The hyperparameter in the cosine schedule. + T: A `float` number. The ending time of the forward process. + =============================================================== + Args: + schedule: A `str`. The noise schedule of the forward SDE. 'discrete' for discrete-time DPMs, + 'linear' or 'cosine' for continuous-time DPMs. + Returns: + A wrapper object of the forward SDE (VP type). + + =============================================================== + Example: + # For discrete-time DPMs, given betas (the beta array for n = 0, 1, ..., N - 1): + >>> ns = NoiseScheduleVP('discrete', betas=betas) + # For discrete-time DPMs, given alphas_cumprod (the \hat{alpha_n} array for n = 0, 1, ..., N - 1): + >>> ns = NoiseScheduleVP('discrete', alphas_cumprod=alphas_cumprod) + # For continuous-time DPMs (VPSDE), linear schedule: + >>> ns = NoiseScheduleVP('linear', continuous_beta_0=0.1, continuous_beta_1=20.) + """ + + if schedule not in ['discrete', 'linear', 'cosine']: + raise ValueError( + "Unsupported noise schedule {}. The schedule needs to be 'discrete' or 'linear' or 'cosine'".format( + schedule)) + + self.schedule = schedule + if schedule == 'discrete': + if betas is not None: + log_alphas = 0.5 * torch.log(1 - betas).cumsum(dim=0) + else: + assert alphas_cumprod is not None + log_alphas = 0.5 * torch.log(alphas_cumprod) + self.total_N = len(log_alphas) + self.T = 1. + self.t_array = torch.linspace(0., 1., self.total_N + 1)[1:].reshape((1, -1)) + self.log_alpha_array = log_alphas.reshape((1, -1,)) + else: + self.total_N = 1000 + self.beta_0 = continuous_beta_0 + self.beta_1 = continuous_beta_1 + self.cosine_s = 0.008 + self.cosine_beta_max = 999. + self.cosine_t_max = math.atan(self.cosine_beta_max * (1. + self.cosine_s) / math.pi) * 2. * ( + 1. + self.cosine_s) / math.pi - self.cosine_s + self.cosine_log_alpha_0 = math.log(math.cos(self.cosine_s / (1. + self.cosine_s) * math.pi / 2.)) + self.schedule = schedule + if schedule == 'cosine': + # For the cosine schedule, T = 1 will have numerical issues. So we manually set the ending time T. + # Note that T = 0.9946 may be not the optimal setting. However, we find it works well. + self.T = 0.9946 + else: + self.T = 1. + + def marginal_log_mean_coeff(self, t): + """ + Compute log(alpha_t) of a given continuous-time label t in [0, T]. + """ + if self.schedule == 'discrete': + return interpolate_fn(t.reshape((-1, 1)), self.t_array.to(t.device), + self.log_alpha_array.to(t.device)).reshape((-1)) + elif self.schedule == 'linear': + return -0.25 * t ** 2 * (self.beta_1 - self.beta_0) - 0.5 * t * self.beta_0 + elif self.schedule == 'cosine': + log_alpha_fn = lambda s: torch.log(torch.cos((s + self.cosine_s) / (1. + self.cosine_s) * math.pi / 2.)) + log_alpha_t = log_alpha_fn(t) - self.cosine_log_alpha_0 + return log_alpha_t + + def marginal_alpha(self, t): + """ + Compute alpha_t of a given continuous-time label t in [0, T]. + """ + return torch.exp(self.marginal_log_mean_coeff(t)) + + def marginal_std(self, t): + """ + Compute sigma_t of a given continuous-time label t in [0, T]. + """ + return torch.sqrt(1. - torch.exp(2. * self.marginal_log_mean_coeff(t))) + + def marginal_lambda(self, t): + """ + Compute lambda_t = log(alpha_t) - log(sigma_t) of a given continuous-time label t in [0, T]. + """ + log_mean_coeff = self.marginal_log_mean_coeff(t) + log_std = 0.5 * torch.log(1. - torch.exp(2. * log_mean_coeff)) + return log_mean_coeff - log_std + + def inverse_lambda(self, lamb): + """ + Compute the continuous-time label t in [0, T] of a given half-logSNR lambda_t. + """ + if self.schedule == 'linear': + tmp = 2. * (self.beta_1 - self.beta_0) * torch.logaddexp(-2. * lamb, torch.zeros((1,)).to(lamb)) + Delta = self.beta_0 ** 2 + tmp + return tmp / (torch.sqrt(Delta) + self.beta_0) / (self.beta_1 - self.beta_0) + elif self.schedule == 'discrete': + log_alpha = -0.5 * torch.logaddexp(torch.zeros((1,)).to(lamb.device), -2. * lamb) + t = interpolate_fn(log_alpha.reshape((-1, 1)), torch.flip(self.log_alpha_array.to(lamb.device), [1]), + torch.flip(self.t_array.to(lamb.device), [1])) + return t.reshape((-1,)) + else: + log_alpha = -0.5 * torch.logaddexp(-2. * lamb, torch.zeros((1,)).to(lamb)) + t_fn = lambda log_alpha_t: torch.arccos(torch.exp(log_alpha_t + self.cosine_log_alpha_0)) * 2. * ( + 1. + self.cosine_s) / math.pi - self.cosine_s + t = t_fn(log_alpha) + return t + + +def model_wrapper( + model, + noise_schedule, + model_type="noise", + model_kwargs={}, + guidance_type="uncond", + condition=None, + unconditional_condition=None, + guidance_scale=1., + classifier_fn=None, + classifier_kwargs={}, +): + """Create a wrapper function for the noise prediction model. + DPM-Solver needs to solve the continuous-time diffusion ODEs. For DPMs trained on discrete-time labels, we need to + firstly wrap the model function to a noise prediction model that accepts the continuous time as the input. + We support four types of the diffusion model by setting `model_type`: + 1. "noise": noise prediction model. (Trained by predicting noise). + 2. "x_start": data prediction model. (Trained by predicting the data x_0 at time 0). + 3. "v": velocity prediction model. (Trained by predicting the velocity). + The "v" prediction is derivation detailed in Appendix D of [1], and is used in Imagen-Video [2]. + [1] Salimans, Tim, and Jonathan Ho. "Progressive distillation for fast sampling of diffusion models." + arXiv preprint arXiv:2202.00512 (2022). + [2] Ho, Jonathan, et al. "Imagen Video: High Definition Video Generation with Diffusion Models." + arXiv preprint arXiv:2210.02303 (2022). + + 4. "score": marginal score function. (Trained by denoising score matching). + Note that the score function and the noise prediction model follows a simple relationship: + ``` + noise(x_t, t) = -sigma_t * score(x_t, t) + ``` + We support three types of guided sampling by DPMs by setting `guidance_type`: + 1. "uncond": unconditional sampling by DPMs. + The input `model` has the following format: + `` + model(x, t_input, **model_kwargs) -> noise | x_start | v | score + `` + 2. "classifier": classifier guidance sampling [3] by DPMs and another classifier. + The input `model` has the following format: + `` + model(x, t_input, **model_kwargs) -> noise | x_start | v | score + `` + The input `classifier_fn` has the following format: + `` + classifier_fn(x, t_input, cond, **classifier_kwargs) -> logits(x, t_input, cond) + `` + [3] P. Dhariwal and A. Q. Nichol, "Diffusion models beat GANs on image synthesis," + in Advances in Neural Information Processing Systems, vol. 34, 2021, pp. 8780-8794. + 3. "classifier-free": classifier-free guidance sampling by conditional DPMs. + The input `model` has the following format: + `` + model(x, t_input, cond, **model_kwargs) -> noise | x_start | v | score + `` + And if cond == `unconditional_condition`, the model output is the unconditional DPM output. + [4] Ho, Jonathan, and Tim Salimans. "Classifier-free diffusion guidance." + arXiv preprint arXiv:2207.12598 (2022). + + The `t_input` is the time label of the model, which may be discrete-time labels (i.e. 0 to 999) + or continuous-time labels (i.e. epsilon to T). + We wrap the model function to accept only `x` and `t_continuous` as inputs, and outputs the predicted noise: + `` + def model_fn(x, t_continuous) -> noise: + t_input = get_model_input_time(t_continuous) + return noise_pred(model, x, t_input, **model_kwargs) + `` + where `t_continuous` is the continuous time labels (i.e. epsilon to T). And we use `model_fn` for DPM-Solver. + =============================================================== + Args: + model: A diffusion model with the corresponding format described above. + noise_schedule: A noise schedule object, such as NoiseScheduleVP. + model_type: A `str`. The parameterization type of the diffusion model. + "noise" or "x_start" or "v" or "score". + model_kwargs: A `dict`. A dict for the other inputs of the model function. + guidance_type: A `str`. The type of the guidance for sampling. + "uncond" or "classifier" or "classifier-free". + condition: A pytorch tensor. The condition for the guided sampling. + Only used for "classifier" or "classifier-free" guidance type. + unconditional_condition: A pytorch tensor. The condition for the unconditional sampling. + Only used for "classifier-free" guidance type. + guidance_scale: A `float`. The scale for the guided sampling. + classifier_fn: A classifier function. Only used for the classifier guidance. + classifier_kwargs: A `dict`. A dict for the other inputs of the classifier function. + Returns: + A noise prediction model that accepts the noised data and the continuous time as the inputs. + """ + + def get_model_input_time(t_continuous): + """ + Convert the continuous-time `t_continuous` (in [epsilon, T]) to the model input time. + For discrete-time DPMs, we convert `t_continuous` in [1 / N, 1] to `t_input` in [0, 1000 * (N - 1) / N]. + For continuous-time DPMs, we just use `t_continuous`. + """ + if noise_schedule.schedule == 'discrete': + return (t_continuous - 1. / noise_schedule.total_N) * 1000. + else: + return t_continuous + + def noise_pred_fn(x, t_continuous, cond=None): + if t_continuous.reshape((-1,)).shape[0] == 1: + t_continuous = t_continuous.expand((x.shape[0])) + t_input = get_model_input_time(t_continuous) + if cond is None: + output = model(x, t_input, **model_kwargs) + else: + output = model(x, t_input, cond, **model_kwargs) + if model_type == "noise": + return output + elif model_type == "x_start": + alpha_t, sigma_t = noise_schedule.marginal_alpha(t_continuous), noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return (x - expand_dims(alpha_t, dims) * output) / expand_dims(sigma_t, dims) + elif model_type == "v": + alpha_t, sigma_t = noise_schedule.marginal_alpha(t_continuous), noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return expand_dims(alpha_t, dims) * output + expand_dims(sigma_t, dims) * x + elif model_type == "score": + sigma_t = noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return -expand_dims(sigma_t, dims) * output + + def cond_grad_fn(x, t_input): + """ + Compute the gradient of the classifier, i.e. nabla_{x} log p_t(cond | x_t). + """ + with torch.enable_grad(): + x_in = x.detach().requires_grad_(True) + log_prob = classifier_fn(x_in, t_input, condition, **classifier_kwargs) + return torch.autograd.grad(log_prob.sum(), x_in)[0] + + def model_fn(x, t_continuous): + """ + The noise predicition model function that is used for DPM-Solver. + """ + if t_continuous.reshape((-1,)).shape[0] == 1: + t_continuous = t_continuous.expand((x.shape[0])) + if guidance_type == "uncond": + return noise_pred_fn(x, t_continuous) + elif guidance_type == "classifier": + assert classifier_fn is not None + t_input = get_model_input_time(t_continuous) + cond_grad = cond_grad_fn(x, t_input) + sigma_t = noise_schedule.marginal_std(t_continuous) + noise = noise_pred_fn(x, t_continuous) + return noise - guidance_scale * expand_dims(sigma_t, dims=cond_grad.dim()) * cond_grad + elif guidance_type == "classifier-free": + if guidance_scale == 1. or unconditional_condition is None: + return noise_pred_fn(x, t_continuous, cond=condition) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t_continuous] * 2) + c_in = torch.cat([unconditional_condition, condition]) + noise_uncond, noise = noise_pred_fn(x_in, t_in, cond=c_in).chunk(2) + return noise_uncond + guidance_scale * (noise - noise_uncond) + + assert model_type in ["noise", "x_start", "v"] + assert guidance_type in ["uncond", "classifier", "classifier-free"] + return model_fn + + +class DPM_Solver: + def __init__(self, model_fn, noise_schedule, predict_x0=False, thresholding=False, max_val=1.): + """Construct a DPM-Solver. + We support both the noise prediction model ("predicting epsilon") and the data prediction model ("predicting x0"). + If `predict_x0` is False, we use the solver for the noise prediction model (DPM-Solver). + If `predict_x0` is True, we use the solver for the data prediction model (DPM-Solver++). + In such case, we further support the "dynamic thresholding" in [1] when `thresholding` is True. + The "dynamic thresholding" can greatly improve the sample quality for pixel-space DPMs with large guidance scales. + Args: + model_fn: A noise prediction model function which accepts the continuous-time input (t in [epsilon, T]): + `` + def model_fn(x, t_continuous): + return noise + `` + noise_schedule: A noise schedule object, such as NoiseScheduleVP. + predict_x0: A `bool`. If true, use the data prediction model; else, use the noise prediction model. + thresholding: A `bool`. Valid when `predict_x0` is True. Whether to use the "dynamic thresholding" in [1]. + max_val: A `float`. Valid when both `predict_x0` and `thresholding` are True. The max value for thresholding. + + [1] Chitwan Saharia, William Chan, Saurabh Saxena, Lala Li, Jay Whang, Emily Denton, Seyed Kamyar Seyed Ghasemipour, Burcu Karagol Ayan, S Sara Mahdavi, Rapha Gontijo Lopes, et al. Photorealistic text-to-image diffusion models with deep language understanding. arXiv preprint arXiv:2205.11487, 2022b. + """ + self.model = model_fn + self.noise_schedule = noise_schedule + self.predict_x0 = predict_x0 + self.thresholding = thresholding + self.max_val = max_val + + def noise_prediction_fn(self, x, t): + """ + Return the noise prediction model. + """ + return self.model(x, t) + + def data_prediction_fn(self, x, t): + """ + Return the data prediction model (with thresholding). + """ + noise = self.noise_prediction_fn(x, t) + dims = x.dim() + alpha_t, sigma_t = self.noise_schedule.marginal_alpha(t), self.noise_schedule.marginal_std(t) + x0 = (x - expand_dims(sigma_t, dims) * noise) / expand_dims(alpha_t, dims) + if self.thresholding: + p = 0.995 # A hyperparameter in the paper of "Imagen" [1]. + s = torch.quantile(torch.abs(x0).reshape((x0.shape[0], -1)), p, dim=1) + s = expand_dims(torch.maximum(s, self.max_val * torch.ones_like(s).to(s.device)), dims) + x0 = torch.clamp(x0, -s, s) / s + return x0 + + def model_fn(self, x, t): + """ + Convert the model to the noise prediction model or the data prediction model. + """ + if self.predict_x0: + return self.data_prediction_fn(x, t) + else: + return self.noise_prediction_fn(x, t) + + def get_time_steps(self, skip_type, t_T, t_0, N, device): + """Compute the intermediate time steps for sampling. + Args: + skip_type: A `str`. The type for the spacing of the time steps. We support three types: + - 'logSNR': uniform logSNR for the time steps. + - 'time_uniform': uniform time for the time steps. (**Recommended for high-resolutional data**.) + - 'time_quadratic': quadratic time for the time steps. (Used in DDIM for low-resolutional data.) + t_T: A `float`. The starting time of the sampling (default is T). + t_0: A `float`. The ending time of the sampling (default is epsilon). + N: A `int`. The total number of the spacing of the time steps. + device: A torch device. + Returns: + A pytorch tensor of the time steps, with the shape (N + 1,). + """ + if skip_type == 'logSNR': + lambda_T = self.noise_schedule.marginal_lambda(torch.tensor(t_T).to(device)) + lambda_0 = self.noise_schedule.marginal_lambda(torch.tensor(t_0).to(device)) + logSNR_steps = torch.linspace(lambda_T.cpu().item(), lambda_0.cpu().item(), N + 1).to(device) + return self.noise_schedule.inverse_lambda(logSNR_steps) + elif skip_type == 'time_uniform': + return torch.linspace(t_T, t_0, N + 1).to(device) + elif skip_type == 'time_quadratic': + t_order = 2 + t = torch.linspace(t_T ** (1. / t_order), t_0 ** (1. / t_order), N + 1).pow(t_order).to(device) + return t + else: + raise ValueError( + "Unsupported skip_type {}, need to be 'logSNR' or 'time_uniform' or 'time_quadratic'".format(skip_type)) + + def get_orders_and_timesteps_for_singlestep_solver(self, steps, order, skip_type, t_T, t_0, device): + """ + Get the order of each step for sampling by the singlestep DPM-Solver. + We combine both DPM-Solver-1,2,3 to use all the function evaluations, which is named as "DPM-Solver-fast". + Given a fixed number of function evaluations by `steps`, the sampling procedure by DPM-Solver-fast is: + - If order == 1: + We take `steps` of DPM-Solver-1 (i.e. DDIM). + - If order == 2: + - Denote K = (steps // 2). We take K or (K + 1) intermediate time steps for sampling. + - If steps % 2 == 0, we use K steps of DPM-Solver-2. + - If steps % 2 == 1, we use K steps of DPM-Solver-2 and 1 step of DPM-Solver-1. + - If order == 3: + - Denote K = (steps // 3 + 1). We take K intermediate time steps for sampling. + - If steps % 3 == 0, we use (K - 2) steps of DPM-Solver-3, and 1 step of DPM-Solver-2 and 1 step of DPM-Solver-1. + - If steps % 3 == 1, we use (K - 1) steps of DPM-Solver-3 and 1 step of DPM-Solver-1. + - If steps % 3 == 2, we use (K - 1) steps of DPM-Solver-3 and 1 step of DPM-Solver-2. + ============================================ + Args: + order: A `int`. The max order for the solver (2 or 3). + steps: A `int`. The total number of function evaluations (NFE). + skip_type: A `str`. The type for the spacing of the time steps. We support three types: + - 'logSNR': uniform logSNR for the time steps. + - 'time_uniform': uniform time for the time steps. (**Recommended for high-resolutional data**.) + - 'time_quadratic': quadratic time for the time steps. (Used in DDIM for low-resolutional data.) + t_T: A `float`. The starting time of the sampling (default is T). + t_0: A `float`. The ending time of the sampling (default is epsilon). + device: A torch device. + Returns: + orders: A list of the solver order of each step. + """ + if order == 3: + K = steps // 3 + 1 + if steps % 3 == 0: + orders = [3, ] * (K - 2) + [2, 1] + elif steps % 3 == 1: + orders = [3, ] * (K - 1) + [1] + else: + orders = [3, ] * (K - 1) + [2] + elif order == 2: + if steps % 2 == 0: + K = steps // 2 + orders = [2, ] * K + else: + K = steps // 2 + 1 + orders = [2, ] * (K - 1) + [1] + elif order == 1: + K = 1 + orders = [1, ] * steps + else: + raise ValueError("'order' must be '1' or '2' or '3'.") + if skip_type == 'logSNR': + # To reproduce the results in DPM-Solver paper + timesteps_outer = self.get_time_steps(skip_type, t_T, t_0, K, device) + else: + timesteps_outer = self.get_time_steps(skip_type, t_T, t_0, steps, device)[ + torch.cumsum(torch.tensor([0, ] + orders)).to(device)] + return timesteps_outer, orders + + def denoise_to_zero_fn(self, x, s): + """ + Denoise at the final step, which is equivalent to solve the ODE from lambda_s to infty by first-order discretization. + """ + return self.data_prediction_fn(x, s) + + def dpm_solver_first_update(self, x, s, t, model_s=None, return_intermediate=False): + """ + DPM-Solver-1 (equivalent to DDIM) from time `s` to time `t`. + Args: + x: A pytorch tensor. The initial value at time `s`. + s: A pytorch tensor. The starting time, with the shape (x.shape[0],). + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + model_s: A pytorch tensor. The model function evaluated at time `s`. + If `model_s` is None, we evaluate the model by `x` and `s`; otherwise we directly use it. + return_intermediate: A `bool`. If true, also return the model value at time `s`. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + ns = self.noise_schedule + dims = x.dim() + lambda_s, lambda_t = ns.marginal_lambda(s), ns.marginal_lambda(t) + h = lambda_t - lambda_s + log_alpha_s, log_alpha_t = ns.marginal_log_mean_coeff(s), ns.marginal_log_mean_coeff(t) + sigma_s, sigma_t = ns.marginal_std(s), ns.marginal_std(t) + alpha_t = torch.exp(log_alpha_t) + + if self.predict_x0: + phi_1 = torch.expm1(-h) + if model_s is None: + model_s = self.model_fn(x, s) + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + ) + if return_intermediate: + return x_t, {'model_s': model_s} + else: + return x_t + else: + phi_1 = torch.expm1(h) + if model_s is None: + model_s = self.model_fn(x, s) + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + ) + if return_intermediate: + return x_t, {'model_s': model_s} + else: + return x_t + + def singlestep_dpm_solver_second_update(self, x, s, t, r1=0.5, model_s=None, return_intermediate=False, + solver_type='dpm_solver'): + """ + Singlestep solver DPM-Solver-2 from time `s` to time `t`. + Args: + x: A pytorch tensor. The initial value at time `s`. + s: A pytorch tensor. The starting time, with the shape (x.shape[0],). + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + r1: A `float`. The hyperparameter of the second-order solver. + model_s: A pytorch tensor. The model function evaluated at time `s`. + If `model_s` is None, we evaluate the model by `x` and `s`; otherwise we directly use it. + return_intermediate: A `bool`. If true, also return the model value at time `s` and `s1` (the intermediate time). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if solver_type not in ['dpm_solver', 'taylor']: + raise ValueError("'solver_type' must be either 'dpm_solver' or 'taylor', got {}".format(solver_type)) + if r1 is None: + r1 = 0.5 + ns = self.noise_schedule + dims = x.dim() + lambda_s, lambda_t = ns.marginal_lambda(s), ns.marginal_lambda(t) + h = lambda_t - lambda_s + lambda_s1 = lambda_s + r1 * h + s1 = ns.inverse_lambda(lambda_s1) + log_alpha_s, log_alpha_s1, log_alpha_t = ns.marginal_log_mean_coeff(s), ns.marginal_log_mean_coeff( + s1), ns.marginal_log_mean_coeff(t) + sigma_s, sigma_s1, sigma_t = ns.marginal_std(s), ns.marginal_std(s1), ns.marginal_std(t) + alpha_s1, alpha_t = torch.exp(log_alpha_s1), torch.exp(log_alpha_t) + + if self.predict_x0: + phi_11 = torch.expm1(-r1 * h) + phi_1 = torch.expm1(-h) + + if model_s is None: + model_s = self.model_fn(x, s) + x_s1 = ( + expand_dims(sigma_s1 / sigma_s, dims) * x + - expand_dims(alpha_s1 * phi_11, dims) * model_s + ) + model_s1 = self.model_fn(x_s1, s1) + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + - (0.5 / r1) * expand_dims(alpha_t * phi_1, dims) * (model_s1 - model_s) + ) + elif solver_type == 'taylor': + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + + (1. / r1) * expand_dims(alpha_t * ((torch.exp(-h) - 1.) / h + 1.), dims) * ( + model_s1 - model_s) + ) + else: + phi_11 = torch.expm1(r1 * h) + phi_1 = torch.expm1(h) + + if model_s is None: + model_s = self.model_fn(x, s) + x_s1 = ( + expand_dims(torch.exp(log_alpha_s1 - log_alpha_s), dims) * x + - expand_dims(sigma_s1 * phi_11, dims) * model_s + ) + model_s1 = self.model_fn(x_s1, s1) + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + - (0.5 / r1) * expand_dims(sigma_t * phi_1, dims) * (model_s1 - model_s) + ) + elif solver_type == 'taylor': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + - (1. / r1) * expand_dims(sigma_t * ((torch.exp(h) - 1.) / h - 1.), dims) * (model_s1 - model_s) + ) + if return_intermediate: + return x_t, {'model_s': model_s, 'model_s1': model_s1} + else: + return x_t + + def singlestep_dpm_solver_third_update(self, x, s, t, r1=1. / 3., r2=2. / 3., model_s=None, model_s1=None, + return_intermediate=False, solver_type='dpm_solver'): + """ + Singlestep solver DPM-Solver-3 from time `s` to time `t`. + Args: + x: A pytorch tensor. The initial value at time `s`. + s: A pytorch tensor. The starting time, with the shape (x.shape[0],). + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + r1: A `float`. The hyperparameter of the third-order solver. + r2: A `float`. The hyperparameter of the third-order solver. + model_s: A pytorch tensor. The model function evaluated at time `s`. + If `model_s` is None, we evaluate the model by `x` and `s`; otherwise we directly use it. + model_s1: A pytorch tensor. The model function evaluated at time `s1` (the intermediate time given by `r1`). + If `model_s1` is None, we evaluate the model at `s1`; otherwise we directly use it. + return_intermediate: A `bool`. If true, also return the model value at time `s`, `s1` and `s2` (the intermediate times). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if solver_type not in ['dpm_solver', 'taylor']: + raise ValueError("'solver_type' must be either 'dpm_solver' or 'taylor', got {}".format(solver_type)) + if r1 is None: + r1 = 1. / 3. + if r2 is None: + r2 = 2. / 3. + ns = self.noise_schedule + dims = x.dim() + lambda_s, lambda_t = ns.marginal_lambda(s), ns.marginal_lambda(t) + h = lambda_t - lambda_s + lambda_s1 = lambda_s + r1 * h + lambda_s2 = lambda_s + r2 * h + s1 = ns.inverse_lambda(lambda_s1) + s2 = ns.inverse_lambda(lambda_s2) + log_alpha_s, log_alpha_s1, log_alpha_s2, log_alpha_t = ns.marginal_log_mean_coeff( + s), ns.marginal_log_mean_coeff(s1), ns.marginal_log_mean_coeff(s2), ns.marginal_log_mean_coeff(t) + sigma_s, sigma_s1, sigma_s2, sigma_t = ns.marginal_std(s), ns.marginal_std(s1), ns.marginal_std( + s2), ns.marginal_std(t) + alpha_s1, alpha_s2, alpha_t = torch.exp(log_alpha_s1), torch.exp(log_alpha_s2), torch.exp(log_alpha_t) + + if self.predict_x0: + phi_11 = torch.expm1(-r1 * h) + phi_12 = torch.expm1(-r2 * h) + phi_1 = torch.expm1(-h) + phi_22 = torch.expm1(-r2 * h) / (r2 * h) + 1. + phi_2 = phi_1 / h + 1. + phi_3 = phi_2 / h - 0.5 + + if model_s is None: + model_s = self.model_fn(x, s) + if model_s1 is None: + x_s1 = ( + expand_dims(sigma_s1 / sigma_s, dims) * x + - expand_dims(alpha_s1 * phi_11, dims) * model_s + ) + model_s1 = self.model_fn(x_s1, s1) + x_s2 = ( + expand_dims(sigma_s2 / sigma_s, dims) * x + - expand_dims(alpha_s2 * phi_12, dims) * model_s + + r2 / r1 * expand_dims(alpha_s2 * phi_22, dims) * (model_s1 - model_s) + ) + model_s2 = self.model_fn(x_s2, s2) + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + + (1. / r2) * expand_dims(alpha_t * phi_2, dims) * (model_s2 - model_s) + ) + elif solver_type == 'taylor': + D1_0 = (1. / r1) * (model_s1 - model_s) + D1_1 = (1. / r2) * (model_s2 - model_s) + D1 = (r2 * D1_0 - r1 * D1_1) / (r2 - r1) + D2 = 2. * (D1_1 - D1_0) / (r2 - r1) + x_t = ( + expand_dims(sigma_t / sigma_s, dims) * x + - expand_dims(alpha_t * phi_1, dims) * model_s + + expand_dims(alpha_t * phi_2, dims) * D1 + - expand_dims(alpha_t * phi_3, dims) * D2 + ) + else: + phi_11 = torch.expm1(r1 * h) + phi_12 = torch.expm1(r2 * h) + phi_1 = torch.expm1(h) + phi_22 = torch.expm1(r2 * h) / (r2 * h) - 1. + phi_2 = phi_1 / h - 1. + phi_3 = phi_2 / h - 0.5 + + if model_s is None: + model_s = self.model_fn(x, s) + if model_s1 is None: + x_s1 = ( + expand_dims(torch.exp(log_alpha_s1 - log_alpha_s), dims) * x + - expand_dims(sigma_s1 * phi_11, dims) * model_s + ) + model_s1 = self.model_fn(x_s1, s1) + x_s2 = ( + expand_dims(torch.exp(log_alpha_s2 - log_alpha_s), dims) * x + - expand_dims(sigma_s2 * phi_12, dims) * model_s + - r2 / r1 * expand_dims(sigma_s2 * phi_22, dims) * (model_s1 - model_s) + ) + model_s2 = self.model_fn(x_s2, s2) + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + - (1. / r2) * expand_dims(sigma_t * phi_2, dims) * (model_s2 - model_s) + ) + elif solver_type == 'taylor': + D1_0 = (1. / r1) * (model_s1 - model_s) + D1_1 = (1. / r2) * (model_s2 - model_s) + D1 = (r2 * D1_0 - r1 * D1_1) / (r2 - r1) + D2 = 2. * (D1_1 - D1_0) / (r2 - r1) + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_s), dims) * x + - expand_dims(sigma_t * phi_1, dims) * model_s + - expand_dims(sigma_t * phi_2, dims) * D1 + - expand_dims(sigma_t * phi_3, dims) * D2 + ) + + if return_intermediate: + return x_t, {'model_s': model_s, 'model_s1': model_s1, 'model_s2': model_s2} + else: + return x_t + + def multistep_dpm_solver_second_update(self, x, model_prev_list, t_prev_list, t, solver_type="dpm_solver"): + """ + Multistep solver DPM-Solver-2 from time `t_prev_list[-1]` to time `t`. + Args: + x: A pytorch tensor. The initial value at time `s`. + model_prev_list: A list of pytorch tensor. The previous computed model values. + t_prev_list: A list of pytorch tensor. The previous times, each time has the shape (x.shape[0],) + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if solver_type not in ['dpm_solver', 'taylor']: + raise ValueError("'solver_type' must be either 'dpm_solver' or 'taylor', got {}".format(solver_type)) + ns = self.noise_schedule + dims = x.dim() + model_prev_1, model_prev_0 = model_prev_list + t_prev_1, t_prev_0 = t_prev_list + lambda_prev_1, lambda_prev_0, lambda_t = ns.marginal_lambda(t_prev_1), ns.marginal_lambda( + t_prev_0), ns.marginal_lambda(t) + log_alpha_prev_0, log_alpha_t = ns.marginal_log_mean_coeff(t_prev_0), ns.marginal_log_mean_coeff(t) + sigma_prev_0, sigma_t = ns.marginal_std(t_prev_0), ns.marginal_std(t) + alpha_t = torch.exp(log_alpha_t) + + h_0 = lambda_prev_0 - lambda_prev_1 + h = lambda_t - lambda_prev_0 + r0 = h_0 / h + D1_0 = expand_dims(1. / r0, dims) * (model_prev_0 - model_prev_1) + if self.predict_x0: + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(sigma_t / sigma_prev_0, dims) * x + - expand_dims(alpha_t * (torch.exp(-h) - 1.), dims) * model_prev_0 + - 0.5 * expand_dims(alpha_t * (torch.exp(-h) - 1.), dims) * D1_0 + ) + elif solver_type == 'taylor': + x_t = ( + expand_dims(sigma_t / sigma_prev_0, dims) * x + - expand_dims(alpha_t * (torch.exp(-h) - 1.), dims) * model_prev_0 + + expand_dims(alpha_t * ((torch.exp(-h) - 1.) / h + 1.), dims) * D1_0 + ) + else: + if solver_type == 'dpm_solver': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dims) * x + - expand_dims(sigma_t * (torch.exp(h) - 1.), dims) * model_prev_0 + - 0.5 * expand_dims(sigma_t * (torch.exp(h) - 1.), dims) * D1_0 + ) + elif solver_type == 'taylor': + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dims) * x + - expand_dims(sigma_t * (torch.exp(h) - 1.), dims) * model_prev_0 + - expand_dims(sigma_t * ((torch.exp(h) - 1.) / h - 1.), dims) * D1_0 + ) + return x_t + + def multistep_dpm_solver_third_update(self, x, model_prev_list, t_prev_list, t, solver_type='dpm_solver'): + """ + Multistep solver DPM-Solver-3 from time `t_prev_list[-1]` to time `t`. + Args: + x: A pytorch tensor. The initial value at time `s`. + model_prev_list: A list of pytorch tensor. The previous computed model values. + t_prev_list: A list of pytorch tensor. The previous times, each time has the shape (x.shape[0],) + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + ns = self.noise_schedule + dims = x.dim() + model_prev_2, model_prev_1, model_prev_0 = model_prev_list + t_prev_2, t_prev_1, t_prev_0 = t_prev_list + lambda_prev_2, lambda_prev_1, lambda_prev_0, lambda_t = ns.marginal_lambda(t_prev_2), ns.marginal_lambda( + t_prev_1), ns.marginal_lambda(t_prev_0), ns.marginal_lambda(t) + log_alpha_prev_0, log_alpha_t = ns.marginal_log_mean_coeff(t_prev_0), ns.marginal_log_mean_coeff(t) + sigma_prev_0, sigma_t = ns.marginal_std(t_prev_0), ns.marginal_std(t) + alpha_t = torch.exp(log_alpha_t) + + h_1 = lambda_prev_1 - lambda_prev_2 + h_0 = lambda_prev_0 - lambda_prev_1 + h = lambda_t - lambda_prev_0 + r0, r1 = h_0 / h, h_1 / h + D1_0 = expand_dims(1. / r0, dims) * (model_prev_0 - model_prev_1) + D1_1 = expand_dims(1. / r1, dims) * (model_prev_1 - model_prev_2) + D1 = D1_0 + expand_dims(r0 / (r0 + r1), dims) * (D1_0 - D1_1) + D2 = expand_dims(1. / (r0 + r1), dims) * (D1_0 - D1_1) + if self.predict_x0: + x_t = ( + expand_dims(sigma_t / sigma_prev_0, dims) * x + - expand_dims(alpha_t * (torch.exp(-h) - 1.), dims) * model_prev_0 + + expand_dims(alpha_t * ((torch.exp(-h) - 1.) / h + 1.), dims) * D1 + - expand_dims(alpha_t * ((torch.exp(-h) - 1. + h) / h ** 2 - 0.5), dims) * D2 + ) + else: + x_t = ( + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dims) * x + - expand_dims(sigma_t * (torch.exp(h) - 1.), dims) * model_prev_0 + - expand_dims(sigma_t * ((torch.exp(h) - 1.) / h - 1.), dims) * D1 + - expand_dims(sigma_t * ((torch.exp(h) - 1. - h) / h ** 2 - 0.5), dims) * D2 + ) + return x_t + + def singlestep_dpm_solver_update(self, x, s, t, order, return_intermediate=False, solver_type='dpm_solver', r1=None, + r2=None): + """ + Singlestep DPM-Solver with the order `order` from time `s` to time `t`. + Args: + x: A pytorch tensor. The initial value at time `s`. + s: A pytorch tensor. The starting time, with the shape (x.shape[0],). + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + order: A `int`. The order of DPM-Solver. We only support order == 1 or 2 or 3. + return_intermediate: A `bool`. If true, also return the model value at time `s`, `s1` and `s2` (the intermediate times). + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + r1: A `float`. The hyperparameter of the second-order or third-order solver. + r2: A `float`. The hyperparameter of the third-order solver. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if order == 1: + return self.dpm_solver_first_update(x, s, t, return_intermediate=return_intermediate) + elif order == 2: + return self.singlestep_dpm_solver_second_update(x, s, t, return_intermediate=return_intermediate, + solver_type=solver_type, r1=r1) + elif order == 3: + return self.singlestep_dpm_solver_third_update(x, s, t, return_intermediate=return_intermediate, + solver_type=solver_type, r1=r1, r2=r2) + else: + raise ValueError("Solver order must be 1 or 2 or 3, got {}".format(order)) + + def multistep_dpm_solver_update(self, x, model_prev_list, t_prev_list, t, order, solver_type='dpm_solver'): + """ + Multistep DPM-Solver with the order `order` from time `t_prev_list[-1]` to time `t`. + Args: + x: A pytorch tensor. The initial value at time `s`. + model_prev_list: A list of pytorch tensor. The previous computed model values. + t_prev_list: A list of pytorch tensor. The previous times, each time has the shape (x.shape[0],) + t: A pytorch tensor. The ending time, with the shape (x.shape[0],). + order: A `int`. The order of DPM-Solver. We only support order == 1 or 2 or 3. + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_t: A pytorch tensor. The approximated solution at time `t`. + """ + if order == 1: + return self.dpm_solver_first_update(x, t_prev_list[-1], t, model_s=model_prev_list[-1]) + elif order == 2: + return self.multistep_dpm_solver_second_update(x, model_prev_list, t_prev_list, t, solver_type=solver_type) + elif order == 3: + return self.multistep_dpm_solver_third_update(x, model_prev_list, t_prev_list, t, solver_type=solver_type) + else: + raise ValueError("Solver order must be 1 or 2 or 3, got {}".format(order)) + + def dpm_solver_adaptive(self, x, order, t_T, t_0, h_init=0.05, atol=0.0078, rtol=0.05, theta=0.9, t_err=1e-5, + solver_type='dpm_solver'): + """ + The adaptive step size solver based on singlestep DPM-Solver. + Args: + x: A pytorch tensor. The initial value at time `t_T`. + order: A `int`. The (higher) order of the solver. We only support order == 2 or 3. + t_T: A `float`. The starting time of the sampling (default is T). + t_0: A `float`. The ending time of the sampling (default is epsilon). + h_init: A `float`. The initial step size (for logSNR). + atol: A `float`. The absolute tolerance of the solver. For image data, the default setting is 0.0078, followed [1]. + rtol: A `float`. The relative tolerance of the solver. The default setting is 0.05. + theta: A `float`. The safety hyperparameter for adapting the step size. The default setting is 0.9, followed [1]. + t_err: A `float`. The tolerance for the time. We solve the diffusion ODE until the absolute error between the + current time and `t_0` is less than `t_err`. The default setting is 1e-5. + solver_type: either 'dpm_solver' or 'taylor'. The type for the high-order solvers. + The type slightly impacts the performance. We recommend to use 'dpm_solver' type. + Returns: + x_0: A pytorch tensor. The approximated solution at time `t_0`. + [1] A. Jolicoeur-Martineau, K. Li, R. Piché-Taillefer, T. Kachman, and I. Mitliagkas, "Gotta go fast when generating data with score-based models," arXiv preprint arXiv:2105.14080, 2021. + """ + ns = self.noise_schedule + s = t_T * torch.ones((x.shape[0],)).to(x) + lambda_s = ns.marginal_lambda(s) + lambda_0 = ns.marginal_lambda(t_0 * torch.ones_like(s).to(x)) + h = h_init * torch.ones_like(s).to(x) + x_prev = x + nfe = 0 + if order == 2: + r1 = 0.5 + lower_update = lambda x, s, t: self.dpm_solver_first_update(x, s, t, return_intermediate=True) + higher_update = lambda x, s, t, **kwargs: self.singlestep_dpm_solver_second_update(x, s, t, r1=r1, + solver_type=solver_type, + **kwargs) + elif order == 3: + r1, r2 = 1. / 3., 2. / 3. + lower_update = lambda x, s, t: self.singlestep_dpm_solver_second_update(x, s, t, r1=r1, + return_intermediate=True, + solver_type=solver_type) + higher_update = lambda x, s, t, **kwargs: self.singlestep_dpm_solver_third_update(x, s, t, r1=r1, r2=r2, + solver_type=solver_type, + **kwargs) + else: + raise ValueError("For adaptive step size solver, order must be 2 or 3, got {}".format(order)) + while torch.abs((s - t_0)).mean() > t_err: + t = ns.inverse_lambda(lambda_s + h) + x_lower, lower_noise_kwargs = lower_update(x, s, t) + x_higher = higher_update(x, s, t, **lower_noise_kwargs) + delta = torch.max(torch.ones_like(x).to(x) * atol, rtol * torch.max(torch.abs(x_lower), torch.abs(x_prev))) + norm_fn = lambda v: torch.sqrt(torch.square(v.reshape((v.shape[0], -1))).mean(dim=-1, keepdim=True)) + E = norm_fn((x_higher - x_lower) / delta).max() + if torch.all(E <= 1.): + x = x_higher + s = t + x_prev = x_lower + lambda_s = ns.marginal_lambda(s) + h = torch.min(theta * h * torch.float_power(E, -1. / order).float(), lambda_0 - lambda_s) + nfe += order + print('adaptive solver nfe', nfe) + return x + + def sample(self, x, steps=20, t_start=None, t_end=None, order=3, skip_type='time_uniform', + method='singlestep', lower_order_final=True, denoise_to_zero=False, solver_type='dpm_solver', + atol=0.0078, rtol=0.05, + ): + """ + Compute the sample at time `t_end` by DPM-Solver, given the initial `x` at time `t_start`. + ===================================================== + We support the following algorithms for both noise prediction model and data prediction model: + - 'singlestep': + Singlestep DPM-Solver (i.e. "DPM-Solver-fast" in the paper), which combines different orders of singlestep DPM-Solver. + We combine all the singlestep solvers with order <= `order` to use up all the function evaluations (steps). + The total number of function evaluations (NFE) == `steps`. + Given a fixed NFE == `steps`, the sampling procedure is: + - If `order` == 1: + - Denote K = steps. We use K steps of DPM-Solver-1 (i.e. DDIM). + - If `order` == 2: + - Denote K = (steps // 2) + (steps % 2). We take K intermediate time steps for sampling. + - If steps % 2 == 0, we use K steps of singlestep DPM-Solver-2. + - If steps % 2 == 1, we use (K - 1) steps of singlestep DPM-Solver-2 and 1 step of DPM-Solver-1. + - If `order` == 3: + - Denote K = (steps // 3 + 1). We take K intermediate time steps for sampling. + - If steps % 3 == 0, we use (K - 2) steps of singlestep DPM-Solver-3, and 1 step of singlestep DPM-Solver-2 and 1 step of DPM-Solver-1. + - If steps % 3 == 1, we use (K - 1) steps of singlestep DPM-Solver-3 and 1 step of DPM-Solver-1. + - If steps % 3 == 2, we use (K - 1) steps of singlestep DPM-Solver-3 and 1 step of singlestep DPM-Solver-2. + - 'multistep': + Multistep DPM-Solver with the order of `order`. The total number of function evaluations (NFE) == `steps`. + We initialize the first `order` values by lower order multistep solvers. + Given a fixed NFE == `steps`, the sampling procedure is: + Denote K = steps. + - If `order` == 1: + - We use K steps of DPM-Solver-1 (i.e. DDIM). + - If `order` == 2: + - We firstly use 1 step of DPM-Solver-1, then use (K - 1) step of multistep DPM-Solver-2. + - If `order` == 3: + - We firstly use 1 step of DPM-Solver-1, then 1 step of multistep DPM-Solver-2, then (K - 2) step of multistep DPM-Solver-3. + - 'singlestep_fixed': + Fixed order singlestep DPM-Solver (i.e. DPM-Solver-1 or singlestep DPM-Solver-2 or singlestep DPM-Solver-3). + We use singlestep DPM-Solver-`order` for `order`=1 or 2 or 3, with total [`steps` // `order`] * `order` NFE. + - 'adaptive': + Adaptive step size DPM-Solver (i.e. "DPM-Solver-12" and "DPM-Solver-23" in the paper). + We ignore `steps` and use adaptive step size DPM-Solver with a higher order of `order`. + You can adjust the absolute tolerance `atol` and the relative tolerance `rtol` to balance the computatation costs + (NFE) and the sample quality. + - If `order` == 2, we use DPM-Solver-12 which combines DPM-Solver-1 and singlestep DPM-Solver-2. + - If `order` == 3, we use DPM-Solver-23 which combines singlestep DPM-Solver-2 and singlestep DPM-Solver-3. + ===================================================== + Some advices for choosing the algorithm: + - For **unconditional sampling** or **guided sampling with small guidance scale** by DPMs: + Use singlestep DPM-Solver ("DPM-Solver-fast" in the paper) with `order = 3`. + e.g. + >>> dpm_solver = DPM_Solver(model_fn, noise_schedule, predict_x0=False) + >>> x_sample = dpm_solver.sample(x, steps=steps, t_start=t_start, t_end=t_end, order=3, + skip_type='time_uniform', method='singlestep') + - For **guided sampling with large guidance scale** by DPMs: + Use multistep DPM-Solver with `predict_x0 = True` and `order = 2`. + e.g. + >>> dpm_solver = DPM_Solver(model_fn, noise_schedule, predict_x0=True) + >>> x_sample = dpm_solver.sample(x, steps=steps, t_start=t_start, t_end=t_end, order=2, + skip_type='time_uniform', method='multistep') + We support three types of `skip_type`: + - 'logSNR': uniform logSNR for the time steps. **Recommended for low-resolutional images** + - 'time_uniform': uniform time for the time steps. **Recommended for high-resolutional images**. + - 'time_quadratic': quadratic time for the time steps. + ===================================================== + Args: + x: A pytorch tensor. The initial value at time `t_start` + e.g. if `t_start` == T, then `x` is a sample from the standard normal distribution. + steps: A `int`. The total number of function evaluations (NFE). + t_start: A `float`. The starting time of the sampling. + If `T` is None, we use self.noise_schedule.T (default is 1.0). + t_end: A `float`. The ending time of the sampling. + If `t_end` is None, we use 1. / self.noise_schedule.total_N. + e.g. if total_N == 1000, we have `t_end` == 1e-3. + For discrete-time DPMs: + - We recommend `t_end` == 1. / self.noise_schedule.total_N. + For continuous-time DPMs: + - We recommend `t_end` == 1e-3 when `steps` <= 15; and `t_end` == 1e-4 when `steps` > 15. + order: A `int`. The order of DPM-Solver. + skip_type: A `str`. The type for the spacing of the time steps. 'time_uniform' or 'logSNR' or 'time_quadratic'. + method: A `str`. The method for sampling. 'singlestep' or 'multistep' or 'singlestep_fixed' or 'adaptive'. + denoise_to_zero: A `bool`. Whether to denoise to time 0 at the final step. + Default is `False`. If `denoise_to_zero` is `True`, the total NFE is (`steps` + 1). + This trick is firstly proposed by DDPM (https://arxiv.org/abs/2006.11239) and + score_sde (https://arxiv.org/abs/2011.13456). Such trick can improve the FID + for diffusion models sampling by diffusion SDEs for low-resolutional images + (such as CIFAR-10). However, we observed that such trick does not matter for + high-resolutional images. As it needs an additional NFE, we do not recommend + it for high-resolutional images. + lower_order_final: A `bool`. Whether to use lower order solvers at the final steps. + Only valid for `method=multistep` and `steps < 15`. We empirically find that + this trick is a key to stabilizing the sampling by DPM-Solver with very few steps + (especially for steps <= 10). So we recommend to set it to be `True`. + solver_type: A `str`. The taylor expansion type for the solver. `dpm_solver` or `taylor`. We recommend `dpm_solver`. + atol: A `float`. The absolute tolerance of the adaptive step size solver. Valid when `method` == 'adaptive'. + rtol: A `float`. The relative tolerance of the adaptive step size solver. Valid when `method` == 'adaptive'. + Returns: + x_end: A pytorch tensor. The approximated solution at time `t_end`. + """ + t_0 = 1. / self.noise_schedule.total_N if t_end is None else t_end + t_T = self.noise_schedule.T if t_start is None else t_start + device = x.device + if method == 'adaptive': + with torch.no_grad(): + x = self.dpm_solver_adaptive(x, order=order, t_T=t_T, t_0=t_0, atol=atol, rtol=rtol, + solver_type=solver_type) + elif method == 'multistep': + assert steps >= order + timesteps = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=steps, device=device) + assert timesteps.shape[0] - 1 == steps + with torch.no_grad(): + vec_t = timesteps[0].expand((x.shape[0])) + model_prev_list = [self.model_fn(x, vec_t)] + t_prev_list = [vec_t] + # Init the first `order` values by lower order multistep DPM-Solver. + for init_order in tqdm(range(1, order), desc="DPM init order"): + vec_t = timesteps[init_order].expand(x.shape[0]) + x = self.multistep_dpm_solver_update(x, model_prev_list, t_prev_list, vec_t, init_order, + solver_type=solver_type) + model_prev_list.append(self.model_fn(x, vec_t)) + t_prev_list.append(vec_t) + # Compute the remaining values by `order`-th order multistep DPM-Solver. + for step in tqdm(range(order, steps + 1), desc="DPM multistep"): + vec_t = timesteps[step].expand(x.shape[0]) + if lower_order_final and steps < 15: + step_order = min(order, steps + 1 - step) + else: + step_order = order + x = self.multistep_dpm_solver_update(x, model_prev_list, t_prev_list, vec_t, step_order, + solver_type=solver_type) + for i in range(order - 1): + t_prev_list[i] = t_prev_list[i + 1] + model_prev_list[i] = model_prev_list[i + 1] + t_prev_list[-1] = vec_t + # We do not need to evaluate the final model value. + if step < steps: + model_prev_list[-1] = self.model_fn(x, vec_t) + elif method in ['singlestep', 'singlestep_fixed']: + if method == 'singlestep': + timesteps_outer, orders = self.get_orders_and_timesteps_for_singlestep_solver(steps=steps, order=order, + skip_type=skip_type, + t_T=t_T, t_0=t_0, + device=device) + elif method == 'singlestep_fixed': + K = steps // order + orders = [order, ] * K + timesteps_outer = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=K, device=device) + for i, order in enumerate(orders): + t_T_inner, t_0_inner = timesteps_outer[i], timesteps_outer[i + 1] + timesteps_inner = self.get_time_steps(skip_type=skip_type, t_T=t_T_inner.item(), t_0=t_0_inner.item(), + N=order, device=device) + lambda_inner = self.noise_schedule.marginal_lambda(timesteps_inner) + vec_s, vec_t = t_T_inner.tile(x.shape[0]), t_0_inner.tile(x.shape[0]) + h = lambda_inner[-1] - lambda_inner[0] + r1 = None if order <= 1 else (lambda_inner[1] - lambda_inner[0]) / h + r2 = None if order <= 2 else (lambda_inner[2] - lambda_inner[0]) / h + x = self.singlestep_dpm_solver_update(x, vec_s, vec_t, order, solver_type=solver_type, r1=r1, r2=r2) + if denoise_to_zero: + x = self.denoise_to_zero_fn(x, torch.ones((x.shape[0],)).to(device) * t_0) + return x + + +############################################################# +# other utility functions +############################################################# + +def interpolate_fn(x, xp, yp): + """ + A piecewise linear function y = f(x), using xp and yp as keypoints. + We implement f(x) in a differentiable way (i.e. applicable for autograd). + The function f(x) is well-defined for all x-axis. (For x beyond the bounds of xp, we use the outmost points of xp to define the linear function.) + Args: + x: PyTorch tensor with shape [N, C], where N is the batch size, C is the number of channels (we use C = 1 for DPM-Solver). + xp: PyTorch tensor with shape [C, K], where K is the number of keypoints. + yp: PyTorch tensor with shape [C, K]. + Returns: + The function values f(x), with shape [N, C]. + """ + N, K = x.shape[0], xp.shape[1] + all_x = torch.cat([x.unsqueeze(2), xp.unsqueeze(0).repeat((N, 1, 1))], dim=2) + sorted_all_x, x_indices = torch.sort(all_x, dim=2) + x_idx = torch.argmin(x_indices, dim=2) + cand_start_idx = x_idx - 1 + start_idx = torch.where( + torch.eq(x_idx, 0), + torch.tensor(1, device=x.device), + torch.where( + torch.eq(x_idx, K), torch.tensor(K - 2, device=x.device), cand_start_idx, + ), + ) + end_idx = torch.where(torch.eq(start_idx, cand_start_idx), start_idx + 2, start_idx + 1) + start_x = torch.gather(sorted_all_x, dim=2, index=start_idx.unsqueeze(2)).squeeze(2) + end_x = torch.gather(sorted_all_x, dim=2, index=end_idx.unsqueeze(2)).squeeze(2) + start_idx2 = torch.where( + torch.eq(x_idx, 0), + torch.tensor(0, device=x.device), + torch.where( + torch.eq(x_idx, K), torch.tensor(K - 2, device=x.device), cand_start_idx, + ), + ) + y_positions_expanded = yp.unsqueeze(0).expand(N, -1, -1) + start_y = torch.gather(y_positions_expanded, dim=2, index=start_idx2.unsqueeze(2)).squeeze(2) + end_y = torch.gather(y_positions_expanded, dim=2, index=(start_idx2 + 1).unsqueeze(2)).squeeze(2) + cand = start_y + (x - start_x) * (end_y - start_y) / (end_x - start_x) + return cand + + +def expand_dims(v, dims): + """ + Expand the tensor `v` to the dim `dims`. + Args: + `v`: a PyTorch tensor with shape [N]. + `dim`: a `int`. + Returns: + a PyTorch tensor with shape [N, 1, 1, ..., 1] and the total dimension is `dims`. + """ + return v[(...,) + (None,) * (dims - 1)] \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/sampler.py b/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/sampler.py new file mode 100644 index 0000000..7d137b8 --- /dev/null +++ b/comparison_models/ControlNet/ldm/models/diffusion/dpm_solver/sampler.py @@ -0,0 +1,87 @@ +"""SAMPLING ONLY.""" +import torch + +from .dpm_solver import NoiseScheduleVP, model_wrapper, DPM_Solver + + +MODEL_TYPES = { + "eps": "noise", + "v": "v" +} + + +class DPMSolverSampler(object): + def __init__(self, model, **kwargs): + super().__init__() + self.model = model + to_torch = lambda x: x.clone().detach().to(torch.float32).to(model.device) + self.register_buffer('alphas_cumprod', to_torch(model.alphas_cumprod)) + + def register_buffer(self, name, attr): + if type(attr) == torch.Tensor: + if attr.device != torch.device("cuda"): + attr = attr.to(torch.device("cuda")) + setattr(self, name, attr) + + @torch.no_grad() + def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + cbs = conditioning[list(conditioning.keys())[0]].shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + + print(f'Data shape for DPM-Solver sampling is {size}, sampling steps {S}') + + device = self.model.betas.device + if x_T is None: + img = torch.randn(size, device=device) + else: + img = x_T + + ns = NoiseScheduleVP('discrete', alphas_cumprod=self.alphas_cumprod) + + model_fn = model_wrapper( + lambda x, t, c: self.model.apply_model(x, t, c), + ns, + model_type=MODEL_TYPES[self.model.parameterization], + guidance_type="classifier-free", + condition=conditioning, + unconditional_condition=unconditional_conditioning, + guidance_scale=unconditional_guidance_scale, + ) + + dpm_solver = DPM_Solver(model_fn, ns, predict_x0=True, thresholding=False) + x = dpm_solver.sample(img, steps=S, skip_type="time_uniform", method="multistep", order=2, lower_order_final=True) + + return x.to(device), None \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/models/diffusion/plms.py b/comparison_models/ControlNet/ldm/models/diffusion/plms.py new file mode 100644 index 0000000..7002a36 --- /dev/null +++ b/comparison_models/ControlNet/ldm/models/diffusion/plms.py @@ -0,0 +1,244 @@ +"""SAMPLING ONLY.""" + +import torch +import numpy as np +from tqdm import tqdm +from functools import partial + +from ldm.modules.diffusionmodules.util import make_ddim_sampling_parameters, make_ddim_timesteps, noise_like +from ldm.models.diffusion.sampling_util import norm_thresholding + + +class PLMSSampler(object): + def __init__(self, model, schedule="linear", **kwargs): + super().__init__() + self.model = model + self.ddpm_num_timesteps = model.num_timesteps + self.schedule = schedule + + def register_buffer(self, name, attr): + if type(attr) == torch.Tensor: + if attr.device != torch.device("cuda"): + attr = attr.to(torch.device("cuda")) + setattr(self, name, attr) + + def make_schedule(self, ddim_num_steps, ddim_discretize="uniform", ddim_eta=0., verbose=True): + if ddim_eta != 0: + raise ValueError('ddim_eta must be 0 for PLMS') + self.ddim_timesteps = make_ddim_timesteps(ddim_discr_method=ddim_discretize, num_ddim_timesteps=ddim_num_steps, + num_ddpm_timesteps=self.ddpm_num_timesteps,verbose=verbose) + alphas_cumprod = self.model.alphas_cumprod + assert alphas_cumprod.shape[0] == self.ddpm_num_timesteps, 'alphas have to be defined for each timestep' + to_torch = lambda x: x.clone().detach().to(torch.float32).to(self.model.device) + + self.register_buffer('betas', to_torch(self.model.betas)) + self.register_buffer('alphas_cumprod', to_torch(alphas_cumprod)) + self.register_buffer('alphas_cumprod_prev', to_torch(self.model.alphas_cumprod_prev)) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod.cpu()))) + self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod.cpu()))) + self.register_buffer('log_one_minus_alphas_cumprod', to_torch(np.log(1. - alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recip_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu()))) + self.register_buffer('sqrt_recipm1_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod.cpu() - 1))) + + # ddim sampling parameters + ddim_sigmas, ddim_alphas, ddim_alphas_prev = make_ddim_sampling_parameters(alphacums=alphas_cumprod.cpu(), + ddim_timesteps=self.ddim_timesteps, + eta=ddim_eta,verbose=verbose) + self.register_buffer('ddim_sigmas', ddim_sigmas) + self.register_buffer('ddim_alphas', ddim_alphas) + self.register_buffer('ddim_alphas_prev', ddim_alphas_prev) + self.register_buffer('ddim_sqrt_one_minus_alphas', np.sqrt(1. - ddim_alphas)) + sigmas_for_original_sampling_steps = ddim_eta * torch.sqrt( + (1 - self.alphas_cumprod_prev) / (1 - self.alphas_cumprod) * ( + 1 - self.alphas_cumprod / self.alphas_cumprod_prev)) + self.register_buffer('ddim_sigmas_for_original_num_steps', sigmas_for_original_sampling_steps) + + @torch.no_grad() + def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + dynamic_threshold=None, + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + cbs = conditioning[list(conditioning.keys())[0]].shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose) + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + print(f'Data shape for PLMS sampling is {size}') + + samples, intermediates = self.plms_sampling(conditioning, size, + callback=callback, + img_callback=img_callback, + quantize_denoised=quantize_x0, + mask=mask, x0=x0, + ddim_use_original_steps=False, + noise_dropout=noise_dropout, + temperature=temperature, + score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + x_T=x_T, + log_every_t=log_every_t, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + dynamic_threshold=dynamic_threshold, + ) + return samples, intermediates + + @torch.no_grad() + def plms_sampling(self, cond, shape, + x_T=None, ddim_use_original_steps=False, + callback=None, timesteps=None, quantize_denoised=False, + mask=None, x0=None, img_callback=None, log_every_t=100, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, + dynamic_threshold=None): + device = self.model.betas.device + b = shape[0] + if x_T is None: + img = torch.randn(shape, device=device) + else: + img = x_T + + if timesteps is None: + timesteps = self.ddpm_num_timesteps if ddim_use_original_steps else self.ddim_timesteps + elif timesteps is not None and not ddim_use_original_steps: + subset_end = int(min(timesteps / self.ddim_timesteps.shape[0], 1) * self.ddim_timesteps.shape[0]) - 1 + timesteps = self.ddim_timesteps[:subset_end] + + intermediates = {'x_inter': [img], 'pred_x0': [img]} + time_range = list(reversed(range(0,timesteps))) if ddim_use_original_steps else np.flip(timesteps) + total_steps = timesteps if ddim_use_original_steps else timesteps.shape[0] + print(f"Running PLMS Sampling with {total_steps} timesteps") + + iterator = tqdm(time_range, desc='PLMS Sampler', total=total_steps) + old_eps = [] + + for i, step in enumerate(iterator): + index = total_steps - i - 1 + ts = torch.full((b,), step, device=device, dtype=torch.long) + ts_next = torch.full((b,), time_range[min(i + 1, len(time_range) - 1)], device=device, dtype=torch.long) + + if mask is not None: + assert x0 is not None + img_orig = self.model.q_sample(x0, ts) # TODO: deterministic forward pass? + img = img_orig * mask + (1. - mask) * img + + outs = self.p_sample_plms(img, cond, ts, index=index, use_original_steps=ddim_use_original_steps, + quantize_denoised=quantize_denoised, temperature=temperature, + noise_dropout=noise_dropout, score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + old_eps=old_eps, t_next=ts_next, + dynamic_threshold=dynamic_threshold) + img, pred_x0, e_t = outs + old_eps.append(e_t) + if len(old_eps) >= 4: + old_eps.pop(0) + if callback: callback(i) + if img_callback: img_callback(pred_x0, i) + + if index % log_every_t == 0 or index == total_steps - 1: + intermediates['x_inter'].append(img) + intermediates['pred_x0'].append(pred_x0) + + return img, intermediates + + @torch.no_grad() + def p_sample_plms(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, old_eps=None, t_next=None, + dynamic_threshold=None): + b, *_, device = *x.shape, x.device + + def get_model_output(x, t): + if unconditional_conditioning is None or unconditional_guidance_scale == 1.: + e_t = self.model.apply_model(x, t, c) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t] * 2) + c_in = torch.cat([unconditional_conditioning, c]) + e_t_uncond, e_t = self.model.apply_model(x_in, t_in, c_in).chunk(2) + e_t = e_t_uncond + unconditional_guidance_scale * (e_t - e_t_uncond) + + if score_corrector is not None: + assert self.model.parameterization == "eps" + e_t = score_corrector.modify_score(self.model, e_t, x, t, c, **corrector_kwargs) + + return e_t + + alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas + alphas_prev = self.model.alphas_cumprod_prev if use_original_steps else self.ddim_alphas_prev + sqrt_one_minus_alphas = self.model.sqrt_one_minus_alphas_cumprod if use_original_steps else self.ddim_sqrt_one_minus_alphas + sigmas = self.model.ddim_sigmas_for_original_num_steps if use_original_steps else self.ddim_sigmas + + def get_x_prev_and_pred_x0(e_t, index): + # select parameters corresponding to the currently considered timestep + a_t = torch.full((b, 1, 1, 1), alphas[index], device=device) + a_prev = torch.full((b, 1, 1, 1), alphas_prev[index], device=device) + sigma_t = torch.full((b, 1, 1, 1), sigmas[index], device=device) + sqrt_one_minus_at = torch.full((b, 1, 1, 1), sqrt_one_minus_alphas[index],device=device) + + # current prediction for x_0 + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + if quantize_denoised: + pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0) + if dynamic_threshold is not None: + pred_x0 = norm_thresholding(pred_x0, dynamic_threshold) + # direction pointing to x_t + dir_xt = (1. - a_prev - sigma_t**2).sqrt() * e_t + noise = sigma_t * noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + x_prev = a_prev.sqrt() * pred_x0 + dir_xt + noise + return x_prev, pred_x0 + + e_t = get_model_output(x, t) + if len(old_eps) == 0: + # Pseudo Improved Euler (2nd order) + x_prev, pred_x0 = get_x_prev_and_pred_x0(e_t, index) + e_t_next = get_model_output(x_prev, t_next) + e_t_prime = (e_t + e_t_next) / 2 + elif len(old_eps) == 1: + # 2nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (3 * e_t - old_eps[-1]) / 2 + elif len(old_eps) == 2: + # 3nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (23 * e_t - 16 * old_eps[-1] + 5 * old_eps[-2]) / 12 + elif len(old_eps) >= 3: + # 4nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (55 * e_t - 59 * old_eps[-1] + 37 * old_eps[-2] - 9 * old_eps[-3]) / 24 + + x_prev, pred_x0 = get_x_prev_and_pred_x0(e_t_prime, index) + + return x_prev, pred_x0, e_t diff --git a/comparison_models/ControlNet/ldm/models/diffusion/sampling_util.py b/comparison_models/ControlNet/ldm/models/diffusion/sampling_util.py new file mode 100644 index 0000000..7eff02b --- /dev/null +++ b/comparison_models/ControlNet/ldm/models/diffusion/sampling_util.py @@ -0,0 +1,22 @@ +import torch +import numpy as np + + +def append_dims(x, target_dims): + """Appends dimensions to the end of a tensor until it has target_dims dimensions. + From https://github.com/crowsonkb/k-diffusion/blob/master/k_diffusion/utils.py""" + dims_to_append = target_dims - x.ndim + if dims_to_append < 0: + raise ValueError(f'input has {x.ndim} dims but target_dims is {target_dims}, which is less') + return x[(...,) + (None,) * dims_to_append] + + +def norm_thresholding(x0, value): + s = append_dims(x0.pow(2).flatten(1).mean(1).sqrt().clamp(min=value), x0.ndim) + return x0 * (value / s) + + +def spatial_norm_thresholding(x0, value): + # b c h w + s = x0.pow(2).mean(1, keepdim=True).sqrt().clamp(min=value) + return x0 * (value / s) \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/modules/attention.py b/comparison_models/ControlNet/ldm/modules/attention.py new file mode 100644 index 0000000..509cd87 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/attention.py @@ -0,0 +1,341 @@ +from inspect import isfunction +import math +import torch +import torch.nn.functional as F +from torch import nn, einsum +from einops import rearrange, repeat +from typing import Optional, Any + +from ldm.modules.diffusionmodules.util import checkpoint + + +try: + import xformers + import xformers.ops + XFORMERS_IS_AVAILBLE = True +except: + XFORMERS_IS_AVAILBLE = False + +# CrossAttn precision handling +import os +_ATTN_PRECISION = os.environ.get("ATTN_PRECISION", "fp32") + +def exists(val): + return val is not None + + +def uniq(arr): + return{el: True for el in arr}.keys() + + +def default(val, d): + if exists(val): + return val + return d() if isfunction(d) else d + + +def max_neg_value(t): + return -torch.finfo(t.dtype).max + + +def init_(tensor): + dim = tensor.shape[-1] + std = 1 / math.sqrt(dim) + tensor.uniform_(-std, std) + return tensor + + +# feedforward +class GEGLU(nn.Module): + def __init__(self, dim_in, dim_out): + super().__init__() + self.proj = nn.Linear(dim_in, dim_out * 2) + + def forward(self, x): + x, gate = self.proj(x).chunk(2, dim=-1) + return x * F.gelu(gate) + + +class FeedForward(nn.Module): + def __init__(self, dim, dim_out=None, mult=4, glu=False, dropout=0.): + super().__init__() + inner_dim = int(dim * mult) + dim_out = default(dim_out, dim) + project_in = nn.Sequential( + nn.Linear(dim, inner_dim), + nn.GELU() + ) if not glu else GEGLU(dim, inner_dim) + + self.net = nn.Sequential( + project_in, + nn.Dropout(dropout), + nn.Linear(inner_dim, dim_out) + ) + + def forward(self, x): + return self.net(x) + + +def zero_module(module): + """ + Zero out the parameters of a module and return it. + """ + for p in module.parameters(): + p.detach().zero_() + return module + + +def Normalize(in_channels): + return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True) + + +class SpatialSelfAttention(nn.Module): + def __init__(self, in_channels): + super().__init__() + self.in_channels = in_channels + + self.norm = Normalize(in_channels) + self.q = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.k = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.v = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.proj_out = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + + def forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + b,c,h,w = q.shape + q = rearrange(q, 'b c h w -> b (h w) c') + k = rearrange(k, 'b c h w -> b c (h w)') + w_ = torch.einsum('bij,bjk->bik', q, k) + + w_ = w_ * (int(c)**(-0.5)) + w_ = torch.nn.functional.softmax(w_, dim=2) + + # attend to values + v = rearrange(v, 'b c h w -> b c (h w)') + w_ = rearrange(w_, 'b i j -> b j i') + h_ = torch.einsum('bij,bjk->bik', v, w_) + h_ = rearrange(h_, 'b c (h w) -> b c h w', h=h) + h_ = self.proj_out(h_) + + return x+h_ + + +class CrossAttention(nn.Module): + def __init__(self, query_dim, context_dim=None, heads=8, dim_head=64, dropout=0.): + super().__init__() + inner_dim = dim_head * heads + context_dim = default(context_dim, query_dim) + + self.scale = dim_head ** -0.5 + self.heads = heads + + self.to_q = nn.Linear(query_dim, inner_dim, bias=False) + self.to_k = nn.Linear(context_dim, inner_dim, bias=False) + self.to_v = nn.Linear(context_dim, inner_dim, bias=False) + + self.to_out = nn.Sequential( + nn.Linear(inner_dim, query_dim), + nn.Dropout(dropout) + ) + + def forward(self, x, context=None, mask=None): + h = self.heads + + q = self.to_q(x) + context = default(context, x) + k = self.to_k(context) + v = self.to_v(context) + + q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v)) + + # force cast to fp32 to avoid overflowing + if _ATTN_PRECISION =="fp32": + with torch.autocast(enabled=False, device_type = 'cuda'): + q, k = q.float(), k.float() + sim = einsum('b i d, b j d -> b i j', q, k) * self.scale + else: + sim = einsum('b i d, b j d -> b i j', q, k) * self.scale + + del q, k + + if exists(mask): + mask = rearrange(mask, 'b ... -> b (...)') + max_neg_value = -torch.finfo(sim.dtype).max + mask = repeat(mask, 'b j -> (b h) () j', h=h) + sim.masked_fill_(~mask, max_neg_value) + + # attention, what we cannot get enough of + sim = sim.softmax(dim=-1) + + out = einsum('b i j, b j d -> b i d', sim, v) + out = rearrange(out, '(b h) n d -> b n (h d)', h=h) + return self.to_out(out) + + +class MemoryEfficientCrossAttention(nn.Module): + # https://github.com/MatthieuTPHR/diffusers/blob/d80b531ff8060ec1ea982b65a1b8df70f73aa67c/src/diffusers/models/attention.py#L223 + def __init__(self, query_dim, context_dim=None, heads=8, dim_head=64, dropout=0.0): + super().__init__() + print(f"Setting up {self.__class__.__name__}. Query dim is {query_dim}, context_dim is {context_dim} and using " + f"{heads} heads.") + inner_dim = dim_head * heads + context_dim = default(context_dim, query_dim) + + self.heads = heads + self.dim_head = dim_head + + self.to_q = nn.Linear(query_dim, inner_dim, bias=False) + self.to_k = nn.Linear(context_dim, inner_dim, bias=False) + self.to_v = nn.Linear(context_dim, inner_dim, bias=False) + + self.to_out = nn.Sequential(nn.Linear(inner_dim, query_dim), nn.Dropout(dropout)) + self.attention_op: Optional[Any] = None + + def forward(self, x, context=None, mask=None): + q = self.to_q(x) + context = default(context, x) + k = self.to_k(context) + v = self.to_v(context) + + b, _, _ = q.shape + q, k, v = map( + lambda t: t.unsqueeze(3) + .reshape(b, t.shape[1], self.heads, self.dim_head) + .permute(0, 2, 1, 3) + .reshape(b * self.heads, t.shape[1], self.dim_head) + .contiguous(), + (q, k, v), + ) + + # actually compute the attention, what we cannot get enough of + out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=None, op=self.attention_op) + + if exists(mask): + raise NotImplementedError + out = ( + out.unsqueeze(0) + .reshape(b, self.heads, out.shape[1], self.dim_head) + .permute(0, 2, 1, 3) + .reshape(b, out.shape[1], self.heads * self.dim_head) + ) + return self.to_out(out) + + +class BasicTransformerBlock(nn.Module): + ATTENTION_MODES = { + "softmax": CrossAttention, # vanilla attention + "softmax-xformers": MemoryEfficientCrossAttention + } + def __init__(self, dim, n_heads, d_head, dropout=0., context_dim=None, gated_ff=True, checkpoint=True, + disable_self_attn=False): + super().__init__() + attn_mode = "softmax-xformers" if XFORMERS_IS_AVAILBLE else "softmax" + assert attn_mode in self.ATTENTION_MODES + attn_cls = self.ATTENTION_MODES[attn_mode] + self.disable_self_attn = disable_self_attn + self.attn1 = attn_cls(query_dim=dim, heads=n_heads, dim_head=d_head, dropout=dropout, + context_dim=context_dim if self.disable_self_attn else None) # is a self-attention if not self.disable_self_attn + self.ff = FeedForward(dim, dropout=dropout, glu=gated_ff) + self.attn2 = attn_cls(query_dim=dim, context_dim=context_dim, + heads=n_heads, dim_head=d_head, dropout=dropout) # is self-attn if context is none + self.norm1 = nn.LayerNorm(dim) + self.norm2 = nn.LayerNorm(dim) + self.norm3 = nn.LayerNorm(dim) + self.checkpoint = checkpoint + + def forward(self, x, context=None): + return checkpoint(self._forward, (x, context), self.parameters(), self.checkpoint) + + def _forward(self, x, context=None): + x = self.attn1(self.norm1(x), context=context if self.disable_self_attn else None) + x + x = self.attn2(self.norm2(x), context=context) + x + x = self.ff(self.norm3(x)) + x + return x + + +class SpatialTransformer(nn.Module): + """ + Transformer block for image-like data. + First, project the input (aka embedding) + and reshape to b, t, d. + Then apply standard transformer action. + Finally, reshape to image + NEW: use_linear for more efficiency instead of the 1x1 convs + """ + def __init__(self, in_channels, n_heads, d_head, + depth=1, dropout=0., context_dim=None, + disable_self_attn=False, use_linear=False, + use_checkpoint=True): + super().__init__() + if exists(context_dim) and not isinstance(context_dim, list): + context_dim = [context_dim] + self.in_channels = in_channels + inner_dim = n_heads * d_head + self.norm = Normalize(in_channels) + if not use_linear: + self.proj_in = nn.Conv2d(in_channels, + inner_dim, + kernel_size=1, + stride=1, + padding=0) + else: + self.proj_in = nn.Linear(in_channels, inner_dim) + + self.transformer_blocks = nn.ModuleList( + [BasicTransformerBlock(inner_dim, n_heads, d_head, dropout=dropout, context_dim=context_dim[d], + disable_self_attn=disable_self_attn, checkpoint=use_checkpoint) + for d in range(depth)] + ) + if not use_linear: + self.proj_out = zero_module(nn.Conv2d(inner_dim, + in_channels, + kernel_size=1, + stride=1, + padding=0)) + else: + self.proj_out = zero_module(nn.Linear(in_channels, inner_dim)) + self.use_linear = use_linear + + def forward(self, x, context=None): + # note: if no context is given, cross-attention defaults to self-attention + if not isinstance(context, list): + context = [context] + b, c, h, w = x.shape + x_in = x + x = self.norm(x) + if not self.use_linear: + x = self.proj_in(x) + x = rearrange(x, 'b c h w -> b (h w) c').contiguous() + if self.use_linear: + x = self.proj_in(x) + for i, block in enumerate(self.transformer_blocks): + x = block(x, context=context[i]) + if self.use_linear: + x = self.proj_out(x) + x = rearrange(x, 'b (h w) c -> b c h w', h=h, w=w).contiguous() + if not self.use_linear: + x = self.proj_out(x) + return x + x_in + diff --git a/comparison_models/ControlNet/ldm/modules/diffusionmodules/__init__.py b/comparison_models/ControlNet/ldm/modules/diffusionmodules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/ControlNet/ldm/modules/diffusionmodules/model.py b/comparison_models/ControlNet/ldm/modules/diffusionmodules/model.py new file mode 100644 index 0000000..b089eeb --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/diffusionmodules/model.py @@ -0,0 +1,852 @@ +# pytorch_diffusion + derived encoder decoder +import math +import torch +import torch.nn as nn +import numpy as np +from einops import rearrange +from typing import Optional, Any + +from ldm.modules.attention import MemoryEfficientCrossAttention + +try: + import xformers + import xformers.ops + XFORMERS_IS_AVAILBLE = True +except: + XFORMERS_IS_AVAILBLE = False + print("No module 'xformers'. Proceeding without it.") + + +def get_timestep_embedding(timesteps, embedding_dim): + """ + This matches the implementation in Denoising Diffusion Probabilistic Models: + From Fairseq. + Build sinusoidal embeddings. + This matches the implementation in tensor2tensor, but differs slightly + from the description in Section 3.5 of "Attention Is All You Need". + """ + assert len(timesteps.shape) == 1 + + half_dim = embedding_dim // 2 + emb = math.log(10000) / (half_dim - 1) + emb = torch.exp(torch.arange(half_dim, dtype=torch.float32) * -emb) + emb = emb.to(device=timesteps.device) + emb = timesteps.float()[:, None] * emb[None, :] + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1) + if embedding_dim % 2 == 1: # zero pad + emb = torch.nn.functional.pad(emb, (0,1,0,0)) + return emb + + +def nonlinearity(x): + # swish + return x*torch.sigmoid(x) + + +def Normalize(in_channels, num_groups=32): + return torch.nn.GroupNorm(num_groups=num_groups, num_channels=in_channels, eps=1e-6, affine=True) + + +class Upsample(nn.Module): + def __init__(self, in_channels, with_conv): + super().__init__() + self.with_conv = with_conv + if self.with_conv: + self.conv = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + x = torch.nn.functional.interpolate(x, scale_factor=2.0, mode="nearest") + if self.with_conv: + x = self.conv(x) + return x + + +class Downsample(nn.Module): + def __init__(self, in_channels, with_conv): + super().__init__() + self.with_conv = with_conv + if self.with_conv: + # no asymmetric padding in torch conv, must do it ourselves + self.conv = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=3, + stride=2, + padding=0) + + def forward(self, x): + if self.with_conv: + pad = (0,1,0,1) + x = torch.nn.functional.pad(x, pad, mode="constant", value=0) + x = self.conv(x) + else: + x = torch.nn.functional.avg_pool2d(x, kernel_size=2, stride=2) + return x + + +class ResnetBlock(nn.Module): + def __init__(self, *, in_channels, out_channels=None, conv_shortcut=False, + dropout, temb_channels=512): + super().__init__() + self.in_channels = in_channels + out_channels = in_channels if out_channels is None else out_channels + self.out_channels = out_channels + self.use_conv_shortcut = conv_shortcut + + self.norm1 = Normalize(in_channels) + self.conv1 = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + if temb_channels > 0: + self.temb_proj = torch.nn.Linear(temb_channels, + out_channels) + self.norm2 = Normalize(out_channels) + self.dropout = torch.nn.Dropout(dropout) + self.conv2 = torch.nn.Conv2d(out_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + if self.in_channels != self.out_channels: + if self.use_conv_shortcut: + self.conv_shortcut = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + else: + self.nin_shortcut = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=1, + stride=1, + padding=0) + + def forward(self, x, temb): + h = x + h = self.norm1(h) + h = nonlinearity(h) + h = self.conv1(h) + + if temb is not None: + h = h + self.temb_proj(nonlinearity(temb))[:,:,None,None] + + h = self.norm2(h) + h = nonlinearity(h) + h = self.dropout(h) + h = self.conv2(h) + + if self.in_channels != self.out_channels: + if self.use_conv_shortcut: + x = self.conv_shortcut(x) + else: + x = self.nin_shortcut(x) + + return x+h + + +class AttnBlock(nn.Module): + def __init__(self, in_channels): + super().__init__() + self.in_channels = in_channels + + self.norm = Normalize(in_channels) + self.q = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.k = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.v = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.proj_out = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + + def forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + b,c,h,w = q.shape + q = q.reshape(b,c,h*w) + q = q.permute(0,2,1) # b,hw,c + k = k.reshape(b,c,h*w) # b,c,hw + w_ = torch.bmm(q,k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j] + w_ = w_ * (int(c)**(-0.5)) + w_ = torch.nn.functional.softmax(w_, dim=2) + + # attend to values + v = v.reshape(b,c,h*w) + w_ = w_.permute(0,2,1) # b,hw,hw (first hw of k, second of q) + h_ = torch.bmm(v,w_) # b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j] + h_ = h_.reshape(b,c,h,w) + + h_ = self.proj_out(h_) + + return x+h_ + +class MemoryEfficientAttnBlock(nn.Module): + """ + Uses xformers efficient implementation, + see https://github.com/MatthieuTPHR/diffusers/blob/d80b531ff8060ec1ea982b65a1b8df70f73aa67c/src/diffusers/models/attention.py#L223 + Note: this is a single-head self-attention operation + """ + # + def __init__(self, in_channels): + super().__init__() + self.in_channels = in_channels + + self.norm = Normalize(in_channels) + self.q = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.k = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.v = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.proj_out = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=1, + stride=1, + padding=0) + self.attention_op: Optional[Any] = None + + def forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + B, C, H, W = q.shape + q, k, v = map(lambda x: rearrange(x, 'b c h w -> b (h w) c'), (q, k, v)) + + q, k, v = map( + lambda t: t.unsqueeze(3) + .reshape(B, t.shape[1], 1, C) + .permute(0, 2, 1, 3) + .reshape(B * 1, t.shape[1], C) + .contiguous(), + (q, k, v), + ) + out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=None, op=self.attention_op) + + out = ( + out.unsqueeze(0) + .reshape(B, 1, out.shape[1], C) + .permute(0, 2, 1, 3) + .reshape(B, out.shape[1], C) + ) + out = rearrange(out, 'b (h w) c -> b c h w', b=B, h=H, w=W, c=C) + out = self.proj_out(out) + return x+out + + +class MemoryEfficientCrossAttentionWrapper(MemoryEfficientCrossAttention): + def forward(self, x, context=None, mask=None): + b, c, h, w = x.shape + x = rearrange(x, 'b c h w -> b (h w) c') + out = super().forward(x, context=context, mask=mask) + out = rearrange(out, 'b (h w) c -> b c h w', h=h, w=w, c=c) + return x + out + + +def make_attn(in_channels, attn_type="vanilla", attn_kwargs=None): + assert attn_type in ["vanilla", "vanilla-xformers", "memory-efficient-cross-attn", "linear", "none"], f'attn_type {attn_type} unknown' + if XFORMERS_IS_AVAILBLE and attn_type == "vanilla": + attn_type = "vanilla-xformers" + print(f"making attention of type '{attn_type}' with {in_channels} in_channels") + if attn_type == "vanilla": + assert attn_kwargs is None + return AttnBlock(in_channels) + elif attn_type == "vanilla-xformers": + print(f"building MemoryEfficientAttnBlock with {in_channels} in_channels...") + return MemoryEfficientAttnBlock(in_channels) + elif type == "memory-efficient-cross-attn": + attn_kwargs["query_dim"] = in_channels + return MemoryEfficientCrossAttentionWrapper(**attn_kwargs) + elif attn_type == "none": + return nn.Identity(in_channels) + else: + raise NotImplementedError() + + +class Model(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, use_timestep=True, use_linear_attn=False, attn_type="vanilla"): + super().__init__() + if use_linear_attn: attn_type = "linear" + self.ch = ch + self.temb_ch = self.ch*4 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + + self.use_timestep = use_timestep + if self.use_timestep: + # timestep embedding + self.temb = nn.Module() + self.temb.dense = nn.ModuleList([ + torch.nn.Linear(self.ch, + self.temb_ch), + torch.nn.Linear(self.temb_ch, + self.temb_ch), + ]) + + # downsampling + self.conv_in = torch.nn.Conv2d(in_channels, + self.ch, + kernel_size=3, + stride=1, + padding=1) + + curr_res = resolution + in_ch_mult = (1,)+tuple(ch_mult) + self.down = nn.ModuleList() + for i_level in range(self.num_resolutions): + block = nn.ModuleList() + attn = nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(make_attn(block_in, attn_type=attn_type)) + down = nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions-1: + down.downsample = Downsample(block_in, resamp_with_conv) + curr_res = curr_res // 2 + self.down.append(down) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = make_attn(block_in, attn_type=attn_type) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # upsampling + self.up = nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = nn.ModuleList() + attn = nn.ModuleList() + block_out = ch*ch_mult[i_level] + skip_in = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks+1): + if i_block == self.num_res_blocks: + skip_in = ch*in_ch_mult[i_level] + block.append(ResnetBlock(in_channels=block_in+skip_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(make_attn(block_in, attn_type=attn_type)) + up = nn.Module() + up.block = block + up.attn = attn + if i_level != 0: + up.upsample = Upsample(block_in, resamp_with_conv) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_ch, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x, t=None, context=None): + #assert x.shape[2] == x.shape[3] == self.resolution + if context is not None: + # assume aligned context, cat along channel axis + x = torch.cat((x, context), dim=1) + if self.use_timestep: + # timestep embedding + assert t is not None + temb = get_timestep_embedding(t, self.ch) + temb = self.temb.dense[0](temb) + temb = nonlinearity(temb) + temb = self.temb.dense[1](temb) + else: + temb = None + + # downsampling + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level].block[i_block](hs[-1], temb) + if len(self.down[i_level].attn) > 0: + h = self.down[i_level].attn[i_block](h) + hs.append(h) + if i_level != self.num_resolutions-1: + hs.append(self.down[i_level].downsample(hs[-1])) + + # middle + h = hs[-1] + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks+1): + h = self.up[i_level].block[i_block]( + torch.cat([h, hs.pop()], dim=1), temb) + if len(self.up[i_level].attn) > 0: + h = self.up[i_level].attn[i_block](h) + if i_level != 0: + h = self.up[i_level].upsample(h) + + # end + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + def get_last_layer(self): + return self.conv_out.weight + + +class Encoder(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, z_channels, double_z=True, use_linear_attn=False, attn_type="vanilla", + **ignore_kwargs): + super().__init__() + if use_linear_attn: attn_type = "linear" + self.ch = ch + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + + # downsampling + self.conv_in = torch.nn.Conv2d(in_channels, + self.ch, + kernel_size=3, + stride=1, + padding=1) + + curr_res = resolution + in_ch_mult = (1,)+tuple(ch_mult) + self.in_ch_mult = in_ch_mult + self.down = nn.ModuleList() + for i_level in range(self.num_resolutions): + block = nn.ModuleList() + attn = nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(make_attn(block_in, attn_type=attn_type)) + down = nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions-1: + down.downsample = Downsample(block_in, resamp_with_conv) + curr_res = curr_res // 2 + self.down.append(down) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = make_attn(block_in, attn_type=attn_type) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + 2*z_channels if double_z else z_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + # timestep embedding + temb = None + + # downsampling + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level].block[i_block](hs[-1], temb) + if len(self.down[i_level].attn) > 0: + h = self.down[i_level].attn[i_block](h) + hs.append(h) + if i_level != self.num_resolutions-1: + hs.append(self.down[i_level].downsample(hs[-1])) + + # middle + h = hs[-1] + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # end + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + +class Decoder(nn.Module): + def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels, + resolution, z_channels, give_pre_end=False, tanh_out=False, use_linear_attn=False, + attn_type="vanilla", **ignorekwargs): + super().__init__() + if use_linear_attn: attn_type = "linear" + self.ch = ch + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + self.give_pre_end = give_pre_end + self.tanh_out = tanh_out + + # compute in_ch_mult, block_in and curr_res at lowest res + in_ch_mult = (1,)+tuple(ch_mult) + block_in = ch*ch_mult[self.num_resolutions-1] + curr_res = resolution // 2**(self.num_resolutions-1) + self.z_shape = (1,z_channels,curr_res,curr_res) + print("Working with z of shape {} = {} dimensions.".format( + self.z_shape, np.prod(self.z_shape))) + + # z to block_in + self.conv_in = torch.nn.Conv2d(z_channels, + block_in, + kernel_size=3, + stride=1, + padding=1) + + # middle + self.mid = nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + self.mid.attn_1 = make_attn(block_in, attn_type=attn_type) + self.mid.block_2 = ResnetBlock(in_channels=block_in, + out_channels=block_in, + temb_channels=self.temb_ch, + dropout=dropout) + + # upsampling + self.up = nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = nn.ModuleList() + attn = nn.ModuleList() + block_out = ch*ch_mult[i_level] + for i_block in range(self.num_res_blocks+1): + block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + if curr_res in attn_resolutions: + attn.append(make_attn(block_in, attn_type=attn_type)) + up = nn.Module() + up.block = block + up.attn = attn + if i_level != 0: + up.upsample = Upsample(block_in, resamp_with_conv) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_ch, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, z): + #assert z.shape[1:] == self.z_shape[1:] + self.last_z_shape = z.shape + + # timestep embedding + temb = None + + # z to block_in + h = self.conv_in(z) + + # middle + h = self.mid.block_1(h, temb) + h = self.mid.attn_1(h) + h = self.mid.block_2(h, temb) + + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks+1): + h = self.up[i_level].block[i_block](h, temb) + if len(self.up[i_level].attn) > 0: + h = self.up[i_level].attn[i_block](h) + if i_level != 0: + h = self.up[i_level].upsample(h) + + # end + if self.give_pre_end: + return h + + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + if self.tanh_out: + h = torch.tanh(h) + return h + + +class SimpleDecoder(nn.Module): + def __init__(self, in_channels, out_channels, *args, **kwargs): + super().__init__() + self.model = nn.ModuleList([nn.Conv2d(in_channels, in_channels, 1), + ResnetBlock(in_channels=in_channels, + out_channels=2 * in_channels, + temb_channels=0, dropout=0.0), + ResnetBlock(in_channels=2 * in_channels, + out_channels=4 * in_channels, + temb_channels=0, dropout=0.0), + ResnetBlock(in_channels=4 * in_channels, + out_channels=2 * in_channels, + temb_channels=0, dropout=0.0), + nn.Conv2d(2*in_channels, in_channels, 1), + Upsample(in_channels, with_conv=True)]) + # end + self.norm_out = Normalize(in_channels) + self.conv_out = torch.nn.Conv2d(in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + for i, layer in enumerate(self.model): + if i in [1,2,3]: + x = layer(x, None) + else: + x = layer(x) + + h = self.norm_out(x) + h = nonlinearity(h) + x = self.conv_out(h) + return x + + +class UpsampleDecoder(nn.Module): + def __init__(self, in_channels, out_channels, ch, num_res_blocks, resolution, + ch_mult=(2,2), dropout=0.0): + super().__init__() + # upsampling + self.temb_ch = 0 + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + block_in = in_channels + curr_res = resolution // 2 ** (self.num_resolutions - 1) + self.res_blocks = nn.ModuleList() + self.upsample_blocks = nn.ModuleList() + for i_level in range(self.num_resolutions): + res_block = [] + block_out = ch * ch_mult[i_level] + for i_block in range(self.num_res_blocks + 1): + res_block.append(ResnetBlock(in_channels=block_in, + out_channels=block_out, + temb_channels=self.temb_ch, + dropout=dropout)) + block_in = block_out + self.res_blocks.append(nn.ModuleList(res_block)) + if i_level != self.num_resolutions - 1: + self.upsample_blocks.append(Upsample(block_in, True)) + curr_res = curr_res * 2 + + # end + self.norm_out = Normalize(block_in) + self.conv_out = torch.nn.Conv2d(block_in, + out_channels, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + # upsampling + h = x + for k, i_level in enumerate(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks + 1): + h = self.res_blocks[i_level][i_block](h, None) + if i_level != self.num_resolutions - 1: + h = self.upsample_blocks[k](h) + h = self.norm_out(h) + h = nonlinearity(h) + h = self.conv_out(h) + return h + + +class LatentRescaler(nn.Module): + def __init__(self, factor, in_channels, mid_channels, out_channels, depth=2): + super().__init__() + # residual block, interpolate, residual block + self.factor = factor + self.conv_in = nn.Conv2d(in_channels, + mid_channels, + kernel_size=3, + stride=1, + padding=1) + self.res_block1 = nn.ModuleList([ResnetBlock(in_channels=mid_channels, + out_channels=mid_channels, + temb_channels=0, + dropout=0.0) for _ in range(depth)]) + self.attn = AttnBlock(mid_channels) + self.res_block2 = nn.ModuleList([ResnetBlock(in_channels=mid_channels, + out_channels=mid_channels, + temb_channels=0, + dropout=0.0) for _ in range(depth)]) + + self.conv_out = nn.Conv2d(mid_channels, + out_channels, + kernel_size=1, + ) + + def forward(self, x): + x = self.conv_in(x) + for block in self.res_block1: + x = block(x, None) + x = torch.nn.functional.interpolate(x, size=(int(round(x.shape[2]*self.factor)), int(round(x.shape[3]*self.factor)))) + x = self.attn(x) + for block in self.res_block2: + x = block(x, None) + x = self.conv_out(x) + return x + + +class MergedRescaleEncoder(nn.Module): + def __init__(self, in_channels, ch, resolution, out_ch, num_res_blocks, + attn_resolutions, dropout=0.0, resamp_with_conv=True, + ch_mult=(1,2,4,8), rescale_factor=1.0, rescale_module_depth=1): + super().__init__() + intermediate_chn = ch * ch_mult[-1] + self.encoder = Encoder(in_channels=in_channels, num_res_blocks=num_res_blocks, ch=ch, ch_mult=ch_mult, + z_channels=intermediate_chn, double_z=False, resolution=resolution, + attn_resolutions=attn_resolutions, dropout=dropout, resamp_with_conv=resamp_with_conv, + out_ch=None) + self.rescaler = LatentRescaler(factor=rescale_factor, in_channels=intermediate_chn, + mid_channels=intermediate_chn, out_channels=out_ch, depth=rescale_module_depth) + + def forward(self, x): + x = self.encoder(x) + x = self.rescaler(x) + return x + + +class MergedRescaleDecoder(nn.Module): + def __init__(self, z_channels, out_ch, resolution, num_res_blocks, attn_resolutions, ch, ch_mult=(1,2,4,8), + dropout=0.0, resamp_with_conv=True, rescale_factor=1.0, rescale_module_depth=1): + super().__init__() + tmp_chn = z_channels*ch_mult[-1] + self.decoder = Decoder(out_ch=out_ch, z_channels=tmp_chn, attn_resolutions=attn_resolutions, dropout=dropout, + resamp_with_conv=resamp_with_conv, in_channels=None, num_res_blocks=num_res_blocks, + ch_mult=ch_mult, resolution=resolution, ch=ch) + self.rescaler = LatentRescaler(factor=rescale_factor, in_channels=z_channels, mid_channels=tmp_chn, + out_channels=tmp_chn, depth=rescale_module_depth) + + def forward(self, x): + x = self.rescaler(x) + x = self.decoder(x) + return x + + +class Upsampler(nn.Module): + def __init__(self, in_size, out_size, in_channels, out_channels, ch_mult=2): + super().__init__() + assert out_size >= in_size + num_blocks = int(np.log2(out_size//in_size))+1 + factor_up = 1.+ (out_size % in_size) + print(f"Building {self.__class__.__name__} with in_size: {in_size} --> out_size {out_size} and factor {factor_up}") + self.rescaler = LatentRescaler(factor=factor_up, in_channels=in_channels, mid_channels=2*in_channels, + out_channels=in_channels) + self.decoder = Decoder(out_ch=out_channels, resolution=out_size, z_channels=in_channels, num_res_blocks=2, + attn_resolutions=[], in_channels=None, ch=in_channels, + ch_mult=[ch_mult for _ in range(num_blocks)]) + + def forward(self, x): + x = self.rescaler(x) + x = self.decoder(x) + return x + + +class Resize(nn.Module): + def __init__(self, in_channels=None, learned=False, mode="bilinear"): + super().__init__() + self.with_conv = learned + self.mode = mode + if self.with_conv: + print(f"Note: {self.__class__.__name} uses learned downsampling and will ignore the fixed {mode} mode") + raise NotImplementedError() + assert in_channels is not None + # no asymmetric padding in torch conv, must do it ourselves + self.conv = torch.nn.Conv2d(in_channels, + in_channels, + kernel_size=4, + stride=2, + padding=1) + + def forward(self, x, scale_factor=1.0): + if scale_factor==1.0: + return x + else: + x = torch.nn.functional.interpolate(x, mode=self.mode, align_corners=False, scale_factor=scale_factor) + return x diff --git a/comparison_models/ControlNet/ldm/modules/diffusionmodules/openaimodel.py b/comparison_models/ControlNet/ldm/modules/diffusionmodules/openaimodel.py new file mode 100644 index 0000000..7df6b5a --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/diffusionmodules/openaimodel.py @@ -0,0 +1,786 @@ +from abc import abstractmethod +import math + +import numpy as np +import torch as th +import torch.nn as nn +import torch.nn.functional as F + +from ldm.modules.diffusionmodules.util import ( + checkpoint, + conv_nd, + linear, + avg_pool_nd, + zero_module, + normalization, + timestep_embedding, +) +from ldm.modules.attention import SpatialTransformer +from ldm.util import exists + + +# dummy replace +def convert_module_to_f16(x): + pass + +def convert_module_to_f32(x): + pass + + +## go +class AttentionPool2d(nn.Module): + """ + Adapted from CLIP: https://github.com/openai/CLIP/blob/main/clip/model.py + """ + + def __init__( + self, + spacial_dim: int, + embed_dim: int, + num_heads_channels: int, + output_dim: int = None, + ): + super().__init__() + self.positional_embedding = nn.Parameter(th.randn(embed_dim, spacial_dim ** 2 + 1) / embed_dim ** 0.5) + self.qkv_proj = conv_nd(1, embed_dim, 3 * embed_dim, 1) + self.c_proj = conv_nd(1, embed_dim, output_dim or embed_dim, 1) + self.num_heads = embed_dim // num_heads_channels + self.attention = QKVAttention(self.num_heads) + + def forward(self, x): + b, c, *_spatial = x.shape + x = x.reshape(b, c, -1) # NC(HW) + x = th.cat([x.mean(dim=-1, keepdim=True), x], dim=-1) # NC(HW+1) + x = x + self.positional_embedding[None, :, :].to(x.dtype) # NC(HW+1) + x = self.qkv_proj(x) + x = self.attention(x) + x = self.c_proj(x) + return x[:, :, 0] + + +class TimestepBlock(nn.Module): + """ + Any module where forward() takes timestep embeddings as a second argument. + """ + + @abstractmethod + def forward(self, x, emb): + """ + Apply the module to `x` given `emb` timestep embeddings. + """ + + +class TimestepEmbedSequential(nn.Sequential, TimestepBlock): + """ + A sequential module that passes timestep embeddings to the children that + support it as an extra input. + """ + + def forward(self, x, emb, context=None): + for layer in self: + if isinstance(layer, TimestepBlock): + x = layer(x, emb) + elif isinstance(layer, SpatialTransformer): + x = layer(x, context) + else: + x = layer(x) + return x + + +class Upsample(nn.Module): + """ + An upsampling layer with an optional convolution. + :param channels: channels in the inputs and outputs. + :param use_conv: a bool determining if a convolution is applied. + :param dims: determines if the signal is 1D, 2D, or 3D. If 3D, then + upsampling occurs in the inner-two dimensions. + """ + + def __init__(self, channels, use_conv, dims=2, out_channels=None, padding=1): + super().__init__() + self.channels = channels + self.out_channels = out_channels or channels + self.use_conv = use_conv + self.dims = dims + if use_conv: + self.conv = conv_nd(dims, self.channels, self.out_channels, 3, padding=padding) + + def forward(self, x): + assert x.shape[1] == self.channels + if self.dims == 3: + x = F.interpolate( + x, (x.shape[2], x.shape[3] * 2, x.shape[4] * 2), mode="nearest" + ) + else: + x = F.interpolate(x, scale_factor=2, mode="nearest") + if self.use_conv: + x = self.conv(x) + return x + +class TransposedUpsample(nn.Module): + 'Learned 2x upsampling without padding' + def __init__(self, channels, out_channels=None, ks=5): + super().__init__() + self.channels = channels + self.out_channels = out_channels or channels + + self.up = nn.ConvTranspose2d(self.channels,self.out_channels,kernel_size=ks,stride=2) + + def forward(self,x): + return self.up(x) + + +class Downsample(nn.Module): + """ + A downsampling layer with an optional convolution. + :param channels: channels in the inputs and outputs. + :param use_conv: a bool determining if a convolution is applied. + :param dims: determines if the signal is 1D, 2D, or 3D. If 3D, then + downsampling occurs in the inner-two dimensions. + """ + + def __init__(self, channels, use_conv, dims=2, out_channels=None,padding=1): + super().__init__() + self.channels = channels + self.out_channels = out_channels or channels + self.use_conv = use_conv + self.dims = dims + stride = 2 if dims != 3 else (1, 2, 2) + if use_conv: + self.op = conv_nd( + dims, self.channels, self.out_channels, 3, stride=stride, padding=padding + ) + else: + assert self.channels == self.out_channels + self.op = avg_pool_nd(dims, kernel_size=stride, stride=stride) + + def forward(self, x): + assert x.shape[1] == self.channels + return self.op(x) + + +class ResBlock(TimestepBlock): + """ + A residual block that can optionally change the number of channels. + :param channels: the number of input channels. + :param emb_channels: the number of timestep embedding channels. + :param dropout: the rate of dropout. + :param out_channels: if specified, the number of out channels. + :param use_conv: if True and out_channels is specified, use a spatial + convolution instead of a smaller 1x1 convolution to change the + channels in the skip connection. + :param dims: determines if the signal is 1D, 2D, or 3D. + :param use_checkpoint: if True, use gradient checkpointing on this module. + :param up: if True, use this block for upsampling. + :param down: if True, use this block for downsampling. + """ + + def __init__( + self, + channels, + emb_channels, + dropout, + out_channels=None, + use_conv=False, + use_scale_shift_norm=False, + dims=2, + use_checkpoint=False, + up=False, + down=False, + ): + super().__init__() + self.channels = channels + self.emb_channels = emb_channels + self.dropout = dropout + self.out_channels = out_channels or channels + self.use_conv = use_conv + self.use_checkpoint = use_checkpoint + self.use_scale_shift_norm = use_scale_shift_norm + + self.in_layers = nn.Sequential( + normalization(channels), + nn.SiLU(), + conv_nd(dims, channels, self.out_channels, 3, padding=1), + ) + + self.updown = up or down + + if up: + self.h_upd = Upsample(channels, False, dims) + self.x_upd = Upsample(channels, False, dims) + elif down: + self.h_upd = Downsample(channels, False, dims) + self.x_upd = Downsample(channels, False, dims) + else: + self.h_upd = self.x_upd = nn.Identity() + + self.emb_layers = nn.Sequential( + nn.SiLU(), + linear( + emb_channels, + 2 * self.out_channels if use_scale_shift_norm else self.out_channels, + ), + ) + self.out_layers = nn.Sequential( + normalization(self.out_channels), + nn.SiLU(), + nn.Dropout(p=dropout), + zero_module( + conv_nd(dims, self.out_channels, self.out_channels, 3, padding=1) + ), + ) + + if self.out_channels == channels: + self.skip_connection = nn.Identity() + elif use_conv: + self.skip_connection = conv_nd( + dims, channels, self.out_channels, 3, padding=1 + ) + else: + self.skip_connection = conv_nd(dims, channels, self.out_channels, 1) + + def forward(self, x, emb): + """ + Apply the block to a Tensor, conditioned on a timestep embedding. + :param x: an [N x C x ...] Tensor of features. + :param emb: an [N x emb_channels] Tensor of timestep embeddings. + :return: an [N x C x ...] Tensor of outputs. + """ + return checkpoint( + self._forward, (x, emb), self.parameters(), self.use_checkpoint + ) + + + def _forward(self, x, emb): + if self.updown: + in_rest, in_conv = self.in_layers[:-1], self.in_layers[-1] + h = in_rest(x) + h = self.h_upd(h) + x = self.x_upd(x) + h = in_conv(h) + else: + h = self.in_layers(x) + emb_out = self.emb_layers(emb).type(h.dtype) + while len(emb_out.shape) < len(h.shape): + emb_out = emb_out[..., None] + if self.use_scale_shift_norm: + out_norm, out_rest = self.out_layers[0], self.out_layers[1:] + scale, shift = th.chunk(emb_out, 2, dim=1) + h = out_norm(h) * (1 + scale) + shift + h = out_rest(h) + else: + h = h + emb_out + h = self.out_layers(h) + return self.skip_connection(x) + h + + +class AttentionBlock(nn.Module): + """ + An attention block that allows spatial positions to attend to each other. + Originally ported from here, but adapted to the N-d case. + https://github.com/hojonathanho/diffusion/blob/1e0dceb3b3495bbe19116a5e1b3596cd0706c543/diffusion_tf/models/unet.py#L66. + """ + + def __init__( + self, + channels, + num_heads=1, + num_head_channels=-1, + use_checkpoint=False, + use_new_attention_order=False, + ): + super().__init__() + self.channels = channels + if num_head_channels == -1: + self.num_heads = num_heads + else: + assert ( + channels % num_head_channels == 0 + ), f"q,k,v channels {channels} is not divisible by num_head_channels {num_head_channels}" + self.num_heads = channels // num_head_channels + self.use_checkpoint = use_checkpoint + self.norm = normalization(channels) + self.qkv = conv_nd(1, channels, channels * 3, 1) + if use_new_attention_order: + # split qkv before split heads + self.attention = QKVAttention(self.num_heads) + else: + # split heads before split qkv + self.attention = QKVAttentionLegacy(self.num_heads) + + self.proj_out = zero_module(conv_nd(1, channels, channels, 1)) + + def forward(self, x): + return checkpoint(self._forward, (x,), self.parameters(), True) # TODO: check checkpoint usage, is True # TODO: fix the .half call!!! + #return pt_checkpoint(self._forward, x) # pytorch + + def _forward(self, x): + b, c, *spatial = x.shape + x = x.reshape(b, c, -1) + qkv = self.qkv(self.norm(x)) + h = self.attention(qkv) + h = self.proj_out(h) + return (x + h).reshape(b, c, *spatial) + + +def count_flops_attn(model, _x, y): + """ + A counter for the `thop` package to count the operations in an + attention operation. + Meant to be used like: + macs, params = thop.profile( + model, + inputs=(inputs, timestamps), + custom_ops={QKVAttention: QKVAttention.count_flops}, + ) + """ + b, c, *spatial = y[0].shape + num_spatial = int(np.prod(spatial)) + # We perform two matmuls with the same number of ops. + # The first computes the weight matrix, the second computes + # the combination of the value vectors. + matmul_ops = 2 * b * (num_spatial ** 2) * c + model.total_ops += th.DoubleTensor([matmul_ops]) + + +class QKVAttentionLegacy(nn.Module): + """ + A module which performs QKV attention. Matches legacy QKVAttention + input/ouput heads shaping + """ + + def __init__(self, n_heads): + super().__init__() + self.n_heads = n_heads + + def forward(self, qkv): + """ + Apply QKV attention. + :param qkv: an [N x (H * 3 * C) x T] tensor of Qs, Ks, and Vs. + :return: an [N x (H * C) x T] tensor after attention. + """ + bs, width, length = qkv.shape + assert width % (3 * self.n_heads) == 0 + ch = width // (3 * self.n_heads) + q, k, v = qkv.reshape(bs * self.n_heads, ch * 3, length).split(ch, dim=1) + scale = 1 / math.sqrt(math.sqrt(ch)) + weight = th.einsum( + "bct,bcs->bts", q * scale, k * scale + ) # More stable with f16 than dividing afterwards + weight = th.softmax(weight.float(), dim=-1).type(weight.dtype) + a = th.einsum("bts,bcs->bct", weight, v) + return a.reshape(bs, -1, length) + + @staticmethod + def count_flops(model, _x, y): + return count_flops_attn(model, _x, y) + + +class QKVAttention(nn.Module): + """ + A module which performs QKV attention and splits in a different order. + """ + + def __init__(self, n_heads): + super().__init__() + self.n_heads = n_heads + + def forward(self, qkv): + """ + Apply QKV attention. + :param qkv: an [N x (3 * H * C) x T] tensor of Qs, Ks, and Vs. + :return: an [N x (H * C) x T] tensor after attention. + """ + bs, width, length = qkv.shape + assert width % (3 * self.n_heads) == 0 + ch = width // (3 * self.n_heads) + q, k, v = qkv.chunk(3, dim=1) + scale = 1 / math.sqrt(math.sqrt(ch)) + weight = th.einsum( + "bct,bcs->bts", + (q * scale).view(bs * self.n_heads, ch, length), + (k * scale).view(bs * self.n_heads, ch, length), + ) # More stable with f16 than dividing afterwards + weight = th.softmax(weight.float(), dim=-1).type(weight.dtype) + a = th.einsum("bts,bcs->bct", weight, v.reshape(bs * self.n_heads, ch, length)) + return a.reshape(bs, -1, length) + + @staticmethod + def count_flops(model, _x, y): + return count_flops_attn(model, _x, y) + + +class UNetModel(nn.Module): + """ + The full UNet model with attention and timestep embedding. + :param in_channels: channels in the input Tensor. + :param model_channels: base channel count for the model. + :param out_channels: channels in the output Tensor. + :param num_res_blocks: number of residual blocks per downsample. + :param attention_resolutions: a collection of downsample rates at which + attention will take place. May be a set, list, or tuple. + For example, if this contains 4, then at 4x downsampling, attention + will be used. + :param dropout: the dropout probability. + :param channel_mult: channel multiplier for each level of the UNet. + :param conv_resample: if True, use learned convolutions for upsampling and + downsampling. + :param dims: determines if the signal is 1D, 2D, or 3D. + :param num_classes: if specified (as an int), then this model will be + class-conditional with `num_classes` classes. + :param use_checkpoint: use gradient checkpointing to reduce memory usage. + :param num_heads: the number of attention heads in each attention layer. + :param num_heads_channels: if specified, ignore num_heads and instead use + a fixed channel width per attention head. + :param num_heads_upsample: works with num_heads to set a different number + of heads for upsampling. Deprecated. + :param use_scale_shift_norm: use a FiLM-like conditioning mechanism. + :param resblock_updown: use residual blocks for up/downsampling. + :param use_new_attention_order: use a different attention pattern for potentially + increased efficiency. + """ + + def __init__( + self, + image_size, + in_channels, + model_channels, + out_channels, + num_res_blocks, + attention_resolutions, + dropout=0, + channel_mult=(1, 2, 4, 8), + conv_resample=True, + dims=2, + num_classes=None, + use_checkpoint=False, + use_fp16=False, + num_heads=-1, + num_head_channels=-1, + num_heads_upsample=-1, + use_scale_shift_norm=False, + resblock_updown=False, + use_new_attention_order=False, + use_spatial_transformer=False, # custom transformer support + transformer_depth=1, # custom transformer support + context_dim=None, # custom transformer support + n_embed=None, # custom support for prediction of discrete ids into codebook of first stage vq model + legacy=True, + disable_self_attentions=None, + num_attention_blocks=None, + disable_middle_self_attn=False, + use_linear_in_transformer=False, + ): + super().__init__() + if use_spatial_transformer: + assert context_dim is not None, 'Fool!! You forgot to include the dimension of your cross-attention conditioning...' + + if context_dim is not None: + assert use_spatial_transformer, 'Fool!! You forgot to use the spatial transformer for your cross-attention conditioning...' + from omegaconf.listconfig import ListConfig + if type(context_dim) == ListConfig: + context_dim = list(context_dim) + + if num_heads_upsample == -1: + num_heads_upsample = num_heads + + if num_heads == -1: + assert num_head_channels != -1, 'Either num_heads or num_head_channels has to be set' + + if num_head_channels == -1: + assert num_heads != -1, 'Either num_heads or num_head_channels has to be set' + + self.image_size = image_size + self.in_channels = in_channels + self.model_channels = model_channels + self.out_channels = out_channels + if isinstance(num_res_blocks, int): + self.num_res_blocks = len(channel_mult) * [num_res_blocks] + else: + if len(num_res_blocks) != len(channel_mult): + raise ValueError("provide num_res_blocks either as an int (globally constant) or " + "as a list/tuple (per-level) with the same length as channel_mult") + self.num_res_blocks = num_res_blocks + if disable_self_attentions is not None: + # should be a list of booleans, indicating whether to disable self-attention in TransformerBlocks or not + assert len(disable_self_attentions) == len(channel_mult) + if num_attention_blocks is not None: + assert len(num_attention_blocks) == len(self.num_res_blocks) + assert all(map(lambda i: self.num_res_blocks[i] >= num_attention_blocks[i], range(len(num_attention_blocks)))) + print(f"Constructor of UNetModel received num_attention_blocks={num_attention_blocks}. " + f"This option has LESS priority than attention_resolutions {attention_resolutions}, " + f"i.e., in cases where num_attention_blocks[i] > 0 but 2**i not in attention_resolutions, " + f"attention will still not be set.") + + self.attention_resolutions = attention_resolutions + self.dropout = dropout + self.channel_mult = channel_mult + self.conv_resample = conv_resample + self.num_classes = num_classes + self.use_checkpoint = use_checkpoint + self.dtype = th.float16 if use_fp16 else th.float32 + self.num_heads = num_heads + self.num_head_channels = num_head_channels + self.num_heads_upsample = num_heads_upsample + self.predict_codebook_ids = n_embed is not None + + time_embed_dim = model_channels * 4 + self.time_embed = nn.Sequential( + linear(model_channels, time_embed_dim), + nn.SiLU(), + linear(time_embed_dim, time_embed_dim), + ) + + if self.num_classes is not None: + if isinstance(self.num_classes, int): + self.label_emb = nn.Embedding(num_classes, time_embed_dim) + elif self.num_classes == "continuous": + print("setting up linear c_adm embedding layer") + self.label_emb = nn.Linear(1, time_embed_dim) + else: + raise ValueError() + + self.input_blocks = nn.ModuleList( + [ + TimestepEmbedSequential( + conv_nd(dims, in_channels, model_channels, 3, padding=1) + ) + ] + ) + self._feature_size = model_channels + input_block_chans = [model_channels] + ch = model_channels + ds = 1 + for level, mult in enumerate(channel_mult): + for nr in range(self.num_res_blocks[level]): + layers = [ + ResBlock( + ch, + time_embed_dim, + dropout, + out_channels=mult * model_channels, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ) + ] + ch = mult * model_channels + if ds in attention_resolutions: + if num_head_channels == -1: + dim_head = ch // num_heads + else: + num_heads = ch // num_head_channels + dim_head = num_head_channels + if legacy: + #num_heads = 1 + dim_head = ch // num_heads if use_spatial_transformer else num_head_channels + if exists(disable_self_attentions): + disabled_sa = disable_self_attentions[level] + else: + disabled_sa = False + + if not exists(num_attention_blocks) or nr < num_attention_blocks[level]: + layers.append( + AttentionBlock( + ch, + use_checkpoint=use_checkpoint, + num_heads=num_heads, + num_head_channels=dim_head, + use_new_attention_order=use_new_attention_order, + ) if not use_spatial_transformer else SpatialTransformer( + ch, num_heads, dim_head, depth=transformer_depth, context_dim=context_dim, + disable_self_attn=disabled_sa, use_linear=use_linear_in_transformer, + use_checkpoint=use_checkpoint + ) + ) + self.input_blocks.append(TimestepEmbedSequential(*layers)) + self._feature_size += ch + input_block_chans.append(ch) + if level != len(channel_mult) - 1: + out_ch = ch + self.input_blocks.append( + TimestepEmbedSequential( + ResBlock( + ch, + time_embed_dim, + dropout, + out_channels=out_ch, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + down=True, + ) + if resblock_updown + else Downsample( + ch, conv_resample, dims=dims, out_channels=out_ch + ) + ) + ) + ch = out_ch + input_block_chans.append(ch) + ds *= 2 + self._feature_size += ch + + if num_head_channels == -1: + dim_head = ch // num_heads + else: + num_heads = ch // num_head_channels + dim_head = num_head_channels + if legacy: + #num_heads = 1 + dim_head = ch // num_heads if use_spatial_transformer else num_head_channels + self.middle_block = TimestepEmbedSequential( + ResBlock( + ch, + time_embed_dim, + dropout, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ), + AttentionBlock( + ch, + use_checkpoint=use_checkpoint, + num_heads=num_heads, + num_head_channels=dim_head, + use_new_attention_order=use_new_attention_order, + ) if not use_spatial_transformer else SpatialTransformer( # always uses a self-attn + ch, num_heads, dim_head, depth=transformer_depth, context_dim=context_dim, + disable_self_attn=disable_middle_self_attn, use_linear=use_linear_in_transformer, + use_checkpoint=use_checkpoint + ), + ResBlock( + ch, + time_embed_dim, + dropout, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ), + ) + self._feature_size += ch + + self.output_blocks = nn.ModuleList([]) + for level, mult in list(enumerate(channel_mult))[::-1]: + for i in range(self.num_res_blocks[level] + 1): + ich = input_block_chans.pop() + layers = [ + ResBlock( + ch + ich, + time_embed_dim, + dropout, + out_channels=model_channels * mult, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + ) + ] + ch = model_channels * mult + if ds in attention_resolutions: + if num_head_channels == -1: + dim_head = ch // num_heads + else: + num_heads = ch // num_head_channels + dim_head = num_head_channels + if legacy: + #num_heads = 1 + dim_head = ch // num_heads if use_spatial_transformer else num_head_channels + if exists(disable_self_attentions): + disabled_sa = disable_self_attentions[level] + else: + disabled_sa = False + + if not exists(num_attention_blocks) or i < num_attention_blocks[level]: + layers.append( + AttentionBlock( + ch, + use_checkpoint=use_checkpoint, + num_heads=num_heads_upsample, + num_head_channels=dim_head, + use_new_attention_order=use_new_attention_order, + ) if not use_spatial_transformer else SpatialTransformer( + ch, num_heads, dim_head, depth=transformer_depth, context_dim=context_dim, + disable_self_attn=disabled_sa, use_linear=use_linear_in_transformer, + use_checkpoint=use_checkpoint + ) + ) + if level and i == self.num_res_blocks[level]: + out_ch = ch + layers.append( + ResBlock( + ch, + time_embed_dim, + dropout, + out_channels=out_ch, + dims=dims, + use_checkpoint=use_checkpoint, + use_scale_shift_norm=use_scale_shift_norm, + up=True, + ) + if resblock_updown + else Upsample(ch, conv_resample, dims=dims, out_channels=out_ch) + ) + ds //= 2 + self.output_blocks.append(TimestepEmbedSequential(*layers)) + self._feature_size += ch + + self.out = nn.Sequential( + normalization(ch), + nn.SiLU(), + zero_module(conv_nd(dims, model_channels, out_channels, 3, padding=1)), + ) + if self.predict_codebook_ids: + self.id_predictor = nn.Sequential( + normalization(ch), + conv_nd(dims, model_channels, n_embed, 1), + #nn.LogSoftmax(dim=1) # change to cross_entropy and produce non-normalized logits + ) + + def convert_to_fp16(self): + """ + Convert the torso of the model to float16. + """ + self.input_blocks.apply(convert_module_to_f16) + self.middle_block.apply(convert_module_to_f16) + self.output_blocks.apply(convert_module_to_f16) + + def convert_to_fp32(self): + """ + Convert the torso of the model to float32. + """ + self.input_blocks.apply(convert_module_to_f32) + self.middle_block.apply(convert_module_to_f32) + self.output_blocks.apply(convert_module_to_f32) + + def forward(self, x, timesteps=None, context=None, y=None,**kwargs): + """ + Apply the model to an input batch. + :param x: an [N x C x ...] Tensor of inputs. + :param timesteps: a 1-D batch of timesteps. + :param context: conditioning plugged in via crossattn + :param y: an [N] Tensor of labels, if class-conditional. + :return: an [N x C x ...] Tensor of outputs. + """ + assert (y is not None) == ( + self.num_classes is not None + ), "must specify y if and only if the model is class-conditional" + hs = [] + t_emb = timestep_embedding(timesteps, self.model_channels, repeat_only=False) + emb = self.time_embed(t_emb) + + if self.num_classes is not None: + assert y.shape[0] == x.shape[0] + emb = emb + self.label_emb(y) + + h = x.type(self.dtype) + for module in self.input_blocks: + h = module(h, emb, context) + hs.append(h) + h = self.middle_block(h, emb, context) + for module in self.output_blocks: + h = th.cat([h, hs.pop()], dim=1) + h = module(h, emb, context) + h = h.type(x.dtype) + if self.predict_codebook_ids: + return self.id_predictor(h) + else: + return self.out(h) diff --git a/comparison_models/ControlNet/ldm/modules/diffusionmodules/upscaling.py b/comparison_models/ControlNet/ldm/modules/diffusionmodules/upscaling.py new file mode 100644 index 0000000..0381666 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/diffusionmodules/upscaling.py @@ -0,0 +1,81 @@ +import torch +import torch.nn as nn +import numpy as np +from functools import partial + +from ldm.modules.diffusionmodules.util import extract_into_tensor, make_beta_schedule +from ldm.util import default + + +class AbstractLowScaleModel(nn.Module): + # for concatenating a downsampled image to the latent representation + def __init__(self, noise_schedule_config=None): + super(AbstractLowScaleModel, self).__init__() + if noise_schedule_config is not None: + self.register_schedule(**noise_schedule_config) + + def register_schedule(self, beta_schedule="linear", timesteps=1000, + linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3): + betas = make_beta_schedule(beta_schedule, timesteps, linear_start=linear_start, linear_end=linear_end, + cosine_s=cosine_s) + alphas = 1. - betas + alphas_cumprod = np.cumprod(alphas, axis=0) + alphas_cumprod_prev = np.append(1., alphas_cumprod[:-1]) + + timesteps, = betas.shape + self.num_timesteps = int(timesteps) + self.linear_start = linear_start + self.linear_end = linear_end + assert alphas_cumprod.shape[0] == self.num_timesteps, 'alphas have to be defined for each timestep' + + to_torch = partial(torch.tensor, dtype=torch.float32) + + self.register_buffer('betas', to_torch(betas)) + self.register_buffer('alphas_cumprod', to_torch(alphas_cumprod)) + self.register_buffer('alphas_cumprod_prev', to_torch(alphas_cumprod_prev)) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', to_torch(np.sqrt(alphas_cumprod))) + self.register_buffer('sqrt_one_minus_alphas_cumprod', to_torch(np.sqrt(1. - alphas_cumprod))) + self.register_buffer('log_one_minus_alphas_cumprod', to_torch(np.log(1. - alphas_cumprod))) + self.register_buffer('sqrt_recip_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod))) + self.register_buffer('sqrt_recipm1_alphas_cumprod', to_torch(np.sqrt(1. / alphas_cumprod - 1))) + + def q_sample(self, x_start, t, noise=None): + noise = default(noise, lambda: torch.randn_like(x_start)) + return (extract_into_tensor(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start + + extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape) * noise) + + def forward(self, x): + return x, None + + def decode(self, x): + return x + + +class SimpleImageConcat(AbstractLowScaleModel): + # no noise level conditioning + def __init__(self): + super(SimpleImageConcat, self).__init__(noise_schedule_config=None) + self.max_noise_level = 0 + + def forward(self, x): + # fix to constant noise level + return x, torch.zeros(x.shape[0], device=x.device).long() + + +class ImageConcatWithNoiseAugmentation(AbstractLowScaleModel): + def __init__(self, noise_schedule_config, max_noise_level=1000, to_cuda=False): + super().__init__(noise_schedule_config=noise_schedule_config) + self.max_noise_level = max_noise_level + + def forward(self, x, noise_level=None): + if noise_level is None: + noise_level = torch.randint(0, self.max_noise_level, (x.shape[0],), device=x.device).long() + else: + assert isinstance(noise_level, torch.Tensor) + z = self.q_sample(x, noise_level) + return z, noise_level + + + diff --git a/comparison_models/ControlNet/ldm/modules/diffusionmodules/util.py b/comparison_models/ControlNet/ldm/modules/diffusionmodules/util.py new file mode 100644 index 0000000..637363d --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/diffusionmodules/util.py @@ -0,0 +1,270 @@ +# adopted from +# https://github.com/openai/improved-diffusion/blob/main/improved_diffusion/gaussian_diffusion.py +# and +# https://github.com/lucidrains/denoising-diffusion-pytorch/blob/7706bdfc6f527f58d33f84b7b522e61e6e3164b3/denoising_diffusion_pytorch/denoising_diffusion_pytorch.py +# and +# https://github.com/openai/guided-diffusion/blob/0ba878e517b276c45d1195eb29f6f5f72659a05b/guided_diffusion/nn.py +# +# thanks! + + +import os +import math +import torch +import torch.nn as nn +import numpy as np +from einops import repeat + +from ldm.util import instantiate_from_config + + +def make_beta_schedule(schedule, n_timestep, linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3): + if schedule == "linear": + betas = ( + torch.linspace(linear_start ** 0.5, linear_end ** 0.5, n_timestep, dtype=torch.float64) ** 2 + ) + + elif schedule == "cosine": + timesteps = ( + torch.arange(n_timestep + 1, dtype=torch.float64) / n_timestep + cosine_s + ) + alphas = timesteps / (1 + cosine_s) * np.pi / 2 + alphas = torch.cos(alphas).pow(2) + alphas = alphas / alphas[0] + betas = 1 - alphas[1:] / alphas[:-1] + betas = np.clip(betas, a_min=0, a_max=0.999) + + elif schedule == "sqrt_linear": + betas = torch.linspace(linear_start, linear_end, n_timestep, dtype=torch.float64) + elif schedule == "sqrt": + betas = torch.linspace(linear_start, linear_end, n_timestep, dtype=torch.float64) ** 0.5 + else: + raise ValueError(f"schedule '{schedule}' unknown.") + return betas.numpy() + + +def make_ddim_timesteps(ddim_discr_method, num_ddim_timesteps, num_ddpm_timesteps, verbose=True): + if ddim_discr_method == 'uniform': + c = num_ddpm_timesteps // num_ddim_timesteps + ddim_timesteps = np.asarray(list(range(0, num_ddpm_timesteps, c))) + elif ddim_discr_method == 'quad': + ddim_timesteps = ((np.linspace(0, np.sqrt(num_ddpm_timesteps * .8), num_ddim_timesteps)) ** 2).astype(int) + else: + raise NotImplementedError(f'There is no ddim discretization method called "{ddim_discr_method}"') + + # assert ddim_timesteps.shape[0] == num_ddim_timesteps + # add one to get the final alpha values right (the ones from first scale to data during sampling) + steps_out = ddim_timesteps + 1 + if verbose: + print(f'Selected timesteps for ddim sampler: {steps_out}') + return steps_out + + +def make_ddim_sampling_parameters(alphacums, ddim_timesteps, eta, verbose=True): + # select alphas for computing the variance schedule + alphas = alphacums[ddim_timesteps] + alphas_prev = np.asarray([alphacums[0]] + alphacums[ddim_timesteps[:-1]].tolist()) + + # according the the formula provided in https://arxiv.org/abs/2010.02502 + sigmas = eta * np.sqrt((1 - alphas_prev) / (1 - alphas) * (1 - alphas / alphas_prev)) + if verbose: + print(f'Selected alphas for ddim sampler: a_t: {alphas}; a_(t-1): {alphas_prev}') + print(f'For the chosen value of eta, which is {eta}, ' + f'this results in the following sigma_t schedule for ddim sampler {sigmas}') + return sigmas, alphas, alphas_prev + + +def betas_for_alpha_bar(num_diffusion_timesteps, alpha_bar, max_beta=0.999): + """ + Create a beta schedule that discretizes the given alpha_t_bar function, + which defines the cumulative product of (1-beta) over time from t = [0,1]. + :param num_diffusion_timesteps: the number of betas to produce. + :param alpha_bar: a lambda that takes an argument t from 0 to 1 and + produces the cumulative product of (1-beta) up to that + part of the diffusion process. + :param max_beta: the maximum beta to use; use values lower than 1 to + prevent singularities. + """ + betas = [] + for i in range(num_diffusion_timesteps): + t1 = i / num_diffusion_timesteps + t2 = (i + 1) / num_diffusion_timesteps + betas.append(min(1 - alpha_bar(t2) / alpha_bar(t1), max_beta)) + return np.array(betas) + + +def extract_into_tensor(a, t, x_shape): + b, *_ = t.shape + out = a.gather(-1, t) + return out.reshape(b, *((1,) * (len(x_shape) - 1))) + + +def checkpoint(func, inputs, params, flag): + """ + Evaluate a function without caching intermediate activations, allowing for + reduced memory at the expense of extra compute in the backward pass. + :param func: the function to evaluate. + :param inputs: the argument sequence to pass to `func`. + :param params: a sequence of parameters `func` depends on but does not + explicitly take as arguments. + :param flag: if False, disable gradient checkpointing. + """ + if flag: + args = tuple(inputs) + tuple(params) + return CheckpointFunction.apply(func, len(inputs), *args) + else: + return func(*inputs) + + +class CheckpointFunction(torch.autograd.Function): + @staticmethod + def forward(ctx, run_function, length, *args): + ctx.run_function = run_function + ctx.input_tensors = list(args[:length]) + ctx.input_params = list(args[length:]) + ctx.gpu_autocast_kwargs = {"enabled": torch.is_autocast_enabled(), + "dtype": torch.get_autocast_gpu_dtype(), + "cache_enabled": torch.is_autocast_cache_enabled()} + with torch.no_grad(): + output_tensors = ctx.run_function(*ctx.input_tensors) + return output_tensors + + @staticmethod + def backward(ctx, *output_grads): + ctx.input_tensors = [x.detach().requires_grad_(True) for x in ctx.input_tensors] + with torch.enable_grad(), \ + torch.cuda.amp.autocast(**ctx.gpu_autocast_kwargs): + # Fixes a bug where the first op in run_function modifies the + # Tensor storage in place, which is not allowed for detach()'d + # Tensors. + shallow_copies = [x.view_as(x) for x in ctx.input_tensors] + output_tensors = ctx.run_function(*shallow_copies) + input_grads = torch.autograd.grad( + output_tensors, + ctx.input_tensors + ctx.input_params, + output_grads, + allow_unused=True, + ) + del ctx.input_tensors + del ctx.input_params + del output_tensors + return (None, None) + input_grads + + +def timestep_embedding(timesteps, dim, max_period=10000, repeat_only=False): + """ + Create sinusoidal timestep embeddings. + :param timesteps: a 1-D Tensor of N indices, one per batch element. + These may be fractional. + :param dim: the dimension of the output. + :param max_period: controls the minimum frequency of the embeddings. + :return: an [N x dim] Tensor of positional embeddings. + """ + if not repeat_only: + half = dim // 2 + freqs = torch.exp( + -math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half + ).to(device=timesteps.device) + args = timesteps[:, None].float() * freqs[None] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + if dim % 2: + embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1) + else: + embedding = repeat(timesteps, 'b -> b d', d=dim) + return embedding + + +def zero_module(module): + """ + Zero out the parameters of a module and return it. + """ + for p in module.parameters(): + p.detach().zero_() + return module + + +def scale_module(module, scale): + """ + Scale the parameters of a module and return it. + """ + for p in module.parameters(): + p.detach().mul_(scale) + return module + + +def mean_flat(tensor): + """ + Take the mean over all non-batch dimensions. + """ + return tensor.mean(dim=list(range(1, len(tensor.shape)))) + + +def normalization(channels): + """ + Make a standard normalization layer. + :param channels: number of input channels. + :return: an nn.Module for normalization. + """ + return GroupNorm32(32, channels) + + +# PyTorch 1.7 has SiLU, but we support PyTorch 1.5. +class SiLU(nn.Module): + def forward(self, x): + return x * torch.sigmoid(x) + + +class GroupNorm32(nn.GroupNorm): + def forward(self, x): + return super().forward(x.float()).type(x.dtype) + +def conv_nd(dims, *args, **kwargs): + """ + Create a 1D, 2D, or 3D convolution module. + """ + if dims == 1: + return nn.Conv1d(*args, **kwargs) + elif dims == 2: + return nn.Conv2d(*args, **kwargs) + elif dims == 3: + return nn.Conv3d(*args, **kwargs) + raise ValueError(f"unsupported dimensions: {dims}") + + +def linear(*args, **kwargs): + """ + Create a linear module. + """ + return nn.Linear(*args, **kwargs) + + +def avg_pool_nd(dims, *args, **kwargs): + """ + Create a 1D, 2D, or 3D average pooling module. + """ + if dims == 1: + return nn.AvgPool1d(*args, **kwargs) + elif dims == 2: + return nn.AvgPool2d(*args, **kwargs) + elif dims == 3: + return nn.AvgPool3d(*args, **kwargs) + raise ValueError(f"unsupported dimensions: {dims}") + + +class HybridConditioner(nn.Module): + + def __init__(self, c_concat_config, c_crossattn_config): + super().__init__() + self.concat_conditioner = instantiate_from_config(c_concat_config) + self.crossattn_conditioner = instantiate_from_config(c_crossattn_config) + + def forward(self, c_concat, c_crossattn): + c_concat = self.concat_conditioner(c_concat) + c_crossattn = self.crossattn_conditioner(c_crossattn) + return {'c_concat': [c_concat], 'c_crossattn': [c_crossattn]} + + +def noise_like(shape, device, repeat=False): + repeat_noise = lambda: torch.randn((1, *shape[1:]), device=device).repeat(shape[0], *((1,) * (len(shape) - 1))) + noise = lambda: torch.randn(shape, device=device) + return repeat_noise() if repeat else noise() \ No newline at end of file diff --git a/comparison_models/ControlNet/ldm/modules/distributions/__init__.py b/comparison_models/ControlNet/ldm/modules/distributions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/ControlNet/ldm/modules/distributions/distributions.py b/comparison_models/ControlNet/ldm/modules/distributions/distributions.py new file mode 100644 index 0000000..f2b8ef9 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/distributions/distributions.py @@ -0,0 +1,92 @@ +import torch +import numpy as np + + +class AbstractDistribution: + def sample(self): + raise NotImplementedError() + + def mode(self): + raise NotImplementedError() + + +class DiracDistribution(AbstractDistribution): + def __init__(self, value): + self.value = value + + def sample(self): + return self.value + + def mode(self): + return self.value + + +class DiagonalGaussianDistribution(object): + def __init__(self, parameters, deterministic=False): + self.parameters = parameters + self.mean, self.logvar = torch.chunk(parameters, 2, dim=1) + self.logvar = torch.clamp(self.logvar, -30.0, 20.0) + self.deterministic = deterministic + self.std = torch.exp(0.5 * self.logvar) + self.var = torch.exp(self.logvar) + if self.deterministic: + self.var = self.std = torch.zeros_like(self.mean).to(device=self.parameters.device) + + def sample(self): + x = self.mean + self.std * torch.randn(self.mean.shape).to(device=self.parameters.device) + return x + + def kl(self, other=None): + if self.deterministic: + return torch.Tensor([0.]) + else: + if other is None: + return 0.5 * torch.sum(torch.pow(self.mean, 2) + + self.var - 1.0 - self.logvar, + dim=[1, 2, 3]) + else: + return 0.5 * torch.sum( + torch.pow(self.mean - other.mean, 2) / other.var + + self.var / other.var - 1.0 - self.logvar + other.logvar, + dim=[1, 2, 3]) + + def nll(self, sample, dims=[1,2,3]): + if self.deterministic: + return torch.Tensor([0.]) + logtwopi = np.log(2.0 * np.pi) + return 0.5 * torch.sum( + logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var, + dim=dims) + + def mode(self): + return self.mean + + +def normal_kl(mean1, logvar1, mean2, logvar2): + """ + source: https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/losses.py#L12 + Compute the KL divergence between two gaussians. + Shapes are automatically broadcasted, so batches can be compared to + scalars, among other use cases. + """ + tensor = None + for obj in (mean1, logvar1, mean2, logvar2): + if isinstance(obj, torch.Tensor): + tensor = obj + break + assert tensor is not None, "at least one argument must be a Tensor" + + # Force variances to be Tensors. Broadcasting helps convert scalars to + # Tensors, but it does not work for torch.exp(). + logvar1, logvar2 = [ + x if isinstance(x, torch.Tensor) else torch.tensor(x).to(tensor) + for x in (logvar1, logvar2) + ] + + return 0.5 * ( + -1.0 + + logvar2 + - logvar1 + + torch.exp(logvar1 - logvar2) + + ((mean1 - mean2) ** 2) * torch.exp(-logvar2) + ) diff --git a/comparison_models/ControlNet/ldm/modules/ema.py b/comparison_models/ControlNet/ldm/modules/ema.py new file mode 100644 index 0000000..bded250 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/ema.py @@ -0,0 +1,80 @@ +import torch +from torch import nn + + +class LitEma(nn.Module): + def __init__(self, model, decay=0.9999, use_num_upates=True): + super().__init__() + if decay < 0.0 or decay > 1.0: + raise ValueError('Decay must be between 0 and 1') + + self.m_name2s_name = {} + self.register_buffer('decay', torch.tensor(decay, dtype=torch.float32)) + self.register_buffer('num_updates', torch.tensor(0, dtype=torch.int) if use_num_upates + else torch.tensor(-1, dtype=torch.int)) + + for name, p in model.named_parameters(): + if p.requires_grad: + # remove as '.'-character is not allowed in buffers + s_name = name.replace('.', '') + self.m_name2s_name.update({name: s_name}) + self.register_buffer(s_name, p.clone().detach().data) + + self.collected_params = [] + + def reset_num_updates(self): + del self.num_updates + self.register_buffer('num_updates', torch.tensor(0, dtype=torch.int)) + + def forward(self, model): + decay = self.decay + + if self.num_updates >= 0: + self.num_updates += 1 + decay = min(self.decay, (1 + self.num_updates) / (10 + self.num_updates)) + + one_minus_decay = 1.0 - decay + + with torch.no_grad(): + m_param = dict(model.named_parameters()) + shadow_params = dict(self.named_buffers()) + + for key in m_param: + if m_param[key].requires_grad: + sname = self.m_name2s_name[key] + shadow_params[sname] = shadow_params[sname].type_as(m_param[key]) + shadow_params[sname].sub_(one_minus_decay * (shadow_params[sname] - m_param[key])) + else: + assert not key in self.m_name2s_name + + def copy_to(self, model): + m_param = dict(model.named_parameters()) + shadow_params = dict(self.named_buffers()) + for key in m_param: + if m_param[key].requires_grad: + m_param[key].data.copy_(shadow_params[self.m_name2s_name[key]].data) + else: + assert not key in self.m_name2s_name + + def store(self, parameters): + """ + Save the current parameters for restoring later. + Args: + parameters: Iterable of `torch.nn.Parameter`; the parameters to be + temporarily stored. + """ + self.collected_params = [param.clone() for param in parameters] + + def restore(self, parameters): + """ + Restore the parameters stored with the `store` method. + Useful to validate the model with EMA parameters without affecting the + original optimization process. Store the parameters before the + `copy_to` method. After validation (or model saving), use this to + restore the former parameters. + Args: + parameters: Iterable of `torch.nn.Parameter`; the parameters to be + updated with the stored parameters. + """ + for c_param, param in zip(self.collected_params, parameters): + param.data.copy_(c_param.data) diff --git a/comparison_models/ControlNet/ldm/modules/encoders/__init__.py b/comparison_models/ControlNet/ldm/modules/encoders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comparison_models/ControlNet/ldm/modules/encoders/modules.py b/comparison_models/ControlNet/ldm/modules/encoders/modules.py new file mode 100644 index 0000000..4edd549 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/encoders/modules.py @@ -0,0 +1,213 @@ +import torch +import torch.nn as nn +from torch.utils.checkpoint import checkpoint + +from transformers import T5Tokenizer, T5EncoderModel, CLIPTokenizer, CLIPTextModel + +import open_clip +from ldm.util import default, count_params + + +class AbstractEncoder(nn.Module): + def __init__(self): + super().__init__() + + def encode(self, *args, **kwargs): + raise NotImplementedError + + +class IdentityEncoder(AbstractEncoder): + + def encode(self, x): + return x + + +class ClassEmbedder(nn.Module): + def __init__(self, embed_dim, n_classes=1000, key='class', ucg_rate=0.1): + super().__init__() + self.key = key + self.embedding = nn.Embedding(n_classes, embed_dim) + self.n_classes = n_classes + self.ucg_rate = ucg_rate + + def forward(self, batch, key=None, disable_dropout=False): + if key is None: + key = self.key + # this is for use in crossattn + c = batch[key][:, None] + if self.ucg_rate > 0. and not disable_dropout: + mask = 1. - torch.bernoulli(torch.ones_like(c) * self.ucg_rate) + c = mask * c + (1-mask) * torch.ones_like(c)*(self.n_classes-1) + c = c.long() + c = self.embedding(c) + return c + + def get_unconditional_conditioning(self, bs, device="cuda"): + uc_class = self.n_classes - 1 # 1000 classes --> 0 ... 999, one extra class for ucg (class 1000) + uc = torch.ones((bs,), device=device) * uc_class + uc = {self.key: uc} + return uc + + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +class FrozenT5Embedder(AbstractEncoder): + """Uses the T5 transformer encoder for text""" + def __init__(self, version="google/t5-v1_1-large", device="cuda", max_length=77, freeze=True): # others are google/t5-v1_1-xl and google/t5-v1_1-xxl + super().__init__() + self.tokenizer = T5Tokenizer.from_pretrained(version) + self.transformer = T5EncoderModel.from_pretrained(version) + self.device = device + self.max_length = max_length # TODO: typical value? + if freeze: + self.freeze() + + def freeze(self): + self.transformer = self.transformer.eval() + #self.train = disabled_train + for param in self.parameters(): + param.requires_grad = False + + def forward(self, text): + batch_encoding = self.tokenizer(text, truncation=True, max_length=self.max_length, return_length=True, + return_overflowing_tokens=False, padding="max_length", return_tensors="pt") + tokens = batch_encoding["input_ids"].to(self.device) + outputs = self.transformer(input_ids=tokens) + + z = outputs.last_hidden_state + return z + + def encode(self, text): + return self(text) + + +class FrozenCLIPEmbedder(AbstractEncoder): + """Uses the CLIP transformer encoder for text (from huggingface)""" + LAYERS = [ + "last", + "pooled", + "hidden" + ] + def __init__(self, version="openai/clip-vit-large-patch14", device="cuda", max_length=77, + freeze=True, layer="last", layer_idx=None): # clip-vit-base-patch32 + super().__init__() + assert layer in self.LAYERS + self.tokenizer = CLIPTokenizer.from_pretrained(version) + self.transformer = CLIPTextModel.from_pretrained(version) + self.device = device + self.max_length = max_length + if freeze: + self.freeze() + self.layer = layer + self.layer_idx = layer_idx + if layer == "hidden": + assert layer_idx is not None + assert 0 <= abs(layer_idx) <= 12 + + def freeze(self): + self.transformer = self.transformer.eval() + #self.train = disabled_train + for param in self.parameters(): + param.requires_grad = False + + def forward(self, text): + batch_encoding = self.tokenizer(text, truncation=True, max_length=self.max_length, return_length=True, + return_overflowing_tokens=False, padding="max_length", return_tensors="pt") + tokens = batch_encoding["input_ids"].to(self.device) + outputs = self.transformer(input_ids=tokens, output_hidden_states=self.layer=="hidden") + if self.layer == "last": + z = outputs.last_hidden_state + elif self.layer == "pooled": + z = outputs.pooler_output[:, None, :] + else: + z = outputs.hidden_states[self.layer_idx] + return z + + def encode(self, text): + return self(text) + + +class FrozenOpenCLIPEmbedder(AbstractEncoder): + """ + Uses the OpenCLIP transformer encoder for text + """ + LAYERS = [ + #"pooled", + "last", + "penultimate" + ] + def __init__(self, arch="ViT-H-14", version="laion2b_s32b_b79k", device="cuda", max_length=77, + freeze=True, layer="last"): + super().__init__() + assert layer in self.LAYERS + model, _, _ = open_clip.create_model_and_transforms(arch, device=torch.device('cpu'), pretrained=version) + del model.visual + self.model = model + + self.device = device + self.max_length = max_length + if freeze: + self.freeze() + self.layer = layer + if self.layer == "last": + self.layer_idx = 0 + elif self.layer == "penultimate": + self.layer_idx = 1 + else: + raise NotImplementedError() + + def freeze(self): + self.model = self.model.eval() + for param in self.parameters(): + param.requires_grad = False + + def forward(self, text): + tokens = open_clip.tokenize(text) + z = self.encode_with_transformer(tokens.to(self.device)) + return z + + def encode_with_transformer(self, text): + x = self.model.token_embedding(text) # [batch_size, n_ctx, d_model] + x = x + self.model.positional_embedding + x = x.permute(1, 0, 2) # NLD -> LND + x = self.text_transformer_forward(x, attn_mask=self.model.attn_mask) + x = x.permute(1, 0, 2) # LND -> NLD + x = self.model.ln_final(x) + return x + + def text_transformer_forward(self, x: torch.Tensor, attn_mask = None): + for i, r in enumerate(self.model.transformer.resblocks): + if i == len(self.model.transformer.resblocks) - self.layer_idx: + break + if self.model.transformer.grad_checkpointing and not torch.jit.is_scripting(): + x = checkpoint(r, x, attn_mask) + else: + x = r(x, attn_mask=attn_mask) + return x + + def encode(self, text): + return self(text) + + +class FrozenCLIPT5Encoder(AbstractEncoder): + def __init__(self, clip_version="openai/clip-vit-large-patch14", t5_version="google/t5-v1_1-xl", device="cuda", + clip_max_length=77, t5_max_length=77): + super().__init__() + self.clip_encoder = FrozenCLIPEmbedder(clip_version, device, max_length=clip_max_length) + self.t5_encoder = FrozenT5Embedder(t5_version, device, max_length=t5_max_length) + print(f"{self.clip_encoder.__class__.__name__} has {count_params(self.clip_encoder)*1.e-6:.2f} M parameters, " + f"{self.t5_encoder.__class__.__name__} comes with {count_params(self.t5_encoder)*1.e-6:.2f} M params.") + + def encode(self, text): + return self(text) + + def forward(self, text): + clip_z = self.clip_encoder.encode(text) + t5_z = self.t5_encoder.encode(text) + return [clip_z, t5_z] + + diff --git a/comparison_models/ControlNet/ldm/modules/image_degradation/__init__.py b/comparison_models/ControlNet/ldm/modules/image_degradation/__init__.py new file mode 100644 index 0000000..7836cad --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/image_degradation/__init__.py @@ -0,0 +1,2 @@ +from ldm.modules.image_degradation.bsrgan import degradation_bsrgan_variant as degradation_fn_bsr +from ldm.modules.image_degradation.bsrgan_light import degradation_bsrgan_variant as degradation_fn_bsr_light diff --git a/comparison_models/ControlNet/ldm/modules/image_degradation/bsrgan.py b/comparison_models/ControlNet/ldm/modules/image_degradation/bsrgan.py new file mode 100644 index 0000000..32ef561 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/image_degradation/bsrgan.py @@ -0,0 +1,730 @@ +# -*- coding: utf-8 -*- +""" +# -------------------------------------------- +# Super-Resolution +# -------------------------------------------- +# +# Kai Zhang (cskaizhang@gmail.com) +# https://github.com/cszn +# From 2019/03--2021/08 +# -------------------------------------------- +""" + +import numpy as np +import cv2 +import torch + +from functools import partial +import random +from scipy import ndimage +import scipy +import scipy.stats as ss +from scipy.interpolate import interp2d +from scipy.linalg import orth +import albumentations + +import ldm.modules.image_degradation.utils_image as util + + +def modcrop_np(img, sf): + ''' + Args: + img: numpy image, WxH or WxHxC + sf: scale factor + Return: + cropped image + ''' + w, h = img.shape[:2] + im = np.copy(img) + return im[:w - w % sf, :h - h % sf, ...] + + +""" +# -------------------------------------------- +# anisotropic Gaussian kernels +# -------------------------------------------- +""" + + +def analytic_kernel(k): + """Calculate the X4 kernel from the X2 kernel (for proof see appendix in paper)""" + k_size = k.shape[0] + # Calculate the big kernels size + big_k = np.zeros((3 * k_size - 2, 3 * k_size - 2)) + # Loop over the small kernel to fill the big one + for r in range(k_size): + for c in range(k_size): + big_k[2 * r:2 * r + k_size, 2 * c:2 * c + k_size] += k[r, c] * k + # Crop the edges of the big kernel to ignore very small values and increase run time of SR + crop = k_size // 2 + cropped_big_k = big_k[crop:-crop, crop:-crop] + # Normalize to 1 + return cropped_big_k / cropped_big_k.sum() + + +def anisotropic_Gaussian(ksize=15, theta=np.pi, l1=6, l2=6): + """ generate an anisotropic Gaussian kernel + Args: + ksize : e.g., 15, kernel size + theta : [0, pi], rotation angle range + l1 : [0.1,50], scaling of eigenvalues + l2 : [0.1,l1], scaling of eigenvalues + If l1 = l2, will get an isotropic Gaussian kernel. + Returns: + k : kernel + """ + + v = np.dot(np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]), np.array([1., 0.])) + V = np.array([[v[0], v[1]], [v[1], -v[0]]]) + D = np.array([[l1, 0], [0, l2]]) + Sigma = np.dot(np.dot(V, D), np.linalg.inv(V)) + k = gm_blur_kernel(mean=[0, 0], cov=Sigma, size=ksize) + + return k + + +def gm_blur_kernel(mean, cov, size=15): + center = size / 2.0 + 0.5 + k = np.zeros([size, size]) + for y in range(size): + for x in range(size): + cy = y - center + 1 + cx = x - center + 1 + k[y, x] = ss.multivariate_normal.pdf([cx, cy], mean=mean, cov=cov) + + k = k / np.sum(k) + return k + + +def shift_pixel(x, sf, upper_left=True): + """shift pixel for super-resolution with different scale factors + Args: + x: WxHxC or WxH + sf: scale factor + upper_left: shift direction + """ + h, w = x.shape[:2] + shift = (sf - 1) * 0.5 + xv, yv = np.arange(0, w, 1.0), np.arange(0, h, 1.0) + if upper_left: + x1 = xv + shift + y1 = yv + shift + else: + x1 = xv - shift + y1 = yv - shift + + x1 = np.clip(x1, 0, w - 1) + y1 = np.clip(y1, 0, h - 1) + + if x.ndim == 2: + x = interp2d(xv, yv, x)(x1, y1) + if x.ndim == 3: + for i in range(x.shape[-1]): + x[:, :, i] = interp2d(xv, yv, x[:, :, i])(x1, y1) + + return x + + +def blur(x, k): + ''' + x: image, NxcxHxW + k: kernel, Nx1xhxw + ''' + n, c = x.shape[:2] + p1, p2 = (k.shape[-2] - 1) // 2, (k.shape[-1] - 1) // 2 + x = torch.nn.functional.pad(x, pad=(p1, p2, p1, p2), mode='replicate') + k = k.repeat(1, c, 1, 1) + k = k.view(-1, 1, k.shape[2], k.shape[3]) + x = x.view(1, -1, x.shape[2], x.shape[3]) + x = torch.nn.functional.conv2d(x, k, bias=None, stride=1, padding=0, groups=n * c) + x = x.view(n, c, x.shape[2], x.shape[3]) + + return x + + +def gen_kernel(k_size=np.array([15, 15]), scale_factor=np.array([4, 4]), min_var=0.6, max_var=10., noise_level=0): + """" + # modified version of https://github.com/assafshocher/BlindSR_dataset_generator + # Kai Zhang + # min_var = 0.175 * sf # variance of the gaussian kernel will be sampled between min_var and max_var + # max_var = 2.5 * sf + """ + # Set random eigen-vals (lambdas) and angle (theta) for COV matrix + lambda_1 = min_var + np.random.rand() * (max_var - min_var) + lambda_2 = min_var + np.random.rand() * (max_var - min_var) + theta = np.random.rand() * np.pi # random theta + noise = -noise_level + np.random.rand(*k_size) * noise_level * 2 + + # Set COV matrix using Lambdas and Theta + LAMBDA = np.diag([lambda_1, lambda_2]) + Q = np.array([[np.cos(theta), -np.sin(theta)], + [np.sin(theta), np.cos(theta)]]) + SIGMA = Q @ LAMBDA @ Q.T + INV_SIGMA = np.linalg.inv(SIGMA)[None, None, :, :] + + # Set expectation position (shifting kernel for aligned image) + MU = k_size // 2 - 0.5 * (scale_factor - 1) # - 0.5 * (scale_factor - k_size % 2) + MU = MU[None, None, :, None] + + # Create meshgrid for Gaussian + [X, Y] = np.meshgrid(range(k_size[0]), range(k_size[1])) + Z = np.stack([X, Y], 2)[:, :, :, None] + + # Calcualte Gaussian for every pixel of the kernel + ZZ = Z - MU + ZZ_t = ZZ.transpose(0, 1, 3, 2) + raw_kernel = np.exp(-0.5 * np.squeeze(ZZ_t @ INV_SIGMA @ ZZ)) * (1 + noise) + + # shift the kernel so it will be centered + # raw_kernel_centered = kernel_shift(raw_kernel, scale_factor) + + # Normalize the kernel and return + # kernel = raw_kernel_centered / np.sum(raw_kernel_centered) + kernel = raw_kernel / np.sum(raw_kernel) + return kernel + + +def fspecial_gaussian(hsize, sigma): + hsize = [hsize, hsize] + siz = [(hsize[0] - 1.0) / 2.0, (hsize[1] - 1.0) / 2.0] + std = sigma + [x, y] = np.meshgrid(np.arange(-siz[1], siz[1] + 1), np.arange(-siz[0], siz[0] + 1)) + arg = -(x * x + y * y) / (2 * std * std) + h = np.exp(arg) + h[h < scipy.finfo(float).eps * h.max()] = 0 + sumh = h.sum() + if sumh != 0: + h = h / sumh + return h + + +def fspecial_laplacian(alpha): + alpha = max([0, min([alpha, 1])]) + h1 = alpha / (alpha + 1) + h2 = (1 - alpha) / (alpha + 1) + h = [[h1, h2, h1], [h2, -4 / (alpha + 1), h2], [h1, h2, h1]] + h = np.array(h) + return h + + +def fspecial(filter_type, *args, **kwargs): + ''' + python code from: + https://github.com/ronaldosena/imagens-medicas-2/blob/40171a6c259edec7827a6693a93955de2bd39e76/Aulas/aula_2_-_uniform_filter/matlab_fspecial.py + ''' + if filter_type == 'gaussian': + return fspecial_gaussian(*args, **kwargs) + if filter_type == 'laplacian': + return fspecial_laplacian(*args, **kwargs) + + +""" +# -------------------------------------------- +# degradation models +# -------------------------------------------- +""" + + +def bicubic_degradation(x, sf=3): + ''' + Args: + x: HxWxC image, [0, 1] + sf: down-scale factor + Return: + bicubicly downsampled LR image + ''' + x = util.imresize_np(x, scale=1 / sf) + return x + + +def srmd_degradation(x, k, sf=3): + ''' blur + bicubic downsampling + Args: + x: HxWxC image, [0, 1] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + Reference: + @inproceedings{zhang2018learning, + title={Learning a single convolutional super-resolution network for multiple degradations}, + author={Zhang, Kai and Zuo, Wangmeng and Zhang, Lei}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + pages={3262--3271}, + year={2018} + } + ''' + x = ndimage.filters.convolve(x, np.expand_dims(k, axis=2), mode='wrap') # 'nearest' | 'mirror' + x = bicubic_degradation(x, sf=sf) + return x + + +def dpsr_degradation(x, k, sf=3): + ''' bicubic downsampling + blur + Args: + x: HxWxC image, [0, 1] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + Reference: + @inproceedings{zhang2019deep, + title={Deep Plug-and-Play Super-Resolution for Arbitrary Blur Kernels}, + author={Zhang, Kai and Zuo, Wangmeng and Zhang, Lei}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + pages={1671--1681}, + year={2019} + } + ''' + x = bicubic_degradation(x, sf=sf) + x = ndimage.filters.convolve(x, np.expand_dims(k, axis=2), mode='wrap') + return x + + +def classical_degradation(x, k, sf=3): + ''' blur + downsampling + Args: + x: HxWxC image, [0, 1]/[0, 255] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + ''' + x = ndimage.filters.convolve(x, np.expand_dims(k, axis=2), mode='wrap') + # x = filters.correlate(x, np.expand_dims(np.flip(k), axis=2)) + st = 0 + return x[st::sf, st::sf, ...] + + +def add_sharpening(img, weight=0.5, radius=50, threshold=10): + """USM sharpening. borrowed from real-ESRGAN + Input image: I; Blurry image: B. + 1. K = I + weight * (I - B) + 2. Mask = 1 if abs(I - B) > threshold, else: 0 + 3. Blur mask: + 4. Out = Mask * K + (1 - Mask) * I + Args: + img (Numpy array): Input image, HWC, BGR; float32, [0, 1]. + weight (float): Sharp weight. Default: 1. + radius (float): Kernel size of Gaussian blur. Default: 50. + threshold (int): + """ + if radius % 2 == 0: + radius += 1 + blur = cv2.GaussianBlur(img, (radius, radius), 0) + residual = img - blur + mask = np.abs(residual) * 255 > threshold + mask = mask.astype('float32') + soft_mask = cv2.GaussianBlur(mask, (radius, radius), 0) + + K = img + weight * residual + K = np.clip(K, 0, 1) + return soft_mask * K + (1 - soft_mask) * img + + +def add_blur(img, sf=4): + wd2 = 4.0 + sf + wd = 2.0 + 0.2 * sf + if random.random() < 0.5: + l1 = wd2 * random.random() + l2 = wd2 * random.random() + k = anisotropic_Gaussian(ksize=2 * random.randint(2, 11) + 3, theta=random.random() * np.pi, l1=l1, l2=l2) + else: + k = fspecial('gaussian', 2 * random.randint(2, 11) + 3, wd * random.random()) + img = ndimage.filters.convolve(img, np.expand_dims(k, axis=2), mode='mirror') + + return img + + +def add_resize(img, sf=4): + rnum = np.random.rand() + if rnum > 0.8: # up + sf1 = random.uniform(1, 2) + elif rnum < 0.7: # down + sf1 = random.uniform(0.5 / sf, 1) + else: + sf1 = 1.0 + img = cv2.resize(img, (int(sf1 * img.shape[1]), int(sf1 * img.shape[0])), interpolation=random.choice([1, 2, 3])) + img = np.clip(img, 0.0, 1.0) + + return img + + +# def add_Gaussian_noise(img, noise_level1=2, noise_level2=25): +# noise_level = random.randint(noise_level1, noise_level2) +# rnum = np.random.rand() +# if rnum > 0.6: # add color Gaussian noise +# img += np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) +# elif rnum < 0.4: # add grayscale Gaussian noise +# img += np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) +# else: # add noise +# L = noise_level2 / 255. +# D = np.diag(np.random.rand(3)) +# U = orth(np.random.rand(3, 3)) +# conv = np.dot(np.dot(np.transpose(U), D), U) +# img += np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) +# img = np.clip(img, 0.0, 1.0) +# return img + +def add_Gaussian_noise(img, noise_level1=2, noise_level2=25): + noise_level = random.randint(noise_level1, noise_level2) + rnum = np.random.rand() + if rnum > 0.6: # add color Gaussian noise + img = img + np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) + elif rnum < 0.4: # add grayscale Gaussian noise + img = img + np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) + else: # add noise + L = noise_level2 / 255. + D = np.diag(np.random.rand(3)) + U = orth(np.random.rand(3, 3)) + conv = np.dot(np.dot(np.transpose(U), D), U) + img = img + np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) + img = np.clip(img, 0.0, 1.0) + return img + + +def add_speckle_noise(img, noise_level1=2, noise_level2=25): + noise_level = random.randint(noise_level1, noise_level2) + img = np.clip(img, 0.0, 1.0) + rnum = random.random() + if rnum > 0.6: + img += img * np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) + elif rnum < 0.4: + img += img * np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) + else: + L = noise_level2 / 255. + D = np.diag(np.random.rand(3)) + U = orth(np.random.rand(3, 3)) + conv = np.dot(np.dot(np.transpose(U), D), U) + img += img * np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) + img = np.clip(img, 0.0, 1.0) + return img + + +def add_Poisson_noise(img): + img = np.clip((img * 255.0).round(), 0, 255) / 255. + vals = 10 ** (2 * random.random() + 2.0) # [2, 4] + if random.random() < 0.5: + img = np.random.poisson(img * vals).astype(np.float32) / vals + else: + img_gray = np.dot(img[..., :3], [0.299, 0.587, 0.114]) + img_gray = np.clip((img_gray * 255.0).round(), 0, 255) / 255. + noise_gray = np.random.poisson(img_gray * vals).astype(np.float32) / vals - img_gray + img += noise_gray[:, :, np.newaxis] + img = np.clip(img, 0.0, 1.0) + return img + + +def add_JPEG_noise(img): + quality_factor = random.randint(30, 95) + img = cv2.cvtColor(util.single2uint(img), cv2.COLOR_RGB2BGR) + result, encimg = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), quality_factor]) + img = cv2.imdecode(encimg, 1) + img = cv2.cvtColor(util.uint2single(img), cv2.COLOR_BGR2RGB) + return img + + +def random_crop(lq, hq, sf=4, lq_patchsize=64): + h, w = lq.shape[:2] + rnd_h = random.randint(0, h - lq_patchsize) + rnd_w = random.randint(0, w - lq_patchsize) + lq = lq[rnd_h:rnd_h + lq_patchsize, rnd_w:rnd_w + lq_patchsize, :] + + rnd_h_H, rnd_w_H = int(rnd_h * sf), int(rnd_w * sf) + hq = hq[rnd_h_H:rnd_h_H + lq_patchsize * sf, rnd_w_H:rnd_w_H + lq_patchsize * sf, :] + return lq, hq + + +def degradation_bsrgan(img, sf=4, lq_patchsize=72, isp_model=None): + """ + This is the degradation model of BSRGAN from the paper + "Designing a Practical Degradation Model for Deep Blind Image Super-Resolution" + ---------- + img: HXWXC, [0, 1], its size should be large than (lq_patchsizexsf)x(lq_patchsizexsf) + sf: scale factor + isp_model: camera ISP model + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + isp_prob, jpeg_prob, scale2_prob = 0.25, 0.9, 0.25 + sf_ori = sf + + h1, w1 = img.shape[:2] + img = img.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = img.shape[:2] + + if h < lq_patchsize * sf or w < lq_patchsize * sf: + raise ValueError(f'img size ({h1}X{w1}) is too small!') + + hq = img.copy() + + if sf == 4 and random.random() < scale2_prob: # downsample1 + if np.random.rand() < 0.5: + img = cv2.resize(img, (int(1 / 2 * img.shape[1]), int(1 / 2 * img.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + img = util.imresize_np(img, 1 / 2, True) + img = np.clip(img, 0.0, 1.0) + sf = 2 + + shuffle_order = random.sample(range(7), 7) + idx1, idx2 = shuffle_order.index(2), shuffle_order.index(3) + if idx1 > idx2: # keep downsample3 last + shuffle_order[idx1], shuffle_order[idx2] = shuffle_order[idx2], shuffle_order[idx1] + + for i in shuffle_order: + + if i == 0: + img = add_blur(img, sf=sf) + + elif i == 1: + img = add_blur(img, sf=sf) + + elif i == 2: + a, b = img.shape[1], img.shape[0] + # downsample2 + if random.random() < 0.75: + sf1 = random.uniform(1, 2 * sf) + img = cv2.resize(img, (int(1 / sf1 * img.shape[1]), int(1 / sf1 * img.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + k = fspecial('gaussian', 25, random.uniform(0.1, 0.6 * sf)) + k_shifted = shift_pixel(k, sf) + k_shifted = k_shifted / k_shifted.sum() # blur with shifted kernel + img = ndimage.filters.convolve(img, np.expand_dims(k_shifted, axis=2), mode='mirror') + img = img[0::sf, 0::sf, ...] # nearest downsampling + img = np.clip(img, 0.0, 1.0) + + elif i == 3: + # downsample3 + img = cv2.resize(img, (int(1 / sf * a), int(1 / sf * b)), interpolation=random.choice([1, 2, 3])) + img = np.clip(img, 0.0, 1.0) + + elif i == 4: + # add Gaussian noise + img = add_Gaussian_noise(img, noise_level1=2, noise_level2=25) + + elif i == 5: + # add JPEG noise + if random.random() < jpeg_prob: + img = add_JPEG_noise(img) + + elif i == 6: + # add processed camera sensor noise + if random.random() < isp_prob and isp_model is not None: + with torch.no_grad(): + img, hq = isp_model.forward(img.copy(), hq) + + # add final JPEG compression noise + img = add_JPEG_noise(img) + + # random crop + img, hq = random_crop(img, hq, sf_ori, lq_patchsize) + + return img, hq + + +# todo no isp_model? +def degradation_bsrgan_variant(image, sf=4, isp_model=None): + """ + This is the degradation model of BSRGAN from the paper + "Designing a Practical Degradation Model for Deep Blind Image Super-Resolution" + ---------- + sf: scale factor + isp_model: camera ISP model + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + image = util.uint2single(image) + isp_prob, jpeg_prob, scale2_prob = 0.25, 0.9, 0.25 + sf_ori = sf + + h1, w1 = image.shape[:2] + image = image.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = image.shape[:2] + + hq = image.copy() + + if sf == 4 and random.random() < scale2_prob: # downsample1 + if np.random.rand() < 0.5: + image = cv2.resize(image, (int(1 / 2 * image.shape[1]), int(1 / 2 * image.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + image = util.imresize_np(image, 1 / 2, True) + image = np.clip(image, 0.0, 1.0) + sf = 2 + + shuffle_order = random.sample(range(7), 7) + idx1, idx2 = shuffle_order.index(2), shuffle_order.index(3) + if idx1 > idx2: # keep downsample3 last + shuffle_order[idx1], shuffle_order[idx2] = shuffle_order[idx2], shuffle_order[idx1] + + for i in shuffle_order: + + if i == 0: + image = add_blur(image, sf=sf) + + elif i == 1: + image = add_blur(image, sf=sf) + + elif i == 2: + a, b = image.shape[1], image.shape[0] + # downsample2 + if random.random() < 0.75: + sf1 = random.uniform(1, 2 * sf) + image = cv2.resize(image, (int(1 / sf1 * image.shape[1]), int(1 / sf1 * image.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + k = fspecial('gaussian', 25, random.uniform(0.1, 0.6 * sf)) + k_shifted = shift_pixel(k, sf) + k_shifted = k_shifted / k_shifted.sum() # blur with shifted kernel + image = ndimage.filters.convolve(image, np.expand_dims(k_shifted, axis=2), mode='mirror') + image = image[0::sf, 0::sf, ...] # nearest downsampling + image = np.clip(image, 0.0, 1.0) + + elif i == 3: + # downsample3 + image = cv2.resize(image, (int(1 / sf * a), int(1 / sf * b)), interpolation=random.choice([1, 2, 3])) + image = np.clip(image, 0.0, 1.0) + + elif i == 4: + # add Gaussian noise + image = add_Gaussian_noise(image, noise_level1=2, noise_level2=25) + + elif i == 5: + # add JPEG noise + if random.random() < jpeg_prob: + image = add_JPEG_noise(image) + + # elif i == 6: + # # add processed camera sensor noise + # if random.random() < isp_prob and isp_model is not None: + # with torch.no_grad(): + # img, hq = isp_model.forward(img.copy(), hq) + + # add final JPEG compression noise + image = add_JPEG_noise(image) + image = util.single2uint(image) + example = {"image":image} + return example + + +# TODO incase there is a pickle error one needs to replace a += x with a = a + x in add_speckle_noise etc... +def degradation_bsrgan_plus(img, sf=4, shuffle_prob=0.5, use_sharp=True, lq_patchsize=64, isp_model=None): + """ + This is an extended degradation model by combining + the degradation models of BSRGAN and Real-ESRGAN + ---------- + img: HXWXC, [0, 1], its size should be large than (lq_patchsizexsf)x(lq_patchsizexsf) + sf: scale factor + use_shuffle: the degradation shuffle + use_sharp: sharpening the img + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + + h1, w1 = img.shape[:2] + img = img.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = img.shape[:2] + + if h < lq_patchsize * sf or w < lq_patchsize * sf: + raise ValueError(f'img size ({h1}X{w1}) is too small!') + + if use_sharp: + img = add_sharpening(img) + hq = img.copy() + + if random.random() < shuffle_prob: + shuffle_order = random.sample(range(13), 13) + else: + shuffle_order = list(range(13)) + # local shuffle for noise, JPEG is always the last one + shuffle_order[2:6] = random.sample(shuffle_order[2:6], len(range(2, 6))) + shuffle_order[9:13] = random.sample(shuffle_order[9:13], len(range(9, 13))) + + poisson_prob, speckle_prob, isp_prob = 0.1, 0.1, 0.1 + + for i in shuffle_order: + if i == 0: + img = add_blur(img, sf=sf) + elif i == 1: + img = add_resize(img, sf=sf) + elif i == 2: + img = add_Gaussian_noise(img, noise_level1=2, noise_level2=25) + elif i == 3: + if random.random() < poisson_prob: + img = add_Poisson_noise(img) + elif i == 4: + if random.random() < speckle_prob: + img = add_speckle_noise(img) + elif i == 5: + if random.random() < isp_prob and isp_model is not None: + with torch.no_grad(): + img, hq = isp_model.forward(img.copy(), hq) + elif i == 6: + img = add_JPEG_noise(img) + elif i == 7: + img = add_blur(img, sf=sf) + elif i == 8: + img = add_resize(img, sf=sf) + elif i == 9: + img = add_Gaussian_noise(img, noise_level1=2, noise_level2=25) + elif i == 10: + if random.random() < poisson_prob: + img = add_Poisson_noise(img) + elif i == 11: + if random.random() < speckle_prob: + img = add_speckle_noise(img) + elif i == 12: + if random.random() < isp_prob and isp_model is not None: + with torch.no_grad(): + img, hq = isp_model.forward(img.copy(), hq) + else: + print('check the shuffle!') + + # resize to desired size + img = cv2.resize(img, (int(1 / sf * hq.shape[1]), int(1 / sf * hq.shape[0])), + interpolation=random.choice([1, 2, 3])) + + # add final JPEG compression noise + img = add_JPEG_noise(img) + + # random crop + img, hq = random_crop(img, hq, sf, lq_patchsize) + + return img, hq + + +if __name__ == '__main__': + print("hey") + img = util.imread_uint('utils/test.png', 3) + print(img) + img = util.uint2single(img) + print(img) + img = img[:448, :448] + h = img.shape[0] // 4 + print("resizing to", h) + sf = 4 + deg_fn = partial(degradation_bsrgan_variant, sf=sf) + for i in range(20): + print(i) + img_lq = deg_fn(img) + print(img_lq) + img_lq_bicubic = albumentations.SmallestMaxSize(max_size=h, interpolation=cv2.INTER_CUBIC)(image=img)["image"] + print(img_lq.shape) + print("bicubic", img_lq_bicubic.shape) + print(img_hq.shape) + lq_nearest = cv2.resize(util.single2uint(img_lq), (int(sf * img_lq.shape[1]), int(sf * img_lq.shape[0])), + interpolation=0) + lq_bicubic_nearest = cv2.resize(util.single2uint(img_lq_bicubic), (int(sf * img_lq.shape[1]), int(sf * img_lq.shape[0])), + interpolation=0) + img_concat = np.concatenate([lq_bicubic_nearest, lq_nearest, util.single2uint(img_hq)], axis=1) + util.imsave(img_concat, str(i) + '.png') + + diff --git a/comparison_models/ControlNet/ldm/modules/image_degradation/bsrgan_light.py b/comparison_models/ControlNet/ldm/modules/image_degradation/bsrgan_light.py new file mode 100644 index 0000000..808c7f8 --- /dev/null +++ b/comparison_models/ControlNet/ldm/modules/image_degradation/bsrgan_light.py @@ -0,0 +1,651 @@ +# -*- coding: utf-8 -*- +import numpy as np +import cv2 +import torch + +from functools import partial +import random +from scipy import ndimage +import scipy +import scipy.stats as ss +from scipy.interpolate import interp2d +from scipy.linalg import orth +import albumentations + +import ldm.modules.image_degradation.utils_image as util + +""" +# -------------------------------------------- +# Super-Resolution +# -------------------------------------------- +# +# Kai Zhang (cskaizhang@gmail.com) +# https://github.com/cszn +# From 2019/03--2021/08 +# -------------------------------------------- +""" + +def modcrop_np(img, sf): + ''' + Args: + img: numpy image, WxH or WxHxC + sf: scale factor + Return: + cropped image + ''' + w, h = img.shape[:2] + im = np.copy(img) + return im[:w - w % sf, :h - h % sf, ...] + + +""" +# -------------------------------------------- +# anisotropic Gaussian kernels +# -------------------------------------------- +""" + + +def analytic_kernel(k): + """Calculate the X4 kernel from the X2 kernel (for proof see appendix in paper)""" + k_size = k.shape[0] + # Calculate the big kernels size + big_k = np.zeros((3 * k_size - 2, 3 * k_size - 2)) + # Loop over the small kernel to fill the big one + for r in range(k_size): + for c in range(k_size): + big_k[2 * r:2 * r + k_size, 2 * c:2 * c + k_size] += k[r, c] * k + # Crop the edges of the big kernel to ignore very small values and increase run time of SR + crop = k_size // 2 + cropped_big_k = big_k[crop:-crop, crop:-crop] + # Normalize to 1 + return cropped_big_k / cropped_big_k.sum() + + +def anisotropic_Gaussian(ksize=15, theta=np.pi, l1=6, l2=6): + """ generate an anisotropic Gaussian kernel + Args: + ksize : e.g., 15, kernel size + theta : [0, pi], rotation angle range + l1 : [0.1,50], scaling of eigenvalues + l2 : [0.1,l1], scaling of eigenvalues + If l1 = l2, will get an isotropic Gaussian kernel. + Returns: + k : kernel + """ + + v = np.dot(np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]), np.array([1., 0.])) + V = np.array([[v[0], v[1]], [v[1], -v[0]]]) + D = np.array([[l1, 0], [0, l2]]) + Sigma = np.dot(np.dot(V, D), np.linalg.inv(V)) + k = gm_blur_kernel(mean=[0, 0], cov=Sigma, size=ksize) + + return k + + +def gm_blur_kernel(mean, cov, size=15): + center = size / 2.0 + 0.5 + k = np.zeros([size, size]) + for y in range(size): + for x in range(size): + cy = y - center + 1 + cx = x - center + 1 + k[y, x] = ss.multivariate_normal.pdf([cx, cy], mean=mean, cov=cov) + + k = k / np.sum(k) + return k + + +def shift_pixel(x, sf, upper_left=True): + """shift pixel for super-resolution with different scale factors + Args: + x: WxHxC or WxH + sf: scale factor + upper_left: shift direction + """ + h, w = x.shape[:2] + shift = (sf - 1) * 0.5 + xv, yv = np.arange(0, w, 1.0), np.arange(0, h, 1.0) + if upper_left: + x1 = xv + shift + y1 = yv + shift + else: + x1 = xv - shift + y1 = yv - shift + + x1 = np.clip(x1, 0, w - 1) + y1 = np.clip(y1, 0, h - 1) + + if x.ndim == 2: + x = interp2d(xv, yv, x)(x1, y1) + if x.ndim == 3: + for i in range(x.shape[-1]): + x[:, :, i] = interp2d(xv, yv, x[:, :, i])(x1, y1) + + return x + + +def blur(x, k): + ''' + x: image, NxcxHxW + k: kernel, Nx1xhxw + ''' + n, c = x.shape[:2] + p1, p2 = (k.shape[-2] - 1) // 2, (k.shape[-1] - 1) // 2 + x = torch.nn.functional.pad(x, pad=(p1, p2, p1, p2), mode='replicate') + k = k.repeat(1, c, 1, 1) + k = k.view(-1, 1, k.shape[2], k.shape[3]) + x = x.view(1, -1, x.shape[2], x.shape[3]) + x = torch.nn.functional.conv2d(x, k, bias=None, stride=1, padding=0, groups=n * c) + x = x.view(n, c, x.shape[2], x.shape[3]) + + return x + + +def gen_kernel(k_size=np.array([15, 15]), scale_factor=np.array([4, 4]), min_var=0.6, max_var=10., noise_level=0): + """" + # modified version of https://github.com/assafshocher/BlindSR_dataset_generator + # Kai Zhang + # min_var = 0.175 * sf # variance of the gaussian kernel will be sampled between min_var and max_var + # max_var = 2.5 * sf + """ + # Set random eigen-vals (lambdas) and angle (theta) for COV matrix + lambda_1 = min_var + np.random.rand() * (max_var - min_var) + lambda_2 = min_var + np.random.rand() * (max_var - min_var) + theta = np.random.rand() * np.pi # random theta + noise = -noise_level + np.random.rand(*k_size) * noise_level * 2 + + # Set COV matrix using Lambdas and Theta + LAMBDA = np.diag([lambda_1, lambda_2]) + Q = np.array([[np.cos(theta), -np.sin(theta)], + [np.sin(theta), np.cos(theta)]]) + SIGMA = Q @ LAMBDA @ Q.T + INV_SIGMA = np.linalg.inv(SIGMA)[None, None, :, :] + + # Set expectation position (shifting kernel for aligned image) + MU = k_size // 2 - 0.5 * (scale_factor - 1) # - 0.5 * (scale_factor - k_size % 2) + MU = MU[None, None, :, None] + + # Create meshgrid for Gaussian + [X, Y] = np.meshgrid(range(k_size[0]), range(k_size[1])) + Z = np.stack([X, Y], 2)[:, :, :, None] + + # Calcualte Gaussian for every pixel of the kernel + ZZ = Z - MU + ZZ_t = ZZ.transpose(0, 1, 3, 2) + raw_kernel = np.exp(-0.5 * np.squeeze(ZZ_t @ INV_SIGMA @ ZZ)) * (1 + noise) + + # shift the kernel so it will be centered + # raw_kernel_centered = kernel_shift(raw_kernel, scale_factor) + + # Normalize the kernel and return + # kernel = raw_kernel_centered / np.sum(raw_kernel_centered) + kernel = raw_kernel / np.sum(raw_kernel) + return kernel + + +def fspecial_gaussian(hsize, sigma): + hsize = [hsize, hsize] + siz = [(hsize[0] - 1.0) / 2.0, (hsize[1] - 1.0) / 2.0] + std = sigma + [x, y] = np.meshgrid(np.arange(-siz[1], siz[1] + 1), np.arange(-siz[0], siz[0] + 1)) + arg = -(x * x + y * y) / (2 * std * std) + h = np.exp(arg) + h[h < scipy.finfo(float).eps * h.max()] = 0 + sumh = h.sum() + if sumh != 0: + h = h / sumh + return h + + +def fspecial_laplacian(alpha): + alpha = max([0, min([alpha, 1])]) + h1 = alpha / (alpha + 1) + h2 = (1 - alpha) / (alpha + 1) + h = [[h1, h2, h1], [h2, -4 / (alpha + 1), h2], [h1, h2, h1]] + h = np.array(h) + return h + + +def fspecial(filter_type, *args, **kwargs): + ''' + python code from: + https://github.com/ronaldosena/imagens-medicas-2/blob/40171a6c259edec7827a6693a93955de2bd39e76/Aulas/aula_2_-_uniform_filter/matlab_fspecial.py + ''' + if filter_type == 'gaussian': + return fspecial_gaussian(*args, **kwargs) + if filter_type == 'laplacian': + return fspecial_laplacian(*args, **kwargs) + + +""" +# -------------------------------------------- +# degradation models +# -------------------------------------------- +""" + + +def bicubic_degradation(x, sf=3): + ''' + Args: + x: HxWxC image, [0, 1] + sf: down-scale factor + Return: + bicubicly downsampled LR image + ''' + x = util.imresize_np(x, scale=1 / sf) + return x + + +def srmd_degradation(x, k, sf=3): + ''' blur + bicubic downsampling + Args: + x: HxWxC image, [0, 1] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + Reference: + @inproceedings{zhang2018learning, + title={Learning a single convolutional super-resolution network for multiple degradations}, + author={Zhang, Kai and Zuo, Wangmeng and Zhang, Lei}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + pages={3262--3271}, + year={2018} + } + ''' + x = ndimage.convolve(x, np.expand_dims(k, axis=2), mode='wrap') # 'nearest' | 'mirror' + x = bicubic_degradation(x, sf=sf) + return x + + +def dpsr_degradation(x, k, sf=3): + ''' bicubic downsampling + blur + Args: + x: HxWxC image, [0, 1] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + Reference: + @inproceedings{zhang2019deep, + title={Deep Plug-and-Play Super-Resolution for Arbitrary Blur Kernels}, + author={Zhang, Kai and Zuo, Wangmeng and Zhang, Lei}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + pages={1671--1681}, + year={2019} + } + ''' + x = bicubic_degradation(x, sf=sf) + x = ndimage.convolve(x, np.expand_dims(k, axis=2), mode='wrap') + return x + + +def classical_degradation(x, k, sf=3): + ''' blur + downsampling + Args: + x: HxWxC image, [0, 1]/[0, 255] + k: hxw, double + sf: down-scale factor + Return: + downsampled LR image + ''' + x = ndimage.convolve(x, np.expand_dims(k, axis=2), mode='wrap') + # x = filters.correlate(x, np.expand_dims(np.flip(k), axis=2)) + st = 0 + return x[st::sf, st::sf, ...] + + +def add_sharpening(img, weight=0.5, radius=50, threshold=10): + """USM sharpening. borrowed from real-ESRGAN + Input image: I; Blurry image: B. + 1. K = I + weight * (I - B) + 2. Mask = 1 if abs(I - B) > threshold, else: 0 + 3. Blur mask: + 4. Out = Mask * K + (1 - Mask) * I + Args: + img (Numpy array): Input image, HWC, BGR; float32, [0, 1]. + weight (float): Sharp weight. Default: 1. + radius (float): Kernel size of Gaussian blur. Default: 50. + threshold (int): + """ + if radius % 2 == 0: + radius += 1 + blur = cv2.GaussianBlur(img, (radius, radius), 0) + residual = img - blur + mask = np.abs(residual) * 255 > threshold + mask = mask.astype('float32') + soft_mask = cv2.GaussianBlur(mask, (radius, radius), 0) + + K = img + weight * residual + K = np.clip(K, 0, 1) + return soft_mask * K + (1 - soft_mask) * img + + +def add_blur(img, sf=4): + wd2 = 4.0 + sf + wd = 2.0 + 0.2 * sf + + wd2 = wd2/4 + wd = wd/4 + + if random.random() < 0.5: + l1 = wd2 * random.random() + l2 = wd2 * random.random() + k = anisotropic_Gaussian(ksize=random.randint(2, 11) + 3, theta=random.random() * np.pi, l1=l1, l2=l2) + else: + k = fspecial('gaussian', random.randint(2, 4) + 3, wd * random.random()) + img = ndimage.convolve(img, np.expand_dims(k, axis=2), mode='mirror') + + return img + + +def add_resize(img, sf=4): + rnum = np.random.rand() + if rnum > 0.8: # up + sf1 = random.uniform(1, 2) + elif rnum < 0.7: # down + sf1 = random.uniform(0.5 / sf, 1) + else: + sf1 = 1.0 + img = cv2.resize(img, (int(sf1 * img.shape[1]), int(sf1 * img.shape[0])), interpolation=random.choice([1, 2, 3])) + img = np.clip(img, 0.0, 1.0) + + return img + + +# def add_Gaussian_noise(img, noise_level1=2, noise_level2=25): +# noise_level = random.randint(noise_level1, noise_level2) +# rnum = np.random.rand() +# if rnum > 0.6: # add color Gaussian noise +# img += np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) +# elif rnum < 0.4: # add grayscale Gaussian noise +# img += np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) +# else: # add noise +# L = noise_level2 / 255. +# D = np.diag(np.random.rand(3)) +# U = orth(np.random.rand(3, 3)) +# conv = np.dot(np.dot(np.transpose(U), D), U) +# img += np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) +# img = np.clip(img, 0.0, 1.0) +# return img + +def add_Gaussian_noise(img, noise_level1=2, noise_level2=25): + noise_level = random.randint(noise_level1, noise_level2) + rnum = np.random.rand() + if rnum > 0.6: # add color Gaussian noise + img = img + np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) + elif rnum < 0.4: # add grayscale Gaussian noise + img = img + np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) + else: # add noise + L = noise_level2 / 255. + D = np.diag(np.random.rand(3)) + U = orth(np.random.rand(3, 3)) + conv = np.dot(np.dot(np.transpose(U), D), U) + img = img + np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) + img = np.clip(img, 0.0, 1.0) + return img + + +def add_speckle_noise(img, noise_level1=2, noise_level2=25): + noise_level = random.randint(noise_level1, noise_level2) + img = np.clip(img, 0.0, 1.0) + rnum = random.random() + if rnum > 0.6: + img += img * np.random.normal(0, noise_level / 255.0, img.shape).astype(np.float32) + elif rnum < 0.4: + img += img * np.random.normal(0, noise_level / 255.0, (*img.shape[:2], 1)).astype(np.float32) + else: + L = noise_level2 / 255. + D = np.diag(np.random.rand(3)) + U = orth(np.random.rand(3, 3)) + conv = np.dot(np.dot(np.transpose(U), D), U) + img += img * np.random.multivariate_normal([0, 0, 0], np.abs(L ** 2 * conv), img.shape[:2]).astype(np.float32) + img = np.clip(img, 0.0, 1.0) + return img + + +def add_Poisson_noise(img): + img = np.clip((img * 255.0).round(), 0, 255) / 255. + vals = 10 ** (2 * random.random() + 2.0) # [2, 4] + if random.random() < 0.5: + img = np.random.poisson(img * vals).astype(np.float32) / vals + else: + img_gray = np.dot(img[..., :3], [0.299, 0.587, 0.114]) + img_gray = np.clip((img_gray * 255.0).round(), 0, 255) / 255. + noise_gray = np.random.poisson(img_gray * vals).astype(np.float32) / vals - img_gray + img += noise_gray[:, :, np.newaxis] + img = np.clip(img, 0.0, 1.0) + return img + + +def add_JPEG_noise(img): + quality_factor = random.randint(80, 95) + img = cv2.cvtColor(util.single2uint(img), cv2.COLOR_RGB2BGR) + result, encimg = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), quality_factor]) + img = cv2.imdecode(encimg, 1) + img = cv2.cvtColor(util.uint2single(img), cv2.COLOR_BGR2RGB) + return img + + +def random_crop(lq, hq, sf=4, lq_patchsize=64): + h, w = lq.shape[:2] + rnd_h = random.randint(0, h - lq_patchsize) + rnd_w = random.randint(0, w - lq_patchsize) + lq = lq[rnd_h:rnd_h + lq_patchsize, rnd_w:rnd_w + lq_patchsize, :] + + rnd_h_H, rnd_w_H = int(rnd_h * sf), int(rnd_w * sf) + hq = hq[rnd_h_H:rnd_h_H + lq_patchsize * sf, rnd_w_H:rnd_w_H + lq_patchsize * sf, :] + return lq, hq + + +def degradation_bsrgan(img, sf=4, lq_patchsize=72, isp_model=None): + """ + This is the degradation model of BSRGAN from the paper + "Designing a Practical Degradation Model for Deep Blind Image Super-Resolution" + ---------- + img: HXWXC, [0, 1], its size should be large than (lq_patchsizexsf)x(lq_patchsizexsf) + sf: scale factor + isp_model: camera ISP model + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + isp_prob, jpeg_prob, scale2_prob = 0.25, 0.9, 0.25 + sf_ori = sf + + h1, w1 = img.shape[:2] + img = img.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = img.shape[:2] + + if h < lq_patchsize * sf or w < lq_patchsize * sf: + raise ValueError(f'img size ({h1}X{w1}) is too small!') + + hq = img.copy() + + if sf == 4 and random.random() < scale2_prob: # downsample1 + if np.random.rand() < 0.5: + img = cv2.resize(img, (int(1 / 2 * img.shape[1]), int(1 / 2 * img.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + img = util.imresize_np(img, 1 / 2, True) + img = np.clip(img, 0.0, 1.0) + sf = 2 + + shuffle_order = random.sample(range(7), 7) + idx1, idx2 = shuffle_order.index(2), shuffle_order.index(3) + if idx1 > idx2: # keep downsample3 last + shuffle_order[idx1], shuffle_order[idx2] = shuffle_order[idx2], shuffle_order[idx1] + + for i in shuffle_order: + + if i == 0: + img = add_blur(img, sf=sf) + + elif i == 1: + img = add_blur(img, sf=sf) + + elif i == 2: + a, b = img.shape[1], img.shape[0] + # downsample2 + if random.random() < 0.75: + sf1 = random.uniform(1, 2 * sf) + img = cv2.resize(img, (int(1 / sf1 * img.shape[1]), int(1 / sf1 * img.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + k = fspecial('gaussian', 25, random.uniform(0.1, 0.6 * sf)) + k_shifted = shift_pixel(k, sf) + k_shifted = k_shifted / k_shifted.sum() # blur with shifted kernel + img = ndimage.convolve(img, np.expand_dims(k_shifted, axis=2), mode='mirror') + img = img[0::sf, 0::sf, ...] # nearest downsampling + img = np.clip(img, 0.0, 1.0) + + elif i == 3: + # downsample3 + img = cv2.resize(img, (int(1 / sf * a), int(1 / sf * b)), interpolation=random.choice([1, 2, 3])) + img = np.clip(img, 0.0, 1.0) + + elif i == 4: + # add Gaussian noise + img = add_Gaussian_noise(img, noise_level1=2, noise_level2=8) + + elif i == 5: + # add JPEG noise + if random.random() < jpeg_prob: + img = add_JPEG_noise(img) + + elif i == 6: + # add processed camera sensor noise + if random.random() < isp_prob and isp_model is not None: + with torch.no_grad(): + img, hq = isp_model.forward(img.copy(), hq) + + # add final JPEG compression noise + img = add_JPEG_noise(img) + + # random crop + img, hq = random_crop(img, hq, sf_ori, lq_patchsize) + + return img, hq + + +# todo no isp_model? +def degradation_bsrgan_variant(image, sf=4, isp_model=None, up=False): + """ + This is the degradation model of BSRGAN from the paper + "Designing a Practical Degradation Model for Deep Blind Image Super-Resolution" + ---------- + sf: scale factor + isp_model: camera ISP model + Returns + ------- + img: low-quality patch, size: lq_patchsizeXlq_patchsizeXC, range: [0, 1] + hq: corresponding high-quality patch, size: (lq_patchsizexsf)X(lq_patchsizexsf)XC, range: [0, 1] + """ + image = util.uint2single(image) + isp_prob, jpeg_prob, scale2_prob = 0.25, 0.9, 0.25 + sf_ori = sf + + h1, w1 = image.shape[:2] + image = image.copy()[:w1 - w1 % sf, :h1 - h1 % sf, ...] # mod crop + h, w = image.shape[:2] + + hq = image.copy() + + if sf == 4 and random.random() < scale2_prob: # downsample1 + if np.random.rand() < 0.5: + image = cv2.resize(image, (int(1 / 2 * image.shape[1]), int(1 / 2 * image.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + image = util.imresize_np(image, 1 / 2, True) + image = np.clip(image, 0.0, 1.0) + sf = 2 + + shuffle_order = random.sample(range(7), 7) + idx1, idx2 = shuffle_order.index(2), shuffle_order.index(3) + if idx1 > idx2: # keep downsample3 last + shuffle_order[idx1], shuffle_order[idx2] = shuffle_order[idx2], shuffle_order[idx1] + + for i in shuffle_order: + + if i == 0: + image = add_blur(image, sf=sf) + + # elif i == 1: + # image = add_blur(image, sf=sf) + + if i == 0: + pass + + elif i == 2: + a, b = image.shape[1], image.shape[0] + # downsample2 + if random.random() < 0.8: + sf1 = random.uniform(1, 2 * sf) + image = cv2.resize(image, (int(1 / sf1 * image.shape[1]), int(1 / sf1 * image.shape[0])), + interpolation=random.choice([1, 2, 3])) + else: + k = fspecial('gaussian', 25, random.uniform(0.1, 0.6 * sf)) + k_shifted = shift_pixel(k, sf) + k_shifted = k_shifted / k_shifted.sum() # blur with shifted kernel + image = ndimage.convolve(image, np.expand_dims(k_shifted, axis=2), mode='mirror') + image = image[0::sf, 0::sf, ...] # nearest downsampling + + image = np.clip(image, 0.0, 1.0) + + elif i == 3: + # downsample3 + image = cv2.resize(image, (int(1 / sf * a), int(1 / sf * b)), interpolation=random.choice([1, 2, 3])) + image = np.clip(image, 0.0, 1.0) + + elif i == 4: + # add Gaussian noise + image = add_Gaussian_noise(image, noise_level1=1, noise_level2=2) + + elif i == 5: + # add JPEG noise + if random.random() < jpeg_prob: + image = add_JPEG_noise(image) + # + # elif i == 6: + # # add processed camera sensor noise + # if random.random() < isp_prob and isp_model is not None: + # with torch.no_grad(): + # img, hq = isp_model.forward(img.copy(), hq) + + # add final JPEG compression noise + image = add_JPEG_noise(image) + image = util.single2uint(image) + if up: + image = cv2.resize(image, (w1, h1), interpolation=cv2.INTER_CUBIC) # todo: random, as above? want to condition on it then + example = {"image": image} + return example + + + + +if __name__ == '__main__': + print("hey") + img = util.imread_uint('utils/test.png', 3) + img = img[:448, :448] + h = img.shape[0] // 4 + print("resizing to", h) + sf = 4 + deg_fn = partial(degradation_bsrgan_variant, sf=sf) + for i in range(20): + print(i) + img_hq = img + img_lq = deg_fn(img)["image"] + img_hq, img_lq = util.uint2single(img_hq), util.uint2single(img_lq) + print(img_lq) + img_lq_bicubic = albumentations.SmallestMaxSize(max_size=h, interpolation=cv2.INTER_CUBIC)(image=img_hq)["image"] + print(img_lq.shape) + print("bicubic", img_lq_bicubic.shape) + print(img_hq.shape) + lq_nearest = cv2.resize(util.single2uint(img_lq), (int(sf * img_lq.shape[1]), int(sf * img_lq.shape[0])), + interpolation=0) + lq_bicubic_nearest = cv2.resize(util.single2uint(img_lq_bicubic), + (int(sf * img_lq.shape[1]), int(sf * img_lq.shape[0])), + interpolation=0) + img_concat = np.concatenate([lq_bicubic_nearest, lq_nearest, util.single2uint(img_hq)], axis=1) + util.imsave(img_concat, str(i) + '.png') diff --git a/comparison_models/ControlNet/ldm/modules/image_degradation/utils/test.png b/comparison_models/ControlNet/ldm/modules/image_degradation/utils/test.png new file mode 100644 index 0000000000000000000000000000000000000000..4249b43de0f22707758d13c240268a401642f6e6 GIT binary patch literal 441072 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ϐ}ewmE<1P{om13ZIR;da-v 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