nikigoli commited on
Commit
383a563
·
verified ·
1 Parent(s): 0a012ef

Upload CountGD++ weights and code

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +4 -0
  2. LICENSE +21 -0
  3. README.md +93 -0
  4. cfg_app.py +118 -0
  5. config.json +157 -0
  6. countgdpp.py +177 -0
  7. datasets/__init__.py +23 -0
  8. datasets/coco.py +660 -0
  9. datasets/coco_eval.py +266 -0
  10. datasets/coco_panoptic.py +99 -0
  11. datasets/cocogrounding_eval.py +271 -0
  12. datasets/data_util.py +170 -0
  13. datasets/dataset.py +44 -0
  14. datasets/odvg.py +263 -0
  15. datasets/panoptic_eval.py +44 -0
  16. datasets/random_crop.py +135 -0
  17. datasets/sltransform.py +247 -0
  18. datasets/transforms.py +324 -0
  19. datasets/transforms_app.py +338 -0
  20. datasets/transforms_orig.py +317 -0
  21. example.py +33 -0
  22. groundingdino/util/__init__.py +1 -0
  23. groundingdino/util/box_ops.py +140 -0
  24. groundingdino/util/get_tokenlizer.py +29 -0
  25. groundingdino/util/inference.py +259 -0
  26. groundingdino/util/logger.py +93 -0
  27. groundingdino/util/misc.py +717 -0
  28. groundingdino/util/slconfig.py +427 -0
  29. groundingdino/util/slio.py +177 -0
  30. groundingdino/util/time_counter.py +62 -0
  31. groundingdino/util/utils.py +610 -0
  32. groundingdino/util/visualizer.py +318 -0
  33. groundingdino/util/vl_utils.py +100 -0
  34. img/336.jpg +0 -0
  35. img/inference-architecture.jpg +3 -0
  36. img/synthetic_blueberry.jpg +3 -0
  37. img/synthetic_strawberry.jpg +3 -0
  38. img/teaser.jpg +3 -0
  39. model.safetensors +3 -0
  40. models/GroundingDINO/__init__.py +15 -0
  41. models/GroundingDINO/backbone/__init__.py +1 -0
  42. models/GroundingDINO/backbone/backbone.py +221 -0
  43. models/GroundingDINO/backbone/position_encoding.py +186 -0
  44. models/GroundingDINO/backbone/swin_transformer.py +804 -0
  45. models/GroundingDINO/bertwarper.py +273 -0
  46. models/GroundingDINO/fuse_modules.py +297 -0
  47. models/GroundingDINO/groundingdino.py +1041 -0
  48. models/GroundingDINO/groundingdino_app.py +1257 -0
  49. models/GroundingDINO/matcher.py +217 -0
  50. models/GroundingDINO/mlp_loca.py +23 -0
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ img/inference-architecture.jpg filter=lfs diff=lfs merge=lfs -text
37
+ img/synthetic_blueberry.jpg filter=lfs diff=lfs merge=lfs -text
38
+ img/synthetic_strawberry.jpg filter=lfs diff=lfs merge=lfs -text
39
+ img/teaser.jpg filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Niki Amini-Naieni
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - en
5
+ library_name: pytorch
6
+ pipeline_tag: zero-shot-object-detection
7
+ tags:
8
+ - CountGD++
9
+ - computer-vision
10
+ - counting
11
+ - grounding-dino
12
+ - model_hub_mixin
13
+ - multi-modal
14
+ - open-vocabulary
15
+ - pytorch_model_hub_mixin
16
+ ---
17
+
18
+ # CountGD++
19
+
20
+ **CountGD++: Generalized Prompting for Open-World Counting** (CVPR 2026)
21
+
22
+ A multi-modal open-world counting model. You say what to count with text,
23
+ visual exemplars (boxes on example objects), or both. You can also say what
24
+ **not** to count with negative text and negative exemplars. Exemplars can come
25
+ from the image being counted or from a different image.
26
+
27
+ [Paper](https://arxiv.org/abs/2512.23351) · [Code](https://github.com/niki-amini-naieni/CountGDPlusPlus)
28
+
29
+ ![teaser](img/teaser.jpg)
30
+
31
+ This repository holds both the **weights** (`model.safetensors`, `config.json`)
32
+ and the **inference code**, so nothing has to be downloaded from Google Drive.
33
+
34
+ ## Usage
35
+
36
+ ```bash
37
+ pip install torch torchvision "transformers<4.50" timm addict "yapf==0.40.1" termcolor \
38
+ scipy matplotlib opencv-python-headless pycocotools pyyaml huggingface_hub safetensors
39
+ ```
40
+
41
+ ```python
42
+ import sys
43
+ from huggingface_hub import snapshot_download
44
+
45
+ repo = snapshot_download("nikigoli/CountGDPlusPlus") # code + weights
46
+ sys.path.insert(0, repo)
47
+ from countgdpp import CountGDPlusPlus, count
48
+
49
+ model = CountGDPlusPlus.from_pretrained("nikigoli/CountGDPlusPlus").to("cuda").eval()
50
+
51
+ # Text only
52
+ print(count(model, f"{repo}/img/336.jpg", "strawberry")["count"])
53
+
54
+ # Positive text + negative text
55
+ print(count(model, f"{repo}/img/336.jpg", "strawberry", negative_texts=["blueberry"])["count"])
56
+
57
+ # Text + visual exemplars drawn on another image, with a negative exemplar
58
+ result = count(
59
+ model, f"{repo}/img/336.jpg", "strawberry",
60
+ exemplar_boxes=[[x1, y1, x2, y2]], # pixels, on exemplar_image
61
+ exemplar_image=f"{repo}/img/synthetic_strawberry.jpg",
62
+ negative_texts=["blueberry"],
63
+ negative_exemplar_boxes=[[[x1, y1, x2, y2]]], # one list per negative text
64
+ negative_exemplar_images=[f"{repo}/img/synthetic_blueberry.jpg"],
65
+ )
66
+ print(result["count"], result["boxes"]) # boxes: normalized (cx, cy, w, h)
67
+ ```
68
+
69
+ `example.py` is a runnable version that also saves a picture of the detections.
70
+
71
+ Notes:
72
+ - The text encoder (`bert-base-uncased`) is fetched from the Hugging Face Hub
73
+ by `transformers` the first time the model loads.
74
+ - The compiled multi-scale deformable attention kernels are optional. Without
75
+ them the model uses an equivalent pure-PyTorch implementation on CPU or GPU.
76
+ To compile the kernels, see the GitHub repository.
77
+ - Pseudo-exemplars and adaptive cropping used for some benchmark results are not
78
+ part of `count()`. See the test scripts in the GitHub repository.
79
+
80
+ ## Architecture
81
+
82
+ ![architecture](img/inference-architecture.jpg)
83
+
84
+ ## Citation
85
+
86
+ ```bibtex
87
+ @InProceedings{AminiNaieni26b,
88
+ title={CountGD++: Generalized Prompting for Open-World Counting},
89
+ author={Amini-Naieni, N. and Zisserman, A.},
90
+ booktitle = {Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR)},
91
+ year={2026}
92
+ }
93
+ ```
cfg_app.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ data_aug_scales = [480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800]
2
+ data_aug_max_size = 1333
3
+ data_aug_scales2_resize = [400, 500, 600]
4
+ data_aug_scales2_crop = [384, 600]
5
+ data_aug_scale_overlap = None
6
+ batch_size = 4
7
+ modelname = 'groundingdino'
8
+ backbone = "swin_B_384_22k"
9
+ position_embedding = 'sine'
10
+ pe_temperatureH = 20
11
+ pe_temperatureW = 20
12
+ return_interm_indices = [1, 2, 3]
13
+ enc_layers = 6
14
+ dec_layers = 6
15
+ pre_norm = False
16
+ dim_feedforward = 2048
17
+ hidden_dim = 256
18
+ dropout = 0.0
19
+ nheads = 8
20
+ num_queries = 900
21
+ query_dim = 4
22
+ num_patterns = 0
23
+ num_feature_levels = 4
24
+ enc_n_points = 4
25
+ dec_n_points = 4
26
+ two_stage_type = 'standard'
27
+ two_stage_bbox_embed_share = False
28
+ two_stage_class_embed_share = False
29
+ transformer_activation = 'relu'
30
+ dec_pred_bbox_embed_share = True
31
+ dn_box_noise_scale = 1.0
32
+ dn_label_noise_ratio = 0.5
33
+ dn_label_coef = 1.0
34
+ dn_bbox_coef = 1.0
35
+ embed_init_tgt = True
36
+ dn_labelbook_size = 91
37
+ max_text_len = 256
38
+ text_encoder_type = "bert-base-uncased"
39
+ use_text_enhancer = True
40
+ use_fusion_layer = True
41
+ use_checkpoint = False
42
+ use_transformer_ckpt = False
43
+ use_text_cross_attention = True
44
+ text_dropout = 0.0
45
+ fusion_dropout = 0.0
46
+ fusion_droppath = 0.1
47
+ sub_sentence_present = True
48
+ max_labels = 90 # pos + neg
49
+ lr = 0.0001 # base learning rate
50
+ backbone_freeze_keywords = None # only for gdino backbone
51
+ freeze_keywords = ['backbone.0', 'bert'] # for whole model, e.g. ['backbone.0', 'bert'] for freeze visual encoder and text encoder
52
+ lr_backbone = 1e-05 # specific learning rate
53
+ lr_backbone_names = ['backbone.0', 'bert']
54
+ lr_linear_proj_mult = 1e-05
55
+ lr_linear_proj_names = ['ref_point_head', 'sampling_offsets']
56
+ weight_decay = 0.0001
57
+ param_dict_type = 'ddetr_in_mmdet'
58
+ ddetr_lr_param = False
59
+ epochs = 30
60
+ lr_drop = 10
61
+ save_checkpoint_interval = 10
62
+ clip_max_norm = 0.1
63
+ onecyclelr = False
64
+ multi_step_lr = False
65
+ lr_drop_list = [10, 20]
66
+ frozen_weights = None
67
+ dilation = False
68
+ pdetr3_bbox_embed_diff_each_layer = False
69
+ pdetr3_refHW = -1
70
+ random_refpoints_xy = False
71
+ fix_refpoints_hw = -1
72
+ dabdetr_yolo_like_anchor_update = False
73
+ dabdetr_deformable_encoder = False
74
+ dabdetr_deformable_decoder = False
75
+ use_deformable_box_attn = False
76
+ box_attn_type = 'roi_align'
77
+ dec_layer_number = None
78
+ decoder_layer_noise = False
79
+ dln_xy_noise = 0.2
80
+ dln_hw_noise = 0.2
81
+ add_channel_attention = False
82
+ add_pos_value = False
83
+ two_stage_pat_embed = 0
84
+ two_stage_add_query_num = 0
85
+ two_stage_learn_wh = False
86
+ two_stage_default_hw = 0.05
87
+ two_stage_keep_all_tokens = False
88
+ num_select = 900
89
+ batch_norm_type = 'FrozenBatchNorm2d'
90
+ masks = False
91
+ aux_loss = True
92
+ set_cost_class = 5.0
93
+ set_cost_bbox = 1.0
94
+ set_cost_giou = 0.0
95
+ cls_loss_coef = 5.0
96
+ bbox_loss_coef = 1.0
97
+ giou_loss_coef = 0.0
98
+ enc_loss_coef = 1.0
99
+ interm_loss_coef = 1.0
100
+ no_interm_box_loss = False
101
+ mask_loss_coef = 1.0
102
+ dice_loss_coef = 1.0
103
+ focal_alpha = 0.25
104
+ focal_gamma = 2.0
105
+ decoder_sa_type = 'sa'
106
+ matcher_type = 'HungarianMatcher'
107
+ decoder_module_seq = ['sa', 'ca', 'ffn']
108
+ nms_iou_threshold = -1
109
+ dec_pred_class_embed_share = True
110
+ match_unstable_error = True
111
+ use_detached_boxes_dec_out = False
112
+ dn_scalar = 100
113
+
114
+ box_threshold = 0.23
115
+ text_threshold = 0
116
+ use_coco_eval = False
117
+ label_list = ['alcohol bottle', 'baguette roll', 'ball', 'banana', 'bead', 'bee', 'birthday candle', 'biscuit', 'boat', 'bottle', 'bowl', 'box', 'bread roll', 'brick', 'buffalo', 'bun', 'calamari ring', 'can', 'candle', 'cap', 'car', 'cartridge', 'cassette', 'cement bag', 'cereal', 'chewing gum piece', 'chopstick', 'clam', 'coffee bean', 'coin', 'cotton ball', 'cow', 'crane', 'crayon', 'croissant', 'crow', 'cup', 'cupcake', 'cupcake holder', 'fish', 'gemstone', 'go game piece', 'goat', 'goldfish snack', 'goose', 'ice cream', 'ice cream cone', 'instant noodle', 'jade stone', 'jeans', 'kidney bean', 'kitchen towel', 'lighter', 'lipstick', 'm&m piece', 'macaron', 'match', 'meat skewer', 'mini blind', 'mosaic tile', 'naan bread', 'nail', 'nut', 'onion ring', 'orange', 'pearl', 'pen', 'pencil', 'penguin', 'pepper', 'person', 'pigeon', 'plate', 'polka dot tile', 'potato', 'rice bag', 'roof tile', 'screw', 'shoe', 'spoon', 'spring roll', 'stair', 'stapler pin', 'straw', 'supermarket shelf', 'swan', 'tomato', 'watermelon', 'window', 'zebra']
118
+ val_label_list = ["apple", "candy piece", "carrom board piece", "cashew nut", "comic book", "crab cake", "deer", "egg", "elephant", "finger food", "green pea", "hot air balloon", "keyboard key", "lego", "marble", "marker", "nail polish", "potato chip", "red bean", "round dessert", "sauce bottle", "sea shell", "sheep", "ski", "stamp", "sticky note", "strawberry", "sunglasses", "tree log", "watch"]
config.json ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_channel_attention": false,
3
+ "add_pos_value": false,
4
+ "aux_loss": true,
5
+ "backbone": "swin_B_384_22k",
6
+ "backbone_freeze_keywords": null,
7
+ "batch_norm_type": "FrozenBatchNorm2d",
8
+ "batch_size": 4,
9
+ "bbox_loss_coef": 1.0,
10
+ "box_attn_type": "roi_align",
11
+ "box_threshold": 0.23,
12
+ "clip_max_norm": 0.1,
13
+ "cls_loss_coef": 5.0,
14
+ "dabdetr_deformable_decoder": false,
15
+ "dabdetr_deformable_encoder": false,
16
+ "dabdetr_yolo_like_anchor_update": false,
17
+ "data_aug_max_size": 1333,
18
+ "data_aug_scale_overlap": null,
19
+ "data_aug_scales": [
20
+ 480,
21
+ 512,
22
+ 544,
23
+ 576,
24
+ 608,
25
+ 640,
26
+ 672,
27
+ 704,
28
+ 736,
29
+ 768,
30
+ 800
31
+ ],
32
+ "data_aug_scales2_crop": [
33
+ 384,
34
+ 600
35
+ ],
36
+ "data_aug_scales2_resize": [
37
+ 400,
38
+ 500,
39
+ 600
40
+ ],
41
+ "ddetr_lr_param": false,
42
+ "dec_layer_number": null,
43
+ "dec_layers": 6,
44
+ "dec_n_points": 4,
45
+ "dec_pred_bbox_embed_share": true,
46
+ "dec_pred_class_embed_share": true,
47
+ "decoder_layer_noise": false,
48
+ "decoder_module_seq": [
49
+ "sa",
50
+ "ca",
51
+ "ffn"
52
+ ],
53
+ "decoder_sa_type": "sa",
54
+ "device": "cpu",
55
+ "dice_loss_coef": 1.0,
56
+ "dilation": false,
57
+ "dim_feedforward": 2048,
58
+ "dln_hw_noise": 0.2,
59
+ "dln_xy_noise": 0.2,
60
+ "dn_bbox_coef": 1.0,
61
+ "dn_box_noise_scale": 1.0,
62
+ "dn_label_coef": 1.0,
63
+ "dn_label_noise_ratio": 0.5,
64
+ "dn_labelbook_size": 91,
65
+ "dn_scalar": 100,
66
+ "dropout": 0.0,
67
+ "embed_init_tgt": true,
68
+ "enc_layers": 6,
69
+ "enc_loss_coef": 1.0,
70
+ "enc_n_points": 4,
71
+ "epochs": 30,
72
+ "fix_refpoints_hw": -1,
73
+ "focal_alpha": 0.25,
74
+ "focal_gamma": 2.0,
75
+ "freeze_keywords": [
76
+ "backbone.0",
77
+ "bert"
78
+ ],
79
+ "frozen_weights": null,
80
+ "fusion_dropout": 0.0,
81
+ "fusion_droppath": 0.1,
82
+ "giou_loss_coef": 0.0,
83
+ "hidden_dim": 256,
84
+ "interm_loss_coef": 1.0,
85
+ "lr": 0.0001,
86
+ "lr_backbone": 1e-05,
87
+ "lr_backbone_names": [
88
+ "backbone.0",
89
+ "bert"
90
+ ],
91
+ "lr_drop": 10,
92
+ "lr_drop_list": [
93
+ 10,
94
+ 20
95
+ ],
96
+ "lr_linear_proj_mult": 1e-05,
97
+ "lr_linear_proj_names": [
98
+ "ref_point_head",
99
+ "sampling_offsets"
100
+ ],
101
+ "mask_loss_coef": 1.0,
102
+ "masks": false,
103
+ "match_unstable_error": true,
104
+ "matcher_type": "HungarianMatcher",
105
+ "max_labels": 90,
106
+ "max_text_len": 256,
107
+ "modelname": "groundingdino",
108
+ "multi_step_lr": false,
109
+ "nheads": 8,
110
+ "nms_iou_threshold": -1,
111
+ "no_interm_box_loss": false,
112
+ "num_feature_levels": 4,
113
+ "num_patterns": 0,
114
+ "num_queries": 900,
115
+ "num_select": 900,
116
+ "onecyclelr": false,
117
+ "param_dict_type": "ddetr_in_mmdet",
118
+ "pdetr3_bbox_embed_diff_each_layer": false,
119
+ "pdetr3_refHW": -1,
120
+ "pe_temperatureH": 20,
121
+ "pe_temperatureW": 20,
122
+ "position_embedding": "sine",
123
+ "pre_norm": false,
124
+ "query_dim": 4,
125
+ "random_refpoints_xy": false,
126
+ "return_interm_indices": [
127
+ 1,
128
+ 2,
129
+ 3
130
+ ],
131
+ "save_checkpoint_interval": 10,
132
+ "set_cost_bbox": 1.0,
133
+ "set_cost_class": 5.0,
134
+ "set_cost_giou": 0.0,
135
+ "sub_sentence_present": true,
136
+ "text_dropout": 0.0,
137
+ "text_encoder_type": "bert-base-uncased",
138
+ "text_threshold": 0,
139
+ "transformer_activation": "relu",
140
+ "two_stage_add_query_num": 0,
141
+ "two_stage_bbox_embed_share": false,
142
+ "two_stage_class_embed_share": false,
143
+ "two_stage_default_hw": 0.05,
144
+ "two_stage_keep_all_tokens": false,
145
+ "two_stage_learn_wh": false,
146
+ "two_stage_pat_embed": 0,
147
+ "two_stage_type": "standard",
148
+ "use_checkpoint": false,
149
+ "use_coco_eval": false,
150
+ "use_deformable_box_attn": false,
151
+ "use_detached_boxes_dec_out": false,
152
+ "use_fusion_layer": true,
153
+ "use_text_cross_attention": true,
154
+ "use_text_enhancer": true,
155
+ "use_transformer_ckpt": false,
156
+ "weight_decay": 0.0001
157
+ }
countgdpp.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CountGD++ on the Hugging Face Hub.
2
+
3
+ from countgdpp import CountGDPlusPlus, count
4
+ model = CountGDPlusPlus.from_pretrained("nikigoli/CountGDPlusPlus").to("cuda").eval()
5
+ result = count(model, "img/336.jpg", "strawberry", negative_texts=["blueberry"])
6
+ print(result["count"])
7
+
8
+ `CountGDPlusPlus` is the CountGD++ GroundingDINO model with Hugging Face's
9
+ PyTorchModelHubMixin added, so `from_pretrained`, `save_pretrained` and
10
+ `push_to_hub` work. Its state dict keys are identical to the original
11
+ `countgd_plusplus.pth` checkpoint.
12
+ """
13
+ import os
14
+ import sys
15
+ from types import SimpleNamespace
16
+ from typing import List, Optional, Sequence, Union
17
+
18
+ import torch
19
+ from PIL import Image
20
+ from huggingface_hub import PyTorchModelHubMixin
21
+
22
+ _HERE = os.path.dirname(os.path.abspath(__file__))
23
+ if _HERE not in sys.path:
24
+ sys.path.insert(0, _HERE)
25
+
26
+ from util.slconfig import SLConfig # noqa: E402
27
+ from util.misc import nested_tensor_from_tensor_list # noqa: E402
28
+ import datasets.transforms_app as T # noqa: E402
29
+ from models.GroundingDINO.groundingdino_app import GroundingDINO # noqa: E402
30
+ from models.GroundingDINO.backbone import build_backbone # noqa: E402
31
+ from models.GroundingDINO.transformer import build_transformer # noqa: E402
32
+
33
+ # Default score threshold used in the paper's demo.
34
+ CONF_THRESH = 0.23
35
+ # BERT token id of "." — separates the positive prompt from negative prompts in the caption.
36
+ _DOT_TOKEN_ID = 1012
37
+ _DROP_KEYS = {"label_list", "val_label_list"}
38
+
39
+
40
+ def default_config() -> dict:
41
+ """Model configuration read from cfg_app.py (the config used by the CountGD++ demo)."""
42
+ cfg = SLConfig.fromfile(os.path.join(_HERE, "cfg_app.py"))._cfg_dict.to_dict()
43
+ cfg = {k: v for k, v in cfg.items() if k not in _DROP_KEYS}
44
+ cfg["text_encoder_type"] = "bert-base-uncased" # fetched from the Hub by transformers
45
+ cfg["device"] = "cpu"
46
+ return cfg
47
+
48
+
49
+ class CountGDPlusPlus(
50
+ GroundingDINO,
51
+ PyTorchModelHubMixin,
52
+ library_name="pytorch",
53
+ repo_url="https://github.com/niki-amini-naieni/CountGDPlusPlus",
54
+ license="mit",
55
+ tags=["CountGD++", "computer-vision", "counting", "grounding-dino", "multi-modal", "open-vocabulary"],
56
+ ):
57
+ """CountGD++ model. Build with `CountGDPlusPlus.from_pretrained(repo_id)`."""
58
+
59
+ def __init__(self, config: Optional[dict] = None):
60
+ config = dict(config or default_config())
61
+ args = SimpleNamespace(**config)
62
+ backbone = build_backbone(args)
63
+ transformer = build_transformer(args)
64
+ # Same arguments as build_groundingdino() in groundingdino_app.py
65
+ super().__init__(
66
+ backbone,
67
+ transformer,
68
+ num_queries=args.num_queries,
69
+ aux_loss=args.aux_loss,
70
+ iter_update=True,
71
+ query_dim=4,
72
+ num_feature_levels=args.num_feature_levels,
73
+ nheads=args.nheads,
74
+ dec_pred_bbox_embed_share=args.dec_pred_bbox_embed_share,
75
+ two_stage_type=args.two_stage_type,
76
+ two_stage_bbox_embed_share=args.two_stage_bbox_embed_share,
77
+ two_stage_class_embed_share=args.two_stage_class_embed_share,
78
+ num_patterns=args.num_patterns,
79
+ dn_number=0,
80
+ dn_box_noise_scale=args.dn_box_noise_scale,
81
+ dn_label_noise_ratio=args.dn_label_noise_ratio,
82
+ dn_labelbook_size=args.dn_labelbook_size,
83
+ text_encoder_type=args.text_encoder_type,
84
+ sub_sentence_present=args.sub_sentence_present,
85
+ max_text_len=args.max_text_len,
86
+ )
87
+ self.countgd_config = config
88
+
89
+
90
+ # ----------------------------------------------------------------------------
91
+ # Inference (same pre/post-processing as the CountGD++ demo app)
92
+ # ----------------------------------------------------------------------------
93
+ def build_transform():
94
+ normalize = T.Compose([T.ToTensor(), T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
95
+ return T.Compose([T.RandomResize([800], max_size=1333), normalize])
96
+
97
+
98
+ def _load(image: Union[str, Image.Image]) -> Image.Image:
99
+ return (Image.open(image) if isinstance(image, str) else image).convert("RGB")
100
+
101
+
102
+ def _prep_exemplars(transform, image, boxes):
103
+ boxes = torch.tensor(boxes, dtype=torch.float32) if boxes else torch.tensor([])
104
+ img, target = transform(image, {"exemplars": boxes})
105
+ return img, target["exemplars"]
106
+
107
+
108
+ @torch.no_grad()
109
+ def count(
110
+ model: CountGDPlusPlus,
111
+ image: Union[str, Image.Image],
112
+ text: str,
113
+ exemplar_boxes: Optional[Sequence[Sequence[float]]] = None,
114
+ exemplar_image: Optional[Union[str, Image.Image]] = None,
115
+ negative_texts: Optional[List[str]] = None,
116
+ negative_exemplar_boxes: Optional[List[Sequence[Sequence[float]]]] = None,
117
+ negative_exemplar_images: Optional[List[Union[str, Image.Image]]] = None,
118
+ conf_thresh: float = CONF_THRESH,
119
+ ):
120
+ """Count objects in `image`.
121
+
122
+ text: what to count, e.g. "strawberry".
123
+ exemplar_boxes: optional visual exemplars [[x1, y1, x2, y2], ...] in pixels,
124
+ drawn on `exemplar_image` (defaults to `image`).
125
+ negative_texts: optional things NOT to count, e.g. ["blueberry"].
126
+ negative_exemplar_boxes / negative_exemplar_images: optional, one entry per
127
+ negative text, same format as the positive ones.
128
+
129
+ Returns dict with "count", "boxes" (normalized cx, cy, w, h) and "scores".
130
+ """
131
+ device = next(model.parameters()).device
132
+ transform = build_transform()
133
+ image = _load(image)
134
+ pos_img = _load(exemplar_image) if exemplar_image is not None else image
135
+ negative_texts = list(negative_texts or [])
136
+ n_neg = len(negative_texts)
137
+ neg_boxes = list(negative_exemplar_boxes or [None] * n_neg)
138
+ neg_imgs = list(negative_exemplar_images or [None] * n_neg)
139
+ if len(neg_boxes) != n_neg or len(neg_imgs) != n_neg:
140
+ raise ValueError("Give one negative exemplar entry (or None) per negative text.")
141
+
142
+ input_image, _ = transform(image, None)
143
+ pos_exemplar_image, pos_exemplars = _prep_exemplars(transform, pos_img, exemplar_boxes)
144
+
145
+ neg_images, neg_exemplars = [], []
146
+ for boxes, img in zip(neg_boxes, neg_imgs):
147
+ img = _load(img) if img is not None else image
148
+ ex_img, ex = _prep_exemplars(transform, img, boxes)
149
+ neg_images.append(nested_tensor_from_tensor_list(ex_img.unsqueeze(0).to(device)))
150
+ neg_exemplars.append([ex.to(device)])
151
+
152
+ caption = text + " . " + "".join(t + " . " for t in negative_texts)
153
+ out = model(
154
+ nested_tensor_from_tensor_list(input_image.unsqueeze(0).to(device)),
155
+ nested_tensor_from_tensor_list(pos_exemplar_image.unsqueeze(0).to(device)),
156
+ [pos_exemplars.to(device)],
157
+ neg_images,
158
+ neg_exemplars,
159
+ captions=[caption],
160
+ )
161
+
162
+ input_ids = out["input_ids"][0]
163
+ logits = out["pred_logits"].sigmoid()[0]
164
+ boxes = out["pred_boxes"][0]
165
+ split = next(i for i, t in enumerate(input_ids) if int(t) == _DOT_TOKEN_ID)
166
+ pos_logits, neg_logits = logits[:, : split + 1], logits[:, split + 1 :]
167
+
168
+ # Stage 1: keep queries confident about the positive prompt.
169
+ keep = pos_logits.max(dim=-1).values > conf_thresh
170
+ boxes, scores = boxes[keep], logits[keep].max(dim=-1).values
171
+ pos_logits, neg_logits = pos_logits[keep], neg_logits[keep]
172
+ # Stage 2: drop queries that match a negative prompt better than the positive one.
173
+ if neg_logits.shape[-1] > 0:
174
+ keep = pos_logits.max(dim=-1).values > neg_logits.max(dim=-1).values
175
+ boxes, scores = boxes[keep], scores[keep]
176
+
177
+ return {"count": int(boxes.shape[0]), "boxes": boxes.cpu().numpy(), "scores": scores.cpu().numpy()}
datasets/__init__.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ import torch.utils.data
3
+ import torchvision
4
+ from .coco import build as build_coco
5
+
6
+
7
+ def get_coco_api_from_dataset(dataset):
8
+ for _ in range(10):
9
+ # if isinstance(dataset, torchvision.datasets.CocoDetection):
10
+ # break
11
+ if isinstance(dataset, torch.utils.data.Subset):
12
+ dataset = dataset.dataset
13
+ if isinstance(dataset, torchvision.datasets.CocoDetection):
14
+ return dataset.coco
15
+
16
+
17
+ def build_dataset(image_set, args, datasetinfo):
18
+ if datasetinfo["dataset_mode"] == 'coco':
19
+ return build_coco(image_set, args, datasetinfo)
20
+ if datasetinfo["dataset_mode"] == 'odvg':
21
+ from .odvg import build_odvg
22
+ return build_odvg(image_set, args, datasetinfo)
23
+ raise ValueError(f'dataset {args.dataset_file} not supported')
datasets/coco.py ADDED
@@ -0,0 +1,660 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ """
3
+ COCO dataset which returns image_id for evaluation.
4
+
5
+ Mostly copy-paste from https://github.com/pytorch/vision/blob/13b35ff/references/detection/coco_utils.py
6
+ """
7
+ if __name__=="__main__":
8
+ # for debug only
9
+ import os, sys
10
+ sys.path.append(os.path.dirname(sys.path[0]))
11
+ from torchvision.datasets.vision import VisionDataset
12
+
13
+ import json
14
+ from pathlib import Path
15
+ import random
16
+ import os
17
+ from typing import Any, Callable, List, Optional, Tuple
18
+
19
+ from PIL import Image
20
+
21
+ import torch
22
+ import torch.utils.data
23
+ import torchvision
24
+ from pycocotools import mask as coco_mask
25
+
26
+ from datasets.data_util import preparing_dataset
27
+ import datasets.transforms_orig as T
28
+ from util.box_ops import box_cxcywh_to_xyxy, box_iou
29
+
30
+ __all__ = ['build']
31
+
32
+
33
+ class label2compat():
34
+ def __init__(self) -> None:
35
+ self.category_map_str = {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "10": 10, "11": 11, "13": 12, "14": 13, "15": 14, "16": 15, "17": 16, "18": 17, "19": 18, "20": 19, "21": 20, "22": 21, "23": 22, "24": 23, "25": 24, "27": 25, "28": 26, "31": 27, "32": 28, "33": 29, "34": 30, "35": 31, "36": 32, "37": 33, "38": 34, "39": 35, "40": 36, "41": 37, "42": 38, "43": 39, "44": 40, "46": 41, "47": 42, "48": 43, "49": 44, "50": 45, "51": 46, "52": 47, "53": 48, "54": 49, "55": 50, "56": 51, "57": 52, "58": 53, "59": 54, "60": 55, "61": 56, "62": 57, "63": 58, "64": 59, "65": 60, "67": 61, "70": 62, "72": 63, "73": 64, "74": 65, "75": 66, "76": 67, "77": 68, "78": 69, "79": 70, "80": 71, "81": 72, "82": 73, "84": 74, "85": 75, "86": 76, "87": 77, "88": 78, "89": 79, "90": 80}
36
+ self.category_map = {int(k):v for k,v in self.category_map_str.items()}
37
+
38
+ def __call__(self, target, img=None):
39
+ labels = target['labels']
40
+ res = torch.zeros(labels.shape, dtype=labels.dtype)
41
+ for idx, item in enumerate(labels):
42
+ res[idx] = self.category_map[item.item()] - 1
43
+ target['label_compat'] = res
44
+ if img is not None:
45
+ return target, img
46
+ else:
47
+ return target
48
+
49
+
50
+ class label_compat2onehot():
51
+ def __init__(self, num_class=80, num_output_objs=1):
52
+ self.num_class = num_class
53
+ self.num_output_objs = num_output_objs
54
+ if num_output_objs != 1:
55
+ raise DeprecationWarning("num_output_objs!=1, which is only used for comparison")
56
+
57
+ def __call__(self, target, img=None):
58
+ labels = target['label_compat']
59
+ place_dict = {k:0 for k in range(self.num_class)}
60
+ if self.num_output_objs == 1:
61
+ res = torch.zeros(self.num_class)
62
+ for i in labels:
63
+ itm = i.item()
64
+ res[itm] = 1.0
65
+ else:
66
+ # compat with baseline
67
+ res = torch.zeros(self.num_class, self.num_output_objs)
68
+ for i in labels:
69
+ itm = i.item()
70
+ res[itm][place_dict[itm]] = 1.0
71
+ place_dict[itm] += 1
72
+ target['label_compat_onehot'] = res
73
+ if img is not None:
74
+ return target, img
75
+ else:
76
+ return target
77
+
78
+
79
+ class box_label_catter():
80
+ def __init__(self):
81
+ pass
82
+
83
+ def __call__(self, target, img=None):
84
+ labels = target['label_compat']
85
+ boxes = target['boxes']
86
+ box_label = torch.cat((boxes, labels.unsqueeze(-1)), 1)
87
+ target['box_label'] = box_label
88
+ if img is not None:
89
+ return target, img
90
+ else:
91
+ return target
92
+
93
+
94
+ class RandomSelectBoxlabels():
95
+ def __init__(self, num_classes, leave_one_out=False, blank_prob=0.8,
96
+ prob_first_item = 0.0,
97
+ prob_random_item = 0.0,
98
+ prob_last_item = 0.8,
99
+ prob_stop_sign = 0.2
100
+ ) -> None:
101
+ self.num_classes = num_classes
102
+ self.leave_one_out = leave_one_out
103
+ self.blank_prob = blank_prob
104
+
105
+ self.set_state(prob_first_item, prob_random_item, prob_last_item, prob_stop_sign)
106
+
107
+
108
+ def get_state(self):
109
+ return [self.prob_first_item, self.prob_random_item, self.prob_last_item, self.prob_stop_sign]
110
+
111
+ def set_state(self, prob_first_item, prob_random_item, prob_last_item, prob_stop_sign):
112
+ sum_prob = prob_first_item + prob_random_item + prob_last_item + prob_stop_sign
113
+ assert sum_prob - 1 < 1e-6, \
114
+ f"Sum up all prob = {sum_prob}. prob_first_item:{prob_first_item}" \
115
+ + f"prob_random_item:{prob_random_item}, prob_last_item:{prob_last_item}" \
116
+ + f"prob_stop_sign:{prob_stop_sign}"
117
+
118
+ self.prob_first_item = prob_first_item
119
+ self.prob_random_item = prob_random_item
120
+ self.prob_last_item = prob_last_item
121
+ self.prob_stop_sign = prob_stop_sign
122
+
123
+
124
+ def sample_for_pred_first_item(self, box_label: torch.FloatTensor):
125
+ box_label_known = torch.Tensor(0,5)
126
+ box_label_unknown = box_label
127
+ return box_label_known, box_label_unknown
128
+
129
+ def sample_for_pred_random_item(self, box_label: torch.FloatTensor):
130
+ n_select = int(random.random() * box_label.shape[0])
131
+ box_label = box_label[torch.randperm(box_label.shape[0])]
132
+ box_label_known = box_label[:n_select]
133
+ box_label_unknown = box_label[n_select:]
134
+ return box_label_known, box_label_unknown
135
+
136
+ def sample_for_pred_last_item(self, box_label: torch.FloatTensor):
137
+ box_label_perm = box_label[torch.randperm(box_label.shape[0])]
138
+ known_label_list = []
139
+ box_label_known = []
140
+ box_label_unknown = []
141
+ for item in box_label_perm:
142
+ label_i = item[4].item()
143
+ if label_i in known_label_list:
144
+ box_label_known.append(item)
145
+ else:
146
+ # first item
147
+ box_label_unknown.append(item)
148
+ known_label_list.append(label_i)
149
+ box_label_known = torch.stack(box_label_known) if len(box_label_known) > 0 else torch.Tensor(0,5)
150
+ box_label_unknown = torch.stack(box_label_unknown) if len(box_label_unknown) > 0 else torch.Tensor(0,5)
151
+ return box_label_known, box_label_unknown
152
+
153
+ def sample_for_pred_stop_sign(self, box_label: torch.FloatTensor):
154
+ box_label_unknown = torch.Tensor(0,5)
155
+ box_label_known = box_label
156
+ return box_label_known, box_label_unknown
157
+
158
+ def __call__(self, target, img=None):
159
+ box_label = target['box_label'] # K, 5
160
+
161
+ dice_number = random.random()
162
+
163
+ if dice_number < self.prob_first_item:
164
+ box_label_known, box_label_unknown = self.sample_for_pred_first_item(box_label)
165
+ elif dice_number < self.prob_first_item + self.prob_random_item:
166
+ box_label_known, box_label_unknown = self.sample_for_pred_random_item(box_label)
167
+ elif dice_number < self.prob_first_item + self.prob_random_item + self.prob_last_item:
168
+ box_label_known, box_label_unknown = self.sample_for_pred_last_item(box_label)
169
+ else:
170
+ box_label_known, box_label_unknown = self.sample_for_pred_stop_sign(box_label)
171
+
172
+ target['label_onehot_known'] = label2onehot(box_label_known[:,-1], self.num_classes)
173
+ target['label_onehot_unknown'] = label2onehot(box_label_unknown[:, -1], self.num_classes)
174
+ target['box_label_known'] = box_label_known
175
+ target['box_label_unknown'] = box_label_unknown
176
+
177
+ return target, img
178
+
179
+
180
+ class RandomDrop():
181
+ def __init__(self, p=0.2) -> None:
182
+ self.p = p
183
+
184
+ def __call__(self, target, img=None):
185
+ known_box = target['box_label_known']
186
+ num_known_box = known_box.size(0)
187
+ idxs = torch.rand(num_known_box)
188
+ # indices = torch.randperm(num_known_box)[:int((1-self).p*num_known_box + 0.5 + random.random())]
189
+ target['box_label_known'] = known_box[idxs > self.p]
190
+ return target, img
191
+
192
+
193
+ class BboxPertuber():
194
+ def __init__(self, max_ratio = 0.02, generate_samples = 1000) -> None:
195
+ self.max_ratio = max_ratio
196
+ self.generate_samples = generate_samples
197
+ self.samples = self.generate_pertube_samples()
198
+ self.idx = 0
199
+
200
+ def generate_pertube_samples(self):
201
+ import torch
202
+ samples = (torch.rand(self.generate_samples, 5) - 0.5) * 2 * self.max_ratio
203
+ return samples
204
+
205
+ def __call__(self, target, img):
206
+ known_box = target['box_label_known'] # Tensor(K,5), K known bbox
207
+ K = known_box.shape[0]
208
+ known_box_pertube = torch.zeros(K, 6) # 4:bbox, 1:prob, 1:label
209
+ if K == 0:
210
+ pass
211
+ else:
212
+ if self.idx + K > self.generate_samples:
213
+ self.idx = 0
214
+ delta = self.samples[self.idx: self.idx + K, :]
215
+ known_box_pertube[:, :4] = known_box[:, :4] + delta[:, :4]
216
+ iou = (torch.diag(box_iou(box_cxcywh_to_xyxy(known_box[:, :4]), box_cxcywh_to_xyxy(known_box_pertube[:, :4]))[0])) * (1 + delta[:, -1])
217
+ known_box_pertube[:, 4].copy_(iou)
218
+ known_box_pertube[:, -1].copy_(known_box[:, -1])
219
+
220
+ target['box_label_known_pertube'] = known_box_pertube
221
+ return target, img
222
+
223
+
224
+ class RandomCutout():
225
+ def __init__(self, factor=0.5) -> None:
226
+ self.factor = factor
227
+
228
+ def __call__(self, target, img=None):
229
+ unknown_box = target['box_label_unknown'] # Ku, 5
230
+ known_box = target['box_label_known_pertube'] # Kk, 6
231
+ Ku = unknown_box.size(0)
232
+
233
+ known_box_add = torch.zeros(Ku, 6) # Ku, 6
234
+ known_box_add[:, :5] = unknown_box
235
+ known_box_add[:, 5].uniform_(0.5, 1)
236
+
237
+
238
+ known_box_add[:, :2] += known_box_add[:, 2:4] * (torch.rand(Ku, 2) - 0.5) / 2
239
+ known_box_add[:, 2:4] /= 2
240
+
241
+ target['box_label_known_pertube'] = torch.cat((known_box, known_box_add))
242
+ return target, img
243
+
244
+
245
+ class RandomSelectBoxes():
246
+ def __init__(self, num_class=80) -> None:
247
+ Warning("This is such a slow function and will be deprecated soon!!!")
248
+ self.num_class = num_class
249
+
250
+ def __call__(self, target, img=None):
251
+ boxes = target['boxes']
252
+ labels = target['label_compat']
253
+
254
+ # transform to list of tensors
255
+ boxs_list = [[] for i in range(self.num_class)]
256
+ for idx, item in enumerate(boxes):
257
+ label = labels[idx].item()
258
+ boxs_list[label].append(item)
259
+ boxs_list_tensor = [torch.stack(i) if len(i) > 0 else torch.Tensor(0,4) for i in boxs_list]
260
+
261
+ # random selection
262
+ box_known = []
263
+ box_unknown = []
264
+ for idx, item in enumerate(boxs_list_tensor):
265
+ ncnt = item.shape[0]
266
+ nselect = int(random.random() * ncnt) # close in both sides, much faster than random.randint
267
+
268
+ item = item[torch.randperm(ncnt)]
269
+ # random.shuffle(item)
270
+ box_known.append(item[:nselect])
271
+ box_unknown.append(item[nselect:])
272
+
273
+ # box_known_tensor = [torch.stack(i) if len(i) > 0 else torch.Tensor(0,4) for i in box_known]
274
+ # box_unknown_tensor = [torch.stack(i) if len(i) > 0 else torch.Tensor(0,4) for i in box_unknown]
275
+ # print('box_unknown_tensor:', box_unknown_tensor)
276
+ target['known_box'] = box_known
277
+ target['unknown_box'] = box_unknown
278
+ return target, img
279
+
280
+
281
+ def label2onehot(label, num_classes):
282
+ """
283
+ label: Tensor(K)
284
+ """
285
+ res = torch.zeros(num_classes)
286
+ for i in label:
287
+ itm = int(i.item())
288
+ res[itm] = 1.0
289
+ return res
290
+
291
+
292
+ class MaskCrop():
293
+ def __init__(self) -> None:
294
+ pass
295
+
296
+ def __call__(self, target, img):
297
+ known_box = target['known_box']
298
+ h,w = img.shape[1:] # h,w
299
+ # imgsize = target['orig_size'] # h,w
300
+
301
+ scale = torch.Tensor([w, h, w, h])
302
+
303
+ # _cnt = 0
304
+ for boxes in known_box:
305
+ if boxes.shape[0] == 0:
306
+ continue
307
+ box_xyxy = box_cxcywh_to_xyxy(boxes) * scale
308
+ for box in box_xyxy:
309
+ x1, y1, x2, y2 = [int(i) for i in box.tolist()]
310
+ img[:, y1:y2, x1:x2] = 0
311
+ # _cnt += 1
312
+ # print("_cnt:", _cnt)
313
+ return target, img
314
+
315
+
316
+ dataset_hook_register = {
317
+ 'label2compat': label2compat,
318
+ 'label_compat2onehot': label_compat2onehot,
319
+ 'box_label_catter': box_label_catter,
320
+ 'RandomSelectBoxlabels': RandomSelectBoxlabels,
321
+ 'RandomSelectBoxes': RandomSelectBoxes,
322
+ 'MaskCrop': MaskCrop,
323
+ 'BboxPertuber': BboxPertuber,
324
+ }
325
+
326
+
327
+ class CocoDetection(torchvision.datasets.CocoDetection):
328
+ def __init__(self, img_folder, ann_file, transforms, return_masks, aux_target_hacks=None):
329
+ super(CocoDetection, self).__init__(img_folder, ann_file)
330
+ self._transforms = transforms
331
+ self.prepare = ConvertCocoPolysToMask(return_masks)
332
+ self.aux_target_hacks = aux_target_hacks
333
+
334
+ def change_hack_attr(self, hackclassname, attrkv_dict):
335
+ target_class = dataset_hook_register[hackclassname]
336
+ for item in self.aux_target_hacks:
337
+ if isinstance(item, target_class):
338
+ for k,v in attrkv_dict.items():
339
+ setattr(item, k, v)
340
+
341
+ def get_hack(self, hackclassname):
342
+ target_class = dataset_hook_register[hackclassname]
343
+ for item in self.aux_target_hacks:
344
+ if isinstance(item, target_class):
345
+ return item
346
+
347
+ def _load_image(self, id: int) -> Image.Image:
348
+ path = self.coco.loadImgs(id)[0]["file_name"]
349
+ abs_path = os.path.join(self.root, path)
350
+ return Image.open(abs_path).convert("RGB")
351
+
352
+ def __getitem__(self, idx):
353
+ """
354
+ Output:
355
+ - target: dict of multiple items
356
+ - boxes: Tensor[num_box, 4]. \
357
+ Init type: x0,y0,x1,y1. unnormalized data.
358
+ Final type: cx,cy,w,h. normalized data.
359
+ """
360
+ try:
361
+ img, target = super(CocoDetection, self).__getitem__(idx)
362
+
363
+ except:
364
+ print("Error idx: {}".format(idx))
365
+ idx += 1
366
+ img, target = super(CocoDetection, self).__getitem__(idx)
367
+
368
+ image_id = self.ids[idx]
369
+ target = {'image_id': image_id, 'annotations': target}
370
+ exemp_count = 0
371
+ for instance in target['annotations']:
372
+ if instance['area'] != 4:
373
+ exemp_count += 1
374
+ assert exemp_count == 3
375
+ img, target = self.prepare(img, target)
376
+ target['exemplars'] = target['boxes'][-3:]
377
+ target['boxes'] = target['boxes'][:-3]
378
+ target['labels'] = target['labels'][:-3]
379
+
380
+
381
+ if self._transforms is not None:
382
+ img, target = self._transforms(img, target)
383
+
384
+ # convert to needed format
385
+ if self.aux_target_hacks is not None:
386
+ for hack_runner in self.aux_target_hacks:
387
+ target, img = hack_runner(target, img=img)
388
+
389
+ return img, target
390
+
391
+
392
+ def convert_coco_poly_to_mask(segmentations, height, width):
393
+ masks = []
394
+ for polygons in segmentations:
395
+ rles = coco_mask.frPyObjects(polygons, height, width)
396
+ mask = coco_mask.decode(rles)
397
+ if len(mask.shape) < 3:
398
+ mask = mask[..., None]
399
+ mask = torch.as_tensor(mask, dtype=torch.uint8)
400
+ mask = mask.any(dim=2)
401
+ masks.append(mask)
402
+ if masks:
403
+ masks = torch.stack(masks, dim=0)
404
+ else:
405
+ masks = torch.zeros((0, height, width), dtype=torch.uint8)
406
+ return masks
407
+
408
+
409
+ class ConvertCocoPolysToMask(object):
410
+ def __init__(self, return_masks=False):
411
+ self.return_masks = return_masks
412
+
413
+ def __call__(self, image, target):
414
+ w, h = image.size
415
+
416
+ image_id = target["image_id"]
417
+ image_id = torch.tensor([image_id])
418
+
419
+ anno = target["annotations"]
420
+
421
+ anno = [obj for obj in anno if 'iscrowd' not in obj or obj['iscrowd'] == 0]
422
+
423
+ boxes = [obj["bbox"] for obj in anno]
424
+ # guard against no boxes via resizing
425
+ boxes = torch.as_tensor(boxes, dtype=torch.float32).reshape(-1, 4)
426
+ boxes[:, 2:] += boxes[:, :2]
427
+ boxes[:, 0::2].clamp_(min=0, max=w)
428
+ boxes[:, 1::2].clamp_(min=0, max=h)
429
+
430
+ classes = [obj["category_id"] for obj in anno]
431
+ classes = torch.tensor(classes, dtype=torch.int64)
432
+
433
+ if self.return_masks:
434
+ segmentations = [obj["segmentation"] for obj in anno]
435
+ masks = convert_coco_poly_to_mask(segmentations, h, w)
436
+
437
+ keypoints = None
438
+ if anno and "keypoints" in anno[0]:
439
+ keypoints = [obj["keypoints"] for obj in anno]
440
+ keypoints = torch.as_tensor(keypoints, dtype=torch.float32)
441
+ num_keypoints = keypoints.shape[0]
442
+ if num_keypoints:
443
+ keypoints = keypoints.view(num_keypoints, -1, 3)
444
+
445
+ keep = (boxes[:, 3] > boxes[:, 1]) & (boxes[:, 2] > boxes[:, 0])
446
+ boxes = boxes[keep]
447
+ classes = classes[keep]
448
+ if self.return_masks:
449
+ masks = masks[keep]
450
+ if keypoints is not None:
451
+ keypoints = keypoints[keep]
452
+
453
+ target = {}
454
+ target["boxes"] = boxes
455
+ target["labels"] = classes
456
+ if self.return_masks:
457
+ target["masks"] = masks
458
+ target["image_id"] = image_id
459
+ if keypoints is not None:
460
+ target["keypoints"] = keypoints
461
+
462
+ # for conversion to coco api
463
+ area = torch.tensor([obj["area"] for obj in anno])
464
+ iscrowd = torch.tensor([obj["iscrowd"] if "iscrowd" in obj else 0 for obj in anno])
465
+ target["area"] = area[keep]
466
+ target["iscrowd"] = iscrowd[keep]
467
+
468
+ target["orig_size"] = torch.as_tensor([int(h), int(w)])
469
+ target["size"] = torch.as_tensor([int(h), int(w)])
470
+
471
+ return image, target
472
+
473
+
474
+ def make_coco_transforms(image_set, fix_size=False, strong_aug=False, args=None):
475
+
476
+ normalize = T.Compose([
477
+ T.ToTensor(),
478
+ T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
479
+ ])
480
+
481
+ # config the params for data aug
482
+ scales = [480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800]
483
+ max_size = 1333
484
+ scales2_resize = [400, 500, 600]
485
+ scales2_crop = [384, 600]
486
+
487
+ # update args from config files
488
+ scales = getattr(args, 'data_aug_scales', scales)
489
+ max_size = getattr(args, 'data_aug_max_size', max_size)
490
+ scales2_resize = getattr(args, 'data_aug_scales2_resize', scales2_resize)
491
+ scales2_crop = getattr(args, 'data_aug_scales2_crop', scales2_crop)
492
+
493
+ # resize them
494
+ data_aug_scale_overlap = getattr(args, 'data_aug_scale_overlap', None)
495
+ if data_aug_scale_overlap is not None and data_aug_scale_overlap > 0:
496
+ data_aug_scale_overlap = float(data_aug_scale_overlap)
497
+ scales = [int(i*data_aug_scale_overlap) for i in scales]
498
+ max_size = int(max_size*data_aug_scale_overlap)
499
+ scales2_resize = [int(i*data_aug_scale_overlap) for i in scales2_resize]
500
+ scales2_crop = [int(i*data_aug_scale_overlap) for i in scales2_crop]
501
+
502
+ datadict_for_print = {
503
+ 'scales': scales,
504
+ 'max_size': max_size,
505
+ 'scales2_resize': scales2_resize,
506
+ 'scales2_crop': scales2_crop
507
+ }
508
+ # print("data_aug_params:", json.dumps(datadict_for_print, indent=2))
509
+
510
+ if image_set == 'train':
511
+ if fix_size:
512
+ return T.Compose([
513
+ T.RandomHorizontalFlip(),
514
+ T.RandomResize([(max_size, max(scales))]),
515
+ # T.RandomResize([(512, 512)]),
516
+ normalize,
517
+ ])
518
+
519
+ if strong_aug:
520
+ import datasets.sltransform as SLT
521
+
522
+ return T.Compose([
523
+ T.RandomHorizontalFlip(),
524
+ T.RandomSelect(
525
+ T.RandomResize(scales, max_size=max_size),
526
+ T.Compose([
527
+ T.RandomResize(scales2_resize),
528
+ T.RandomSizeCrop(*scales2_crop),
529
+ T.RandomResize(scales, max_size=max_size),
530
+ ])
531
+ ),
532
+ SLT.RandomSelectMulti([
533
+ SLT.RandomCrop(),
534
+ SLT.LightingNoise(),
535
+ SLT.AdjustBrightness(2),
536
+ SLT.AdjustContrast(2),
537
+ ]),
538
+ normalize,
539
+ ])
540
+
541
+ return T.Compose([
542
+ T.RandomHorizontalFlip(),
543
+ T.RandomSelect(
544
+ T.RandomResize(scales, max_size=max_size),
545
+ T.Compose([
546
+ T.RandomResize(scales2_resize),
547
+ T.RandomSizeCrop(*scales2_crop),
548
+ T.RandomResize(scales, max_size=max_size),
549
+ ])
550
+ ),
551
+ normalize,
552
+ ])
553
+
554
+ if image_set in ['val', 'eval_debug', 'train_reg', 'test']:
555
+
556
+ if os.environ.get("GFLOPS_DEBUG_SHILONG", False) == 'INFO':
557
+ print("Under debug mode for flops calculation only!!!!!!!!!!!!!!!!")
558
+ return T.Compose([
559
+ T.ResizeDebug((1280, 800)),
560
+ normalize,
561
+ ])
562
+
563
+ return T.Compose([
564
+ T.RandomResize([max(scales)], max_size=max_size),
565
+ normalize,
566
+ ])
567
+
568
+
569
+
570
+ raise ValueError(f'unknown {image_set}')
571
+
572
+
573
+ def get_aux_target_hacks_list(image_set, args):
574
+ if args.modelname in ['q2bs_mask', 'q2bs']:
575
+ aux_target_hacks_list = [
576
+ label2compat(),
577
+ label_compat2onehot(),
578
+ RandomSelectBoxes(num_class=args.num_classes)
579
+ ]
580
+ if args.masked_data and image_set == 'train':
581
+ # aux_target_hacks_list.append()
582
+ aux_target_hacks_list.append(MaskCrop())
583
+ elif args.modelname in ['q2bm_v2', 'q2bs_ce', 'q2op', 'q2ofocal', 'q2opclip', 'q2ocqonly']:
584
+ aux_target_hacks_list = [
585
+ label2compat(),
586
+ label_compat2onehot(),
587
+ box_label_catter(),
588
+ RandomSelectBoxlabels(num_classes=args.num_classes,
589
+ prob_first_item=args.prob_first_item,
590
+ prob_random_item=args.prob_random_item,
591
+ prob_last_item=args.prob_last_item,
592
+ prob_stop_sign=args.prob_stop_sign,
593
+ ),
594
+ BboxPertuber(max_ratio=0.02, generate_samples=1000),
595
+ ]
596
+ elif args.modelname in ['q2omask', 'q2osa']:
597
+ if args.coco_aug:
598
+ aux_target_hacks_list = [
599
+ label2compat(),
600
+ label_compat2onehot(),
601
+ box_label_catter(),
602
+ RandomSelectBoxlabels(num_classes=args.num_classes,
603
+ prob_first_item=args.prob_first_item,
604
+ prob_random_item=args.prob_random_item,
605
+ prob_last_item=args.prob_last_item,
606
+ prob_stop_sign=args.prob_stop_sign,
607
+ ),
608
+ RandomDrop(p=0.2),
609
+ BboxPertuber(max_ratio=0.02, generate_samples=1000),
610
+ RandomCutout(factor=0.5)
611
+ ]
612
+ else:
613
+ aux_target_hacks_list = [
614
+ label2compat(),
615
+ label_compat2onehot(),
616
+ box_label_catter(),
617
+ RandomSelectBoxlabels(num_classes=args.num_classes,
618
+ prob_first_item=args.prob_first_item,
619
+ prob_random_item=args.prob_random_item,
620
+ prob_last_item=args.prob_last_item,
621
+ prob_stop_sign=args.prob_stop_sign,
622
+ ),
623
+ BboxPertuber(max_ratio=0.02, generate_samples=1000),
624
+ ]
625
+ else:
626
+ aux_target_hacks_list = None
627
+
628
+ return aux_target_hacks_list
629
+
630
+
631
+ def build(image_set, args, datasetinfo):
632
+ img_folder = datasetinfo["root"]
633
+ ann_file = datasetinfo["anno"]
634
+
635
+ # copy to local path
636
+ if os.environ.get('DATA_COPY_SHILONG') == 'INFO':
637
+ preparing_dataset(dict(img_folder=img_folder, ann_file=ann_file), image_set, args)
638
+
639
+ try:
640
+ strong_aug = args.strong_aug
641
+ except:
642
+ strong_aug = False
643
+ print(img_folder, ann_file)
644
+ dataset = CocoDetection(img_folder, ann_file,
645
+ transforms=make_coco_transforms(image_set, fix_size=args.fix_size, strong_aug=strong_aug, args=args),
646
+ return_masks=args.masks,
647
+ aux_target_hacks=None,
648
+ )
649
+ return dataset
650
+
651
+
652
+ if __name__ == "__main__":
653
+ # Objects365 Val example
654
+ dataset_o365 = CocoDetection(
655
+ '/path/Objects365/train/',
656
+ "/path/Objects365/slannos/anno_preprocess_train_v2.json",
657
+ transforms=None,
658
+ return_masks=False,
659
+ )
660
+ print('len(dataset_o365):', len(dataset_o365))
datasets/coco_eval.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ """
3
+ COCO evaluator that works in distributed mode.
4
+
5
+ Mostly copy-paste from https://github.com/pytorch/vision/blob/edfd5a7/references/detection/coco_eval.py
6
+ The difference is that there is less copy-pasting from pycocotools
7
+ in the end of the file, as python3 can suppress prints with contextlib
8
+ """
9
+ import os
10
+ import contextlib
11
+ import copy
12
+ import numpy as np
13
+ import torch
14
+
15
+ from pycocotools.cocoeval import COCOeval
16
+ from pycocotools.coco import COCO
17
+ import pycocotools.mask as mask_util
18
+
19
+ from util.misc import all_gather
20
+
21
+
22
+ class CocoEvaluator(object):
23
+ def __init__(self, coco_gt, iou_types, useCats=True):
24
+ assert isinstance(iou_types, (list, tuple))
25
+ coco_gt = copy.deepcopy(coco_gt)
26
+ self.coco_gt = coco_gt
27
+
28
+ self.iou_types = iou_types
29
+ self.coco_eval = {}
30
+ for iou_type in iou_types:
31
+ self.coco_eval[iou_type] = COCOeval(coco_gt, iouType=iou_type)
32
+ self.coco_eval[iou_type].useCats = useCats
33
+
34
+ self.img_ids = []
35
+ self.eval_imgs = {k: [] for k in iou_types}
36
+ self.useCats = useCats
37
+
38
+ def update(self, predictions):
39
+ img_ids = list(np.unique(list(predictions.keys())))
40
+ self.img_ids.extend(img_ids)
41
+
42
+ for iou_type in self.iou_types:
43
+ results = self.prepare(predictions, iou_type)
44
+
45
+ # suppress pycocotools prints
46
+ with open(os.devnull, 'w') as devnull:
47
+ with contextlib.redirect_stdout(devnull):
48
+ coco_dt = COCO.loadRes(self.coco_gt, results) if results else COCO()
49
+ coco_eval = self.coco_eval[iou_type]
50
+
51
+ coco_eval.cocoDt = coco_dt
52
+ coco_eval.params.imgIds = list(img_ids)
53
+ coco_eval.params.useCats = self.useCats
54
+ img_ids, eval_imgs = evaluate(coco_eval)
55
+
56
+ self.eval_imgs[iou_type].append(eval_imgs)
57
+
58
+ def synchronize_between_processes(self):
59
+ for iou_type in self.iou_types:
60
+ self.eval_imgs[iou_type] = np.concatenate(self.eval_imgs[iou_type], 2)
61
+ create_common_coco_eval(self.coco_eval[iou_type], self.img_ids, self.eval_imgs[iou_type])
62
+
63
+ def accumulate(self):
64
+ for coco_eval in self.coco_eval.values():
65
+ coco_eval.accumulate()
66
+
67
+ def summarize(self):
68
+ for iou_type, coco_eval in self.coco_eval.items():
69
+ print("IoU metric: {}".format(iou_type))
70
+ coco_eval.summarize()
71
+
72
+ def prepare(self, predictions, iou_type):
73
+ if iou_type == "bbox":
74
+ return self.prepare_for_coco_detection(predictions)
75
+ elif iou_type == "segm":
76
+ return self.prepare_for_coco_segmentation(predictions)
77
+ elif iou_type == "keypoints":
78
+ return self.prepare_for_coco_keypoint(predictions)
79
+ else:
80
+ raise ValueError("Unknown iou type {}".format(iou_type))
81
+
82
+ def prepare_for_coco_detection(self, predictions):
83
+ coco_results = []
84
+ for original_id, prediction in predictions.items():
85
+ if len(prediction) == 0:
86
+ continue
87
+
88
+ boxes = prediction["boxes"]
89
+ boxes = convert_to_xywh(boxes).tolist()
90
+ if not isinstance(prediction["scores"], list):
91
+ scores = prediction["scores"].tolist()
92
+ else:
93
+ scores = prediction["scores"]
94
+ if not isinstance(prediction["labels"], list):
95
+ labels = prediction["labels"].tolist()
96
+ else:
97
+ labels = prediction["labels"]
98
+
99
+
100
+ try:
101
+ coco_results.extend(
102
+ [
103
+ {
104
+ "image_id": original_id,
105
+ "category_id": labels[k],
106
+ "bbox": box,
107
+ "score": scores[k],
108
+ }
109
+ for k, box in enumerate(boxes)
110
+ ]
111
+ )
112
+ except:
113
+ import ipdb; ipdb.set_trace()
114
+ return coco_results
115
+
116
+ def prepare_for_coco_segmentation(self, predictions):
117
+ coco_results = []
118
+ for original_id, prediction in predictions.items():
119
+ if len(prediction) == 0:
120
+ continue
121
+
122
+ scores = prediction["scores"]
123
+ labels = prediction["labels"]
124
+ masks = prediction["masks"]
125
+
126
+ masks = masks > 0.5
127
+
128
+ scores = prediction["scores"].tolist()
129
+ labels = prediction["labels"].tolist()
130
+
131
+ rles = [
132
+ mask_util.encode(np.array(mask[0, :, :, np.newaxis], dtype=np.uint8, order="F"))[0]
133
+ for mask in masks
134
+ ]
135
+ for rle in rles:
136
+ rle["counts"] = rle["counts"].decode("utf-8")
137
+
138
+ coco_results.extend(
139
+ [
140
+ {
141
+ "image_id": original_id,
142
+ "category_id": labels[k],
143
+ "segmentation": rle,
144
+ "score": scores[k],
145
+ }
146
+ for k, rle in enumerate(rles)
147
+ ]
148
+ )
149
+ return coco_results
150
+
151
+ def prepare_for_coco_keypoint(self, predictions):
152
+ coco_results = []
153
+ for original_id, prediction in predictions.items():
154
+ if len(prediction) == 0:
155
+ continue
156
+
157
+ boxes = prediction["boxes"]
158
+ boxes = convert_to_xywh(boxes).tolist()
159
+ scores = prediction["scores"].tolist()
160
+ labels = prediction["labels"].tolist()
161
+ keypoints = prediction["keypoints"]
162
+ keypoints = keypoints.flatten(start_dim=1).tolist()
163
+
164
+ coco_results.extend(
165
+ [
166
+ {
167
+ "image_id": original_id,
168
+ "category_id": labels[k],
169
+ 'keypoints': keypoint,
170
+ "score": scores[k],
171
+ }
172
+ for k, keypoint in enumerate(keypoints)
173
+ ]
174
+ )
175
+ return coco_results
176
+
177
+
178
+ def convert_to_xywh(boxes):
179
+ xmin, ymin, xmax, ymax = boxes.unbind(1)
180
+ return torch.stack((xmin, ymin, xmax - xmin, ymax - ymin), dim=1)
181
+
182
+
183
+ def merge(img_ids, eval_imgs):
184
+ all_img_ids = all_gather(img_ids)
185
+ all_eval_imgs = all_gather(eval_imgs)
186
+
187
+ merged_img_ids = []
188
+ for p in all_img_ids:
189
+ merged_img_ids.extend(p)
190
+
191
+ merged_eval_imgs = []
192
+ for p in all_eval_imgs:
193
+ merged_eval_imgs.append(p)
194
+
195
+ merged_img_ids = np.array(merged_img_ids)
196
+ merged_eval_imgs = np.concatenate(merged_eval_imgs, 2)
197
+
198
+ # keep only unique (and in sorted order) images
199
+ merged_img_ids, idx = np.unique(merged_img_ids, return_index=True)
200
+ merged_eval_imgs = merged_eval_imgs[..., idx]
201
+
202
+ return merged_img_ids, merged_eval_imgs
203
+
204
+
205
+ def create_common_coco_eval(coco_eval, img_ids, eval_imgs):
206
+ img_ids, eval_imgs = merge(img_ids, eval_imgs)
207
+ img_ids = list(img_ids)
208
+ eval_imgs = list(eval_imgs.flatten())
209
+
210
+ coco_eval.evalImgs = eval_imgs
211
+ coco_eval.params.imgIds = img_ids
212
+ coco_eval._paramsEval = copy.deepcopy(coco_eval.params)
213
+
214
+
215
+ #################################################################
216
+ # From pycocotools, just removed the prints and fixed
217
+ # a Python3 bug about unicode not defined
218
+ #################################################################
219
+
220
+
221
+ def evaluate(self):
222
+ '''
223
+ Run per image evaluation on given images and store results (a list of dict) in self.evalImgs
224
+ :return: None
225
+ '''
226
+ p = self.params
227
+ # add backward compatibility if useSegm is specified in params
228
+ if p.useSegm is not None:
229
+ p.iouType = 'segm' if p.useSegm == 1 else 'bbox'
230
+ print('useSegm (deprecated) is not None. Running {} evaluation'.format(p.iouType))
231
+ p.imgIds = list(np.unique(p.imgIds))
232
+ if p.useCats:
233
+ p.catIds = list(np.unique(p.catIds))
234
+ p.maxDets = sorted(p.maxDets)
235
+ self.params = p
236
+
237
+ self._prepare()
238
+ # loop through images, area range, max detection number
239
+ catIds = p.catIds if p.useCats else [-1]
240
+
241
+ if p.iouType == 'segm' or p.iouType == 'bbox':
242
+ computeIoU = self.computeIoU
243
+ elif p.iouType == 'keypoints':
244
+ computeIoU = self.computeOks
245
+ self.ious = {
246
+ (imgId, catId): computeIoU(imgId, catId)
247
+ for imgId in p.imgIds
248
+ for catId in catIds}
249
+
250
+ evaluateImg = self.evaluateImg
251
+ maxDet = p.maxDets[-1]
252
+ evalImgs = [
253
+ evaluateImg(imgId, catId, areaRng, maxDet)
254
+ for catId in catIds
255
+ for areaRng in p.areaRng
256
+ for imgId in p.imgIds
257
+ ]
258
+ # this is NOT in the pycocotools code, but could be done outside
259
+ evalImgs = np.asarray(evalImgs).reshape(len(catIds), len(p.areaRng), len(p.imgIds))
260
+ self._paramsEval = copy.deepcopy(self.params)
261
+
262
+ return p.imgIds, evalImgs
263
+
264
+ #################################################################
265
+ # end of straight copy from pycocotools, just removing the prints
266
+ #################################################################
datasets/coco_panoptic.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ import json
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+ import torch
7
+ from PIL import Image
8
+
9
+ from panopticapi.utils import rgb2id
10
+ from util.box_ops import masks_to_boxes
11
+
12
+ from .coco import make_coco_transforms
13
+
14
+
15
+ class CocoPanoptic:
16
+ def __init__(self, img_folder, ann_folder, ann_file, transforms=None, return_masks=True):
17
+ with open(ann_file, 'r') as f:
18
+ self.coco = json.load(f)
19
+
20
+ # sort 'images' field so that they are aligned with 'annotations'
21
+ # i.e., in alphabetical order
22
+ self.coco['images'] = sorted(self.coco['images'], key=lambda x: x['id'])
23
+ # sanity check
24
+ if "annotations" in self.coco:
25
+ for img, ann in zip(self.coco['images'], self.coco['annotations']):
26
+ assert img['file_name'][:-4] == ann['file_name'][:-4]
27
+
28
+ self.img_folder = img_folder
29
+ self.ann_folder = ann_folder
30
+ self.ann_file = ann_file
31
+ self.transforms = transforms
32
+ self.return_masks = return_masks
33
+
34
+ def __getitem__(self, idx):
35
+ ann_info = self.coco['annotations'][idx] if "annotations" in self.coco else self.coco['images'][idx]
36
+ img_path = Path(self.img_folder) / ann_info['file_name'].replace('.png', '.jpg')
37
+ ann_path = Path(self.ann_folder) / ann_info['file_name']
38
+
39
+ img = Image.open(img_path).convert('RGB')
40
+ w, h = img.size
41
+ if "segments_info" in ann_info:
42
+ masks = np.asarray(Image.open(ann_path), dtype=np.uint32)
43
+ masks = rgb2id(masks)
44
+
45
+ ids = np.array([ann['id'] for ann in ann_info['segments_info']])
46
+ masks = masks == ids[:, None, None]
47
+
48
+ masks = torch.as_tensor(masks, dtype=torch.uint8)
49
+ labels = torch.tensor([ann['category_id'] for ann in ann_info['segments_info']], dtype=torch.int64)
50
+
51
+ target = {}
52
+ target['image_id'] = torch.tensor([ann_info['image_id'] if "image_id" in ann_info else ann_info["id"]])
53
+ if self.return_masks:
54
+ target['masks'] = masks
55
+ target['labels'] = labels
56
+
57
+ target["boxes"] = masks_to_boxes(masks)
58
+
59
+ target['size'] = torch.as_tensor([int(h), int(w)])
60
+ target['orig_size'] = torch.as_tensor([int(h), int(w)])
61
+ if "segments_info" in ann_info:
62
+ for name in ['iscrowd', 'area']:
63
+ target[name] = torch.tensor([ann[name] for ann in ann_info['segments_info']])
64
+
65
+ if self.transforms is not None:
66
+ img, target = self.transforms(img, target)
67
+
68
+ return img, target
69
+
70
+ def __len__(self):
71
+ return len(self.coco['images'])
72
+
73
+ def get_height_and_width(self, idx):
74
+ img_info = self.coco['images'][idx]
75
+ height = img_info['height']
76
+ width = img_info['width']
77
+ return height, width
78
+
79
+
80
+ def build(image_set, args):
81
+ img_folder_root = Path(args.coco_path)
82
+ ann_folder_root = Path(args.coco_panoptic_path)
83
+ assert img_folder_root.exists(), f'provided COCO path {img_folder_root} does not exist'
84
+ assert ann_folder_root.exists(), f'provided COCO path {ann_folder_root} does not exist'
85
+ mode = 'panoptic'
86
+ PATHS = {
87
+ "train": ("train2017", Path("annotations") / f'{mode}_train2017.json'),
88
+ "val": ("val2017", Path("annotations") / f'{mode}_val2017.json'),
89
+ }
90
+
91
+ img_folder, ann_file = PATHS[image_set]
92
+ img_folder_path = img_folder_root / img_folder
93
+ ann_folder = ann_folder_root / f'{mode}_{img_folder}'
94
+ ann_file = ann_folder_root / ann_file
95
+
96
+ dataset = CocoPanoptic(img_folder_path, ann_folder, ann_file,
97
+ transforms=make_coco_transforms(image_set), return_masks=args.masks)
98
+
99
+ return dataset
datasets/cocogrounding_eval.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO. Midified by Shilong Liu.
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+ # Copyright (c) Aishwarya Kamath & Nicolas Carion. Licensed under the Apache License 2.0. All Rights Reserved
8
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
9
+ """
10
+ COCO evaluator that works in distributed mode.
11
+
12
+ Mostly copy-paste from https://github.com/pytorch/vision/blob/edfd5a7/references/detection/coco_eval.py
13
+ The difference is that there is less copy-pasting from pycocotools
14
+ in the end of the file, as python3 can suppress prints with contextlib
15
+ """
16
+ import contextlib
17
+ import copy
18
+ import os
19
+
20
+ import numpy as np
21
+ import pycocotools.mask as mask_util
22
+ import torch
23
+ from pycocotools.coco import COCO
24
+ from pycocotools.cocoeval import COCOeval
25
+
26
+ from groundingdino.util.misc import all_gather
27
+
28
+
29
+ class CocoGroundingEvaluator(object):
30
+ def __init__(self, coco_gt, iou_types, useCats=True):
31
+ assert isinstance(iou_types, (list, tuple))
32
+ coco_gt = copy.deepcopy(coco_gt)
33
+ self.coco_gt = coco_gt
34
+
35
+ self.iou_types = iou_types
36
+ self.coco_eval = {}
37
+ for iou_type in iou_types:
38
+ self.coco_eval[iou_type] = COCOeval(coco_gt, iouType=iou_type)
39
+ self.coco_eval[iou_type].useCats = useCats
40
+
41
+ self.img_ids = []
42
+ self.eval_imgs = {k: [] for k in iou_types}
43
+ self.useCats = useCats
44
+
45
+ def update(self, predictions):
46
+ img_ids = list(np.unique(list(predictions.keys())))
47
+ self.img_ids.extend(img_ids)
48
+ # import pdb;pdb.set_trace()
49
+ for iou_type in self.iou_types:
50
+ results = self.prepare(predictions, iou_type)
51
+
52
+ # suppress pycocotools prints
53
+ with open(os.devnull, "w") as devnull:
54
+ with contextlib.redirect_stdout(devnull):
55
+ coco_dt = COCO.loadRes(self.coco_gt, results) if results else COCO()
56
+
57
+ coco_eval = self.coco_eval[iou_type]
58
+
59
+ coco_eval.cocoDt = coco_dt
60
+ coco_eval.params.imgIds = list(img_ids)
61
+ coco_eval.params.useCats = self.useCats
62
+ img_ids, eval_imgs = evaluate(coco_eval)
63
+
64
+ self.eval_imgs[iou_type].append(eval_imgs)
65
+
66
+ def synchronize_between_processes(self):
67
+ for iou_type in self.iou_types:
68
+ self.eval_imgs[iou_type] = np.concatenate(self.eval_imgs[iou_type], 2)
69
+ create_common_coco_eval(self.coco_eval[iou_type], self.img_ids, self.eval_imgs[iou_type])
70
+
71
+ def accumulate(self):
72
+ for coco_eval in self.coco_eval.values():
73
+ coco_eval.accumulate()
74
+
75
+ def summarize(self):
76
+ for iou_type, coco_eval in self.coco_eval.items():
77
+ print("IoU metric: {}".format(iou_type))
78
+ coco_eval.summarize()
79
+
80
+ def prepare(self, predictions, iou_type):
81
+ if iou_type == "bbox":
82
+ return self.prepare_for_coco_detection(predictions)
83
+ elif iou_type == "segm":
84
+ return self.prepare_for_coco_segmentation(predictions)
85
+ elif iou_type == "keypoints":
86
+ return self.prepare_for_coco_keypoint(predictions)
87
+ else:
88
+ raise ValueError("Unknown iou type {}".format(iou_type))
89
+
90
+ def prepare_for_coco_detection(self, predictions):
91
+ coco_results = []
92
+ for original_id, prediction in predictions.items():
93
+ if len(prediction) == 0:
94
+ continue
95
+
96
+ boxes = prediction["boxes"]
97
+ boxes = convert_to_xywh(boxes).tolist()
98
+ scores = prediction["scores"].tolist()
99
+ labels = prediction["labels"].tolist()
100
+
101
+ coco_results.extend(
102
+ [
103
+ {
104
+ "image_id": original_id,
105
+ "category_id": labels[k],
106
+ "bbox": box,
107
+ "score": scores[k],
108
+ }
109
+ for k, box in enumerate(boxes)
110
+ ]
111
+ )
112
+ return coco_results
113
+
114
+ def prepare_for_coco_segmentation(self, predictions):
115
+ coco_results = []
116
+ for original_id, prediction in predictions.items():
117
+ if len(prediction) == 0:
118
+ continue
119
+
120
+ scores = prediction["scores"]
121
+ labels = prediction["labels"]
122
+ masks = prediction["masks"]
123
+
124
+ masks = masks > 0.5
125
+
126
+ scores = prediction["scores"].tolist()
127
+ labels = prediction["labels"].tolist()
128
+
129
+ rles = [
130
+ mask_util.encode(np.array(mask[0, :, :, np.newaxis], dtype=np.uint8, order="F"))[0]
131
+ for mask in masks
132
+ ]
133
+ for rle in rles:
134
+ rle["counts"] = rle["counts"].decode("utf-8")
135
+
136
+ coco_results.extend(
137
+ [
138
+ {
139
+ "image_id": original_id,
140
+ "category_id": labels[k],
141
+ "segmentation": rle,
142
+ "score": scores[k],
143
+ }
144
+ for k, rle in enumerate(rles)
145
+ ]
146
+ )
147
+ return coco_results
148
+
149
+ def prepare_for_coco_keypoint(self, predictions):
150
+ coco_results = []
151
+ for original_id, prediction in predictions.items():
152
+ if len(prediction) == 0:
153
+ continue
154
+
155
+ boxes = prediction["boxes"]
156
+ boxes = convert_to_xywh(boxes).tolist()
157
+ scores = prediction["scores"].tolist()
158
+ labels = prediction["labels"].tolist()
159
+ keypoints = prediction["keypoints"]
160
+ keypoints = keypoints.flatten(start_dim=1).tolist()
161
+
162
+ coco_results.extend(
163
+ [
164
+ {
165
+ "image_id": original_id,
166
+ "category_id": labels[k],
167
+ "keypoints": keypoint,
168
+ "score": scores[k],
169
+ }
170
+ for k, keypoint in enumerate(keypoints)
171
+ ]
172
+ )
173
+ return coco_results
174
+
175
+
176
+ def convert_to_xywh(boxes):
177
+ xmin, ymin, xmax, ymax = boxes.unbind(1)
178
+ return torch.stack((xmin, ymin, xmax - xmin, ymax - ymin), dim=1)
179
+
180
+
181
+ def merge(img_ids, eval_imgs):
182
+ all_img_ids = all_gather(img_ids)
183
+ all_eval_imgs = all_gather(eval_imgs)
184
+
185
+ merged_img_ids = []
186
+ for p in all_img_ids:
187
+ merged_img_ids.extend(p)
188
+
189
+ merged_eval_imgs = []
190
+ for p in all_eval_imgs:
191
+ merged_eval_imgs.append(p)
192
+
193
+ merged_img_ids = np.array(merged_img_ids)
194
+ merged_eval_imgs = np.concatenate(merged_eval_imgs, 2)
195
+
196
+ # keep only unique (and in sorted order) images
197
+ merged_img_ids, idx = np.unique(merged_img_ids, return_index=True)
198
+ merged_eval_imgs = merged_eval_imgs[..., idx]
199
+
200
+ return merged_img_ids, merged_eval_imgs
201
+
202
+
203
+ def create_common_coco_eval(coco_eval, img_ids, eval_imgs):
204
+ img_ids, eval_imgs = merge(img_ids, eval_imgs)
205
+ img_ids = list(img_ids)
206
+ eval_imgs = list(eval_imgs.flatten())
207
+
208
+ coco_eval.evalImgs = eval_imgs
209
+ coco_eval.params.imgIds = img_ids
210
+ coco_eval._paramsEval = copy.deepcopy(coco_eval.params)
211
+
212
+
213
+ #################################################################
214
+ # From pycocotools, just removed the prints and fixed
215
+ # a Python3 bug about unicode not defined
216
+ #################################################################
217
+
218
+
219
+ def evaluate(self):
220
+ """
221
+ Run per image evaluation on given images and store results (a list of dict) in self.evalImgs
222
+ :return: None
223
+ """
224
+ # tic = time.time()
225
+ # print('Running per image evaluation...')
226
+
227
+ # import pdb;pdb.set_trace()
228
+ p = self.params
229
+ # add backward compatibility if useSegm is specified in params
230
+ if p.useSegm is not None:
231
+ p.iouType = "segm" if p.useSegm == 1 else "bbox"
232
+ print("useSegm (deprecated) is not None. Running {} evaluation".format(p.iouType))
233
+ # print('Evaluate annotation type *{}*'.format(p.iouType))
234
+ p.imgIds = list(np.unique(p.imgIds))
235
+ if p.useCats:
236
+ p.catIds = list(np.unique(p.catIds))
237
+ p.maxDets = sorted(p.maxDets)
238
+ self.params = p
239
+
240
+ self._prepare()
241
+ # loop through images, area range, max detection number
242
+ catIds = p.catIds if p.useCats else [-1]
243
+
244
+ if p.iouType == "segm" or p.iouType == "bbox":
245
+ computeIoU = self.computeIoU
246
+ elif p.iouType == "keypoints":
247
+ computeIoU = self.computeOks
248
+ self.ious = {
249
+ (imgId, catId): computeIoU(imgId, catId)
250
+ for imgId in p.imgIds
251
+ for catId in catIds}
252
+
253
+ evaluateImg = self.evaluateImg
254
+ maxDet = p.maxDets[-1]
255
+ evalImgs = [
256
+ evaluateImg(imgId, catId, areaRng, maxDet)
257
+ for catId in catIds
258
+ for areaRng in p.areaRng
259
+ for imgId in p.imgIds
260
+ ]
261
+ # this is NOT in the pycocotools code, but could be done outside
262
+ evalImgs = np.asarray(evalImgs).reshape(len(catIds), len(p.areaRng), len(p.imgIds))
263
+ self._paramsEval = copy.deepcopy(self.params)
264
+ # toc = time.time()
265
+ # print('DONE (t={:0.2f}s).'.format(toc-tic))
266
+ return p.imgIds, evalImgs
267
+
268
+
269
+ #################################################################
270
+ # end of straight copy from pycocotools, just removing the prints
271
+ #################################################################
datasets/data_util.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import os.path as osp
3
+ import shutil
4
+ import time
5
+ import datetime
6
+
7
+ import torch
8
+
9
+ from util.slconfig import SLConfig
10
+
11
+ class Error(OSError):
12
+ pass
13
+
14
+ def slcopytree(src, dst, symlinks=False, ignore=None, copy_function=shutil.copyfile,
15
+ ignore_dangling_symlinks=False):
16
+ """
17
+ modified from shutil.copytree without copystat.
18
+
19
+ Recursively copy a directory tree.
20
+
21
+ The destination directory must not already exist.
22
+ If exception(s) occur, an Error is raised with a list of reasons.
23
+
24
+ If the optional symlinks flag is true, symbolic links in the
25
+ source tree result in symbolic links in the destination tree; if
26
+ it is false, the contents of the files pointed to by symbolic
27
+ links are copied. If the file pointed by the symlink doesn't
28
+ exist, an exception will be added in the list of errors raised in
29
+ an Error exception at the end of the copy process.
30
+
31
+ You can set the optional ignore_dangling_symlinks flag to true if you
32
+ want to silence this exception. Notice that this has no effect on
33
+ platforms that don't support os.symlink.
34
+
35
+ The optional ignore argument is a callable. If given, it
36
+ is called with the `src` parameter, which is the directory
37
+ being visited by copytree(), and `names` which is the list of
38
+ `src` contents, as returned by os.listdir():
39
+
40
+ callable(src, names) -> ignored_names
41
+
42
+ Since copytree() is called recursively, the callable will be
43
+ called once for each directory that is copied. It returns a
44
+ list of names relative to the `src` directory that should
45
+ not be copied.
46
+
47
+ The optional copy_function argument is a callable that will be used
48
+ to copy each file. It will be called with the source path and the
49
+ destination path as arguments. By default, copy2() is used, but any
50
+ function that supports the same signature (like copy()) can be used.
51
+
52
+ """
53
+ errors = []
54
+ if os.path.isdir(src):
55
+ names = os.listdir(src)
56
+ if ignore is not None:
57
+ ignored_names = ignore(src, names)
58
+ else:
59
+ ignored_names = set()
60
+
61
+ os.makedirs(dst)
62
+ for name in names:
63
+ if name in ignored_names:
64
+ continue
65
+ srcname = os.path.join(src, name)
66
+ dstname = os.path.join(dst, name)
67
+ try:
68
+ if os.path.islink(srcname):
69
+ linkto = os.readlink(srcname)
70
+ if symlinks:
71
+ # We can't just leave it to `copy_function` because legacy
72
+ # code with a custom `copy_function` may rely on copytree
73
+ # doing the right thing.
74
+ os.symlink(linkto, dstname)
75
+ else:
76
+ # ignore dangling symlink if the flag is on
77
+ if not os.path.exists(linkto) and ignore_dangling_symlinks:
78
+ continue
79
+ # otherwise let the copy occurs. copy2 will raise an error
80
+ if os.path.isdir(srcname):
81
+ slcopytree(srcname, dstname, symlinks, ignore,
82
+ copy_function)
83
+ else:
84
+ copy_function(srcname, dstname)
85
+ elif os.path.isdir(srcname):
86
+ slcopytree(srcname, dstname, symlinks, ignore, copy_function)
87
+ else:
88
+ # Will raise a SpecialFileError for unsupported file types
89
+ copy_function(srcname, dstname)
90
+ # catch the Error from the recursive copytree so that we can
91
+ # continue with other files
92
+ except Error as err:
93
+ errors.extend(err.args[0])
94
+ except OSError as why:
95
+ errors.append((srcname, dstname, str(why)))
96
+ else:
97
+ copy_function(src, dst)
98
+
99
+ if errors:
100
+ raise Error(errors)
101
+ return dst
102
+
103
+ def check_and_copy(src_path, tgt_path):
104
+ if os.path.exists(tgt_path):
105
+ return None
106
+
107
+ return slcopytree(src_path, tgt_path)
108
+
109
+
110
+ def remove(srcpath):
111
+ if os.path.isdir(srcpath):
112
+ return shutil.rmtree(srcpath)
113
+ else:
114
+ return os.remove(srcpath)
115
+
116
+
117
+ def preparing_dataset(pathdict, image_set, args):
118
+ start_time = time.time()
119
+ dataset_file = args.dataset_file
120
+ data_static_info = SLConfig.fromfile('util/static_data_path.py')
121
+ static_dict = data_static_info[dataset_file][image_set]
122
+
123
+ copyfilelist = []
124
+ for k,tgt_v in pathdict.items():
125
+ if os.path.exists(tgt_v):
126
+ if args.local_rank == 0:
127
+ print("path <{}> exist. remove it!".format(tgt_v))
128
+ remove(tgt_v)
129
+ # continue
130
+
131
+ if args.local_rank == 0:
132
+ src_v = static_dict[k]
133
+ assert isinstance(src_v, str)
134
+ if src_v.endswith('.zip'):
135
+ # copy
136
+ cp_tgt_dir = os.path.dirname(tgt_v)
137
+ filename = os.path.basename(src_v)
138
+ cp_tgt_path = os.path.join(cp_tgt_dir, filename)
139
+ print('Copy from <{}> to <{}>.'.format(src_v, cp_tgt_path))
140
+ os.makedirs(cp_tgt_dir, exist_ok=True)
141
+ check_and_copy(src_v, cp_tgt_path)
142
+
143
+ # unzip
144
+ import zipfile
145
+ print("Starting unzip <{}>".format(cp_tgt_path))
146
+ with zipfile.ZipFile(cp_tgt_path, 'r') as zip_ref:
147
+ zip_ref.extractall(os.path.dirname(cp_tgt_path))
148
+
149
+ copyfilelist.append(cp_tgt_path)
150
+ copyfilelist.append(tgt_v)
151
+ else:
152
+ print('Copy from <{}> to <{}>.'.format(src_v, tgt_v))
153
+ os.makedirs(os.path.dirname(tgt_v), exist_ok=True)
154
+ check_and_copy(src_v, tgt_v)
155
+ copyfilelist.append(tgt_v)
156
+
157
+ if len(copyfilelist) == 0:
158
+ copyfilelist = None
159
+ args.copyfilelist = copyfilelist
160
+
161
+ if args.distributed:
162
+ torch.distributed.barrier()
163
+ total_time = time.time() - start_time
164
+ if copyfilelist:
165
+ total_time_str = str(datetime.timedelta(seconds=int(total_time)))
166
+ print('Data copy time {}'.format(total_time_str))
167
+ return copyfilelist
168
+
169
+
170
+
datasets/dataset.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import print_function
2
+
3
+ import torch
4
+ import torchvision.datasets as datasets
5
+ from torch.utils.data import Dataset
6
+ from PIL import Image
7
+ from .tsv_io import TSVFile
8
+ import numpy as np
9
+ import base64
10
+ import io
11
+
12
+
13
+ class TSVDataset(Dataset):
14
+ """ TSV dataset for ImageNet 1K training
15
+ """
16
+ def __init__(self, tsv_file, transform=None, target_transform=None):
17
+ self.tsv = TSVFile(tsv_file)
18
+ self.transform = transform
19
+ self.target_transform = target_transform
20
+
21
+ def __getitem__(self, index):
22
+ """
23
+ Args:
24
+ index (int): Index
25
+ Returns:
26
+ tuple: (image, target) where target is class_index of the target class.
27
+ """
28
+ row = self.tsv.seek(index)
29
+ image_data = base64.b64decode(row[-1])
30
+ image = Image.open(io.BytesIO(image_data))
31
+ image = image.convert('RGB')
32
+ target = int(row[1])
33
+
34
+ if self.transform is not None:
35
+ img = self.transform(image)
36
+ else:
37
+ img = image
38
+ if self.target_transform is not None:
39
+ target = self.target_transform(target)
40
+
41
+ return img, target
42
+
43
+ def __len__(self):
44
+ return self.tsv.num_rows()
datasets/odvg.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from torchvision.datasets.vision import VisionDataset
2
+ import os.path
3
+ from typing import Callable, Optional
4
+ import json
5
+ from PIL import Image
6
+ import torch
7
+ import random
8
+ import os, sys
9
+ sys.path.append(os.path.dirname(sys.path[0]))
10
+
11
+ import datasets.transforms as T
12
+
13
+ class ODVGDataset(VisionDataset):
14
+ """
15
+ Args:
16
+ root (string): Root directory where images are downloaded to.
17
+ anno (string): Path to json annotation file.
18
+ label_map_anno (string): Path to json label mapping file. Only for Object Detection
19
+ transform (callable, optional): A function/transform that takes in an PIL image
20
+ and returns a transformed version. E.g, ``transforms.PILToTensor``
21
+ target_transform (callable, optional): A function/transform that takes in the
22
+ target and transforms it.
23
+ transforms (callable, optional): A function/transform that takes input sample and its target as entry
24
+ and returns a transformed version.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ root: str,
30
+ anno: str,
31
+ label_map_anno: str = None,
32
+ max_labels: int = 80,
33
+ transform: Optional[Callable] = None,
34
+ target_transform: Optional[Callable] = None,
35
+ transforms: Optional[Callable] = None,
36
+ ) -> None:
37
+ super().__init__(root, transforms, transform, target_transform)
38
+ self.root = root
39
+ self.dataset_mode = "OD" if label_map_anno else "VG"
40
+ self.max_labels = max_labels
41
+ if self.dataset_mode == "OD":
42
+ self.load_label_map(label_map_anno)
43
+ self._load_metas(anno)
44
+ self.get_dataset_info()
45
+
46
+ def load_label_map(self, label_map_anno):
47
+ with open(label_map_anno, 'r') as file:
48
+ self.label_map = json.load(file)
49
+ self.label_index = set(self.label_map.keys())
50
+
51
+ def _load_metas(self, anno):
52
+ with open(anno, 'r')as f:
53
+ self.metas = [json.loads(line) for line in f]
54
+
55
+ def get_dataset_info(self):
56
+ print(f" == total images: {len(self)}")
57
+ if self.dataset_mode == "OD":
58
+ print(f" == total labels: {len(self.label_map)}")
59
+
60
+ def __getitem__(self, index: int):
61
+ meta = self.metas[index]
62
+ rel_path = meta["filename"]
63
+ abs_path = os.path.join(self.root, rel_path)
64
+ if not os.path.exists(abs_path):
65
+ raise FileNotFoundError(f"{abs_path} not found.")
66
+ image = Image.open(abs_path).convert('RGB')
67
+
68
+ w, h = image.size
69
+ if self.dataset_mode == "OD":
70
+ # Process the dots for the instances.
71
+ anno = meta["detection"]
72
+ instances = [obj for obj in anno["instances"]]
73
+ exemplar_labels = [str(obj["label"]) for obj in meta["exemplars"]]
74
+ boxes = [obj["bbox"] for obj in instances]
75
+ # generate vg_labels
76
+ # pos bbox labels
77
+ ori_classes = [str(obj["label"]) for obj in instances] + exemplar_labels
78
+ pos_labels = set(ori_classes)
79
+ # neg bbox labels
80
+ neg_labels = self.label_index.difference(pos_labels)
81
+
82
+ vg_labels = list(pos_labels)
83
+ num_to_add = min(len(neg_labels), self.max_labels-len(pos_labels))
84
+ if num_to_add > 0:
85
+ vg_labels.extend(random.sample(neg_labels, num_to_add))
86
+
87
+ # shuffle
88
+ for i in range(len(vg_labels)-1, 0, -1):
89
+ j = random.randint(0, i)
90
+ vg_labels[i], vg_labels[j] = vg_labels[j], vg_labels[i]
91
+
92
+ caption_list = [self.label_map[lb] for lb in vg_labels]
93
+ caption_dict = {item:index for index, item in enumerate(caption_list)}
94
+
95
+ caption = ' . '.join(caption_list) + ' .'
96
+ classes = [caption_dict[self.label_map[str(obj["label"])]] for obj in instances]
97
+ boxes = torch.as_tensor(boxes, dtype=torch.float32).reshape(-1, 4)
98
+ classes = torch.tensor(classes, dtype=torch.int64)
99
+
100
+ # Process the exemplars for the instances.
101
+ exemplars = [obj for obj in meta["exemplars"]]
102
+ exemplar_boxes = [obj["bbox"] for obj in exemplars]
103
+ exemplar_boxes = torch.as_tensor(exemplar_boxes, dtype=torch.float32).reshape(-1, 4)
104
+ exemplar_classes = [caption_dict[self.label_map[str(obj["label"])]] for obj in exemplars]
105
+ exemplar_classes = torch.tensor(exemplar_classes, dtype=torch.int64)
106
+ elif self.dataset_mode == "VG":
107
+ anno = meta["grounding"]
108
+ instances = [obj for obj in anno["regions"]]
109
+ boxes = [obj["bbox"] for obj in instances]
110
+ caption_list = [obj["phrase"] for obj in instances]
111
+ c = list(zip(boxes, caption_list))
112
+ random.shuffle(c)
113
+ boxes[:], caption_list[:] = zip(*c)
114
+ uni_caption_list = list(set(caption_list))
115
+ label_map = {}
116
+ for idx in range(len(uni_caption_list)):
117
+ label_map[uni_caption_list[idx]] = idx
118
+ classes = [label_map[cap] for cap in caption_list]
119
+ caption = ' . '.join(uni_caption_list) + ' .'
120
+ boxes = torch.as_tensor(boxes, dtype=torch.float32).reshape(-1, 4)
121
+ classes = torch.tensor(classes, dtype=torch.int64)
122
+ caption_list = uni_caption_list
123
+ target = {}
124
+ target["size"] = torch.as_tensor([int(h), int(w)])
125
+ target["cap_list"] = caption_list
126
+ target["caption"] = caption
127
+ target["boxes"] = boxes
128
+ target["labels"] = classes
129
+ target["exemplar_boxes"] = exemplar_boxes
130
+ target["exemplar_labels"] = exemplar_classes
131
+
132
+ if self.transforms is not None:
133
+ image, target = self.transforms(image, target)
134
+
135
+ return image, target
136
+
137
+
138
+ def __len__(self) -> int:
139
+ return len(self.metas)
140
+
141
+
142
+ def make_coco_transforms(image_set, fix_size=False, strong_aug=False, args=None):
143
+
144
+ normalize = T.Compose([
145
+ T.ToTensor(),
146
+ T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
147
+ ])
148
+
149
+ # config the params for data aug
150
+ scales = [480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800]
151
+ max_size = 1333
152
+ scales2_resize = [400, 500, 600]
153
+ scales2_crop = [384, 600]
154
+
155
+ # update args from config files
156
+ scales = getattr(args, 'data_aug_scales', scales)
157
+ max_size = getattr(args, 'data_aug_max_size', max_size)
158
+ scales2_resize = getattr(args, 'data_aug_scales2_resize', scales2_resize)
159
+ scales2_crop = getattr(args, 'data_aug_scales2_crop', scales2_crop)
160
+
161
+ # resize them
162
+ data_aug_scale_overlap = getattr(args, 'data_aug_scale_overlap', None)
163
+ if data_aug_scale_overlap is not None and data_aug_scale_overlap > 0:
164
+ data_aug_scale_overlap = float(data_aug_scale_overlap)
165
+ scales = [int(i*data_aug_scale_overlap) for i in scales]
166
+ max_size = int(max_size*data_aug_scale_overlap)
167
+ scales2_resize = [int(i*data_aug_scale_overlap) for i in scales2_resize]
168
+ scales2_crop = [int(i*data_aug_scale_overlap) for i in scales2_crop]
169
+
170
+ # datadict_for_print = {
171
+ # 'scales': scales,
172
+ # 'max_size': max_size,
173
+ # 'scales2_resize': scales2_resize,
174
+ # 'scales2_crop': scales2_crop
175
+ # }
176
+ # print("data_aug_params:", json.dumps(datadict_for_print, indent=2))
177
+
178
+ if image_set == 'train':
179
+ if fix_size:
180
+ return T.Compose([
181
+ T.RandomHorizontalFlip(),
182
+ T.RandomResize([(max_size, max(scales))]),
183
+ normalize,
184
+ ])
185
+
186
+ if strong_aug:
187
+ import datasets.sltransform as SLT
188
+
189
+ return T.Compose([
190
+ T.RandomHorizontalFlip(),
191
+ T.RandomSelect(
192
+ T.RandomResize(scales, max_size=max_size),
193
+ T.Compose([
194
+ T.RandomResize(scales2_resize),
195
+ T.RandomSizeCrop(*scales2_crop),
196
+ T.RandomResize(scales, max_size=max_size),
197
+ ])
198
+ ),
199
+ SLT.RandomSelectMulti([
200
+ SLT.RandomCrop(),
201
+ SLT.LightingNoise(),
202
+ SLT.AdjustBrightness(2),
203
+ SLT.AdjustContrast(2),
204
+ ]),
205
+ normalize,
206
+ ])
207
+
208
+ return T.Compose([
209
+ T.RandomHorizontalFlip(),
210
+ T.RandomSelect(
211
+ T.RandomResize(scales, max_size=max_size),
212
+ T.Compose([
213
+ T.RandomResize(scales2_resize),
214
+ T.RandomSizeCrop(*scales2_crop),
215
+ T.RandomResize(scales, max_size=max_size),
216
+ ])
217
+ ),
218
+ normalize,
219
+ ])
220
+
221
+ if image_set in ['val', 'eval_debug', 'train_reg', 'test']:
222
+
223
+ if os.environ.get("GFLOPS_DEBUG_SHILONG", False) == 'INFO':
224
+ print("Under debug mode for flops calculation only!!!!!!!!!!!!!!!!")
225
+ return T.Compose([
226
+ T.ResizeDebug((1280, 800)),
227
+ normalize,
228
+ ])
229
+
230
+ return T.Compose([
231
+ T.RandomResize([max(scales)], max_size=max_size),
232
+ normalize,
233
+ ])
234
+
235
+ raise ValueError(f'unknown {image_set}')
236
+
237
+ def build_odvg(image_set, args, datasetinfo):
238
+ img_folder = datasetinfo["root"]
239
+ ann_file = datasetinfo["anno"]
240
+ label_map = datasetinfo["label_map"] if "label_map" in datasetinfo else None
241
+ try:
242
+ strong_aug = args.strong_aug
243
+ except:
244
+ strong_aug = False
245
+ print(img_folder, ann_file, label_map)
246
+ dataset = ODVGDataset(img_folder, ann_file, label_map, max_labels=args.max_labels,
247
+ transforms=make_coco_transforms(image_set, fix_size=args.fix_size, strong_aug=strong_aug, args=args),
248
+ )
249
+ return dataset
250
+
251
+
252
+ if __name__=="__main__":
253
+ dataset_vg = ODVGDataset("path/GRIT-20M/data/","path/GRIT-20M/anno/grit_odvg_10k.jsonl",)
254
+ print(len(dataset_vg))
255
+ data = dataset_vg[random.randint(0, 100)]
256
+ print(data)
257
+ dataset_od = ODVGDataset("pathl/V3Det/",
258
+ "path/V3Det/annotations/v3det_2023_v1_all_odvg.jsonl",
259
+ "path/V3Det/annotations/v3det_label_map.json",
260
+ )
261
+ print(len(dataset_od))
262
+ data = dataset_od[random.randint(0, 100)]
263
+ print(data)
datasets/panoptic_eval.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ import json
3
+ import os
4
+
5
+ import util.misc as utils
6
+
7
+ try:
8
+ from panopticapi.evaluation import pq_compute
9
+ except ImportError:
10
+ pass
11
+
12
+
13
+ class PanopticEvaluator(object):
14
+ def __init__(self, ann_file, ann_folder, output_dir="panoptic_eval"):
15
+ self.gt_json = ann_file
16
+ self.gt_folder = ann_folder
17
+ if utils.is_main_process():
18
+ if not os.path.exists(output_dir):
19
+ os.mkdir(output_dir)
20
+ self.output_dir = output_dir
21
+ self.predictions = []
22
+
23
+ def update(self, predictions):
24
+ for p in predictions:
25
+ with open(os.path.join(self.output_dir, p["file_name"]), "wb") as f:
26
+ f.write(p.pop("png_string"))
27
+
28
+ self.predictions += predictions
29
+
30
+ def synchronize_between_processes(self):
31
+ all_predictions = utils.all_gather(self.predictions)
32
+ merged_predictions = []
33
+ for p in all_predictions:
34
+ merged_predictions += p
35
+ self.predictions = merged_predictions
36
+
37
+ def summarize(self):
38
+ if utils.is_main_process():
39
+ json_data = {"annotations": self.predictions}
40
+ predictions_json = os.path.join(self.output_dir, "predictions.json")
41
+ with open(predictions_json, "w") as f:
42
+ f.write(json.dumps(json_data))
43
+ return pq_compute(self.gt_json, predictions_json, gt_folder=self.gt_folder, pred_folder=self.output_dir)
44
+ return None
datasets/random_crop.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import PIL #version 1.2.0
2
+ import torch
3
+ import os
4
+ import torchvision.transforms.functional as F
5
+ import numpy as np
6
+ import random
7
+
8
+
9
+ def intersect(boxes1, boxes2):
10
+ '''
11
+ Find intersection of every box combination between two sets of box
12
+ boxes1: bounding boxes 1, a tensor of dimensions (n1, 4)
13
+ boxes2: bounding boxes 2, a tensor of dimensions (n2, 4)
14
+
15
+ Out: Intersection each of boxes1 with respect to each of boxes2,
16
+ a tensor of dimensions (n1, n2)
17
+ '''
18
+ n1 = boxes1.size(0)
19
+ n2 = boxes2.size(0)
20
+ max_xy = torch.min(boxes1[:, 2:].unsqueeze(1).expand(n1, n2, 2),
21
+ boxes2[:, 2:].unsqueeze(0).expand(n1, n2, 2))
22
+
23
+ min_xy = torch.max(boxes1[:, :2].unsqueeze(1).expand(n1, n2, 2),
24
+ boxes2[:, :2].unsqueeze(0).expand(n1, n2, 2))
25
+ inter = torch.clamp(max_xy - min_xy , min=0) # (n1, n2, 2)
26
+ return inter[:, :, 0] * inter[:, :, 1] #(n1, n2)
27
+ def find_IoU(boxes1, boxes2):
28
+ '''
29
+ Find IoU between every boxes set of boxes
30
+ boxes1: a tensor of dimensions (n1, 4) (left, top, right , bottom)
31
+ boxes2: a tensor of dimensions (n2, 4)
32
+
33
+ Out: IoU each of boxes1 with respect to each of boxes2, a tensor of
34
+ dimensions (n1, n2)
35
+
36
+ Formula:
37
+ (box1 ∩ box2) / (box1 u box2) = (box1 ∩ box2) / (area(box1) + area(box2) - (box1 ∩ box2 ))
38
+ '''
39
+ inter = intersect(boxes1, boxes2)
40
+ area_boxes1 = (boxes1[:, 2] - boxes1[:, 0]) * (boxes1[:, 3] - boxes1[:, 1])
41
+ area_boxes2 = (boxes2[:, 2] - boxes2[:, 0]) * (boxes2[:, 3] - boxes2[:, 1])
42
+
43
+ area_boxes1 = area_boxes1.unsqueeze(1).expand_as(inter) #(n1, n2)
44
+ area_boxes2 = area_boxes2.unsqueeze(0).expand_as(inter) #(n1, n2)
45
+ union = (area_boxes1 + area_boxes2 - inter)
46
+ return inter / union
47
+
48
+
49
+ def random_crop(image, boxes, labels, difficulties=None):
50
+ '''
51
+ image: A PIL image
52
+ boxes: Bounding boxes, a tensor of dimensions (#objects, 4)
53
+ labels: labels of object, a tensor of dimensions (#objects)
54
+ difficulties: difficulties of detect object, a tensor of dimensions (#objects)
55
+
56
+ Out: cropped image , new boxes, new labels, new difficulties
57
+ '''
58
+ if type(image) == PIL.Image.Image:
59
+ image = F.to_tensor(image)
60
+ original_h = image.size(1)
61
+ original_w = image.size(2)
62
+
63
+ while True:
64
+ mode = random.choice([0.1, 0.3, 0.5, 0.9, None])
65
+
66
+ if mode is None:
67
+ return F.to_pil_image(image), boxes, labels, difficulties
68
+
69
+ new_image = image
70
+ new_boxes = boxes
71
+ new_difficulties = difficulties
72
+ new_labels = labels
73
+ for _ in range(50):
74
+ # Crop dimensions: [0.3, 1] of original dimensions
75
+ new_h = random.uniform(0.3*original_h, original_h)
76
+ new_w = random.uniform(0.3*original_w, original_w)
77
+
78
+ # Aspect ratio constraint b/t .5 & 2
79
+ if new_h/new_w < 0.5 or new_h/new_w > 2:
80
+ continue
81
+
82
+ #Crop coordinate
83
+ left = random.uniform(0, original_w - new_w)
84
+ right = left + new_w
85
+ top = random.uniform(0, original_h - new_h)
86
+ bottom = top + new_h
87
+ crop = torch.FloatTensor([int(left), int(top), int(right), int(bottom)])
88
+
89
+ # Calculate IoU between the crop and the bounding boxes
90
+ overlap = find_IoU(crop.unsqueeze(0), boxes) #(1, #objects)
91
+ overlap = overlap.squeeze(0)
92
+
93
+ # If not a single bounding box has a IoU of greater than the minimum, try again
94
+ if overlap.shape[0] == 0:
95
+ continue
96
+ if overlap.max().item() < mode:
97
+ continue
98
+
99
+ #Crop
100
+ new_image = image[:, int(top):int(bottom), int(left):int(right)] #(3, new_h, new_w)
101
+
102
+ #Center of bounding boxes
103
+ center_bb = (boxes[:, :2] + boxes[:, 2:])/2.0
104
+
105
+ #Find bounding box has been had center in crop
106
+ center_in_crop = (center_bb[:, 0] >left) * (center_bb[:, 0] < right
107
+ ) *(center_bb[:, 1] > top) * (center_bb[:, 1] < bottom) #( #objects)
108
+
109
+ if not center_in_crop.any():
110
+ continue
111
+
112
+ #take matching bounding box
113
+ new_boxes = boxes[center_in_crop, :]
114
+
115
+ #take matching labels
116
+ new_labels = labels[center_in_crop]
117
+
118
+ #take matching difficulities
119
+ if difficulties is not None:
120
+ new_difficulties = difficulties[center_in_crop]
121
+ else:
122
+ new_difficulties = None
123
+
124
+ #Use the box left and top corner or the crop's
125
+ new_boxes[:, :2] = torch.max(new_boxes[:, :2], crop[:2])
126
+
127
+ #adjust to crop
128
+ new_boxes[:, :2] -= crop[:2]
129
+
130
+ new_boxes[:, 2:] = torch.min(new_boxes[:, 2:],crop[2:])
131
+
132
+ #adjust to crop
133
+ new_boxes[:, 2:] -= crop[:2]
134
+
135
+ return F.to_pil_image(new_image), new_boxes, new_labels, new_difficulties
datasets/sltransform.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modified from https://github.com/anhtuan85/Data-Augmentation-for-Object-Detection/blob/master/augmentation.ipynb
2
+
3
+ import PIL #version 1.2.0
4
+ from PIL import Image #version 6.1.0
5
+ import torch
6
+ import os
7
+ import torchvision.transforms.functional as F
8
+ import numpy as np
9
+ import random
10
+
11
+ from .random_crop import random_crop
12
+ from util.box_ops import box_cxcywh_to_xyxy, box_xyxy_to_cxcywh
13
+
14
+ class AdjustContrast:
15
+ def __init__(self, contrast_factor):
16
+ self.contrast_factor = contrast_factor
17
+
18
+ def __call__(self, img, target):
19
+ """
20
+ img (PIL Image or Tensor): Image to be adjusted.
21
+ """
22
+ _contrast_factor = ((random.random() + 1.0) / 2.0) * self.contrast_factor
23
+ img = F.adjust_contrast(img, _contrast_factor)
24
+ return img, target
25
+
26
+ class AdjustBrightness:
27
+ def __init__(self, brightness_factor):
28
+ self.brightness_factor = brightness_factor
29
+
30
+ def __call__(self, img, target):
31
+ """
32
+ img (PIL Image or Tensor): Image to be adjusted.
33
+ """
34
+ _brightness_factor = ((random.random() + 1.0) / 2.0) * self.brightness_factor
35
+ img = F.adjust_brightness(img, _brightness_factor)
36
+ return img, target
37
+
38
+ def lighting_noise(image):
39
+ '''
40
+ color channel swap in image
41
+ image: A PIL image
42
+ '''
43
+ new_image = image
44
+ perms = ((0, 1, 2), (0, 2, 1), (1, 0, 2),
45
+ (1, 2, 0), (2, 0, 1), (2, 1, 0))
46
+ swap = perms[random.randint(0, len(perms)- 1)]
47
+ new_image = F.to_tensor(new_image)
48
+ new_image = new_image[swap, :, :]
49
+ new_image = F.to_pil_image(new_image)
50
+ return new_image
51
+
52
+ class LightingNoise:
53
+ def __init__(self) -> None:
54
+ pass
55
+
56
+ def __call__(self, img, target):
57
+ return lighting_noise(img), target
58
+
59
+
60
+ def rotate(image, boxes, angle):
61
+ '''
62
+ Rotate image and bounding box
63
+ image: A Pil image (w, h)
64
+ boxes: A tensors of dimensions (#objects, 4)
65
+
66
+ Out: rotated image (w, h), rotated boxes
67
+ '''
68
+ new_image = image.copy()
69
+ new_boxes = boxes.clone()
70
+
71
+ #Rotate image, expand = True
72
+ w = image.width
73
+ h = image.height
74
+ cx = w/2
75
+ cy = h/2
76
+ new_image = new_image.rotate(angle, expand=True)
77
+ angle = np.radians(angle)
78
+ alpha = np.cos(angle)
79
+ beta = np.sin(angle)
80
+ #Get affine matrix
81
+ AffineMatrix = torch.tensor([[alpha, beta, (1-alpha)*cx - beta*cy],
82
+ [-beta, alpha, beta*cx + (1-alpha)*cy]])
83
+
84
+ #Rotation boxes
85
+ box_width = (boxes[:,2] - boxes[:,0]).reshape(-1,1)
86
+ box_height = (boxes[:,3] - boxes[:,1]).reshape(-1,1)
87
+
88
+ #Get corners for boxes
89
+ x1 = boxes[:,0].reshape(-1,1)
90
+ y1 = boxes[:,1].reshape(-1,1)
91
+
92
+ x2 = x1 + box_width
93
+ y2 = y1
94
+
95
+ x3 = x1
96
+ y3 = y1 + box_height
97
+
98
+ x4 = boxes[:,2].reshape(-1,1)
99
+ y4 = boxes[:,3].reshape(-1,1)
100
+
101
+ corners = torch.stack((x1,y1,x2,y2,x3,y3,x4,y4), dim= 1)
102
+ # corners.reshape(-1, 8) #Tensors of dimensions (#objects, 8)
103
+ corners = corners.reshape(-1,2) #Tensors of dimension (4* #objects, 2)
104
+ corners = torch.cat((corners, torch.ones(corners.shape[0], 1)), dim= 1) #(Tensors of dimension (4* #objects, 3))
105
+
106
+ cos = np.abs(AffineMatrix[0, 0])
107
+ sin = np.abs(AffineMatrix[0, 1])
108
+
109
+ nW = int((h * sin) + (w * cos))
110
+ nH = int((h * cos) + (w * sin))
111
+ AffineMatrix[0, 2] += (nW / 2) - cx
112
+ AffineMatrix[1, 2] += (nH / 2) - cy
113
+
114
+
115
+ #Apply affine transform
116
+ rotate_corners = torch.mm(AffineMatrix, corners.t().to(torch.float64)).t()
117
+ rotate_corners = rotate_corners.reshape(-1,8)
118
+
119
+ x_corners = rotate_corners[:,[0,2,4,6]]
120
+ y_corners = rotate_corners[:,[1,3,5,7]]
121
+
122
+ #Get (x_min, y_min, x_max, y_max)
123
+ x_min, _ = torch.min(x_corners, dim= 1)
124
+ x_min = x_min.reshape(-1, 1)
125
+ y_min, _ = torch.min(y_corners, dim= 1)
126
+ y_min = y_min.reshape(-1, 1)
127
+ x_max, _ = torch.max(x_corners, dim= 1)
128
+ x_max = x_max.reshape(-1, 1)
129
+ y_max, _ = torch.max(y_corners, dim= 1)
130
+ y_max = y_max.reshape(-1, 1)
131
+
132
+ new_boxes = torch.cat((x_min, y_min, x_max, y_max), dim= 1)
133
+
134
+ scale_x = new_image.width / w
135
+ scale_y = new_image.height / h
136
+
137
+ #Resize new image to (w, h)
138
+
139
+ new_image = new_image.resize((w, h))
140
+
141
+ #Resize boxes
142
+ new_boxes /= torch.Tensor([scale_x, scale_y, scale_x, scale_y])
143
+ new_boxes[:, 0] = torch.clamp(new_boxes[:, 0], 0, w)
144
+ new_boxes[:, 1] = torch.clamp(new_boxes[:, 1], 0, h)
145
+ new_boxes[:, 2] = torch.clamp(new_boxes[:, 2], 0, w)
146
+ new_boxes[:, 3] = torch.clamp(new_boxes[:, 3], 0, h)
147
+ return new_image, new_boxes
148
+
149
+ # def convert_xywh_to_xyxy(boxes: torch.Tensor):
150
+ # _boxes = boxes.clone()
151
+ # box_xy = _boxes[:, :2]
152
+ # box_wh = _boxes[:, 2:]
153
+ # box_x1y1 = box_xy - box_wh/2
154
+ # box_x2y2 = box_xy + box_wh/2
155
+ # box_xyxy = torch.cat((box_x1y1, box_x2y2), dim=-1)
156
+ # return box_xyxy
157
+
158
+ class Rotate:
159
+ def __init__(self, angle=10) -> None:
160
+ self.angle = angle
161
+
162
+ def __call__(self, img, target):
163
+ w,h = img.size
164
+ whwh = torch.Tensor([w, h, w, h])
165
+ boxes_xyxy = box_cxcywh_to_xyxy(target['boxes']) * whwh
166
+ img, boxes_new = rotate(img, boxes_xyxy, self.angle)
167
+ target['boxes'] = box_xyxy_to_cxcywh(boxes_new).to(boxes_xyxy.dtype) / (whwh + 1e-3)
168
+ return img, target
169
+
170
+
171
+ class RandomCrop:
172
+ def __init__(self) -> None:
173
+ pass
174
+
175
+ def __call__(self, img, target):
176
+ w,h = img.size
177
+ try:
178
+ boxes_xyxy = target['boxes']
179
+ labels = target['labels']
180
+ img, new_boxes, new_labels, _ = random_crop(img, boxes_xyxy, labels)
181
+ target['boxes'] = new_boxes
182
+ target['labels'] = new_labels
183
+ except Exception as e:
184
+ pass
185
+ return img, target
186
+
187
+
188
+ class RandomCropDebug:
189
+ def __init__(self) -> None:
190
+ pass
191
+
192
+ def __call__(self, img, target):
193
+ boxes_xyxy = target['boxes'].clone()
194
+ labels = target['labels'].clone()
195
+ img, new_boxes, new_labels, _ = random_crop(img, boxes_xyxy, labels)
196
+ target['boxes'] = new_boxes
197
+ target['labels'] = new_labels
198
+
199
+
200
+ return img, target
201
+
202
+ class RandomSelectMulti(object):
203
+ """
204
+ Randomly selects between transforms1 and transforms2,
205
+ """
206
+ def __init__(self, transformslist, p=-1):
207
+ self.transformslist = transformslist
208
+ self.p = p
209
+ assert p == -1
210
+
211
+ def __call__(self, img, target):
212
+ if self.p == -1:
213
+ return random.choice(self.transformslist)(img, target)
214
+
215
+
216
+ class Albumentations:
217
+ def __init__(self):
218
+ import albumentations as A
219
+ self.transform = A.Compose([
220
+ A.Blur(p=0.01),
221
+ A.MedianBlur(p=0.01),
222
+ A.ToGray(p=0.01),
223
+ A.CLAHE(p=0.01),
224
+ A.RandomBrightnessContrast(p=0.005),
225
+ A.RandomGamma(p=0.005),
226
+ A.ImageCompression(quality_lower=75, p=0.005)],
227
+ bbox_params=A.BboxParams(format='pascal_voc', label_fields=['class_labels']))
228
+
229
+ def __call__(self, img, target, p=1.0):
230
+ """
231
+ Input:
232
+ target['boxes']: xyxy, unnormalized data.
233
+
234
+ """
235
+ boxes_raw = target['boxes']
236
+ labels_raw = target['labels']
237
+ img_np = np.array(img)
238
+ if self.transform and random.random() < p:
239
+ new_res = self.transform(image=img_np, bboxes=boxes_raw, class_labels=labels_raw) # transformed
240
+ boxes_new = torch.Tensor(new_res['bboxes']).to(boxes_raw.dtype).reshape_as(boxes_raw)
241
+ img_np = new_res['image']
242
+ labels_new = torch.Tensor(new_res['class_labels']).to(labels_raw.dtype)
243
+ img_new = Image.fromarray(img_np)
244
+ target['boxes'] = boxes_new
245
+ target['labels'] = labels_new
246
+
247
+ return img_new, target
datasets/transforms.py ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ """
3
+ Transforms and data augmentation for both image + bbox.
4
+ """
5
+ import random
6
+
7
+ import PIL
8
+ import torch
9
+ import torchvision.transforms as T
10
+ import torchvision.transforms.functional as F
11
+
12
+ from util.box_ops import box_xyxy_to_cxcywh
13
+ from util.misc import interpolate
14
+
15
+
16
+ def crop(image, target, region):
17
+ cropped_image = F.crop(image, *region)
18
+
19
+ target = target.copy()
20
+ i, j, h, w = region
21
+
22
+ # should we do something wrt the original size?
23
+ target["size"] = torch.tensor([h, w])
24
+
25
+ fields = ["labels", "area"]
26
+ exemplar_fields = ["exemplar_labels", "exemplar_area", "exemplar_boxes"]
27
+
28
+ if "boxes" in target:
29
+ boxes = target["boxes"]
30
+ max_size = torch.as_tensor([w, h], dtype=torch.float32)
31
+ cropped_boxes = boxes - torch.as_tensor([j, i, j, i])
32
+ cropped_boxes = torch.min(cropped_boxes.reshape(-1, 2, 2), max_size)
33
+ cropped_boxes = cropped_boxes.clamp(min=0)
34
+ area = (cropped_boxes[:, 1, :] - cropped_boxes[:, 0, :]).prod(dim=1)
35
+ target["boxes"] = cropped_boxes.reshape(-1, 4)
36
+ target["area"] = area
37
+ fields.append("boxes")
38
+ if "exemplar_boxes" in target:
39
+ exemplar_boxes = target["exemplar_boxes"]
40
+ max_size = torch.as_tensor([w, h], dtype=torch.float32)
41
+ cropped_exemplar_boxes = exemplar_boxes - torch.as_tensor([j, i, j, i])
42
+ cropped_exemplar_boxes = torch.min(cropped_exemplar_boxes.reshape(-1, 2, 2), max_size)
43
+ cropped_exemplar_boxes = cropped_exemplar_boxes.clamp(min=0)
44
+ exemplar_area = (cropped_exemplar_boxes[:, 1, :] - cropped_exemplar_boxes[:, 0, :]).prod(dim=1)
45
+ target["exemplar_boxes"] = cropped_exemplar_boxes.reshape(-1, 4)
46
+ target["exemplar_area"] = exemplar_area
47
+
48
+ if "masks" in target:
49
+ # FIXME should we update the area here if there are no boxes?
50
+ target['masks'] = target['masks'][:, i:i + h, j:j + w]
51
+ fields.append("masks")
52
+
53
+ # remove elements for which the boxes or masks that have zero area
54
+ if "boxes" in target or "masks" in target:
55
+ # favor boxes selection when defining which elements to keep
56
+ # this is compatible with previous implementation
57
+ if "boxes" in target:
58
+ cropped_boxes = target['boxes'].reshape(-1, 2, 2)
59
+ keep = torch.all(cropped_boxes[:, 1, :] > cropped_boxes[:, 0, :], dim=1)
60
+ else:
61
+ keep = target['masks'].flatten(1).any(1)
62
+
63
+ for field in fields:
64
+ target[field] = target[field][keep]
65
+
66
+ # remove exemplar elements for which the boxes have zero area
67
+ if "exemplar_boxes" in target:
68
+ cropped_exemplar_boxes = target["exemplar_boxes"].reshape(-1, 2, 2)
69
+ keep = torch.all(cropped_exemplar_boxes[:, 1, :] > cropped_exemplar_boxes[:, 0, :], dim=1)
70
+ for field in exemplar_fields:
71
+ target[field] = target[field][keep]
72
+
73
+ return cropped_image, target
74
+
75
+
76
+ def hflip(image, target):
77
+ flipped_image = F.hflip(image)
78
+
79
+ w, h = image.size
80
+
81
+ target = target.copy()
82
+
83
+ if "boxes" in target:
84
+ boxes = target["boxes"]
85
+ boxes = boxes[:, [2, 1, 0, 3]] * torch.as_tensor([-1, 1, -1, 1]) + torch.as_tensor([w, 0, w, 0])
86
+ target["boxes"] = boxes
87
+
88
+ if "exemplar_boxes" in target:
89
+ exemplar_boxes = target["exemplar_boxes"]
90
+ exemplar_boxes = exemplar_boxes[:, [2, 1, 0, 3]] * torch.as_tensor([-1, 1, -1, 1]) + torch.as_tensor([w, 0, w, 0])
91
+ target["exemplar_boxes"] = exemplar_boxes
92
+
93
+ if "masks" in target:
94
+ target['masks'] = target['masks'].flip(-1)
95
+
96
+ return flipped_image, target
97
+
98
+
99
+ def resize(image, target, size, max_size=None):
100
+ # size can be min_size (scalar) or (w, h) tuple
101
+
102
+ def get_size_with_aspect_ratio(image_size, size, max_size=None):
103
+ w, h = image_size
104
+ if max_size is not None:
105
+ min_original_size = float(min((w, h)))
106
+ max_original_size = float(max((w, h)))
107
+ if max_original_size / min_original_size * size > max_size:
108
+ size = int(round(max_size * min_original_size / max_original_size))
109
+
110
+ if (w <= h and w == size) or (h <= w and h == size):
111
+ return (h, w)
112
+
113
+ if w < h:
114
+ ow = size
115
+ oh = int(size * h / w)
116
+ else:
117
+ oh = size
118
+ ow = int(size * w / h)
119
+
120
+ return (oh, ow)
121
+
122
+ def get_size(image_size, size, max_size=None):
123
+ if isinstance(size, (list, tuple)):
124
+ return size[::-1]
125
+ else:
126
+ return get_size_with_aspect_ratio(image_size, size, max_size)
127
+
128
+ size = get_size(image.size, size, max_size)
129
+ rescaled_image = F.resize(image, size)
130
+
131
+ if target is None:
132
+ return rescaled_image, None
133
+
134
+ ratios = tuple(float(s) / float(s_orig) for s, s_orig in zip(rescaled_image.size, image.size))
135
+ ratio_width, ratio_height = ratios
136
+
137
+ target = target.copy()
138
+
139
+ if "boxes" in target:
140
+ boxes = target["boxes"]
141
+ scaled_boxes = boxes * torch.as_tensor([ratio_width, ratio_height, ratio_width, ratio_height])
142
+ target["boxes"] = scaled_boxes
143
+
144
+ if "area" in target:
145
+ area = target["area"]
146
+ scaled_area = area * (ratio_width * ratio_height)
147
+ target["area"] = scaled_area
148
+
149
+ if "exemplar_boxes" in target:
150
+ exemplar_boxes = target["exemplar_boxes"]
151
+ scaled_exemplar_boxes = exemplar_boxes * torch.as_tensor([ratio_width, ratio_height, ratio_width, ratio_height])
152
+ target["exemplar_boxes"] = scaled_exemplar_boxes
153
+ if "exemplar_area" in target:
154
+ exemplar_area = target["exemplar_area"]
155
+ scaled_exemplar_area = exemplar_area * (ratio_width * ratio_height)
156
+ target["exemplar_area"] = scaled_exemplar_area
157
+
158
+ h, w = size
159
+ target["size"] = torch.tensor([h, w])
160
+
161
+ if "masks" in target:
162
+ target['masks'] = interpolate(
163
+ target['masks'][:, None].float(), size, mode="nearest")[:, 0] > 0.5
164
+
165
+ return rescaled_image, target
166
+
167
+
168
+ def pad(image, target, padding):
169
+ # assumes that we only pad on the bottom right corners
170
+ padded_image = F.pad(image, (0, 0, padding[0], padding[1]))
171
+ if target is None:
172
+ return padded_image, None
173
+ target = target.copy()
174
+ # should we do something wrt the original size?
175
+ target["size"] = torch.tensor(padded_image.size[::-1])
176
+ if "masks" in target:
177
+ target['masks'] = torch.nn.functional.pad(target['masks'], (0, padding[0], 0, padding[1]))
178
+ return padded_image, target
179
+
180
+
181
+ class ResizeDebug(object):
182
+ def __init__(self, size):
183
+ self.size = size
184
+
185
+ def __call__(self, img, target):
186
+ return resize(img, target, self.size)
187
+
188
+
189
+ class RandomCrop(object):
190
+ def __init__(self, size):
191
+ self.size = size
192
+
193
+ def __call__(self, img, target):
194
+ region = T.RandomCrop.get_params(img, self.size)
195
+ return crop(img, target, region)
196
+
197
+
198
+ class RandomSizeCrop(object):
199
+ def __init__(self, min_size: int, max_size: int):
200
+ self.min_size = min_size
201
+ self.max_size = max_size
202
+
203
+ def __call__(self, img: PIL.Image.Image, target: dict):
204
+ w = random.randint(self.min_size, min(img.width, self.max_size))
205
+ h = random.randint(self.min_size, min(img.height, self.max_size))
206
+ region = T.RandomCrop.get_params(img, [h, w])
207
+ return crop(img, target, region)
208
+
209
+
210
+ class CenterCrop(object):
211
+ def __init__(self, size):
212
+ self.size = size
213
+
214
+ def __call__(self, img, target):
215
+ image_width, image_height = img.size
216
+ crop_height, crop_width = self.size
217
+ crop_top = int(round((image_height - crop_height) / 2.))
218
+ crop_left = int(round((image_width - crop_width) / 2.))
219
+ return crop(img, target, (crop_top, crop_left, crop_height, crop_width))
220
+
221
+
222
+ class RandomHorizontalFlip(object):
223
+ def __init__(self, p=0.5):
224
+ self.p = p
225
+
226
+ def __call__(self, img, target):
227
+ if random.random() < self.p:
228
+ return hflip(img, target)
229
+ return img, target
230
+
231
+
232
+ class RandomResize(object):
233
+ def __init__(self, sizes, max_size=None):
234
+ assert isinstance(sizes, (list, tuple))
235
+ self.sizes = sizes
236
+ self.max_size = max_size
237
+
238
+ def __call__(self, img, target=None):
239
+ size = random.choice(self.sizes)
240
+ return resize(img, target, size, self.max_size)
241
+
242
+
243
+ class RandomPad(object):
244
+ def __init__(self, max_pad):
245
+ self.max_pad = max_pad
246
+
247
+ def __call__(self, img, target):
248
+ pad_x = random.randint(0, self.max_pad)
249
+ pad_y = random.randint(0, self.max_pad)
250
+ return pad(img, target, (pad_x, pad_y))
251
+
252
+
253
+ class RandomSelect(object):
254
+ """
255
+ Randomly selects between transforms1 and transforms2,
256
+ with probability p for transforms1 and (1 - p) for transforms2
257
+ """
258
+ def __init__(self, transforms1, transforms2, p=0.5):
259
+ self.transforms1 = transforms1
260
+ self.transforms2 = transforms2
261
+ self.p = p
262
+
263
+ def __call__(self, img, target):
264
+ if random.random() < self.p:
265
+ return self.transforms1(img, target)
266
+ return self.transforms2(img, target)
267
+
268
+
269
+ class ToTensor(object):
270
+ def __call__(self, img, target):
271
+ return F.to_tensor(img), target
272
+
273
+
274
+ class RandomErasing(object):
275
+
276
+ def __init__(self, *args, **kwargs):
277
+ self.eraser = T.RandomErasing(*args, **kwargs)
278
+
279
+ def __call__(self, img, target):
280
+ return self.eraser(img), target
281
+
282
+
283
+ class Normalize(object):
284
+ def __init__(self, mean, std):
285
+ self.mean = mean
286
+ self.std = std
287
+
288
+ def __call__(self, image, target=None):
289
+ image = F.normalize(image, mean=self.mean, std=self.std)
290
+ if target is None:
291
+ return image, None
292
+ target = target.copy()
293
+ h, w = image.shape[-2:]
294
+
295
+ if "boxes" in target:
296
+ boxes = target["boxes"]
297
+ boxes = box_xyxy_to_cxcywh(boxes)
298
+ boxes = boxes / torch.tensor([w, h, w, h], dtype=torch.float32)
299
+ target["boxes"] = boxes
300
+
301
+ if "exemplar_boxes" in target:
302
+ exemplar_boxes = target["exemplar_boxes"]
303
+ exemplar_boxes = box_xyxy_to_cxcywh(exemplar_boxes)
304
+ exemplar_boxes = exemplar_boxes / torch.tensor([w, h, w, h], dtype=torch.float32)
305
+ target["exemplar_boxes"] = exemplar_boxes
306
+ return image, target
307
+
308
+
309
+ class Compose(object):
310
+ def __init__(self, transforms):
311
+ self.transforms = transforms
312
+
313
+ def __call__(self, image, target):
314
+ for t in self.transforms:
315
+ image, target = t(image, target)
316
+ return image, target
317
+
318
+ def __repr__(self):
319
+ format_string = self.__class__.__name__ + "("
320
+ for t in self.transforms:
321
+ format_string += "\n"
322
+ format_string += " {0}".format(t)
323
+ format_string += "\n)"
324
+ return format_string
datasets/transforms_app.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ """
3
+ Transforms and data augmentation for both image + bbox.
4
+ """
5
+ import random
6
+
7
+ import PIL
8
+ import torch
9
+ import torchvision.transforms as T
10
+ import torchvision.transforms.functional as F
11
+
12
+ from util.box_ops import box_xyxy_to_cxcywh
13
+ from util.misc import interpolate
14
+
15
+
16
+ def crop(image, target, region):
17
+ cropped_image = F.crop(image, *region)
18
+
19
+ target = target.copy()
20
+ i, j, h, w = region
21
+
22
+ # should we do something wrt the original size?
23
+ target["size"] = torch.tensor([h, w])
24
+
25
+ fields = ["labels", "area"]
26
+
27
+ # Crop exemplars.
28
+ exemplars = target["exemplars"]
29
+ max_size = torch.as_tensor([w, h], dtype=torch.float32)
30
+ # Shift exemplars to cropped region.
31
+ cropped_exemplars = exemplars - torch.as_tensor([j, i, j, i])
32
+ # Correct exemplar regions that go past new image boundary (too far right).
33
+ cropped_exemplars = torch.min(cropped_exemplars.reshape(-1, 2, 2), max_size)
34
+ # Correct exemplar regions that go past new image boundary (too far left).
35
+ cropped_exemplars = cropped_exemplars.clamp(min=0)
36
+ # Get new exemplar areas.
37
+ area_exemplars = (cropped_exemplars[:, 1, :] - cropped_exemplars[:, 0, :]).prod(
38
+ dim=1
39
+ )
40
+ # Update [target] with cropped exemplars.
41
+ target["exemplars"] = cropped_exemplars.reshape(-1, 4)
42
+ if "boxes" in target:
43
+ boxes = target["boxes"]
44
+ max_size = torch.as_tensor([w, h], dtype=torch.float32)
45
+ cropped_boxes = boxes - torch.as_tensor([j, i, j, i])
46
+ cropped_boxes = torch.min(cropped_boxes.reshape(-1, 2, 2), max_size)
47
+ cropped_boxes = cropped_boxes.clamp(min=0)
48
+ area = (cropped_boxes[:, 1, :] - cropped_boxes[:, 0, :]).prod(dim=1)
49
+ target["boxes"] = cropped_boxes.reshape(-1, 4)
50
+ target["area"] = area
51
+ fields.append("boxes")
52
+
53
+ if "masks" in target:
54
+ # FIXME should we update the area here if there are no boxes?
55
+ target["masks"] = target["masks"][:, i : i + h, j : j + w]
56
+ fields.append("masks")
57
+
58
+ # Remove exemplars that have zero area (due to cropping).
59
+ keep = area_exemplars > 0
60
+ target["exemplars"] = target["exemplars"][keep, :]
61
+
62
+ # remove elements for which the boxes or masks that have zero area
63
+ if "boxes" in target or "masks" in target:
64
+ # favor boxes selection when defining which elements to keep
65
+ # this is compatible with previous implementation
66
+ if "boxes" in target:
67
+ cropped_boxes = target["boxes"].reshape(-1, 2, 2)
68
+ keep = torch.all(cropped_boxes[:, 1, :] > cropped_boxes[:, 0, :], dim=1)
69
+ else:
70
+ keep = target["masks"].flatten(1).any(1)
71
+
72
+ for field in fields:
73
+ target[field] = target[field][keep]
74
+
75
+ return cropped_image, target
76
+
77
+
78
+ def hflip(image, target):
79
+ flipped_image = F.hflip(image)
80
+
81
+ w, h = image.size
82
+
83
+ target = target.copy()
84
+
85
+ exemplars = target["exemplars"]
86
+ # Flip image across x-axis.
87
+ exemplars = exemplars[:, [2, 1, 0, 3]] * torch.as_tensor([-1, 1, -1, 1])
88
+ # Shift flipped image to (0, 0).
89
+ exemplars = exemplars + torch.as_tensor([w, 0, w, 0])
90
+ # Update [target] with horizontally flipped exemplars.
91
+ target["exemplars"] = exemplars
92
+
93
+ if "boxes" in target:
94
+ boxes = target["boxes"]
95
+ boxes = boxes[:, [2, 1, 0, 3]] * torch.as_tensor(
96
+ [-1, 1, -1, 1]
97
+ ) + torch.as_tensor([w, 0, w, 0])
98
+ target["boxes"] = boxes
99
+
100
+ if "masks" in target:
101
+ target["masks"] = target["masks"].flip(-1)
102
+
103
+ return flipped_image, target
104
+
105
+
106
+ def resize(image, target, size, max_size=None):
107
+ # size can be min_size (scalar) or (w, h) tuple
108
+
109
+ def get_size_with_aspect_ratio(image_size, size, max_size=None):
110
+ w, h = image_size
111
+ if max_size is not None:
112
+ min_original_size = float(min((w, h)))
113
+ max_original_size = float(max((w, h)))
114
+ if max_original_size / min_original_size * size > max_size:
115
+ size = int(round(max_size * min_original_size / max_original_size))
116
+
117
+ if (w <= h and w == size) or (h <= w and h == size):
118
+ return (h, w)
119
+
120
+ if w < h:
121
+ ow = size
122
+ oh = int(size * h / w)
123
+ else:
124
+ oh = size
125
+ ow = int(size * w / h)
126
+
127
+ return (oh, ow)
128
+
129
+ def get_size(image_size, size, max_size=None):
130
+ if isinstance(size, (list, tuple)):
131
+ return size[::-1]
132
+ else:
133
+ return get_size_with_aspect_ratio(image_size, size, max_size)
134
+
135
+ try:
136
+ size = get_size(image.size, size, max_size)
137
+ except:
138
+ size = get_size((image.shape[-1], image.shape[-2]), size, max_size)
139
+ rescaled_image = F.resize(image, size)
140
+
141
+ if target is None:
142
+ return rescaled_image, None
143
+
144
+ ratios = tuple(
145
+ float(s) / float(s_orig) for s, s_orig in zip(rescaled_image.size, image.size)
146
+ )
147
+ ratio_width, ratio_height = ratios
148
+
149
+ target = target.copy()
150
+
151
+ # Rescale exemplars.
152
+ exemplars = target["exemplars"]
153
+ if exemplars.shape[-1] == 4:
154
+ scaled_exemplars = exemplars * torch.as_tensor(
155
+ [ratio_width, ratio_height, ratio_width, ratio_height]
156
+ )
157
+ else:
158
+ scaled_exemplars = exemplars
159
+
160
+ target["exemplars"] = scaled_exemplars
161
+
162
+ if "boxes" in target:
163
+ boxes = target["boxes"]
164
+ scaled_boxes = boxes * torch.as_tensor(
165
+ [ratio_width, ratio_height, ratio_width, ratio_height]
166
+ )
167
+ target["boxes"] = scaled_boxes
168
+
169
+ if "area" in target:
170
+ area = target["area"]
171
+ scaled_area = area * (ratio_width * ratio_height)
172
+ target["area"] = scaled_area
173
+
174
+ h, w = size
175
+ target["size"] = torch.tensor([h, w])
176
+
177
+ if "masks" in target:
178
+ target["masks"] = (
179
+ interpolate(target["masks"][:, None].float(), size, mode="nearest")[:, 0]
180
+ > 0.5
181
+ )
182
+
183
+ return rescaled_image, target
184
+
185
+
186
+ def pad(image, target, padding):
187
+ # assumes that we only pad on the bottom right corners
188
+ padded_image = F.pad(image, (0, 0, padding[0], padding[1]))
189
+ if target is None:
190
+ return padded_image, None
191
+ target = target.copy()
192
+ # should we do something wrt the original size?
193
+ target["size"] = torch.tensor(padded_image.size[::-1])
194
+ if "masks" in target:
195
+ target["masks"] = torch.nn.functional.pad(
196
+ target["masks"], (0, padding[0], 0, padding[1])
197
+ )
198
+ return padded_image, target
199
+
200
+
201
+ class ResizeDebug(object):
202
+ def __init__(self, size):
203
+ self.size = size
204
+
205
+ def __call__(self, img, target):
206
+ return resize(img, target, self.size)
207
+
208
+
209
+ class RandomCrop(object):
210
+ def __init__(self, size):
211
+ self.size = size
212
+
213
+ def __call__(self, img, target):
214
+ region = T.RandomCrop.get_params(img, self.size)
215
+ return crop(img, target, region)
216
+
217
+
218
+ class RandomSizeCrop(object):
219
+ def __init__(self, min_size: int, max_size: int):
220
+ self.min_size = min_size
221
+ self.max_size = max_size
222
+
223
+ def __call__(self, img: PIL.Image.Image, target: dict):
224
+ w = random.randint(self.min_size, min(img.width, self.max_size))
225
+ h = random.randint(self.min_size, min(img.height, self.max_size))
226
+ region = T.RandomCrop.get_params(img, [h, w])
227
+ return crop(img, target, region)
228
+
229
+
230
+ class CenterCrop(object):
231
+ def __init__(self, size):
232
+ self.size = size
233
+
234
+ def __call__(self, img, target):
235
+ image_width, image_height = img.size
236
+ crop_height, crop_width = self.size
237
+ crop_top = int(round((image_height - crop_height) / 2.0))
238
+ crop_left = int(round((image_width - crop_width) / 2.0))
239
+ return crop(img, target, (crop_top, crop_left, crop_height, crop_width))
240
+
241
+
242
+ class RandomHorizontalFlip(object):
243
+ def __init__(self, p=0.5):
244
+ self.p = p
245
+
246
+ def __call__(self, img, target):
247
+ if random.random() < self.p:
248
+ return hflip(img, target)
249
+ return img, target
250
+
251
+
252
+ class RandomResize(object):
253
+ def __init__(self, sizes, max_size=None):
254
+ assert isinstance(sizes, (list, tuple))
255
+ self.sizes = sizes
256
+ self.max_size = max_size
257
+
258
+ def __call__(self, img, target=None):
259
+ size = random.choice(self.sizes)
260
+ return resize(img, target, size, self.max_size)
261
+
262
+
263
+ class RandomPad(object):
264
+ def __init__(self, max_pad):
265
+ self.max_pad = max_pad
266
+
267
+ def __call__(self, img, target):
268
+ pad_x = random.randint(0, self.max_pad)
269
+ pad_y = random.randint(0, self.max_pad)
270
+ return pad(img, target, (pad_x, pad_y))
271
+
272
+
273
+ class RandomSelect(object):
274
+ """
275
+ Randomly selects between transforms1 and transforms2,
276
+ with probability p for transforms1 and (1 - p) for transforms2
277
+ """
278
+
279
+ def __init__(self, transforms1, transforms2, p=0.5):
280
+ self.transforms1 = transforms1
281
+ self.transforms2 = transforms2
282
+ self.p = p
283
+
284
+ def __call__(self, img, target):
285
+ if random.random() < self.p:
286
+ return self.transforms1(img, target)
287
+ return self.transforms2(img, target)
288
+
289
+
290
+ class ToTensor(object):
291
+ def __call__(self, img, target):
292
+ return F.to_tensor(img), target
293
+
294
+
295
+ class RandomErasing(object):
296
+ def __init__(self, *args, **kwargs):
297
+ self.eraser = T.RandomErasing(*args, **kwargs)
298
+
299
+ def __call__(self, img, target):
300
+ return self.eraser(img), target
301
+
302
+
303
+ class Normalize(object):
304
+ def __init__(self, mean, std):
305
+ self.mean = mean
306
+ self.std = std
307
+
308
+ def __call__(self, image, target=None):
309
+ image = F.normalize(image, mean=self.mean, std=self.std)
310
+ if target is None:
311
+ return image, None
312
+ target = target.copy()
313
+ h, w = image.shape[-2:]
314
+ # No normalization of exemplars needed, since they are used directly for cropping.
315
+ if "boxes" in target:
316
+ boxes = target["boxes"]
317
+ boxes = box_xyxy_to_cxcywh(boxes)
318
+ boxes = boxes / torch.tensor([w, h, w, h], dtype=torch.float32)
319
+ target["boxes"] = boxes
320
+ return image, target
321
+
322
+
323
+ class Compose(object):
324
+ def __init__(self, transforms):
325
+ self.transforms = transforms
326
+
327
+ def __call__(self, image, target):
328
+ for t in self.transforms:
329
+ image, target = t(image, target)
330
+ return image, target
331
+
332
+ def __repr__(self):
333
+ format_string = self.__class__.__name__ + "("
334
+ for t in self.transforms:
335
+ format_string += "\n"
336
+ format_string += " {0}".format(t)
337
+ format_string += "\n)"
338
+ return format_string
datasets/transforms_orig.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ """
3
+ Transforms and data augmentation for both image + bbox.
4
+ """
5
+ import random
6
+
7
+ import PIL
8
+ import torch
9
+ import torchvision.transforms as T
10
+ import torchvision.transforms.functional as F
11
+
12
+ from util.box_ops import box_xyxy_to_cxcywh
13
+ from util.misc import interpolate
14
+
15
+
16
+ def crop(image, target, region):
17
+ cropped_image = F.crop(image, *region)
18
+
19
+ target = target.copy()
20
+ i, j, h, w = region
21
+
22
+ # should we do something wrt the original size?
23
+ target["size"] = torch.tensor([h, w])
24
+
25
+ fields = ["labels", "area"]
26
+
27
+ # Crop exemplars.
28
+ exemplars = target["exemplars"]
29
+ max_size = torch.as_tensor([w, h], dtype=torch.float32)
30
+ # Shift exemplars to cropped region.
31
+ cropped_exemplars = exemplars - torch.as_tensor([j, i, j, i])
32
+ # Correct exemplar regions that go past new image boundary (too far right).
33
+ cropped_exemplars = torch.min(cropped_exemplars.reshape(-1, 2, 2), max_size)
34
+ # Correct exemplar regions that go past new image boundary (too far left).
35
+ cropped_exemplars = cropped_exemplars.clamp(min=0)
36
+ # Get new exemplar areas.
37
+ area_exemplars = (cropped_exemplars[:, 1, :] - cropped_exemplars[:, 0, :]).prod(dim=1)
38
+ # Update [target] with cropped exemplars.
39
+ target["exemplars"] = cropped_exemplars.reshape(-1, 4)
40
+ if "boxes" in target:
41
+ boxes = target["boxes"]
42
+ max_size = torch.as_tensor([w, h], dtype=torch.float32)
43
+ cropped_boxes = boxes - torch.as_tensor([j, i, j, i])
44
+ cropped_boxes = torch.min(cropped_boxes.reshape(-1, 2, 2), max_size)
45
+ cropped_boxes = cropped_boxes.clamp(min=0)
46
+ area = (cropped_boxes[:, 1, :] - cropped_boxes[:, 0, :]).prod(dim=1)
47
+ target["boxes"] = cropped_boxes.reshape(-1, 4)
48
+ target["area"] = area
49
+ fields.append("boxes")
50
+
51
+ if "masks" in target:
52
+ # FIXME should we update the area here if there are no boxes?
53
+ target['masks'] = target['masks'][:, i:i + h, j:j + w]
54
+ fields.append("masks")
55
+
56
+ # Remove exemplars that have zero area (due to cropping).
57
+ keep = area_exemplars > 0
58
+ target["exemplars"] = target["exemplars"][keep, :]
59
+
60
+ # remove elements for which the boxes or masks that have zero area
61
+ if "boxes" in target or "masks" in target:
62
+ # favor boxes selection when defining which elements to keep
63
+ # this is compatible with previous implementation
64
+ if "boxes" in target:
65
+ cropped_boxes = target['boxes'].reshape(-1, 2, 2)
66
+ keep = torch.all(cropped_boxes[:, 1, :] > cropped_boxes[:, 0, :], dim=1)
67
+ else:
68
+ keep = target['masks'].flatten(1).any(1)
69
+
70
+ for field in fields:
71
+ target[field] = target[field][keep]
72
+
73
+ return cropped_image, target
74
+
75
+
76
+ def hflip(image, target):
77
+ flipped_image = F.hflip(image)
78
+
79
+ w, h = image.size
80
+
81
+ target = target.copy()
82
+
83
+ exemplars = target["exemplars"]
84
+ # Flip image across x-axis.
85
+ exemplars = exemplars[:, [2, 1, 0, 3]] * torch.as_tensor([-1, 1, -1, 1])
86
+ # Shift flipped image to (0, 0).
87
+ exemplars = exemplars + torch.as_tensor([w, 0, w, 0])
88
+ # Update [target] with horizontally flipped exemplars.
89
+ target["exemplars"] = exemplars
90
+
91
+ if "boxes" in target:
92
+ boxes = target["boxes"]
93
+ boxes = boxes[:, [2, 1, 0, 3]] * torch.as_tensor([-1, 1, -1, 1]) + torch.as_tensor([w, 0, w, 0])
94
+ target["boxes"] = boxes
95
+
96
+ if "masks" in target:
97
+ target['masks'] = target['masks'].flip(-1)
98
+
99
+ return flipped_image, target
100
+
101
+
102
+ def resize(image, target, size, max_size=None):
103
+ # size can be min_size (scalar) or (w, h) tuple
104
+
105
+ def get_size_with_aspect_ratio(image_size, size, max_size=None):
106
+ w, h = image_size
107
+ if max_size is not None:
108
+ min_original_size = float(min((w, h)))
109
+ max_original_size = float(max((w, h)))
110
+ if max_original_size / min_original_size * size > max_size:
111
+ size = int(round(max_size * min_original_size / max_original_size))
112
+
113
+ if (w <= h and w == size) or (h <= w and h == size):
114
+ return (h, w)
115
+
116
+ if w < h:
117
+ ow = size
118
+ oh = int(size * h / w)
119
+ else:
120
+ oh = size
121
+ ow = int(size * w / h)
122
+
123
+ return (oh, ow)
124
+
125
+ def get_size(image_size, size, max_size=None):
126
+ if isinstance(size, (list, tuple)):
127
+ return size[::-1]
128
+ else:
129
+ return get_size_with_aspect_ratio(image_size, size, max_size)
130
+
131
+ size = get_size(image.size, size, max_size)
132
+ rescaled_image = F.resize(image, size)
133
+
134
+ if target is None:
135
+ return rescaled_image, None
136
+
137
+ ratios = tuple(float(s) / float(s_orig) for s, s_orig in zip(rescaled_image.size, image.size))
138
+ ratio_width, ratio_height = ratios
139
+
140
+ target = target.copy()
141
+
142
+ # Rescale exemplars.
143
+ exemplars = target["exemplars"]
144
+ scaled_exemplars = exemplars * torch.as_tensor([ratio_width, ratio_height, ratio_width, ratio_height])
145
+ target["exemplars"] = scaled_exemplars
146
+
147
+ if "boxes" in target:
148
+ boxes = target["boxes"]
149
+ scaled_boxes = boxes * torch.as_tensor([ratio_width, ratio_height, ratio_width, ratio_height])
150
+ target["boxes"] = scaled_boxes
151
+
152
+ if "area" in target:
153
+ area = target["area"]
154
+ scaled_area = area * (ratio_width * ratio_height)
155
+ target["area"] = scaled_area
156
+
157
+ h, w = size
158
+ target["size"] = torch.tensor([h, w])
159
+
160
+ if "masks" in target:
161
+ target['masks'] = interpolate(
162
+ target['masks'][:, None].float(), size, mode="nearest")[:, 0] > 0.5
163
+
164
+ return rescaled_image, target
165
+
166
+
167
+ def pad(image, target, padding):
168
+ # assumes that we only pad on the bottom right corners
169
+ padded_image = F.pad(image, (0, 0, padding[0], padding[1]))
170
+ if target is None:
171
+ return padded_image, None
172
+ target = target.copy()
173
+ # should we do something wrt the original size?
174
+ target["size"] = torch.tensor(padded_image.size[::-1])
175
+ if "masks" in target:
176
+ target['masks'] = torch.nn.functional.pad(target['masks'], (0, padding[0], 0, padding[1]))
177
+ return padded_image, target
178
+
179
+
180
+ class ResizeDebug(object):
181
+ def __init__(self, size):
182
+ self.size = size
183
+
184
+ def __call__(self, img, target):
185
+ return resize(img, target, self.size)
186
+
187
+
188
+ class RandomCrop(object):
189
+ def __init__(self, size):
190
+ self.size = size
191
+
192
+ def __call__(self, img, target):
193
+ region = T.RandomCrop.get_params(img, self.size)
194
+ return crop(img, target, region)
195
+
196
+
197
+ class RandomSizeCrop(object):
198
+ def __init__(self, min_size: int, max_size: int):
199
+ self.min_size = min_size
200
+ self.max_size = max_size
201
+
202
+ def __call__(self, img: PIL.Image.Image, target: dict):
203
+ w = random.randint(self.min_size, min(img.width, self.max_size))
204
+ h = random.randint(self.min_size, min(img.height, self.max_size))
205
+ region = T.RandomCrop.get_params(img, [h, w])
206
+ return crop(img, target, region)
207
+
208
+
209
+ class CenterCrop(object):
210
+ def __init__(self, size):
211
+ self.size = size
212
+
213
+ def __call__(self, img, target):
214
+ image_width, image_height = img.size
215
+ crop_height, crop_width = self.size
216
+ crop_top = int(round((image_height - crop_height) / 2.))
217
+ crop_left = int(round((image_width - crop_width) / 2.))
218
+ return crop(img, target, (crop_top, crop_left, crop_height, crop_width))
219
+
220
+
221
+ class RandomHorizontalFlip(object):
222
+ def __init__(self, p=0.5):
223
+ self.p = p
224
+
225
+ def __call__(self, img, target):
226
+ if random.random() < self.p:
227
+ return hflip(img, target)
228
+ return img, target
229
+
230
+
231
+ class RandomResize(object):
232
+ def __init__(self, sizes, max_size=None):
233
+ assert isinstance(sizes, (list, tuple))
234
+ self.sizes = sizes
235
+ self.max_size = max_size
236
+
237
+ def __call__(self, img, target=None):
238
+ size = random.choice(self.sizes)
239
+ return resize(img, target, size, self.max_size)
240
+
241
+
242
+ class RandomPad(object):
243
+ def __init__(self, max_pad):
244
+ self.max_pad = max_pad
245
+
246
+ def __call__(self, img, target):
247
+ pad_x = random.randint(0, self.max_pad)
248
+ pad_y = random.randint(0, self.max_pad)
249
+ return pad(img, target, (pad_x, pad_y))
250
+
251
+
252
+ class RandomSelect(object):
253
+ """
254
+ Randomly selects between transforms1 and transforms2,
255
+ with probability p for transforms1 and (1 - p) for transforms2
256
+ """
257
+ def __init__(self, transforms1, transforms2, p=0.5):
258
+ self.transforms1 = transforms1
259
+ self.transforms2 = transforms2
260
+ self.p = p
261
+
262
+ def __call__(self, img, target):
263
+ if random.random() < self.p:
264
+ return self.transforms1(img, target)
265
+ return self.transforms2(img, target)
266
+
267
+
268
+ class ToTensor(object):
269
+ def __call__(self, img, target):
270
+ return F.to_tensor(img), target
271
+
272
+
273
+ class RandomErasing(object):
274
+
275
+ def __init__(self, *args, **kwargs):
276
+ self.eraser = T.RandomErasing(*args, **kwargs)
277
+
278
+ def __call__(self, img, target):
279
+ return self.eraser(img), target
280
+
281
+
282
+ class Normalize(object):
283
+ def __init__(self, mean, std):
284
+ self.mean = mean
285
+ self.std = std
286
+
287
+ def __call__(self, image, target=None):
288
+ image = F.normalize(image, mean=self.mean, std=self.std)
289
+ if target is None:
290
+ return image, None
291
+ target = target.copy()
292
+ h, w = image.shape[-2:]
293
+ # No normalization of exemplars needed, since they are used directly for cropping.
294
+ if "boxes" in target:
295
+ boxes = target["boxes"]
296
+ boxes = box_xyxy_to_cxcywh(boxes)
297
+ boxes = boxes / torch.tensor([w, h, w, h], dtype=torch.float32)
298
+ target["boxes"] = boxes
299
+ return image, target
300
+
301
+
302
+ class Compose(object):
303
+ def __init__(self, transforms):
304
+ self.transforms = transforms
305
+
306
+ def __call__(self, image, target):
307
+ for t in self.transforms:
308
+ image, target = t(image, target)
309
+ return image, target
310
+
311
+ def __repr__(self):
312
+ format_string = self.__class__.__name__ + "("
313
+ for t in self.transforms:
314
+ format_string += "\n"
315
+ format_string += " {0}".format(t)
316
+ format_string += "\n)"
317
+ return format_string
example.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Count strawberries (not blueberries) in img/336.jpg and save the detections.
2
+
3
+ python example.py [--repo nikigoli/CountGDPlusPlus] [--device cuda]
4
+ """
5
+ import argparse
6
+ import os
7
+
8
+ import torch
9
+ from PIL import Image, ImageDraw
10
+
11
+ from countgdpp import CountGDPlusPlus, count
12
+
13
+ HERE = os.path.dirname(os.path.abspath(__file__))
14
+
15
+ parser = argparse.ArgumentParser()
16
+ parser.add_argument("--repo", default="nikigoli/CountGDPlusPlus")
17
+ parser.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
18
+ args = parser.parse_args()
19
+
20
+ model = CountGDPlusPlus.from_pretrained(args.repo).to(args.device).eval()
21
+
22
+ image_path = os.path.join(HERE, "img", "336.jpg")
23
+ result = count(model, image_path, "strawberry", negative_texts=["blueberry"])
24
+ print("Predicted count:", result["count"])
25
+
26
+ image = Image.open(image_path).convert("RGB")
27
+ w, h = image.size
28
+ draw = ImageDraw.Draw(image)
29
+ for cx, cy, bw, bh in result["boxes"]:
30
+ draw.rectangle([(cx - bw / 2) * w, (cy - bh / 2) * h, (cx + bw / 2) * w, (cy + bh / 2) * h],
31
+ outline="cyan", width=3)
32
+ image.save("detections.png")
33
+ print("Saved detections.png")
groundingdino/util/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
groundingdino/util/box_ops.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ """
3
+ Utilities for bounding box manipulation and GIoU.
4
+ """
5
+ import torch
6
+ from torchvision.ops.boxes import box_area
7
+
8
+
9
+ def box_cxcywh_to_xyxy(x):
10
+ x_c, y_c, w, h = x.unbind(-1)
11
+ b = [(x_c - 0.5 * w), (y_c - 0.5 * h), (x_c + 0.5 * w), (y_c + 0.5 * h)]
12
+ return torch.stack(b, dim=-1)
13
+
14
+
15
+ def box_xyxy_to_cxcywh(x):
16
+ x0, y0, x1, y1 = x.unbind(-1)
17
+ b = [(x0 + x1) / 2, (y0 + y1) / 2, (x1 - x0), (y1 - y0)]
18
+ return torch.stack(b, dim=-1)
19
+
20
+
21
+ # modified from torchvision to also return the union
22
+ def box_iou(boxes1, boxes2):
23
+ area1 = box_area(boxes1)
24
+ area2 = box_area(boxes2)
25
+
26
+ # import ipdb; ipdb.set_trace()
27
+ lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2]
28
+ rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2]
29
+
30
+ wh = (rb - lt).clamp(min=0) # [N,M,2]
31
+ inter = wh[:, :, 0] * wh[:, :, 1] # [N,M]
32
+
33
+ union = area1[:, None] + area2 - inter
34
+
35
+ iou = inter / (union + 1e-6)
36
+ return iou, union
37
+
38
+
39
+ def generalized_box_iou(boxes1, boxes2):
40
+ """
41
+ Generalized IoU from https://giou.stanford.edu/
42
+
43
+ The boxes should be in [x0, y0, x1, y1] format
44
+
45
+ Returns a [N, M] pairwise matrix, where N = len(boxes1)
46
+ and M = len(boxes2)
47
+ """
48
+ # degenerate boxes gives inf / nan results
49
+ # so do an early check
50
+ assert (boxes1[:, 2:] >= boxes1[:, :2]).all()
51
+ assert (boxes2[:, 2:] >= boxes2[:, :2]).all()
52
+ # except:
53
+ # import ipdb; ipdb.set_trace()
54
+ iou, union = box_iou(boxes1, boxes2)
55
+
56
+ lt = torch.min(boxes1[:, None, :2], boxes2[:, :2])
57
+ rb = torch.max(boxes1[:, None, 2:], boxes2[:, 2:])
58
+
59
+ wh = (rb - lt).clamp(min=0) # [N,M,2]
60
+ area = wh[:, :, 0] * wh[:, :, 1]
61
+
62
+ return iou - (area - union) / (area + 1e-6)
63
+
64
+
65
+ # modified from torchvision to also return the union
66
+ def box_iou_pairwise(boxes1, boxes2):
67
+ area1 = box_area(boxes1)
68
+ area2 = box_area(boxes2)
69
+
70
+ lt = torch.max(boxes1[:, :2], boxes2[:, :2]) # [N,2]
71
+ rb = torch.min(boxes1[:, 2:], boxes2[:, 2:]) # [N,2]
72
+
73
+ wh = (rb - lt).clamp(min=0) # [N,2]
74
+ inter = wh[:, 0] * wh[:, 1] # [N]
75
+
76
+ union = area1 + area2 - inter
77
+
78
+ iou = inter / union
79
+ return iou, union
80
+
81
+
82
+ def generalized_box_iou_pairwise(boxes1, boxes2):
83
+ """
84
+ Generalized IoU from https://giou.stanford.edu/
85
+
86
+ Input:
87
+ - boxes1, boxes2: N,4
88
+ Output:
89
+ - giou: N, 4
90
+ """
91
+ # degenerate boxes gives inf / nan results
92
+ # so do an early check
93
+ assert (boxes1[:, 2:] >= boxes1[:, :2]).all()
94
+ assert (boxes2[:, 2:] >= boxes2[:, :2]).all()
95
+ assert boxes1.shape == boxes2.shape
96
+ iou, union = box_iou_pairwise(boxes1, boxes2) # N, 4
97
+
98
+ lt = torch.min(boxes1[:, :2], boxes2[:, :2])
99
+ rb = torch.max(boxes1[:, 2:], boxes2[:, 2:])
100
+
101
+ wh = (rb - lt).clamp(min=0) # [N,2]
102
+ area = wh[:, 0] * wh[:, 1]
103
+
104
+ return iou - (area - union) / area
105
+
106
+
107
+ def masks_to_boxes(masks):
108
+ """Compute the bounding boxes around the provided masks
109
+
110
+ The masks should be in format [N, H, W] where N is the number of masks, (H, W) are the spatial dimensions.
111
+
112
+ Returns a [N, 4] tensors, with the boxes in xyxy format
113
+ """
114
+ if masks.numel() == 0:
115
+ return torch.zeros((0, 4), device=masks.device)
116
+
117
+ h, w = masks.shape[-2:]
118
+
119
+ y = torch.arange(0, h, dtype=torch.float)
120
+ x = torch.arange(0, w, dtype=torch.float)
121
+ y, x = torch.meshgrid(y, x)
122
+
123
+ x_mask = masks * x.unsqueeze(0)
124
+ x_max = x_mask.flatten(1).max(-1)[0]
125
+ x_min = x_mask.masked_fill(~(masks.bool()), 1e8).flatten(1).min(-1)[0]
126
+
127
+ y_mask = masks * y.unsqueeze(0)
128
+ y_max = y_mask.flatten(1).max(-1)[0]
129
+ y_min = y_mask.masked_fill(~(masks.bool()), 1e8).flatten(1).min(-1)[0]
130
+
131
+ return torch.stack([x_min, y_min, x_max, y_max], 1)
132
+
133
+
134
+ if __name__ == "__main__":
135
+ x = torch.rand(5, 4)
136
+ y = torch.rand(3, 4)
137
+ iou, union = box_iou(x, y)
138
+ import ipdb
139
+
140
+ ipdb.set_trace()
groundingdino/util/get_tokenlizer.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoTokenizer, BertModel, BertTokenizer, RobertaModel, RobertaTokenizerFast
2
+ import os
3
+
4
+ def get_tokenlizer(text_encoder_type):
5
+ if not isinstance(text_encoder_type, str):
6
+ # print("text_encoder_type is not a str")
7
+ if hasattr(text_encoder_type, "text_encoder_type"):
8
+ text_encoder_type = text_encoder_type.text_encoder_type
9
+ elif text_encoder_type.get("text_encoder_type", False):
10
+ text_encoder_type = text_encoder_type.get("text_encoder_type")
11
+ elif os.path.isdir(text_encoder_type) and os.path.exists(text_encoder_type):
12
+ pass
13
+ else:
14
+ raise ValueError(
15
+ "Unknown type of text_encoder_type: {}".format(type(text_encoder_type))
16
+ )
17
+ print("final text_encoder_type: {}".format(text_encoder_type))
18
+ tokenizer = AutoTokenizer.from_pretrained(text_encoder_type)
19
+ print("load tokenizer done.")
20
+ return tokenizer
21
+
22
+
23
+ def get_pretrained_language_model(text_encoder_type):
24
+ if text_encoder_type == "bert-base-uncased" or (os.path.isdir(text_encoder_type) and os.path.exists(text_encoder_type)):
25
+ return BertModel.from_pretrained(text_encoder_type)
26
+ if text_encoder_type == "roberta-base":
27
+ return RobertaModel.from_pretrained(text_encoder_type)
28
+
29
+ raise ValueError("Unknown text_encoder_type {}".format(text_encoder_type))
groundingdino/util/inference.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple, List
2
+
3
+ import cv2
4
+ import numpy as np
5
+ import supervision as sv
6
+ import torch
7
+ from PIL import Image
8
+ from torchvision.ops import box_convert
9
+ import bisect
10
+
11
+ import groundingdino.datasets.transforms as T
12
+ from groundingdino.models import build_model
13
+ from groundingdino.util.misc import clean_state_dict
14
+ from groundingdino.util.slconfig import SLConfig
15
+ from groundingdino.util.utils import get_phrases_from_posmap
16
+
17
+ # ----------------------------------------------------------------------------------------------------------------------
18
+ # OLD API
19
+ # ----------------------------------------------------------------------------------------------------------------------
20
+
21
+
22
+ def preprocess_caption(caption: str) -> str:
23
+ result = caption.lower().strip()
24
+ if result.endswith("."):
25
+ return result
26
+ return result + "."
27
+
28
+
29
+ def load_model(model_config_path: str, model_checkpoint_path: str, device: str = "cuda"):
30
+ args = SLConfig.fromfile(model_config_path)
31
+ args.device = device
32
+ model = build_model(args)
33
+ checkpoint = torch.load(model_checkpoint_path, map_location="cpu")
34
+ model.load_state_dict(clean_state_dict(checkpoint["model"]), strict=False)
35
+ model.eval()
36
+ return model
37
+
38
+
39
+ def load_image(image_path: str) -> Tuple[np.array, torch.Tensor]:
40
+ transform = T.Compose(
41
+ [
42
+ T.RandomResize([800], max_size=1333),
43
+ T.ToTensor(),
44
+ T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
45
+ ]
46
+ )
47
+ image_source = Image.open(image_path).convert("RGB")
48
+ image = np.asarray(image_source)
49
+ image_transformed, _ = transform(image_source, None)
50
+ return image, image_transformed
51
+
52
+
53
+ def predict(
54
+ model,
55
+ image: torch.Tensor,
56
+ caption: str,
57
+ box_threshold: float,
58
+ text_threshold: float,
59
+ device: str = "cuda",
60
+ remove_combined: bool = False
61
+ ) -> Tuple[torch.Tensor, torch.Tensor, List[str]]:
62
+ caption = preprocess_caption(caption=caption)
63
+
64
+ model = model.to(device)
65
+ image = image.to(device)
66
+
67
+ with torch.no_grad():
68
+ outputs = model(image[None], captions=[caption])
69
+
70
+ prediction_logits = outputs["pred_logits"].cpu().sigmoid()[0] # prediction_logits.shape = (nq, 256)
71
+ prediction_boxes = outputs["pred_boxes"].cpu()[0] # prediction_boxes.shape = (nq, 4)
72
+
73
+ mask = prediction_logits.max(dim=1)[0] > box_threshold
74
+ logits = prediction_logits[mask] # logits.shape = (n, 256)
75
+ boxes = prediction_boxes[mask] # boxes.shape = (n, 4)
76
+
77
+ tokenizer = model.tokenizer
78
+ tokenized = tokenizer(caption)
79
+
80
+ if remove_combined:
81
+ sep_idx = [i for i in range(len(tokenized['input_ids'])) if tokenized['input_ids'][i] in [101, 102, 1012]]
82
+
83
+ phrases = []
84
+ for logit in logits:
85
+ max_idx = logit.argmax()
86
+ insert_idx = bisect.bisect_left(sep_idx, max_idx)
87
+ right_idx = sep_idx[insert_idx]
88
+ left_idx = sep_idx[insert_idx - 1]
89
+ phrases.append(get_phrases_from_posmap(logit > text_threshold, tokenized, tokenizer, left_idx, right_idx).replace('.', ''))
90
+ else:
91
+ phrases = [
92
+ get_phrases_from_posmap(logit > text_threshold, tokenized, tokenizer).replace('.', '')
93
+ for logit
94
+ in logits
95
+ ]
96
+
97
+ return boxes, logits.max(dim=1)[0], phrases
98
+
99
+
100
+ def annotate(image_source: np.ndarray, boxes: torch.Tensor, logits: torch.Tensor, phrases: List[str]) -> np.ndarray:
101
+ h, w, _ = image_source.shape
102
+ boxes = boxes * torch.Tensor([w, h, w, h])
103
+ xyxy = box_convert(boxes=boxes, in_fmt="cxcywh", out_fmt="xyxy").numpy()
104
+ detections = sv.Detections(xyxy=xyxy)
105
+
106
+ labels = [
107
+ f"{phrase} {logit:.2f}"
108
+ for phrase, logit
109
+ in zip(phrases, logits)
110
+ ]
111
+
112
+ box_annotator = sv.BoxAnnotator()
113
+ annotated_frame = cv2.cvtColor(image_source, cv2.COLOR_RGB2BGR)
114
+ annotated_frame = box_annotator.annotate(scene=annotated_frame, detections=detections, labels=labels)
115
+ return annotated_frame
116
+
117
+
118
+ # ----------------------------------------------------------------------------------------------------------------------
119
+ # NEW API
120
+ # ----------------------------------------------------------------------------------------------------------------------
121
+
122
+
123
+ class Model:
124
+
125
+ def __init__(
126
+ self,
127
+ model_config_path: str,
128
+ model_checkpoint_path: str,
129
+ device: str = "cuda"
130
+ ):
131
+ self.model = load_model(
132
+ model_config_path=model_config_path,
133
+ model_checkpoint_path=model_checkpoint_path,
134
+ device=device
135
+ ).to(device)
136
+ self.device = device
137
+
138
+ def predict_with_caption(
139
+ self,
140
+ image: np.ndarray,
141
+ caption: str,
142
+ box_threshold: float = 0.35,
143
+ text_threshold: float = 0.25
144
+ ) -> Tuple[sv.Detections, List[str]]:
145
+ """
146
+ import cv2
147
+
148
+ image = cv2.imread(IMAGE_PATH)
149
+
150
+ model = Model(model_config_path=CONFIG_PATH, model_checkpoint_path=WEIGHTS_PATH)
151
+ detections, labels = model.predict_with_caption(
152
+ image=image,
153
+ caption=caption,
154
+ box_threshold=BOX_THRESHOLD,
155
+ text_threshold=TEXT_THRESHOLD
156
+ )
157
+
158
+ import supervision as sv
159
+
160
+ box_annotator = sv.BoxAnnotator()
161
+ annotated_image = box_annotator.annotate(scene=image, detections=detections, labels=labels)
162
+ """
163
+ processed_image = Model.preprocess_image(image_bgr=image).to(self.device)
164
+ boxes, logits, phrases = predict(
165
+ model=self.model,
166
+ image=processed_image,
167
+ caption=caption,
168
+ box_threshold=box_threshold,
169
+ text_threshold=text_threshold,
170
+ device=self.device)
171
+ source_h, source_w, _ = image.shape
172
+ detections = Model.post_process_result(
173
+ source_h=source_h,
174
+ source_w=source_w,
175
+ boxes=boxes,
176
+ logits=logits)
177
+ return detections, phrases
178
+
179
+ def predict_with_classes(
180
+ self,
181
+ image: np.ndarray,
182
+ classes: List[str],
183
+ box_threshold: float,
184
+ text_threshold: float
185
+ ) -> sv.Detections:
186
+ """
187
+ import cv2
188
+
189
+ image = cv2.imread(IMAGE_PATH)
190
+
191
+ model = Model(model_config_path=CONFIG_PATH, model_checkpoint_path=WEIGHTS_PATH)
192
+ detections = model.predict_with_classes(
193
+ image=image,
194
+ classes=CLASSES,
195
+ box_threshold=BOX_THRESHOLD,
196
+ text_threshold=TEXT_THRESHOLD
197
+ )
198
+
199
+
200
+ import supervision as sv
201
+
202
+ box_annotator = sv.BoxAnnotator()
203
+ annotated_image = box_annotator.annotate(scene=image, detections=detections)
204
+ """
205
+ caption = ". ".join(classes)
206
+ processed_image = Model.preprocess_image(image_bgr=image).to(self.device)
207
+ boxes, logits, phrases = predict(
208
+ model=self.model,
209
+ image=processed_image,
210
+ caption=caption,
211
+ box_threshold=box_threshold,
212
+ text_threshold=text_threshold,
213
+ device=self.device)
214
+ source_h, source_w, _ = image.shape
215
+ detections = Model.post_process_result(
216
+ source_h=source_h,
217
+ source_w=source_w,
218
+ boxes=boxes,
219
+ logits=logits)
220
+ class_id = Model.phrases2classes(phrases=phrases, classes=classes)
221
+ detections.class_id = class_id
222
+ return detections
223
+
224
+ @staticmethod
225
+ def preprocess_image(image_bgr: np.ndarray) -> torch.Tensor:
226
+ transform = T.Compose(
227
+ [
228
+ T.RandomResize([800], max_size=1333),
229
+ T.ToTensor(),
230
+ T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
231
+ ]
232
+ )
233
+ image_pillow = Image.fromarray(cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB))
234
+ image_transformed, _ = transform(image_pillow, None)
235
+ return image_transformed
236
+
237
+ @staticmethod
238
+ def post_process_result(
239
+ source_h: int,
240
+ source_w: int,
241
+ boxes: torch.Tensor,
242
+ logits: torch.Tensor
243
+ ) -> sv.Detections:
244
+ boxes = boxes * torch.Tensor([source_w, source_h, source_w, source_h])
245
+ xyxy = box_convert(boxes=boxes, in_fmt="cxcywh", out_fmt="xyxy").numpy()
246
+ confidence = logits.numpy()
247
+ return sv.Detections(xyxy=xyxy, confidence=confidence)
248
+
249
+ @staticmethod
250
+ def phrases2classes(phrases: List[str], classes: List[str]) -> np.ndarray:
251
+ class_ids = []
252
+ for phrase in phrases:
253
+ for class_ in classes:
254
+ if class_ in phrase:
255
+ class_ids.append(classes.index(class_))
256
+ break
257
+ else:
258
+ class_ids.append(None)
259
+ return np.array(class_ids)
groundingdino/util/logger.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ import functools
3
+ import logging
4
+ import os
5
+ import sys
6
+
7
+ from termcolor import colored
8
+
9
+
10
+ class _ColorfulFormatter(logging.Formatter):
11
+ def __init__(self, *args, **kwargs):
12
+ self._root_name = kwargs.pop("root_name") + "."
13
+ self._abbrev_name = kwargs.pop("abbrev_name", "")
14
+ if len(self._abbrev_name):
15
+ self._abbrev_name = self._abbrev_name + "."
16
+ super(_ColorfulFormatter, self).__init__(*args, **kwargs)
17
+
18
+ def formatMessage(self, record):
19
+ record.name = record.name.replace(self._root_name, self._abbrev_name)
20
+ log = super(_ColorfulFormatter, self).formatMessage(record)
21
+ if record.levelno == logging.WARNING:
22
+ prefix = colored("WARNING", "red", attrs=["blink"])
23
+ elif record.levelno == logging.ERROR or record.levelno == logging.CRITICAL:
24
+ prefix = colored("ERROR", "red", attrs=["blink", "underline"])
25
+ else:
26
+ return log
27
+ return prefix + " " + log
28
+
29
+
30
+ # so that calling setup_logger multiple times won't add many handlers
31
+ @functools.lru_cache()
32
+ def setup_logger(output=None, distributed_rank=0, *, color=True, name="imagenet", abbrev_name=None):
33
+ """
34
+ Initialize the detectron2 logger and set its verbosity level to "INFO".
35
+
36
+ Args:
37
+ output (str): a file name or a directory to save log. If None, will not save log file.
38
+ If ends with ".txt" or ".log", assumed to be a file name.
39
+ Otherwise, logs will be saved to `output/log.txt`.
40
+ name (str): the root module name of this logger
41
+
42
+ Returns:
43
+ logging.Logger: a logger
44
+ """
45
+ logger = logging.getLogger(name)
46
+ logger.setLevel(logging.DEBUG)
47
+ logger.propagate = False
48
+
49
+ if abbrev_name is None:
50
+ abbrev_name = name
51
+
52
+ plain_formatter = logging.Formatter(
53
+ "[%(asctime)s.%(msecs)03d]: %(message)s", datefmt="%m/%d %H:%M:%S"
54
+ )
55
+ # stdout logging: master only
56
+ if distributed_rank == 0:
57
+ ch = logging.StreamHandler(stream=sys.stdout)
58
+ ch.setLevel(logging.DEBUG)
59
+ if color:
60
+ formatter = _ColorfulFormatter(
61
+ colored("[%(asctime)s.%(msecs)03d]: ", "green") + "%(message)s",
62
+ datefmt="%m/%d %H:%M:%S",
63
+ root_name=name,
64
+ abbrev_name=str(abbrev_name),
65
+ )
66
+ else:
67
+ formatter = plain_formatter
68
+ ch.setFormatter(formatter)
69
+ logger.addHandler(ch)
70
+
71
+ # file logging: all workers
72
+ if output is not None:
73
+ if output.endswith(".txt") or output.endswith(".log"):
74
+ filename = output
75
+ else:
76
+ filename = os.path.join(output, "log.txt")
77
+ if distributed_rank > 0:
78
+ filename = filename + f".rank{distributed_rank}"
79
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
80
+
81
+ fh = logging.StreamHandler(_cached_log_stream(filename))
82
+ fh.setLevel(logging.DEBUG)
83
+ fh.setFormatter(plain_formatter)
84
+ logger.addHandler(fh)
85
+
86
+ return logger
87
+
88
+
89
+ # cache the opened file object, so that different calls to `setup_logger`
90
+ # with the same file name can safely write to the same file.
91
+ @functools.lru_cache(maxsize=None)
92
+ def _cached_log_stream(filename):
93
+ return open(filename, "a")
groundingdino/util/misc.py ADDED
@@ -0,0 +1,717 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ """
3
+ Misc functions, including distributed helpers.
4
+
5
+ Mostly copy-paste from torchvision references.
6
+ """
7
+ import colorsys
8
+ import datetime
9
+ import functools
10
+ import io
11
+ import json
12
+ import os
13
+ import pickle
14
+ import subprocess
15
+ import time
16
+ from collections import OrderedDict, defaultdict, deque
17
+ from typing import List, Optional
18
+
19
+ import numpy as np
20
+ import torch
21
+ import torch.distributed as dist
22
+
23
+ # needed due to empty tensor bug in pytorch and torchvision 0.5
24
+ import torchvision
25
+ from torch import Tensor
26
+
27
+ __torchvision_need_compat_flag = float(torchvision.__version__.split(".")[1]) < 7
28
+ if __torchvision_need_compat_flag:
29
+ from torchvision.ops import _new_empty_tensor
30
+ from torchvision.ops.misc import _output_size
31
+
32
+
33
+ class SmoothedValue(object):
34
+ """Track a series of values and provide access to smoothed values over a
35
+ window or the global series average.
36
+ """
37
+
38
+ def __init__(self, window_size=20, fmt=None):
39
+ if fmt is None:
40
+ fmt = "{median:.4f} ({global_avg:.4f})"
41
+ self.deque = deque(maxlen=window_size)
42
+ self.total = 0.0
43
+ self.count = 0
44
+ self.fmt = fmt
45
+
46
+ def update(self, value, n=1):
47
+ self.deque.append(value)
48
+ self.count += n
49
+ self.total += value * n
50
+
51
+ def synchronize_between_processes(self):
52
+ """
53
+ Warning: does not synchronize the deque!
54
+ """
55
+ if not is_dist_avail_and_initialized():
56
+ return
57
+ t = torch.tensor([self.count, self.total], dtype=torch.float64, device="cuda")
58
+ dist.barrier()
59
+ dist.all_reduce(t)
60
+ t = t.tolist()
61
+ self.count = int(t[0])
62
+ self.total = t[1]
63
+
64
+ @property
65
+ def median(self):
66
+ d = torch.tensor(list(self.deque))
67
+ if d.shape[0] == 0:
68
+ return 0
69
+ return d.median().item()
70
+
71
+ @property
72
+ def avg(self):
73
+ d = torch.tensor(list(self.deque), dtype=torch.float32)
74
+ return d.mean().item()
75
+
76
+ @property
77
+ def global_avg(self):
78
+ if os.environ.get("SHILONG_AMP", None) == "1":
79
+ eps = 1e-4
80
+ else:
81
+ eps = 1e-6
82
+ return self.total / (self.count + eps)
83
+
84
+ @property
85
+ def max(self):
86
+ return max(self.deque)
87
+
88
+ @property
89
+ def value(self):
90
+ return self.deque[-1]
91
+
92
+ def __str__(self):
93
+ return self.fmt.format(
94
+ median=self.median,
95
+ avg=self.avg,
96
+ global_avg=self.global_avg,
97
+ max=self.max,
98
+ value=self.value,
99
+ )
100
+
101
+
102
+ @functools.lru_cache()
103
+ def _get_global_gloo_group():
104
+ """
105
+ Return a process group based on gloo backend, containing all the ranks
106
+ The result is cached.
107
+ """
108
+
109
+ if dist.get_backend() == "nccl":
110
+ return dist.new_group(backend="gloo")
111
+
112
+ return dist.group.WORLD
113
+
114
+
115
+ def all_gather_cpu(data):
116
+ """
117
+ Run all_gather on arbitrary picklable data (not necessarily tensors)
118
+ Args:
119
+ data: any picklable object
120
+ Returns:
121
+ list[data]: list of data gathered from each rank
122
+ """
123
+
124
+ world_size = get_world_size()
125
+ if world_size == 1:
126
+ return [data]
127
+
128
+ cpu_group = _get_global_gloo_group()
129
+
130
+ buffer = io.BytesIO()
131
+ torch.save(data, buffer)
132
+ data_view = buffer.getbuffer()
133
+ device = "cuda" if cpu_group is None else "cpu"
134
+ tensor = torch.ByteTensor(data_view).to(device)
135
+
136
+ # obtain Tensor size of each rank
137
+ local_size = torch.tensor([tensor.numel()], device=device, dtype=torch.long)
138
+ size_list = [torch.tensor([0], device=device, dtype=torch.long) for _ in range(world_size)]
139
+ if cpu_group is None:
140
+ dist.all_gather(size_list, local_size)
141
+ else:
142
+ print("gathering on cpu")
143
+ dist.all_gather(size_list, local_size, group=cpu_group)
144
+ size_list = [int(size.item()) for size in size_list]
145
+ max_size = max(size_list)
146
+ assert isinstance(local_size.item(), int)
147
+ local_size = int(local_size.item())
148
+
149
+ # receiving Tensor from all ranks
150
+ # we pad the tensor because torch all_gather does not support
151
+ # gathering tensors of different shapes
152
+ tensor_list = []
153
+ for _ in size_list:
154
+ tensor_list.append(torch.empty((max_size,), dtype=torch.uint8, device=device))
155
+ if local_size != max_size:
156
+ padding = torch.empty(size=(max_size - local_size,), dtype=torch.uint8, device=device)
157
+ tensor = torch.cat((tensor, padding), dim=0)
158
+ if cpu_group is None:
159
+ dist.all_gather(tensor_list, tensor)
160
+ else:
161
+ dist.all_gather(tensor_list, tensor, group=cpu_group)
162
+
163
+ data_list = []
164
+ for size, tensor in zip(size_list, tensor_list):
165
+ tensor = torch.split(tensor, [size, max_size - size], dim=0)[0]
166
+ buffer = io.BytesIO(tensor.cpu().numpy())
167
+ obj = torch.load(buffer)
168
+ data_list.append(obj)
169
+
170
+ return data_list
171
+
172
+
173
+ def all_gather(data):
174
+ """
175
+ Run all_gather on arbitrary picklable data (not necessarily tensors)
176
+ Args:
177
+ data: any picklable object
178
+ Returns:
179
+ list[data]: list of data gathered from each rank
180
+ """
181
+
182
+ if os.getenv("CPU_REDUCE") == "1":
183
+ return all_gather_cpu(data)
184
+
185
+ world_size = get_world_size()
186
+ if world_size == 1:
187
+ return [data]
188
+
189
+ # serialized to a Tensor
190
+ buffer = pickle.dumps(data)
191
+ storage = torch.ByteStorage.from_buffer(buffer)
192
+ tensor = torch.ByteTensor(storage).to("cuda")
193
+
194
+ # obtain Tensor size of each rank
195
+ local_size = torch.tensor([tensor.numel()], device="cuda")
196
+ size_list = [torch.tensor([0], device="cuda") for _ in range(world_size)]
197
+ dist.all_gather(size_list, local_size)
198
+ size_list = [int(size.item()) for size in size_list]
199
+ max_size = max(size_list)
200
+
201
+ # receiving Tensor from all ranks
202
+ # we pad the tensor because torch all_gather does not support
203
+ # gathering tensors of different shapes
204
+ tensor_list = []
205
+ for _ in size_list:
206
+ tensor_list.append(torch.empty((max_size,), dtype=torch.uint8, device="cuda"))
207
+ if local_size != max_size:
208
+ padding = torch.empty(size=(max_size - local_size,), dtype=torch.uint8, device="cuda")
209
+ tensor = torch.cat((tensor, padding), dim=0)
210
+ dist.all_gather(tensor_list, tensor)
211
+
212
+ data_list = []
213
+ for size, tensor in zip(size_list, tensor_list):
214
+ buffer = tensor.cpu().numpy().tobytes()[:size]
215
+ data_list.append(pickle.loads(buffer))
216
+
217
+ return data_list
218
+
219
+
220
+ def reduce_dict(input_dict, average=True):
221
+ """
222
+ Args:
223
+ input_dict (dict): all the values will be reduced
224
+ average (bool): whether to do average or sum
225
+ Reduce the values in the dictionary from all processes so that all processes
226
+ have the averaged results. Returns a dict with the same fields as
227
+ input_dict, after reduction.
228
+ """
229
+ world_size = get_world_size()
230
+ if world_size < 2:
231
+ return input_dict
232
+ with torch.no_grad():
233
+ names = []
234
+ values = []
235
+ # sort the keys so that they are consistent across processes
236
+ for k in sorted(input_dict.keys()):
237
+ names.append(k)
238
+ values.append(input_dict[k])
239
+ values = torch.stack(values, dim=0)
240
+ dist.all_reduce(values)
241
+ if average:
242
+ values /= world_size
243
+ reduced_dict = {k: v for k, v in zip(names, values)}
244
+ return reduced_dict
245
+
246
+
247
+ class MetricLogger(object):
248
+ def __init__(self, delimiter="\t"):
249
+ self.meters = defaultdict(SmoothedValue)
250
+ self.delimiter = delimiter
251
+
252
+ def update(self, **kwargs):
253
+ for k, v in kwargs.items():
254
+ if isinstance(v, torch.Tensor):
255
+ v = v.item()
256
+ assert isinstance(v, (float, int))
257
+ self.meters[k].update(v)
258
+
259
+ def __getattr__(self, attr):
260
+ if attr in self.meters:
261
+ return self.meters[attr]
262
+ if attr in self.__dict__:
263
+ return self.__dict__[attr]
264
+ raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, attr))
265
+
266
+ def __str__(self):
267
+ loss_str = []
268
+ for name, meter in self.meters.items():
269
+ # print(name, str(meter))
270
+ # import ipdb;ipdb.set_trace()
271
+ if meter.count > 0:
272
+ loss_str.append("{}: {}".format(name, str(meter)))
273
+ return self.delimiter.join(loss_str)
274
+
275
+ def synchronize_between_processes(self):
276
+ for meter in self.meters.values():
277
+ meter.synchronize_between_processes()
278
+
279
+ def add_meter(self, name, meter):
280
+ self.meters[name] = meter
281
+
282
+ def log_every(self, iterable, print_freq, header=None, logger=None):
283
+ if logger is None:
284
+ print_func = print
285
+ else:
286
+ print_func = logger.info
287
+
288
+ i = 0
289
+ if not header:
290
+ header = ""
291
+ start_time = time.time()
292
+ end = time.time()
293
+ iter_time = SmoothedValue(fmt="{avg:.4f}")
294
+ data_time = SmoothedValue(fmt="{avg:.4f}")
295
+ space_fmt = ":" + str(len(str(len(iterable)))) + "d"
296
+ if torch.cuda.is_available():
297
+ log_msg = self.delimiter.join(
298
+ [
299
+ header,
300
+ "[{0" + space_fmt + "}/{1}]",
301
+ "eta: {eta}",
302
+ "{meters}",
303
+ "time: {time}",
304
+ "data: {data}",
305
+ "max mem: {memory:.0f}",
306
+ ]
307
+ )
308
+ else:
309
+ log_msg = self.delimiter.join(
310
+ [
311
+ header,
312
+ "[{0" + space_fmt + "}/{1}]",
313
+ "eta: {eta}",
314
+ "{meters}",
315
+ "time: {time}",
316
+ "data: {data}",
317
+ ]
318
+ )
319
+ MB = 1024.0 * 1024.0
320
+ for obj in iterable:
321
+ data_time.update(time.time() - end)
322
+ yield obj
323
+ # import ipdb; ipdb.set_trace()
324
+ iter_time.update(time.time() - end)
325
+ if i % print_freq == 0 or i == len(iterable) - 1:
326
+ eta_seconds = iter_time.global_avg * (len(iterable) - i)
327
+ eta_string = str(datetime.timedelta(seconds=int(eta_seconds)))
328
+ if torch.cuda.is_available():
329
+ print_func(
330
+ log_msg.format(
331
+ i,
332
+ len(iterable),
333
+ eta=eta_string,
334
+ meters=str(self),
335
+ time=str(iter_time),
336
+ data=str(data_time),
337
+ memory=torch.cuda.max_memory_allocated() / MB,
338
+ )
339
+ )
340
+ else:
341
+ print_func(
342
+ log_msg.format(
343
+ i,
344
+ len(iterable),
345
+ eta=eta_string,
346
+ meters=str(self),
347
+ time=str(iter_time),
348
+ data=str(data_time),
349
+ )
350
+ )
351
+ i += 1
352
+ end = time.time()
353
+ total_time = time.time() - start_time
354
+ total_time_str = str(datetime.timedelta(seconds=int(total_time)))
355
+ print_func(
356
+ "{} Total time: {} ({:.4f} s / it)".format(
357
+ header, total_time_str, total_time / len(iterable)
358
+ )
359
+ )
360
+
361
+
362
+ def get_sha():
363
+ cwd = os.path.dirname(os.path.abspath(__file__))
364
+
365
+ def _run(command):
366
+ return subprocess.check_output(command, cwd=cwd).decode("ascii").strip()
367
+
368
+ sha = "N/A"
369
+ diff = "clean"
370
+ branch = "N/A"
371
+ try:
372
+ sha = _run(["git", "rev-parse", "HEAD"])
373
+ subprocess.check_output(["git", "diff"], cwd=cwd)
374
+ diff = _run(["git", "diff-index", "HEAD"])
375
+ diff = "has uncommited changes" if diff else "clean"
376
+ branch = _run(["git", "rev-parse", "--abbrev-ref", "HEAD"])
377
+ except Exception:
378
+ pass
379
+ message = f"sha: {sha}, status: {diff}, branch: {branch}"
380
+ return message
381
+
382
+
383
+ def collate_fn(batch):
384
+ # import ipdb; ipdb.set_trace()
385
+ batch = list(zip(*batch))
386
+ batch[0] = nested_tensor_from_tensor_list(batch[0])
387
+ return tuple(batch)
388
+
389
+
390
+ def _max_by_axis(the_list):
391
+ # type: (List[List[int]]) -> List[int]
392
+ maxes = the_list[0]
393
+ for sublist in the_list[1:]:
394
+ for index, item in enumerate(sublist):
395
+ maxes[index] = max(maxes[index], item)
396
+ return maxes
397
+
398
+
399
+ class NestedTensor(object):
400
+ def __init__(self, tensors, mask: Optional[Tensor]):
401
+ self.tensors = tensors
402
+ self.mask = mask
403
+ if mask == "auto":
404
+ self.mask = torch.zeros_like(tensors).to(tensors.device)
405
+ if self.mask.dim() == 3:
406
+ self.mask = self.mask.sum(0).to(bool)
407
+ elif self.mask.dim() == 4:
408
+ self.mask = self.mask.sum(1).to(bool)
409
+ else:
410
+ raise ValueError(
411
+ "tensors dim must be 3 or 4 but {}({})".format(
412
+ self.tensors.dim(), self.tensors.shape
413
+ )
414
+ )
415
+
416
+ def imgsize(self):
417
+ res = []
418
+ for i in range(self.tensors.shape[0]):
419
+ mask = self.mask[i]
420
+ maxH = (~mask).sum(0).max()
421
+ maxW = (~mask).sum(1).max()
422
+ res.append(torch.Tensor([maxH, maxW]))
423
+ return res
424
+
425
+ def to(self, device):
426
+ # type: (Device) -> NestedTensor # noqa
427
+ cast_tensor = self.tensors.to(device)
428
+ mask = self.mask
429
+ if mask is not None:
430
+ assert mask is not None
431
+ cast_mask = mask.to(device)
432
+ else:
433
+ cast_mask = None
434
+ return NestedTensor(cast_tensor, cast_mask)
435
+
436
+ def to_img_list_single(self, tensor, mask):
437
+ assert tensor.dim() == 3, "dim of tensor should be 3 but {}".format(tensor.dim())
438
+ maxH = (~mask).sum(0).max()
439
+ maxW = (~mask).sum(1).max()
440
+ img = tensor[:, :maxH, :maxW]
441
+ return img
442
+
443
+ def to_img_list(self):
444
+ """remove the padding and convert to img list
445
+
446
+ Returns:
447
+ [type]: [description]
448
+ """
449
+ if self.tensors.dim() == 3:
450
+ return self.to_img_list_single(self.tensors, self.mask)
451
+ else:
452
+ res = []
453
+ for i in range(self.tensors.shape[0]):
454
+ tensor_i = self.tensors[i]
455
+ mask_i = self.mask[i]
456
+ res.append(self.to_img_list_single(tensor_i, mask_i))
457
+ return res
458
+
459
+ @property
460
+ def device(self):
461
+ return self.tensors.device
462
+
463
+ def decompose(self):
464
+ return self.tensors, self.mask
465
+
466
+ def __repr__(self):
467
+ return str(self.tensors)
468
+
469
+ @property
470
+ def shape(self):
471
+ return {"tensors.shape": self.tensors.shape, "mask.shape": self.mask.shape}
472
+
473
+
474
+ def nested_tensor_from_tensor_list(tensor_list: List[Tensor]):
475
+ # TODO make this more general
476
+ if tensor_list[0].ndim == 3:
477
+ if torchvision._is_tracing():
478
+ # nested_tensor_from_tensor_list() does not export well to ONNX
479
+ # call _onnx_nested_tensor_from_tensor_list() instead
480
+ return _onnx_nested_tensor_from_tensor_list(tensor_list)
481
+
482
+ # TODO make it support different-sized images
483
+ max_size = _max_by_axis([list(img.shape) for img in tensor_list])
484
+ # min_size = tuple(min(s) for s in zip(*[img.shape for img in tensor_list]))
485
+ batch_shape = [len(tensor_list)] + max_size
486
+ b, c, h, w = batch_shape
487
+ dtype = tensor_list[0].dtype
488
+ device = tensor_list[0].device
489
+ tensor = torch.zeros(batch_shape, dtype=dtype, device=device)
490
+ mask = torch.ones((b, h, w), dtype=torch.bool, device=device)
491
+ for img, pad_img, m in zip(tensor_list, tensor, mask):
492
+ pad_img[: img.shape[0], : img.shape[1], : img.shape[2]].copy_(img)
493
+ m[: img.shape[1], : img.shape[2]] = False
494
+ else:
495
+ raise ValueError("not supported")
496
+ return NestedTensor(tensor, mask)
497
+
498
+
499
+ # _onnx_nested_tensor_from_tensor_list() is an implementation of
500
+ # nested_tensor_from_tensor_list() that is supported by ONNX tracing.
501
+ @torch.jit.unused
502
+ def _onnx_nested_tensor_from_tensor_list(tensor_list: List[Tensor]) -> NestedTensor:
503
+ max_size = []
504
+ for i in range(tensor_list[0].dim()):
505
+ max_size_i = torch.max(
506
+ torch.stack([img.shape[i] for img in tensor_list]).to(torch.float32)
507
+ ).to(torch.int64)
508
+ max_size.append(max_size_i)
509
+ max_size = tuple(max_size)
510
+
511
+ # work around for
512
+ # pad_img[: img.shape[0], : img.shape[1], : img.shape[2]].copy_(img)
513
+ # m[: img.shape[1], :img.shape[2]] = False
514
+ # which is not yet supported in onnx
515
+ padded_imgs = []
516
+ padded_masks = []
517
+ for img in tensor_list:
518
+ padding = [(s1 - s2) for s1, s2 in zip(max_size, tuple(img.shape))]
519
+ padded_img = torch.nn.functional.pad(img, (0, padding[2], 0, padding[1], 0, padding[0]))
520
+ padded_imgs.append(padded_img)
521
+
522
+ m = torch.zeros_like(img[0], dtype=torch.int, device=img.device)
523
+ padded_mask = torch.nn.functional.pad(m, (0, padding[2], 0, padding[1]), "constant", 1)
524
+ padded_masks.append(padded_mask.to(torch.bool))
525
+
526
+ tensor = torch.stack(padded_imgs)
527
+ mask = torch.stack(padded_masks)
528
+
529
+ return NestedTensor(tensor, mask=mask)
530
+
531
+
532
+ def setup_for_distributed(is_master):
533
+ """
534
+ This function disables printing when not in master process
535
+ """
536
+ import builtins as __builtin__
537
+
538
+ builtin_print = __builtin__.print
539
+
540
+ def print(*args, **kwargs):
541
+ force = kwargs.pop("force", False)
542
+ if is_master or force:
543
+ builtin_print(*args, **kwargs)
544
+
545
+ __builtin__.print = print
546
+
547
+
548
+ def is_dist_avail_and_initialized():
549
+ if not dist.is_available():
550
+ return False
551
+ if not dist.is_initialized():
552
+ return False
553
+ return True
554
+
555
+
556
+ def get_world_size():
557
+ if not is_dist_avail_and_initialized():
558
+ return 1
559
+ return dist.get_world_size()
560
+
561
+
562
+ def get_rank():
563
+ if not is_dist_avail_and_initialized():
564
+ return 0
565
+ return dist.get_rank()
566
+
567
+
568
+ def is_main_process():
569
+ return get_rank() == 0
570
+
571
+
572
+ def save_on_master(*args, **kwargs):
573
+ if is_main_process():
574
+ torch.save(*args, **kwargs)
575
+
576
+
577
+ def init_distributed_mode(args):
578
+ if "WORLD_SIZE" in os.environ and os.environ["WORLD_SIZE"] != "": # 'RANK' in os.environ and
579
+ args.rank = int(os.environ["RANK"])
580
+ args.world_size = int(os.environ["WORLD_SIZE"])
581
+ args.gpu = args.local_rank = int(os.environ["LOCAL_RANK"])
582
+
583
+ # launch by torch.distributed.launch
584
+ # Single node
585
+ # python -m torch.distributed.launch --nproc_per_node=8 main.py --world-size 1 --rank 0 ...
586
+ # Multi nodes
587
+ # python -m torch.distributed.launch --nproc_per_node=8 main.py --world-size 2 --rank 0 --dist-url 'tcp://IP_OF_NODE0:FREEPORT' ...
588
+ # python -m torch.distributed.launch --nproc_per_node=8 main.py --world-size 2 --rank 1 --dist-url 'tcp://IP_OF_NODE0:FREEPORT' ...
589
+ # args.rank = int(os.environ.get('OMPI_COMM_WORLD_RANK'))
590
+ # local_world_size = int(os.environ['GPU_PER_NODE_COUNT'])
591
+ # args.world_size = args.world_size * local_world_size
592
+ # args.gpu = args.local_rank = int(os.environ['LOCAL_RANK'])
593
+ # args.rank = args.rank * local_world_size + args.local_rank
594
+ print(
595
+ "world size: {}, rank: {}, local rank: {}".format(
596
+ args.world_size, args.rank, args.local_rank
597
+ )
598
+ )
599
+ print(json.dumps(dict(os.environ), indent=2))
600
+ elif "SLURM_PROCID" in os.environ:
601
+ args.rank = int(os.environ["SLURM_PROCID"])
602
+ args.gpu = args.local_rank = int(os.environ["SLURM_LOCALID"])
603
+ args.world_size = int(os.environ["SLURM_NPROCS"])
604
+
605
+ print(
606
+ "world size: {}, world rank: {}, local rank: {}, device_count: {}".format(
607
+ args.world_size, args.rank, args.local_rank, torch.cuda.device_count()
608
+ )
609
+ )
610
+ else:
611
+ print("Not using distributed mode")
612
+ args.distributed = False
613
+ args.world_size = 1
614
+ args.rank = 0
615
+ args.local_rank = 0
616
+ return
617
+
618
+ print("world_size:{} rank:{} local_rank:{}".format(args.world_size, args.rank, args.local_rank))
619
+ args.distributed = True
620
+ torch.cuda.set_device(args.local_rank)
621
+ args.dist_backend = "nccl"
622
+ print("| distributed init (rank {}): {}".format(args.rank, args.dist_url), flush=True)
623
+
624
+ torch.distributed.init_process_group(
625
+ backend=args.dist_backend,
626
+ world_size=args.world_size,
627
+ rank=args.rank,
628
+ init_method=args.dist_url,
629
+ )
630
+
631
+ print("Before torch.distributed.barrier()")
632
+ torch.distributed.barrier()
633
+ print("End torch.distributed.barrier()")
634
+ setup_for_distributed(args.rank == 0)
635
+
636
+
637
+ @torch.no_grad()
638
+ def accuracy(output, target, topk=(1,)):
639
+ """Computes the precision@k for the specified values of k"""
640
+ if target.numel() == 0:
641
+ return [torch.zeros([], device=output.device)]
642
+ maxk = max(topk)
643
+ batch_size = target.size(0)
644
+
645
+ _, pred = output.topk(maxk, 1, True, True)
646
+ pred = pred.t()
647
+ correct = pred.eq(target.view(1, -1).expand_as(pred))
648
+
649
+ res = []
650
+ for k in topk:
651
+ correct_k = correct[:k].view(-1).float().sum(0)
652
+ res.append(correct_k.mul_(100.0 / batch_size))
653
+ return res
654
+
655
+
656
+ @torch.no_grad()
657
+ def accuracy_onehot(pred, gt):
658
+ """_summary_
659
+
660
+ Args:
661
+ pred (_type_): n, c
662
+ gt (_type_): n, c
663
+ """
664
+ tp = ((pred - gt).abs().sum(-1) < 1e-4).float().sum()
665
+ acc = tp / gt.shape[0] * 100
666
+ return acc
667
+
668
+
669
+ def interpolate(input, size=None, scale_factor=None, mode="nearest", align_corners=None):
670
+ # type: (Tensor, Optional[List[int]], Optional[float], str, Optional[bool]) -> Tensor
671
+ """
672
+ Equivalent to nn.functional.interpolate, but with support for empty batch sizes.
673
+ This will eventually be supported natively by PyTorch, and this
674
+ class can go away.
675
+ """
676
+ if __torchvision_need_compat_flag < 0.7:
677
+ if input.numel() > 0:
678
+ return torch.nn.functional.interpolate(input, size, scale_factor, mode, align_corners)
679
+
680
+ output_shape = _output_size(2, input, size, scale_factor)
681
+ output_shape = list(input.shape[:-2]) + list(output_shape)
682
+ return _new_empty_tensor(input, output_shape)
683
+ else:
684
+ return torchvision.ops.misc.interpolate(input, size, scale_factor, mode, align_corners)
685
+
686
+
687
+ class color_sys:
688
+ def __init__(self, num_colors) -> None:
689
+ self.num_colors = num_colors
690
+ colors = []
691
+ for i in np.arange(0.0, 360.0, 360.0 / num_colors):
692
+ hue = i / 360.0
693
+ lightness = (50 + np.random.rand() * 10) / 100.0
694
+ saturation = (90 + np.random.rand() * 10) / 100.0
695
+ colors.append(
696
+ tuple([int(j * 255) for j in colorsys.hls_to_rgb(hue, lightness, saturation)])
697
+ )
698
+ self.colors = colors
699
+
700
+ def __call__(self, idx):
701
+ return self.colors[idx]
702
+
703
+
704
+ def inverse_sigmoid(x, eps=1e-3):
705
+ x = x.clamp(min=0, max=1)
706
+ x1 = x.clamp(min=eps)
707
+ x2 = (1 - x).clamp(min=eps)
708
+ return torch.log(x1 / x2)
709
+
710
+
711
+ def clean_state_dict(state_dict):
712
+ new_state_dict = OrderedDict()
713
+ for k, v in state_dict.items():
714
+ if k[:7] == "module.":
715
+ k = k[7:] # remove `module.`
716
+ new_state_dict[k] = v
717
+ return new_state_dict
groundingdino/util/slconfig.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==========================================================
2
+ # Modified from mmcv
3
+ # ==========================================================
4
+ import ast
5
+ import os
6
+ import os.path as osp
7
+ import shutil
8
+ import sys
9
+ import tempfile
10
+ from argparse import Action
11
+ from importlib import import_module
12
+
13
+ from addict import Dict
14
+ from yapf.yapflib.yapf_api import FormatCode
15
+
16
+ BASE_KEY = "_base_"
17
+ DELETE_KEY = "_delete_"
18
+ RESERVED_KEYS = ["filename", "text", "pretty_text", "get", "dump", "merge_from_dict"]
19
+
20
+
21
+ def check_file_exist(filename, msg_tmpl='file "{}" does not exist'):
22
+ if not osp.isfile(filename):
23
+ raise FileNotFoundError(msg_tmpl.format(filename))
24
+
25
+
26
+ class ConfigDict(Dict):
27
+ def __missing__(self, name):
28
+ raise KeyError(name)
29
+
30
+ def __getattr__(self, name):
31
+ try:
32
+ value = super(ConfigDict, self).__getattr__(name)
33
+ except KeyError:
34
+ ex = AttributeError(f"'{self.__class__.__name__}' object has no " f"attribute '{name}'")
35
+ except Exception as e:
36
+ ex = e
37
+ else:
38
+ return value
39
+ raise ex
40
+
41
+
42
+ class SLConfig(object):
43
+ """
44
+ config files.
45
+ only support .py file as config now.
46
+
47
+ ref: mmcv.utils.config
48
+
49
+ Example:
50
+ >>> cfg = Config(dict(a=1, b=dict(b1=[0, 1])))
51
+ >>> cfg.a
52
+ 1
53
+ >>> cfg.b
54
+ {'b1': [0, 1]}
55
+ >>> cfg.b.b1
56
+ [0, 1]
57
+ >>> cfg = Config.fromfile('tests/data/config/a.py')
58
+ >>> cfg.filename
59
+ "/home/kchen/projects/mmcv/tests/data/config/a.py"
60
+ >>> cfg.item4
61
+ 'test'
62
+ >>> cfg
63
+ "Config [path: /home/kchen/projects/mmcv/tests/data/config/a.py]: "
64
+ "{'item1': [1, 2], 'item2': {'a': 0}, 'item3': True, 'item4': 'test'}"
65
+ """
66
+
67
+ @staticmethod
68
+ def _validate_py_syntax(filename):
69
+ with open(filename) as f:
70
+ content = f.read()
71
+ try:
72
+ ast.parse(content)
73
+ except SyntaxError:
74
+ raise SyntaxError("There are syntax errors in config " f"file {filename}")
75
+
76
+ @staticmethod
77
+ def _file2dict(filename):
78
+ filename = osp.abspath(osp.expanduser(filename))
79
+ check_file_exist(filename)
80
+ if filename.lower().endswith(".py"):
81
+ with tempfile.TemporaryDirectory() as temp_config_dir:
82
+ temp_config_file = tempfile.NamedTemporaryFile(dir=temp_config_dir, suffix=".py")
83
+ temp_config_name = osp.basename(temp_config_file.name)
84
+ if os.name == 'nt':
85
+ temp_config_file.close()
86
+ shutil.copyfile(filename, osp.join(temp_config_dir, temp_config_name))
87
+ temp_module_name = osp.splitext(temp_config_name)[0]
88
+ sys.path.insert(0, temp_config_dir)
89
+ SLConfig._validate_py_syntax(filename)
90
+ mod = import_module(temp_module_name)
91
+ sys.path.pop(0)
92
+ cfg_dict = {
93
+ name: value for name, value in mod.__dict__.items() if not name.startswith("__")
94
+ }
95
+ # delete imported module
96
+ del sys.modules[temp_module_name]
97
+ # close temp file
98
+ temp_config_file.close()
99
+ elif filename.lower().endswith((".yml", ".yaml", ".json")):
100
+ from .slio import slload
101
+
102
+ cfg_dict = slload(filename)
103
+ else:
104
+ raise IOError("Only py/yml/yaml/json type are supported now!")
105
+
106
+ cfg_text = filename + "\n"
107
+ with open(filename, "r") as f:
108
+ cfg_text += f.read()
109
+
110
+ # parse the base file
111
+ if BASE_KEY in cfg_dict:
112
+ cfg_dir = osp.dirname(filename)
113
+ base_filename = cfg_dict.pop(BASE_KEY)
114
+ base_filename = base_filename if isinstance(base_filename, list) else [base_filename]
115
+
116
+ cfg_dict_list = list()
117
+ cfg_text_list = list()
118
+ for f in base_filename:
119
+ _cfg_dict, _cfg_text = SLConfig._file2dict(osp.join(cfg_dir, f))
120
+ cfg_dict_list.append(_cfg_dict)
121
+ cfg_text_list.append(_cfg_text)
122
+
123
+ base_cfg_dict = dict()
124
+ for c in cfg_dict_list:
125
+ if len(base_cfg_dict.keys() & c.keys()) > 0:
126
+ raise KeyError("Duplicate key is not allowed among bases")
127
+ # TODO Allow the duplicate key while warnning user
128
+ base_cfg_dict.update(c)
129
+
130
+ base_cfg_dict = SLConfig._merge_a_into_b(cfg_dict, base_cfg_dict)
131
+ cfg_dict = base_cfg_dict
132
+
133
+ # merge cfg_text
134
+ cfg_text_list.append(cfg_text)
135
+ cfg_text = "\n".join(cfg_text_list)
136
+
137
+ return cfg_dict, cfg_text
138
+
139
+ @staticmethod
140
+ def _merge_a_into_b(a, b):
141
+ """merge dict `a` into dict `b` (non-inplace).
142
+ values in `a` will overwrite `b`.
143
+ copy first to avoid inplace modification
144
+
145
+ Args:
146
+ a ([type]): [description]
147
+ b ([type]): [description]
148
+
149
+ Returns:
150
+ [dict]: [description]
151
+ """
152
+ # import ipdb; ipdb.set_trace()
153
+ if not isinstance(a, dict):
154
+ return a
155
+
156
+ b = b.copy()
157
+ for k, v in a.items():
158
+ if isinstance(v, dict) and k in b and not v.pop(DELETE_KEY, False):
159
+
160
+ if not isinstance(b[k], dict) and not isinstance(b[k], list):
161
+ # if :
162
+ # import ipdb; ipdb.set_trace()
163
+ raise TypeError(
164
+ f"{k}={v} in child config cannot inherit from base "
165
+ f"because {k} is a dict in the child config but is of "
166
+ f"type {type(b[k])} in base config. You may set "
167
+ f"`{DELETE_KEY}=True` to ignore the base config"
168
+ )
169
+ b[k] = SLConfig._merge_a_into_b(v, b[k])
170
+ elif isinstance(b, list):
171
+ try:
172
+ _ = int(k)
173
+ except:
174
+ raise TypeError(
175
+ f"b is a list, " f"index {k} should be an int when input but {type(k)}"
176
+ )
177
+ b[int(k)] = SLConfig._merge_a_into_b(v, b[int(k)])
178
+ else:
179
+ b[k] = v
180
+
181
+ return b
182
+
183
+ @staticmethod
184
+ def fromfile(filename):
185
+ cfg_dict, cfg_text = SLConfig._file2dict(filename)
186
+ return SLConfig(cfg_dict, cfg_text=cfg_text, filename=filename)
187
+
188
+ def __init__(self, cfg_dict=None, cfg_text=None, filename=None):
189
+ if cfg_dict is None:
190
+ cfg_dict = dict()
191
+ elif not isinstance(cfg_dict, dict):
192
+ raise TypeError("cfg_dict must be a dict, but " f"got {type(cfg_dict)}")
193
+ for key in cfg_dict:
194
+ if key in RESERVED_KEYS:
195
+ raise KeyError(f"{key} is reserved for config file")
196
+
197
+ super(SLConfig, self).__setattr__("_cfg_dict", ConfigDict(cfg_dict))
198
+ super(SLConfig, self).__setattr__("_filename", filename)
199
+ if cfg_text:
200
+ text = cfg_text
201
+ elif filename:
202
+ with open(filename, "r") as f:
203
+ text = f.read()
204
+ else:
205
+ text = ""
206
+ super(SLConfig, self).__setattr__("_text", text)
207
+
208
+ @property
209
+ def filename(self):
210
+ return self._filename
211
+
212
+ @property
213
+ def text(self):
214
+ return self._text
215
+
216
+ @property
217
+ def pretty_text(self):
218
+
219
+ indent = 4
220
+
221
+ def _indent(s_, num_spaces):
222
+ s = s_.split("\n")
223
+ if len(s) == 1:
224
+ return s_
225
+ first = s.pop(0)
226
+ s = [(num_spaces * " ") + line for line in s]
227
+ s = "\n".join(s)
228
+ s = first + "\n" + s
229
+ return s
230
+
231
+ def _format_basic_types(k, v, use_mapping=False):
232
+ if isinstance(v, str):
233
+ v_str = f"'{v}'"
234
+ else:
235
+ v_str = str(v)
236
+
237
+ if use_mapping:
238
+ k_str = f"'{k}'" if isinstance(k, str) else str(k)
239
+ attr_str = f"{k_str}: {v_str}"
240
+ else:
241
+ attr_str = f"{str(k)}={v_str}"
242
+ attr_str = _indent(attr_str, indent)
243
+
244
+ return attr_str
245
+
246
+ def _format_list(k, v, use_mapping=False):
247
+ # check if all items in the list are dict
248
+ if all(isinstance(_, dict) for _ in v):
249
+ v_str = "[\n"
250
+ v_str += "\n".join(
251
+ f"dict({_indent(_format_dict(v_), indent)})," for v_ in v
252
+ ).rstrip(",")
253
+ if use_mapping:
254
+ k_str = f"'{k}'" if isinstance(k, str) else str(k)
255
+ attr_str = f"{k_str}: {v_str}"
256
+ else:
257
+ attr_str = f"{str(k)}={v_str}"
258
+ attr_str = _indent(attr_str, indent) + "]"
259
+ else:
260
+ attr_str = _format_basic_types(k, v, use_mapping)
261
+ return attr_str
262
+
263
+ def _contain_invalid_identifier(dict_str):
264
+ contain_invalid_identifier = False
265
+ for key_name in dict_str:
266
+ contain_invalid_identifier |= not str(key_name).isidentifier()
267
+ return contain_invalid_identifier
268
+
269
+ def _format_dict(input_dict, outest_level=False):
270
+ r = ""
271
+ s = []
272
+
273
+ use_mapping = _contain_invalid_identifier(input_dict)
274
+ if use_mapping:
275
+ r += "{"
276
+ for idx, (k, v) in enumerate(input_dict.items()):
277
+ is_last = idx >= len(input_dict) - 1
278
+ end = "" if outest_level or is_last else ","
279
+ if isinstance(v, dict):
280
+ v_str = "\n" + _format_dict(v)
281
+ if use_mapping:
282
+ k_str = f"'{k}'" if isinstance(k, str) else str(k)
283
+ attr_str = f"{k_str}: dict({v_str}"
284
+ else:
285
+ attr_str = f"{str(k)}=dict({v_str}"
286
+ attr_str = _indent(attr_str, indent) + ")" + end
287
+ elif isinstance(v, list):
288
+ attr_str = _format_list(k, v, use_mapping) + end
289
+ else:
290
+ attr_str = _format_basic_types(k, v, use_mapping) + end
291
+
292
+ s.append(attr_str)
293
+ r += "\n".join(s)
294
+ if use_mapping:
295
+ r += "}"
296
+ return r
297
+
298
+ cfg_dict = self._cfg_dict.to_dict()
299
+ text = _format_dict(cfg_dict, outest_level=True)
300
+ # copied from setup.cfg
301
+ yapf_style = dict(
302
+ based_on_style="pep8",
303
+ blank_line_before_nested_class_or_def=True,
304
+ split_before_expression_after_opening_paren=True,
305
+ )
306
+ text, _ = FormatCode(text, style_config=yapf_style, verify=True)
307
+
308
+ return text
309
+
310
+ def __repr__(self):
311
+ return f"Config (path: {self.filename}): {self._cfg_dict.__repr__()}"
312
+
313
+ def __len__(self):
314
+ return len(self._cfg_dict)
315
+
316
+ def __getattr__(self, name):
317
+ # # debug
318
+ # print('+'*15)
319
+ # print('name=%s' % name)
320
+ # print("addr:", id(self))
321
+ # # print('type(self):', type(self))
322
+ # print(self.__dict__)
323
+ # print('+'*15)
324
+ # if self.__dict__ == {}:
325
+ # raise ValueError
326
+
327
+ return getattr(self._cfg_dict, name)
328
+
329
+ def __getitem__(self, name):
330
+ return self._cfg_dict.__getitem__(name)
331
+
332
+ def __setattr__(self, name, value):
333
+ if isinstance(value, dict):
334
+ value = ConfigDict(value)
335
+ self._cfg_dict.__setattr__(name, value)
336
+
337
+ def __setitem__(self, name, value):
338
+ if isinstance(value, dict):
339
+ value = ConfigDict(value)
340
+ self._cfg_dict.__setitem__(name, value)
341
+
342
+ def __iter__(self):
343
+ return iter(self._cfg_dict)
344
+
345
+ def dump(self, file=None):
346
+ # import ipdb; ipdb.set_trace()
347
+ if file is None:
348
+ return self.pretty_text
349
+ else:
350
+ with open(file, "w") as f:
351
+ f.write(self.pretty_text)
352
+
353
+ def merge_from_dict(self, options):
354
+ """Merge list into cfg_dict
355
+
356
+ Merge the dict parsed by MultipleKVAction into this cfg.
357
+
358
+ Examples:
359
+ >>> options = {'model.backbone.depth': 50,
360
+ ... 'model.backbone.with_cp':True}
361
+ >>> cfg = Config(dict(model=dict(backbone=dict(type='ResNet'))))
362
+ >>> cfg.merge_from_dict(options)
363
+ >>> cfg_dict = super(Config, self).__getattribute__('_cfg_dict')
364
+ >>> assert cfg_dict == dict(
365
+ ... model=dict(backbone=dict(depth=50, with_cp=True)))
366
+
367
+ Args:
368
+ options (dict): dict of configs to merge from.
369
+ """
370
+ option_cfg_dict = {}
371
+ for full_key, v in options.items():
372
+ d = option_cfg_dict
373
+ key_list = full_key.split(".")
374
+ for subkey in key_list[:-1]:
375
+ d.setdefault(subkey, ConfigDict())
376
+ d = d[subkey]
377
+ subkey = key_list[-1]
378
+ d[subkey] = v
379
+
380
+ cfg_dict = super(SLConfig, self).__getattribute__("_cfg_dict")
381
+ super(SLConfig, self).__setattr__(
382
+ "_cfg_dict", SLConfig._merge_a_into_b(option_cfg_dict, cfg_dict)
383
+ )
384
+
385
+ # for multiprocess
386
+ def __setstate__(self, state):
387
+ self.__init__(state)
388
+
389
+ def copy(self):
390
+ return SLConfig(self._cfg_dict.copy())
391
+
392
+ def deepcopy(self):
393
+ return SLConfig(self._cfg_dict.deepcopy())
394
+
395
+
396
+ class DictAction(Action):
397
+ """
398
+ argparse action to split an argument into KEY=VALUE form
399
+ on the first = and append to a dictionary. List options should
400
+ be passed as comma separated values, i.e KEY=V1,V2,V3
401
+ """
402
+
403
+ @staticmethod
404
+ def _parse_int_float_bool(val):
405
+ try:
406
+ return int(val)
407
+ except ValueError:
408
+ pass
409
+ try:
410
+ return float(val)
411
+ except ValueError:
412
+ pass
413
+ if val.lower() in ["true", "false"]:
414
+ return True if val.lower() == "true" else False
415
+ if val.lower() in ["none", "null"]:
416
+ return None
417
+ return val
418
+
419
+ def __call__(self, parser, namespace, values, option_string=None):
420
+ options = {}
421
+ for kv in values:
422
+ key, val = kv.split("=", maxsplit=1)
423
+ val = [self._parse_int_float_bool(v) for v in val.split(",")]
424
+ if len(val) == 1:
425
+ val = val[0]
426
+ options[key] = val
427
+ setattr(namespace, self.dest, options)
groundingdino/util/slio.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==========================================================
2
+ # Modified from mmcv
3
+ # ==========================================================
4
+
5
+ import json
6
+ import pickle
7
+ from abc import ABCMeta, abstractmethod
8
+ from pathlib import Path
9
+
10
+ import yaml
11
+
12
+ try:
13
+ from yaml import CLoader as Loader, CDumper as Dumper
14
+ except ImportError:
15
+ from yaml import Loader, Dumper
16
+
17
+
18
+ # ===========================
19
+ # Rigister handler
20
+ # ===========================
21
+
22
+
23
+ class BaseFileHandler(metaclass=ABCMeta):
24
+ @abstractmethod
25
+ def load_from_fileobj(self, file, **kwargs):
26
+ pass
27
+
28
+ @abstractmethod
29
+ def dump_to_fileobj(self, obj, file, **kwargs):
30
+ pass
31
+
32
+ @abstractmethod
33
+ def dump_to_str(self, obj, **kwargs):
34
+ pass
35
+
36
+ def load_from_path(self, filepath, mode="r", **kwargs):
37
+ with open(filepath, mode) as f:
38
+ return self.load_from_fileobj(f, **kwargs)
39
+
40
+ def dump_to_path(self, obj, filepath, mode="w", **kwargs):
41
+ with open(filepath, mode) as f:
42
+ self.dump_to_fileobj(obj, f, **kwargs)
43
+
44
+
45
+ class JsonHandler(BaseFileHandler):
46
+ def load_from_fileobj(self, file):
47
+ return json.load(file)
48
+
49
+ def dump_to_fileobj(self, obj, file, **kwargs):
50
+ json.dump(obj, file, **kwargs)
51
+
52
+ def dump_to_str(self, obj, **kwargs):
53
+ return json.dumps(obj, **kwargs)
54
+
55
+
56
+ class PickleHandler(BaseFileHandler):
57
+ def load_from_fileobj(self, file, **kwargs):
58
+ return pickle.load(file, **kwargs)
59
+
60
+ def load_from_path(self, filepath, **kwargs):
61
+ return super(PickleHandler, self).load_from_path(filepath, mode="rb", **kwargs)
62
+
63
+ def dump_to_str(self, obj, **kwargs):
64
+ kwargs.setdefault("protocol", 2)
65
+ return pickle.dumps(obj, **kwargs)
66
+
67
+ def dump_to_fileobj(self, obj, file, **kwargs):
68
+ kwargs.setdefault("protocol", 2)
69
+ pickle.dump(obj, file, **kwargs)
70
+
71
+ def dump_to_path(self, obj, filepath, **kwargs):
72
+ super(PickleHandler, self).dump_to_path(obj, filepath, mode="wb", **kwargs)
73
+
74
+
75
+ class YamlHandler(BaseFileHandler):
76
+ def load_from_fileobj(self, file, **kwargs):
77
+ kwargs.setdefault("Loader", Loader)
78
+ return yaml.load(file, **kwargs)
79
+
80
+ def dump_to_fileobj(self, obj, file, **kwargs):
81
+ kwargs.setdefault("Dumper", Dumper)
82
+ yaml.dump(obj, file, **kwargs)
83
+
84
+ def dump_to_str(self, obj, **kwargs):
85
+ kwargs.setdefault("Dumper", Dumper)
86
+ return yaml.dump(obj, **kwargs)
87
+
88
+
89
+ file_handlers = {
90
+ "json": JsonHandler(),
91
+ "yaml": YamlHandler(),
92
+ "yml": YamlHandler(),
93
+ "pickle": PickleHandler(),
94
+ "pkl": PickleHandler(),
95
+ }
96
+
97
+ # ===========================
98
+ # load and dump
99
+ # ===========================
100
+
101
+
102
+ def is_str(x):
103
+ """Whether the input is an string instance.
104
+
105
+ Note: This method is deprecated since python 2 is no longer supported.
106
+ """
107
+ return isinstance(x, str)
108
+
109
+
110
+ def slload(file, file_format=None, **kwargs):
111
+ """Load data from json/yaml/pickle files.
112
+
113
+ This method provides a unified api for loading data from serialized files.
114
+
115
+ Args:
116
+ file (str or :obj:`Path` or file-like object): Filename or a file-like
117
+ object.
118
+ file_format (str, optional): If not specified, the file format will be
119
+ inferred from the file extension, otherwise use the specified one.
120
+ Currently supported formats include "json", "yaml/yml" and
121
+ "pickle/pkl".
122
+
123
+ Returns:
124
+ The content from the file.
125
+ """
126
+ if isinstance(file, Path):
127
+ file = str(file)
128
+ if file_format is None and is_str(file):
129
+ file_format = file.split(".")[-1]
130
+ if file_format not in file_handlers:
131
+ raise TypeError(f"Unsupported format: {file_format}")
132
+
133
+ handler = file_handlers[file_format]
134
+ if is_str(file):
135
+ obj = handler.load_from_path(file, **kwargs)
136
+ elif hasattr(file, "read"):
137
+ obj = handler.load_from_fileobj(file, **kwargs)
138
+ else:
139
+ raise TypeError('"file" must be a filepath str or a file-object')
140
+ return obj
141
+
142
+
143
+ def sldump(obj, file=None, file_format=None, **kwargs):
144
+ """Dump data to json/yaml/pickle strings or files.
145
+
146
+ This method provides a unified api for dumping data as strings or to files,
147
+ and also supports custom arguments for each file format.
148
+
149
+ Args:
150
+ obj (any): The python object to be dumped.
151
+ file (str or :obj:`Path` or file-like object, optional): If not
152
+ specified, then the object is dump to a str, otherwise to a file
153
+ specified by the filename or file-like object.
154
+ file_format (str, optional): Same as :func:`load`.
155
+
156
+ Returns:
157
+ bool: True for success, False otherwise.
158
+ """
159
+ if isinstance(file, Path):
160
+ file = str(file)
161
+ if file_format is None:
162
+ if is_str(file):
163
+ file_format = file.split(".")[-1]
164
+ elif file is None:
165
+ raise ValueError("file_format must be specified since file is None")
166
+ if file_format not in file_handlers:
167
+ raise TypeError(f"Unsupported format: {file_format}")
168
+
169
+ handler = file_handlers[file_format]
170
+ if file is None:
171
+ return handler.dump_to_str(obj, **kwargs)
172
+ elif is_str(file):
173
+ handler.dump_to_path(obj, file, **kwargs)
174
+ elif hasattr(file, "write"):
175
+ handler.dump_to_fileobj(obj, file, **kwargs)
176
+ else:
177
+ raise TypeError('"file" must be a filename str or a file-object')
groundingdino/util/time_counter.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+
4
+
5
+ class TimeCounter:
6
+ def __init__(self) -> None:
7
+ pass
8
+
9
+ def clear(self):
10
+ self.timedict = {}
11
+ self.basetime = time.perf_counter()
12
+
13
+ def timeit(self, name):
14
+ nowtime = time.perf_counter() - self.basetime
15
+ self.timedict[name] = nowtime
16
+ self.basetime = time.perf_counter()
17
+
18
+
19
+ class TimeHolder:
20
+ def __init__(self) -> None:
21
+ self.timedict = {}
22
+
23
+ def update(self, _timedict: dict):
24
+ for k, v in _timedict.items():
25
+ if k not in self.timedict:
26
+ self.timedict[k] = AverageMeter(name=k, val_only=True)
27
+ self.timedict[k].update(val=v)
28
+
29
+ def final_res(self):
30
+ return {k: v.avg for k, v in self.timedict.items()}
31
+
32
+ def __str__(self):
33
+ return json.dumps(self.final_res(), indent=2)
34
+
35
+
36
+ class AverageMeter(object):
37
+ """Computes and stores the average and current value"""
38
+
39
+ def __init__(self, name, fmt=":f", val_only=False):
40
+ self.name = name
41
+ self.fmt = fmt
42
+ self.val_only = val_only
43
+ self.reset()
44
+
45
+ def reset(self):
46
+ self.val = 0
47
+ self.avg = 0
48
+ self.sum = 0
49
+ self.count = 0
50
+
51
+ def update(self, val, n=1):
52
+ self.val = val
53
+ self.sum += val * n
54
+ self.count += n
55
+ self.avg = self.sum / self.count
56
+
57
+ def __str__(self):
58
+ if self.val_only:
59
+ fmtstr = "{name} {val" + self.fmt + "}"
60
+ else:
61
+ fmtstr = "{name} {val" + self.fmt + "} ({avg" + self.fmt + "})"
62
+ return fmtstr.format(**self.__dict__)
groundingdino/util/utils.py ADDED
@@ -0,0 +1,610 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ import warnings
4
+ from collections import OrderedDict
5
+ from copy import deepcopy
6
+ from typing import Any, Dict, List
7
+
8
+ import numpy as np
9
+ import torch
10
+ from transformers import AutoTokenizer
11
+
12
+ from groundingdino.util.slconfig import SLConfig
13
+
14
+
15
+ def slprint(x, name="x"):
16
+ if isinstance(x, (torch.Tensor, np.ndarray)):
17
+ print(f"{name}.shape:", x.shape)
18
+ elif isinstance(x, (tuple, list)):
19
+ print("type x:", type(x))
20
+ for i in range(min(10, len(x))):
21
+ slprint(x[i], f"{name}[{i}]")
22
+ elif isinstance(x, dict):
23
+ for k, v in x.items():
24
+ slprint(v, f"{name}[{k}]")
25
+ else:
26
+ print(f"{name}.type:", type(x))
27
+
28
+
29
+ def clean_state_dict(state_dict):
30
+ new_state_dict = OrderedDict()
31
+ for k, v in state_dict.items():
32
+ if k[:7] == "module.":
33
+ k = k[7:] # remove `module.`
34
+ new_state_dict[k] = v
35
+ return new_state_dict
36
+
37
+
38
+ def renorm(
39
+ img: torch.FloatTensor, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
40
+ ) -> torch.FloatTensor:
41
+ # img: tensor(3,H,W) or tensor(B,3,H,W)
42
+ # return: same as img
43
+ assert img.dim() == 3 or img.dim() == 4, "img.dim() should be 3 or 4 but %d" % img.dim()
44
+ if img.dim() == 3:
45
+ assert img.size(0) == 3, 'img.size(0) shoule be 3 but "%d". (%s)' % (
46
+ img.size(0),
47
+ str(img.size()),
48
+ )
49
+ img_perm = img.permute(1, 2, 0)
50
+ mean = torch.Tensor(mean)
51
+ std = torch.Tensor(std)
52
+ img_res = img_perm * std + mean
53
+ return img_res.permute(2, 0, 1)
54
+ else: # img.dim() == 4
55
+ assert img.size(1) == 3, 'img.size(1) shoule be 3 but "%d". (%s)' % (
56
+ img.size(1),
57
+ str(img.size()),
58
+ )
59
+ img_perm = img.permute(0, 2, 3, 1)
60
+ mean = torch.Tensor(mean)
61
+ std = torch.Tensor(std)
62
+ img_res = img_perm * std + mean
63
+ return img_res.permute(0, 3, 1, 2)
64
+
65
+
66
+ class CocoClassMapper:
67
+ def __init__(self) -> None:
68
+ self.category_map_str = {
69
+ "1": 1,
70
+ "2": 2,
71
+ "3": 3,
72
+ "4": 4,
73
+ "5": 5,
74
+ "6": 6,
75
+ "7": 7,
76
+ "8": 8,
77
+ "9": 9,
78
+ "10": 10,
79
+ "11": 11,
80
+ "13": 12,
81
+ "14": 13,
82
+ "15": 14,
83
+ "16": 15,
84
+ "17": 16,
85
+ "18": 17,
86
+ "19": 18,
87
+ "20": 19,
88
+ "21": 20,
89
+ "22": 21,
90
+ "23": 22,
91
+ "24": 23,
92
+ "25": 24,
93
+ "27": 25,
94
+ "28": 26,
95
+ "31": 27,
96
+ "32": 28,
97
+ "33": 29,
98
+ "34": 30,
99
+ "35": 31,
100
+ "36": 32,
101
+ "37": 33,
102
+ "38": 34,
103
+ "39": 35,
104
+ "40": 36,
105
+ "41": 37,
106
+ "42": 38,
107
+ "43": 39,
108
+ "44": 40,
109
+ "46": 41,
110
+ "47": 42,
111
+ "48": 43,
112
+ "49": 44,
113
+ "50": 45,
114
+ "51": 46,
115
+ "52": 47,
116
+ "53": 48,
117
+ "54": 49,
118
+ "55": 50,
119
+ "56": 51,
120
+ "57": 52,
121
+ "58": 53,
122
+ "59": 54,
123
+ "60": 55,
124
+ "61": 56,
125
+ "62": 57,
126
+ "63": 58,
127
+ "64": 59,
128
+ "65": 60,
129
+ "67": 61,
130
+ "70": 62,
131
+ "72": 63,
132
+ "73": 64,
133
+ "74": 65,
134
+ "75": 66,
135
+ "76": 67,
136
+ "77": 68,
137
+ "78": 69,
138
+ "79": 70,
139
+ "80": 71,
140
+ "81": 72,
141
+ "82": 73,
142
+ "84": 74,
143
+ "85": 75,
144
+ "86": 76,
145
+ "87": 77,
146
+ "88": 78,
147
+ "89": 79,
148
+ "90": 80,
149
+ }
150
+ self.origin2compact_mapper = {int(k): v - 1 for k, v in self.category_map_str.items()}
151
+ self.compact2origin_mapper = {int(v - 1): int(k) for k, v in self.category_map_str.items()}
152
+
153
+ def origin2compact(self, idx):
154
+ return self.origin2compact_mapper[int(idx)]
155
+
156
+ def compact2origin(self, idx):
157
+ return self.compact2origin_mapper[int(idx)]
158
+
159
+
160
+ def to_device(item, device):
161
+ if isinstance(item, torch.Tensor):
162
+ return item.to(device)
163
+ elif isinstance(item, list):
164
+ return [to_device(i, device) for i in item]
165
+ elif isinstance(item, dict):
166
+ return {k: to_device(v, device) for k, v in item.items()}
167
+ else:
168
+ raise NotImplementedError(
169
+ "Call Shilong if you use other containers! type: {}".format(type(item))
170
+ )
171
+
172
+
173
+ #
174
+ def get_gaussian_mean(x, axis, other_axis, softmax=True):
175
+ """
176
+
177
+ Args:
178
+ x (float): Input images(BxCxHxW)
179
+ axis (int): The index for weighted mean
180
+ other_axis (int): The other index
181
+
182
+ Returns: weighted index for axis, BxC
183
+
184
+ """
185
+ mat2line = torch.sum(x, axis=other_axis)
186
+ # mat2line = mat2line / mat2line.mean() * 10
187
+ if softmax:
188
+ u = torch.softmax(mat2line, axis=2)
189
+ else:
190
+ u = mat2line / (mat2line.sum(2, keepdim=True) + 1e-6)
191
+ size = x.shape[axis]
192
+ ind = torch.linspace(0, 1, size).to(x.device)
193
+ batch = x.shape[0]
194
+ channel = x.shape[1]
195
+ index = ind.repeat([batch, channel, 1])
196
+ mean_position = torch.sum(index * u, dim=2)
197
+ return mean_position
198
+
199
+
200
+ def get_expected_points_from_map(hm, softmax=True):
201
+ """get_gaussian_map_from_points
202
+ B,C,H,W -> B,N,2 float(0, 1) float(0, 1)
203
+ softargmax function
204
+
205
+ Args:
206
+ hm (float): Input images(BxCxHxW)
207
+
208
+ Returns:
209
+ weighted index for axis, BxCx2. float between 0 and 1.
210
+
211
+ """
212
+ # hm = 10*hm
213
+ B, C, H, W = hm.shape
214
+ y_mean = get_gaussian_mean(hm, 2, 3, softmax=softmax) # B,C
215
+ x_mean = get_gaussian_mean(hm, 3, 2, softmax=softmax) # B,C
216
+ # return torch.cat((x_mean.unsqueeze(-1), y_mean.unsqueeze(-1)), 2)
217
+ return torch.stack([x_mean, y_mean], dim=2)
218
+
219
+
220
+ # Positional encoding (section 5.1)
221
+ # borrow from nerf
222
+ class Embedder:
223
+ def __init__(self, **kwargs):
224
+ self.kwargs = kwargs
225
+ self.create_embedding_fn()
226
+
227
+ def create_embedding_fn(self):
228
+ embed_fns = []
229
+ d = self.kwargs["input_dims"]
230
+ out_dim = 0
231
+ if self.kwargs["include_input"]:
232
+ embed_fns.append(lambda x: x)
233
+ out_dim += d
234
+
235
+ max_freq = self.kwargs["max_freq_log2"]
236
+ N_freqs = self.kwargs["num_freqs"]
237
+
238
+ if self.kwargs["log_sampling"]:
239
+ freq_bands = 2.0 ** torch.linspace(0.0, max_freq, steps=N_freqs)
240
+ else:
241
+ freq_bands = torch.linspace(2.0**0.0, 2.0**max_freq, steps=N_freqs)
242
+
243
+ for freq in freq_bands:
244
+ for p_fn in self.kwargs["periodic_fns"]:
245
+ embed_fns.append(lambda x, p_fn=p_fn, freq=freq: p_fn(x * freq))
246
+ out_dim += d
247
+
248
+ self.embed_fns = embed_fns
249
+ self.out_dim = out_dim
250
+
251
+ def embed(self, inputs):
252
+ return torch.cat([fn(inputs) for fn in self.embed_fns], -1)
253
+
254
+
255
+ def get_embedder(multires, i=0):
256
+ import torch.nn as nn
257
+
258
+ if i == -1:
259
+ return nn.Identity(), 3
260
+
261
+ embed_kwargs = {
262
+ "include_input": True,
263
+ "input_dims": 3,
264
+ "max_freq_log2": multires - 1,
265
+ "num_freqs": multires,
266
+ "log_sampling": True,
267
+ "periodic_fns": [torch.sin, torch.cos],
268
+ }
269
+
270
+ embedder_obj = Embedder(**embed_kwargs)
271
+ embed = lambda x, eo=embedder_obj: eo.embed(x)
272
+ return embed, embedder_obj.out_dim
273
+
274
+
275
+ class APOPMeter:
276
+ def __init__(self) -> None:
277
+ self.tp = 0
278
+ self.fp = 0
279
+ self.tn = 0
280
+ self.fn = 0
281
+
282
+ def update(self, pred, gt):
283
+ """
284
+ Input:
285
+ pred, gt: Tensor()
286
+ """
287
+ assert pred.shape == gt.shape
288
+ self.tp += torch.logical_and(pred == 1, gt == 1).sum().item()
289
+ self.fp += torch.logical_and(pred == 1, gt == 0).sum().item()
290
+ self.tn += torch.logical_and(pred == 0, gt == 0).sum().item()
291
+ self.tn += torch.logical_and(pred == 1, gt == 0).sum().item()
292
+
293
+ def update_cm(self, tp, fp, tn, fn):
294
+ self.tp += tp
295
+ self.fp += fp
296
+ self.tn += tn
297
+ self.tn += fn
298
+
299
+
300
+ def inverse_sigmoid(x, eps=1e-5):
301
+ x = x.clamp(min=0, max=1)
302
+ x1 = x.clamp(min=eps)
303
+ x2 = (1 - x).clamp(min=eps)
304
+ return torch.log(x1 / x2)
305
+
306
+
307
+ def get_raw_dict(args):
308
+ """
309
+ return the dicf contained in args.
310
+
311
+ e.g:
312
+ >>> with open(path, 'w') as f:
313
+ json.dump(get_raw_dict(args), f, indent=2)
314
+ """
315
+ if isinstance(args, argparse.Namespace):
316
+ return vars(args)
317
+ elif isinstance(args, dict):
318
+ return args
319
+ elif isinstance(args, SLConfig):
320
+ return args._cfg_dict
321
+ else:
322
+ raise NotImplementedError("Unknown type {}".format(type(args)))
323
+
324
+
325
+ def stat_tensors(tensor):
326
+ assert tensor.dim() == 1
327
+ tensor_sm = tensor.softmax(0)
328
+ entropy = (tensor_sm * torch.log(tensor_sm + 1e-9)).sum()
329
+
330
+ return {
331
+ "max": tensor.max(),
332
+ "min": tensor.min(),
333
+ "mean": tensor.mean(),
334
+ "var": tensor.var(),
335
+ "std": tensor.var() ** 0.5,
336
+ "entropy": entropy,
337
+ }
338
+
339
+
340
+ class NiceRepr:
341
+ """Inherit from this class and define ``__nice__`` to "nicely" print your
342
+ objects.
343
+
344
+ Defines ``__str__`` and ``__repr__`` in terms of ``__nice__`` function
345
+ Classes that inherit from :class:`NiceRepr` should redefine ``__nice__``.
346
+ If the inheriting class has a ``__len__``, method then the default
347
+ ``__nice__`` method will return its length.
348
+
349
+ Example:
350
+ >>> class Foo(NiceRepr):
351
+ ... def __nice__(self):
352
+ ... return 'info'
353
+ >>> foo = Foo()
354
+ >>> assert str(foo) == '<Foo(info)>'
355
+ >>> assert repr(foo).startswith('<Foo(info) at ')
356
+
357
+ Example:
358
+ >>> class Bar(NiceRepr):
359
+ ... pass
360
+ >>> bar = Bar()
361
+ >>> import pytest
362
+ >>> with pytest.warns(None) as record:
363
+ >>> assert 'object at' in str(bar)
364
+ >>> assert 'object at' in repr(bar)
365
+
366
+ Example:
367
+ >>> class Baz(NiceRepr):
368
+ ... def __len__(self):
369
+ ... return 5
370
+ >>> baz = Baz()
371
+ >>> assert str(baz) == '<Baz(5)>'
372
+ """
373
+
374
+ def __nice__(self):
375
+ """str: a "nice" summary string describing this module"""
376
+ if hasattr(self, "__len__"):
377
+ # It is a common pattern for objects to use __len__ in __nice__
378
+ # As a convenience we define a default __nice__ for these objects
379
+ return str(len(self))
380
+ else:
381
+ # In all other cases force the subclass to overload __nice__
382
+ raise NotImplementedError(f"Define the __nice__ method for {self.__class__!r}")
383
+
384
+ def __repr__(self):
385
+ """str: the string of the module"""
386
+ try:
387
+ nice = self.__nice__()
388
+ classname = self.__class__.__name__
389
+ return f"<{classname}({nice}) at {hex(id(self))}>"
390
+ except NotImplementedError as ex:
391
+ warnings.warn(str(ex), category=RuntimeWarning)
392
+ return object.__repr__(self)
393
+
394
+ def __str__(self):
395
+ """str: the string of the module"""
396
+ try:
397
+ classname = self.__class__.__name__
398
+ nice = self.__nice__()
399
+ return f"<{classname}({nice})>"
400
+ except NotImplementedError as ex:
401
+ warnings.warn(str(ex), category=RuntimeWarning)
402
+ return object.__repr__(self)
403
+
404
+
405
+ def ensure_rng(rng=None):
406
+ """Coerces input into a random number generator.
407
+
408
+ If the input is None, then a global random state is returned.
409
+
410
+ If the input is a numeric value, then that is used as a seed to construct a
411
+ random state. Otherwise the input is returned as-is.
412
+
413
+ Adapted from [1]_.
414
+
415
+ Args:
416
+ rng (int | numpy.random.RandomState | None):
417
+ if None, then defaults to the global rng. Otherwise this can be an
418
+ integer or a RandomState class
419
+ Returns:
420
+ (numpy.random.RandomState) : rng -
421
+ a numpy random number generator
422
+
423
+ References:
424
+ .. [1] https://gitlab.kitware.com/computer-vision/kwarray/blob/master/kwarray/util_random.py#L270 # noqa: E501
425
+ """
426
+
427
+ if rng is None:
428
+ rng = np.random.mtrand._rand
429
+ elif isinstance(rng, int):
430
+ rng = np.random.RandomState(rng)
431
+ else:
432
+ rng = rng
433
+ return rng
434
+
435
+
436
+ def random_boxes(num=1, scale=1, rng=None):
437
+ """Simple version of ``kwimage.Boxes.random``
438
+
439
+ Returns:
440
+ Tensor: shape (n, 4) in x1, y1, x2, y2 format.
441
+
442
+ References:
443
+ https://gitlab.kitware.com/computer-vision/kwimage/blob/master/kwimage/structs/boxes.py#L1390
444
+
445
+ Example:
446
+ >>> num = 3
447
+ >>> scale = 512
448
+ >>> rng = 0
449
+ >>> boxes = random_boxes(num, scale, rng)
450
+ >>> print(boxes)
451
+ tensor([[280.9925, 278.9802, 308.6148, 366.1769],
452
+ [216.9113, 330.6978, 224.0446, 456.5878],
453
+ [405.3632, 196.3221, 493.3953, 270.7942]])
454
+ """
455
+ rng = ensure_rng(rng)
456
+
457
+ tlbr = rng.rand(num, 4).astype(np.float32)
458
+
459
+ tl_x = np.minimum(tlbr[:, 0], tlbr[:, 2])
460
+ tl_y = np.minimum(tlbr[:, 1], tlbr[:, 3])
461
+ br_x = np.maximum(tlbr[:, 0], tlbr[:, 2])
462
+ br_y = np.maximum(tlbr[:, 1], tlbr[:, 3])
463
+
464
+ tlbr[:, 0] = tl_x * scale
465
+ tlbr[:, 1] = tl_y * scale
466
+ tlbr[:, 2] = br_x * scale
467
+ tlbr[:, 3] = br_y * scale
468
+
469
+ boxes = torch.from_numpy(tlbr)
470
+ return boxes
471
+
472
+
473
+ class ModelEma(torch.nn.Module):
474
+ def __init__(self, model, decay=0.9997, device=None):
475
+ super(ModelEma, self).__init__()
476
+ # make a copy of the model for accumulating moving average of weights
477
+ self.module = deepcopy(model)
478
+ self.module.eval()
479
+
480
+ # import ipdb; ipdb.set_trace()
481
+
482
+ self.decay = decay
483
+ self.device = device # perform ema on different device from model if set
484
+ if self.device is not None:
485
+ self.module.to(device=device)
486
+
487
+ def _update(self, model, update_fn):
488
+ with torch.no_grad():
489
+ for ema_v, model_v in zip(
490
+ self.module.state_dict().values(), model.state_dict().values()
491
+ ):
492
+ if self.device is not None:
493
+ model_v = model_v.to(device=self.device)
494
+ ema_v.copy_(update_fn(ema_v, model_v))
495
+
496
+ def update(self, model):
497
+ self._update(model, update_fn=lambda e, m: self.decay * e + (1.0 - self.decay) * m)
498
+
499
+ def set(self, model):
500
+ self._update(model, update_fn=lambda e, m: m)
501
+
502
+
503
+ class BestMetricSingle:
504
+ def __init__(self, init_res=0.0, better="large") -> None:
505
+ self.init_res = init_res
506
+ self.best_res = init_res
507
+ self.best_ep = -1
508
+
509
+ self.better = better
510
+ assert better in ["large", "small"]
511
+
512
+ def isbetter(self, new_res, old_res):
513
+ if self.better == "large":
514
+ return new_res > old_res
515
+ if self.better == "small":
516
+ return new_res < old_res
517
+
518
+ def update(self, new_res, ep):
519
+ if self.isbetter(new_res, self.best_res):
520
+ self.best_res = new_res
521
+ self.best_ep = ep
522
+ return True
523
+ return False
524
+
525
+ def __str__(self) -> str:
526
+ return "best_res: {}\t best_ep: {}".format(self.best_res, self.best_ep)
527
+
528
+ def __repr__(self) -> str:
529
+ return self.__str__()
530
+
531
+ def summary(self) -> dict:
532
+ return {
533
+ "best_res": self.best_res,
534
+ "best_ep": self.best_ep,
535
+ }
536
+
537
+
538
+ class BestMetricHolder:
539
+ def __init__(self, init_res=0.0, better="large", use_ema=False) -> None:
540
+ self.best_all = BestMetricSingle(init_res, better)
541
+ self.use_ema = use_ema
542
+ if use_ema:
543
+ self.best_ema = BestMetricSingle(init_res, better)
544
+ self.best_regular = BestMetricSingle(init_res, better)
545
+
546
+ def update(self, new_res, epoch, is_ema=False):
547
+ """
548
+ return if the results is the best.
549
+ """
550
+ if not self.use_ema:
551
+ return self.best_all.update(new_res, epoch)
552
+ else:
553
+ if is_ema:
554
+ self.best_ema.update(new_res, epoch)
555
+ return self.best_all.update(new_res, epoch)
556
+ else:
557
+ self.best_regular.update(new_res, epoch)
558
+ return self.best_all.update(new_res, epoch)
559
+
560
+ def summary(self):
561
+ if not self.use_ema:
562
+ return self.best_all.summary()
563
+
564
+ res = {}
565
+ res.update({f"all_{k}": v for k, v in self.best_all.summary().items()})
566
+ res.update({f"regular_{k}": v for k, v in self.best_regular.summary().items()})
567
+ res.update({f"ema_{k}": v for k, v in self.best_ema.summary().items()})
568
+ return res
569
+
570
+ def __repr__(self) -> str:
571
+ return json.dumps(self.summary(), indent=2)
572
+
573
+ def __str__(self) -> str:
574
+ return self.__repr__()
575
+
576
+
577
+ def targets_to(targets: List[Dict[str, Any]], device):
578
+ """Moves the target dicts to the given device."""
579
+ excluded_keys = [
580
+ "questionId",
581
+ "tokens_positive",
582
+ "strings_positive",
583
+ "tokens",
584
+ "dataset_name",
585
+ "sentence_id",
586
+ "original_img_id",
587
+ "nb_eval",
588
+ "task_id",
589
+ "original_id",
590
+ "token_span",
591
+ "caption",
592
+ "dataset_type",
593
+ ]
594
+ return [
595
+ {k: v.to(device) if k not in excluded_keys else v for k, v in t.items()} for t in targets
596
+ ]
597
+
598
+
599
+ def get_phrases_from_posmap(
600
+ posmap: torch.BoolTensor, tokenized: Dict, tokenizer: AutoTokenizer, left_idx: int = 0, right_idx: int = 255
601
+ ):
602
+ assert isinstance(posmap, torch.Tensor), "posmap must be torch.Tensor"
603
+ if posmap.dim() == 1:
604
+ posmap[0: left_idx + 1] = False
605
+ posmap[right_idx:] = False
606
+ non_zero_idx = posmap.nonzero(as_tuple=True)[0].tolist()
607
+ token_ids = [tokenized["input_ids"][i] for i in non_zero_idx]
608
+ return tokenizer.decode(token_ids)
609
+ else:
610
+ raise NotImplementedError("posmap must be 1-dim")
groundingdino/util/visualizer.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : visualizer.py
4
+ @Time : 2022/04/05 11:39:33
5
+ @Author : Shilong Liu
6
+ @Contact : slongliu86@gmail.com
7
+ """
8
+
9
+ import datetime
10
+ import os
11
+
12
+ import cv2
13
+ import matplotlib.pyplot as plt
14
+ import numpy as np
15
+ import torch
16
+ from matplotlib import transforms
17
+ from matplotlib.collections import PatchCollection
18
+ from matplotlib.patches import Polygon
19
+ from pycocotools import mask as maskUtils
20
+
21
+
22
+ def renorm(
23
+ img: torch.FloatTensor, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
24
+ ) -> torch.FloatTensor:
25
+ # img: tensor(3,H,W) or tensor(B,3,H,W)
26
+ # return: same as img
27
+ assert img.dim() == 3 or img.dim() == 4, "img.dim() should be 3 or 4 but %d" % img.dim()
28
+ if img.dim() == 3:
29
+ assert img.size(0) == 3, 'img.size(0) shoule be 3 but "%d". (%s)' % (
30
+ img.size(0),
31
+ str(img.size()),
32
+ )
33
+ img_perm = img.permute(1, 2, 0)
34
+ mean = torch.Tensor(mean)
35
+ std = torch.Tensor(std)
36
+ img_res = img_perm * std + mean
37
+ return img_res.permute(2, 0, 1)
38
+ else: # img.dim() == 4
39
+ assert img.size(1) == 3, 'img.size(1) shoule be 3 but "%d". (%s)' % (
40
+ img.size(1),
41
+ str(img.size()),
42
+ )
43
+ img_perm = img.permute(0, 2, 3, 1)
44
+ mean = torch.Tensor(mean)
45
+ std = torch.Tensor(std)
46
+ img_res = img_perm * std + mean
47
+ return img_res.permute(0, 3, 1, 2)
48
+
49
+
50
+ class ColorMap:
51
+ def __init__(self, basergb=[255, 255, 0]):
52
+ self.basergb = np.array(basergb)
53
+
54
+ def __call__(self, attnmap):
55
+ # attnmap: h, w. np.uint8.
56
+ # return: h, w, 4. np.uint8.
57
+ assert attnmap.dtype == np.uint8
58
+ h, w = attnmap.shape
59
+ res = self.basergb.copy()
60
+ res = res[None][None].repeat(h, 0).repeat(w, 1) # h, w, 3
61
+ attn1 = attnmap.copy()[..., None] # h, w, 1
62
+ res = np.concatenate((res, attn1), axis=-1).astype(np.uint8)
63
+ return res
64
+
65
+
66
+ def rainbow_text(x, y, ls, lc, **kw):
67
+ """
68
+ Take a list of strings ``ls`` and colors ``lc`` and place them next to each
69
+ other, with text ls[i] being shown in color lc[i].
70
+
71
+ This example shows how to do both vertical and horizontal text, and will
72
+ pass all keyword arguments to plt.text, so you can set the font size,
73
+ family, etc.
74
+ """
75
+ t = plt.gca().transData
76
+ fig = plt.gcf()
77
+ plt.show()
78
+
79
+ # horizontal version
80
+ for s, c in zip(ls, lc):
81
+ text = plt.text(x, y, " " + s + " ", color=c, transform=t, **kw)
82
+ text.draw(fig.canvas.get_renderer())
83
+ ex = text.get_window_extent()
84
+ t = transforms.offset_copy(text._transform, x=ex.width, units="dots")
85
+
86
+ # #vertical version
87
+ # for s,c in zip(ls,lc):
88
+ # text = plt.text(x,y," "+s+" ",color=c, transform=t,
89
+ # rotation=90,va='bottom',ha='center',**kw)
90
+ # text.draw(fig.canvas.get_renderer())
91
+ # ex = text.get_window_extent()
92
+ # t = transforms.offset_copy(text._transform, y=ex.height, units='dots')
93
+
94
+
95
+ class COCOVisualizer:
96
+ def __init__(self, coco=None, tokenlizer=None) -> None:
97
+ self.coco = coco
98
+
99
+ def visualize(self, img, tgt, caption=None, dpi=180, savedir="vis"):
100
+ """
101
+ img: tensor(3, H, W)
102
+ tgt: make sure they are all on cpu.
103
+ must have items: 'image_id', 'boxes', 'size'
104
+ """
105
+ plt.figure(dpi=dpi)
106
+ plt.rcParams["font.size"] = "5"
107
+ ax = plt.gca()
108
+ img = renorm(img).permute(1, 2, 0)
109
+ # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO':
110
+ # import ipdb; ipdb.set_trace()
111
+ ax.imshow(img)
112
+
113
+ self.addtgt(tgt)
114
+
115
+ if tgt is None:
116
+ image_id = 0
117
+ elif "image_id" not in tgt:
118
+ image_id = 0
119
+ else:
120
+ image_id = tgt["image_id"]
121
+
122
+ if caption is None:
123
+ savename = "{}/{}-{}.png".format(
124
+ savedir, int(image_id), str(datetime.datetime.now()).replace(" ", "-")
125
+ )
126
+ else:
127
+ savename = "{}/{}-{}-{}.png".format(
128
+ savedir, caption, int(image_id), str(datetime.datetime.now()).replace(" ", "-")
129
+ )
130
+ print("savename: {}".format(savename))
131
+ os.makedirs(os.path.dirname(savename), exist_ok=True)
132
+ plt.savefig(savename)
133
+ plt.close()
134
+
135
+ def addtgt(self, tgt):
136
+ """ """
137
+ if tgt is None or not "boxes" in tgt:
138
+ ax = plt.gca()
139
+
140
+ if "caption" in tgt:
141
+ ax.set_title(tgt["caption"], wrap=True)
142
+
143
+ ax.set_axis_off()
144
+ return
145
+
146
+ ax = plt.gca()
147
+ H, W = tgt["size"]
148
+ numbox = tgt["boxes"].shape[0]
149
+
150
+ color = []
151
+ polygons = []
152
+ boxes = []
153
+ for box in tgt["boxes"].cpu():
154
+ unnormbbox = box * torch.Tensor([W, H, W, H])
155
+ unnormbbox[:2] -= unnormbbox[2:] / 2
156
+ [bbox_x, bbox_y, bbox_w, bbox_h] = unnormbbox.tolist()
157
+ boxes.append([bbox_x, bbox_y, bbox_w, bbox_h])
158
+ poly = [
159
+ [bbox_x, bbox_y],
160
+ [bbox_x, bbox_y + bbox_h],
161
+ [bbox_x + bbox_w, bbox_y + bbox_h],
162
+ [bbox_x + bbox_w, bbox_y],
163
+ ]
164
+ np_poly = np.array(poly).reshape((4, 2))
165
+ polygons.append(Polygon(np_poly))
166
+ c = (np.random.random((1, 3)) * 0.6 + 0.4).tolist()[0]
167
+ color.append(c)
168
+
169
+ p = PatchCollection(polygons, facecolor=color, linewidths=0, alpha=0.1)
170
+ ax.add_collection(p)
171
+ p = PatchCollection(polygons, facecolor="none", edgecolors=color, linewidths=2)
172
+ ax.add_collection(p)
173
+
174
+ if "strings_positive" in tgt and len(tgt["strings_positive"]) > 0:
175
+ assert (
176
+ len(tgt["strings_positive"]) == numbox
177
+ ), f"{len(tgt['strings_positive'])} = {numbox}, "
178
+ for idx, strlist in enumerate(tgt["strings_positive"]):
179
+ cate_id = int(tgt["labels"][idx])
180
+ _string = str(cate_id) + ":" + " ".join(strlist)
181
+ bbox_x, bbox_y, bbox_w, bbox_h = boxes[idx]
182
+ # ax.text(bbox_x, bbox_y, _string, color='black', bbox={'facecolor': 'yellow', 'alpha': 1.0, 'pad': 1})
183
+ ax.text(
184
+ bbox_x,
185
+ bbox_y,
186
+ _string,
187
+ color="black",
188
+ bbox={"facecolor": color[idx], "alpha": 0.6, "pad": 1},
189
+ )
190
+
191
+ if "box_label" in tgt:
192
+ assert len(tgt["box_label"]) == numbox, f"{len(tgt['box_label'])} = {numbox}, "
193
+ for idx, bl in enumerate(tgt["box_label"]):
194
+ _string = str(bl)
195
+ bbox_x, bbox_y, bbox_w, bbox_h = boxes[idx]
196
+ # ax.text(bbox_x, bbox_y, _string, color='black', bbox={'facecolor': 'yellow', 'alpha': 1.0, 'pad': 1})
197
+ ax.text(
198
+ bbox_x,
199
+ bbox_y,
200
+ _string,
201
+ color="black",
202
+ bbox={"facecolor": color[idx], "alpha": 0.6, "pad": 1},
203
+ )
204
+
205
+ if "caption" in tgt:
206
+ ax.set_title(tgt["caption"], wrap=True)
207
+ # plt.figure()
208
+ # rainbow_text(0.0,0.0,"all unicorns poop rainbows ! ! !".split(),
209
+ # ['red', 'orange', 'brown', 'green', 'blue', 'purple', 'black'])
210
+
211
+ if "attn" in tgt:
212
+ # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO':
213
+ # import ipdb; ipdb.set_trace()
214
+ if isinstance(tgt["attn"], tuple):
215
+ tgt["attn"] = [tgt["attn"]]
216
+ for item in tgt["attn"]:
217
+ attn_map, basergb = item
218
+ attn_map = (attn_map - attn_map.min()) / (attn_map.max() - attn_map.min() + 1e-3)
219
+ attn_map = (attn_map * 255).astype(np.uint8)
220
+ cm = ColorMap(basergb)
221
+ heatmap = cm(attn_map)
222
+ ax.imshow(heatmap)
223
+ ax.set_axis_off()
224
+
225
+ def showAnns(self, anns, draw_bbox=False):
226
+ """
227
+ Display the specified annotations.
228
+ :param anns (array of object): annotations to display
229
+ :return: None
230
+ """
231
+ if len(anns) == 0:
232
+ return 0
233
+ if "segmentation" in anns[0] or "keypoints" in anns[0]:
234
+ datasetType = "instances"
235
+ elif "caption" in anns[0]:
236
+ datasetType = "captions"
237
+ else:
238
+ raise Exception("datasetType not supported")
239
+ if datasetType == "instances":
240
+ ax = plt.gca()
241
+ ax.set_autoscale_on(False)
242
+ polygons = []
243
+ color = []
244
+ for ann in anns:
245
+ c = (np.random.random((1, 3)) * 0.6 + 0.4).tolist()[0]
246
+ if "segmentation" in ann:
247
+ if type(ann["segmentation"]) == list:
248
+ # polygon
249
+ for seg in ann["segmentation"]:
250
+ poly = np.array(seg).reshape((int(len(seg) / 2), 2))
251
+ polygons.append(Polygon(poly))
252
+ color.append(c)
253
+ else:
254
+ # mask
255
+ t = self.imgs[ann["image_id"]]
256
+ if type(ann["segmentation"]["counts"]) == list:
257
+ rle = maskUtils.frPyObjects(
258
+ [ann["segmentation"]], t["height"], t["width"]
259
+ )
260
+ else:
261
+ rle = [ann["segmentation"]]
262
+ m = maskUtils.decode(rle)
263
+ img = np.ones((m.shape[0], m.shape[1], 3))
264
+ if ann["iscrowd"] == 1:
265
+ color_mask = np.array([2.0, 166.0, 101.0]) / 255
266
+ if ann["iscrowd"] == 0:
267
+ color_mask = np.random.random((1, 3)).tolist()[0]
268
+ for i in range(3):
269
+ img[:, :, i] = color_mask[i]
270
+ ax.imshow(np.dstack((img, m * 0.5)))
271
+ if "keypoints" in ann and type(ann["keypoints"]) == list:
272
+ # turn skeleton into zero-based index
273
+ sks = np.array(self.loadCats(ann["category_id"])[0]["skeleton"]) - 1
274
+ kp = np.array(ann["keypoints"])
275
+ x = kp[0::3]
276
+ y = kp[1::3]
277
+ v = kp[2::3]
278
+ for sk in sks:
279
+ if np.all(v[sk] > 0):
280
+ plt.plot(x[sk], y[sk], linewidth=3, color=c)
281
+ plt.plot(
282
+ x[v > 0],
283
+ y[v > 0],
284
+ "o",
285
+ markersize=8,
286
+ markerfacecolor=c,
287
+ markeredgecolor="k",
288
+ markeredgewidth=2,
289
+ )
290
+ plt.plot(
291
+ x[v > 1],
292
+ y[v > 1],
293
+ "o",
294
+ markersize=8,
295
+ markerfacecolor=c,
296
+ markeredgecolor=c,
297
+ markeredgewidth=2,
298
+ )
299
+
300
+ if draw_bbox:
301
+ [bbox_x, bbox_y, bbox_w, bbox_h] = ann["bbox"]
302
+ poly = [
303
+ [bbox_x, bbox_y],
304
+ [bbox_x, bbox_y + bbox_h],
305
+ [bbox_x + bbox_w, bbox_y + bbox_h],
306
+ [bbox_x + bbox_w, bbox_y],
307
+ ]
308
+ np_poly = np.array(poly).reshape((4, 2))
309
+ polygons.append(Polygon(np_poly))
310
+ color.append(c)
311
+
312
+ # p = PatchCollection(polygons, facecolor=color, linewidths=0, alpha=0.4)
313
+ # ax.add_collection(p)
314
+ p = PatchCollection(polygons, facecolor="none", edgecolors=color, linewidths=2)
315
+ ax.add_collection(p)
316
+ elif datasetType == "captions":
317
+ for ann in anns:
318
+ print(ann["caption"])
groundingdino/util/vl_utils.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ from typing import List
4
+
5
+ import torch
6
+
7
+
8
+ def create_positive_map_from_span(tokenized, token_span, max_text_len=256):
9
+ """construct a map such that positive_map[i,j] = True iff box i is associated to token j
10
+ Input:
11
+ - tokenized:
12
+ - input_ids: Tensor[1, ntokens]
13
+ - attention_mask: Tensor[1, ntokens]
14
+ - token_span: list with length num_boxes.
15
+ - each item: [start_idx, end_idx]
16
+ """
17
+ positive_map = torch.zeros((len(token_span), max_text_len), dtype=torch.float)
18
+ for j, tok_list in enumerate(token_span):
19
+ for (beg, end) in tok_list:
20
+ beg_pos = tokenized.char_to_token(beg)
21
+ end_pos = tokenized.char_to_token(end - 1)
22
+ if beg_pos is None:
23
+ try:
24
+ beg_pos = tokenized.char_to_token(beg + 1)
25
+ if beg_pos is None:
26
+ beg_pos = tokenized.char_to_token(beg + 2)
27
+ except:
28
+ beg_pos = None
29
+ if end_pos is None:
30
+ try:
31
+ end_pos = tokenized.char_to_token(end - 2)
32
+ if end_pos is None:
33
+ end_pos = tokenized.char_to_token(end - 3)
34
+ except:
35
+ end_pos = None
36
+ if beg_pos is None or end_pos is None:
37
+ continue
38
+
39
+ assert beg_pos is not None and end_pos is not None
40
+ if os.environ.get("SHILONG_DEBUG_ONLY_ONE_POS", None) == "TRUE":
41
+ positive_map[j, beg_pos] = 1
42
+ break
43
+ else:
44
+ positive_map[j, beg_pos : end_pos + 1].fill_(1)
45
+
46
+ return positive_map / (positive_map.sum(-1)[:, None] + 1e-6)
47
+
48
+
49
+ def build_captions_and_token_span(cat_list, force_lowercase):
50
+ """
51
+ Return:
52
+ captions: str
53
+ cat2tokenspan: dict
54
+ {
55
+ 'dog': [[0, 2]],
56
+ ...
57
+ }
58
+ """
59
+
60
+ cat2tokenspan = {}
61
+ captions = ""
62
+ for catname in cat_list:
63
+ class_name = catname
64
+ if force_lowercase:
65
+ class_name = class_name.lower()
66
+ if "/" in class_name:
67
+ class_name_list: List = class_name.strip().split("/")
68
+ class_name_list.append(class_name)
69
+ class_name: str = random.choice(class_name_list)
70
+
71
+ tokens_positive_i = []
72
+ subnamelist = [i.strip() for i in class_name.strip().split(" ")]
73
+ for subname in subnamelist:
74
+ if len(subname) == 0:
75
+ continue
76
+ if len(captions) > 0:
77
+ captions = captions + " "
78
+ strat_idx = len(captions)
79
+ end_idx = strat_idx + len(subname)
80
+ tokens_positive_i.append([strat_idx, end_idx])
81
+ captions = captions + subname
82
+
83
+ if len(tokens_positive_i) > 0:
84
+ captions = captions + " ."
85
+ cat2tokenspan[class_name] = tokens_positive_i
86
+
87
+ return captions, cat2tokenspan
88
+
89
+
90
+ def build_id2posspan_and_caption(category_dict: dict):
91
+ """Build id2pos_span and caption from category_dict
92
+
93
+ Args:
94
+ category_dict (dict): category_dict
95
+ """
96
+ cat_list = [item["name"].lower() for item in category_dict]
97
+ id2catname = {item["id"]: item["name"].lower() for item in category_dict}
98
+ caption, cat2posspan = build_captions_and_token_span(cat_list, force_lowercase=True)
99
+ id2posspan = {catid: cat2posspan[catname] for catid, catname in id2catname.items()}
100
+ return id2posspan, caption
img/336.jpg ADDED
img/inference-architecture.jpg ADDED

Git LFS Details

  • SHA256: d154e0e1ba2601ee5a426424f1e1f0efb83a84564d1564960e6e13aa63d3c7ea
  • Pointer size: 131 Bytes
  • Size of remote file: 108 kB
img/synthetic_blueberry.jpg ADDED

Git LFS Details

  • SHA256: f911a4f249c4de149a883b65ff77f549d5c848aebf40f1a246c28983983fa140
  • Pointer size: 132 Bytes
  • Size of remote file: 1.47 MB
img/synthetic_strawberry.jpg ADDED

Git LFS Details

  • SHA256: 861339ad69483128a7c50cc356f10d708e50e64d50beb11ae07438f11cc68220
  • Pointer size: 132 Bytes
  • Size of remote file: 1.38 MB
img/teaser.jpg ADDED

Git LFS Details

  • SHA256: bfe4ec0216403aa012c534a562dfd03a352b6cf51fff9f0fb531bda8a7753002
  • Pointer size: 131 Bytes
  • Size of remote file: 425 kB
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:94eabe4e2595dac1e04cc1703641458b13c1c624ce10072ba57dd96b625a41f5
3
+ size 937560192
models/GroundingDINO/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+ # Conditional DETR
8
+ # Copyright (c) 2021 Microsoft. All Rights Reserved.
9
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
10
+ # ------------------------------------------------------------------------
11
+ # Copied from DETR (https://github.com/facebookresearch/detr)
12
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
13
+ # ------------------------------------------------------------------------
14
+
15
+ from .groundingdino import build_groundingdino
models/GroundingDINO/backbone/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .backbone import build_backbone
models/GroundingDINO/backbone/backbone.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+ # Conditional DETR
8
+ # Copyright (c) 2021 Microsoft. All Rights Reserved.
9
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
10
+ # ------------------------------------------------------------------------
11
+ # Copied from DETR (https://github.com/facebookresearch/detr)
12
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
13
+ # ------------------------------------------------------------------------
14
+
15
+ """
16
+ Backbone modules.
17
+ """
18
+
19
+ from typing import Dict, List
20
+
21
+ import torch
22
+ import torch.nn.functional as F
23
+ import torchvision
24
+ from torch import nn
25
+ from torchvision.models._utils import IntermediateLayerGetter
26
+
27
+ from groundingdino.util.misc import NestedTensor, clean_state_dict, is_main_process
28
+
29
+ from .position_encoding import build_position_encoding
30
+ from .swin_transformer import build_swin_transformer
31
+
32
+
33
+ class FrozenBatchNorm2d(torch.nn.Module):
34
+ """
35
+ BatchNorm2d where the batch statistics and the affine parameters are fixed.
36
+
37
+ Copy-paste from torchvision.misc.ops with added eps before rqsrt,
38
+ without which any other models than torchvision.models.resnet[18,34,50,101]
39
+ produce nans.
40
+ """
41
+
42
+ def __init__(self, n):
43
+ super(FrozenBatchNorm2d, self).__init__()
44
+ self.register_buffer("weight", torch.ones(n))
45
+ self.register_buffer("bias", torch.zeros(n))
46
+ self.register_buffer("running_mean", torch.zeros(n))
47
+ self.register_buffer("running_var", torch.ones(n))
48
+
49
+ def _load_from_state_dict(
50
+ self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
51
+ ):
52
+ num_batches_tracked_key = prefix + "num_batches_tracked"
53
+ if num_batches_tracked_key in state_dict:
54
+ del state_dict[num_batches_tracked_key]
55
+
56
+ super(FrozenBatchNorm2d, self)._load_from_state_dict(
57
+ state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
58
+ )
59
+
60
+ def forward(self, x):
61
+ # move reshapes to the beginning
62
+ # to make it fuser-friendly
63
+ w = self.weight.reshape(1, -1, 1, 1)
64
+ b = self.bias.reshape(1, -1, 1, 1)
65
+ rv = self.running_var.reshape(1, -1, 1, 1)
66
+ rm = self.running_mean.reshape(1, -1, 1, 1)
67
+ eps = 1e-5
68
+ scale = w * (rv + eps).rsqrt()
69
+ bias = b - rm * scale
70
+ return x * scale + bias
71
+
72
+
73
+ class BackboneBase(nn.Module):
74
+ def __init__(
75
+ self,
76
+ backbone: nn.Module,
77
+ train_backbone: bool,
78
+ num_channels: int,
79
+ return_interm_indices: list,
80
+ ):
81
+ super().__init__()
82
+ for name, parameter in backbone.named_parameters():
83
+ if (
84
+ not train_backbone
85
+ or "layer2" not in name
86
+ and "layer3" not in name
87
+ and "layer4" not in name
88
+ ):
89
+ parameter.requires_grad_(False)
90
+
91
+ return_layers = {}
92
+ for idx, layer_index in enumerate(return_interm_indices):
93
+ return_layers.update(
94
+ {"layer{}".format(5 - len(return_interm_indices) + idx): "{}".format(layer_index)}
95
+ )
96
+
97
+ # if len:
98
+ # if use_stage1_feature:
99
+ # return_layers = {"layer1": "0", "layer2": "1", "layer3": "2", "layer4": "3"}
100
+ # else:
101
+ # return_layers = {"layer2": "0", "layer3": "1", "layer4": "2"}
102
+ # else:
103
+ # return_layers = {'layer4': "0"}
104
+ self.body = IntermediateLayerGetter(backbone, return_layers=return_layers)
105
+ self.num_channels = num_channels
106
+
107
+ def forward(self, tensor_list: NestedTensor):
108
+ xs = self.body(tensor_list.tensors)
109
+ out: Dict[str, NestedTensor] = {}
110
+ for name, x in xs.items():
111
+ m = tensor_list.mask
112
+ assert m is not None
113
+ mask = F.interpolate(m[None].float(), size=x.shape[-2:]).to(torch.bool)[0]
114
+ out[name] = NestedTensor(x, mask)
115
+ # import ipdb; ipdb.set_trace()
116
+ return out
117
+
118
+
119
+ class Backbone(BackboneBase):
120
+ """ResNet backbone with frozen BatchNorm."""
121
+
122
+ def __init__(
123
+ self,
124
+ name: str,
125
+ train_backbone: bool,
126
+ dilation: bool,
127
+ return_interm_indices: list,
128
+ batch_norm=FrozenBatchNorm2d,
129
+ ):
130
+ if name in ["resnet18", "resnet34", "resnet50", "resnet101"]:
131
+ backbone = getattr(torchvision.models, name)(
132
+ replace_stride_with_dilation=[False, False, dilation],
133
+ pretrained=is_main_process(),
134
+ norm_layer=batch_norm,
135
+ )
136
+ else:
137
+ raise NotImplementedError("Why you can get here with name {}".format(name))
138
+ # num_channels = 512 if name in ('resnet18', 'resnet34') else 2048
139
+ assert name not in ("resnet18", "resnet34"), "Only resnet50 and resnet101 are available."
140
+ assert return_interm_indices in [[0, 1, 2, 3], [1, 2, 3], [3]]
141
+ num_channels_all = [256, 512, 1024, 2048]
142
+ num_channels = num_channels_all[4 - len(return_interm_indices) :]
143
+ super().__init__(backbone, train_backbone, num_channels, return_interm_indices)
144
+
145
+
146
+ class Joiner(nn.Sequential):
147
+ def __init__(self, backbone, position_embedding):
148
+ super().__init__(backbone, position_embedding)
149
+
150
+ def forward(self, tensor_list: NestedTensor):
151
+ xs = self[0](tensor_list)
152
+ out: List[NestedTensor] = []
153
+ pos = []
154
+ for name, x in xs.items():
155
+ out.append(x)
156
+ # position encoding
157
+ pos.append(self[1](x).to(x.tensors.dtype))
158
+
159
+ return out, pos
160
+
161
+
162
+ def build_backbone(args):
163
+ """
164
+ Useful args:
165
+ - backbone: backbone name
166
+ - lr_backbone:
167
+ - dilation
168
+ - return_interm_indices: available: [0,1,2,3], [1,2,3], [3]
169
+ - backbone_freeze_keywords:
170
+ - use_checkpoint: for swin only for now
171
+
172
+ """
173
+ position_embedding = build_position_encoding(args)
174
+ train_backbone = True
175
+ if not train_backbone:
176
+ raise ValueError("Please set lr_backbone > 0")
177
+ return_interm_indices = args.return_interm_indices
178
+ assert return_interm_indices in [[0, 1, 2, 3], [1, 2, 3], [3]]
179
+ args.backbone_freeze_keywords
180
+ use_checkpoint = getattr(args, "use_checkpoint", False)
181
+
182
+ if args.backbone in ["resnet50", "resnet101"]:
183
+ backbone = Backbone(
184
+ args.backbone,
185
+ train_backbone,
186
+ args.dilation,
187
+ return_interm_indices,
188
+ batch_norm=FrozenBatchNorm2d,
189
+ )
190
+ bb_num_channels = backbone.num_channels
191
+ elif args.backbone in [
192
+ "swin_T_224_1k",
193
+ "swin_B_224_22k",
194
+ "swin_B_384_22k",
195
+ "swin_L_224_22k",
196
+ "swin_L_384_22k",
197
+ ]:
198
+ pretrain_img_size = int(args.backbone.split("_")[-2])
199
+ backbone = build_swin_transformer(
200
+ args.backbone,
201
+ pretrain_img_size=pretrain_img_size,
202
+ out_indices=tuple(return_interm_indices),
203
+ dilation=False,
204
+ use_checkpoint=use_checkpoint,
205
+ )
206
+
207
+ bb_num_channels = backbone.num_features[4 - len(return_interm_indices) :]
208
+ else:
209
+ raise NotImplementedError("Unknown backbone {}".format(args.backbone))
210
+
211
+ assert len(bb_num_channels) == len(
212
+ return_interm_indices
213
+ ), f"len(bb_num_channels) {len(bb_num_channels)} != len(return_interm_indices) {len(return_interm_indices)}"
214
+
215
+ model = Joiner(backbone, position_embedding)
216
+ model.num_channels = bb_num_channels
217
+ assert isinstance(
218
+ bb_num_channels, List
219
+ ), "bb_num_channels is expected to be a List but {}".format(type(bb_num_channels))
220
+ # import ipdb; ipdb.set_trace()
221
+ return model
models/GroundingDINO/backbone/position_encoding.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+ # DINO
8
+ # Copyright (c) 2022 IDEA. All Rights Reserved.
9
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
10
+ # ------------------------------------------------------------------------
11
+ # Conditional DETR
12
+ # Copyright (c) 2021 Microsoft. All Rights Reserved.
13
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
14
+ # ------------------------------------------------------------------------
15
+ # Copied from DETR (https://github.com/facebookresearch/detr)
16
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
17
+ # ------------------------------------------------------------------------
18
+
19
+ """
20
+ Various positional encodings for the transformer.
21
+ """
22
+ import math
23
+
24
+ import torch
25
+ from torch import nn
26
+
27
+ from groundingdino.util.misc import NestedTensor
28
+
29
+
30
+ class PositionEmbeddingSine(nn.Module):
31
+ """
32
+ This is a more standard version of the position embedding, very similar to the one
33
+ used by the Attention is all you need paper, generalized to work on images.
34
+ """
35
+
36
+ def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):
37
+ super().__init__()
38
+ self.num_pos_feats = num_pos_feats
39
+ self.temperature = temperature
40
+ self.normalize = normalize
41
+ if scale is not None and normalize is False:
42
+ raise ValueError("normalize should be True if scale is passed")
43
+ if scale is None:
44
+ scale = 2 * math.pi
45
+ self.scale = scale
46
+
47
+ def forward(self, tensor_list: NestedTensor):
48
+ x = tensor_list.tensors
49
+ mask = tensor_list.mask
50
+ assert mask is not None
51
+ not_mask = ~mask
52
+ y_embed = not_mask.cumsum(1, dtype=torch.float32)
53
+ x_embed = not_mask.cumsum(2, dtype=torch.float32)
54
+ if self.normalize:
55
+ eps = 1e-6
56
+ # if os.environ.get("SHILONG_AMP", None) == '1':
57
+ # eps = 1e-4
58
+ # else:
59
+ # eps = 1e-6
60
+ y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale
61
+ x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale
62
+
63
+ dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
64
+ dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats)
65
+
66
+ pos_x = x_embed[:, :, :, None] / dim_t
67
+ pos_y = y_embed[:, :, :, None] / dim_t
68
+ pos_x = torch.stack(
69
+ (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4
70
+ ).flatten(3)
71
+ pos_y = torch.stack(
72
+ (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4
73
+ ).flatten(3)
74
+ pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
75
+ return pos
76
+
77
+
78
+ class PositionEmbeddingSineHW(nn.Module):
79
+ """
80
+ This is a more standard version of the position embedding, very similar to the one
81
+ used by the Attention is all you need paper, generalized to work on images.
82
+ """
83
+
84
+ def __init__(
85
+ self, num_pos_feats=64, temperatureH=10000, temperatureW=10000, normalize=False, scale=None
86
+ ):
87
+ super().__init__()
88
+ self.num_pos_feats = num_pos_feats
89
+ self.temperatureH = temperatureH
90
+ self.temperatureW = temperatureW
91
+ self.normalize = normalize
92
+ if scale is not None and normalize is False:
93
+ raise ValueError("normalize should be True if scale is passed")
94
+ if scale is None:
95
+ scale = 2 * math.pi
96
+ self.scale = scale
97
+
98
+ def forward(self, tensor_list: NestedTensor):
99
+ x = tensor_list.tensors
100
+ mask = tensor_list.mask
101
+ assert mask is not None
102
+ not_mask = ~mask
103
+ y_embed = not_mask.cumsum(1, dtype=torch.float32)
104
+ x_embed = not_mask.cumsum(2, dtype=torch.float32)
105
+
106
+ # import ipdb; ipdb.set_trace()
107
+
108
+ if self.normalize:
109
+ eps = 1e-6
110
+ y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale
111
+ x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale
112
+
113
+ dim_tx = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
114
+ dim_tx = self.temperatureW ** (2 * (torch.div(dim_tx, 2, rounding_mode='floor')) / self.num_pos_feats)
115
+ pos_x = x_embed[:, :, :, None] / dim_tx
116
+
117
+ dim_ty = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
118
+ dim_ty = self.temperatureH ** (2 * (torch.div(dim_ty, 2, rounding_mode='floor')) / self.num_pos_feats)
119
+ pos_y = y_embed[:, :, :, None] / dim_ty
120
+
121
+ pos_x = torch.stack(
122
+ (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4
123
+ ).flatten(3)
124
+ pos_y = torch.stack(
125
+ (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4
126
+ ).flatten(3)
127
+ pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
128
+
129
+ # import ipdb; ipdb.set_trace()
130
+
131
+ return pos
132
+
133
+
134
+ class PositionEmbeddingLearned(nn.Module):
135
+ """
136
+ Absolute pos embedding, learned.
137
+ """
138
+
139
+ def __init__(self, num_pos_feats=256):
140
+ super().__init__()
141
+ self.row_embed = nn.Embedding(50, num_pos_feats)
142
+ self.col_embed = nn.Embedding(50, num_pos_feats)
143
+ self.reset_parameters()
144
+
145
+ def reset_parameters(self):
146
+ nn.init.uniform_(self.row_embed.weight)
147
+ nn.init.uniform_(self.col_embed.weight)
148
+
149
+ def forward(self, tensor_list: NestedTensor):
150
+ x = tensor_list.tensors
151
+ h, w = x.shape[-2:]
152
+ i = torch.arange(w, device=x.device)
153
+ j = torch.arange(h, device=x.device)
154
+ x_emb = self.col_embed(i)
155
+ y_emb = self.row_embed(j)
156
+ pos = (
157
+ torch.cat(
158
+ [
159
+ x_emb.unsqueeze(0).repeat(h, 1, 1),
160
+ y_emb.unsqueeze(1).repeat(1, w, 1),
161
+ ],
162
+ dim=-1,
163
+ )
164
+ .permute(2, 0, 1)
165
+ .unsqueeze(0)
166
+ .repeat(x.shape[0], 1, 1, 1)
167
+ )
168
+ return pos
169
+
170
+
171
+ def build_position_encoding(args):
172
+ N_steps = args.hidden_dim // 2
173
+ if args.position_embedding in ("v2", "sine"):
174
+ # TODO find a better way of exposing other arguments
175
+ position_embedding = PositionEmbeddingSineHW(
176
+ N_steps,
177
+ temperatureH=args.pe_temperatureH,
178
+ temperatureW=args.pe_temperatureW,
179
+ normalize=True,
180
+ )
181
+ elif args.position_embedding in ("v3", "learned"):
182
+ position_embedding = PositionEmbeddingLearned(N_steps)
183
+ else:
184
+ raise ValueError(f"not supported {args.position_embedding}")
185
+
186
+ return position_embedding
models/GroundingDINO/backbone/swin_transformer.py ADDED
@@ -0,0 +1,804 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+ # DINO
8
+ # Copyright (c) 2022 IDEA. All Rights Reserved.
9
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
10
+ # --------------------------------------------------------
11
+ # modified from https://github.com/SwinTransformer/Swin-Transformer-Object-Detection/blob/master/mmdet/models/backbones/swin_transformer.py
12
+ # --------------------------------------------------------
13
+
14
+ import numpy as np
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+ import torch.utils.checkpoint as checkpoint
19
+ from timm.models.layers import DropPath, to_2tuple, trunc_normal_
20
+
21
+ from groundingdino.util.misc import NestedTensor
22
+
23
+
24
+ class Mlp(nn.Module):
25
+ """Multilayer perceptron."""
26
+
27
+ def __init__(
28
+ self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.0
29
+ ):
30
+ super().__init__()
31
+ out_features = out_features or in_features
32
+ hidden_features = hidden_features or in_features
33
+ self.fc1 = nn.Linear(in_features, hidden_features)
34
+ self.act = act_layer()
35
+ self.fc2 = nn.Linear(hidden_features, out_features)
36
+ self.drop = nn.Dropout(drop)
37
+
38
+ def forward(self, x):
39
+ x = self.fc1(x)
40
+ x = self.act(x)
41
+ x = self.drop(x)
42
+ x = self.fc2(x)
43
+ x = self.drop(x)
44
+ return x
45
+
46
+
47
+ def window_partition(x, window_size):
48
+ """
49
+ Args:
50
+ x: (B, H, W, C)
51
+ window_size (int): window size
52
+ Returns:
53
+ windows: (num_windows*B, window_size, window_size, C)
54
+ """
55
+ B, H, W, C = x.shape
56
+ x = x.view(B, H // window_size, window_size, W // window_size, window_size, C)
57
+ windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C)
58
+ return windows
59
+
60
+
61
+ def window_reverse(windows, window_size, H, W):
62
+ """
63
+ Args:
64
+ windows: (num_windows*B, window_size, window_size, C)
65
+ window_size (int): Window size
66
+ H (int): Height of image
67
+ W (int): Width of image
68
+ Returns:
69
+ x: (B, H, W, C)
70
+ """
71
+ B = int(windows.shape[0] / (H * W / window_size / window_size))
72
+ x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1)
73
+ x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1)
74
+ return x
75
+
76
+
77
+ class WindowAttention(nn.Module):
78
+ """Window based multi-head self attention (W-MSA) module with relative position bias.
79
+ It supports both of shifted and non-shifted window.
80
+ Args:
81
+ dim (int): Number of input channels.
82
+ window_size (tuple[int]): The height and width of the window.
83
+ num_heads (int): Number of attention heads.
84
+ qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
85
+ qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set
86
+ attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0
87
+ proj_drop (float, optional): Dropout ratio of output. Default: 0.0
88
+ """
89
+
90
+ def __init__(
91
+ self,
92
+ dim,
93
+ window_size,
94
+ num_heads,
95
+ qkv_bias=True,
96
+ qk_scale=None,
97
+ attn_drop=0.0,
98
+ proj_drop=0.0,
99
+ ):
100
+
101
+ super().__init__()
102
+ self.dim = dim
103
+ self.window_size = window_size # Wh, Ww
104
+ self.num_heads = num_heads
105
+ head_dim = dim // num_heads
106
+ self.scale = qk_scale or head_dim**-0.5
107
+
108
+ # define a parameter table of relative position bias
109
+ self.relative_position_bias_table = nn.Parameter(
110
+ torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads)
111
+ ) # 2*Wh-1 * 2*Ww-1, nH
112
+
113
+ # get pair-wise relative position index for each token inside the window
114
+ coords_h = torch.arange(self.window_size[0])
115
+ coords_w = torch.arange(self.window_size[1])
116
+ coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww
117
+ coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
118
+ relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww
119
+ relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2
120
+ relative_coords[:, :, 0] += self.window_size[0] - 1 # shift to start from 0
121
+ relative_coords[:, :, 1] += self.window_size[1] - 1
122
+ relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1
123
+ relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww
124
+ self.register_buffer("relative_position_index", relative_position_index)
125
+
126
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
127
+ self.attn_drop = nn.Dropout(attn_drop)
128
+ self.proj = nn.Linear(dim, dim)
129
+ self.proj_drop = nn.Dropout(proj_drop)
130
+
131
+ trunc_normal_(self.relative_position_bias_table, std=0.02)
132
+ self.softmax = nn.Softmax(dim=-1)
133
+
134
+ def forward(self, x, mask=None):
135
+ """Forward function.
136
+ Args:
137
+ x: input features with shape of (num_windows*B, N, C)
138
+ mask: (0/-inf) mask with shape of (num_windows, Wh*Ww, Wh*Ww) or None
139
+ """
140
+ B_, N, C = x.shape
141
+ qkv = (
142
+ self.qkv(x)
143
+ .reshape(B_, N, 3, self.num_heads, C // self.num_heads)
144
+ .permute(2, 0, 3, 1, 4)
145
+ )
146
+ q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
147
+
148
+ q = q * self.scale
149
+ attn = q @ k.transpose(-2, -1)
150
+
151
+ relative_position_bias = self.relative_position_bias_table[
152
+ self.relative_position_index.view(-1)
153
+ ].view(
154
+ self.window_size[0] * self.window_size[1], self.window_size[0] * self.window_size[1], -1
155
+ ) # Wh*Ww,Wh*Ww,nH
156
+ relative_position_bias = relative_position_bias.permute(
157
+ 2, 0, 1
158
+ ).contiguous() # nH, Wh*Ww, Wh*Ww
159
+ attn = attn + relative_position_bias.unsqueeze(0)
160
+
161
+ if mask is not None:
162
+ nW = mask.shape[0]
163
+ attn = attn.view(B_ // nW, nW, self.num_heads, N, N) + mask.unsqueeze(1).unsqueeze(0)
164
+ attn = attn.view(-1, self.num_heads, N, N)
165
+ attn = self.softmax(attn)
166
+ else:
167
+ attn = self.softmax(attn)
168
+
169
+ attn = self.attn_drop(attn)
170
+
171
+ x = (attn @ v).transpose(1, 2).reshape(B_, N, C)
172
+ x = self.proj(x)
173
+ x = self.proj_drop(x)
174
+ return x
175
+
176
+
177
+ class SwinTransformerBlock(nn.Module):
178
+ """Swin Transformer Block.
179
+ Args:
180
+ dim (int): Number of input channels.
181
+ num_heads (int): Number of attention heads.
182
+ window_size (int): Window size.
183
+ shift_size (int): Shift size for SW-MSA.
184
+ mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
185
+ qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
186
+ qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set.
187
+ drop (float, optional): Dropout rate. Default: 0.0
188
+ attn_drop (float, optional): Attention dropout rate. Default: 0.0
189
+ drop_path (float, optional): Stochastic depth rate. Default: 0.0
190
+ act_layer (nn.Module, optional): Activation layer. Default: nn.GELU
191
+ norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
192
+ """
193
+
194
+ def __init__(
195
+ self,
196
+ dim,
197
+ num_heads,
198
+ window_size=7,
199
+ shift_size=0,
200
+ mlp_ratio=4.0,
201
+ qkv_bias=True,
202
+ qk_scale=None,
203
+ drop=0.0,
204
+ attn_drop=0.0,
205
+ drop_path=0.0,
206
+ act_layer=nn.GELU,
207
+ norm_layer=nn.LayerNorm,
208
+ ):
209
+ super().__init__()
210
+ self.dim = dim
211
+ self.num_heads = num_heads
212
+ self.window_size = window_size
213
+ self.shift_size = shift_size
214
+ self.mlp_ratio = mlp_ratio
215
+ assert 0 <= self.shift_size < self.window_size, "shift_size must in 0-window_size"
216
+
217
+ self.norm1 = norm_layer(dim)
218
+ self.attn = WindowAttention(
219
+ dim,
220
+ window_size=to_2tuple(self.window_size),
221
+ num_heads=num_heads,
222
+ qkv_bias=qkv_bias,
223
+ qk_scale=qk_scale,
224
+ attn_drop=attn_drop,
225
+ proj_drop=drop,
226
+ )
227
+
228
+ self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
229
+ self.norm2 = norm_layer(dim)
230
+ mlp_hidden_dim = int(dim * mlp_ratio)
231
+ self.mlp = Mlp(
232
+ in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop
233
+ )
234
+
235
+ self.H = None
236
+ self.W = None
237
+
238
+ def forward(self, x, mask_matrix):
239
+ """Forward function.
240
+ Args:
241
+ x: Input feature, tensor size (B, H*W, C).
242
+ H, W: Spatial resolution of the input feature.
243
+ mask_matrix: Attention mask for cyclic shift.
244
+ """
245
+ B, L, C = x.shape
246
+ H, W = self.H, self.W
247
+ assert L == H * W, "input feature has wrong size"
248
+
249
+ shortcut = x
250
+ x = self.norm1(x)
251
+ x = x.view(B, H, W, C)
252
+
253
+ # pad feature maps to multiples of window size
254
+ pad_l = pad_t = 0
255
+ pad_r = (self.window_size - W % self.window_size) % self.window_size
256
+ pad_b = (self.window_size - H % self.window_size) % self.window_size
257
+ x = F.pad(x, (0, 0, pad_l, pad_r, pad_t, pad_b))
258
+ _, Hp, Wp, _ = x.shape
259
+
260
+ # cyclic shift
261
+ if self.shift_size > 0:
262
+ shifted_x = torch.roll(x, shifts=(-self.shift_size, -self.shift_size), dims=(1, 2))
263
+ attn_mask = mask_matrix
264
+ else:
265
+ shifted_x = x
266
+ attn_mask = None
267
+
268
+ # partition windows
269
+ x_windows = window_partition(
270
+ shifted_x, self.window_size
271
+ ) # nW*B, window_size, window_size, C
272
+ x_windows = x_windows.view(
273
+ -1, self.window_size * self.window_size, C
274
+ ) # nW*B, window_size*window_size, C
275
+
276
+ # W-MSA/SW-MSA
277
+ attn_windows = self.attn(x_windows, mask=attn_mask) # nW*B, window_size*window_size, C
278
+
279
+ # merge windows
280
+ attn_windows = attn_windows.view(-1, self.window_size, self.window_size, C)
281
+ shifted_x = window_reverse(attn_windows, self.window_size, Hp, Wp) # B H' W' C
282
+
283
+ # reverse cyclic shift
284
+ if self.shift_size > 0:
285
+ x = torch.roll(shifted_x, shifts=(self.shift_size, self.shift_size), dims=(1, 2))
286
+ else:
287
+ x = shifted_x
288
+
289
+ if pad_r > 0 or pad_b > 0:
290
+ x = x[:, :H, :W, :].contiguous()
291
+
292
+ x = x.view(B, H * W, C)
293
+
294
+ # FFN
295
+ x = shortcut + self.drop_path(x)
296
+ x = x + self.drop_path(self.mlp(self.norm2(x)))
297
+
298
+ return x
299
+
300
+
301
+ class PatchMerging(nn.Module):
302
+ """Patch Merging Layer
303
+ Args:
304
+ dim (int): Number of input channels.
305
+ norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
306
+ """
307
+
308
+ def __init__(self, dim, norm_layer=nn.LayerNorm):
309
+ super().__init__()
310
+ self.dim = dim
311
+ self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False)
312
+ self.norm = norm_layer(4 * dim)
313
+
314
+ def forward(self, x, H, W):
315
+ """Forward function.
316
+ Args:
317
+ x: Input feature, tensor size (B, H*W, C).
318
+ H, W: Spatial resolution of the input feature.
319
+ """
320
+ B, L, C = x.shape
321
+ assert L == H * W, "input feature has wrong size"
322
+
323
+ x = x.view(B, H, W, C)
324
+
325
+ # padding
326
+ pad_input = (H % 2 == 1) or (W % 2 == 1)
327
+ if pad_input:
328
+ x = F.pad(x, (0, 0, 0, W % 2, 0, H % 2))
329
+
330
+ x0 = x[:, 0::2, 0::2, :] # B H/2 W/2 C
331
+ x1 = x[:, 1::2, 0::2, :] # B H/2 W/2 C
332
+ x2 = x[:, 0::2, 1::2, :] # B H/2 W/2 C
333
+ x3 = x[:, 1::2, 1::2, :] # B H/2 W/2 C
334
+ x = torch.cat([x0, x1, x2, x3], -1) # B H/2 W/2 4*C
335
+ x = x.view(B, -1, 4 * C) # B H/2*W/2 4*C
336
+
337
+ x = self.norm(x)
338
+ x = self.reduction(x)
339
+
340
+ return x
341
+
342
+
343
+ class BasicLayer(nn.Module):
344
+ """A basic Swin Transformer layer for one stage.
345
+ Args:
346
+ dim (int): Number of feature channels
347
+ depth (int): Depths of this stage.
348
+ num_heads (int): Number of attention head.
349
+ window_size (int): Local window size. Default: 7.
350
+ mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4.
351
+ qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
352
+ qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set.
353
+ drop (float, optional): Dropout rate. Default: 0.0
354
+ attn_drop (float, optional): Attention dropout rate. Default: 0.0
355
+ drop_path (float | tuple[float], optional): Stochastic depth rate. Default: 0.0
356
+ norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
357
+ downsample (nn.Module | None, optional): Downsample layer at the end of the layer. Default: None
358
+ use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False.
359
+ """
360
+
361
+ def __init__(
362
+ self,
363
+ dim,
364
+ depth,
365
+ num_heads,
366
+ window_size=7,
367
+ mlp_ratio=4.0,
368
+ qkv_bias=True,
369
+ qk_scale=None,
370
+ drop=0.0,
371
+ attn_drop=0.0,
372
+ drop_path=0.0,
373
+ norm_layer=nn.LayerNorm,
374
+ downsample=None,
375
+ use_checkpoint=False,
376
+ ):
377
+ super().__init__()
378
+ self.window_size = window_size
379
+ self.shift_size = window_size // 2
380
+ self.depth = depth
381
+ self.use_checkpoint = use_checkpoint
382
+
383
+ # build blocks
384
+ self.blocks = nn.ModuleList(
385
+ [
386
+ SwinTransformerBlock(
387
+ dim=dim,
388
+ num_heads=num_heads,
389
+ window_size=window_size,
390
+ shift_size=0 if (i % 2 == 0) else window_size // 2,
391
+ mlp_ratio=mlp_ratio,
392
+ qkv_bias=qkv_bias,
393
+ qk_scale=qk_scale,
394
+ drop=drop,
395
+ attn_drop=attn_drop,
396
+ drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path,
397
+ norm_layer=norm_layer,
398
+ )
399
+ for i in range(depth)
400
+ ]
401
+ )
402
+
403
+ # patch merging layer
404
+ if downsample is not None:
405
+ self.downsample = downsample(dim=dim, norm_layer=norm_layer)
406
+ else:
407
+ self.downsample = None
408
+
409
+ def forward(self, x, H, W):
410
+ """Forward function.
411
+ Args:
412
+ x: Input feature, tensor size (B, H*W, C).
413
+ H, W: Spatial resolution of the input feature.
414
+ """
415
+
416
+ # calculate attention mask for SW-MSA
417
+ Hp = int(np.ceil(H / self.window_size)) * self.window_size
418
+ Wp = int(np.ceil(W / self.window_size)) * self.window_size
419
+ img_mask = torch.zeros((1, Hp, Wp, 1), device=x.device) # 1 Hp Wp 1
420
+ h_slices = (
421
+ slice(0, -self.window_size),
422
+ slice(-self.window_size, -self.shift_size),
423
+ slice(-self.shift_size, None),
424
+ )
425
+ w_slices = (
426
+ slice(0, -self.window_size),
427
+ slice(-self.window_size, -self.shift_size),
428
+ slice(-self.shift_size, None),
429
+ )
430
+ cnt = 0
431
+ for h in h_slices:
432
+ for w in w_slices:
433
+ img_mask[:, h, w, :] = cnt
434
+ cnt += 1
435
+
436
+ mask_windows = window_partition(
437
+ img_mask, self.window_size
438
+ ) # nW, window_size, window_size, 1
439
+ mask_windows = mask_windows.view(-1, self.window_size * self.window_size)
440
+ attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2)
441
+ attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill(
442
+ attn_mask == 0, float(0.0)
443
+ )
444
+
445
+ for blk in self.blocks:
446
+ blk.H, blk.W = H, W
447
+ if self.use_checkpoint:
448
+ x = checkpoint.checkpoint(blk, x, attn_mask)
449
+ else:
450
+ x = blk(x, attn_mask)
451
+ if self.downsample is not None:
452
+ x_down = self.downsample(x, H, W)
453
+ Wh, Ww = (H + 1) // 2, (W + 1) // 2
454
+ return x, H, W, x_down, Wh, Ww
455
+ else:
456
+ return x, H, W, x, H, W
457
+
458
+
459
+ class PatchEmbed(nn.Module):
460
+ """Image to Patch Embedding
461
+ Args:
462
+ patch_size (int): Patch token size. Default: 4.
463
+ in_chans (int): Number of input image channels. Default: 3.
464
+ embed_dim (int): Number of linear projection output channels. Default: 96.
465
+ norm_layer (nn.Module, optional): Normalization layer. Default: None
466
+ """
467
+
468
+ def __init__(self, patch_size=4, in_chans=3, embed_dim=96, norm_layer=None):
469
+ super().__init__()
470
+ patch_size = to_2tuple(patch_size)
471
+ self.patch_size = patch_size
472
+
473
+ self.in_chans = in_chans
474
+ self.embed_dim = embed_dim
475
+
476
+ self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)
477
+ if norm_layer is not None:
478
+ self.norm = norm_layer(embed_dim)
479
+ else:
480
+ self.norm = None
481
+
482
+ def forward(self, x):
483
+ """Forward function."""
484
+ # padding
485
+ _, _, H, W = x.size()
486
+ if W % self.patch_size[1] != 0:
487
+ x = F.pad(x, (0, self.patch_size[1] - W % self.patch_size[1]))
488
+ if H % self.patch_size[0] != 0:
489
+ x = F.pad(x, (0, 0, 0, self.patch_size[0] - H % self.patch_size[0]))
490
+
491
+ x = self.proj(x) # B C Wh Ww
492
+ if self.norm is not None:
493
+ Wh, Ww = x.size(2), x.size(3)
494
+ x = x.flatten(2).transpose(1, 2)
495
+ x = self.norm(x)
496
+ x = x.transpose(1, 2).view(-1, self.embed_dim, Wh, Ww)
497
+
498
+ return x
499
+
500
+
501
+ class SwinTransformer(nn.Module):
502
+ """Swin Transformer backbone.
503
+ A PyTorch impl of : `Swin Transformer: Hierarchical Vision Transformer using Shifted Windows` -
504
+ https://arxiv.org/pdf/2103.14030
505
+ Args:
506
+ pretrain_img_size (int): Input image size for training the pretrained model,
507
+ used in absolute postion embedding. Default 224.
508
+ patch_size (int | tuple(int)): Patch size. Default: 4.
509
+ in_chans (int): Number of input image channels. Default: 3.
510
+ embed_dim (int): Number of linear projection output channels. Default: 96.
511
+ depths (tuple[int]): Depths of each Swin Transformer stage.
512
+ num_heads (tuple[int]): Number of attention head of each stage.
513
+ window_size (int): Window size. Default: 7.
514
+ mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4.
515
+ qkv_bias (bool): If True, add a learnable bias to query, key, value. Default: True
516
+ qk_scale (float): Override default qk scale of head_dim ** -0.5 if set.
517
+ drop_rate (float): Dropout rate.
518
+ attn_drop_rate (float): Attention dropout rate. Default: 0.
519
+ drop_path_rate (float): Stochastic depth rate. Default: 0.2.
520
+ norm_layer (nn.Module): Normalization layer. Default: nn.LayerNorm.
521
+ ape (bool): If True, add absolute position embedding to the patch embedding. Default: False.
522
+ patch_norm (bool): If True, add normalization after patch embedding. Default: True.
523
+ out_indices (Sequence[int]): Output from which stages.
524
+ frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
525
+ -1 means not freezing any parameters.
526
+ use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False.
527
+ dilation (bool): if True, the output size if 16x downsample, ow 32x downsample.
528
+ """
529
+
530
+ def __init__(
531
+ self,
532
+ pretrain_img_size=224,
533
+ patch_size=4,
534
+ in_chans=3,
535
+ embed_dim=96,
536
+ depths=[2, 2, 6, 2],
537
+ num_heads=[3, 6, 12, 24],
538
+ window_size=7,
539
+ mlp_ratio=4.0,
540
+ qkv_bias=True,
541
+ qk_scale=None,
542
+ drop_rate=0.0,
543
+ attn_drop_rate=0.0,
544
+ drop_path_rate=0.2,
545
+ norm_layer=nn.LayerNorm,
546
+ ape=False,
547
+ patch_norm=True,
548
+ out_indices=(0, 1, 2, 3),
549
+ frozen_stages=-1,
550
+ dilation=False,
551
+ use_checkpoint=False,
552
+ ):
553
+ super().__init__()
554
+
555
+ self.pretrain_img_size = pretrain_img_size
556
+ self.num_layers = len(depths)
557
+ self.embed_dim = embed_dim
558
+ self.ape = ape
559
+ self.patch_norm = patch_norm
560
+ self.out_indices = out_indices
561
+ self.frozen_stages = frozen_stages
562
+ self.dilation = dilation
563
+
564
+ # if use_checkpoint:
565
+ # print("use_checkpoint!!!!!!!!!!!!!!!!!!!!!!!!")
566
+
567
+ # split image into non-overlapping patches
568
+ self.patch_embed = PatchEmbed(
569
+ patch_size=patch_size,
570
+ in_chans=in_chans,
571
+ embed_dim=embed_dim,
572
+ norm_layer=norm_layer if self.patch_norm else None,
573
+ )
574
+
575
+ # absolute position embedding
576
+ if self.ape:
577
+ pretrain_img_size = to_2tuple(pretrain_img_size)
578
+ patch_size = to_2tuple(patch_size)
579
+ patches_resolution = [
580
+ pretrain_img_size[0] // patch_size[0],
581
+ pretrain_img_size[1] // patch_size[1],
582
+ ]
583
+
584
+ self.absolute_pos_embed = nn.Parameter(
585
+ torch.zeros(1, embed_dim, patches_resolution[0], patches_resolution[1])
586
+ )
587
+ trunc_normal_(self.absolute_pos_embed, std=0.02)
588
+
589
+ self.pos_drop = nn.Dropout(p=drop_rate)
590
+
591
+ # stochastic depth
592
+ dpr = [
593
+ x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))
594
+ ] # stochastic depth decay rule
595
+
596
+ # build layers
597
+ self.layers = nn.ModuleList()
598
+ # prepare downsample list
599
+ downsamplelist = [PatchMerging for i in range(self.num_layers)]
600
+ downsamplelist[-1] = None
601
+ num_features = [int(embed_dim * 2**i) for i in range(self.num_layers)]
602
+ if self.dilation:
603
+ downsamplelist[-2] = None
604
+ num_features[-1] = int(embed_dim * 2 ** (self.num_layers - 1)) // 2
605
+ for i_layer in range(self.num_layers):
606
+ layer = BasicLayer(
607
+ # dim=int(embed_dim * 2 ** i_layer),
608
+ dim=num_features[i_layer],
609
+ depth=depths[i_layer],
610
+ num_heads=num_heads[i_layer],
611
+ window_size=window_size,
612
+ mlp_ratio=mlp_ratio,
613
+ qkv_bias=qkv_bias,
614
+ qk_scale=qk_scale,
615
+ drop=drop_rate,
616
+ attn_drop=attn_drop_rate,
617
+ drop_path=dpr[sum(depths[:i_layer]) : sum(depths[: i_layer + 1])],
618
+ norm_layer=norm_layer,
619
+ # downsample=PatchMerging if (i_layer < self.num_layers - 1) else None,
620
+ downsample=downsamplelist[i_layer],
621
+ use_checkpoint=use_checkpoint,
622
+ )
623
+ self.layers.append(layer)
624
+
625
+ # num_features = [int(embed_dim * 2 ** i) for i in range(self.num_layers)]
626
+ self.num_features = num_features
627
+
628
+ # add a norm layer for each output
629
+ for i_layer in out_indices:
630
+ layer = norm_layer(num_features[i_layer])
631
+ layer_name = f"norm{i_layer}"
632
+ self.add_module(layer_name, layer)
633
+
634
+ self._freeze_stages()
635
+
636
+ def _freeze_stages(self):
637
+ if self.frozen_stages >= 0:
638
+ self.patch_embed.eval()
639
+ for param in self.patch_embed.parameters():
640
+ param.requires_grad = False
641
+
642
+ if self.frozen_stages >= 1 and self.ape:
643
+ self.absolute_pos_embed.requires_grad = False
644
+
645
+ if self.frozen_stages >= 2:
646
+ self.pos_drop.eval()
647
+ for i in range(0, self.frozen_stages - 1):
648
+ m = self.layers[i]
649
+ m.eval()
650
+ for param in m.parameters():
651
+ param.requires_grad = False
652
+
653
+ # def init_weights(self, pretrained=None):
654
+ # """Initialize the weights in backbone.
655
+ # Args:
656
+ # pretrained (str, optional): Path to pre-trained weights.
657
+ # Defaults to None.
658
+ # """
659
+
660
+ # def _init_weights(m):
661
+ # if isinstance(m, nn.Linear):
662
+ # trunc_normal_(m.weight, std=.02)
663
+ # if isinstance(m, nn.Linear) and m.bias is not None:
664
+ # nn.init.constant_(m.bias, 0)
665
+ # elif isinstance(m, nn.LayerNorm):
666
+ # nn.init.constant_(m.bias, 0)
667
+ # nn.init.constant_(m.weight, 1.0)
668
+
669
+ # if isinstance(pretrained, str):
670
+ # self.apply(_init_weights)
671
+ # logger = get_root_logger()
672
+ # load_checkpoint(self, pretrained, strict=False, logger=logger)
673
+ # elif pretrained is None:
674
+ # self.apply(_init_weights)
675
+ # else:
676
+ # raise TypeError('pretrained must be a str or None')
677
+
678
+ def forward_raw(self, x):
679
+ """Forward function."""
680
+ x = self.patch_embed(x)
681
+
682
+ Wh, Ww = x.size(2), x.size(3)
683
+ if self.ape:
684
+ # interpolate the position embedding to the corresponding size
685
+ absolute_pos_embed = F.interpolate(
686
+ self.absolute_pos_embed, size=(Wh, Ww), mode="bicubic"
687
+ )
688
+ x = (x + absolute_pos_embed).flatten(2).transpose(1, 2) # B Wh*Ww C
689
+ else:
690
+ x = x.flatten(2).transpose(1, 2)
691
+ x = self.pos_drop(x)
692
+
693
+ outs = []
694
+ for i in range(self.num_layers):
695
+ layer = self.layers[i]
696
+ x_out, H, W, x, Wh, Ww = layer(x, Wh, Ww)
697
+ # import ipdb; ipdb.set_trace()
698
+
699
+ if i in self.out_indices:
700
+ norm_layer = getattr(self, f"norm{i}")
701
+ x_out = norm_layer(x_out)
702
+
703
+ out = x_out.view(-1, H, W, self.num_features[i]).permute(0, 3, 1, 2).contiguous()
704
+ outs.append(out)
705
+ # in:
706
+ # torch.Size([2, 3, 1024, 1024])
707
+ # outs:
708
+ # [torch.Size([2, 192, 256, 256]), torch.Size([2, 384, 128, 128]), \
709
+ # torch.Size([2, 768, 64, 64]), torch.Size([2, 1536, 32, 32])]
710
+ return tuple(outs)
711
+
712
+ def forward(self, tensor_list: NestedTensor):
713
+
714
+ x = tensor_list.tensors
715
+
716
+ """Forward function."""
717
+ x = self.patch_embed(x)
718
+
719
+
720
+ Wh, Ww = x.size(2), x.size(3)
721
+ if self.ape:
722
+ # interpolate the position embedding to the corresponding size
723
+ absolute_pos_embed = F.interpolate(
724
+ self.absolute_pos_embed, size=(Wh, Ww), mode="bicubic"
725
+ )
726
+ x = (x + absolute_pos_embed).flatten(2).transpose(1, 2) # B Wh*Ww C
727
+ else:
728
+ x = x.flatten(2).transpose(1, 2)
729
+ x = self.pos_drop(x)
730
+
731
+ outs = []
732
+ for i in range(self.num_layers):
733
+ layer = self.layers[i]
734
+ x_out, H, W, x, Wh, Ww = layer(x, Wh, Ww)
735
+
736
+ if i in self.out_indices:
737
+ norm_layer = getattr(self, f"norm{i}")
738
+ x_out = norm_layer(x_out)
739
+
740
+ out = x_out.view(-1, H, W, self.num_features[i]).permute(0, 3, 1, 2).contiguous()
741
+ outs.append(out)
742
+ # in:
743
+ # torch.Size([2, 3, 1024, 1024])
744
+ # out:
745
+ # [torch.Size([2, 192, 256, 256]), torch.Size([2, 384, 128, 128]), \
746
+ # torch.Size([2, 768, 64, 64]), torch.Size([2, 1536, 32, 32])]
747
+
748
+ # collect for nesttensors
749
+ outs_dict = {}
750
+ for idx, out_i in enumerate(outs):
751
+ m = tensor_list.mask
752
+ assert m is not None
753
+ mask = F.interpolate(m[None].float(), size=out_i.shape[-2:]).to(torch.bool)[0]
754
+ outs_dict[idx] = NestedTensor(out_i, mask)
755
+
756
+ return outs_dict
757
+
758
+ def train(self, mode=True):
759
+ """Convert the model into training mode while keep layers freezed."""
760
+ super(SwinTransformer, self).train(mode)
761
+ self._freeze_stages()
762
+
763
+
764
+ def build_swin_transformer(modelname, pretrain_img_size, **kw):
765
+ assert modelname in [
766
+ "swin_T_224_1k",
767
+ "swin_B_224_22k",
768
+ "swin_B_384_22k",
769
+ "swin_L_224_22k",
770
+ "swin_L_384_22k",
771
+ ]
772
+
773
+ model_para_dict = {
774
+ "swin_T_224_1k": dict(
775
+ embed_dim=96, depths=[2, 2, 6, 2], num_heads=[3, 6, 12, 24], window_size=7
776
+ ),
777
+ "swin_B_224_22k": dict(
778
+ embed_dim=128, depths=[2, 2, 18, 2], num_heads=[4, 8, 16, 32], window_size=7
779
+ ),
780
+ "swin_B_384_22k": dict(
781
+ embed_dim=128, depths=[2, 2, 18, 2], num_heads=[4, 8, 16, 32], window_size=12
782
+ ),
783
+ "swin_L_224_22k": dict(
784
+ embed_dim=192, depths=[2, 2, 18, 2], num_heads=[6, 12, 24, 48], window_size=7
785
+ ),
786
+ "swin_L_384_22k": dict(
787
+ embed_dim=192, depths=[2, 2, 18, 2], num_heads=[6, 12, 24, 48], window_size=12
788
+ ),
789
+ }
790
+ kw_cgf = model_para_dict[modelname]
791
+ kw_cgf.update(kw)
792
+ model = SwinTransformer(pretrain_img_size=pretrain_img_size, **kw_cgf)
793
+ return model
794
+
795
+
796
+ if __name__ == "__main__":
797
+ model = build_swin_transformer("swin_L_384_22k", 384, dilation=True)
798
+ x = torch.rand(2, 3, 1024, 1024)
799
+ y = model.forward_raw(x)
800
+ import ipdb
801
+
802
+ ipdb.set_trace()
803
+ x = torch.rand(2, 3, 384, 384)
804
+ y = model.forward_raw(x)
models/GroundingDINO/bertwarper.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+
8
+ import torch
9
+ import torch.nn.functional as F
10
+ import torch.utils.checkpoint as checkpoint
11
+ from torch import Tensor, nn
12
+ from torchvision.ops.boxes import nms
13
+ from transformers import BertConfig, BertModel, BertPreTrainedModel
14
+ from transformers.modeling_outputs import BaseModelOutputWithPoolingAndCrossAttentions
15
+
16
+
17
+ class BertModelWarper(nn.Module):
18
+ def __init__(self, bert_model):
19
+ super().__init__()
20
+ # self.bert = bert_modelc
21
+
22
+ self.config = bert_model.config
23
+ self.embeddings = bert_model.embeddings
24
+ self.encoder = bert_model.encoder
25
+ self.pooler = bert_model.pooler
26
+
27
+ self.get_extended_attention_mask = bert_model.get_extended_attention_mask
28
+ self.invert_attention_mask = bert_model.invert_attention_mask
29
+ self.get_head_mask = bert_model.get_head_mask
30
+
31
+ def forward(
32
+ self,
33
+ input_ids=None,
34
+ attention_mask=None,
35
+ token_type_ids=None,
36
+ position_ids=None,
37
+ head_mask=None,
38
+ inputs_embeds=None,
39
+ encoder_hidden_states=None,
40
+ encoder_attention_mask=None,
41
+ past_key_values=None,
42
+ use_cache=None,
43
+ output_attentions=None,
44
+ output_hidden_states=None,
45
+ return_dict=None,
46
+ ):
47
+ r"""
48
+ encoder_hidden_states (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length, hidden_size)`, `optional`):
49
+ Sequence of hidden-states at the output of the last layer of the encoder. Used in the cross-attention if
50
+ the model is configured as a decoder.
51
+ encoder_attention_mask (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`):
52
+ Mask to avoid performing attention on the padding token indices of the encoder input. This mask is used in
53
+ the cross-attention if the model is configured as a decoder. Mask values selected in ``[0, 1]``:
54
+
55
+ - 1 for tokens that are **not masked**,
56
+ - 0 for tokens that are **masked**.
57
+ past_key_values (:obj:`tuple(tuple(torch.FloatTensor))` of length :obj:`config.n_layers` with each tuple having 4 tensors of shape :obj:`(batch_size, num_heads, sequence_length - 1, embed_size_per_head)`):
58
+ Contains precomputed key and value hidden states of the attention blocks. Can be used to speed up decoding.
59
+
60
+ If :obj:`past_key_values` are used, the user can optionally input only the last :obj:`decoder_input_ids`
61
+ (those that don't have their past key value states given to this model) of shape :obj:`(batch_size, 1)`
62
+ instead of all :obj:`decoder_input_ids` of shape :obj:`(batch_size, sequence_length)`.
63
+ use_cache (:obj:`bool`, `optional`):
64
+ If set to :obj:`True`, :obj:`past_key_values` key value states are returned and can be used to speed up
65
+ decoding (see :obj:`past_key_values`).
66
+ """
67
+ output_attentions = (
68
+ output_attentions if output_attentions is not None else self.config.output_attentions
69
+ )
70
+ output_hidden_states = (
71
+ output_hidden_states
72
+ if output_hidden_states is not None
73
+ else self.config.output_hidden_states
74
+ )
75
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
76
+
77
+ if self.config.is_decoder:
78
+ use_cache = use_cache if use_cache is not None else self.config.use_cache
79
+ else:
80
+ use_cache = False
81
+
82
+ if input_ids is not None and inputs_embeds is not None:
83
+ raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
84
+ elif input_ids is not None:
85
+ input_shape = input_ids.size()
86
+ batch_size, seq_length = input_shape
87
+ elif inputs_embeds is not None:
88
+ input_shape = inputs_embeds.size()[:-1]
89
+ batch_size, seq_length = input_shape
90
+ else:
91
+ raise ValueError("You have to specify either input_ids or inputs_embeds")
92
+
93
+ device = input_ids.device if input_ids is not None else inputs_embeds.device
94
+
95
+ # past_key_values_length
96
+ past_key_values_length = (
97
+ past_key_values[0][0].shape[2] if past_key_values is not None else 0
98
+ )
99
+
100
+ if attention_mask is None:
101
+ attention_mask = torch.ones(
102
+ ((batch_size, seq_length + past_key_values_length)), device=device
103
+ )
104
+ if token_type_ids is None:
105
+ token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=device)
106
+
107
+ # We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length]
108
+ # ourselves in which case we just need to make it broadcastable to all heads.
109
+ extended_attention_mask: torch.Tensor = self.get_extended_attention_mask(
110
+ attention_mask, input_shape, device
111
+ )
112
+
113
+ # If a 2D or 3D attention mask is provided for the cross-attention
114
+ # we need to make broadcastable to [batch_size, num_heads, seq_length, seq_length]
115
+ if self.config.is_decoder and encoder_hidden_states is not None:
116
+ encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states.size()
117
+ encoder_hidden_shape = (encoder_batch_size, encoder_sequence_length)
118
+ if encoder_attention_mask is None:
119
+ encoder_attention_mask = torch.ones(encoder_hidden_shape, device=device)
120
+ encoder_extended_attention_mask = self.invert_attention_mask(encoder_attention_mask)
121
+ else:
122
+ encoder_extended_attention_mask = None
123
+ # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO':
124
+ # import ipdb; ipdb.set_trace()
125
+
126
+ # Prepare head mask if needed
127
+ # 1.0 in head_mask indicate we keep the head
128
+ # attention_probs has shape bsz x n_heads x N x N
129
+ # input head_mask has shape [num_heads] or [num_hidden_layers x num_heads]
130
+ # and head_mask is converted to shape [num_hidden_layers x batch x num_heads x seq_length x seq_length]
131
+ head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers)
132
+
133
+ embedding_output = self.embeddings(
134
+ input_ids=input_ids,
135
+ position_ids=position_ids,
136
+ token_type_ids=token_type_ids,
137
+ inputs_embeds=inputs_embeds,
138
+ past_key_values_length=past_key_values_length,
139
+ )
140
+
141
+ encoder_outputs = self.encoder(
142
+ embedding_output,
143
+ attention_mask=extended_attention_mask,
144
+ head_mask=head_mask,
145
+ encoder_hidden_states=encoder_hidden_states,
146
+ encoder_attention_mask=encoder_extended_attention_mask,
147
+ past_key_values=past_key_values,
148
+ use_cache=use_cache,
149
+ output_attentions=output_attentions,
150
+ output_hidden_states=output_hidden_states,
151
+ return_dict=return_dict,
152
+ )
153
+ sequence_output = encoder_outputs[0]
154
+ pooled_output = self.pooler(sequence_output) if self.pooler is not None else None
155
+
156
+ if not return_dict:
157
+ return (sequence_output, pooled_output) + encoder_outputs[1:]
158
+
159
+ return BaseModelOutputWithPoolingAndCrossAttentions(
160
+ last_hidden_state=sequence_output,
161
+ pooler_output=pooled_output,
162
+ past_key_values=encoder_outputs.past_key_values,
163
+ hidden_states=encoder_outputs.hidden_states,
164
+ attentions=encoder_outputs.attentions,
165
+ cross_attentions=encoder_outputs.cross_attentions,
166
+ )
167
+
168
+
169
+ class TextEncoderShell(nn.Module):
170
+ def __init__(self, text_encoder):
171
+ super().__init__()
172
+ self.text_encoder = text_encoder
173
+ self.config = self.text_encoder.config
174
+
175
+ def forward(self, **kw):
176
+ # feed into text encoder
177
+ return self.text_encoder(**kw)
178
+
179
+
180
+ def generate_masks_with_special_tokens(tokenized, special_tokens_list, tokenizer):
181
+ """Generate attention mask between each pair of special tokens
182
+ Args:
183
+ input_ids (torch.Tensor): input ids. Shape: [bs, num_token]
184
+ special_tokens_mask (list): special tokens mask.
185
+ Returns:
186
+ torch.Tensor: attention mask between each special tokens.
187
+ """
188
+ input_ids = tokenized["input_ids"]
189
+ bs, num_token = input_ids.shape
190
+ # special_tokens_mask: bs, num_token. 1 for special tokens. 0 for normal tokens
191
+ special_tokens_mask = torch.zeros((bs, num_token), device=input_ids.device).bool()
192
+ for special_token in special_tokens_list:
193
+ special_tokens_mask |= input_ids == special_token
194
+
195
+ # idxs: each row is a list of indices of special tokens
196
+ idxs = torch.nonzero(special_tokens_mask)
197
+
198
+ # generate attention mask and positional ids
199
+ attention_mask = (
200
+ torch.eye(num_token, device=input_ids.device).bool().unsqueeze(0).repeat(bs, 1, 1)
201
+ )
202
+ position_ids = torch.zeros((bs, num_token), device=input_ids.device)
203
+ previous_col = 0
204
+ for i in range(idxs.shape[0]):
205
+ row, col = idxs[i]
206
+ if (col == 0) or (col == num_token - 1):
207
+ attention_mask[row, col, col] = True
208
+ position_ids[row, col] = 0
209
+ else:
210
+ attention_mask[row, previous_col + 1 : col + 1, previous_col + 1 : col + 1] = True
211
+ position_ids[row, previous_col + 1 : col + 1] = torch.arange(
212
+ 0, col - previous_col, device=input_ids.device
213
+ )
214
+
215
+ previous_col = col
216
+
217
+ # # padding mask
218
+ # padding_mask = tokenized['attention_mask']
219
+ # attention_mask = attention_mask & padding_mask.unsqueeze(1).bool() & padding_mask.unsqueeze(2).bool()
220
+
221
+ return attention_mask, position_ids.to(torch.long)
222
+
223
+
224
+ def generate_masks_with_special_tokens_and_transfer_map(tokenized, special_tokens_list, tokenizer):
225
+ """Generate attention mask between each pair of special tokens
226
+ Args:
227
+ input_ids (torch.Tensor): input ids. Shape: [bs, num_token]
228
+ special_tokens_mask (list): special tokens mask.
229
+ Returns:
230
+ torch.Tensor: attention mask between each special tokens.
231
+ """
232
+ input_ids = tokenized["input_ids"]
233
+ bs, num_token = input_ids.shape
234
+ # special_tokens_mask: bs, num_token. 1 for special tokens. 0 for normal tokens
235
+ special_tokens_mask = torch.zeros((bs, num_token), device=input_ids.device).bool()
236
+ for special_token in special_tokens_list:
237
+ special_tokens_mask |= input_ids == special_token
238
+
239
+ # idxs: each row is a list of indices of special tokens
240
+ idxs = torch.nonzero(special_tokens_mask)
241
+
242
+ # generate attention mask and positional ids
243
+ attention_mask = (
244
+ torch.eye(num_token, device=input_ids.device).bool().unsqueeze(0).repeat(bs, 1, 1)
245
+ )
246
+ position_ids = torch.zeros((bs, num_token), device=input_ids.device)
247
+ cate_to_token_mask_list = [[] for _ in range(bs)]
248
+ previous_col = 0
249
+ for i in range(idxs.shape[0]):
250
+ row, col = idxs[i]
251
+ if (col == 0) or (col == num_token - 1):
252
+ attention_mask[row, col, col] = True
253
+ position_ids[row, col] = 0
254
+ else:
255
+ attention_mask[row, previous_col + 1 : col + 1, previous_col + 1 : col + 1] = True
256
+ position_ids[row, previous_col + 1 : col + 1] = torch.arange(
257
+ 0, col - previous_col, device=input_ids.device
258
+ )
259
+ c2t_maski = torch.zeros((num_token), device=input_ids.device).bool()
260
+ c2t_maski[previous_col + 1 : col] = True
261
+ cate_to_token_mask_list[row].append(c2t_maski)
262
+ previous_col = col
263
+
264
+ cate_to_token_mask_list = [
265
+ torch.stack(cate_to_token_mask_listi, dim=0)
266
+ for cate_to_token_mask_listi in cate_to_token_mask_list
267
+ ]
268
+
269
+ # # padding mask
270
+ # padding_mask = tokenized['attention_mask']
271
+ # attention_mask = attention_mask & padding_mask.unsqueeze(1).bool() & padding_mask.unsqueeze(2).bool()
272
+
273
+ return attention_mask, position_ids.to(torch.long), cate_to_token_mask_list
models/GroundingDINO/fuse_modules.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+ from timm.models.layers import DropPath
12
+
13
+
14
+ class FeatureResizer(nn.Module):
15
+ """
16
+ This class takes as input a set of embeddings of dimension C1 and outputs a set of
17
+ embedding of dimension C2, after a linear transformation, dropout and normalization (LN).
18
+ """
19
+
20
+ def __init__(self, input_feat_size, output_feat_size, dropout, do_ln=True):
21
+ super().__init__()
22
+ self.do_ln = do_ln
23
+ # Object feature encoding
24
+ self.fc = nn.Linear(input_feat_size, output_feat_size, bias=True)
25
+ self.layer_norm = nn.LayerNorm(output_feat_size, eps=1e-12)
26
+ self.dropout = nn.Dropout(dropout)
27
+
28
+ def forward(self, encoder_features):
29
+ x = self.fc(encoder_features)
30
+ if self.do_ln:
31
+ x = self.layer_norm(x)
32
+ output = self.dropout(x)
33
+ return output
34
+
35
+
36
+ def l1norm(X, dim, eps=1e-8):
37
+ """L1-normalize columns of X"""
38
+ norm = torch.abs(X).sum(dim=dim, keepdim=True) + eps
39
+ X = torch.div(X, norm)
40
+ return X
41
+
42
+
43
+ def l2norm(X, dim, eps=1e-8):
44
+ """L2-normalize columns of X"""
45
+ norm = torch.pow(X, 2).sum(dim=dim, keepdim=True).sqrt() + eps
46
+ X = torch.div(X, norm)
47
+ return X
48
+
49
+
50
+ def func_attention(query, context, smooth=1, raw_feature_norm="softmax", eps=1e-8):
51
+ """
52
+ query: (n_context, queryL, d)
53
+ context: (n_context, sourceL, d)
54
+ """
55
+ batch_size_q, queryL = query.size(0), query.size(1)
56
+ batch_size, sourceL = context.size(0), context.size(1)
57
+
58
+ # Get attention
59
+ # --> (batch, d, queryL)
60
+ queryT = torch.transpose(query, 1, 2)
61
+
62
+ # (batch, sourceL, d)(batch, d, queryL)
63
+ # --> (batch, sourceL, queryL)
64
+ attn = torch.bmm(context, queryT)
65
+ if raw_feature_norm == "softmax":
66
+ # --> (batch*sourceL, queryL)
67
+ attn = attn.view(batch_size * sourceL, queryL)
68
+ attn = nn.Softmax()(attn)
69
+ # --> (batch, sourceL, queryL)
70
+ attn = attn.view(batch_size, sourceL, queryL)
71
+ elif raw_feature_norm == "l2norm":
72
+ attn = l2norm(attn, 2)
73
+ elif raw_feature_norm == "clipped_l2norm":
74
+ attn = nn.LeakyReLU(0.1)(attn)
75
+ attn = l2norm(attn, 2)
76
+ else:
77
+ raise ValueError("unknown first norm type:", raw_feature_norm)
78
+ # --> (batch, queryL, sourceL)
79
+ attn = torch.transpose(attn, 1, 2).contiguous()
80
+ # --> (batch*queryL, sourceL)
81
+ attn = attn.view(batch_size * queryL, sourceL)
82
+ attn = nn.Softmax()(attn * smooth)
83
+ # --> (batch, queryL, sourceL)
84
+ attn = attn.view(batch_size, queryL, sourceL)
85
+ # --> (batch, sourceL, queryL)
86
+ attnT = torch.transpose(attn, 1, 2).contiguous()
87
+
88
+ # --> (batch, d, sourceL)
89
+ contextT = torch.transpose(context, 1, 2)
90
+ # (batch x d x sourceL)(batch x sourceL x queryL)
91
+ # --> (batch, d, queryL)
92
+ weightedContext = torch.bmm(contextT, attnT)
93
+ # --> (batch, queryL, d)
94
+ weightedContext = torch.transpose(weightedContext, 1, 2)
95
+
96
+ return weightedContext, attnT
97
+
98
+
99
+ class BiMultiHeadAttention(nn.Module):
100
+ def __init__(self, v_dim, l_dim, embed_dim, num_heads, dropout=0.1, cfg=None):
101
+ super(BiMultiHeadAttention, self).__init__()
102
+
103
+ self.embed_dim = embed_dim
104
+ self.num_heads = num_heads
105
+ self.head_dim = embed_dim // num_heads
106
+ self.v_dim = v_dim
107
+ self.l_dim = l_dim
108
+
109
+ assert (
110
+ self.head_dim * self.num_heads == self.embed_dim
111
+ ), f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`: {self.num_heads})."
112
+ self.scale = self.head_dim ** (-0.5)
113
+ self.dropout = dropout
114
+
115
+ self.v_proj = nn.Linear(self.v_dim, self.embed_dim)
116
+ self.l_proj = nn.Linear(self.l_dim, self.embed_dim)
117
+ self.values_v_proj = nn.Linear(self.v_dim, self.embed_dim)
118
+ self.values_l_proj = nn.Linear(self.l_dim, self.embed_dim)
119
+
120
+ self.out_v_proj = nn.Linear(self.embed_dim, self.v_dim)
121
+ self.out_l_proj = nn.Linear(self.embed_dim, self.l_dim)
122
+
123
+ self.stable_softmax_2d = True
124
+ self.clamp_min_for_underflow = True
125
+ self.clamp_max_for_overflow = True
126
+
127
+ self._reset_parameters()
128
+
129
+ def _shape(self, tensor: torch.Tensor, seq_len: int, bsz: int):
130
+ return tensor.view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2).contiguous()
131
+
132
+ def _reset_parameters(self):
133
+ nn.init.xavier_uniform_(self.v_proj.weight)
134
+ self.v_proj.bias.data.fill_(0)
135
+ nn.init.xavier_uniform_(self.l_proj.weight)
136
+ self.l_proj.bias.data.fill_(0)
137
+ nn.init.xavier_uniform_(self.values_v_proj.weight)
138
+ self.values_v_proj.bias.data.fill_(0)
139
+ nn.init.xavier_uniform_(self.values_l_proj.weight)
140
+ self.values_l_proj.bias.data.fill_(0)
141
+ nn.init.xavier_uniform_(self.out_v_proj.weight)
142
+ self.out_v_proj.bias.data.fill_(0)
143
+ nn.init.xavier_uniform_(self.out_l_proj.weight)
144
+ self.out_l_proj.bias.data.fill_(0)
145
+
146
+ def forward(self, v, l, attention_mask_v=None, attention_mask_l=None):
147
+ """_summary_
148
+
149
+ Args:
150
+ v (_type_): bs, n_img, dim
151
+ l (_type_): bs, n_text, dim
152
+ attention_mask_v (_type_, optional): _description_. bs, n_img
153
+ attention_mask_l (_type_, optional): _description_. bs, n_text
154
+
155
+ Returns:
156
+ _type_: _description_
157
+ """
158
+ # if os.environ.get('IPDB_SHILONG_DEBUG', None) == 'INFO':
159
+ # import ipdb; ipdb.set_trace()
160
+ bsz, tgt_len, _ = v.size()
161
+
162
+ query_states = self.v_proj(v) * self.scale
163
+ key_states = self._shape(self.l_proj(l), -1, bsz)
164
+ value_v_states = self._shape(self.values_v_proj(v), -1, bsz)
165
+ value_l_states = self._shape(self.values_l_proj(l), -1, bsz)
166
+
167
+ proj_shape = (bsz * self.num_heads, -1, self.head_dim)
168
+ query_states = self._shape(query_states, tgt_len, bsz).view(*proj_shape)
169
+ key_states = key_states.view(*proj_shape)
170
+ value_v_states = value_v_states.view(*proj_shape)
171
+ value_l_states = value_l_states.view(*proj_shape)
172
+
173
+ src_len = key_states.size(1)
174
+ attn_weights = torch.bmm(query_states, key_states.transpose(1, 2)) # bs*nhead, nimg, ntxt
175
+
176
+ if attn_weights.size() != (bsz * self.num_heads, tgt_len, src_len):
177
+ raise ValueError(
178
+ f"Attention weights should be of size {(bsz * self.num_heads, tgt_len, src_len)}, but is {attn_weights.size()}"
179
+ )
180
+
181
+ if self.stable_softmax_2d:
182
+ attn_weights = attn_weights - attn_weights.max()
183
+
184
+ if self.clamp_min_for_underflow:
185
+ attn_weights = torch.clamp(
186
+ attn_weights, min=-50000
187
+ ) # Do not increase -50000, data type half has quite limited range
188
+ if self.clamp_max_for_overflow:
189
+ attn_weights = torch.clamp(
190
+ attn_weights, max=50000
191
+ ) # Do not increase 50000, data type half has quite limited range
192
+
193
+ attn_weights_T = attn_weights.transpose(1, 2)
194
+ attn_weights_l = attn_weights_T - torch.max(attn_weights_T, dim=-1, keepdim=True)[0]
195
+ if self.clamp_min_for_underflow:
196
+ attn_weights_l = torch.clamp(
197
+ attn_weights_l, min=-50000
198
+ ) # Do not increase -50000, data type half has quite limited range
199
+ if self.clamp_max_for_overflow:
200
+ attn_weights_l = torch.clamp(
201
+ attn_weights_l, max=50000
202
+ ) # Do not increase 50000, data type half has quite limited range
203
+
204
+ # mask vison for language
205
+ if attention_mask_v is not None:
206
+ attention_mask_v = (
207
+ attention_mask_v[:, None, None, :].repeat(1, self.num_heads, 1, 1).flatten(0, 1)
208
+ )
209
+ attn_weights_l.masked_fill_(attention_mask_v, float("-inf"))
210
+
211
+ attn_weights_l = attn_weights_l.softmax(dim=-1)
212
+
213
+ # mask language for vision
214
+ if attention_mask_l is not None:
215
+ attention_mask_l = (
216
+ attention_mask_l[:, None, None, :].repeat(1, self.num_heads, 1, 1).flatten(0, 1)
217
+ )
218
+ attn_weights.masked_fill_(attention_mask_l, float("-inf"))
219
+ attn_weights_v = attn_weights.softmax(dim=-1)
220
+
221
+ attn_probs_v = F.dropout(attn_weights_v, p=self.dropout, training=self.training)
222
+ attn_probs_l = F.dropout(attn_weights_l, p=self.dropout, training=self.training)
223
+
224
+ attn_output_v = torch.bmm(attn_probs_v, value_l_states)
225
+ attn_output_l = torch.bmm(attn_probs_l, value_v_states)
226
+
227
+ if attn_output_v.size() != (bsz * self.num_heads, tgt_len, self.head_dim):
228
+ raise ValueError(
229
+ f"`attn_output_v` should be of size {(bsz, self.num_heads, tgt_len, self.head_dim)}, but is {attn_output_v.size()}"
230
+ )
231
+
232
+ if attn_output_l.size() != (bsz * self.num_heads, src_len, self.head_dim):
233
+ raise ValueError(
234
+ f"`attn_output_l` should be of size {(bsz, self.num_heads, src_len, self.head_dim)}, but is {attn_output_l.size()}"
235
+ )
236
+
237
+ attn_output_v = attn_output_v.view(bsz, self.num_heads, tgt_len, self.head_dim)
238
+ attn_output_v = attn_output_v.transpose(1, 2)
239
+ attn_output_v = attn_output_v.reshape(bsz, tgt_len, self.embed_dim)
240
+
241
+ attn_output_l = attn_output_l.view(bsz, self.num_heads, src_len, self.head_dim)
242
+ attn_output_l = attn_output_l.transpose(1, 2)
243
+ attn_output_l = attn_output_l.reshape(bsz, src_len, self.embed_dim)
244
+
245
+ attn_output_v = self.out_v_proj(attn_output_v)
246
+ attn_output_l = self.out_l_proj(attn_output_l)
247
+
248
+ return attn_output_v, attn_output_l
249
+
250
+
251
+ # Bi-Direction MHA (text->image, image->text)
252
+ class BiAttentionBlock(nn.Module):
253
+ def __init__(
254
+ self,
255
+ v_dim,
256
+ l_dim,
257
+ embed_dim,
258
+ num_heads,
259
+ dropout=0.1,
260
+ drop_path=0.0,
261
+ init_values=1e-4,
262
+ cfg=None,
263
+ ):
264
+ """
265
+ Inputs:
266
+ embed_dim - Dimensionality of input and attention feature vectors
267
+ hidden_dim - Dimensionality of hidden layer in feed-forward network
268
+ (usually 2-4x larger than embed_dim)
269
+ num_heads - Number of heads to use in the Multi-Head Attention block
270
+ dropout - Amount of dropout to apply in the feed-forward network
271
+ """
272
+ super(BiAttentionBlock, self).__init__()
273
+
274
+ # pre layer norm
275
+ self.layer_norm_v = nn.LayerNorm(v_dim)
276
+ self.layer_norm_l = nn.LayerNorm(l_dim)
277
+ self.attn = BiMultiHeadAttention(
278
+ v_dim=v_dim, l_dim=l_dim, embed_dim=embed_dim, num_heads=num_heads, dropout=dropout
279
+ )
280
+
281
+ # add layer scale for training stability
282
+ self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
283
+ self.gamma_v = nn.Parameter(init_values * torch.ones((v_dim)), requires_grad=True)
284
+ self.gamma_l = nn.Parameter(init_values * torch.ones((l_dim)), requires_grad=True)
285
+
286
+ def forward(self, v, l, attention_mask_v=None, attention_mask_l=None):
287
+ v = self.layer_norm_v(v)
288
+ l = self.layer_norm_l(l)
289
+ delta_v, delta_l = self.attn(
290
+ v, l, attention_mask_v=attention_mask_v, attention_mask_l=attention_mask_l
291
+ )
292
+ # v, l = v + delta_v, l + delta_l
293
+ v = v + self.drop_path(self.gamma_v * delta_v)
294
+ l = l + self.drop_path(self.gamma_l * delta_l)
295
+ return v, l
296
+
297
+ # def forward(self, v:List[torch.Tensor], l, attention_mask_v=None, attention_mask_l=None)
models/GroundingDINO/groundingdino.py ADDED
@@ -0,0 +1,1041 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+ # Conditional DETR model and criterion classes.
8
+ # Copyright (c) 2021 Microsoft. All Rights Reserved.
9
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
10
+ # ------------------------------------------------------------------------
11
+ # Modified from DETR (https://github.com/facebookresearch/detr)
12
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
13
+ # ------------------------------------------------------------------------
14
+ # Modified from Deformable DETR (https://github.com/fundamentalvision/Deformable-DETR)
15
+ # Copyright (c) 2020 SenseTime. All Rights Reserved.
16
+ # ------------------------------------------------------------------------
17
+ import copy
18
+ from typing import List
19
+
20
+ import torch
21
+ import torch.nn.functional as F
22
+ from torch import nn
23
+ from torchvision.ops.boxes import nms
24
+ from torchvision.ops import roi_align
25
+ from transformers import AutoTokenizer, BertModel, BertTokenizer, RobertaModel, RobertaTokenizerFast
26
+
27
+ from groundingdino.util import box_ops, get_tokenlizer
28
+ from groundingdino.util.misc import (
29
+ NestedTensor,
30
+ accuracy,
31
+ get_world_size,
32
+ interpolate,
33
+ inverse_sigmoid,
34
+ is_dist_avail_and_initialized,
35
+ nested_tensor_from_tensor_list,
36
+ )
37
+ from groundingdino.util.utils import get_phrases_from_posmap
38
+ from groundingdino.util.visualizer import COCOVisualizer
39
+ from groundingdino.util.vl_utils import create_positive_map_from_span
40
+
41
+ from ..registry import MODULE_BUILD_FUNCS
42
+ from .backbone import build_backbone
43
+ from .bertwarper import (
44
+ BertModelWarper,
45
+ generate_masks_with_special_tokens,
46
+ generate_masks_with_special_tokens_and_transfer_map,
47
+ )
48
+ from .transformer import build_transformer
49
+ from .transformer_loca import TransformerEncoder
50
+ from .positional_encoding_loca import PositionalEncodingsFixed
51
+ from .utils import MLP, ContrastiveEmbed, sigmoid_focal_loss
52
+
53
+ from .matcher import build_matcher
54
+ import numpy as np
55
+
56
+
57
+
58
+ class GroundingDINO(nn.Module):
59
+ """This is the Cross-Attention Detector module that performs object detection"""
60
+
61
+ def __init__(
62
+ self,
63
+ backbone,
64
+ transformer,
65
+ num_queries,
66
+ aux_loss=False,
67
+ iter_update=False,
68
+ query_dim=2,
69
+ num_feature_levels=1,
70
+ nheads=8,
71
+ # two stage
72
+ two_stage_type="no", # ['no', 'standard']
73
+ dec_pred_bbox_embed_share=True,
74
+ two_stage_class_embed_share=True,
75
+ two_stage_bbox_embed_share=True,
76
+ num_patterns=0,
77
+ dn_number=100,
78
+ dn_box_noise_scale=0.4,
79
+ dn_label_noise_ratio=0.5,
80
+ dn_labelbook_size=100,
81
+ text_encoder_type="bert-base-uncased",
82
+ sub_sentence_present=True,
83
+ max_text_len=256,
84
+ ):
85
+ """Initializes the model.
86
+ Parameters:
87
+ backbone: torch module of the backbone to be used. See backbone.py
88
+ transformer: torch module of the transformer architecture. See transformer.py
89
+ num_queries: number of object queries, ie detection slot. This is the maximal number of objects
90
+ Conditional DETR can detect in a single image. For COCO, we recommend 100 queries.
91
+ aux_loss: True if auxiliary decoding losses (loss at each decoder layer) are to be used.
92
+ """
93
+ super().__init__()
94
+ self.num_queries = num_queries
95
+ self.transformer = transformer
96
+ self.hidden_dim = hidden_dim = transformer.d_model
97
+ self.num_feature_levels = num_feature_levels
98
+ self.nheads = nheads
99
+ self.max_text_len = 256
100
+ self.sub_sentence_present = sub_sentence_present
101
+
102
+ # setting query dim
103
+ self.query_dim = query_dim
104
+ assert query_dim == 4
105
+
106
+ # visual exemplar cropping
107
+ self.feature_map_proj = nn.Conv2d(
108
+ (256 + 512 + 1024), hidden_dim, kernel_size=1
109
+ )
110
+ self.feature_map_encoder = TransformerEncoder(
111
+ 3, hidden_dim, 8, 0.1, 1e-5,
112
+ 8, True, nn.GELU, True
113
+ )
114
+ self.feature_map_pos_embed = PositionalEncodingsFixed(hidden_dim)
115
+
116
+ # for dn training
117
+ self.num_patterns = num_patterns
118
+ self.dn_number = dn_number
119
+ self.dn_box_noise_scale = dn_box_noise_scale
120
+ self.dn_label_noise_ratio = dn_label_noise_ratio
121
+ self.dn_labelbook_size = dn_labelbook_size
122
+
123
+ # bert
124
+ self.tokenizer = get_tokenlizer.get_tokenlizer(text_encoder_type)
125
+ self.bert = get_tokenlizer.get_pretrained_language_model(text_encoder_type)
126
+ self.bert.pooler.dense.weight.requires_grad_(False)
127
+ self.bert.pooler.dense.bias.requires_grad_(False)
128
+ self.bert = BertModelWarper(bert_model=self.bert)
129
+
130
+ self.feat_map = nn.Linear(self.bert.config.hidden_size, self.hidden_dim, bias=True)
131
+ nn.init.constant_(self.feat_map.bias.data, 0)
132
+ nn.init.xavier_uniform_(self.feat_map.weight.data)
133
+ # freeze
134
+
135
+ # special tokens
136
+ self.specical_tokens = self.tokenizer.convert_tokens_to_ids(["[CLS]", "[SEP]", ".", "?"])
137
+ self.dot_id = self.tokenizer.convert_tokens_to_ids(".")
138
+
139
+ # prepare input projection layers
140
+ if num_feature_levels > 1:
141
+ num_backbone_outs = len(backbone.num_channels)
142
+ input_proj_list = []
143
+ for _ in range(num_backbone_outs):
144
+ in_channels = backbone.num_channels[_]
145
+ input_proj_list.append(
146
+ nn.Sequential(
147
+ nn.Conv2d(in_channels, hidden_dim, kernel_size=1),
148
+ nn.GroupNorm(32, hidden_dim),
149
+ )
150
+ )
151
+ for _ in range(num_feature_levels - num_backbone_outs):
152
+ input_proj_list.append(
153
+ nn.Sequential(
154
+ nn.Conv2d(in_channels, hidden_dim, kernel_size=3, stride=2, padding=1),
155
+ nn.GroupNorm(32, hidden_dim),
156
+ )
157
+ )
158
+ in_channels = hidden_dim
159
+ self.input_proj = nn.ModuleList(input_proj_list)
160
+ else:
161
+ assert two_stage_type == "no", "two_stage_type should be no if num_feature_levels=1 !!!"
162
+ self.input_proj = nn.ModuleList(
163
+ [
164
+ nn.Sequential(
165
+ nn.Conv2d(backbone.num_channels[-1], hidden_dim, kernel_size=1),
166
+ nn.GroupNorm(32, hidden_dim),
167
+ )
168
+ ]
169
+ )
170
+
171
+ self.backbone = backbone
172
+ self.aux_loss = aux_loss
173
+ self.box_pred_damping = box_pred_damping = None
174
+
175
+ self.iter_update = iter_update
176
+ assert iter_update, "Why not iter_update?"
177
+
178
+ # prepare pred layers
179
+ self.dec_pred_bbox_embed_share = dec_pred_bbox_embed_share
180
+ # prepare class & box embed
181
+ _class_embed = ContrastiveEmbed()
182
+
183
+ _bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3)
184
+ nn.init.constant_(_bbox_embed.layers[-1].weight.data, 0)
185
+ nn.init.constant_(_bbox_embed.layers[-1].bias.data, 0)
186
+
187
+ if dec_pred_bbox_embed_share:
188
+ box_embed_layerlist = [_bbox_embed for i in range(transformer.num_decoder_layers)]
189
+ else:
190
+ box_embed_layerlist = [
191
+ copy.deepcopy(_bbox_embed) for i in range(transformer.num_decoder_layers)
192
+ ]
193
+ class_embed_layerlist = [_class_embed for i in range(transformer.num_decoder_layers)]
194
+ self.bbox_embed = nn.ModuleList(box_embed_layerlist)
195
+ self.class_embed = nn.ModuleList(class_embed_layerlist)
196
+ self.transformer.decoder.bbox_embed = self.bbox_embed
197
+ self.transformer.decoder.class_embed = self.class_embed
198
+
199
+ # two stage
200
+ self.two_stage_type = two_stage_type
201
+ assert two_stage_type in ["no", "standard"], "unknown param {} of two_stage_type".format(
202
+ two_stage_type
203
+ )
204
+ if two_stage_type != "no":
205
+ if two_stage_bbox_embed_share:
206
+ assert dec_pred_bbox_embed_share
207
+ self.transformer.enc_out_bbox_embed = _bbox_embed
208
+ else:
209
+ self.transformer.enc_out_bbox_embed = copy.deepcopy(_bbox_embed)
210
+
211
+ if two_stage_class_embed_share:
212
+ assert dec_pred_bbox_embed_share
213
+ self.transformer.enc_out_class_embed = _class_embed
214
+ else:
215
+ self.transformer.enc_out_class_embed = copy.deepcopy(_class_embed)
216
+
217
+ self.refpoint_embed = None
218
+
219
+ self._reset_parameters()
220
+
221
+ def _reset_parameters(self):
222
+ # init input_proj
223
+ for proj in self.input_proj:
224
+ nn.init.xavier_uniform_(proj[0].weight, gain=1)
225
+ nn.init.constant_(proj[0].bias, 0)
226
+
227
+ def init_ref_points(self, use_num_queries):
228
+ self.refpoint_embed = nn.Embedding(use_num_queries, self.query_dim)
229
+
230
+ def add_exemplar_tokens(self, tokenized, text_dict, exemplar_tokens, exemplar_labels):
231
+ input_ids = tokenized["input_ids"]
232
+
233
+ device = input_ids.device
234
+ new_input_ids = []
235
+ encoded_text = text_dict["encoded_text"]
236
+ new_encoded_text = []
237
+ text_token_mask = text_dict["text_token_mask"]
238
+ new_text_token_mask = []
239
+ position_ids = text_dict["position_ids"]
240
+ text_self_attention_masks = text_dict["text_self_attention_masks"]
241
+
242
+ for sample_ind in range(len(exemplar_tokens)):
243
+ new_input_ids_sample = input_ids[sample_ind]
244
+ new_encoded_text_sample = encoded_text[sample_ind]
245
+ for exemplar_ind in range(len(exemplar_tokens[sample_ind])):
246
+ label = exemplar_labels[sample_ind][exemplar_ind]
247
+ exemplar = exemplar_tokens[sample_ind][exemplar_ind].unsqueeze(0) # add an L (# of boxes) dimension
248
+ label_count = -1
249
+ for token_ind in range(len(new_input_ids_sample)):
250
+ input_id = new_input_ids_sample[token_ind]
251
+ if (input_id not in self.specical_tokens) and (token_ind == 0 or (new_input_ids_sample[token_ind - 1] in self.specical_tokens)):
252
+ label_count += 1
253
+ elif token_ind > 0 \
254
+ and new_input_ids_sample[token_ind] == self.dot_id \
255
+ and new_input_ids_sample[token_ind - 1] in self.specical_tokens:
256
+ label_count += 1
257
+ if label_count == label:
258
+ # Get the index where to insert the exemplar tokens.
259
+ ind_to_insert_exemplar = token_ind
260
+ while new_input_ids_sample[ind_to_insert_exemplar] not in self.specical_tokens:
261
+ ind_to_insert_exemplar += 1
262
+ break
263
+
264
+ # * token indicates exemplar
265
+ new_input_ids_sample = torch.cat([new_input_ids_sample[:ind_to_insert_exemplar], torch.tensor([1008] * exemplar.shape[0]).to(device), new_input_ids_sample[ind_to_insert_exemplar:]])
266
+ new_encoded_text_sample = torch.cat([new_encoded_text_sample[:ind_to_insert_exemplar, :], exemplar, new_encoded_text_sample[ind_to_insert_exemplar:, :]])
267
+
268
+ new_text_token_mask_sample = torch.full((len(new_input_ids_sample),), True).to(device)
269
+ new_input_ids.append(new_input_ids_sample)
270
+ new_encoded_text.append(new_encoded_text_sample)
271
+ new_text_token_mask.append(new_text_token_mask_sample)
272
+
273
+ # Add padding for variable numbers of exemplars for each image.
274
+ pad_token_id = self.tokenizer.pad_token_id
275
+ bs = len(new_input_ids)
276
+ d_model = new_encoded_text[0].shape[-1]
277
+ device = input_ids.device
278
+
279
+ lens = [ids.numel() for ids in new_input_ids]
280
+ Lmax = max(lens)
281
+
282
+ padded_input_ids = torch.full((bs, Lmax), pad_token_id, dtype=torch.long, device=device)
283
+ padded_encoded_text = torch.zeros(bs, Lmax, d_model, dtype=new_encoded_text[0].dtype, device=device)
284
+ padded_text_token_mask = torch.zeros(bs, Lmax, dtype=torch.bool, device=device) # False for PAD
285
+
286
+ for i in range(bs):
287
+ L = min(lens[i], Lmax)
288
+ padded_input_ids[i, :L] = new_input_ids[i][:L]
289
+ padded_encoded_text[i, :L] = new_encoded_text[i][:L]
290
+ padded_text_token_mask[i, :L] = new_text_token_mask[i][:L] # True for real tokens
291
+
292
+ tokenized["input_ids"] = padded_input_ids
293
+ tokenized["attention_mask"] = padded_text_token_mask.long() # HF expects 0/1
294
+
295
+ text_self_attention_masks, position_ids, _ = generate_masks_with_special_tokens_and_transfer_map(
296
+ tokenized, self.specical_tokens, None
297
+ )
298
+
299
+
300
+ text_dict = {
301
+ "encoded_text": padded_encoded_text,
302
+ "text_token_mask": padded_text_token_mask, # True=valid, False=pad
303
+ "position_ids": position_ids,
304
+ "text_self_attention_masks": text_self_attention_masks,
305
+ }
306
+ return text_dict
307
+
308
+ def combine_features(self, features):
309
+
310
+ (bs, c, h, w) = (features[0].decompose()[0].shape[-4], features[0].decompose()[0].shape[-3], features[0].decompose()[0].shape[-2], features[0].decompose()[0].shape[-1])
311
+
312
+ x = torch.cat([
313
+ F.interpolate(feat.decompose()[0], size=(h, w), mode='bilinear', align_corners=True)
314
+ for feat in features
315
+ ], dim=1)
316
+
317
+ x = self.feature_map_proj(x)
318
+
319
+ #pos_emb = self.feature_map_pos_embed(bs, h, w, x.device)
320
+
321
+ #pos_emb = pos_emb.flatten(2).permute(2, 0, 1)
322
+
323
+ #x = x.flatten(2).permute(2, 0, 1)
324
+
325
+ #x = self.feature_map_encoder(x, pos_emb, src_key_padding_mask=None, src_mask=None)
326
+
327
+ #x = x.permute(1, 2, 0).reshape(-1, self.hidden_dim, h, w)
328
+
329
+ return x
330
+
331
+
332
+ def forward(self, samples: NestedTensor, exemplar_boxes, exemplar_labels, targets: List = None, **kw):
333
+ """The forward expects a NestedTensor, which consists of:
334
+ - samples.tensor: batched images, of shape [batch_size x 3 x H x W]
335
+ - samples.mask: a binary mask of shape [batch_size x H x W], containing 1 on padded pixels
336
+
337
+ It returns a dict with the following elements:
338
+ - "pred_logits": the classification logits (including no-object) for all queries.
339
+ Shape= [batch_size x num_queries x num_classes]
340
+ - "pred_boxes": The normalized boxes coordinates for all queries, represented as
341
+ (center_x, center_y, width, height). These values are normalized in [0, 1],
342
+ relative to the size of each individual image (disregarding possible padding).
343
+ See PostProcess for information on how to retrieve the unnormalized bounding box.
344
+ - "aux_outputs": Optional, only returned when auxilary losses are activated. It is a list of
345
+ dictionnaries containing the two above keys for each decoder layer.
346
+ """
347
+
348
+ if targets is None:
349
+ captions = kw["captions"]
350
+ else:
351
+ captions = [t["caption"] for t in targets]
352
+
353
+ # encoder texts
354
+
355
+ tokenized = self.tokenizer(captions, padding="longest", return_tensors="pt").to(
356
+ samples.device
357
+ )
358
+
359
+ one_hot_token = tokenized
360
+
361
+ (
362
+ text_self_attention_masks,
363
+ position_ids,
364
+ cate_to_token_mask_list,
365
+ ) = generate_masks_with_special_tokens_and_transfer_map(
366
+ tokenized, self.specical_tokens, self.tokenizer
367
+ )
368
+
369
+ if text_self_attention_masks.shape[1] > self.max_text_len:
370
+ text_self_attention_masks = text_self_attention_masks[
371
+ :, : self.max_text_len, : self.max_text_len
372
+ ]
373
+ position_ids = position_ids[:, : self.max_text_len]
374
+ tokenized["input_ids"] = tokenized["input_ids"][:, : self.max_text_len]
375
+ tokenized["attention_mask"] = tokenized["attention_mask"][:, : self.max_text_len]
376
+ tokenized["token_type_ids"] = tokenized["token_type_ids"][:, : self.max_text_len]
377
+
378
+ # extract text embeddings
379
+ if self.sub_sentence_present:
380
+ tokenized_for_encoder = {k: v for k, v in tokenized.items() if k != "attention_mask"}
381
+ tokenized_for_encoder["attention_mask"] = text_self_attention_masks
382
+ tokenized_for_encoder["position_ids"] = position_ids
383
+ else:
384
+ tokenized_for_encoder = tokenized
385
+
386
+ bert_output = self.bert(**tokenized_for_encoder) # bs, 195, 768
387
+
388
+ encoded_text = self.feat_map(bert_output["last_hidden_state"]) # bs, 195, d_model
389
+ text_token_mask = tokenized.attention_mask.bool() # bs, 195
390
+ # text_token_mask: True for nomask, False for mask
391
+ # text_self_attention_masks: True for nomask, False for mask
392
+
393
+ if encoded_text.shape[1] > self.max_text_len:
394
+ encoded_text = encoded_text[:, : self.max_text_len, :]
395
+ text_token_mask = text_token_mask[:, : self.max_text_len]
396
+ position_ids = position_ids[:, : self.max_text_len]
397
+ text_self_attention_masks = text_self_attention_masks[
398
+ :, : self.max_text_len, : self.max_text_len
399
+ ]
400
+
401
+
402
+ text_dict = {
403
+ "encoded_text": encoded_text, # bs, 195, d_model
404
+ "text_token_mask": text_token_mask, # bs, 195
405
+ "position_ids": position_ids, # bs, 195
406
+ "text_self_attention_masks": text_self_attention_masks, # bs, 195,195
407
+ }
408
+
409
+ if isinstance(samples, (list, torch.Tensor)):
410
+ samples = nested_tensor_from_tensor_list(samples)
411
+
412
+ features, poss = self.backbone(samples)
413
+ combined_features = self.combine_features(features)
414
+
415
+ # Get visual exemplar tokens.
416
+ bs = len(exemplar_boxes)
417
+ exemplar_tokens = roi_align(combined_features, boxes=exemplar_boxes, output_size=(1, 1), spatial_scale=(1 / 8), aligned=True)
418
+ if exemplar_tokens.shape[0] > 0:
419
+ # Assemble boxes back into [bs, num_exemplars, 4] list
420
+ exemplar_counts = [b.size(0) for b in exemplar_boxes]
421
+ exemplar_tokens = list(torch.split(exemplar_tokens, exemplar_counts, dim=0))
422
+ exemplar_tokens = [exemp.squeeze(-1).squeeze(-1) for exemp in exemplar_tokens]
423
+ text_dict = self.add_exemplar_tokens(tokenized, text_dict, exemplar_tokens, exemplar_labels)
424
+
425
+ srcs = []
426
+ masks = []
427
+ for l, feat in enumerate(features):
428
+ src, mask = feat.decompose()
429
+ srcs.append(self.input_proj[l](src))
430
+ masks.append(mask)
431
+ assert mask is not None
432
+ if self.num_feature_levels > len(srcs):
433
+ _len_srcs = len(srcs)
434
+ for l in range(_len_srcs, self.num_feature_levels):
435
+ if l == _len_srcs:
436
+ src = self.input_proj[l](features[-1].tensors)
437
+ else:
438
+ src = self.input_proj[l](srcs[-1])
439
+ m = samples.mask
440
+ mask = F.interpolate(m[None].float(), size=src.shape[-2:]).to(torch.bool)[0]
441
+ pos_l = self.backbone[1](NestedTensor(src, mask)).to(src.dtype)
442
+ srcs.append(src)
443
+ masks.append(mask)
444
+ poss.append(pos_l)
445
+
446
+ # Comment out the next two lines after the ablation study is complete
447
+ #new_attention_mask = torch.ones_like(text_dict["text_self_attention_masks"], dtype=torch.bool)
448
+ #text_dict["text_self_attention_masks"] = new_attention_mask
449
+
450
+ input_query_bbox = input_query_label = attn_mask = dn_meta = None
451
+ hs, reference, hs_enc, ref_enc, init_box_proposal = self.transformer(
452
+ srcs, masks, input_query_bbox, poss, input_query_label, attn_mask, text_dict
453
+ )
454
+
455
+
456
+ # deformable-detr-like anchor update
457
+ outputs_coord_list = []
458
+ for dec_lid, (layer_ref_sig, layer_bbox_embed, layer_hs) in enumerate(
459
+ zip(reference[:-1], self.bbox_embed, hs)
460
+ ):
461
+ layer_delta_unsig = layer_bbox_embed(layer_hs)
462
+ layer_outputs_unsig = layer_delta_unsig + inverse_sigmoid(layer_ref_sig)
463
+ layer_outputs_unsig = layer_outputs_unsig.sigmoid()
464
+ outputs_coord_list.append(layer_outputs_unsig)
465
+ outputs_coord_list = torch.stack(outputs_coord_list)
466
+
467
+
468
+ outputs_class = torch.stack(
469
+ [
470
+ layer_cls_embed(layer_hs, text_dict)
471
+ for layer_cls_embed, layer_hs in zip(self.class_embed, hs)
472
+ ]
473
+ )
474
+
475
+ out = {"pred_logits": outputs_class[-1], "pred_boxes": outputs_coord_list[-1]}
476
+
477
+ # Used to calculate losses
478
+ bs, len_td = text_dict['text_token_mask'].shape
479
+ out['text_mask']=torch.zeros(bs, self.max_text_len, dtype=torch.bool).to(
480
+ samples.device
481
+ )
482
+ for b in range(bs):
483
+ for j in range(len_td):
484
+ if text_dict['text_token_mask'][b][j] == True:
485
+ out['text_mask'][b][j] = True
486
+
487
+ # for intermediate outputs
488
+ if self.aux_loss:
489
+ out['aux_outputs'] = self._set_aux_loss(outputs_class, outputs_coord_list)
490
+ out['token']=one_hot_token
491
+ # # for encoder output
492
+ if hs_enc is not None:
493
+ # prepare intermediate outputs
494
+ interm_coord = ref_enc[-1]
495
+ interm_class = self.transformer.enc_out_class_embed(hs_enc[-1], text_dict)
496
+ out['interm_outputs'] = {'pred_logits': interm_class, 'pred_boxes': interm_coord}
497
+ out['interm_outputs_for_matching_pre'] = {'pred_logits': interm_class, 'pred_boxes': init_box_proposal}
498
+
499
+ # outputs['pred_logits'].shape
500
+ # torch.Size([4, 900, 256])
501
+
502
+ # outputs['pred_boxes'].shape
503
+ # torch.Size([4, 900, 4])
504
+
505
+ # outputs['text_mask'].shape
506
+ # torch.Size([256])
507
+
508
+ # outputs['text_mask']
509
+
510
+ # outputs['aux_outputs'][0].keys()
511
+ # dict_keys(['pred_logits', 'pred_boxes', 'one_hot', 'text_mask'])
512
+
513
+ # outputs['aux_outputs'][img_idx]
514
+
515
+ # outputs['token']
516
+ # <class 'transformers.tokenization_utils_base.BatchEncoding'>
517
+
518
+ # outputs['interm_outputs'].keys()
519
+ # dict_keys(['pred_logits', 'pred_boxes', 'one_hot', 'text_mask'])
520
+
521
+
522
+ # outputs['interm_outputs_for_matching_pre'].keys()
523
+ # dict_keys(['pred_logits', 'pred_boxes'])
524
+
525
+ # outputs['one_hot'].shape
526
+ # torch.Size([4, 900, 256])
527
+
528
+ return out
529
+
530
+ @torch.jit.unused
531
+ def _set_aux_loss(self, outputs_class, outputs_coord):
532
+ # this is a workaround to make torchscript happy, as torchscript
533
+ # doesn't support dictionary with non-homogeneous values, such
534
+ # as a dict having both a Tensor and a list.
535
+ return [
536
+ {"pred_logits": a, "pred_boxes": b}
537
+ for a, b in zip(outputs_class[:-1], outputs_coord[:-1])
538
+ ]
539
+
540
+ class SetCriterion(nn.Module):
541
+ def __init__(self, matcher, weight_dict, focal_alpha,focal_gamma, losses):
542
+ """ Create the criterion.
543
+ Parameters:
544
+ matcher: module able to compute a matching between targets and proposals
545
+ weight_dict: dict containing as key the names of the losses and as values their relative weight.
546
+ losses: list of all the losses to be applied. See get_loss for list of available losses.
547
+ focal_alpha: alpha in Focal Loss
548
+ """
549
+ super().__init__()
550
+ self.matcher = matcher
551
+ self.weight_dict = weight_dict
552
+ self.losses = losses
553
+ self.focal_alpha = focal_alpha
554
+ self.focal_gamma= focal_gamma
555
+
556
+ @torch.no_grad()
557
+ def loss_cardinality(self, outputs, targets, indices, num_boxes):
558
+ """ Compute the cardinality error, ie the absolute error in the number of predicted non-empty boxes
559
+ This is not really a loss, it is intended for logging purposes only. It doesn't propagate gradients
560
+ """
561
+
562
+ pred_logits = outputs['pred_logits']
563
+ device = pred_logits.device
564
+ tgt_lengths = torch.as_tensor([len(v["labels"]) for v in targets], device=device)
565
+ # Count the number of predictions that are NOT "no-object" (which is the last class)
566
+ card_pred = (pred_logits.argmax(-1) != pred_logits.shape[-1] - 1).sum(1)
567
+ card_err = F.l1_loss(card_pred.float(), tgt_lengths.float())
568
+ losses = {'cardinality_error': card_err}
569
+ return losses
570
+
571
+ def loss_boxes(self, outputs, targets, indices, num_boxes):
572
+ """Compute the losses related to the bounding boxes, the L1 regression loss and the GIoU loss
573
+ targets dicts must contain the key "boxes" containing a tensor of dim [nb_target_boxes, 4]
574
+ The target boxes are expected in format (center_x, center_y, w, h), normalized by the image size.
575
+ """
576
+ assert 'pred_boxes' in outputs
577
+ idx = self._get_src_permutation_idx(indices)
578
+ src_boxes = outputs['pred_boxes'][idx]
579
+ target_boxes = torch.cat([t['boxes'][i] for t, (_, i) in zip(targets, indices)], dim=0)
580
+ # Get the normalized exemplars for weak box supervision.
581
+ exemplars = [t['exemplar_boxes'] for t in targets]
582
+
583
+ # Match and isolate exemplar source and targets.
584
+ src_boxes_exemplar = []
585
+ target_boxes_exemplar = []
586
+ bs = len(targets)
587
+ for ind in range(bs):
588
+ # For each exemplar, find the corresponding target box.
589
+ # The 'matching' target box is the one with the closest center (note: dot annotations are not necessarily exactly at the center of the exemplar bounding box).
590
+ # We use the l2 distance.
591
+ target_boxes_per_img = target_boxes[idx[0] == ind, :]
592
+ src_boxes_per_img = src_boxes[idx[0] == ind, :]
593
+ exemplars_per_img = exemplars[ind]
594
+
595
+ for exemplar in exemplars_per_img:
596
+
597
+ if target_boxes_per_img.shape[0] > 0:
598
+ # Handle case where dots are cropped out but exemplar remains.
599
+ dist = torch.sqrt(torch.sum((exemplar[:2] - target_boxes_per_img[:, :2]) ** 2, dim=-1))
600
+ _, min_index = torch.min(dist, dim=0)
601
+ src_boxes_exemplar.append(src_boxes_per_img[min_index,:])
602
+ target_boxes_exemplar.append(exemplar)
603
+
604
+ # Handle no exemplars case.
605
+ if len(src_boxes_exemplar) > 0:
606
+ src_boxes_exemplar = torch.stack(src_boxes_exemplar, dim=0)
607
+ target_boxes_exemplar = torch.stack(target_boxes_exemplar, dim=0)
608
+ loss_hw_bbox = F.l1_loss(src_boxes_exemplar[:, 2:], target_boxes_exemplar[:, 2:], reduction='none')
609
+ loss_giou = 1 - torch.diag(box_ops.generalized_box_iou(
610
+ box_ops.box_cxcywh_to_xyxy(src_boxes_exemplar),
611
+ box_ops.box_cxcywh_to_xyxy(target_boxes_exemplar)))
612
+ else:
613
+ loss_hw_bbox = torch.tensor(0).to(src_boxes.device)
614
+ loss_giou = torch.tensor(0).to(src_boxes.device)
615
+
616
+ loss_center_bbox = F.l1_loss(src_boxes[:, :2], target_boxes[:, :2], reduction='none')
617
+
618
+ losses = {}
619
+ losses['loss_bbox'] = (loss_center_bbox.sum() + loss_hw_bbox.sum()) / num_boxes
620
+ losses['loss_giou'] = loss_giou.sum() / num_boxes
621
+
622
+ # calculate the x,y and h,w loss
623
+ with torch.no_grad():
624
+ losses['loss_xy'] = loss_center_bbox.sum() / num_boxes
625
+ losses['loss_hw'] = loss_hw_bbox.sum() / num_boxes
626
+
627
+ return losses
628
+
629
+
630
+ def token_sigmoid_binary_focal_loss(self, outputs, targets, indices, num_boxes):
631
+ pred_logits=outputs['pred_logits']
632
+ new_targets=outputs['one_hot'].to(pred_logits.device)
633
+ text_mask=outputs['text_mask']
634
+
635
+ assert (new_targets.dim() == 3)
636
+ assert (pred_logits.dim() == 3) # batch x from x to
637
+
638
+ bs, n, _ = pred_logits.shape
639
+ alpha=self.focal_alpha
640
+ gamma=self.focal_gamma
641
+ if text_mask is not None:
642
+ # ODVG: each sample has different mask
643
+ text_mask = text_mask.repeat(1, pred_logits.size(1)).view(outputs['text_mask'].shape[0],-1,outputs['text_mask'].shape[1])
644
+ pred_logits = torch.masked_select(pred_logits, text_mask)
645
+ new_targets = torch.masked_select(new_targets, text_mask)
646
+
647
+ new_targets=new_targets.float()
648
+ p = torch.sigmoid(pred_logits)
649
+ ce_loss = F.binary_cross_entropy_with_logits(pred_logits, new_targets, reduction="none")
650
+ p_t = p * new_targets + (1 - p) * (1 - new_targets)
651
+ loss = ce_loss * ((1 - p_t) ** gamma)
652
+
653
+ if alpha >= 0:
654
+ alpha_t = alpha * new_targets + (1 - alpha) * (1 - new_targets)
655
+ loss = alpha_t * loss
656
+
657
+ total_num_pos=0
658
+ for batch_indices in indices:
659
+ total_num_pos += len(batch_indices[0])
660
+ num_pos_avg_per_gpu = max(total_num_pos , 1.0)
661
+ loss=loss.sum()/num_pos_avg_per_gpu
662
+
663
+ losses = {'loss_ce': loss}
664
+ return losses
665
+
666
+
667
+ def _get_src_permutation_idx(self, indices):
668
+ # permute predictions following indices
669
+ batch_idx = torch.cat([torch.full_like(src, i) for i, (src, _) in enumerate(indices)])
670
+ src_idx = torch.cat([src for (src, _) in indices])
671
+ return batch_idx, src_idx
672
+
673
+ def _get_tgt_permutation_idx(self, indices):
674
+ # permute targets following indices
675
+ batch_idx = torch.cat([torch.full_like(tgt, i) for i, (_, tgt) in enumerate(indices)])
676
+ tgt_idx = torch.cat([tgt for (_, tgt) in indices])
677
+ return batch_idx, tgt_idx
678
+
679
+ def get_loss(self, loss, outputs, targets, indices, num_boxes, **kwargs):
680
+ loss_map = {
681
+ 'labels': self.token_sigmoid_binary_focal_loss,
682
+ 'cardinality': self.loss_cardinality,
683
+ 'boxes': self.loss_boxes,
684
+ }
685
+ assert loss in loss_map, f'do you really want to compute {loss} loss?'
686
+ return loss_map[loss](outputs, targets, indices, num_boxes, **kwargs)
687
+
688
+ def forward(self, outputs, targets, cat_list, caption, return_indices=False):
689
+ """ This performs the loss computation.
690
+ Parameters:
691
+ outputs: dict of tensors, see the output specification of the model for the format
692
+ targets: list of dicts, such that len(targets) == batch_size.
693
+ The expected keys in each dict depends on the losses applied, see each loss' doc
694
+
695
+ return_indices: used for vis. if True, the layer0-5 indices will be returned as well.
696
+ """
697
+ device=next(iter(outputs.values())).device
698
+ one_hot = torch.zeros(outputs['pred_logits'].size(),dtype=torch.int64) # torch.Size([bs, 900, 256])
699
+ token = outputs['token']
700
+
701
+ label_map_list = []
702
+ indices = []
703
+ for j in range(len(cat_list)): # bs
704
+ label_map=[]
705
+ for i in range(len(cat_list[j])):
706
+ label_id=torch.tensor([i])
707
+ per_label = create_positive_map_exemplar(token['input_ids'][j], label_id, [101, 102, 1012, 1029])
708
+ label_map.append(per_label)
709
+ label_map=torch.stack(label_map,dim=0).squeeze(1)
710
+
711
+ label_map_list.append(label_map)
712
+ for j in range(len(cat_list)): # bs
713
+ for_match = {
714
+ "pred_logits" : outputs['pred_logits'][j].unsqueeze(0),
715
+ "pred_boxes" : outputs['pred_boxes'][j].unsqueeze(0)
716
+ }
717
+
718
+ inds = self.matcher(for_match, [targets[j]], label_map_list[j])
719
+ indices.extend(inds)
720
+ # indices : A list of size batch_size, containing tuples of (index_i, index_j) where:
721
+ # - index_i is the indices of the selected predictions (in order)
722
+ # - index_j is the indices of the corresponding selected targets (in order)
723
+
724
+ # import pdb; pdb.set_trace()
725
+ tgt_ids = [v["labels"].cpu() for v in targets]
726
+ # len(tgt_ids) == bs
727
+ for i in range(len(indices)):
728
+ tgt_ids[i]=tgt_ids[i][indices[i][1]]
729
+ one_hot[i,indices[i][0]] = label_map_list[i][tgt_ids[i]].to(torch.long)
730
+ outputs['one_hot'] = one_hot
731
+ if return_indices:
732
+ indices0_copy = indices
733
+ indices_list = []
734
+
735
+ # Compute the average number of target boxes accross all nodes, for normalization purposes
736
+ num_boxes_list = [len(t["labels"]) for t in targets]
737
+ num_boxes = sum(num_boxes_list)
738
+ num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=device)
739
+ if is_dist_avail_and_initialized():
740
+ torch.distributed.all_reduce(num_boxes)
741
+ num_boxes = torch.clamp(num_boxes / get_world_size(), min=1).item()
742
+
743
+ # Compute all the requested losses
744
+ losses = {}
745
+ for loss in self.losses:
746
+ losses.update(self.get_loss(loss, outputs, targets, indices, num_boxes))
747
+
748
+ # In case of auxiliary losses, we repeat this process with the output of each intermediate layer.
749
+ if 'aux_outputs' in outputs:
750
+ for idx, aux_outputs in enumerate(outputs['aux_outputs']):
751
+ indices = []
752
+ for j in range(len(cat_list)): # bs
753
+ aux_output_single = {
754
+ 'pred_logits' : aux_outputs['pred_logits'][j].unsqueeze(0),
755
+ 'pred_boxes': aux_outputs['pred_boxes'][j].unsqueeze(0)
756
+ }
757
+ inds = self.matcher(aux_output_single, [targets[j]], label_map_list[j])
758
+ indices.extend(inds)
759
+ one_hot_aux = torch.zeros(outputs['pred_logits'].size(),dtype=torch.int64)
760
+ tgt_ids = [v["labels"].cpu() for v in targets]
761
+ for i in range(len(indices)):
762
+ tgt_ids[i]=tgt_ids[i][indices[i][1]]
763
+ one_hot_aux[i,indices[i][0]] = label_map_list[i][tgt_ids[i]].to(torch.long)
764
+ aux_outputs['one_hot'] = one_hot_aux
765
+ aux_outputs['text_mask'] = outputs['text_mask']
766
+ if return_indices:
767
+ indices_list.append(indices)
768
+ for loss in self.losses:
769
+ kwargs = {}
770
+ l_dict = self.get_loss(loss, aux_outputs, targets, indices, num_boxes, **kwargs)
771
+ l_dict = {k + f'_{idx}': v for k, v in l_dict.items()}
772
+ losses.update(l_dict)
773
+
774
+ # interm_outputs loss
775
+ if 'interm_outputs' in outputs:
776
+ interm_outputs = outputs['interm_outputs']
777
+ indices = []
778
+ for j in range(len(cat_list)): # bs
779
+ interm_output_single = {
780
+ 'pred_logits' : interm_outputs['pred_logits'][j].unsqueeze(0),
781
+ 'pred_boxes': interm_outputs['pred_boxes'][j].unsqueeze(0)
782
+ }
783
+ inds = self.matcher(interm_output_single, [targets[j]], label_map_list[j])
784
+ indices.extend(inds)
785
+ one_hot_aux = torch.zeros(outputs['pred_logits'].size(),dtype=torch.int64)
786
+ tgt_ids = [v["labels"].cpu() for v in targets]
787
+ for i in range(len(indices)):
788
+ tgt_ids[i]=tgt_ids[i][indices[i][1]]
789
+ one_hot_aux[i,indices[i][0]] = label_map_list[i][tgt_ids[i]].to(torch.long)
790
+ interm_outputs['one_hot'] = one_hot_aux
791
+ interm_outputs['text_mask'] = outputs['text_mask']
792
+ if return_indices:
793
+ indices_list.append(indices)
794
+ for loss in self.losses:
795
+ kwargs = {}
796
+ l_dict = self.get_loss(loss, interm_outputs, targets, indices, num_boxes, **kwargs)
797
+ l_dict = {k + f'_interm': v for k, v in l_dict.items()}
798
+ losses.update(l_dict)
799
+
800
+ if return_indices:
801
+ indices_list.append(indices0_copy)
802
+ return losses, indices_list
803
+
804
+
805
+ return losses
806
+
807
+
808
+ class PostProcess(nn.Module):
809
+ """ This module converts the model's output into the format expected by the coco api"""
810
+ def __init__(self, num_select=100,text_encoder_type='text_encoder_type', nms_iou_threshold=-1,use_coco_eval=False,args=None) -> None:
811
+ super().__init__()
812
+ self.num_select = num_select
813
+ self.tokenizer = get_tokenlizer.get_tokenlizer(text_encoder_type)
814
+ if args.use_coco_eval:
815
+ from pycocotools.coco import COCO
816
+ coco = COCO(args.coco_val_path)
817
+ category_dict = coco.loadCats(coco.getCatIds())
818
+ cat_list = [item['name'] for item in category_dict]
819
+ else:
820
+ cat_list=args.label_list
821
+ caption = " . ".join(cat_list) + ' .'
822
+ tokenized = self.tokenizer(caption, padding="longest", return_tensors="pt")
823
+ label_list = torch.arange(len(cat_list))
824
+ pos_map=create_positive_map(tokenized,label_list,cat_list,caption)
825
+ # build a mapping from label_id to pos_map
826
+ if args.use_coco_eval:
827
+ id_map = {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10, 10: 11, 11: 13, 12: 14, 13: 15, 14: 16, 15: 17, 16: 18, 17: 19, 18: 20, 19: 21, 20: 22, 21: 23, 22: 24, 23: 25, 24: 27, 25: 28, 26: 31, 27: 32, 28: 33, 29: 34, 30: 35, 31: 36, 32: 37, 33: 38, 34: 39, 35: 40, 36: 41, 37: 42, 38: 43, 39: 44, 40: 46,
828
+ 41: 47, 42: 48, 43: 49, 44: 50, 45: 51, 46: 52, 47: 53, 48: 54, 49: 55, 50: 56, 51: 57, 52: 58, 53: 59, 54: 60, 55: 61, 56: 62, 57: 63, 58: 64, 59: 65, 60: 67, 61: 70, 62: 72, 63: 73, 64: 74, 65: 75, 66: 76, 67: 77, 68: 78, 69: 79, 70: 80, 71: 81, 72: 82, 73: 84, 74: 85, 75: 86, 76: 87, 77: 88, 78: 89, 79: 90}
829
+ new_pos_map = torch.zeros((91, 256))
830
+ for k, v in id_map.items():
831
+ new_pos_map[v] = pos_map[k]
832
+ pos_map=new_pos_map
833
+
834
+
835
+ self.nms_iou_threshold=nms_iou_threshold
836
+ self.positive_map = pos_map
837
+
838
+ @torch.no_grad()
839
+ def forward(self, outputs, target_sizes, not_to_xyxy=False, test=False):
840
+ """ Perform the computation
841
+ Parameters:
842
+ outputs: raw outputs of the model
843
+ target_sizes: tensor of dimension [batch_size x 2] containing the size of each images of the batch
844
+ For evaluation, this must be the original image size (before any data augmentation)
845
+ For visualization, this should be the image size after data augment, but before padding
846
+ """
847
+ num_select = self.num_select
848
+ out_logits, out_bbox = outputs['pred_logits'], outputs['pred_boxes']
849
+
850
+
851
+ prob_to_token = out_logits.sigmoid()
852
+ pos_maps = self.positive_map.to(prob_to_token.device)
853
+ for label_ind in range(len(pos_maps)):
854
+ if pos_maps[label_ind].sum() != 0:
855
+ pos_maps[label_ind]=pos_maps[label_ind]/pos_maps[label_ind].sum()
856
+
857
+ prob_to_label = prob_to_token @ pos_maps.T
858
+
859
+ assert len(out_logits) == len(target_sizes)
860
+ assert target_sizes.shape[1] == 2
861
+
862
+ prob = prob_to_label
863
+ topk_values, topk_indexes = torch.topk(prob.view(prob.shape[0], -1), num_select, dim=1)
864
+ scores = topk_values
865
+ topk_boxes = torch.div(topk_indexes, prob.shape[2], rounding_mode='trunc')
866
+ labels = topk_indexes % prob.shape[2]
867
+ if not_to_xyxy:
868
+ boxes = out_bbox
869
+ else:
870
+ boxes = box_ops.box_cxcywh_to_xyxy(out_bbox)
871
+
872
+ # if test:
873
+ # assert not not_to_xyxy
874
+ # boxes[:,:,2:] = boxes[:,:,2:] - boxes[:,:,:2]
875
+ boxes = torch.gather(boxes, 1, topk_boxes.unsqueeze(-1).repeat(1,1,4))
876
+
877
+ # and from relative [0, 1] to absolute [0, height] coordinates
878
+ img_h, img_w = target_sizes.unbind(1)
879
+ scale_fct = torch.stack([img_w, img_h, img_w, img_h], dim=1)
880
+ boxes = boxes * scale_fct[:, None, :]
881
+
882
+ if self.nms_iou_threshold > 0:
883
+ item_indices = [nms(b, s, iou_threshold=self.nms_iou_threshold) for b,s in zip(boxes, scores)]
884
+
885
+ results = [{'scores': s[i], 'labels': l[i], 'boxes': b[i]} for s, l, b, i in zip(scores, labels, boxes, item_indices)]
886
+ else:
887
+ results = [{'scores': s, 'labels': l, 'boxes': b} for s, l, b in zip(scores, labels, boxes)]
888
+ results = [{'scores': s, 'labels': l, 'boxes': b} for s, l, b in zip(scores, labels, boxes)]
889
+ return results
890
+
891
+
892
+ @MODULE_BUILD_FUNCS.registe_with_name(module_name="groundingdino")
893
+ def build_groundingdino(args):
894
+ device = torch.device(args.device)
895
+ backbone = build_backbone(args)
896
+ transformer = build_transformer(args)
897
+
898
+ dn_labelbook_size = args.dn_labelbook_size
899
+ dec_pred_bbox_embed_share = args.dec_pred_bbox_embed_share
900
+ sub_sentence_present = args.sub_sentence_present
901
+
902
+ model = GroundingDINO(
903
+ backbone,
904
+ transformer,
905
+ num_queries=args.num_queries,
906
+ aux_loss=args.aux_loss,
907
+ iter_update=True,
908
+ query_dim=4,
909
+ num_feature_levels=args.num_feature_levels,
910
+ nheads=args.nheads,
911
+ dec_pred_bbox_embed_share=dec_pred_bbox_embed_share,
912
+ two_stage_type=args.two_stage_type,
913
+ two_stage_bbox_embed_share=args.two_stage_bbox_embed_share,
914
+ two_stage_class_embed_share=args.two_stage_class_embed_share,
915
+ num_patterns=args.num_patterns,
916
+ dn_number=0,
917
+ dn_box_noise_scale=args.dn_box_noise_scale,
918
+ dn_label_noise_ratio=args.dn_label_noise_ratio,
919
+ dn_labelbook_size=dn_labelbook_size,
920
+ text_encoder_type=args.text_encoder_type,
921
+ sub_sentence_present=sub_sentence_present,
922
+ max_text_len=args.max_text_len,
923
+ )
924
+
925
+
926
+
927
+ matcher = build_matcher(args)
928
+
929
+ # prepare weight dict
930
+ weight_dict = {'loss_ce': args.cls_loss_coef, 'loss_bbox': args.bbox_loss_coef}
931
+ weight_dict['loss_giou'] = args.giou_loss_coef
932
+ clean_weight_dict_wo_dn = copy.deepcopy(weight_dict)
933
+
934
+
935
+
936
+ clean_weight_dict = copy.deepcopy(weight_dict)
937
+
938
+ # TODO this is a hack
939
+ if args.aux_loss:
940
+ aux_weight_dict = {}
941
+ for i in range(args.dec_layers - 1):
942
+ aux_weight_dict.update({k + f'_{i}': v for k, v in clean_weight_dict.items()})
943
+ weight_dict.update(aux_weight_dict)
944
+
945
+ if args.two_stage_type != 'no':
946
+ interm_weight_dict = {}
947
+ try:
948
+ no_interm_box_loss = args.no_interm_box_loss
949
+ except:
950
+ no_interm_box_loss = False
951
+ _coeff_weight_dict = {
952
+ 'loss_ce': 1.0,
953
+ 'loss_bbox': 1.0 if not no_interm_box_loss else 0.0,
954
+ 'loss_giou': 1.0 if not no_interm_box_loss else 0.0,
955
+ }
956
+ try:
957
+ interm_loss_coef = args.interm_loss_coef
958
+ except:
959
+ interm_loss_coef = 1.0
960
+ interm_weight_dict.update({k + f'_interm': v * interm_loss_coef * _coeff_weight_dict[k] for k, v in clean_weight_dict_wo_dn.items()})
961
+ weight_dict.update(interm_weight_dict)
962
+
963
+ # losses = ['labels', 'boxes', 'cardinality']
964
+ losses = ['labels', 'boxes']
965
+
966
+ criterion = SetCriterion(matcher=matcher, weight_dict=weight_dict,
967
+ focal_alpha=args.focal_alpha, focal_gamma=args.focal_gamma,losses=losses
968
+ )
969
+ criterion.to(device)
970
+ postprocessors = {'bbox': PostProcess(num_select=args.num_select , text_encoder_type=args.text_encoder_type,nms_iou_threshold=args.nms_iou_threshold,args=args)}
971
+
972
+ return model, criterion, postprocessors
973
+
974
+ def create_positive_map(tokenized, tokens_positive,cat_list,caption):
975
+ """construct a map such that positive_map[i,j] = True iff box i is associated to token j"""
976
+ positive_map = torch.zeros((len(tokens_positive), 256), dtype=torch.float)
977
+
978
+ for j,label in enumerate(tokens_positive):
979
+
980
+ start_ind = caption.find(cat_list[label])
981
+ end_ind = start_ind + len(cat_list[label]) - 1
982
+ beg_pos = tokenized.char_to_token(start_ind)
983
+ try:
984
+ end_pos = tokenized.char_to_token(end_ind)
985
+ except:
986
+ end_pos = None
987
+ if end_pos is None:
988
+ try:
989
+ end_pos = tokenized.char_to_token(end_ind - 1)
990
+ if end_pos is None:
991
+ end_pos = tokenized.char_to_token(end_ind - 2)
992
+ except:
993
+ end_pos = None
994
+ # except Exception as e:
995
+ # print("beg:", beg, "end:", end)
996
+ # print("token_positive:", tokens_positive)
997
+ # # print("beg_pos:", beg_pos, "end_pos:", end_pos)
998
+ # raise e
999
+ # if beg_pos is None:
1000
+ # try:
1001
+ # beg_pos = tokenized.char_to_token(beg + 1)
1002
+ # if beg_pos is None:
1003
+ # beg_pos = tokenized.char_to_token(beg + 2)
1004
+ # except:
1005
+ # beg_pos = None
1006
+ # if end_pos is None:
1007
+ # try:
1008
+ # end_pos = tokenized.char_to_token(end - 2)
1009
+ # if end_pos is None:
1010
+ # end_pos = tokenized.char_to_token(end - 3)
1011
+ # except:
1012
+ # end_pos = None
1013
+ if beg_pos is None or end_pos is None:
1014
+ continue
1015
+ if beg_pos < 0 or end_pos < 0:
1016
+ continue
1017
+ if beg_pos > end_pos:
1018
+ continue
1019
+ # assert beg_pos is not None and end_pos is not None
1020
+ positive_map[j,beg_pos: end_pos + 1].fill_(1)
1021
+ return positive_map
1022
+
1023
+
1024
+ def create_positive_map_exemplar(input_ids, label, special_tokens):
1025
+ tokens_positive = torch.zeros(256, dtype=torch.float)
1026
+ count = -1
1027
+ for token_ind in range(len(input_ids)):
1028
+ input_id = input_ids[token_ind]
1029
+ if (input_id not in special_tokens) and (token_ind == 0 or (input_ids[token_ind - 1] in special_tokens)):
1030
+ count += 1
1031
+ if count == label:
1032
+ ind_to_insert_ones = token_ind
1033
+
1034
+ while input_ids[ind_to_insert_ones] not in special_tokens:
1035
+ tokens_positive[ind_to_insert_ones] = 1
1036
+ ind_to_insert_ones += 1
1037
+ break
1038
+ return tokens_positive
1039
+
1040
+
1041
+
models/GroundingDINO/groundingdino_app.py ADDED
@@ -0,0 +1,1257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # Grounding DINO
3
+ # url: https://github.com/IDEA-Research/GroundingDINO
4
+ # Copyright (c) 2023 IDEA. All Rights Reserved.
5
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
6
+ # ------------------------------------------------------------------------
7
+ # Conditional DETR model and criterion classes.
8
+ # Copyright (c) 2021 Microsoft. All Rights Reserved.
9
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
10
+ # ------------------------------------------------------------------------
11
+ # Modified from DETR (https://github.com/facebookresearch/detr)
12
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
13
+ # ------------------------------------------------------------------------
14
+ # Modified from Deformable DETR (https://github.com/fundamentalvision/Deformable-DETR)
15
+ # Copyright (c) 2020 SenseTime. All Rights Reserved.
16
+ # ------------------------------------------------------------------------
17
+ import copy
18
+ from typing import List
19
+ import torchvision.transforms.functional as vis_F
20
+ from torchvision.transforms import InterpolationMode
21
+ import torch
22
+ import torch.nn.functional as F
23
+ from torch import nn
24
+ from torchvision.ops.boxes import nms
25
+ from torchvision.ops import roi_align
26
+ from transformers import (
27
+ AutoTokenizer,
28
+ BertModel,
29
+ BertTokenizer,
30
+ RobertaModel,
31
+ RobertaTokenizerFast,
32
+ )
33
+
34
+ from groundingdino.util import box_ops, get_tokenlizer
35
+ from groundingdino.util.misc import (
36
+ NestedTensor,
37
+ accuracy,
38
+ get_world_size,
39
+ interpolate,
40
+ inverse_sigmoid,
41
+ is_dist_avail_and_initialized,
42
+ nested_tensor_from_tensor_list,
43
+ )
44
+ from groundingdino.util.utils import get_phrases_from_posmap
45
+ from groundingdino.util.visualizer import COCOVisualizer
46
+ from groundingdino.util.vl_utils import create_positive_map_from_span
47
+
48
+ from ..registry import MODULE_BUILD_FUNCS
49
+ from .backbone import build_backbone
50
+ from .bertwarper import (
51
+ BertModelWarper,
52
+ generate_masks_with_special_tokens,
53
+ generate_masks_with_special_tokens_and_transfer_map,
54
+ )
55
+ from .transformer import build_transformer
56
+ from .utils import MLP, ContrastiveEmbed, sigmoid_focal_loss
57
+
58
+ from .matcher import build_matcher
59
+ import numpy as np
60
+ import matplotlib.pyplot as plt
61
+ from matplotlib.patches import Rectangle
62
+ from groundingdino.util.visualizer import renorm
63
+
64
+
65
+ def numpy_2_cv2(np_img):
66
+ if np.min(np_img) < 0:
67
+ raise Exception("image min is less than 0. Img min: " + str(np.min(np_img)))
68
+ if np.max(np_img) > 1:
69
+ raise Exception("image max is greater than 1. Img max: " + str(np.max(np_img)))
70
+ np_img = (np_img * 255).astype(np.uint8)
71
+ # Need to somehow ensure image is in RGB format. Note this line shows up in SAM demo: image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
72
+ cv2_image = np.asarray(np_img)
73
+ return cv2_image
74
+
75
+
76
+ def vis_exemps(image, exemp, f_name):
77
+ plt.imshow(image)
78
+ plt.gca().add_patch(
79
+ Rectangle(
80
+ (exemp[0], exemp[1]),
81
+ exemp[2] - exemp[0],
82
+ exemp[3] - exemp[1],
83
+ edgecolor="red",
84
+ facecolor="none",
85
+ lw=1,
86
+ )
87
+ )
88
+ plt.savefig(f_name)
89
+ plt.close()
90
+
91
+
92
+ class GroundingDINO(nn.Module):
93
+ """This is the Cross-Attention Detector module that performs object detection"""
94
+
95
+ def __init__(
96
+ self,
97
+ backbone,
98
+ transformer,
99
+ num_queries,
100
+ aux_loss=False,
101
+ iter_update=False,
102
+ query_dim=2,
103
+ num_feature_levels=1,
104
+ nheads=8,
105
+ # two stage
106
+ two_stage_type="no", # ['no', 'standard']
107
+ dec_pred_bbox_embed_share=True,
108
+ two_stage_class_embed_share=True,
109
+ two_stage_bbox_embed_share=True,
110
+ num_patterns=0,
111
+ dn_number=100,
112
+ dn_box_noise_scale=0.4,
113
+ dn_label_noise_ratio=0.5,
114
+ dn_labelbook_size=100,
115
+ text_encoder_type="bert-base-uncased",
116
+ sub_sentence_present=True,
117
+ max_text_len=256,
118
+ ):
119
+ """Initializes the model.
120
+ Parameters:
121
+ backbone: torch module of the backbone to be used. See backbone.py
122
+ transformer: torch module of the transformer architecture. See transformer.py
123
+ num_queries: number of object queries, ie detection slot. This is the maximal number of objects
124
+ Conditional DETR can detect in a single image. For COCO, we recommend 100 queries.
125
+ aux_loss: True if auxiliary decoding losses (loss at each decoder layer) are to be used.
126
+ """
127
+ super().__init__()
128
+ self.num_queries = num_queries
129
+ self.transformer = transformer
130
+ self.hidden_dim = hidden_dim = transformer.d_model
131
+ self.num_feature_levels = num_feature_levels
132
+ self.nheads = nheads
133
+ self.max_text_len = max_text_len
134
+ self.sub_sentence_present = sub_sentence_present
135
+
136
+ # setting query dim
137
+ self.query_dim = query_dim
138
+ assert query_dim == 4
139
+
140
+ # visual exemplar cropping
141
+ self.feature_map_proj = nn.Conv2d((256 + 512 + 1024), hidden_dim, kernel_size=1)
142
+
143
+ # for dn training
144
+ self.num_patterns = num_patterns
145
+ self.dn_number = dn_number
146
+ self.dn_box_noise_scale = dn_box_noise_scale
147
+ self.dn_label_noise_ratio = dn_label_noise_ratio
148
+ self.dn_labelbook_size = dn_labelbook_size
149
+
150
+ # bert
151
+ self.tokenizer = get_tokenlizer.get_tokenlizer(text_encoder_type)
152
+ self.bert = get_tokenlizer.get_pretrained_language_model(text_encoder_type)
153
+ self.bert.pooler.dense.weight.requires_grad_(False)
154
+ self.bert.pooler.dense.bias.requires_grad_(False)
155
+ self.bert = BertModelWarper(bert_model=self.bert)
156
+
157
+ self.feat_map = nn.Linear(
158
+ self.bert.config.hidden_size, self.hidden_dim, bias=True
159
+ )
160
+ nn.init.constant_(self.feat_map.bias.data, 0)
161
+ nn.init.xavier_uniform_(self.feat_map.weight.data)
162
+ # freeze
163
+
164
+ # special tokens
165
+ self.specical_tokens = self.tokenizer.convert_tokens_to_ids(
166
+ ["[CLS]", "[SEP]", ".", "?"]
167
+ )
168
+
169
+ # prepare input projection layers
170
+ if num_feature_levels > 1:
171
+ num_backbone_outs = len(backbone.num_channels)
172
+ input_proj_list = []
173
+ for _ in range(num_backbone_outs):
174
+ in_channels = backbone.num_channels[_]
175
+ input_proj_list.append(
176
+ nn.Sequential(
177
+ nn.Conv2d(in_channels, hidden_dim, kernel_size=1),
178
+ nn.GroupNorm(32, hidden_dim),
179
+ )
180
+ )
181
+ for _ in range(num_feature_levels - num_backbone_outs):
182
+ input_proj_list.append(
183
+ nn.Sequential(
184
+ nn.Conv2d(
185
+ in_channels, hidden_dim, kernel_size=3, stride=2, padding=1
186
+ ),
187
+ nn.GroupNorm(32, hidden_dim),
188
+ )
189
+ )
190
+ in_channels = hidden_dim
191
+ self.input_proj = nn.ModuleList(input_proj_list)
192
+ else:
193
+ assert (
194
+ two_stage_type == "no"
195
+ ), "two_stage_type should be no if num_feature_levels=1 !!!"
196
+ self.input_proj = nn.ModuleList(
197
+ [
198
+ nn.Sequential(
199
+ nn.Conv2d(backbone.num_channels[-1], hidden_dim, kernel_size=1),
200
+ nn.GroupNorm(32, hidden_dim),
201
+ )
202
+ ]
203
+ )
204
+
205
+ self.backbone = backbone
206
+ self.aux_loss = aux_loss
207
+ self.box_pred_damping = box_pred_damping = None
208
+
209
+ self.iter_update = iter_update
210
+ assert iter_update, "Why not iter_update?"
211
+
212
+ # prepare pred layers
213
+ self.dec_pred_bbox_embed_share = dec_pred_bbox_embed_share
214
+ # prepare class & box embed
215
+ _class_embed = ContrastiveEmbed()
216
+
217
+ _bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3)
218
+ nn.init.constant_(_bbox_embed.layers[-1].weight.data, 0)
219
+ nn.init.constant_(_bbox_embed.layers[-1].bias.data, 0)
220
+
221
+ if dec_pred_bbox_embed_share:
222
+ box_embed_layerlist = [
223
+ _bbox_embed for i in range(transformer.num_decoder_layers)
224
+ ]
225
+ else:
226
+ box_embed_layerlist = [
227
+ copy.deepcopy(_bbox_embed)
228
+ for i in range(transformer.num_decoder_layers)
229
+ ]
230
+ class_embed_layerlist = [
231
+ _class_embed for i in range(transformer.num_decoder_layers)
232
+ ]
233
+ self.bbox_embed = nn.ModuleList(box_embed_layerlist)
234
+ self.class_embed = nn.ModuleList(class_embed_layerlist)
235
+ self.transformer.decoder.bbox_embed = self.bbox_embed
236
+ self.transformer.decoder.class_embed = self.class_embed
237
+
238
+ # two stage
239
+ self.two_stage_type = two_stage_type
240
+ assert two_stage_type in [
241
+ "no",
242
+ "standard",
243
+ ], "unknown param {} of two_stage_type".format(two_stage_type)
244
+ if two_stage_type != "no":
245
+ if two_stage_bbox_embed_share:
246
+ assert dec_pred_bbox_embed_share
247
+ self.transformer.enc_out_bbox_embed = _bbox_embed
248
+ else:
249
+ self.transformer.enc_out_bbox_embed = copy.deepcopy(_bbox_embed)
250
+
251
+ if two_stage_class_embed_share:
252
+ assert dec_pred_bbox_embed_share
253
+ self.transformer.enc_out_class_embed = _class_embed
254
+ else:
255
+ self.transformer.enc_out_class_embed = copy.deepcopy(_class_embed)
256
+
257
+ self.refpoint_embed = None
258
+
259
+ self._reset_parameters()
260
+
261
+ def _reset_parameters(self):
262
+ # init input_proj
263
+ for proj in self.input_proj:
264
+ nn.init.xavier_uniform_(proj[0].weight, gain=1)
265
+ nn.init.constant_(proj[0].bias, 0)
266
+
267
+ def init_ref_points(self, use_num_queries):
268
+ self.refpoint_embed = nn.Embedding(use_num_queries, self.query_dim)
269
+
270
+ def add_exemplar_tokens(self, tokenized, text_dict, exemplar_tokens, labels):
271
+ input_ids = tokenized["input_ids"]
272
+
273
+ device = input_ids.device
274
+ new_input_ids = []
275
+ encoded_text = text_dict["encoded_text"]
276
+ new_encoded_text = []
277
+ text_token_mask = text_dict["text_token_mask"]
278
+ new_text_token_mask = []
279
+ position_ids = text_dict["position_ids"]
280
+ text_self_attention_masks = text_dict["text_self_attention_masks"]
281
+
282
+ for sample_ind in range(len(labels)):
283
+ label = labels[sample_ind][0]
284
+ exemplars = exemplar_tokens[sample_ind]
285
+ label_count = -1
286
+ assert len(input_ids[sample_ind]) == len(position_ids[sample_ind])
287
+ for token_ind in range(len(input_ids[sample_ind])):
288
+ input_id = input_ids[sample_ind][token_ind]
289
+ if (token_ind > 0 and input_ids[sample_ind][token_ind - 1] in self.specical_tokens):
290
+ label_count += 1
291
+ if label_count == label:
292
+ # Get the index where to insert the exemplar tokens.
293
+ ind_to_insert_exemplar = token_ind
294
+ while (
295
+ input_ids[sample_ind][ind_to_insert_exemplar]
296
+ not in self.specical_tokens
297
+ ):
298
+ ind_to_insert_exemplar += 1
299
+ break
300
+
301
+ # Handle no text case.
302
+ if label_count == -1:
303
+ ind_to_insert_exemplar = 1
304
+ # * token indicates exemplar.
305
+ new_input_ids.append(
306
+ torch.cat(
307
+ [
308
+ input_ids[sample_ind][:ind_to_insert_exemplar],
309
+ torch.tensor([1008] * exemplars.shape[0]).to(device),
310
+ input_ids[sample_ind][ind_to_insert_exemplar:],
311
+ ]
312
+ )
313
+ )
314
+ new_encoded_text.append(
315
+ torch.cat(
316
+ [
317
+ encoded_text[sample_ind][:ind_to_insert_exemplar, :],
318
+ exemplars,
319
+ encoded_text[sample_ind][ind_to_insert_exemplar:, :],
320
+ ]
321
+ )
322
+ )
323
+ new_text_token_mask.append(
324
+ torch.full((len(new_input_ids[sample_ind]),), True).to(device)
325
+ )
326
+
327
+ tokenized["input_ids"] = torch.stack(new_input_ids)
328
+
329
+ (
330
+ text_self_attention_masks,
331
+ position_ids,
332
+ _,
333
+ ) = generate_masks_with_special_tokens_and_transfer_map(
334
+ tokenized, self.specical_tokens, None
335
+ )
336
+
337
+ return ({
338
+ "encoded_text": torch.stack(new_encoded_text),
339
+ "text_token_mask": torch.stack(new_text_token_mask),
340
+ "position_ids": position_ids,
341
+ "text_self_attention_masks": text_self_attention_masks,
342
+ }, tokenized)
343
+
344
+ def combine_features(self, features):
345
+ (bs, c, h, w) = (
346
+ features[0].decompose()[0].shape[-4],
347
+ features[0].decompose()[0].shape[-3],
348
+ features[0].decompose()[0].shape[-2],
349
+ features[0].decompose()[0].shape[-1],
350
+ )
351
+
352
+ x = torch.cat(
353
+ [
354
+ F.interpolate(
355
+ feat.decompose()[0],
356
+ size=(h, w),
357
+ mode="bilinear",
358
+ align_corners=True,
359
+ )
360
+ for feat in features
361
+ ],
362
+ dim=1,
363
+ )
364
+
365
+ x = self.feature_map_proj(x)
366
+
367
+ return x
368
+
369
+ def forward(
370
+ self,
371
+ input_images: NestedTensor,
372
+ input_image_pos_exemplars: NestedTensor,
373
+ pos_exemplars: List,
374
+ input_images_neg_exemplars: List[NestedTensor],
375
+ neg_exemplars: List,
376
+ targets: List = None,
377
+ cropped=False,
378
+ orig_img=None,
379
+ crop_width=0,
380
+ crop_height=0,
381
+ **kw,
382
+ ):
383
+ """The forward expects a NestedTensor, which consists of:
384
+ - samples.tensor: batched images, of shape [batch_size x 3 x H x W]
385
+ - samples.mask: a binary mask of shape [batch_size x H x W], containing 1 on padded pixels
386
+ It returns a dict with the following elements:
387
+ - "pred_logits": the classification logits (including no-object) for all queries.
388
+ Shape= [batch_size x num_queries x num_classes]
389
+ - "pred_boxes": The normalized boxes coordinates for all queries, represented as
390
+ (center_x, center_y, width, height). These values are normalized in [0, 1],
391
+ relative to the size of each individual image (disregarding possible padding).
392
+ See PostProcess for information on how to retrieve the unnormalized bounding box.
393
+ - "aux_outputs": Optional, only returned when auxilary losses are activated. It is a list of
394
+ dictionnaries containing the two above keys for each decoder layer.
395
+ """
396
+
397
+ if targets is None:
398
+ captions = kw["captions"]
399
+ else:
400
+ captions = [t["caption"] for t in targets]
401
+
402
+ # encoder texts
403
+
404
+ tokenized = self.tokenizer(captions, padding="longest", return_tensors="pt").to(
405
+ input_images.device
406
+ )
407
+
408
+ one_hot_token = tokenized
409
+
410
+ (
411
+ text_self_attention_masks,
412
+ position_ids,
413
+ cate_to_token_mask_list,
414
+ ) = generate_masks_with_special_tokens_and_transfer_map(
415
+ tokenized, self.specical_tokens, self.tokenizer
416
+ )
417
+
418
+ if text_self_attention_masks.shape[1] > self.max_text_len:
419
+ text_self_attention_masks = text_self_attention_masks[
420
+ :, : self.max_text_len, : self.max_text_len
421
+ ]
422
+ position_ids = position_ids[:, : self.max_text_len]
423
+ tokenized["input_ids"] = tokenized["input_ids"][:, : self.max_text_len]
424
+ tokenized["attention_mask"] = tokenized["attention_mask"][
425
+ :, : self.max_text_len
426
+ ]
427
+ tokenized["token_type_ids"] = tokenized["token_type_ids"][
428
+ :, : self.max_text_len
429
+ ]
430
+
431
+ # extract text embeddings
432
+ if self.sub_sentence_present:
433
+ tokenized_for_encoder = {
434
+ k: v for k, v in tokenized.items() if k != "attention_mask"
435
+ }
436
+ tokenized_for_encoder["attention_mask"] = text_self_attention_masks
437
+ tokenized_for_encoder["position_ids"] = position_ids
438
+ else:
439
+ tokenized_for_encoder = tokenized
440
+
441
+ bert_output = self.bert(**tokenized_for_encoder) # bs, 195, 768
442
+
443
+ encoded_text = self.feat_map(
444
+ bert_output["last_hidden_state"]
445
+ ) # bs, 195, d_model
446
+ text_token_mask = tokenized.attention_mask.bool() # bs, 195
447
+ # text_token_mask: True for nomask, False for mask
448
+ # text_self_attention_masks: True for nomask, False for mask
449
+
450
+ if encoded_text.shape[1] > self.max_text_len:
451
+ encoded_text = encoded_text[:, : self.max_text_len, :]
452
+ text_token_mask = text_token_mask[:, : self.max_text_len]
453
+ position_ids = position_ids[:, : self.max_text_len]
454
+ text_self_attention_masks = text_self_attention_masks[
455
+ :, : self.max_text_len, : self.max_text_len
456
+ ]
457
+
458
+ text_dict = {
459
+ "encoded_text": encoded_text, # bs, 195, d_model
460
+ "text_token_mask": text_token_mask, # bs, 195
461
+ "position_ids": position_ids, # bs, 195
462
+ "text_self_attention_masks": text_self_attention_masks, # bs, 195,195
463
+ }
464
+
465
+ if isinstance(input_images, (list, torch.Tensor)):
466
+ input_images = nested_tensor_from_tensor_list(input_images)
467
+
468
+ features, poss = self.backbone(input_images)
469
+
470
+ # Positive exemplars
471
+ features_pos_exemp, _ = self.backbone(input_image_pos_exemplars)
472
+ combined_pos_features = self.combine_features(features_pos_exemp)
473
+ # Get positive visual exemplar tokens.
474
+ bs = len(pos_exemplars)
475
+ num_pos_exemplars = pos_exemplars[0].shape[0]
476
+ if num_pos_exemplars > 0:
477
+ pos_exemplar_tokens = (
478
+ roi_align(
479
+ combined_pos_features,
480
+ boxes=pos_exemplars,
481
+ output_size=(1, 1),
482
+ spatial_scale=(1 / 8),
483
+ aligned=True,
484
+ )
485
+ .squeeze(-1)
486
+ .squeeze(-1)
487
+ .reshape(bs, num_pos_exemplars, -1)
488
+ )
489
+ else:
490
+ pos_exemplar_tokens = None
491
+
492
+ if pos_exemplar_tokens is not None:
493
+ text_dict, tokenized = self.add_exemplar_tokens(
494
+ tokenized, text_dict, pos_exemplar_tokens, [torch.tensor([0]).to(input_images.device)]
495
+ )
496
+
497
+ # Negative exemplars
498
+ num_neg_exemplar_imgs = len(input_images_neg_exemplars)
499
+ for ind in range(num_neg_exemplar_imgs):
500
+ exemplar_img = input_images_neg_exemplars[ind]
501
+ exemplars = neg_exemplars[ind]
502
+ bs = 1
503
+ num_neg_exemplars = len(exemplars[0]) # Each set of exemplars has a batch dimension for compatibility with roialign
504
+ if exemplar_img is not None and num_neg_exemplars > 0:
505
+ features_neg_exemp, _ = self.backbone(exemplar_img)
506
+ combined_neg_features = self.combine_features(features_neg_exemp)
507
+ # Get negative visual exemplar tokens.
508
+ neg_exemplar_tokens = (
509
+ roi_align(
510
+ combined_neg_features,
511
+ boxes=exemplars,
512
+ output_size=(1, 1),
513
+ spatial_scale=(1 / 8),
514
+ aligned=True,
515
+ )
516
+ .squeeze(-1)
517
+ .squeeze(-1)
518
+ .reshape(bs, num_neg_exemplars, -1)
519
+ )
520
+ text_dict, tokenized = self.add_exemplar_tokens(
521
+ tokenized, text_dict, neg_exemplar_tokens, [torch.tensor([ind + 1]).to(input_images.device)]
522
+ )
523
+
524
+ srcs = []
525
+ masks = []
526
+ for l, feat in enumerate(features):
527
+ src, mask = feat.decompose()
528
+ srcs.append(self.input_proj[l](src))
529
+ masks.append(mask)
530
+ assert mask is not None
531
+ if self.num_feature_levels > len(srcs):
532
+ _len_srcs = len(srcs)
533
+ for l in range(_len_srcs, self.num_feature_levels):
534
+ if l == _len_srcs:
535
+ src = self.input_proj[l](features[-1].tensors)
536
+ else:
537
+ src = self.input_proj[l](srcs[-1])
538
+ m = input_images.mask
539
+ mask = F.interpolate(m[None].float(), size=src.shape[-2:]).to(
540
+ torch.bool
541
+ )[0]
542
+ pos_l = self.backbone[1](NestedTensor(src, mask)).to(src.dtype)
543
+ srcs.append(src)
544
+ masks.append(mask)
545
+ poss.append(pos_l)
546
+
547
+ input_query_bbox = input_query_label = attn_mask = dn_meta = None
548
+ hs, reference, hs_enc, ref_enc, init_box_proposal = self.transformer(
549
+ srcs, masks, input_query_bbox, poss, input_query_label, attn_mask, text_dict
550
+ )
551
+
552
+ # deformable-detr-like anchor update
553
+ outputs_coord_list = []
554
+ for dec_lid, (layer_ref_sig, layer_bbox_embed, layer_hs) in enumerate(
555
+ zip(reference[:-1], self.bbox_embed, hs)
556
+ ):
557
+ layer_delta_unsig = layer_bbox_embed(layer_hs)
558
+ layer_outputs_unsig = layer_delta_unsig + inverse_sigmoid(layer_ref_sig)
559
+ layer_outputs_unsig = layer_outputs_unsig.sigmoid()
560
+ outputs_coord_list.append(layer_outputs_unsig)
561
+ outputs_coord_list = torch.stack(outputs_coord_list)
562
+
563
+ outputs_class = torch.stack(
564
+ [
565
+ layer_cls_embed(layer_hs, text_dict)
566
+ for layer_cls_embed, layer_hs in zip(self.class_embed, hs)
567
+ ]
568
+ )
569
+
570
+ out = {"pred_logits": outputs_class[-1], "pred_boxes": outputs_coord_list[-1]}
571
+
572
+ # Used to calculate losses
573
+ bs, len_td = text_dict["text_token_mask"].shape
574
+ out["text_mask"] = torch.zeros(bs, self.max_text_len, dtype=torch.bool).to(
575
+ input_images.device
576
+ )
577
+ for b in range(bs):
578
+ for j in range(len_td):
579
+ if text_dict["text_token_mask"][b][j] == True:
580
+ out["text_mask"][b][j] = True
581
+
582
+ # for intermediate outputs
583
+ if self.aux_loss:
584
+ out["aux_outputs"] = self._set_aux_loss(outputs_class, outputs_coord_list)
585
+ out["token"] = one_hot_token
586
+ # # for encoder output
587
+ if hs_enc is not None:
588
+ # prepare intermediate outputs
589
+ interm_coord = ref_enc[-1]
590
+ interm_class = self.transformer.enc_out_class_embed(hs_enc[-1], text_dict)
591
+ out["interm_outputs"] = {
592
+ "pred_logits": interm_class,
593
+ "pred_boxes": interm_coord,
594
+ }
595
+ out["interm_outputs_for_matching_pre"] = {
596
+ "pred_logits": interm_class,
597
+ "pred_boxes": init_box_proposal,
598
+ }
599
+
600
+ out["input_ids"] = tokenized["input_ids"]
601
+
602
+ # outputs['pred_logits'].shape
603
+ # torch.Size([4, 900, 256])
604
+
605
+ # outputs['pred_boxes'].shape
606
+ # torch.Size([4, 900, 4])
607
+
608
+ # outputs['text_mask'].shape
609
+ # torch.Size([256])
610
+
611
+ # outputs['text_mask']
612
+
613
+ # outputs['aux_outputs'][0].keys()
614
+ # dict_keys(['pred_logits', 'pred_boxes', 'one_hot', 'text_mask'])
615
+
616
+ # outputs['aux_outputs'][img_idx]
617
+
618
+ # outputs['token']
619
+ # <class 'transformers.tokenization_utils_base.BatchEncoding'>
620
+
621
+ # outputs['interm_outputs'].keys()
622
+ # dict_keys(['pred_logits', 'pred_boxes', 'one_hot', 'text_mask'])
623
+
624
+ # outputs['interm_outputs_for_matching_pre'].keys()
625
+ # dict_keys(['pred_logits', 'pred_boxes'])
626
+
627
+ # outputs['one_hot'].shape
628
+ # torch.Size([4, 900, 256])
629
+
630
+ return out
631
+
632
+ @torch.jit.unused
633
+ def _set_aux_loss(self, outputs_class, outputs_coord):
634
+ # this is a workaround to make torchscript happy, as torchscript
635
+ # doesn't support dictionary with non-homogeneous values, such
636
+ # as a dict having both a Tensor and a list.
637
+ return [
638
+ {"pred_logits": a, "pred_boxes": b}
639
+ for a, b in zip(outputs_class[:-1], outputs_coord[:-1])
640
+ ]
641
+
642
+
643
+ class SetCriterion(nn.Module):
644
+ def __init__(self, matcher, weight_dict, focal_alpha, focal_gamma, losses):
645
+ """Create the criterion.
646
+ Parameters:
647
+ matcher: module able to compute a matching between targets and proposals
648
+ weight_dict: dict containing as key the names of the losses and as values their relative weight.
649
+ losses: list of all the losses to be applied. See get_loss for list of available losses.
650
+ focal_alpha: alpha in Focal Loss
651
+ """
652
+ super().__init__()
653
+ self.matcher = matcher
654
+ self.weight_dict = weight_dict
655
+ self.losses = losses
656
+ self.focal_alpha = focal_alpha
657
+ self.focal_gamma = focal_gamma
658
+
659
+ @torch.no_grad()
660
+ def loss_cardinality(self, outputs, targets, indices, num_boxes):
661
+ """Compute the cardinality error, ie the absolute error in the number of predicted non-empty boxes
662
+ This is not really a loss, it is intended for logging purposes only. It doesn't propagate gradients
663
+ """
664
+
665
+ pred_logits = outputs["pred_logits"]
666
+ device = pred_logits.device
667
+ tgt_lengths = torch.as_tensor(
668
+ [len(v["labels"]) for v in targets], device=device
669
+ )
670
+ # Count the number of predictions that are NOT "no-object" (which is the last class)
671
+ card_pred = (pred_logits.argmax(-1) != pred_logits.shape[-1] - 1).sum(1)
672
+ card_err = F.l1_loss(card_pred.float(), tgt_lengths.float())
673
+ losses = {"cardinality_error": card_err}
674
+ return losses
675
+
676
+ def loss_boxes(self, outputs, targets, indices, num_boxes):
677
+ """Compute the losses related to the bounding boxes, the L1 regression loss and the GIoU loss
678
+ targets dicts must contain the key "boxes" containing a tensor of dim [nb_target_boxes, 4]
679
+ The target boxes are expected in format (center_x, center_y, w, h), normalized by the image size.
680
+ """
681
+ assert "pred_boxes" in outputs
682
+ idx = self._get_src_permutation_idx(indices)
683
+ src_boxes = outputs["pred_boxes"][idx]
684
+ target_boxes = torch.cat(
685
+ [t["boxes"][i] for t, (_, i) in zip(targets, indices)], dim=0
686
+ )
687
+
688
+ loss_bbox = F.l1_loss(src_boxes[:, :2], target_boxes[:, :2], reduction="none")
689
+
690
+ losses = {}
691
+ losses["loss_bbox"] = loss_bbox.sum() / num_boxes
692
+
693
+ loss_giou = 1 - torch.diag(
694
+ box_ops.generalized_box_iou(
695
+ box_ops.box_cxcywh_to_xyxy(src_boxes),
696
+ box_ops.box_cxcywh_to_xyxy(target_boxes),
697
+ )
698
+ )
699
+ losses["loss_giou"] = loss_giou.sum() / num_boxes
700
+
701
+ # calculate the x,y and h,w loss
702
+ with torch.no_grad():
703
+ losses["loss_xy"] = loss_bbox[..., :2].sum() / num_boxes
704
+ losses["loss_hw"] = loss_bbox[..., 2:].sum() / num_boxes
705
+
706
+ return losses
707
+
708
+ def token_sigmoid_binary_focal_loss(self, outputs, targets, indices, num_boxes):
709
+ pred_logits = outputs["pred_logits"]
710
+ new_targets = outputs["one_hot"].to(pred_logits.device)
711
+ text_mask = outputs["text_mask"]
712
+
713
+ assert new_targets.dim() == 3
714
+ assert pred_logits.dim() == 3 # batch x from x to
715
+
716
+ bs, n, _ = pred_logits.shape
717
+ alpha = self.focal_alpha
718
+ gamma = self.focal_gamma
719
+ if text_mask is not None:
720
+ # ODVG: each sample has different mask
721
+ text_mask = text_mask.repeat(1, pred_logits.size(1)).view(
722
+ outputs["text_mask"].shape[0], -1, outputs["text_mask"].shape[1]
723
+ )
724
+ pred_logits = torch.masked_select(pred_logits, text_mask)
725
+ new_targets = torch.masked_select(new_targets, text_mask)
726
+
727
+ new_targets = new_targets.float()
728
+ p = torch.sigmoid(pred_logits)
729
+ ce_loss = F.binary_cross_entropy_with_logits(
730
+ pred_logits, new_targets, reduction="none"
731
+ )
732
+ p_t = p * new_targets + (1 - p) * (1 - new_targets)
733
+ loss = ce_loss * ((1 - p_t) ** gamma)
734
+
735
+ if alpha >= 0:
736
+ alpha_t = alpha * new_targets + (1 - alpha) * (1 - new_targets)
737
+ loss = alpha_t * loss
738
+
739
+ total_num_pos = 0
740
+ for batch_indices in indices:
741
+ total_num_pos += len(batch_indices[0])
742
+ num_pos_avg_per_gpu = max(total_num_pos, 1.0)
743
+ loss = loss.sum() / num_pos_avg_per_gpu
744
+
745
+ losses = {"loss_ce": loss}
746
+ return losses
747
+
748
+ def _get_src_permutation_idx(self, indices):
749
+ # permute predictions following indices
750
+ batch_idx = torch.cat(
751
+ [torch.full_like(src, i) for i, (src, _) in enumerate(indices)]
752
+ )
753
+ src_idx = torch.cat([src for (src, _) in indices])
754
+ return batch_idx, src_idx
755
+
756
+ def _get_tgt_permutation_idx(self, indices):
757
+ # permute targets following indices
758
+ batch_idx = torch.cat(
759
+ [torch.full_like(tgt, i) for i, (_, tgt) in enumerate(indices)]
760
+ )
761
+ tgt_idx = torch.cat([tgt for (_, tgt) in indices])
762
+ return batch_idx, tgt_idx
763
+
764
+ def get_loss(self, loss, outputs, targets, indices, num_boxes, **kwargs):
765
+ loss_map = {
766
+ "labels": self.token_sigmoid_binary_focal_loss,
767
+ "cardinality": self.loss_cardinality,
768
+ "boxes": self.loss_boxes,
769
+ }
770
+ assert loss in loss_map, f"do you really want to compute {loss} loss?"
771
+ return loss_map[loss](outputs, targets, indices, num_boxes, **kwargs)
772
+
773
+ def forward(self, outputs, targets, cat_list, caption, return_indices=False):
774
+ """This performs the loss computation.
775
+ Parameters:
776
+ outputs: dict of tensors, see the output specification of the model for the format
777
+ targets: list of dicts, such that len(targets) == batch_size.
778
+ The expected keys in each dict depends on the losses applied, see each loss' doc
779
+ return_indices: used for vis. if True, the layer0-5 indices will be returned as well.
780
+ """
781
+ device = next(iter(outputs.values())).device
782
+ one_hot = torch.zeros(
783
+ outputs["pred_logits"].size(), dtype=torch.int64
784
+ ) # torch.Size([bs, 900, 256])
785
+ token = outputs["token"]
786
+
787
+ label_map_list = []
788
+ indices = []
789
+ for j in range(len(cat_list)): # bs
790
+ label_map = []
791
+ for i in range(len(cat_list[j])):
792
+ label_id = torch.tensor([i])
793
+ per_label = create_positive_map_exemplar(
794
+ token["input_ids"][j], label_id, [101, 102, 1012, 1029]
795
+ )
796
+ label_map.append(per_label)
797
+ label_map = torch.stack(label_map, dim=0).squeeze(1)
798
+
799
+ label_map_list.append(label_map)
800
+ for j in range(len(cat_list)): # bs
801
+ for_match = {
802
+ "pred_logits": outputs["pred_logits"][j].unsqueeze(0),
803
+ "pred_boxes": outputs["pred_boxes"][j].unsqueeze(0),
804
+ }
805
+
806
+ inds = self.matcher(for_match, [targets[j]], label_map_list[j])
807
+ indices.extend(inds)
808
+ # indices : A list of size batch_size, containing tuples of (index_i, index_j) where:
809
+ # - index_i is the indices of the selected predictions (in order)
810
+ # - index_j is the indices of the corresponding selected targets (in order)
811
+
812
+ # import pdb; pdb.set_trace()
813
+ tgt_ids = [v["labels"].cpu() for v in targets]
814
+ # len(tgt_ids) == bs
815
+ for i in range(len(indices)):
816
+ tgt_ids[i] = tgt_ids[i][indices[i][1]]
817
+ one_hot[i, indices[i][0]] = label_map_list[i][tgt_ids[i]].to(torch.long)
818
+ outputs["one_hot"] = one_hot
819
+ if return_indices:
820
+ indices0_copy = indices
821
+ indices_list = []
822
+
823
+ # Compute the average number of target boxes accross all nodes, for normalization purposes
824
+ num_boxes_list = [len(t["labels"]) for t in targets]
825
+ num_boxes = sum(num_boxes_list)
826
+ num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=device)
827
+ if is_dist_avail_and_initialized():
828
+ torch.distributed.all_reduce(num_boxes)
829
+ num_boxes = torch.clamp(num_boxes / get_world_size(), min=1).item()
830
+
831
+ # Compute all the requested losses
832
+ losses = {}
833
+ for loss in self.losses:
834
+ losses.update(self.get_loss(loss, outputs, targets, indices, num_boxes))
835
+
836
+ # In case of auxiliary losses, we repeat this process with the output of each intermediate layer.
837
+ if "aux_outputs" in outputs:
838
+ for idx, aux_outputs in enumerate(outputs["aux_outputs"]):
839
+ indices = []
840
+ for j in range(len(cat_list)): # bs
841
+ aux_output_single = {
842
+ "pred_logits": aux_outputs["pred_logits"][j].unsqueeze(0),
843
+ "pred_boxes": aux_outputs["pred_boxes"][j].unsqueeze(0),
844
+ }
845
+ inds = self.matcher(
846
+ aux_output_single, [targets[j]], label_map_list[j]
847
+ )
848
+ indices.extend(inds)
849
+ one_hot_aux = torch.zeros(
850
+ outputs["pred_logits"].size(), dtype=torch.int64
851
+ )
852
+ tgt_ids = [v["labels"].cpu() for v in targets]
853
+ for i in range(len(indices)):
854
+ tgt_ids[i] = tgt_ids[i][indices[i][1]]
855
+ one_hot_aux[i, indices[i][0]] = label_map_list[i][tgt_ids[i]].to(
856
+ torch.long
857
+ )
858
+ aux_outputs["one_hot"] = one_hot_aux
859
+ aux_outputs["text_mask"] = outputs["text_mask"]
860
+ if return_indices:
861
+ indices_list.append(indices)
862
+ for loss in self.losses:
863
+ kwargs = {}
864
+ l_dict = self.get_loss(
865
+ loss, aux_outputs, targets, indices, num_boxes, **kwargs
866
+ )
867
+ l_dict = {k + f"_{idx}": v for k, v in l_dict.items()}
868
+ losses.update(l_dict)
869
+
870
+ # interm_outputs loss
871
+ if "interm_outputs" in outputs:
872
+ interm_outputs = outputs["interm_outputs"]
873
+ indices = []
874
+ for j in range(len(cat_list)): # bs
875
+ interm_output_single = {
876
+ "pred_logits": interm_outputs["pred_logits"][j].unsqueeze(0),
877
+ "pred_boxes": interm_outputs["pred_boxes"][j].unsqueeze(0),
878
+ }
879
+ inds = self.matcher(
880
+ interm_output_single, [targets[j]], label_map_list[j]
881
+ )
882
+ indices.extend(inds)
883
+ one_hot_aux = torch.zeros(outputs["pred_logits"].size(), dtype=torch.int64)
884
+ tgt_ids = [v["labels"].cpu() for v in targets]
885
+ for i in range(len(indices)):
886
+ tgt_ids[i] = tgt_ids[i][indices[i][1]]
887
+ one_hot_aux[i, indices[i][0]] = label_map_list[i][tgt_ids[i]].to(
888
+ torch.long
889
+ )
890
+ interm_outputs["one_hot"] = one_hot_aux
891
+ interm_outputs["text_mask"] = outputs["text_mask"]
892
+ if return_indices:
893
+ indices_list.append(indices)
894
+ for loss in self.losses:
895
+ kwargs = {}
896
+ l_dict = self.get_loss(
897
+ loss, interm_outputs, targets, indices, num_boxes, **kwargs
898
+ )
899
+ l_dict = {k + f"_interm": v for k, v in l_dict.items()}
900
+ losses.update(l_dict)
901
+
902
+ if return_indices:
903
+ indices_list.append(indices0_copy)
904
+ return losses, indices_list
905
+
906
+ return losses
907
+
908
+
909
+ class PostProcess(nn.Module):
910
+ """This module converts the model's output into the format expected by the coco api"""
911
+
912
+ def __init__(
913
+ self,
914
+ num_select=100,
915
+ text_encoder_type="text_encoder_type",
916
+ nms_iou_threshold=-1,
917
+ use_coco_eval=False,
918
+ args=None,
919
+ ) -> None:
920
+ super().__init__()
921
+ self.num_select = num_select
922
+ self.tokenizer = get_tokenlizer.get_tokenlizer(text_encoder_type)
923
+ if args.use_coco_eval:
924
+ from pycocotools.coco import COCO
925
+
926
+ coco = COCO(args.coco_val_path)
927
+ category_dict = coco.loadCats(coco.getCatIds())
928
+ cat_list = [item["name"] for item in category_dict]
929
+ else:
930
+ cat_list = args.label_list
931
+ caption = " . ".join(cat_list) + " ."
932
+ tokenized = self.tokenizer(caption, padding="longest", return_tensors="pt")
933
+ label_list = torch.arange(len(cat_list))
934
+ pos_map = create_positive_map(tokenized, label_list, cat_list, caption)
935
+ # build a mapping from label_id to pos_map
936
+ if args.use_coco_eval:
937
+ id_map = {
938
+ 0: 1,
939
+ 1: 2,
940
+ 2: 3,
941
+ 3: 4,
942
+ 4: 5,
943
+ 5: 6,
944
+ 6: 7,
945
+ 7: 8,
946
+ 8: 9,
947
+ 9: 10,
948
+ 10: 11,
949
+ 11: 13,
950
+ 12: 14,
951
+ 13: 15,
952
+ 14: 16,
953
+ 15: 17,
954
+ 16: 18,
955
+ 17: 19,
956
+ 18: 20,
957
+ 19: 21,
958
+ 20: 22,
959
+ 21: 23,
960
+ 22: 24,
961
+ 23: 25,
962
+ 24: 27,
963
+ 25: 28,
964
+ 26: 31,
965
+ 27: 32,
966
+ 28: 33,
967
+ 29: 34,
968
+ 30: 35,
969
+ 31: 36,
970
+ 32: 37,
971
+ 33: 38,
972
+ 34: 39,
973
+ 35: 40,
974
+ 36: 41,
975
+ 37: 42,
976
+ 38: 43,
977
+ 39: 44,
978
+ 40: 46,
979
+ 41: 47,
980
+ 42: 48,
981
+ 43: 49,
982
+ 44: 50,
983
+ 45: 51,
984
+ 46: 52,
985
+ 47: 53,
986
+ 48: 54,
987
+ 49: 55,
988
+ 50: 56,
989
+ 51: 57,
990
+ 52: 58,
991
+ 53: 59,
992
+ 54: 60,
993
+ 55: 61,
994
+ 56: 62,
995
+ 57: 63,
996
+ 58: 64,
997
+ 59: 65,
998
+ 60: 67,
999
+ 61: 70,
1000
+ 62: 72,
1001
+ 63: 73,
1002
+ 64: 74,
1003
+ 65: 75,
1004
+ 66: 76,
1005
+ 67: 77,
1006
+ 68: 78,
1007
+ 69: 79,
1008
+ 70: 80,
1009
+ 71: 81,
1010
+ 72: 82,
1011
+ 73: 84,
1012
+ 74: 85,
1013
+ 75: 86,
1014
+ 76: 87,
1015
+ 77: 88,
1016
+ 78: 89,
1017
+ 79: 90,
1018
+ }
1019
+ new_pos_map = torch.zeros((91, 256))
1020
+ for k, v in id_map.items():
1021
+ new_pos_map[v] = pos_map[k]
1022
+ pos_map = new_pos_map
1023
+
1024
+ self.nms_iou_threshold = nms_iou_threshold
1025
+ self.positive_map = pos_map
1026
+
1027
+ @torch.no_grad()
1028
+ def forward(self, outputs, target_sizes, not_to_xyxy=False, test=False):
1029
+ """Perform the computation
1030
+ Parameters:
1031
+ outputs: raw outputs of the model
1032
+ target_sizes: tensor of dimension [batch_size x 2] containing the size of each images of the batch
1033
+ For evaluation, this must be the original image size (before any data augmentation)
1034
+ For visualization, this should be the image size after data augment, but before padding
1035
+ """
1036
+ num_select = self.num_select
1037
+ out_logits, out_bbox = outputs["pred_logits"], outputs["pred_boxes"]
1038
+
1039
+ prob_to_token = out_logits.sigmoid()
1040
+ pos_maps = self.positive_map.to(prob_to_token.device)
1041
+ for label_ind in range(len(pos_maps)):
1042
+ if pos_maps[label_ind].sum() != 0:
1043
+ pos_maps[label_ind] = pos_maps[label_ind] / pos_maps[label_ind].sum()
1044
+
1045
+ prob_to_label = prob_to_token @ pos_maps.T
1046
+
1047
+ assert len(out_logits) == len(target_sizes)
1048
+ assert target_sizes.shape[1] == 2
1049
+
1050
+ prob = prob_to_label
1051
+ topk_values, topk_indexes = torch.topk(
1052
+ prob.view(prob.shape[0], -1), num_select, dim=1
1053
+ )
1054
+ scores = topk_values
1055
+ topk_boxes = torch.div(topk_indexes, prob.shape[2], rounding_mode="trunc")
1056
+ labels = topk_indexes % prob.shape[2]
1057
+ if not_to_xyxy:
1058
+ boxes = out_bbox
1059
+ else:
1060
+ boxes = box_ops.box_cxcywh_to_xyxy(out_bbox)
1061
+
1062
+ # if test:
1063
+ # assert not not_to_xyxy
1064
+ # boxes[:,:,2:] = boxes[:,:,2:] - boxes[:,:,:2]
1065
+ boxes = torch.gather(boxes, 1, topk_boxes.unsqueeze(-1).repeat(1, 1, 4))
1066
+
1067
+ # and from relative [0, 1] to absolute [0, height] coordinates
1068
+ img_h, img_w = target_sizes.unbind(1)
1069
+ scale_fct = torch.stack([img_w, img_h, img_w, img_h], dim=1)
1070
+ boxes = boxes * scale_fct[:, None, :]
1071
+
1072
+ if self.nms_iou_threshold > 0:
1073
+ item_indices = [
1074
+ nms(b, s, iou_threshold=self.nms_iou_threshold)
1075
+ for b, s in zip(boxes, scores)
1076
+ ]
1077
+
1078
+ results = [
1079
+ {"scores": s[i], "labels": l[i], "boxes": b[i]}
1080
+ for s, l, b, i in zip(scores, labels, boxes, item_indices)
1081
+ ]
1082
+ else:
1083
+ results = [
1084
+ {"scores": s, "labels": l, "boxes": b}
1085
+ for s, l, b in zip(scores, labels, boxes)
1086
+ ]
1087
+ results = [
1088
+ {"scores": s, "labels": l, "boxes": b}
1089
+ for s, l, b in zip(scores, labels, boxes)
1090
+ ]
1091
+ return results
1092
+
1093
+
1094
+ @MODULE_BUILD_FUNCS.registe_with_name(module_name="groundingdino_app")
1095
+ def build_groundingdino(args):
1096
+ device = torch.device(args.device)
1097
+ backbone = build_backbone(args)
1098
+ transformer = build_transformer(args)
1099
+
1100
+ dn_labelbook_size = args.dn_labelbook_size
1101
+ dec_pred_bbox_embed_share = args.dec_pred_bbox_embed_share
1102
+ sub_sentence_present = args.sub_sentence_present
1103
+
1104
+ model = GroundingDINO(
1105
+ backbone,
1106
+ transformer,
1107
+ num_queries=args.num_queries,
1108
+ aux_loss=args.aux_loss,
1109
+ iter_update=True,
1110
+ query_dim=4,
1111
+ num_feature_levels=args.num_feature_levels,
1112
+ nheads=args.nheads,
1113
+ dec_pred_bbox_embed_share=dec_pred_bbox_embed_share,
1114
+ two_stage_type=args.two_stage_type,
1115
+ two_stage_bbox_embed_share=args.two_stage_bbox_embed_share,
1116
+ two_stage_class_embed_share=args.two_stage_class_embed_share,
1117
+ num_patterns=args.num_patterns,
1118
+ dn_number=0,
1119
+ dn_box_noise_scale=args.dn_box_noise_scale,
1120
+ dn_label_noise_ratio=args.dn_label_noise_ratio,
1121
+ dn_labelbook_size=dn_labelbook_size,
1122
+ text_encoder_type=args.text_encoder_type,
1123
+ sub_sentence_present=sub_sentence_present,
1124
+ max_text_len=args.max_text_len,
1125
+ )
1126
+
1127
+ matcher = build_matcher(args)
1128
+
1129
+ # prepare weight dict
1130
+ weight_dict = {"loss_ce": args.cls_loss_coef, "loss_bbox": args.bbox_loss_coef}
1131
+ weight_dict["loss_giou"] = args.giou_loss_coef
1132
+ clean_weight_dict_wo_dn = copy.deepcopy(weight_dict)
1133
+
1134
+ clean_weight_dict = copy.deepcopy(weight_dict)
1135
+
1136
+ # TODO this is a hack
1137
+ if args.aux_loss:
1138
+ aux_weight_dict = {}
1139
+ for i in range(args.dec_layers - 1):
1140
+ aux_weight_dict.update(
1141
+ {k + f"_{i}": v for k, v in clean_weight_dict.items()}
1142
+ )
1143
+ weight_dict.update(aux_weight_dict)
1144
+
1145
+ if args.two_stage_type != "no":
1146
+ interm_weight_dict = {}
1147
+ try:
1148
+ no_interm_box_loss = args.no_interm_box_loss
1149
+ except:
1150
+ no_interm_box_loss = False
1151
+ _coeff_weight_dict = {
1152
+ "loss_ce": 1.0,
1153
+ "loss_bbox": 1.0 if not no_interm_box_loss else 0.0,
1154
+ "loss_giou": 1.0 if not no_interm_box_loss else 0.0,
1155
+ }
1156
+ try:
1157
+ interm_loss_coef = args.interm_loss_coef
1158
+ except:
1159
+ interm_loss_coef = 1.0
1160
+ interm_weight_dict.update(
1161
+ {
1162
+ k + f"_interm": v * interm_loss_coef * _coeff_weight_dict[k]
1163
+ for k, v in clean_weight_dict_wo_dn.items()
1164
+ }
1165
+ )
1166
+ weight_dict.update(interm_weight_dict)
1167
+
1168
+ # losses = ['labels', 'boxes', 'cardinality']
1169
+ losses = ["labels", "boxes"]
1170
+
1171
+ criterion = SetCriterion(
1172
+ matcher=matcher,
1173
+ weight_dict=weight_dict,
1174
+ focal_alpha=args.focal_alpha,
1175
+ focal_gamma=args.focal_gamma,
1176
+ losses=losses,
1177
+ )
1178
+ criterion.to(device)
1179
+ postprocessors = {
1180
+ "bbox": PostProcess(
1181
+ num_select=args.num_select,
1182
+ text_encoder_type=args.text_encoder_type,
1183
+ nms_iou_threshold=args.nms_iou_threshold,
1184
+ args=args,
1185
+ )
1186
+ }
1187
+
1188
+ return model, criterion, postprocessors
1189
+
1190
+
1191
+ def create_positive_map(tokenized, tokens_positive, cat_list, caption):
1192
+ """construct a map such that positive_map[i,j] = True iff box i is associated to token j"""
1193
+ positive_map = torch.zeros((len(tokens_positive), 256), dtype=torch.float)
1194
+
1195
+ for j, label in enumerate(tokens_positive):
1196
+ start_ind = caption.find(cat_list[label])
1197
+ end_ind = start_ind + len(cat_list[label]) - 1
1198
+ beg_pos = tokenized.char_to_token(start_ind)
1199
+ try:
1200
+ end_pos = tokenized.char_to_token(end_ind)
1201
+ except:
1202
+ end_pos = None
1203
+ if end_pos is None:
1204
+ try:
1205
+ end_pos = tokenized.char_to_token(end_ind - 1)
1206
+ if end_pos is None:
1207
+ end_pos = tokenized.char_to_token(end_ind - 2)
1208
+ except:
1209
+ end_pos = None
1210
+ # except Exception as e:
1211
+ # print("beg:", beg, "end:", end)
1212
+ # print("token_positive:", tokens_positive)
1213
+ # # print("beg_pos:", beg_pos, "end_pos:", end_pos)
1214
+ # raise e
1215
+ # if beg_pos is None:
1216
+ # try:
1217
+ # beg_pos = tokenized.char_to_token(beg + 1)
1218
+ # if beg_pos is None:
1219
+ # beg_pos = tokenized.char_to_token(beg + 2)
1220
+ # except:
1221
+ # beg_pos = None
1222
+ # if end_pos is None:
1223
+ # try:
1224
+ # end_pos = tokenized.char_to_token(end - 2)
1225
+ # if end_pos is None:
1226
+ # end_pos = tokenized.char_to_token(end - 3)
1227
+ # except:
1228
+ # end_pos = None
1229
+ if beg_pos is None or end_pos is None:
1230
+ continue
1231
+ if beg_pos < 0 or end_pos < 0:
1232
+ continue
1233
+ if beg_pos > end_pos:
1234
+ continue
1235
+ # assert beg_pos is not None and end_pos is not None
1236
+ positive_map[j, beg_pos : end_pos + 1].fill_(1)
1237
+ return positive_map
1238
+
1239
+
1240
+ def create_positive_map_exemplar(input_ids, label, special_tokens):
1241
+ tokens_positive = torch.zeros(256, dtype=torch.float)
1242
+ count = -1
1243
+ for token_ind in range(len(input_ids)):
1244
+ input_id = input_ids[token_ind]
1245
+ if (input_id not in special_tokens) and (
1246
+ token_ind == 0 or (input_ids[token_ind - 1] in special_tokens)
1247
+ ):
1248
+ count += 1
1249
+ if count == label:
1250
+ ind_to_insert_ones = token_ind
1251
+
1252
+ while input_ids[ind_to_insert_ones] not in special_tokens:
1253
+ tokens_positive[ind_to_insert_ones] = 1
1254
+ ind_to_insert_ones += 1
1255
+ break
1256
+ return tokens_positive
1257
+
models/GroundingDINO/matcher.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------------------------------------------------------------------
2
+ # DINO
3
+ # Copyright (c) 2022 IDEA. All Rights Reserved.
4
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
5
+ # ------------------------------------------------------------------------
6
+ # Modules to compute the matching cost and solve the corresponding LSAP.
7
+ # Copyright (c) 2021 Microsoft. All Rights Reserved.
8
+ # Licensed under the Apache License, Version 2.0 [see LICENSE for details]
9
+ # ------------------------------------------------------------------------
10
+ # Modified from DETR (https://github.com/facebookresearch/detr)
11
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
12
+ # ------------------------------------------------------------------------
13
+ # Modified from Deformable DETR (https://github.com/fundamentalvision/Deformable-DETR)
14
+ # Copyright (c) 2020 SenseTime. All Rights Reserved.
15
+ # ------------------------------------------------------------------------
16
+
17
+
18
+ import torch, os
19
+ from torch import nn
20
+ from scipy.optimize import linear_sum_assignment
21
+
22
+ from util.box_ops import box_cxcywh_to_xyxy, generalized_box_iou
23
+
24
+
25
+ class HungarianMatcher(nn.Module):
26
+ """This class computes an assignment between the targets and the predictions of the network
27
+ For efficiency reasons, the targets don't include the no_object. Because of this, in general,
28
+ there are more predictions than targets. In this case, we do a 1-to-1 matching of the best predictions,
29
+ while the others are un-matched (and thus treated as non-objects).
30
+ """
31
+
32
+ def __init__(self, cost_class: float = 1, cost_bbox: float = 1, cost_giou: float = 1, focal_alpha = 0.25):
33
+ """Creates the matcher
34
+ Params:
35
+ cost_class: This is the relative weight of the classification error in the matching cost
36
+ cost_bbox: This is the relative weight of the L1 error of the bounding box coordinates in the matching cost
37
+ cost_giou: This is the relative weight of the giou loss of the bounding box in the matching cost
38
+ """
39
+ super().__init__()
40
+ self.cost_class = cost_class
41
+ self.cost_bbox = cost_bbox
42
+ self.cost_giou = cost_giou
43
+ assert cost_class != 0 or cost_bbox != 0 or cost_giou != 0, "all costs cant be 0"
44
+
45
+ self.focal_alpha = focal_alpha
46
+
47
+ @torch.no_grad()
48
+ def forward(self, outputs, targets, label_map):
49
+ """ Performs the matching
50
+ Params:
51
+ outputs: This is a dict that contains at least these entries:
52
+ "pred_logits": Tensor of dim [batch_size, num_queries, num_classes] with the classification logits
53
+ "pred_boxes": Tensor of dim [batch_size, num_queries, 4] with the predicted box coordinates
54
+ targets: This is a list of targets (len(targets) = batch_size), where each target is a dict containing:
55
+ "labels": Tensor of dim [num_target_boxes] (where num_target_boxes is the number of ground-truth
56
+ objects in the target) containing the class labels
57
+ "boxes": Tensor of dim [num_target_boxes, 4] containing the target box coordinates
58
+ Returns:
59
+ A list of size batch_size, containing tuples of (index_i, index_j) where:
60
+ - index_i is the indices of the selected predictions (in order)
61
+ - index_j is the indices of the corresponding selected targets (in order)
62
+ For each batch element, it holds:
63
+ len(index_i) = len(index_j) = min(num_queries, num_target_boxes)
64
+ """
65
+
66
+ bs, num_queries = outputs["pred_logits"].shape[:2]
67
+
68
+ # We flatten to compute the cost matrices in a batch
69
+ out_prob = outputs["pred_logits"].flatten(0, 1).sigmoid() # [batch_size * num_queries, num_classes]
70
+ out_bbox = outputs["pred_boxes"].flatten(0, 1) # [batch_size * num_queries, 4]
71
+
72
+ # Also concat the target labels and boxes
73
+ tgt_ids = torch.cat([v["labels"] for v in targets])
74
+ tgt_bbox = torch.cat([v["boxes"] for v in targets])
75
+
76
+ # Compute the classification cost.
77
+ alpha = self.focal_alpha
78
+ gamma = 2.0
79
+
80
+ new_label_map=label_map[tgt_ids.cpu()]
81
+
82
+ neg_cost_class = (1 - alpha) * (out_prob ** gamma) * (-(1 - out_prob + 1e-8).log())
83
+ pos_cost_class = alpha * ((1 - out_prob) ** gamma) * (-(out_prob + 1e-8).log())
84
+ new_label_map=new_label_map.to(pos_cost_class.device)
85
+ cost_bbox = torch.cdist(out_bbox[:, :2], tgt_bbox[:, :2], p=1)
86
+
87
+ # cost_class=(pos_cost_class @ new_label_map.T - neg_cost_class@ new_label_map.T)
88
+ cost_class=[]
89
+ for idx_map in new_label_map:
90
+ idx_map = idx_map / idx_map.sum()
91
+ cost_class.append(pos_cost_class @ idx_map - neg_cost_class@ idx_map)
92
+ if cost_class:
93
+ cost_class=torch.stack(cost_class,dim=0).T
94
+ else:
95
+ cost_class=torch.zeros_like(cost_bbox)
96
+ # Compute the L1 cost between boxes
97
+
98
+
99
+ # Compute the giou cost betwen boxes
100
+ cost_giou = -generalized_box_iou(box_cxcywh_to_xyxy(out_bbox), box_cxcywh_to_xyxy(tgt_bbox))
101
+ # import pdb;pdb.set_trace()
102
+ # Final cost matrix
103
+ C = self.cost_bbox * cost_bbox + self.cost_class * cost_class + self.cost_giou * cost_giou
104
+ C = C.view(bs, num_queries, -1).cpu()
105
+ C[torch.isnan(C)] = 0.0
106
+ C[torch.isinf(C)] = 0.0
107
+
108
+ sizes = [len(v["boxes"]) for v in targets]
109
+ try:
110
+ indices = [linear_sum_assignment(c[i]) for i, c in enumerate(C.split(sizes, -1))]
111
+ except:
112
+ print("warning: use SimpleMinsumMatcher")
113
+ indices = []
114
+ device = C.device
115
+ for i, (c, _size) in enumerate(zip(C.split(sizes, -1), sizes)):
116
+ weight_mat = c[i]
117
+ idx_i = weight_mat.min(0)[1]
118
+ idx_j = torch.arange(_size).to(device)
119
+ indices.append((idx_i, idx_j))
120
+ return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]
121
+
122
+
123
+ class SimpleMinsumMatcher(nn.Module):
124
+ """This class computes an assignment between the targets and the predictions of the network
125
+ For efficiency reasons, the targets don't include the no_object. Because of this, in general,
126
+ there are more predictions than targets. In this case, we do a 1-to-1 matching of the best predictions,
127
+ while the others are un-matched (and thus treated as non-objects).
128
+ """
129
+
130
+ def __init__(self, cost_class: float = 1, cost_bbox: float = 1, cost_giou: float = 1, focal_alpha = 0.25):
131
+ """Creates the matcher
132
+ Params:
133
+ cost_class: This is the relative weight of the classification error in the matching cost
134
+ cost_bbox: This is the relative weight of the L1 error of the bounding box coordinates in the matching cost
135
+ cost_giou: This is the relative weight of the giou loss of the bounding box in the matching cost
136
+ """
137
+ super().__init__()
138
+ self.cost_class = cost_class
139
+ self.cost_bbox = cost_bbox
140
+ self.cost_giou = cost_giou
141
+ assert cost_class != 0 or cost_bbox != 0 or cost_giou != 0, "all costs cant be 0"
142
+
143
+ self.focal_alpha = focal_alpha
144
+
145
+ @torch.no_grad()
146
+ def forward(self, outputs, targets):
147
+ """ Performs the matching
148
+ Params:
149
+ outputs: This is a dict that contains at least these entries:
150
+ "pred_logits": Tensor of dim [batch_size, num_queries, num_classes] with the classification logits
151
+ "pred_boxes": Tensor of dim [batch_size, num_queries, 4] with the predicted box coordinates
152
+ targets: This is a list of targets (len(targets) = batch_size), where each target is a dict containing:
153
+ "labels": Tensor of dim [num_target_boxes] (where num_target_boxes is the number of ground-truth
154
+ objects in the target) containing the class labels
155
+ "boxes": Tensor of dim [num_target_boxes, 4] containing the target box coordinates
156
+ Returns:
157
+ A list of size batch_size, containing tuples of (index_i, index_j) where:
158
+ - index_i is the indices of the selected predictions (in order)
159
+ - index_j is the indices of the corresponding selected targets (in order)
160
+ For each batch element, it holds:
161
+ len(index_i) = len(index_j) = min(num_queries, num_target_boxes)
162
+ """
163
+
164
+ bs, num_queries = outputs["pred_logits"].shape[:2]
165
+
166
+ # We flatten to compute the cost matrices in a batch
167
+ out_prob = outputs["pred_logits"].flatten(0, 1).sigmoid() # [batch_size * num_queries, num_classes]
168
+ out_bbox = outputs["pred_boxes"].flatten(0, 1) # [batch_size * num_queries, 4]
169
+
170
+ # Also concat the target labels and boxes
171
+ tgt_ids = torch.cat([v["labels"] for v in targets])
172
+ tgt_bbox = torch.cat([v["boxes"] for v in targets])
173
+
174
+ # Compute the classification cost.
175
+ alpha = self.focal_alpha
176
+ gamma = 2.0
177
+ neg_cost_class = (1 - alpha) * (out_prob ** gamma) * (-(1 - out_prob + 1e-8).log())
178
+ pos_cost_class = alpha * ((1 - out_prob) ** gamma) * (-(out_prob + 1e-8).log())
179
+ cost_class = pos_cost_class[:, tgt_ids] - neg_cost_class[:, tgt_ids]
180
+
181
+ # Compute the L1 cost between boxes
182
+ cost_bbox = torch.cdist(out_bbox, tgt_bbox, p=1)
183
+
184
+ # Compute the giou cost betwen boxes
185
+ cost_giou = -generalized_box_iou(box_cxcywh_to_xyxy(out_bbox), box_cxcywh_to_xyxy(tgt_bbox))
186
+
187
+ # Final cost matrix
188
+
189
+ C = self.cost_bbox * cost_bbox + self.cost_class * cost_class + self.cost_giou * cost_giou
190
+ C = C.view(bs, num_queries, -1)
191
+
192
+ sizes = [len(v["boxes"]) for v in targets]
193
+ indices = []
194
+ device = C.device
195
+ for i, (c, _size) in enumerate(zip(C.split(sizes, -1), sizes)):
196
+ weight_mat = c[i]
197
+ idx_i = weight_mat.min(0)[1]
198
+ idx_j = torch.arange(_size).to(device)
199
+ indices.append((idx_i, idx_j))
200
+
201
+ return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]
202
+
203
+
204
+ def build_matcher(args):
205
+ assert args.matcher_type in ['HungarianMatcher', 'SimpleMinsumMatcher'], "Unknown args.matcher_type: {}".format(args.matcher_type)
206
+ if args.matcher_type == 'HungarianMatcher':
207
+ return HungarianMatcher(
208
+ cost_class=args.set_cost_class, cost_bbox=args.set_cost_bbox, cost_giou=args.set_cost_giou,
209
+ focal_alpha=args.focal_alpha
210
+ )
211
+ elif args.matcher_type == 'SimpleMinsumMatcher':
212
+ return SimpleMinsumMatcher(
213
+ cost_class=args.set_cost_class, cost_bbox=args.set_cost_bbox, cost_giou=args.set_cost_giou,
214
+ focal_alpha=args.focal_alpha
215
+ )
216
+ else:
217
+ raise NotImplementedError("Unknown args.matcher_type: {}".format(args.matcher_type))
models/GroundingDINO/mlp_loca.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from torch import nn
2
+
3
+
4
+ class MLP(nn.Module):
5
+
6
+ def __init__(
7
+ self,
8
+ input_dim: int,
9
+ hidden_dim: int,
10
+ dropout: float,
11
+ activation: nn.Module
12
+ ):
13
+ super(MLP, self).__init__()
14
+
15
+ self.linear1 = nn.Linear(input_dim, hidden_dim)
16
+ self.linear2 = nn.Linear(hidden_dim, input_dim)
17
+ self.dropout = nn.Dropout(dropout)
18
+ self.activation = activation()
19
+
20
+ def forward(self, x):
21
+ return (
22
+ self.linear2(self.dropout(self.activation(self.linear1(x))))
23
+ )