Spaces:
Nymbo
/
Running on Zero

ML-Motivators commited on
Commit
c804dfb
β€’
1 Parent(s): ba6ca56
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. densepose/__init__.py +20 -20
  2. densepose/config.py +277 -277
  3. densepose/converters/__init__.py +15 -15
  4. densepose/converters/base.py +93 -93
  5. densepose/converters/builtin.py +31 -31
  6. densepose/converters/chart_output_hflip.py +71 -71
  7. densepose/converters/chart_output_to_chart_result.py +188 -188
  8. densepose/converters/hflip.py +34 -34
  9. densepose/converters/segm_to_mask.py +150 -150
  10. densepose/converters/to_chart_result.py +70 -70
  11. densepose/converters/to_mask.py +49 -49
  12. densepose/data/__init__.py +25 -25
  13. densepose/data/build.py +736 -736
  14. densepose/data/combined_loader.py +44 -44
  15. densepose/data/dataset_mapper.py +168 -168
  16. densepose/data/datasets/__init__.py +5 -5
  17. densepose/data/datasets/builtin.py +16 -16
  18. densepose/data/datasets/chimpnsee.py +29 -29
  19. densepose/data/datasets/coco.py +432 -432
  20. densepose/data/datasets/dataset_type.py +11 -11
  21. densepose/data/datasets/lvis.py +257 -257
  22. densepose/data/image_list_dataset.py +72 -72
  23. densepose/data/inference_based_loader.py +172 -172
  24. densepose/data/meshes/__init__.py +5 -5
  25. densepose/data/meshes/builtin.py +101 -101
  26. densepose/data/meshes/catalog.py +71 -71
  27. densepose/data/samplers/__init__.py +8 -8
  28. densepose/data/samplers/densepose_base.py +203 -203
  29. densepose/data/samplers/densepose_confidence_based.py +108 -108
  30. densepose/data/samplers/densepose_cse_base.py +139 -139
  31. densepose/data/samplers/densepose_cse_confidence_based.py +119 -119
  32. densepose/data/samplers/densepose_cse_uniform.py +12 -12
  33. densepose/data/samplers/densepose_uniform.py +41 -41
  34. densepose/data/samplers/mask_from_densepose.py +28 -28
  35. densepose/data/samplers/prediction_to_gt.py +98 -98
  36. densepose/data/transform/__init__.py +3 -3
  37. densepose/data/transform/image.py +39 -39
  38. densepose/data/utils.py +38 -38
  39. densepose/data/video/__init__.py +17 -17
  40. densepose/data/video/frame_selector.py +87 -87
  41. densepose/data/video/video_keyframe_dataset.py +300 -300
  42. densepose/engine/__init__.py +3 -3
  43. densepose/engine/trainer.py +258 -258
  44. densepose/evaluation/__init__.py +3 -3
  45. densepose/evaluation/d2_evaluator_adapter.py +50 -50
  46. densepose/evaluation/densepose_coco_evaluation.py +0 -0
  47. densepose/evaluation/evaluator.py +421 -421
  48. densepose/evaluation/mesh_alignment_evaluator.py +66 -66
  49. densepose/evaluation/tensor_storage.py +239 -239
  50. densepose/modeling/__init__.py +13 -13
densepose/__init__.py CHANGED
@@ -1,20 +1,20 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
- from .data.datasets import builtin # just to register data
3
- from .converters import builtin as builtin_converters # register converters
4
- from .config import (
5
- add_densepose_config,
6
- add_densepose_head_config,
7
- add_hrnet_config,
8
- add_dataset_category_config,
9
- add_bootstrap_config,
10
- load_bootstrap_config,
11
- )
12
- from .structures import DensePoseDataRelative, DensePoseList, DensePoseTransformData
13
- from .evaluation import DensePoseCOCOEvaluator
14
- from .modeling.roi_heads import DensePoseROIHeads
15
- from .modeling.test_time_augmentation import (
16
- DensePoseGeneralizedRCNNWithTTA,
17
- DensePoseDatasetMapperTTA,
18
- )
19
- from .utils.transform import load_from_cfg
20
- from .modeling.hrfpn import build_hrfpn_backbone
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ from .data.datasets import builtin # just to register data
3
+ from .converters import builtin as builtin_converters # register converters
4
+ from .config import (
5
+ add_densepose_config,
6
+ add_densepose_head_config,
7
+ add_hrnet_config,
8
+ add_dataset_category_config,
9
+ add_bootstrap_config,
10
+ load_bootstrap_config,
11
+ )
12
+ from .structures import DensePoseDataRelative, DensePoseList, DensePoseTransformData
13
+ from .evaluation import DensePoseCOCOEvaluator
14
+ from .modeling.roi_heads import DensePoseROIHeads
15
+ from .modeling.test_time_augmentation import (
16
+ DensePoseGeneralizedRCNNWithTTA,
17
+ DensePoseDatasetMapperTTA,
18
+ )
19
+ from .utils.transform import load_from_cfg
20
+ from .modeling.hrfpn import build_hrfpn_backbone
densepose/config.py CHANGED
@@ -1,277 +1,277 @@
1
- # -*- coding = utf-8 -*-
2
- # Copyright (c) Facebook, Inc. and its affiliates.
3
- # pyre-ignore-all-errors
4
-
5
- from detectron2.config import CfgNode as CN
6
-
7
-
8
- def add_dataset_category_config(cfg: CN) -> None:
9
- """
10
- Add config for additional category-related dataset options
11
- - category whitelisting
12
- - category mapping
13
- """
14
- _C = cfg
15
- _C.DATASETS.CATEGORY_MAPS = CN(new_allowed=True)
16
- _C.DATASETS.WHITELISTED_CATEGORIES = CN(new_allowed=True)
17
- # class to mesh mapping
18
- _C.DATASETS.CLASS_TO_MESH_NAME_MAPPING = CN(new_allowed=True)
19
-
20
-
21
- def add_evaluation_config(cfg: CN) -> None:
22
- _C = cfg
23
- _C.DENSEPOSE_EVALUATION = CN()
24
- # evaluator type, possible values:
25
- # - "iou": evaluator for models that produce iou data
26
- # - "cse": evaluator for models that produce cse data
27
- _C.DENSEPOSE_EVALUATION.TYPE = "iou"
28
- # storage for DensePose results, possible values:
29
- # - "none": no explicit storage, all the results are stored in the
30
- # dictionary with predictions, memory intensive;
31
- # historically the default storage type
32
- # - "ram": RAM storage, uses per-process RAM storage, which is
33
- # reduced to a single process storage on later stages,
34
- # less memory intensive
35
- # - "file": file storage, uses per-process file-based storage,
36
- # the least memory intensive, but may create bottlenecks
37
- # on file system accesses
38
- _C.DENSEPOSE_EVALUATION.STORAGE = "none"
39
- # minimum threshold for IOU values: the lower its values is,
40
- # the more matches are produced (and the higher the AP score)
41
- _C.DENSEPOSE_EVALUATION.MIN_IOU_THRESHOLD = 0.5
42
- # Non-distributed inference is slower (at inference time) but can avoid RAM OOM
43
- _C.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE = True
44
- # evaluate mesh alignment based on vertex embeddings, only makes sense in CSE context
45
- _C.DENSEPOSE_EVALUATION.EVALUATE_MESH_ALIGNMENT = False
46
- # meshes to compute mesh alignment for
47
- _C.DENSEPOSE_EVALUATION.MESH_ALIGNMENT_MESH_NAMES = []
48
-
49
-
50
- def add_bootstrap_config(cfg: CN) -> None:
51
- """ """
52
- _C = cfg
53
- _C.BOOTSTRAP_DATASETS = []
54
- _C.BOOTSTRAP_MODEL = CN()
55
- _C.BOOTSTRAP_MODEL.WEIGHTS = ""
56
- _C.BOOTSTRAP_MODEL.DEVICE = "cuda"
57
-
58
-
59
- def get_bootstrap_dataset_config() -> CN:
60
- _C = CN()
61
- _C.DATASET = ""
62
- # ratio used to mix data loaders
63
- _C.RATIO = 0.1
64
- # image loader
65
- _C.IMAGE_LOADER = CN(new_allowed=True)
66
- _C.IMAGE_LOADER.TYPE = ""
67
- _C.IMAGE_LOADER.BATCH_SIZE = 4
68
- _C.IMAGE_LOADER.NUM_WORKERS = 4
69
- _C.IMAGE_LOADER.CATEGORIES = []
70
- _C.IMAGE_LOADER.MAX_COUNT_PER_CATEGORY = 1_000_000
71
- _C.IMAGE_LOADER.CATEGORY_TO_CLASS_MAPPING = CN(new_allowed=True)
72
- # inference
73
- _C.INFERENCE = CN()
74
- # batch size for model inputs
75
- _C.INFERENCE.INPUT_BATCH_SIZE = 4
76
- # batch size to group model outputs
77
- _C.INFERENCE.OUTPUT_BATCH_SIZE = 2
78
- # sampled data
79
- _C.DATA_SAMPLER = CN(new_allowed=True)
80
- _C.DATA_SAMPLER.TYPE = ""
81
- _C.DATA_SAMPLER.USE_GROUND_TRUTH_CATEGORIES = False
82
- # filter
83
- _C.FILTER = CN(new_allowed=True)
84
- _C.FILTER.TYPE = ""
85
- return _C
86
-
87
-
88
- def load_bootstrap_config(cfg: CN) -> None:
89
- """
90
- Bootstrap datasets are given as a list of `dict` that are not automatically
91
- converted into CfgNode. This method processes all bootstrap dataset entries
92
- and ensures that they are in CfgNode format and comply with the specification
93
- """
94
- if not cfg.BOOTSTRAP_DATASETS:
95
- return
96
-
97
- bootstrap_datasets_cfgnodes = []
98
- for dataset_cfg in cfg.BOOTSTRAP_DATASETS:
99
- _C = get_bootstrap_dataset_config().clone()
100
- _C.merge_from_other_cfg(CN(dataset_cfg))
101
- bootstrap_datasets_cfgnodes.append(_C)
102
- cfg.BOOTSTRAP_DATASETS = bootstrap_datasets_cfgnodes
103
-
104
-
105
- def add_densepose_head_cse_config(cfg: CN) -> None:
106
- """
107
- Add configuration options for Continuous Surface Embeddings (CSE)
108
- """
109
- _C = cfg
110
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE = CN()
111
- # Dimensionality D of the embedding space
112
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_SIZE = 16
113
- # Embedder specifications for various mesh IDs
114
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDERS = CN(new_allowed=True)
115
- # normalization coefficient for embedding distances
116
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDING_DIST_GAUSS_SIGMA = 0.01
117
- # normalization coefficient for geodesic distances
118
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.GEODESIC_DIST_GAUSS_SIGMA = 0.01
119
- # embedding loss weight
120
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_LOSS_WEIGHT = 0.6
121
- # embedding loss name, currently the following options are supported:
122
- # - EmbeddingLoss: cross-entropy on vertex labels
123
- # - SoftEmbeddingLoss: cross-entropy on vertex label combined with
124
- # Gaussian penalty on distance between vertices
125
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_LOSS_NAME = "EmbeddingLoss"
126
- # optimizer hyperparameters
127
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.FEATURES_LR_FACTOR = 1.0
128
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDING_LR_FACTOR = 1.0
129
- # Shape to shape cycle consistency loss parameters:
130
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS = CN({"ENABLED": False})
131
- # shape to shape cycle consistency loss weight
132
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.WEIGHT = 0.025
133
- # norm type used for loss computation
134
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.NORM_P = 2
135
- # normalization term for embedding similarity matrices
136
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.TEMPERATURE = 0.05
137
- # maximum number of vertices to include into shape to shape cycle loss
138
- # if negative or zero, all vertices are considered
139
- # if positive, random subset of vertices of given size is considered
140
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.MAX_NUM_VERTICES = 4936
141
- # Pixel to shape cycle consistency loss parameters:
142
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS = CN({"ENABLED": False})
143
- # pixel to shape cycle consistency loss weight
144
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.WEIGHT = 0.0001
145
- # norm type used for loss computation
146
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.NORM_P = 2
147
- # map images to all meshes and back (if false, use only gt meshes from the batch)
148
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.USE_ALL_MESHES_NOT_GT_ONLY = False
149
- # Randomly select at most this number of pixels from every instance
150
- # if negative or zero, all vertices are considered
151
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.NUM_PIXELS_TO_SAMPLE = 100
152
- # normalization factor for pixel to pixel distances (higher value = smoother distribution)
153
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.PIXEL_SIGMA = 5.0
154
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.TEMPERATURE_PIXEL_TO_VERTEX = 0.05
155
- _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.TEMPERATURE_VERTEX_TO_PIXEL = 0.05
156
-
157
-
158
- def add_densepose_head_config(cfg: CN) -> None:
159
- """
160
- Add config for densepose head.
161
- """
162
- _C = cfg
163
-
164
- _C.MODEL.DENSEPOSE_ON = True
165
-
166
- _C.MODEL.ROI_DENSEPOSE_HEAD = CN()
167
- _C.MODEL.ROI_DENSEPOSE_HEAD.NAME = ""
168
- _C.MODEL.ROI_DENSEPOSE_HEAD.NUM_STACKED_CONVS = 8
169
- # Number of parts used for point labels
170
- _C.MODEL.ROI_DENSEPOSE_HEAD.NUM_PATCHES = 24
171
- _C.MODEL.ROI_DENSEPOSE_HEAD.DECONV_KERNEL = 4
172
- _C.MODEL.ROI_DENSEPOSE_HEAD.CONV_HEAD_DIM = 512
173
- _C.MODEL.ROI_DENSEPOSE_HEAD.CONV_HEAD_KERNEL = 3
174
- _C.MODEL.ROI_DENSEPOSE_HEAD.UP_SCALE = 2
175
- _C.MODEL.ROI_DENSEPOSE_HEAD.HEATMAP_SIZE = 112
176
- _C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_TYPE = "ROIAlignV2"
177
- _C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_RESOLUTION = 28
178
- _C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_SAMPLING_RATIO = 2
179
- _C.MODEL.ROI_DENSEPOSE_HEAD.NUM_COARSE_SEGM_CHANNELS = 2 # 15 or 2
180
- # Overlap threshold for an RoI to be considered foreground (if >= FG_IOU_THRESHOLD)
181
- _C.MODEL.ROI_DENSEPOSE_HEAD.FG_IOU_THRESHOLD = 0.7
182
- # Loss weights for annotation masks.(14 Parts)
183
- _C.MODEL.ROI_DENSEPOSE_HEAD.INDEX_WEIGHTS = 5.0
184
- # Loss weights for surface parts. (24 Parts)
185
- _C.MODEL.ROI_DENSEPOSE_HEAD.PART_WEIGHTS = 1.0
186
- # Loss weights for UV regression.
187
- _C.MODEL.ROI_DENSEPOSE_HEAD.POINT_REGRESSION_WEIGHTS = 0.01
188
- # Coarse segmentation is trained using instance segmentation task data
189
- _C.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS = False
190
- # For Decoder
191
- _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_ON = True
192
- _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_NUM_CLASSES = 256
193
- _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_CONV_DIMS = 256
194
- _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_NORM = ""
195
- _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_COMMON_STRIDE = 4
196
- # For DeepLab head
197
- _C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB = CN()
198
- _C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB.NORM = "GN"
199
- _C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB.NONLOCAL_ON = 0
200
- # Predictor class name, must be registered in DENSEPOSE_PREDICTOR_REGISTRY
201
- # Some registered predictors:
202
- # "DensePoseChartPredictor": predicts segmentation and UV coordinates for predefined charts
203
- # "DensePoseChartWithConfidencePredictor": predicts segmentation, UV coordinates
204
- # and associated confidences for predefined charts (default)
205
- # "DensePoseEmbeddingWithConfidencePredictor": predicts segmentation, embeddings
206
- # and associated confidences for CSE
207
- _C.MODEL.ROI_DENSEPOSE_HEAD.PREDICTOR_NAME = "DensePoseChartWithConfidencePredictor"
208
- # Loss class name, must be registered in DENSEPOSE_LOSS_REGISTRY
209
- # Some registered losses:
210
- # "DensePoseChartLoss": loss for chart-based models that estimate
211
- # segmentation and UV coordinates
212
- # "DensePoseChartWithConfidenceLoss": loss for chart-based models that estimate
213
- # segmentation, UV coordinates and the corresponding confidences (default)
214
- _C.MODEL.ROI_DENSEPOSE_HEAD.LOSS_NAME = "DensePoseChartWithConfidenceLoss"
215
- # Confidences
216
- # Enable learning UV confidences (variances) along with the actual values
217
- _C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE = CN({"ENABLED": False})
218
- # UV confidence lower bound
219
- _C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE.EPSILON = 0.01
220
- # Enable learning segmentation confidences (variances) along with the actual values
221
- _C.MODEL.ROI_DENSEPOSE_HEAD.SEGM_CONFIDENCE = CN({"ENABLED": False})
222
- # Segmentation confidence lower bound
223
- _C.MODEL.ROI_DENSEPOSE_HEAD.SEGM_CONFIDENCE.EPSILON = 0.01
224
- # Statistical model type for confidence learning, possible values:
225
- # - "iid_iso": statistically independent identically distributed residuals
226
- # with isotropic covariance
227
- # - "indep_aniso": statistically independent residuals with anisotropic
228
- # covariances
229
- _C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE.TYPE = "iid_iso"
230
- # List of angles for rotation in data augmentation during training
231
- _C.INPUT.ROTATION_ANGLES = [0]
232
- _C.TEST.AUG.ROTATION_ANGLES = () # Rotation TTA
233
-
234
- add_densepose_head_cse_config(cfg)
235
-
236
-
237
- def add_hrnet_config(cfg: CN) -> None:
238
- """
239
- Add config for HRNet backbone.
240
- """
241
- _C = cfg
242
-
243
- # For HigherHRNet w32
244
- _C.MODEL.HRNET = CN()
245
- _C.MODEL.HRNET.STEM_INPLANES = 64
246
- _C.MODEL.HRNET.STAGE2 = CN()
247
- _C.MODEL.HRNET.STAGE2.NUM_MODULES = 1
248
- _C.MODEL.HRNET.STAGE2.NUM_BRANCHES = 2
249
- _C.MODEL.HRNET.STAGE2.BLOCK = "BASIC"
250
- _C.MODEL.HRNET.STAGE2.NUM_BLOCKS = [4, 4]
251
- _C.MODEL.HRNET.STAGE2.NUM_CHANNELS = [32, 64]
252
- _C.MODEL.HRNET.STAGE2.FUSE_METHOD = "SUM"
253
- _C.MODEL.HRNET.STAGE3 = CN()
254
- _C.MODEL.HRNET.STAGE3.NUM_MODULES = 4
255
- _C.MODEL.HRNET.STAGE3.NUM_BRANCHES = 3
256
- _C.MODEL.HRNET.STAGE3.BLOCK = "BASIC"
257
- _C.MODEL.HRNET.STAGE3.NUM_BLOCKS = [4, 4, 4]
258
- _C.MODEL.HRNET.STAGE3.NUM_CHANNELS = [32, 64, 128]
259
- _C.MODEL.HRNET.STAGE3.FUSE_METHOD = "SUM"
260
- _C.MODEL.HRNET.STAGE4 = CN()
261
- _C.MODEL.HRNET.STAGE4.NUM_MODULES = 3
262
- _C.MODEL.HRNET.STAGE4.NUM_BRANCHES = 4
263
- _C.MODEL.HRNET.STAGE4.BLOCK = "BASIC"
264
- _C.MODEL.HRNET.STAGE4.NUM_BLOCKS = [4, 4, 4, 4]
265
- _C.MODEL.HRNET.STAGE4.NUM_CHANNELS = [32, 64, 128, 256]
266
- _C.MODEL.HRNET.STAGE4.FUSE_METHOD = "SUM"
267
-
268
- _C.MODEL.HRNET.HRFPN = CN()
269
- _C.MODEL.HRNET.HRFPN.OUT_CHANNELS = 256
270
-
271
-
272
- def add_densepose_config(cfg: CN) -> None:
273
- add_densepose_head_config(cfg)
274
- add_hrnet_config(cfg)
275
- add_bootstrap_config(cfg)
276
- add_dataset_category_config(cfg)
277
- add_evaluation_config(cfg)
 
1
+ # -*- coding = utf-8 -*-
2
+ # Copyright (c) Facebook, Inc. and its affiliates.
3
+ # pyre-ignore-all-errors
4
+
5
+ from detectron2.config import CfgNode as CN
6
+
7
+
8
+ def add_dataset_category_config(cfg: CN) -> None:
9
+ """
10
+ Add config for additional category-related dataset options
11
+ - category whitelisting
12
+ - category mapping
13
+ """
14
+ _C = cfg
15
+ _C.DATASETS.CATEGORY_MAPS = CN(new_allowed=True)
16
+ _C.DATASETS.WHITELISTED_CATEGORIES = CN(new_allowed=True)
17
+ # class to mesh mapping
18
+ _C.DATASETS.CLASS_TO_MESH_NAME_MAPPING = CN(new_allowed=True)
19
+
20
+
21
+ def add_evaluation_config(cfg: CN) -> None:
22
+ _C = cfg
23
+ _C.DENSEPOSE_EVALUATION = CN()
24
+ # evaluator type, possible values:
25
+ # - "iou": evaluator for models that produce iou data
26
+ # - "cse": evaluator for models that produce cse data
27
+ _C.DENSEPOSE_EVALUATION.TYPE = "iou"
28
+ # storage for DensePose results, possible values:
29
+ # - "none": no explicit storage, all the results are stored in the
30
+ # dictionary with predictions, memory intensive;
31
+ # historically the default storage type
32
+ # - "ram": RAM storage, uses per-process RAM storage, which is
33
+ # reduced to a single process storage on later stages,
34
+ # less memory intensive
35
+ # - "file": file storage, uses per-process file-based storage,
36
+ # the least memory intensive, but may create bottlenecks
37
+ # on file system accesses
38
+ _C.DENSEPOSE_EVALUATION.STORAGE = "none"
39
+ # minimum threshold for IOU values: the lower its values is,
40
+ # the more matches are produced (and the higher the AP score)
41
+ _C.DENSEPOSE_EVALUATION.MIN_IOU_THRESHOLD = 0.5
42
+ # Non-distributed inference is slower (at inference time) but can avoid RAM OOM
43
+ _C.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE = True
44
+ # evaluate mesh alignment based on vertex embeddings, only makes sense in CSE context
45
+ _C.DENSEPOSE_EVALUATION.EVALUATE_MESH_ALIGNMENT = False
46
+ # meshes to compute mesh alignment for
47
+ _C.DENSEPOSE_EVALUATION.MESH_ALIGNMENT_MESH_NAMES = []
48
+
49
+
50
+ def add_bootstrap_config(cfg: CN) -> None:
51
+ """ """
52
+ _C = cfg
53
+ _C.BOOTSTRAP_DATASETS = []
54
+ _C.BOOTSTRAP_MODEL = CN()
55
+ _C.BOOTSTRAP_MODEL.WEIGHTS = ""
56
+ _C.BOOTSTRAP_MODEL.DEVICE = "cuda"
57
+
58
+
59
+ def get_bootstrap_dataset_config() -> CN:
60
+ _C = CN()
61
+ _C.DATASET = ""
62
+ # ratio used to mix data loaders
63
+ _C.RATIO = 0.1
64
+ # image loader
65
+ _C.IMAGE_LOADER = CN(new_allowed=True)
66
+ _C.IMAGE_LOADER.TYPE = ""
67
+ _C.IMAGE_LOADER.BATCH_SIZE = 4
68
+ _C.IMAGE_LOADER.NUM_WORKERS = 4
69
+ _C.IMAGE_LOADER.CATEGORIES = []
70
+ _C.IMAGE_LOADER.MAX_COUNT_PER_CATEGORY = 1_000_000
71
+ _C.IMAGE_LOADER.CATEGORY_TO_CLASS_MAPPING = CN(new_allowed=True)
72
+ # inference
73
+ _C.INFERENCE = CN()
74
+ # batch size for model inputs
75
+ _C.INFERENCE.INPUT_BATCH_SIZE = 4
76
+ # batch size to group model outputs
77
+ _C.INFERENCE.OUTPUT_BATCH_SIZE = 2
78
+ # sampled data
79
+ _C.DATA_SAMPLER = CN(new_allowed=True)
80
+ _C.DATA_SAMPLER.TYPE = ""
81
+ _C.DATA_SAMPLER.USE_GROUND_TRUTH_CATEGORIES = False
82
+ # filter
83
+ _C.FILTER = CN(new_allowed=True)
84
+ _C.FILTER.TYPE = ""
85
+ return _C
86
+
87
+
88
+ def load_bootstrap_config(cfg: CN) -> None:
89
+ """
90
+ Bootstrap datasets are given as a list of `dict` that are not automatically
91
+ converted into CfgNode. This method processes all bootstrap dataset entries
92
+ and ensures that they are in CfgNode format and comply with the specification
93
+ """
94
+ if not cfg.BOOTSTRAP_DATASETS:
95
+ return
96
+
97
+ bootstrap_datasets_cfgnodes = []
98
+ for dataset_cfg in cfg.BOOTSTRAP_DATASETS:
99
+ _C = get_bootstrap_dataset_config().clone()
100
+ _C.merge_from_other_cfg(CN(dataset_cfg))
101
+ bootstrap_datasets_cfgnodes.append(_C)
102
+ cfg.BOOTSTRAP_DATASETS = bootstrap_datasets_cfgnodes
103
+
104
+
105
+ def add_densepose_head_cse_config(cfg: CN) -> None:
106
+ """
107
+ Add configuration options for Continuous Surface Embeddings (CSE)
108
+ """
109
+ _C = cfg
110
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE = CN()
111
+ # Dimensionality D of the embedding space
112
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_SIZE = 16
113
+ # Embedder specifications for various mesh IDs
114
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDERS = CN(new_allowed=True)
115
+ # normalization coefficient for embedding distances
116
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDING_DIST_GAUSS_SIGMA = 0.01
117
+ # normalization coefficient for geodesic distances
118
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.GEODESIC_DIST_GAUSS_SIGMA = 0.01
119
+ # embedding loss weight
120
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_LOSS_WEIGHT = 0.6
121
+ # embedding loss name, currently the following options are supported:
122
+ # - EmbeddingLoss: cross-entropy on vertex labels
123
+ # - SoftEmbeddingLoss: cross-entropy on vertex label combined with
124
+ # Gaussian penalty on distance between vertices
125
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_LOSS_NAME = "EmbeddingLoss"
126
+ # optimizer hyperparameters
127
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.FEATURES_LR_FACTOR = 1.0
128
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDING_LR_FACTOR = 1.0
129
+ # Shape to shape cycle consistency loss parameters:
130
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS = CN({"ENABLED": False})
131
+ # shape to shape cycle consistency loss weight
132
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.WEIGHT = 0.025
133
+ # norm type used for loss computation
134
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.NORM_P = 2
135
+ # normalization term for embedding similarity matrices
136
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.TEMPERATURE = 0.05
137
+ # maximum number of vertices to include into shape to shape cycle loss
138
+ # if negative or zero, all vertices are considered
139
+ # if positive, random subset of vertices of given size is considered
140
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.MAX_NUM_VERTICES = 4936
141
+ # Pixel to shape cycle consistency loss parameters:
142
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS = CN({"ENABLED": False})
143
+ # pixel to shape cycle consistency loss weight
144
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.WEIGHT = 0.0001
145
+ # norm type used for loss computation
146
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.NORM_P = 2
147
+ # map images to all meshes and back (if false, use only gt meshes from the batch)
148
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.USE_ALL_MESHES_NOT_GT_ONLY = False
149
+ # Randomly select at most this number of pixels from every instance
150
+ # if negative or zero, all vertices are considered
151
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.NUM_PIXELS_TO_SAMPLE = 100
152
+ # normalization factor for pixel to pixel distances (higher value = smoother distribution)
153
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.PIXEL_SIGMA = 5.0
154
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.TEMPERATURE_PIXEL_TO_VERTEX = 0.05
155
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.TEMPERATURE_VERTEX_TO_PIXEL = 0.05
156
+
157
+
158
+ def add_densepose_head_config(cfg: CN) -> None:
159
+ """
160
+ Add config for densepose head.
161
+ """
162
+ _C = cfg
163
+
164
+ _C.MODEL.DENSEPOSE_ON = True
165
+
166
+ _C.MODEL.ROI_DENSEPOSE_HEAD = CN()
167
+ _C.MODEL.ROI_DENSEPOSE_HEAD.NAME = ""
168
+ _C.MODEL.ROI_DENSEPOSE_HEAD.NUM_STACKED_CONVS = 8
169
+ # Number of parts used for point labels
170
+ _C.MODEL.ROI_DENSEPOSE_HEAD.NUM_PATCHES = 24
171
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DECONV_KERNEL = 4
172
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CONV_HEAD_DIM = 512
173
+ _C.MODEL.ROI_DENSEPOSE_HEAD.CONV_HEAD_KERNEL = 3
174
+ _C.MODEL.ROI_DENSEPOSE_HEAD.UP_SCALE = 2
175
+ _C.MODEL.ROI_DENSEPOSE_HEAD.HEATMAP_SIZE = 112
176
+ _C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_TYPE = "ROIAlignV2"
177
+ _C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_RESOLUTION = 28
178
+ _C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_SAMPLING_RATIO = 2
179
+ _C.MODEL.ROI_DENSEPOSE_HEAD.NUM_COARSE_SEGM_CHANNELS = 2 # 15 or 2
180
+ # Overlap threshold for an RoI to be considered foreground (if >= FG_IOU_THRESHOLD)
181
+ _C.MODEL.ROI_DENSEPOSE_HEAD.FG_IOU_THRESHOLD = 0.7
182
+ # Loss weights for annotation masks.(14 Parts)
183
+ _C.MODEL.ROI_DENSEPOSE_HEAD.INDEX_WEIGHTS = 5.0
184
+ # Loss weights for surface parts. (24 Parts)
185
+ _C.MODEL.ROI_DENSEPOSE_HEAD.PART_WEIGHTS = 1.0
186
+ # Loss weights for UV regression.
187
+ _C.MODEL.ROI_DENSEPOSE_HEAD.POINT_REGRESSION_WEIGHTS = 0.01
188
+ # Coarse segmentation is trained using instance segmentation task data
189
+ _C.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS = False
190
+ # For Decoder
191
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_ON = True
192
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_NUM_CLASSES = 256
193
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_CONV_DIMS = 256
194
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_NORM = ""
195
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_COMMON_STRIDE = 4
196
+ # For DeepLab head
197
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB = CN()
198
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB.NORM = "GN"
199
+ _C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB.NONLOCAL_ON = 0
200
+ # Predictor class name, must be registered in DENSEPOSE_PREDICTOR_REGISTRY
201
+ # Some registered predictors:
202
+ # "DensePoseChartPredictor": predicts segmentation and UV coordinates for predefined charts
203
+ # "DensePoseChartWithConfidencePredictor": predicts segmentation, UV coordinates
204
+ # and associated confidences for predefined charts (default)
205
+ # "DensePoseEmbeddingWithConfidencePredictor": predicts segmentation, embeddings
206
+ # and associated confidences for CSE
207
+ _C.MODEL.ROI_DENSEPOSE_HEAD.PREDICTOR_NAME = "DensePoseChartWithConfidencePredictor"
208
+ # Loss class name, must be registered in DENSEPOSE_LOSS_REGISTRY
209
+ # Some registered losses:
210
+ # "DensePoseChartLoss": loss for chart-based models that estimate
211
+ # segmentation and UV coordinates
212
+ # "DensePoseChartWithConfidenceLoss": loss for chart-based models that estimate
213
+ # segmentation, UV coordinates and the corresponding confidences (default)
214
+ _C.MODEL.ROI_DENSEPOSE_HEAD.LOSS_NAME = "DensePoseChartWithConfidenceLoss"
215
+ # Confidences
216
+ # Enable learning UV confidences (variances) along with the actual values
217
+ _C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE = CN({"ENABLED": False})
218
+ # UV confidence lower bound
219
+ _C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE.EPSILON = 0.01
220
+ # Enable learning segmentation confidences (variances) along with the actual values
221
+ _C.MODEL.ROI_DENSEPOSE_HEAD.SEGM_CONFIDENCE = CN({"ENABLED": False})
222
+ # Segmentation confidence lower bound
223
+ _C.MODEL.ROI_DENSEPOSE_HEAD.SEGM_CONFIDENCE.EPSILON = 0.01
224
+ # Statistical model type for confidence learning, possible values:
225
+ # - "iid_iso": statistically independent identically distributed residuals
226
+ # with isotropic covariance
227
+ # - "indep_aniso": statistically independent residuals with anisotropic
228
+ # covariances
229
+ _C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE.TYPE = "iid_iso"
230
+ # List of angles for rotation in data augmentation during training
231
+ _C.INPUT.ROTATION_ANGLES = [0]
232
+ _C.TEST.AUG.ROTATION_ANGLES = () # Rotation TTA
233
+
234
+ add_densepose_head_cse_config(cfg)
235
+
236
+
237
+ def add_hrnet_config(cfg: CN) -> None:
238
+ """
239
+ Add config for HRNet backbone.
240
+ """
241
+ _C = cfg
242
+
243
+ # For HigherHRNet w32
244
+ _C.MODEL.HRNET = CN()
245
+ _C.MODEL.HRNET.STEM_INPLANES = 64
246
+ _C.MODEL.HRNET.STAGE2 = CN()
247
+ _C.MODEL.HRNET.STAGE2.NUM_MODULES = 1
248
+ _C.MODEL.HRNET.STAGE2.NUM_BRANCHES = 2
249
+ _C.MODEL.HRNET.STAGE2.BLOCK = "BASIC"
250
+ _C.MODEL.HRNET.STAGE2.NUM_BLOCKS = [4, 4]
251
+ _C.MODEL.HRNET.STAGE2.NUM_CHANNELS = [32, 64]
252
+ _C.MODEL.HRNET.STAGE2.FUSE_METHOD = "SUM"
253
+ _C.MODEL.HRNET.STAGE3 = CN()
254
+ _C.MODEL.HRNET.STAGE3.NUM_MODULES = 4
255
+ _C.MODEL.HRNET.STAGE3.NUM_BRANCHES = 3
256
+ _C.MODEL.HRNET.STAGE3.BLOCK = "BASIC"
257
+ _C.MODEL.HRNET.STAGE3.NUM_BLOCKS = [4, 4, 4]
258
+ _C.MODEL.HRNET.STAGE3.NUM_CHANNELS = [32, 64, 128]
259
+ _C.MODEL.HRNET.STAGE3.FUSE_METHOD = "SUM"
260
+ _C.MODEL.HRNET.STAGE4 = CN()
261
+ _C.MODEL.HRNET.STAGE4.NUM_MODULES = 3
262
+ _C.MODEL.HRNET.STAGE4.NUM_BRANCHES = 4
263
+ _C.MODEL.HRNET.STAGE4.BLOCK = "BASIC"
264
+ _C.MODEL.HRNET.STAGE4.NUM_BLOCKS = [4, 4, 4, 4]
265
+ _C.MODEL.HRNET.STAGE4.NUM_CHANNELS = [32, 64, 128, 256]
266
+ _C.MODEL.HRNET.STAGE4.FUSE_METHOD = "SUM"
267
+
268
+ _C.MODEL.HRNET.HRFPN = CN()
269
+ _C.MODEL.HRNET.HRFPN.OUT_CHANNELS = 256
270
+
271
+
272
+ def add_densepose_config(cfg: CN) -> None:
273
+ add_densepose_head_config(cfg)
274
+ add_hrnet_config(cfg)
275
+ add_bootstrap_config(cfg)
276
+ add_dataset_category_config(cfg)
277
+ add_evaluation_config(cfg)
densepose/converters/__init__.py CHANGED
@@ -1,15 +1,15 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .hflip import HFlipConverter
4
- from .to_mask import ToMaskConverter
5
- from .to_chart_result import ToChartResultConverter, ToChartResultConverterWithConfidences
6
- from .segm_to_mask import (
7
- predictor_output_with_fine_and_coarse_segm_to_mask,
8
- predictor_output_with_coarse_segm_to_mask,
9
- resample_fine_and_coarse_segm_to_bbox,
10
- )
11
- from .chart_output_to_chart_result import (
12
- densepose_chart_predictor_output_to_result,
13
- densepose_chart_predictor_output_to_result_with_confidences,
14
- )
15
- from .chart_output_hflip import densepose_chart_predictor_output_hflip
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .hflip import HFlipConverter
4
+ from .to_mask import ToMaskConverter
5
+ from .to_chart_result import ToChartResultConverter, ToChartResultConverterWithConfidences
6
+ from .segm_to_mask import (
7
+ predictor_output_with_fine_and_coarse_segm_to_mask,
8
+ predictor_output_with_coarse_segm_to_mask,
9
+ resample_fine_and_coarse_segm_to_bbox,
10
+ )
11
+ from .chart_output_to_chart_result import (
12
+ densepose_chart_predictor_output_to_result,
13
+ densepose_chart_predictor_output_to_result_with_confidences,
14
+ )
15
+ from .chart_output_hflip import densepose_chart_predictor_output_hflip
densepose/converters/base.py CHANGED
@@ -1,93 +1,93 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Any, Tuple, Type
4
- import torch
5
-
6
-
7
- class BaseConverter:
8
- """
9
- Converter base class to be reused by various converters.
10
- Converter allows one to convert data from various source types to a particular
11
- destination type. Each source type needs to register its converter. The
12
- registration for each source type is valid for all descendants of that type.
13
- """
14
-
15
- @classmethod
16
- def register(cls, from_type: Type, converter: Any = None):
17
- """
18
- Registers a converter for the specified type.
19
- Can be used as a decorator (if converter is None), or called as a method.
20
-
21
- Args:
22
- from_type (type): type to register the converter for;
23
- all instances of this type will use the same converter
24
- converter (callable): converter to be registered for the given
25
- type; if None, this method is assumed to be a decorator for the converter
26
- """
27
-
28
- if converter is not None:
29
- cls._do_register(from_type, converter)
30
-
31
- def wrapper(converter: Any) -> Any:
32
- cls._do_register(from_type, converter)
33
- return converter
34
-
35
- return wrapper
36
-
37
- @classmethod
38
- def _do_register(cls, from_type: Type, converter: Any):
39
- cls.registry[from_type] = converter # pyre-ignore[16]
40
-
41
- @classmethod
42
- def _lookup_converter(cls, from_type: Type) -> Any:
43
- """
44
- Perform recursive lookup for the given type
45
- to find registered converter. If a converter was found for some base
46
- class, it gets registered for this class to save on further lookups.
47
-
48
- Args:
49
- from_type: type for which to find a converter
50
- Return:
51
- callable or None - registered converter or None
52
- if no suitable entry was found in the registry
53
- """
54
- if from_type in cls.registry: # pyre-ignore[16]
55
- return cls.registry[from_type]
56
- for base in from_type.__bases__:
57
- converter = cls._lookup_converter(base)
58
- if converter is not None:
59
- cls._do_register(from_type, converter)
60
- return converter
61
- return None
62
-
63
- @classmethod
64
- def convert(cls, instance: Any, *args, **kwargs):
65
- """
66
- Convert an instance to the destination type using some registered
67
- converter. Does recursive lookup for base classes, so there's no need
68
- for explicit registration for derived classes.
69
-
70
- Args:
71
- instance: source instance to convert to the destination type
72
- Return:
73
- An instance of the destination type obtained from the source instance
74
- Raises KeyError, if no suitable converter found
75
- """
76
- instance_type = type(instance)
77
- converter = cls._lookup_converter(instance_type)
78
- if converter is None:
79
- if cls.dst_type is None: # pyre-ignore[16]
80
- output_type_str = "itself"
81
- else:
82
- output_type_str = cls.dst_type
83
- raise KeyError(f"Could not find converter from {instance_type} to {output_type_str}")
84
- return converter(instance, *args, **kwargs)
85
-
86
-
87
- IntTupleBox = Tuple[int, int, int, int]
88
-
89
-
90
- def make_int_box(box: torch.Tensor) -> IntTupleBox:
91
- int_box = [0, 0, 0, 0]
92
- int_box[0], int_box[1], int_box[2], int_box[3] = tuple(box.long().tolist())
93
- return int_box[0], int_box[1], int_box[2], int_box[3]
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Any, Tuple, Type
4
+ import torch
5
+
6
+
7
+ class BaseConverter:
8
+ """
9
+ Converter base class to be reused by various converters.
10
+ Converter allows one to convert data from various source types to a particular
11
+ destination type. Each source type needs to register its converter. The
12
+ registration for each source type is valid for all descendants of that type.
13
+ """
14
+
15
+ @classmethod
16
+ def register(cls, from_type: Type, converter: Any = None):
17
+ """
18
+ Registers a converter for the specified type.
19
+ Can be used as a decorator (if converter is None), or called as a method.
20
+
21
+ Args:
22
+ from_type (type): type to register the converter for;
23
+ all instances of this type will use the same converter
24
+ converter (callable): converter to be registered for the given
25
+ type; if None, this method is assumed to be a decorator for the converter
26
+ """
27
+
28
+ if converter is not None:
29
+ cls._do_register(from_type, converter)
30
+
31
+ def wrapper(converter: Any) -> Any:
32
+ cls._do_register(from_type, converter)
33
+ return converter
34
+
35
+ return wrapper
36
+
37
+ @classmethod
38
+ def _do_register(cls, from_type: Type, converter: Any):
39
+ cls.registry[from_type] = converter # pyre-ignore[16]
40
+
41
+ @classmethod
42
+ def _lookup_converter(cls, from_type: Type) -> Any:
43
+ """
44
+ Perform recursive lookup for the given type
45
+ to find registered converter. If a converter was found for some base
46
+ class, it gets registered for this class to save on further lookups.
47
+
48
+ Args:
49
+ from_type: type for which to find a converter
50
+ Return:
51
+ callable or None - registered converter or None
52
+ if no suitable entry was found in the registry
53
+ """
54
+ if from_type in cls.registry: # pyre-ignore[16]
55
+ return cls.registry[from_type]
56
+ for base in from_type.__bases__:
57
+ converter = cls._lookup_converter(base)
58
+ if converter is not None:
59
+ cls._do_register(from_type, converter)
60
+ return converter
61
+ return None
62
+
63
+ @classmethod
64
+ def convert(cls, instance: Any, *args, **kwargs):
65
+ """
66
+ Convert an instance to the destination type using some registered
67
+ converter. Does recursive lookup for base classes, so there's no need
68
+ for explicit registration for derived classes.
69
+
70
+ Args:
71
+ instance: source instance to convert to the destination type
72
+ Return:
73
+ An instance of the destination type obtained from the source instance
74
+ Raises KeyError, if no suitable converter found
75
+ """
76
+ instance_type = type(instance)
77
+ converter = cls._lookup_converter(instance_type)
78
+ if converter is None:
79
+ if cls.dst_type is None: # pyre-ignore[16]
80
+ output_type_str = "itself"
81
+ else:
82
+ output_type_str = cls.dst_type
83
+ raise KeyError(f"Could not find converter from {instance_type} to {output_type_str}")
84
+ return converter(instance, *args, **kwargs)
85
+
86
+
87
+ IntTupleBox = Tuple[int, int, int, int]
88
+
89
+
90
+ def make_int_box(box: torch.Tensor) -> IntTupleBox:
91
+ int_box = [0, 0, 0, 0]
92
+ int_box[0], int_box[1], int_box[2], int_box[3] = tuple(box.long().tolist())
93
+ return int_box[0], int_box[1], int_box[2], int_box[3]
densepose/converters/builtin.py CHANGED
@@ -1,31 +1,31 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from ..structures import DensePoseChartPredictorOutput, DensePoseEmbeddingPredictorOutput
4
- from . import (
5
- HFlipConverter,
6
- ToChartResultConverter,
7
- ToChartResultConverterWithConfidences,
8
- ToMaskConverter,
9
- densepose_chart_predictor_output_hflip,
10
- densepose_chart_predictor_output_to_result,
11
- densepose_chart_predictor_output_to_result_with_confidences,
12
- predictor_output_with_coarse_segm_to_mask,
13
- predictor_output_with_fine_and_coarse_segm_to_mask,
14
- )
15
-
16
- ToMaskConverter.register(
17
- DensePoseChartPredictorOutput, predictor_output_with_fine_and_coarse_segm_to_mask
18
- )
19
- ToMaskConverter.register(
20
- DensePoseEmbeddingPredictorOutput, predictor_output_with_coarse_segm_to_mask
21
- )
22
-
23
- ToChartResultConverter.register(
24
- DensePoseChartPredictorOutput, densepose_chart_predictor_output_to_result
25
- )
26
-
27
- ToChartResultConverterWithConfidences.register(
28
- DensePoseChartPredictorOutput, densepose_chart_predictor_output_to_result_with_confidences
29
- )
30
-
31
- HFlipConverter.register(DensePoseChartPredictorOutput, densepose_chart_predictor_output_hflip)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from ..structures import DensePoseChartPredictorOutput, DensePoseEmbeddingPredictorOutput
4
+ from . import (
5
+ HFlipConverter,
6
+ ToChartResultConverter,
7
+ ToChartResultConverterWithConfidences,
8
+ ToMaskConverter,
9
+ densepose_chart_predictor_output_hflip,
10
+ densepose_chart_predictor_output_to_result,
11
+ densepose_chart_predictor_output_to_result_with_confidences,
12
+ predictor_output_with_coarse_segm_to_mask,
13
+ predictor_output_with_fine_and_coarse_segm_to_mask,
14
+ )
15
+
16
+ ToMaskConverter.register(
17
+ DensePoseChartPredictorOutput, predictor_output_with_fine_and_coarse_segm_to_mask
18
+ )
19
+ ToMaskConverter.register(
20
+ DensePoseEmbeddingPredictorOutput, predictor_output_with_coarse_segm_to_mask
21
+ )
22
+
23
+ ToChartResultConverter.register(
24
+ DensePoseChartPredictorOutput, densepose_chart_predictor_output_to_result
25
+ )
26
+
27
+ ToChartResultConverterWithConfidences.register(
28
+ DensePoseChartPredictorOutput, densepose_chart_predictor_output_to_result_with_confidences
29
+ )
30
+
31
+ HFlipConverter.register(DensePoseChartPredictorOutput, densepose_chart_predictor_output_hflip)
densepose/converters/chart_output_hflip.py CHANGED
@@ -1,71 +1,71 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
- from dataclasses import fields
3
- import torch
4
-
5
- from densepose.structures import DensePoseChartPredictorOutput, DensePoseTransformData
6
-
7
-
8
- def densepose_chart_predictor_output_hflip(
9
- densepose_predictor_output: DensePoseChartPredictorOutput,
10
- transform_data: DensePoseTransformData,
11
- ) -> DensePoseChartPredictorOutput:
12
- """
13
- Change to take into account a Horizontal flip.
14
- """
15
- if len(densepose_predictor_output) > 0:
16
-
17
- PredictorOutput = type(densepose_predictor_output)
18
- output_dict = {}
19
-
20
- for field in fields(densepose_predictor_output):
21
- field_value = getattr(densepose_predictor_output, field.name)
22
- # flip tensors
23
- if isinstance(field_value, torch.Tensor):
24
- setattr(densepose_predictor_output, field.name, torch.flip(field_value, [3]))
25
-
26
- densepose_predictor_output = _flip_iuv_semantics_tensor(
27
- densepose_predictor_output, transform_data
28
- )
29
- densepose_predictor_output = _flip_segm_semantics_tensor(
30
- densepose_predictor_output, transform_data
31
- )
32
-
33
- for field in fields(densepose_predictor_output):
34
- output_dict[field.name] = getattr(densepose_predictor_output, field.name)
35
-
36
- return PredictorOutput(**output_dict)
37
- else:
38
- return densepose_predictor_output
39
-
40
-
41
- def _flip_iuv_semantics_tensor(
42
- densepose_predictor_output: DensePoseChartPredictorOutput,
43
- dp_transform_data: DensePoseTransformData,
44
- ) -> DensePoseChartPredictorOutput:
45
- point_label_symmetries = dp_transform_data.point_label_symmetries
46
- uv_symmetries = dp_transform_data.uv_symmetries
47
-
48
- N, C, H, W = densepose_predictor_output.u.shape
49
- u_loc = (densepose_predictor_output.u[:, 1:, :, :].clamp(0, 1) * 255).long()
50
- v_loc = (densepose_predictor_output.v[:, 1:, :, :].clamp(0, 1) * 255).long()
51
- Iindex = torch.arange(C - 1, device=densepose_predictor_output.u.device)[
52
- None, :, None, None
53
- ].expand(N, C - 1, H, W)
54
- densepose_predictor_output.u[:, 1:, :, :] = uv_symmetries["U_transforms"][Iindex, v_loc, u_loc]
55
- densepose_predictor_output.v[:, 1:, :, :] = uv_symmetries["V_transforms"][Iindex, v_loc, u_loc]
56
-
57
- for el in ["fine_segm", "u", "v"]:
58
- densepose_predictor_output.__dict__[el] = densepose_predictor_output.__dict__[el][
59
- :, point_label_symmetries, :, :
60
- ]
61
- return densepose_predictor_output
62
-
63
-
64
- def _flip_segm_semantics_tensor(
65
- densepose_predictor_output: DensePoseChartPredictorOutput, dp_transform_data
66
- ):
67
- if densepose_predictor_output.coarse_segm.shape[1] > 2:
68
- densepose_predictor_output.coarse_segm = densepose_predictor_output.coarse_segm[
69
- :, dp_transform_data.mask_label_symmetries, :, :
70
- ]
71
- return densepose_predictor_output
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ from dataclasses import fields
3
+ import torch
4
+
5
+ from densepose.structures import DensePoseChartPredictorOutput, DensePoseTransformData
6
+
7
+
8
+ def densepose_chart_predictor_output_hflip(
9
+ densepose_predictor_output: DensePoseChartPredictorOutput,
10
+ transform_data: DensePoseTransformData,
11
+ ) -> DensePoseChartPredictorOutput:
12
+ """
13
+ Change to take into account a Horizontal flip.
14
+ """
15
+ if len(densepose_predictor_output) > 0:
16
+
17
+ PredictorOutput = type(densepose_predictor_output)
18
+ output_dict = {}
19
+
20
+ for field in fields(densepose_predictor_output):
21
+ field_value = getattr(densepose_predictor_output, field.name)
22
+ # flip tensors
23
+ if isinstance(field_value, torch.Tensor):
24
+ setattr(densepose_predictor_output, field.name, torch.flip(field_value, [3]))
25
+
26
+ densepose_predictor_output = _flip_iuv_semantics_tensor(
27
+ densepose_predictor_output, transform_data
28
+ )
29
+ densepose_predictor_output = _flip_segm_semantics_tensor(
30
+ densepose_predictor_output, transform_data
31
+ )
32
+
33
+ for field in fields(densepose_predictor_output):
34
+ output_dict[field.name] = getattr(densepose_predictor_output, field.name)
35
+
36
+ return PredictorOutput(**output_dict)
37
+ else:
38
+ return densepose_predictor_output
39
+
40
+
41
+ def _flip_iuv_semantics_tensor(
42
+ densepose_predictor_output: DensePoseChartPredictorOutput,
43
+ dp_transform_data: DensePoseTransformData,
44
+ ) -> DensePoseChartPredictorOutput:
45
+ point_label_symmetries = dp_transform_data.point_label_symmetries
46
+ uv_symmetries = dp_transform_data.uv_symmetries
47
+
48
+ N, C, H, W = densepose_predictor_output.u.shape
49
+ u_loc = (densepose_predictor_output.u[:, 1:, :, :].clamp(0, 1) * 255).long()
50
+ v_loc = (densepose_predictor_output.v[:, 1:, :, :].clamp(0, 1) * 255).long()
51
+ Iindex = torch.arange(C - 1, device=densepose_predictor_output.u.device)[
52
+ None, :, None, None
53
+ ].expand(N, C - 1, H, W)
54
+ densepose_predictor_output.u[:, 1:, :, :] = uv_symmetries["U_transforms"][Iindex, v_loc, u_loc]
55
+ densepose_predictor_output.v[:, 1:, :, :] = uv_symmetries["V_transforms"][Iindex, v_loc, u_loc]
56
+
57
+ for el in ["fine_segm", "u", "v"]:
58
+ densepose_predictor_output.__dict__[el] = densepose_predictor_output.__dict__[el][
59
+ :, point_label_symmetries, :, :
60
+ ]
61
+ return densepose_predictor_output
62
+
63
+
64
+ def _flip_segm_semantics_tensor(
65
+ densepose_predictor_output: DensePoseChartPredictorOutput, dp_transform_data
66
+ ):
67
+ if densepose_predictor_output.coarse_segm.shape[1] > 2:
68
+ densepose_predictor_output.coarse_segm = densepose_predictor_output.coarse_segm[
69
+ :, dp_transform_data.mask_label_symmetries, :, :
70
+ ]
71
+ return densepose_predictor_output
densepose/converters/chart_output_to_chart_result.py CHANGED
@@ -1,188 +1,188 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Dict
4
- import torch
5
- from torch.nn import functional as F
6
-
7
- from detectron2.structures.boxes import Boxes, BoxMode
8
-
9
- from ..structures import (
10
- DensePoseChartPredictorOutput,
11
- DensePoseChartResult,
12
- DensePoseChartResultWithConfidences,
13
- )
14
- from . import resample_fine_and_coarse_segm_to_bbox
15
- from .base import IntTupleBox, make_int_box
16
-
17
-
18
- def resample_uv_tensors_to_bbox(
19
- u: torch.Tensor,
20
- v: torch.Tensor,
21
- labels: torch.Tensor,
22
- box_xywh_abs: IntTupleBox,
23
- ) -> torch.Tensor:
24
- """
25
- Resamples U and V coordinate estimates for the given bounding box
26
-
27
- Args:
28
- u (tensor [1, C, H, W] of float): U coordinates
29
- v (tensor [1, C, H, W] of float): V coordinates
30
- labels (tensor [H, W] of long): labels obtained by resampling segmentation
31
- outputs for the given bounding box
32
- box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
33
- Return:
34
- Resampled U and V coordinates - a tensor [2, H, W] of float
35
- """
36
- x, y, w, h = box_xywh_abs
37
- w = max(int(w), 1)
38
- h = max(int(h), 1)
39
- u_bbox = F.interpolate(u, (h, w), mode="bilinear", align_corners=False)
40
- v_bbox = F.interpolate(v, (h, w), mode="bilinear", align_corners=False)
41
- uv = torch.zeros([2, h, w], dtype=torch.float32, device=u.device)
42
- for part_id in range(1, u_bbox.size(1)):
43
- uv[0][labels == part_id] = u_bbox[0, part_id][labels == part_id]
44
- uv[1][labels == part_id] = v_bbox[0, part_id][labels == part_id]
45
- return uv
46
-
47
-
48
- def resample_uv_to_bbox(
49
- predictor_output: DensePoseChartPredictorOutput,
50
- labels: torch.Tensor,
51
- box_xywh_abs: IntTupleBox,
52
- ) -> torch.Tensor:
53
- """
54
- Resamples U and V coordinate estimates for the given bounding box
55
-
56
- Args:
57
- predictor_output (DensePoseChartPredictorOutput): DensePose predictor
58
- output to be resampled
59
- labels (tensor [H, W] of long): labels obtained by resampling segmentation
60
- outputs for the given bounding box
61
- box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
62
- Return:
63
- Resampled U and V coordinates - a tensor [2, H, W] of float
64
- """
65
- return resample_uv_tensors_to_bbox(
66
- predictor_output.u,
67
- predictor_output.v,
68
- labels,
69
- box_xywh_abs,
70
- )
71
-
72
-
73
- def densepose_chart_predictor_output_to_result(
74
- predictor_output: DensePoseChartPredictorOutput, boxes: Boxes
75
- ) -> DensePoseChartResult:
76
- """
77
- Convert densepose chart predictor outputs to results
78
-
79
- Args:
80
- predictor_output (DensePoseChartPredictorOutput): DensePose predictor
81
- output to be converted to results, must contain only 1 output
82
- boxes (Boxes): bounding box that corresponds to the predictor output,
83
- must contain only 1 bounding box
84
- Return:
85
- DensePose chart-based result (DensePoseChartResult)
86
- """
87
- assert len(predictor_output) == 1 and len(boxes) == 1, (
88
- f"Predictor output to result conversion can operate only single outputs"
89
- f", got {len(predictor_output)} predictor outputs and {len(boxes)} boxes"
90
- )
91
-
92
- boxes_xyxy_abs = boxes.tensor.clone()
93
- boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
94
- box_xywh = make_int_box(boxes_xywh_abs[0])
95
-
96
- labels = resample_fine_and_coarse_segm_to_bbox(predictor_output, box_xywh).squeeze(0)
97
- uv = resample_uv_to_bbox(predictor_output, labels, box_xywh)
98
- return DensePoseChartResult(labels=labels, uv=uv)
99
-
100
-
101
- def resample_confidences_to_bbox(
102
- predictor_output: DensePoseChartPredictorOutput,
103
- labels: torch.Tensor,
104
- box_xywh_abs: IntTupleBox,
105
- ) -> Dict[str, torch.Tensor]:
106
- """
107
- Resamples confidences for the given bounding box
108
-
109
- Args:
110
- predictor_output (DensePoseChartPredictorOutput): DensePose predictor
111
- output to be resampled
112
- labels (tensor [H, W] of long): labels obtained by resampling segmentation
113
- outputs for the given bounding box
114
- box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
115
- Return:
116
- Resampled confidences - a dict of [H, W] tensors of float
117
- """
118
-
119
- x, y, w, h = box_xywh_abs
120
- w = max(int(w), 1)
121
- h = max(int(h), 1)
122
-
123
- confidence_names = [
124
- "sigma_1",
125
- "sigma_2",
126
- "kappa_u",
127
- "kappa_v",
128
- "fine_segm_confidence",
129
- "coarse_segm_confidence",
130
- ]
131
- confidence_results = {key: None for key in confidence_names}
132
- confidence_names = [
133
- key for key in confidence_names if getattr(predictor_output, key) is not None
134
- ]
135
- confidence_base = torch.zeros([h, w], dtype=torch.float32, device=predictor_output.u.device)
136
-
137
- # assign data from channels that correspond to the labels
138
- for key in confidence_names:
139
- resampled_confidence = F.interpolate(
140
- getattr(predictor_output, key),
141
- (h, w),
142
- mode="bilinear",
143
- align_corners=False,
144
- )
145
- result = confidence_base.clone()
146
- for part_id in range(1, predictor_output.u.size(1)):
147
- if resampled_confidence.size(1) != predictor_output.u.size(1):
148
- # confidence is not part-based, don't try to fill it part by part
149
- continue
150
- result[labels == part_id] = resampled_confidence[0, part_id][labels == part_id]
151
-
152
- if resampled_confidence.size(1) != predictor_output.u.size(1):
153
- # confidence is not part-based, fill the data with the first channel
154
- # (targeted for segmentation confidences that have only 1 channel)
155
- result = resampled_confidence[0, 0]
156
-
157
- confidence_results[key] = result
158
-
159
- return confidence_results # pyre-ignore[7]
160
-
161
-
162
- def densepose_chart_predictor_output_to_result_with_confidences(
163
- predictor_output: DensePoseChartPredictorOutput, boxes: Boxes
164
- ) -> DensePoseChartResultWithConfidences:
165
- """
166
- Convert densepose chart predictor outputs to results
167
-
168
- Args:
169
- predictor_output (DensePoseChartPredictorOutput): DensePose predictor
170
- output with confidences to be converted to results, must contain only 1 output
171
- boxes (Boxes): bounding box that corresponds to the predictor output,
172
- must contain only 1 bounding box
173
- Return:
174
- DensePose chart-based result with confidences (DensePoseChartResultWithConfidences)
175
- """
176
- assert len(predictor_output) == 1 and len(boxes) == 1, (
177
- f"Predictor output to result conversion can operate only single outputs"
178
- f", got {len(predictor_output)} predictor outputs and {len(boxes)} boxes"
179
- )
180
-
181
- boxes_xyxy_abs = boxes.tensor.clone()
182
- boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
183
- box_xywh = make_int_box(boxes_xywh_abs[0])
184
-
185
- labels = resample_fine_and_coarse_segm_to_bbox(predictor_output, box_xywh).squeeze(0)
186
- uv = resample_uv_to_bbox(predictor_output, labels, box_xywh)
187
- confidences = resample_confidences_to_bbox(predictor_output, labels, box_xywh)
188
- return DensePoseChartResultWithConfidences(labels=labels, uv=uv, **confidences)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Dict
4
+ import torch
5
+ from torch.nn import functional as F
6
+
7
+ from detectron2.structures.boxes import Boxes, BoxMode
8
+
9
+ from ..structures import (
10
+ DensePoseChartPredictorOutput,
11
+ DensePoseChartResult,
12
+ DensePoseChartResultWithConfidences,
13
+ )
14
+ from . import resample_fine_and_coarse_segm_to_bbox
15
+ from .base import IntTupleBox, make_int_box
16
+
17
+
18
+ def resample_uv_tensors_to_bbox(
19
+ u: torch.Tensor,
20
+ v: torch.Tensor,
21
+ labels: torch.Tensor,
22
+ box_xywh_abs: IntTupleBox,
23
+ ) -> torch.Tensor:
24
+ """
25
+ Resamples U and V coordinate estimates for the given bounding box
26
+
27
+ Args:
28
+ u (tensor [1, C, H, W] of float): U coordinates
29
+ v (tensor [1, C, H, W] of float): V coordinates
30
+ labels (tensor [H, W] of long): labels obtained by resampling segmentation
31
+ outputs for the given bounding box
32
+ box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
33
+ Return:
34
+ Resampled U and V coordinates - a tensor [2, H, W] of float
35
+ """
36
+ x, y, w, h = box_xywh_abs
37
+ w = max(int(w), 1)
38
+ h = max(int(h), 1)
39
+ u_bbox = F.interpolate(u, (h, w), mode="bilinear", align_corners=False)
40
+ v_bbox = F.interpolate(v, (h, w), mode="bilinear", align_corners=False)
41
+ uv = torch.zeros([2, h, w], dtype=torch.float32, device=u.device)
42
+ for part_id in range(1, u_bbox.size(1)):
43
+ uv[0][labels == part_id] = u_bbox[0, part_id][labels == part_id]
44
+ uv[1][labels == part_id] = v_bbox[0, part_id][labels == part_id]
45
+ return uv
46
+
47
+
48
+ def resample_uv_to_bbox(
49
+ predictor_output: DensePoseChartPredictorOutput,
50
+ labels: torch.Tensor,
51
+ box_xywh_abs: IntTupleBox,
52
+ ) -> torch.Tensor:
53
+ """
54
+ Resamples U and V coordinate estimates for the given bounding box
55
+
56
+ Args:
57
+ predictor_output (DensePoseChartPredictorOutput): DensePose predictor
58
+ output to be resampled
59
+ labels (tensor [H, W] of long): labels obtained by resampling segmentation
60
+ outputs for the given bounding box
61
+ box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
62
+ Return:
63
+ Resampled U and V coordinates - a tensor [2, H, W] of float
64
+ """
65
+ return resample_uv_tensors_to_bbox(
66
+ predictor_output.u,
67
+ predictor_output.v,
68
+ labels,
69
+ box_xywh_abs,
70
+ )
71
+
72
+
73
+ def densepose_chart_predictor_output_to_result(
74
+ predictor_output: DensePoseChartPredictorOutput, boxes: Boxes
75
+ ) -> DensePoseChartResult:
76
+ """
77
+ Convert densepose chart predictor outputs to results
78
+
79
+ Args:
80
+ predictor_output (DensePoseChartPredictorOutput): DensePose predictor
81
+ output to be converted to results, must contain only 1 output
82
+ boxes (Boxes): bounding box that corresponds to the predictor output,
83
+ must contain only 1 bounding box
84
+ Return:
85
+ DensePose chart-based result (DensePoseChartResult)
86
+ """
87
+ assert len(predictor_output) == 1 and len(boxes) == 1, (
88
+ f"Predictor output to result conversion can operate only single outputs"
89
+ f", got {len(predictor_output)} predictor outputs and {len(boxes)} boxes"
90
+ )
91
+
92
+ boxes_xyxy_abs = boxes.tensor.clone()
93
+ boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
94
+ box_xywh = make_int_box(boxes_xywh_abs[0])
95
+
96
+ labels = resample_fine_and_coarse_segm_to_bbox(predictor_output, box_xywh).squeeze(0)
97
+ uv = resample_uv_to_bbox(predictor_output, labels, box_xywh)
98
+ return DensePoseChartResult(labels=labels, uv=uv)
99
+
100
+
101
+ def resample_confidences_to_bbox(
102
+ predictor_output: DensePoseChartPredictorOutput,
103
+ labels: torch.Tensor,
104
+ box_xywh_abs: IntTupleBox,
105
+ ) -> Dict[str, torch.Tensor]:
106
+ """
107
+ Resamples confidences for the given bounding box
108
+
109
+ Args:
110
+ predictor_output (DensePoseChartPredictorOutput): DensePose predictor
111
+ output to be resampled
112
+ labels (tensor [H, W] of long): labels obtained by resampling segmentation
113
+ outputs for the given bounding box
114
+ box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
115
+ Return:
116
+ Resampled confidences - a dict of [H, W] tensors of float
117
+ """
118
+
119
+ x, y, w, h = box_xywh_abs
120
+ w = max(int(w), 1)
121
+ h = max(int(h), 1)
122
+
123
+ confidence_names = [
124
+ "sigma_1",
125
+ "sigma_2",
126
+ "kappa_u",
127
+ "kappa_v",
128
+ "fine_segm_confidence",
129
+ "coarse_segm_confidence",
130
+ ]
131
+ confidence_results = {key: None for key in confidence_names}
132
+ confidence_names = [
133
+ key for key in confidence_names if getattr(predictor_output, key) is not None
134
+ ]
135
+ confidence_base = torch.zeros([h, w], dtype=torch.float32, device=predictor_output.u.device)
136
+
137
+ # assign data from channels that correspond to the labels
138
+ for key in confidence_names:
139
+ resampled_confidence = F.interpolate(
140
+ getattr(predictor_output, key),
141
+ (h, w),
142
+ mode="bilinear",
143
+ align_corners=False,
144
+ )
145
+ result = confidence_base.clone()
146
+ for part_id in range(1, predictor_output.u.size(1)):
147
+ if resampled_confidence.size(1) != predictor_output.u.size(1):
148
+ # confidence is not part-based, don't try to fill it part by part
149
+ continue
150
+ result[labels == part_id] = resampled_confidence[0, part_id][labels == part_id]
151
+
152
+ if resampled_confidence.size(1) != predictor_output.u.size(1):
153
+ # confidence is not part-based, fill the data with the first channel
154
+ # (targeted for segmentation confidences that have only 1 channel)
155
+ result = resampled_confidence[0, 0]
156
+
157
+ confidence_results[key] = result
158
+
159
+ return confidence_results # pyre-ignore[7]
160
+
161
+
162
+ def densepose_chart_predictor_output_to_result_with_confidences(
163
+ predictor_output: DensePoseChartPredictorOutput, boxes: Boxes
164
+ ) -> DensePoseChartResultWithConfidences:
165
+ """
166
+ Convert densepose chart predictor outputs to results
167
+
168
+ Args:
169
+ predictor_output (DensePoseChartPredictorOutput): DensePose predictor
170
+ output with confidences to be converted to results, must contain only 1 output
171
+ boxes (Boxes): bounding box that corresponds to the predictor output,
172
+ must contain only 1 bounding box
173
+ Return:
174
+ DensePose chart-based result with confidences (DensePoseChartResultWithConfidences)
175
+ """
176
+ assert len(predictor_output) == 1 and len(boxes) == 1, (
177
+ f"Predictor output to result conversion can operate only single outputs"
178
+ f", got {len(predictor_output)} predictor outputs and {len(boxes)} boxes"
179
+ )
180
+
181
+ boxes_xyxy_abs = boxes.tensor.clone()
182
+ boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
183
+ box_xywh = make_int_box(boxes_xywh_abs[0])
184
+
185
+ labels = resample_fine_and_coarse_segm_to_bbox(predictor_output, box_xywh).squeeze(0)
186
+ uv = resample_uv_to_bbox(predictor_output, labels, box_xywh)
187
+ confidences = resample_confidences_to_bbox(predictor_output, labels, box_xywh)
188
+ return DensePoseChartResultWithConfidences(labels=labels, uv=uv, **confidences)
densepose/converters/hflip.py CHANGED
@@ -1,34 +1,34 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Any
4
-
5
- from .base import BaseConverter
6
-
7
-
8
- class HFlipConverter(BaseConverter):
9
- """
10
- Converts various DensePose predictor outputs to DensePose results.
11
- Each DensePose predictor output type has to register its convertion strategy.
12
- """
13
-
14
- registry = {}
15
- dst_type = None
16
-
17
- @classmethod
18
- # pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
19
- # inconsistently.
20
- def convert(cls, predictor_outputs: Any, transform_data: Any, *args, **kwargs):
21
- """
22
- Performs an horizontal flip on DensePose predictor outputs.
23
- Does recursive lookup for base classes, so there's no need
24
- for explicit registration for derived classes.
25
-
26
- Args:
27
- predictor_outputs: DensePose predictor output to be converted to BitMasks
28
- transform_data: Anything useful for the flip
29
- Return:
30
- An instance of the same type as predictor_outputs
31
- """
32
- return super(HFlipConverter, cls).convert(
33
- predictor_outputs, transform_data, *args, **kwargs
34
- )
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Any
4
+
5
+ from .base import BaseConverter
6
+
7
+
8
+ class HFlipConverter(BaseConverter):
9
+ """
10
+ Converts various DensePose predictor outputs to DensePose results.
11
+ Each DensePose predictor output type has to register its convertion strategy.
12
+ """
13
+
14
+ registry = {}
15
+ dst_type = None
16
+
17
+ @classmethod
18
+ # pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
19
+ # inconsistently.
20
+ def convert(cls, predictor_outputs: Any, transform_data: Any, *args, **kwargs):
21
+ """
22
+ Performs an horizontal flip on DensePose predictor outputs.
23
+ Does recursive lookup for base classes, so there's no need
24
+ for explicit registration for derived classes.
25
+
26
+ Args:
27
+ predictor_outputs: DensePose predictor output to be converted to BitMasks
28
+ transform_data: Anything useful for the flip
29
+ Return:
30
+ An instance of the same type as predictor_outputs
31
+ """
32
+ return super(HFlipConverter, cls).convert(
33
+ predictor_outputs, transform_data, *args, **kwargs
34
+ )
densepose/converters/segm_to_mask.py CHANGED
@@ -1,150 +1,150 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Any
4
- import torch
5
- from torch.nn import functional as F
6
-
7
- from detectron2.structures import BitMasks, Boxes, BoxMode
8
-
9
- from .base import IntTupleBox, make_int_box
10
- from .to_mask import ImageSizeType
11
-
12
-
13
- def resample_coarse_segm_tensor_to_bbox(coarse_segm: torch.Tensor, box_xywh_abs: IntTupleBox):
14
- """
15
- Resample coarse segmentation tensor to the given
16
- bounding box and derive labels for each pixel of the bounding box
17
-
18
- Args:
19
- coarse_segm: float tensor of shape [1, K, Hout, Wout]
20
- box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
21
- corner coordinates, width (W) and height (H)
22
- Return:
23
- Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
24
- """
25
- x, y, w, h = box_xywh_abs
26
- w = max(int(w), 1)
27
- h = max(int(h), 1)
28
- labels = F.interpolate(coarse_segm, (h, w), mode="bilinear", align_corners=False).argmax(dim=1)
29
- return labels
30
-
31
-
32
- def resample_fine_and_coarse_segm_tensors_to_bbox(
33
- fine_segm: torch.Tensor, coarse_segm: torch.Tensor, box_xywh_abs: IntTupleBox
34
- ):
35
- """
36
- Resample fine and coarse segmentation tensors to the given
37
- bounding box and derive labels for each pixel of the bounding box
38
-
39
- Args:
40
- fine_segm: float tensor of shape [1, C, Hout, Wout]
41
- coarse_segm: float tensor of shape [1, K, Hout, Wout]
42
- box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
43
- corner coordinates, width (W) and height (H)
44
- Return:
45
- Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
46
- """
47
- x, y, w, h = box_xywh_abs
48
- w = max(int(w), 1)
49
- h = max(int(h), 1)
50
- # coarse segmentation
51
- coarse_segm_bbox = F.interpolate(
52
- coarse_segm,
53
- (h, w),
54
- mode="bilinear",
55
- align_corners=False,
56
- ).argmax(dim=1)
57
- # combined coarse and fine segmentation
58
- labels = (
59
- F.interpolate(fine_segm, (h, w), mode="bilinear", align_corners=False).argmax(dim=1)
60
- * (coarse_segm_bbox > 0).long()
61
- )
62
- return labels
63
-
64
-
65
- def resample_fine_and_coarse_segm_to_bbox(predictor_output: Any, box_xywh_abs: IntTupleBox):
66
- """
67
- Resample fine and coarse segmentation outputs from a predictor to the given
68
- bounding box and derive labels for each pixel of the bounding box
69
-
70
- Args:
71
- predictor_output: DensePose predictor output that contains segmentation
72
- results to be resampled
73
- box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
74
- corner coordinates, width (W) and height (H)
75
- Return:
76
- Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
77
- """
78
- return resample_fine_and_coarse_segm_tensors_to_bbox(
79
- predictor_output.fine_segm,
80
- predictor_output.coarse_segm,
81
- box_xywh_abs,
82
- )
83
-
84
-
85
- def predictor_output_with_coarse_segm_to_mask(
86
- predictor_output: Any, boxes: Boxes, image_size_hw: ImageSizeType
87
- ) -> BitMasks:
88
- """
89
- Convert predictor output with coarse and fine segmentation to a mask.
90
- Assumes that predictor output has the following attributes:
91
- - coarse_segm (tensor of size [N, D, H, W]): coarse segmentation
92
- unnormalized scores for N instances; D is the number of coarse
93
- segmentation labels, H and W is the resolution of the estimate
94
-
95
- Args:
96
- predictor_output: DensePose predictor output to be converted to mask
97
- boxes (Boxes): bounding boxes that correspond to the DensePose
98
- predictor outputs
99
- image_size_hw (tuple [int, int]): image height Himg and width Wimg
100
- Return:
101
- BitMasks that contain a bool tensor of size [N, Himg, Wimg] with
102
- a mask of the size of the image for each instance
103
- """
104
- H, W = image_size_hw
105
- boxes_xyxy_abs = boxes.tensor.clone()
106
- boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
107
- N = len(boxes_xywh_abs)
108
- masks = torch.zeros((N, H, W), dtype=torch.bool, device=boxes.tensor.device)
109
- for i in range(len(boxes_xywh_abs)):
110
- box_xywh = make_int_box(boxes_xywh_abs[i])
111
- box_mask = resample_coarse_segm_tensor_to_bbox(predictor_output[i].coarse_segm, box_xywh)
112
- x, y, w, h = box_xywh
113
- masks[i, y : y + h, x : x + w] = box_mask
114
-
115
- return BitMasks(masks)
116
-
117
-
118
- def predictor_output_with_fine_and_coarse_segm_to_mask(
119
- predictor_output: Any, boxes: Boxes, image_size_hw: ImageSizeType
120
- ) -> BitMasks:
121
- """
122
- Convert predictor output with coarse and fine segmentation to a mask.
123
- Assumes that predictor output has the following attributes:
124
- - coarse_segm (tensor of size [N, D, H, W]): coarse segmentation
125
- unnormalized scores for N instances; D is the number of coarse
126
- segmentation labels, H and W is the resolution of the estimate
127
- - fine_segm (tensor of size [N, C, H, W]): fine segmentation
128
- unnormalized scores for N instances; C is the number of fine
129
- segmentation labels, H and W is the resolution of the estimate
130
-
131
- Args:
132
- predictor_output: DensePose predictor output to be converted to mask
133
- boxes (Boxes): bounding boxes that correspond to the DensePose
134
- predictor outputs
135
- image_size_hw (tuple [int, int]): image height Himg and width Wimg
136
- Return:
137
- BitMasks that contain a bool tensor of size [N, Himg, Wimg] with
138
- a mask of the size of the image for each instance
139
- """
140
- H, W = image_size_hw
141
- boxes_xyxy_abs = boxes.tensor.clone()
142
- boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
143
- N = len(boxes_xywh_abs)
144
- masks = torch.zeros((N, H, W), dtype=torch.bool, device=boxes.tensor.device)
145
- for i in range(len(boxes_xywh_abs)):
146
- box_xywh = make_int_box(boxes_xywh_abs[i])
147
- labels_i = resample_fine_and_coarse_segm_to_bbox(predictor_output[i], box_xywh)
148
- x, y, w, h = box_xywh
149
- masks[i, y : y + h, x : x + w] = labels_i > 0
150
- return BitMasks(masks)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Any
4
+ import torch
5
+ from torch.nn import functional as F
6
+
7
+ from detectron2.structures import BitMasks, Boxes, BoxMode
8
+
9
+ from .base import IntTupleBox, make_int_box
10
+ from .to_mask import ImageSizeType
11
+
12
+
13
+ def resample_coarse_segm_tensor_to_bbox(coarse_segm: torch.Tensor, box_xywh_abs: IntTupleBox):
14
+ """
15
+ Resample coarse segmentation tensor to the given
16
+ bounding box and derive labels for each pixel of the bounding box
17
+
18
+ Args:
19
+ coarse_segm: float tensor of shape [1, K, Hout, Wout]
20
+ box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
21
+ corner coordinates, width (W) and height (H)
22
+ Return:
23
+ Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
24
+ """
25
+ x, y, w, h = box_xywh_abs
26
+ w = max(int(w), 1)
27
+ h = max(int(h), 1)
28
+ labels = F.interpolate(coarse_segm, (h, w), mode="bilinear", align_corners=False).argmax(dim=1)
29
+ return labels
30
+
31
+
32
+ def resample_fine_and_coarse_segm_tensors_to_bbox(
33
+ fine_segm: torch.Tensor, coarse_segm: torch.Tensor, box_xywh_abs: IntTupleBox
34
+ ):
35
+ """
36
+ Resample fine and coarse segmentation tensors to the given
37
+ bounding box and derive labels for each pixel of the bounding box
38
+
39
+ Args:
40
+ fine_segm: float tensor of shape [1, C, Hout, Wout]
41
+ coarse_segm: float tensor of shape [1, K, Hout, Wout]
42
+ box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
43
+ corner coordinates, width (W) and height (H)
44
+ Return:
45
+ Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
46
+ """
47
+ x, y, w, h = box_xywh_abs
48
+ w = max(int(w), 1)
49
+ h = max(int(h), 1)
50
+ # coarse segmentation
51
+ coarse_segm_bbox = F.interpolate(
52
+ coarse_segm,
53
+ (h, w),
54
+ mode="bilinear",
55
+ align_corners=False,
56
+ ).argmax(dim=1)
57
+ # combined coarse and fine segmentation
58
+ labels = (
59
+ F.interpolate(fine_segm, (h, w), mode="bilinear", align_corners=False).argmax(dim=1)
60
+ * (coarse_segm_bbox > 0).long()
61
+ )
62
+ return labels
63
+
64
+
65
+ def resample_fine_and_coarse_segm_to_bbox(predictor_output: Any, box_xywh_abs: IntTupleBox):
66
+ """
67
+ Resample fine and coarse segmentation outputs from a predictor to the given
68
+ bounding box and derive labels for each pixel of the bounding box
69
+
70
+ Args:
71
+ predictor_output: DensePose predictor output that contains segmentation
72
+ results to be resampled
73
+ box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
74
+ corner coordinates, width (W) and height (H)
75
+ Return:
76
+ Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
77
+ """
78
+ return resample_fine_and_coarse_segm_tensors_to_bbox(
79
+ predictor_output.fine_segm,
80
+ predictor_output.coarse_segm,
81
+ box_xywh_abs,
82
+ )
83
+
84
+
85
+ def predictor_output_with_coarse_segm_to_mask(
86
+ predictor_output: Any, boxes: Boxes, image_size_hw: ImageSizeType
87
+ ) -> BitMasks:
88
+ """
89
+ Convert predictor output with coarse and fine segmentation to a mask.
90
+ Assumes that predictor output has the following attributes:
91
+ - coarse_segm (tensor of size [N, D, H, W]): coarse segmentation
92
+ unnormalized scores for N instances; D is the number of coarse
93
+ segmentation labels, H and W is the resolution of the estimate
94
+
95
+ Args:
96
+ predictor_output: DensePose predictor output to be converted to mask
97
+ boxes (Boxes): bounding boxes that correspond to the DensePose
98
+ predictor outputs
99
+ image_size_hw (tuple [int, int]): image height Himg and width Wimg
100
+ Return:
101
+ BitMasks that contain a bool tensor of size [N, Himg, Wimg] with
102
+ a mask of the size of the image for each instance
103
+ """
104
+ H, W = image_size_hw
105
+ boxes_xyxy_abs = boxes.tensor.clone()
106
+ boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
107
+ N = len(boxes_xywh_abs)
108
+ masks = torch.zeros((N, H, W), dtype=torch.bool, device=boxes.tensor.device)
109
+ for i in range(len(boxes_xywh_abs)):
110
+ box_xywh = make_int_box(boxes_xywh_abs[i])
111
+ box_mask = resample_coarse_segm_tensor_to_bbox(predictor_output[i].coarse_segm, box_xywh)
112
+ x, y, w, h = box_xywh
113
+ masks[i, y : y + h, x : x + w] = box_mask
114
+
115
+ return BitMasks(masks)
116
+
117
+
118
+ def predictor_output_with_fine_and_coarse_segm_to_mask(
119
+ predictor_output: Any, boxes: Boxes, image_size_hw: ImageSizeType
120
+ ) -> BitMasks:
121
+ """
122
+ Convert predictor output with coarse and fine segmentation to a mask.
123
+ Assumes that predictor output has the following attributes:
124
+ - coarse_segm (tensor of size [N, D, H, W]): coarse segmentation
125
+ unnormalized scores for N instances; D is the number of coarse
126
+ segmentation labels, H and W is the resolution of the estimate
127
+ - fine_segm (tensor of size [N, C, H, W]): fine segmentation
128
+ unnormalized scores for N instances; C is the number of fine
129
+ segmentation labels, H and W is the resolution of the estimate
130
+
131
+ Args:
132
+ predictor_output: DensePose predictor output to be converted to mask
133
+ boxes (Boxes): bounding boxes that correspond to the DensePose
134
+ predictor outputs
135
+ image_size_hw (tuple [int, int]): image height Himg and width Wimg
136
+ Return:
137
+ BitMasks that contain a bool tensor of size [N, Himg, Wimg] with
138
+ a mask of the size of the image for each instance
139
+ """
140
+ H, W = image_size_hw
141
+ boxes_xyxy_abs = boxes.tensor.clone()
142
+ boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
143
+ N = len(boxes_xywh_abs)
144
+ masks = torch.zeros((N, H, W), dtype=torch.bool, device=boxes.tensor.device)
145
+ for i in range(len(boxes_xywh_abs)):
146
+ box_xywh = make_int_box(boxes_xywh_abs[i])
147
+ labels_i = resample_fine_and_coarse_segm_to_bbox(predictor_output[i], box_xywh)
148
+ x, y, w, h = box_xywh
149
+ masks[i, y : y + h, x : x + w] = labels_i > 0
150
+ return BitMasks(masks)
densepose/converters/to_chart_result.py CHANGED
@@ -1,70 +1,70 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Any
4
-
5
- from detectron2.structures import Boxes
6
-
7
- from ..structures import DensePoseChartResult, DensePoseChartResultWithConfidences
8
- from .base import BaseConverter
9
-
10
-
11
- class ToChartResultConverter(BaseConverter):
12
- """
13
- Converts various DensePose predictor outputs to DensePose results.
14
- Each DensePose predictor output type has to register its convertion strategy.
15
- """
16
-
17
- registry = {}
18
- dst_type = DensePoseChartResult
19
-
20
- @classmethod
21
- # pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
22
- # inconsistently.
23
- def convert(cls, predictor_outputs: Any, boxes: Boxes, *args, **kwargs) -> DensePoseChartResult:
24
- """
25
- Convert DensePose predictor outputs to DensePoseResult using some registered
26
- converter. Does recursive lookup for base classes, so there's no need
27
- for explicit registration for derived classes.
28
-
29
- Args:
30
- densepose_predictor_outputs: DensePose predictor output to be
31
- converted to BitMasks
32
- boxes (Boxes): bounding boxes that correspond to the DensePose
33
- predictor outputs
34
- Return:
35
- An instance of DensePoseResult. If no suitable converter was found, raises KeyError
36
- """
37
- return super(ToChartResultConverter, cls).convert(predictor_outputs, boxes, *args, **kwargs)
38
-
39
-
40
- class ToChartResultConverterWithConfidences(BaseConverter):
41
- """
42
- Converts various DensePose predictor outputs to DensePose results.
43
- Each DensePose predictor output type has to register its convertion strategy.
44
- """
45
-
46
- registry = {}
47
- dst_type = DensePoseChartResultWithConfidences
48
-
49
- @classmethod
50
- # pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
51
- # inconsistently.
52
- def convert(
53
- cls, predictor_outputs: Any, boxes: Boxes, *args, **kwargs
54
- ) -> DensePoseChartResultWithConfidences:
55
- """
56
- Convert DensePose predictor outputs to DensePoseResult with confidences
57
- using some registered converter. Does recursive lookup for base classes,
58
- so there's no need for explicit registration for derived classes.
59
-
60
- Args:
61
- densepose_predictor_outputs: DensePose predictor output with confidences
62
- to be converted to BitMasks
63
- boxes (Boxes): bounding boxes that correspond to the DensePose
64
- predictor outputs
65
- Return:
66
- An instance of DensePoseResult. If no suitable converter was found, raises KeyError
67
- """
68
- return super(ToChartResultConverterWithConfidences, cls).convert(
69
- predictor_outputs, boxes, *args, **kwargs
70
- )
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Any
4
+
5
+ from detectron2.structures import Boxes
6
+
7
+ from ..structures import DensePoseChartResult, DensePoseChartResultWithConfidences
8
+ from .base import BaseConverter
9
+
10
+
11
+ class ToChartResultConverter(BaseConverter):
12
+ """
13
+ Converts various DensePose predictor outputs to DensePose results.
14
+ Each DensePose predictor output type has to register its convertion strategy.
15
+ """
16
+
17
+ registry = {}
18
+ dst_type = DensePoseChartResult
19
+
20
+ @classmethod
21
+ # pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
22
+ # inconsistently.
23
+ def convert(cls, predictor_outputs: Any, boxes: Boxes, *args, **kwargs) -> DensePoseChartResult:
24
+ """
25
+ Convert DensePose predictor outputs to DensePoseResult using some registered
26
+ converter. Does recursive lookup for base classes, so there's no need
27
+ for explicit registration for derived classes.
28
+
29
+ Args:
30
+ densepose_predictor_outputs: DensePose predictor output to be
31
+ converted to BitMasks
32
+ boxes (Boxes): bounding boxes that correspond to the DensePose
33
+ predictor outputs
34
+ Return:
35
+ An instance of DensePoseResult. If no suitable converter was found, raises KeyError
36
+ """
37
+ return super(ToChartResultConverter, cls).convert(predictor_outputs, boxes, *args, **kwargs)
38
+
39
+
40
+ class ToChartResultConverterWithConfidences(BaseConverter):
41
+ """
42
+ Converts various DensePose predictor outputs to DensePose results.
43
+ Each DensePose predictor output type has to register its convertion strategy.
44
+ """
45
+
46
+ registry = {}
47
+ dst_type = DensePoseChartResultWithConfidences
48
+
49
+ @classmethod
50
+ # pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
51
+ # inconsistently.
52
+ def convert(
53
+ cls, predictor_outputs: Any, boxes: Boxes, *args, **kwargs
54
+ ) -> DensePoseChartResultWithConfidences:
55
+ """
56
+ Convert DensePose predictor outputs to DensePoseResult with confidences
57
+ using some registered converter. Does recursive lookup for base classes,
58
+ so there's no need for explicit registration for derived classes.
59
+
60
+ Args:
61
+ densepose_predictor_outputs: DensePose predictor output with confidences
62
+ to be converted to BitMasks
63
+ boxes (Boxes): bounding boxes that correspond to the DensePose
64
+ predictor outputs
65
+ Return:
66
+ An instance of DensePoseResult. If no suitable converter was found, raises KeyError
67
+ """
68
+ return super(ToChartResultConverterWithConfidences, cls).convert(
69
+ predictor_outputs, boxes, *args, **kwargs
70
+ )
densepose/converters/to_mask.py CHANGED
@@ -1,49 +1,49 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Any, Tuple
4
-
5
- from detectron2.structures import BitMasks, Boxes
6
-
7
- from .base import BaseConverter
8
-
9
- ImageSizeType = Tuple[int, int]
10
-
11
-
12
- class ToMaskConverter(BaseConverter):
13
- """
14
- Converts various DensePose predictor outputs to masks
15
- in bit mask format (see `BitMasks`). Each DensePose predictor output type
16
- has to register its convertion strategy.
17
- """
18
-
19
- registry = {}
20
- dst_type = BitMasks
21
-
22
- @classmethod
23
- # pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
24
- # inconsistently.
25
- def convert(
26
- cls,
27
- densepose_predictor_outputs: Any,
28
- boxes: Boxes,
29
- image_size_hw: ImageSizeType,
30
- *args,
31
- **kwargs
32
- ) -> BitMasks:
33
- """
34
- Convert DensePose predictor outputs to BitMasks using some registered
35
- converter. Does recursive lookup for base classes, so there's no need
36
- for explicit registration for derived classes.
37
-
38
- Args:
39
- densepose_predictor_outputs: DensePose predictor output to be
40
- converted to BitMasks
41
- boxes (Boxes): bounding boxes that correspond to the DensePose
42
- predictor outputs
43
- image_size_hw (tuple [int, int]): image height and width
44
- Return:
45
- An instance of `BitMasks`. If no suitable converter was found, raises KeyError
46
- """
47
- return super(ToMaskConverter, cls).convert(
48
- densepose_predictor_outputs, boxes, image_size_hw, *args, **kwargs
49
- )
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Any, Tuple
4
+
5
+ from detectron2.structures import BitMasks, Boxes
6
+
7
+ from .base import BaseConverter
8
+
9
+ ImageSizeType = Tuple[int, int]
10
+
11
+
12
+ class ToMaskConverter(BaseConverter):
13
+ """
14
+ Converts various DensePose predictor outputs to masks
15
+ in bit mask format (see `BitMasks`). Each DensePose predictor output type
16
+ has to register its convertion strategy.
17
+ """
18
+
19
+ registry = {}
20
+ dst_type = BitMasks
21
+
22
+ @classmethod
23
+ # pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
24
+ # inconsistently.
25
+ def convert(
26
+ cls,
27
+ densepose_predictor_outputs: Any,
28
+ boxes: Boxes,
29
+ image_size_hw: ImageSizeType,
30
+ *args,
31
+ **kwargs
32
+ ) -> BitMasks:
33
+ """
34
+ Convert DensePose predictor outputs to BitMasks using some registered
35
+ converter. Does recursive lookup for base classes, so there's no need
36
+ for explicit registration for derived classes.
37
+
38
+ Args:
39
+ densepose_predictor_outputs: DensePose predictor output to be
40
+ converted to BitMasks
41
+ boxes (Boxes): bounding boxes that correspond to the DensePose
42
+ predictor outputs
43
+ image_size_hw (tuple [int, int]): image height and width
44
+ Return:
45
+ An instance of `BitMasks`. If no suitable converter was found, raises KeyError
46
+ """
47
+ return super(ToMaskConverter, cls).convert(
48
+ densepose_predictor_outputs, boxes, image_size_hw, *args, **kwargs
49
+ )
densepose/data/__init__.py CHANGED
@@ -1,25 +1,25 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .meshes import builtin
4
- from .build import (
5
- build_detection_test_loader,
6
- build_detection_train_loader,
7
- build_combined_loader,
8
- build_frame_selector,
9
- build_inference_based_loaders,
10
- has_inference_based_loaders,
11
- BootstrapDatasetFactoryCatalog,
12
- )
13
- from .combined_loader import CombinedDataLoader
14
- from .dataset_mapper import DatasetMapper
15
- from .inference_based_loader import InferenceBasedLoader, ScoreBasedFilter
16
- from .image_list_dataset import ImageListDataset
17
- from .utils import is_relative_local_path, maybe_prepend_base_path
18
-
19
- # ensure the builtin datasets are registered
20
- from . import datasets
21
-
22
- # ensure the bootstrap datasets builders are registered
23
- from . import build
24
-
25
- __all__ = [k for k in globals().keys() if not k.startswith("_")]
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .meshes import builtin
4
+ from .build import (
5
+ build_detection_test_loader,
6
+ build_detection_train_loader,
7
+ build_combined_loader,
8
+ build_frame_selector,
9
+ build_inference_based_loaders,
10
+ has_inference_based_loaders,
11
+ BootstrapDatasetFactoryCatalog,
12
+ )
13
+ from .combined_loader import CombinedDataLoader
14
+ from .dataset_mapper import DatasetMapper
15
+ from .inference_based_loader import InferenceBasedLoader, ScoreBasedFilter
16
+ from .image_list_dataset import ImageListDataset
17
+ from .utils import is_relative_local_path, maybe_prepend_base_path
18
+
19
+ # ensure the builtin datasets are registered
20
+ from . import datasets
21
+
22
+ # ensure the bootstrap datasets builders are registered
23
+ from . import build
24
+
25
+ __all__ = [k for k in globals().keys() if not k.startswith("_")]
densepose/data/build.py CHANGED
@@ -1,736 +1,736 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import itertools
4
- import logging
5
- import numpy as np
6
- from collections import UserDict, defaultdict
7
- from dataclasses import dataclass
8
- from typing import Any, Callable, Collection, Dict, Iterable, List, Optional, Sequence, Tuple
9
- import torch
10
- from torch.utils.data.dataset import Dataset
11
-
12
- from detectron2.config import CfgNode
13
- from detectron2.data.build import build_detection_test_loader as d2_build_detection_test_loader
14
- from detectron2.data.build import build_detection_train_loader as d2_build_detection_train_loader
15
- from detectron2.data.build import (
16
- load_proposals_into_dataset,
17
- print_instances_class_histogram,
18
- trivial_batch_collator,
19
- worker_init_reset_seed,
20
- )
21
- from detectron2.data.catalog import DatasetCatalog, Metadata, MetadataCatalog
22
- from detectron2.data.samplers import TrainingSampler
23
- from detectron2.utils.comm import get_world_size
24
-
25
- from densepose.config import get_bootstrap_dataset_config
26
- from densepose.modeling import build_densepose_embedder
27
-
28
- from .combined_loader import CombinedDataLoader, Loader
29
- from .dataset_mapper import DatasetMapper
30
- from .datasets.coco import DENSEPOSE_CSE_KEYS_WITHOUT_MASK, DENSEPOSE_IUV_KEYS_WITHOUT_MASK
31
- from .datasets.dataset_type import DatasetType
32
- from .inference_based_loader import InferenceBasedLoader, ScoreBasedFilter
33
- from .samplers import (
34
- DensePoseConfidenceBasedSampler,
35
- DensePoseCSEConfidenceBasedSampler,
36
- DensePoseCSEUniformSampler,
37
- DensePoseUniformSampler,
38
- MaskFromDensePoseSampler,
39
- PredictionToGroundTruthSampler,
40
- )
41
- from .transform import ImageResizeTransform
42
- from .utils import get_category_to_class_mapping, get_class_to_mesh_name_mapping
43
- from .video import (
44
- FirstKFramesSelector,
45
- FrameSelectionStrategy,
46
- LastKFramesSelector,
47
- RandomKFramesSelector,
48
- VideoKeyframeDataset,
49
- video_list_from_file,
50
- )
51
-
52
- __all__ = ["build_detection_train_loader", "build_detection_test_loader"]
53
-
54
-
55
- Instance = Dict[str, Any]
56
- InstancePredicate = Callable[[Instance], bool]
57
-
58
-
59
- def _compute_num_images_per_worker(cfg: CfgNode) -> int:
60
- num_workers = get_world_size()
61
- images_per_batch = cfg.SOLVER.IMS_PER_BATCH
62
- assert (
63
- images_per_batch % num_workers == 0
64
- ), "SOLVER.IMS_PER_BATCH ({}) must be divisible by the number of workers ({}).".format(
65
- images_per_batch, num_workers
66
- )
67
- assert (
68
- images_per_batch >= num_workers
69
- ), "SOLVER.IMS_PER_BATCH ({}) must be larger than the number of workers ({}).".format(
70
- images_per_batch, num_workers
71
- )
72
- images_per_worker = images_per_batch // num_workers
73
- return images_per_worker
74
-
75
-
76
- def _map_category_id_to_contiguous_id(dataset_name: str, dataset_dicts: Iterable[Instance]) -> None:
77
- meta = MetadataCatalog.get(dataset_name)
78
- for dataset_dict in dataset_dicts:
79
- for ann in dataset_dict["annotations"]:
80
- ann["category_id"] = meta.thing_dataset_id_to_contiguous_id[ann["category_id"]]
81
-
82
-
83
- @dataclass
84
- class _DatasetCategory:
85
- """
86
- Class representing category data in a dataset:
87
- - id: category ID, as specified in the dataset annotations file
88
- - name: category name, as specified in the dataset annotations file
89
- - mapped_id: category ID after applying category maps (DATASETS.CATEGORY_MAPS config option)
90
- - mapped_name: category name after applying category maps
91
- - dataset_name: dataset in which the category is defined
92
-
93
- For example, when training models in a class-agnostic manner, one could take LVIS 1.0
94
- dataset and map the animal categories to the same category as human data from COCO:
95
- id = 225
96
- name = "cat"
97
- mapped_id = 1
98
- mapped_name = "person"
99
- dataset_name = "lvis_v1_animals_dp_train"
100
- """
101
-
102
- id: int
103
- name: str
104
- mapped_id: int
105
- mapped_name: str
106
- dataset_name: str
107
-
108
-
109
- _MergedCategoriesT = Dict[int, List[_DatasetCategory]]
110
-
111
-
112
- def _add_category_id_to_contiguous_id_maps_to_metadata(
113
- merged_categories: _MergedCategoriesT,
114
- ) -> None:
115
- merged_categories_per_dataset = {}
116
- for contiguous_cat_id, cat_id in enumerate(sorted(merged_categories.keys())):
117
- for cat in merged_categories[cat_id]:
118
- if cat.dataset_name not in merged_categories_per_dataset:
119
- merged_categories_per_dataset[cat.dataset_name] = defaultdict(list)
120
- merged_categories_per_dataset[cat.dataset_name][cat_id].append(
121
- (
122
- contiguous_cat_id,
123
- cat,
124
- )
125
- )
126
-
127
- logger = logging.getLogger(__name__)
128
- for dataset_name, merged_categories in merged_categories_per_dataset.items():
129
- meta = MetadataCatalog.get(dataset_name)
130
- if not hasattr(meta, "thing_classes"):
131
- meta.thing_classes = []
132
- meta.thing_dataset_id_to_contiguous_id = {}
133
- meta.thing_dataset_id_to_merged_id = {}
134
- else:
135
- meta.thing_classes.clear()
136
- meta.thing_dataset_id_to_contiguous_id.clear()
137
- meta.thing_dataset_id_to_merged_id.clear()
138
- logger.info(f"Dataset {dataset_name}: category ID to contiguous ID mapping:")
139
- for _cat_id, categories in sorted(merged_categories.items()):
140
- added_to_thing_classes = False
141
- for contiguous_cat_id, cat in categories:
142
- if not added_to_thing_classes:
143
- meta.thing_classes.append(cat.mapped_name)
144
- added_to_thing_classes = True
145
- meta.thing_dataset_id_to_contiguous_id[cat.id] = contiguous_cat_id
146
- meta.thing_dataset_id_to_merged_id[cat.id] = cat.mapped_id
147
- logger.info(f"{cat.id} ({cat.name}) -> {contiguous_cat_id}")
148
-
149
-
150
- def _maybe_create_general_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
151
- def has_annotations(instance: Instance) -> bool:
152
- return "annotations" in instance
153
-
154
- def has_only_crowd_anotations(instance: Instance) -> bool:
155
- for ann in instance["annotations"]:
156
- if ann.get("is_crowd", 0) == 0:
157
- return False
158
- return True
159
-
160
- def general_keep_instance_predicate(instance: Instance) -> bool:
161
- return has_annotations(instance) and not has_only_crowd_anotations(instance)
162
-
163
- if not cfg.DATALOADER.FILTER_EMPTY_ANNOTATIONS:
164
- return None
165
- return general_keep_instance_predicate
166
-
167
-
168
- def _maybe_create_keypoints_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
169
-
170
- min_num_keypoints = cfg.MODEL.ROI_KEYPOINT_HEAD.MIN_KEYPOINTS_PER_IMAGE
171
-
172
- def has_sufficient_num_keypoints(instance: Instance) -> bool:
173
- num_kpts = sum(
174
- (np.array(ann["keypoints"][2::3]) > 0).sum()
175
- for ann in instance["annotations"]
176
- if "keypoints" in ann
177
- )
178
- return num_kpts >= min_num_keypoints
179
-
180
- if cfg.MODEL.KEYPOINT_ON and (min_num_keypoints > 0):
181
- return has_sufficient_num_keypoints
182
- return None
183
-
184
-
185
- def _maybe_create_mask_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
186
- if not cfg.MODEL.MASK_ON:
187
- return None
188
-
189
- def has_mask_annotations(instance: Instance) -> bool:
190
- return any("segmentation" in ann for ann in instance["annotations"])
191
-
192
- return has_mask_annotations
193
-
194
-
195
- def _maybe_create_densepose_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
196
- if not cfg.MODEL.DENSEPOSE_ON:
197
- return None
198
-
199
- use_masks = cfg.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS
200
-
201
- def has_densepose_annotations(instance: Instance) -> bool:
202
- for ann in instance["annotations"]:
203
- if all(key in ann for key in DENSEPOSE_IUV_KEYS_WITHOUT_MASK) or all(
204
- key in ann for key in DENSEPOSE_CSE_KEYS_WITHOUT_MASK
205
- ):
206
- return True
207
- if use_masks and "segmentation" in ann:
208
- return True
209
- return False
210
-
211
- return has_densepose_annotations
212
-
213
-
214
- def _maybe_create_specific_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
215
- specific_predicate_creators = [
216
- _maybe_create_keypoints_keep_instance_predicate,
217
- _maybe_create_mask_keep_instance_predicate,
218
- _maybe_create_densepose_keep_instance_predicate,
219
- ]
220
- predicates = [creator(cfg) for creator in specific_predicate_creators]
221
- predicates = [p for p in predicates if p is not None]
222
- if not predicates:
223
- return None
224
-
225
- def combined_predicate(instance: Instance) -> bool:
226
- return any(p(instance) for p in predicates)
227
-
228
- return combined_predicate
229
-
230
-
231
- def _get_train_keep_instance_predicate(cfg: CfgNode):
232
- general_keep_predicate = _maybe_create_general_keep_instance_predicate(cfg)
233
- combined_specific_keep_predicate = _maybe_create_specific_keep_instance_predicate(cfg)
234
-
235
- def combined_general_specific_keep_predicate(instance: Instance) -> bool:
236
- return general_keep_predicate(instance) and combined_specific_keep_predicate(instance)
237
-
238
- if (general_keep_predicate is None) and (combined_specific_keep_predicate is None):
239
- return None
240
- if general_keep_predicate is None:
241
- return combined_specific_keep_predicate
242
- if combined_specific_keep_predicate is None:
243
- return general_keep_predicate
244
- return combined_general_specific_keep_predicate
245
-
246
-
247
- def _get_test_keep_instance_predicate(cfg: CfgNode):
248
- general_keep_predicate = _maybe_create_general_keep_instance_predicate(cfg)
249
- return general_keep_predicate
250
-
251
-
252
- def _maybe_filter_and_map_categories(
253
- dataset_name: str, dataset_dicts: List[Instance]
254
- ) -> List[Instance]:
255
- meta = MetadataCatalog.get(dataset_name)
256
- category_id_map = meta.thing_dataset_id_to_contiguous_id
257
- filtered_dataset_dicts = []
258
- for dataset_dict in dataset_dicts:
259
- anns = []
260
- for ann in dataset_dict["annotations"]:
261
- cat_id = ann["category_id"]
262
- if cat_id not in category_id_map:
263
- continue
264
- ann["category_id"] = category_id_map[cat_id]
265
- anns.append(ann)
266
- dataset_dict["annotations"] = anns
267
- filtered_dataset_dicts.append(dataset_dict)
268
- return filtered_dataset_dicts
269
-
270
-
271
- def _add_category_whitelists_to_metadata(cfg: CfgNode) -> None:
272
- for dataset_name, whitelisted_cat_ids in cfg.DATASETS.WHITELISTED_CATEGORIES.items():
273
- meta = MetadataCatalog.get(dataset_name)
274
- meta.whitelisted_categories = whitelisted_cat_ids
275
- logger = logging.getLogger(__name__)
276
- logger.info(
277
- "Whitelisted categories for dataset {}: {}".format(
278
- dataset_name, meta.whitelisted_categories
279
- )
280
- )
281
-
282
-
283
- def _add_category_maps_to_metadata(cfg: CfgNode) -> None:
284
- for dataset_name, category_map in cfg.DATASETS.CATEGORY_MAPS.items():
285
- category_map = {
286
- int(cat_id_src): int(cat_id_dst) for cat_id_src, cat_id_dst in category_map.items()
287
- }
288
- meta = MetadataCatalog.get(dataset_name)
289
- meta.category_map = category_map
290
- logger = logging.getLogger(__name__)
291
- logger.info("Category maps for dataset {}: {}".format(dataset_name, meta.category_map))
292
-
293
-
294
- def _add_category_info_to_bootstrapping_metadata(dataset_name: str, dataset_cfg: CfgNode) -> None:
295
- meta = MetadataCatalog.get(dataset_name)
296
- meta.category_to_class_mapping = get_category_to_class_mapping(dataset_cfg)
297
- meta.categories = dataset_cfg.CATEGORIES
298
- meta.max_count_per_category = dataset_cfg.MAX_COUNT_PER_CATEGORY
299
- logger = logging.getLogger(__name__)
300
- logger.info(
301
- "Category to class mapping for dataset {}: {}".format(
302
- dataset_name, meta.category_to_class_mapping
303
- )
304
- )
305
-
306
-
307
- def _maybe_add_class_to_mesh_name_map_to_metadata(dataset_names: List[str], cfg: CfgNode) -> None:
308
- for dataset_name in dataset_names:
309
- meta = MetadataCatalog.get(dataset_name)
310
- if not hasattr(meta, "class_to_mesh_name"):
311
- meta.class_to_mesh_name = get_class_to_mesh_name_mapping(cfg)
312
-
313
-
314
- def _merge_categories(dataset_names: Collection[str]) -> _MergedCategoriesT:
315
- merged_categories = defaultdict(list)
316
- category_names = {}
317
- for dataset_name in dataset_names:
318
- meta = MetadataCatalog.get(dataset_name)
319
- whitelisted_categories = meta.get("whitelisted_categories")
320
- category_map = meta.get("category_map", {})
321
- cat_ids = (
322
- whitelisted_categories if whitelisted_categories is not None else meta.categories.keys()
323
- )
324
- for cat_id in cat_ids:
325
- cat_name = meta.categories[cat_id]
326
- cat_id_mapped = category_map.get(cat_id, cat_id)
327
- if cat_id_mapped == cat_id or cat_id_mapped in cat_ids:
328
- category_names[cat_id] = cat_name
329
- else:
330
- category_names[cat_id] = str(cat_id_mapped)
331
- # assign temporary mapped category name, this name can be changed
332
- # during the second pass, since mapped ID can correspond to a category
333
- # from a different dataset
334
- cat_name_mapped = meta.categories[cat_id_mapped]
335
- merged_categories[cat_id_mapped].append(
336
- _DatasetCategory(
337
- id=cat_id,
338
- name=cat_name,
339
- mapped_id=cat_id_mapped,
340
- mapped_name=cat_name_mapped,
341
- dataset_name=dataset_name,
342
- )
343
- )
344
- # second pass to assign proper mapped category names
345
- for cat_id, categories in merged_categories.items():
346
- for cat in categories:
347
- if cat_id in category_names and cat.mapped_name != category_names[cat_id]:
348
- cat.mapped_name = category_names[cat_id]
349
-
350
- return merged_categories
351
-
352
-
353
- def _warn_if_merged_different_categories(merged_categories: _MergedCategoriesT) -> None:
354
- logger = logging.getLogger(__name__)
355
- for cat_id in merged_categories:
356
- merged_categories_i = merged_categories[cat_id]
357
- first_cat_name = merged_categories_i[0].name
358
- if len(merged_categories_i) > 1 and not all(
359
- cat.name == first_cat_name for cat in merged_categories_i[1:]
360
- ):
361
- cat_summary_str = ", ".join(
362
- [f"{cat.id} ({cat.name}) from {cat.dataset_name}" for cat in merged_categories_i]
363
- )
364
- logger.warning(
365
- f"Merged category {cat_id} corresponds to the following categories: "
366
- f"{cat_summary_str}"
367
- )
368
-
369
-
370
- def combine_detection_dataset_dicts(
371
- dataset_names: Collection[str],
372
- keep_instance_predicate: Optional[InstancePredicate] = None,
373
- proposal_files: Optional[Collection[str]] = None,
374
- ) -> List[Instance]:
375
- """
376
- Load and prepare dataset dicts for training / testing
377
-
378
- Args:
379
- dataset_names (Collection[str]): a list of dataset names
380
- keep_instance_predicate (Callable: Dict[str, Any] -> bool): predicate
381
- applied to instance dicts which defines whether to keep the instance
382
- proposal_files (Collection[str]): if given, a list of object proposal files
383
- that match each dataset in `dataset_names`.
384
- """
385
- assert len(dataset_names)
386
- if proposal_files is None:
387
- proposal_files = [None] * len(dataset_names)
388
- assert len(dataset_names) == len(proposal_files)
389
- # load datasets and metadata
390
- dataset_name_to_dicts = {}
391
- for dataset_name in dataset_names:
392
- dataset_name_to_dicts[dataset_name] = DatasetCatalog.get(dataset_name)
393
- assert len(dataset_name_to_dicts), f"Dataset '{dataset_name}' is empty!"
394
- # merge categories, requires category metadata to be loaded
395
- # cat_id -> [(orig_cat_id, cat_name, dataset_name)]
396
- merged_categories = _merge_categories(dataset_names)
397
- _warn_if_merged_different_categories(merged_categories)
398
- merged_category_names = [
399
- merged_categories[cat_id][0].mapped_name for cat_id in sorted(merged_categories)
400
- ]
401
- # map to contiguous category IDs
402
- _add_category_id_to_contiguous_id_maps_to_metadata(merged_categories)
403
- # load annotations and dataset metadata
404
- for dataset_name, proposal_file in zip(dataset_names, proposal_files):
405
- dataset_dicts = dataset_name_to_dicts[dataset_name]
406
- assert len(dataset_dicts), f"Dataset '{dataset_name}' is empty!"
407
- if proposal_file is not None:
408
- dataset_dicts = load_proposals_into_dataset(dataset_dicts, proposal_file)
409
- dataset_dicts = _maybe_filter_and_map_categories(dataset_name, dataset_dicts)
410
- print_instances_class_histogram(dataset_dicts, merged_category_names)
411
- dataset_name_to_dicts[dataset_name] = dataset_dicts
412
-
413
- if keep_instance_predicate is not None:
414
- all_datasets_dicts_plain = [
415
- d
416
- for d in itertools.chain.from_iterable(dataset_name_to_dicts.values())
417
- if keep_instance_predicate(d)
418
- ]
419
- else:
420
- all_datasets_dicts_plain = list(
421
- itertools.chain.from_iterable(dataset_name_to_dicts.values())
422
- )
423
- return all_datasets_dicts_plain
424
-
425
-
426
- def build_detection_train_loader(cfg: CfgNode, mapper=None):
427
- """
428
- A data loader is created in a way similar to that of Detectron2.
429
- The main differences are:
430
- - it allows to combine datasets with different but compatible object category sets
431
-
432
- The data loader is created by the following steps:
433
- 1. Use the dataset names in config to query :class:`DatasetCatalog`, and obtain a list of dicts.
434
- 2. Start workers to work on the dicts. Each worker will:
435
- * Map each metadata dict into another format to be consumed by the model.
436
- * Batch them by simply putting dicts into a list.
437
- The batched ``list[mapped_dict]`` is what this dataloader will return.
438
-
439
- Args:
440
- cfg (CfgNode): the config
441
- mapper (callable): a callable which takes a sample (dict) from dataset and
442
- returns the format to be consumed by the model.
443
- By default it will be `DatasetMapper(cfg, True)`.
444
-
445
- Returns:
446
- an infinite iterator of training data
447
- """
448
-
449
- _add_category_whitelists_to_metadata(cfg)
450
- _add_category_maps_to_metadata(cfg)
451
- _maybe_add_class_to_mesh_name_map_to_metadata(cfg.DATASETS.TRAIN, cfg)
452
- dataset_dicts = combine_detection_dataset_dicts(
453
- cfg.DATASETS.TRAIN,
454
- keep_instance_predicate=_get_train_keep_instance_predicate(cfg),
455
- proposal_files=cfg.DATASETS.PROPOSAL_FILES_TRAIN if cfg.MODEL.LOAD_PROPOSALS else None,
456
- )
457
- if mapper is None:
458
- mapper = DatasetMapper(cfg, True)
459
- return d2_build_detection_train_loader(cfg, dataset=dataset_dicts, mapper=mapper)
460
-
461
-
462
- def build_detection_test_loader(cfg, dataset_name, mapper=None):
463
- """
464
- Similar to `build_detection_train_loader`.
465
- But this function uses the given `dataset_name` argument (instead of the names in cfg),
466
- and uses batch size 1.
467
-
468
- Args:
469
- cfg: a detectron2 CfgNode
470
- dataset_name (str): a name of the dataset that's available in the DatasetCatalog
471
- mapper (callable): a callable which takes a sample (dict) from dataset
472
- and returns the format to be consumed by the model.
473
- By default it will be `DatasetMapper(cfg, False)`.
474
-
475
- Returns:
476
- DataLoader: a torch DataLoader, that loads the given detection
477
- dataset, with test-time transformation and batching.
478
- """
479
- _add_category_whitelists_to_metadata(cfg)
480
- _add_category_maps_to_metadata(cfg)
481
- _maybe_add_class_to_mesh_name_map_to_metadata([dataset_name], cfg)
482
- dataset_dicts = combine_detection_dataset_dicts(
483
- [dataset_name],
484
- keep_instance_predicate=_get_test_keep_instance_predicate(cfg),
485
- proposal_files=[
486
- cfg.DATASETS.PROPOSAL_FILES_TEST[list(cfg.DATASETS.TEST).index(dataset_name)]
487
- ]
488
- if cfg.MODEL.LOAD_PROPOSALS
489
- else None,
490
- )
491
- sampler = None
492
- if not cfg.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE:
493
- sampler = torch.utils.data.SequentialSampler(dataset_dicts)
494
- if mapper is None:
495
- mapper = DatasetMapper(cfg, False)
496
- return d2_build_detection_test_loader(
497
- dataset_dicts, mapper=mapper, num_workers=cfg.DATALOADER.NUM_WORKERS, sampler=sampler
498
- )
499
-
500
-
501
- def build_frame_selector(cfg: CfgNode):
502
- strategy = FrameSelectionStrategy(cfg.STRATEGY)
503
- if strategy == FrameSelectionStrategy.RANDOM_K:
504
- frame_selector = RandomKFramesSelector(cfg.NUM_IMAGES)
505
- elif strategy == FrameSelectionStrategy.FIRST_K:
506
- frame_selector = FirstKFramesSelector(cfg.NUM_IMAGES)
507
- elif strategy == FrameSelectionStrategy.LAST_K:
508
- frame_selector = LastKFramesSelector(cfg.NUM_IMAGES)
509
- elif strategy == FrameSelectionStrategy.ALL:
510
- frame_selector = None
511
- # pyre-fixme[61]: `frame_selector` may not be initialized here.
512
- return frame_selector
513
-
514
-
515
- def build_transform(cfg: CfgNode, data_type: str):
516
- if cfg.TYPE == "resize":
517
- if data_type == "image":
518
- return ImageResizeTransform(cfg.MIN_SIZE, cfg.MAX_SIZE)
519
- raise ValueError(f"Unknown transform {cfg.TYPE} for data type {data_type}")
520
-
521
-
522
- def build_combined_loader(cfg: CfgNode, loaders: Collection[Loader], ratios: Sequence[float]):
523
- images_per_worker = _compute_num_images_per_worker(cfg)
524
- return CombinedDataLoader(loaders, images_per_worker, ratios)
525
-
526
-
527
- def build_bootstrap_dataset(dataset_name: str, cfg: CfgNode) -> Sequence[torch.Tensor]:
528
- """
529
- Build dataset that provides data to bootstrap on
530
-
531
- Args:
532
- dataset_name (str): Name of the dataset, needs to have associated metadata
533
- to load the data
534
- cfg (CfgNode): bootstrapping config
535
- Returns:
536
- Sequence[Tensor] - dataset that provides image batches, Tensors of size
537
- [N, C, H, W] of type float32
538
- """
539
- logger = logging.getLogger(__name__)
540
- _add_category_info_to_bootstrapping_metadata(dataset_name, cfg)
541
- meta = MetadataCatalog.get(dataset_name)
542
- factory = BootstrapDatasetFactoryCatalog.get(meta.dataset_type)
543
- dataset = None
544
- if factory is not None:
545
- dataset = factory(meta, cfg)
546
- if dataset is None:
547
- logger.warning(f"Failed to create dataset {dataset_name} of type {meta.dataset_type}")
548
- return dataset
549
-
550
-
551
- def build_data_sampler(cfg: CfgNode, sampler_cfg: CfgNode, embedder: Optional[torch.nn.Module]):
552
- if sampler_cfg.TYPE == "densepose_uniform":
553
- data_sampler = PredictionToGroundTruthSampler()
554
- # transform densepose pred -> gt
555
- data_sampler.register_sampler(
556
- "pred_densepose",
557
- "gt_densepose",
558
- DensePoseUniformSampler(count_per_class=sampler_cfg.COUNT_PER_CLASS),
559
- )
560
- data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
561
- return data_sampler
562
- elif sampler_cfg.TYPE == "densepose_UV_confidence":
563
- data_sampler = PredictionToGroundTruthSampler()
564
- # transform densepose pred -> gt
565
- data_sampler.register_sampler(
566
- "pred_densepose",
567
- "gt_densepose",
568
- DensePoseConfidenceBasedSampler(
569
- confidence_channel="sigma_2",
570
- count_per_class=sampler_cfg.COUNT_PER_CLASS,
571
- search_proportion=0.5,
572
- ),
573
- )
574
- data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
575
- return data_sampler
576
- elif sampler_cfg.TYPE == "densepose_fine_segm_confidence":
577
- data_sampler = PredictionToGroundTruthSampler()
578
- # transform densepose pred -> gt
579
- data_sampler.register_sampler(
580
- "pred_densepose",
581
- "gt_densepose",
582
- DensePoseConfidenceBasedSampler(
583
- confidence_channel="fine_segm_confidence",
584
- count_per_class=sampler_cfg.COUNT_PER_CLASS,
585
- search_proportion=0.5,
586
- ),
587
- )
588
- data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
589
- return data_sampler
590
- elif sampler_cfg.TYPE == "densepose_coarse_segm_confidence":
591
- data_sampler = PredictionToGroundTruthSampler()
592
- # transform densepose pred -> gt
593
- data_sampler.register_sampler(
594
- "pred_densepose",
595
- "gt_densepose",
596
- DensePoseConfidenceBasedSampler(
597
- confidence_channel="coarse_segm_confidence",
598
- count_per_class=sampler_cfg.COUNT_PER_CLASS,
599
- search_proportion=0.5,
600
- ),
601
- )
602
- data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
603
- return data_sampler
604
- elif sampler_cfg.TYPE == "densepose_cse_uniform":
605
- assert embedder is not None
606
- data_sampler = PredictionToGroundTruthSampler()
607
- # transform densepose pred -> gt
608
- data_sampler.register_sampler(
609
- "pred_densepose",
610
- "gt_densepose",
611
- DensePoseCSEUniformSampler(
612
- cfg=cfg,
613
- use_gt_categories=sampler_cfg.USE_GROUND_TRUTH_CATEGORIES,
614
- embedder=embedder,
615
- count_per_class=sampler_cfg.COUNT_PER_CLASS,
616
- ),
617
- )
618
- data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
619
- return data_sampler
620
- elif sampler_cfg.TYPE == "densepose_cse_coarse_segm_confidence":
621
- assert embedder is not None
622
- data_sampler = PredictionToGroundTruthSampler()
623
- # transform densepose pred -> gt
624
- data_sampler.register_sampler(
625
- "pred_densepose",
626
- "gt_densepose",
627
- DensePoseCSEConfidenceBasedSampler(
628
- cfg=cfg,
629
- use_gt_categories=sampler_cfg.USE_GROUND_TRUTH_CATEGORIES,
630
- embedder=embedder,
631
- confidence_channel="coarse_segm_confidence",
632
- count_per_class=sampler_cfg.COUNT_PER_CLASS,
633
- search_proportion=0.5,
634
- ),
635
- )
636
- data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
637
- return data_sampler
638
-
639
- raise ValueError(f"Unknown data sampler type {sampler_cfg.TYPE}")
640
-
641
-
642
- def build_data_filter(cfg: CfgNode):
643
- if cfg.TYPE == "detection_score":
644
- min_score = cfg.MIN_VALUE
645
- return ScoreBasedFilter(min_score=min_score)
646
- raise ValueError(f"Unknown data filter type {cfg.TYPE}")
647
-
648
-
649
- def build_inference_based_loader(
650
- cfg: CfgNode,
651
- dataset_cfg: CfgNode,
652
- model: torch.nn.Module,
653
- embedder: Optional[torch.nn.Module] = None,
654
- ) -> InferenceBasedLoader:
655
- """
656
- Constructs data loader based on inference results of a model.
657
- """
658
- dataset = build_bootstrap_dataset(dataset_cfg.DATASET, dataset_cfg.IMAGE_LOADER)
659
- meta = MetadataCatalog.get(dataset_cfg.DATASET)
660
- training_sampler = TrainingSampler(len(dataset))
661
- data_loader = torch.utils.data.DataLoader(
662
- dataset, # pyre-ignore[6]
663
- batch_size=dataset_cfg.IMAGE_LOADER.BATCH_SIZE,
664
- sampler=training_sampler,
665
- num_workers=dataset_cfg.IMAGE_LOADER.NUM_WORKERS,
666
- collate_fn=trivial_batch_collator,
667
- worker_init_fn=worker_init_reset_seed,
668
- )
669
- return InferenceBasedLoader(
670
- model,
671
- data_loader=data_loader,
672
- data_sampler=build_data_sampler(cfg, dataset_cfg.DATA_SAMPLER, embedder),
673
- data_filter=build_data_filter(dataset_cfg.FILTER),
674
- shuffle=True,
675
- batch_size=dataset_cfg.INFERENCE.OUTPUT_BATCH_SIZE,
676
- inference_batch_size=dataset_cfg.INFERENCE.INPUT_BATCH_SIZE,
677
- category_to_class_mapping=meta.category_to_class_mapping,
678
- )
679
-
680
-
681
- def has_inference_based_loaders(cfg: CfgNode) -> bool:
682
- """
683
- Returns True, if at least one inferense-based loader must
684
- be instantiated for training
685
- """
686
- return len(cfg.BOOTSTRAP_DATASETS) > 0
687
-
688
-
689
- def build_inference_based_loaders(
690
- cfg: CfgNode, model: torch.nn.Module
691
- ) -> Tuple[List[InferenceBasedLoader], List[float]]:
692
- loaders = []
693
- ratios = []
694
- embedder = build_densepose_embedder(cfg).to(device=model.device) # pyre-ignore[16]
695
- for dataset_spec in cfg.BOOTSTRAP_DATASETS:
696
- dataset_cfg = get_bootstrap_dataset_config().clone()
697
- dataset_cfg.merge_from_other_cfg(CfgNode(dataset_spec))
698
- loader = build_inference_based_loader(cfg, dataset_cfg, model, embedder)
699
- loaders.append(loader)
700
- ratios.append(dataset_cfg.RATIO)
701
- return loaders, ratios
702
-
703
-
704
- def build_video_list_dataset(meta: Metadata, cfg: CfgNode):
705
- video_list_fpath = meta.video_list_fpath
706
- video_base_path = meta.video_base_path
707
- category = meta.category
708
- if cfg.TYPE == "video_keyframe":
709
- frame_selector = build_frame_selector(cfg.SELECT)
710
- transform = build_transform(cfg.TRANSFORM, data_type="image")
711
- video_list = video_list_from_file(video_list_fpath, video_base_path)
712
- keyframe_helper_fpath = getattr(cfg, "KEYFRAME_HELPER", None)
713
- return VideoKeyframeDataset(
714
- video_list, category, frame_selector, transform, keyframe_helper_fpath
715
- )
716
-
717
-
718
- class _BootstrapDatasetFactoryCatalog(UserDict):
719
- """
720
- A global dictionary that stores information about bootstrapped datasets creation functions
721
- from metadata and config, for diverse DatasetType
722
- """
723
-
724
- def register(self, dataset_type: DatasetType, factory: Callable[[Metadata, CfgNode], Dataset]):
725
- """
726
- Args:
727
- dataset_type (DatasetType): a DatasetType e.g. DatasetType.VIDEO_LIST
728
- factory (Callable[Metadata, CfgNode]): a callable which takes Metadata and cfg
729
- arguments and returns a dataset object.
730
- """
731
- assert dataset_type not in self, "Dataset '{}' is already registered!".format(dataset_type)
732
- self[dataset_type] = factory
733
-
734
-
735
- BootstrapDatasetFactoryCatalog = _BootstrapDatasetFactoryCatalog()
736
- BootstrapDatasetFactoryCatalog.register(DatasetType.VIDEO_LIST, build_video_list_dataset)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import itertools
4
+ import logging
5
+ import numpy as np
6
+ from collections import UserDict, defaultdict
7
+ from dataclasses import dataclass
8
+ from typing import Any, Callable, Collection, Dict, Iterable, List, Optional, Sequence, Tuple
9
+ import torch
10
+ from torch.utils.data.dataset import Dataset
11
+
12
+ from detectron2.config import CfgNode
13
+ from detectron2.data.build import build_detection_test_loader as d2_build_detection_test_loader
14
+ from detectron2.data.build import build_detection_train_loader as d2_build_detection_train_loader
15
+ from detectron2.data.build import (
16
+ load_proposals_into_dataset,
17
+ print_instances_class_histogram,
18
+ trivial_batch_collator,
19
+ worker_init_reset_seed,
20
+ )
21
+ from detectron2.data.catalog import DatasetCatalog, Metadata, MetadataCatalog
22
+ from detectron2.data.samplers import TrainingSampler
23
+ from detectron2.utils.comm import get_world_size
24
+
25
+ from densepose.config import get_bootstrap_dataset_config
26
+ from densepose.modeling import build_densepose_embedder
27
+
28
+ from .combined_loader import CombinedDataLoader, Loader
29
+ from .dataset_mapper import DatasetMapper
30
+ from .datasets.coco import DENSEPOSE_CSE_KEYS_WITHOUT_MASK, DENSEPOSE_IUV_KEYS_WITHOUT_MASK
31
+ from .datasets.dataset_type import DatasetType
32
+ from .inference_based_loader import InferenceBasedLoader, ScoreBasedFilter
33
+ from .samplers import (
34
+ DensePoseConfidenceBasedSampler,
35
+ DensePoseCSEConfidenceBasedSampler,
36
+ DensePoseCSEUniformSampler,
37
+ DensePoseUniformSampler,
38
+ MaskFromDensePoseSampler,
39
+ PredictionToGroundTruthSampler,
40
+ )
41
+ from .transform import ImageResizeTransform
42
+ from .utils import get_category_to_class_mapping, get_class_to_mesh_name_mapping
43
+ from .video import (
44
+ FirstKFramesSelector,
45
+ FrameSelectionStrategy,
46
+ LastKFramesSelector,
47
+ RandomKFramesSelector,
48
+ VideoKeyframeDataset,
49
+ video_list_from_file,
50
+ )
51
+
52
+ __all__ = ["build_detection_train_loader", "build_detection_test_loader"]
53
+
54
+
55
+ Instance = Dict[str, Any]
56
+ InstancePredicate = Callable[[Instance], bool]
57
+
58
+
59
+ def _compute_num_images_per_worker(cfg: CfgNode) -> int:
60
+ num_workers = get_world_size()
61
+ images_per_batch = cfg.SOLVER.IMS_PER_BATCH
62
+ assert (
63
+ images_per_batch % num_workers == 0
64
+ ), "SOLVER.IMS_PER_BATCH ({}) must be divisible by the number of workers ({}).".format(
65
+ images_per_batch, num_workers
66
+ )
67
+ assert (
68
+ images_per_batch >= num_workers
69
+ ), "SOLVER.IMS_PER_BATCH ({}) must be larger than the number of workers ({}).".format(
70
+ images_per_batch, num_workers
71
+ )
72
+ images_per_worker = images_per_batch // num_workers
73
+ return images_per_worker
74
+
75
+
76
+ def _map_category_id_to_contiguous_id(dataset_name: str, dataset_dicts: Iterable[Instance]) -> None:
77
+ meta = MetadataCatalog.get(dataset_name)
78
+ for dataset_dict in dataset_dicts:
79
+ for ann in dataset_dict["annotations"]:
80
+ ann["category_id"] = meta.thing_dataset_id_to_contiguous_id[ann["category_id"]]
81
+
82
+
83
+ @dataclass
84
+ class _DatasetCategory:
85
+ """
86
+ Class representing category data in a dataset:
87
+ - id: category ID, as specified in the dataset annotations file
88
+ - name: category name, as specified in the dataset annotations file
89
+ - mapped_id: category ID after applying category maps (DATASETS.CATEGORY_MAPS config option)
90
+ - mapped_name: category name after applying category maps
91
+ - dataset_name: dataset in which the category is defined
92
+
93
+ For example, when training models in a class-agnostic manner, one could take LVIS 1.0
94
+ dataset and map the animal categories to the same category as human data from COCO:
95
+ id = 225
96
+ name = "cat"
97
+ mapped_id = 1
98
+ mapped_name = "person"
99
+ dataset_name = "lvis_v1_animals_dp_train"
100
+ """
101
+
102
+ id: int
103
+ name: str
104
+ mapped_id: int
105
+ mapped_name: str
106
+ dataset_name: str
107
+
108
+
109
+ _MergedCategoriesT = Dict[int, List[_DatasetCategory]]
110
+
111
+
112
+ def _add_category_id_to_contiguous_id_maps_to_metadata(
113
+ merged_categories: _MergedCategoriesT,
114
+ ) -> None:
115
+ merged_categories_per_dataset = {}
116
+ for contiguous_cat_id, cat_id in enumerate(sorted(merged_categories.keys())):
117
+ for cat in merged_categories[cat_id]:
118
+ if cat.dataset_name not in merged_categories_per_dataset:
119
+ merged_categories_per_dataset[cat.dataset_name] = defaultdict(list)
120
+ merged_categories_per_dataset[cat.dataset_name][cat_id].append(
121
+ (
122
+ contiguous_cat_id,
123
+ cat,
124
+ )
125
+ )
126
+
127
+ logger = logging.getLogger(__name__)
128
+ for dataset_name, merged_categories in merged_categories_per_dataset.items():
129
+ meta = MetadataCatalog.get(dataset_name)
130
+ if not hasattr(meta, "thing_classes"):
131
+ meta.thing_classes = []
132
+ meta.thing_dataset_id_to_contiguous_id = {}
133
+ meta.thing_dataset_id_to_merged_id = {}
134
+ else:
135
+ meta.thing_classes.clear()
136
+ meta.thing_dataset_id_to_contiguous_id.clear()
137
+ meta.thing_dataset_id_to_merged_id.clear()
138
+ logger.info(f"Dataset {dataset_name}: category ID to contiguous ID mapping:")
139
+ for _cat_id, categories in sorted(merged_categories.items()):
140
+ added_to_thing_classes = False
141
+ for contiguous_cat_id, cat in categories:
142
+ if not added_to_thing_classes:
143
+ meta.thing_classes.append(cat.mapped_name)
144
+ added_to_thing_classes = True
145
+ meta.thing_dataset_id_to_contiguous_id[cat.id] = contiguous_cat_id
146
+ meta.thing_dataset_id_to_merged_id[cat.id] = cat.mapped_id
147
+ logger.info(f"{cat.id} ({cat.name}) -> {contiguous_cat_id}")
148
+
149
+
150
+ def _maybe_create_general_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
151
+ def has_annotations(instance: Instance) -> bool:
152
+ return "annotations" in instance
153
+
154
+ def has_only_crowd_anotations(instance: Instance) -> bool:
155
+ for ann in instance["annotations"]:
156
+ if ann.get("is_crowd", 0) == 0:
157
+ return False
158
+ return True
159
+
160
+ def general_keep_instance_predicate(instance: Instance) -> bool:
161
+ return has_annotations(instance) and not has_only_crowd_anotations(instance)
162
+
163
+ if not cfg.DATALOADER.FILTER_EMPTY_ANNOTATIONS:
164
+ return None
165
+ return general_keep_instance_predicate
166
+
167
+
168
+ def _maybe_create_keypoints_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
169
+
170
+ min_num_keypoints = cfg.MODEL.ROI_KEYPOINT_HEAD.MIN_KEYPOINTS_PER_IMAGE
171
+
172
+ def has_sufficient_num_keypoints(instance: Instance) -> bool:
173
+ num_kpts = sum(
174
+ (np.array(ann["keypoints"][2::3]) > 0).sum()
175
+ for ann in instance["annotations"]
176
+ if "keypoints" in ann
177
+ )
178
+ return num_kpts >= min_num_keypoints
179
+
180
+ if cfg.MODEL.KEYPOINT_ON and (min_num_keypoints > 0):
181
+ return has_sufficient_num_keypoints
182
+ return None
183
+
184
+
185
+ def _maybe_create_mask_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
186
+ if not cfg.MODEL.MASK_ON:
187
+ return None
188
+
189
+ def has_mask_annotations(instance: Instance) -> bool:
190
+ return any("segmentation" in ann for ann in instance["annotations"])
191
+
192
+ return has_mask_annotations
193
+
194
+
195
+ def _maybe_create_densepose_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
196
+ if not cfg.MODEL.DENSEPOSE_ON:
197
+ return None
198
+
199
+ use_masks = cfg.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS
200
+
201
+ def has_densepose_annotations(instance: Instance) -> bool:
202
+ for ann in instance["annotations"]:
203
+ if all(key in ann for key in DENSEPOSE_IUV_KEYS_WITHOUT_MASK) or all(
204
+ key in ann for key in DENSEPOSE_CSE_KEYS_WITHOUT_MASK
205
+ ):
206
+ return True
207
+ if use_masks and "segmentation" in ann:
208
+ return True
209
+ return False
210
+
211
+ return has_densepose_annotations
212
+
213
+
214
+ def _maybe_create_specific_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
215
+ specific_predicate_creators = [
216
+ _maybe_create_keypoints_keep_instance_predicate,
217
+ _maybe_create_mask_keep_instance_predicate,
218
+ _maybe_create_densepose_keep_instance_predicate,
219
+ ]
220
+ predicates = [creator(cfg) for creator in specific_predicate_creators]
221
+ predicates = [p for p in predicates if p is not None]
222
+ if not predicates:
223
+ return None
224
+
225
+ def combined_predicate(instance: Instance) -> bool:
226
+ return any(p(instance) for p in predicates)
227
+
228
+ return combined_predicate
229
+
230
+
231
+ def _get_train_keep_instance_predicate(cfg: CfgNode):
232
+ general_keep_predicate = _maybe_create_general_keep_instance_predicate(cfg)
233
+ combined_specific_keep_predicate = _maybe_create_specific_keep_instance_predicate(cfg)
234
+
235
+ def combined_general_specific_keep_predicate(instance: Instance) -> bool:
236
+ return general_keep_predicate(instance) and combined_specific_keep_predicate(instance)
237
+
238
+ if (general_keep_predicate is None) and (combined_specific_keep_predicate is None):
239
+ return None
240
+ if general_keep_predicate is None:
241
+ return combined_specific_keep_predicate
242
+ if combined_specific_keep_predicate is None:
243
+ return general_keep_predicate
244
+ return combined_general_specific_keep_predicate
245
+
246
+
247
+ def _get_test_keep_instance_predicate(cfg: CfgNode):
248
+ general_keep_predicate = _maybe_create_general_keep_instance_predicate(cfg)
249
+ return general_keep_predicate
250
+
251
+
252
+ def _maybe_filter_and_map_categories(
253
+ dataset_name: str, dataset_dicts: List[Instance]
254
+ ) -> List[Instance]:
255
+ meta = MetadataCatalog.get(dataset_name)
256
+ category_id_map = meta.thing_dataset_id_to_contiguous_id
257
+ filtered_dataset_dicts = []
258
+ for dataset_dict in dataset_dicts:
259
+ anns = []
260
+ for ann in dataset_dict["annotations"]:
261
+ cat_id = ann["category_id"]
262
+ if cat_id not in category_id_map:
263
+ continue
264
+ ann["category_id"] = category_id_map[cat_id]
265
+ anns.append(ann)
266
+ dataset_dict["annotations"] = anns
267
+ filtered_dataset_dicts.append(dataset_dict)
268
+ return filtered_dataset_dicts
269
+
270
+
271
+ def _add_category_whitelists_to_metadata(cfg: CfgNode) -> None:
272
+ for dataset_name, whitelisted_cat_ids in cfg.DATASETS.WHITELISTED_CATEGORIES.items():
273
+ meta = MetadataCatalog.get(dataset_name)
274
+ meta.whitelisted_categories = whitelisted_cat_ids
275
+ logger = logging.getLogger(__name__)
276
+ logger.info(
277
+ "Whitelisted categories for dataset {}: {}".format(
278
+ dataset_name, meta.whitelisted_categories
279
+ )
280
+ )
281
+
282
+
283
+ def _add_category_maps_to_metadata(cfg: CfgNode) -> None:
284
+ for dataset_name, category_map in cfg.DATASETS.CATEGORY_MAPS.items():
285
+ category_map = {
286
+ int(cat_id_src): int(cat_id_dst) for cat_id_src, cat_id_dst in category_map.items()
287
+ }
288
+ meta = MetadataCatalog.get(dataset_name)
289
+ meta.category_map = category_map
290
+ logger = logging.getLogger(__name__)
291
+ logger.info("Category maps for dataset {}: {}".format(dataset_name, meta.category_map))
292
+
293
+
294
+ def _add_category_info_to_bootstrapping_metadata(dataset_name: str, dataset_cfg: CfgNode) -> None:
295
+ meta = MetadataCatalog.get(dataset_name)
296
+ meta.category_to_class_mapping = get_category_to_class_mapping(dataset_cfg)
297
+ meta.categories = dataset_cfg.CATEGORIES
298
+ meta.max_count_per_category = dataset_cfg.MAX_COUNT_PER_CATEGORY
299
+ logger = logging.getLogger(__name__)
300
+ logger.info(
301
+ "Category to class mapping for dataset {}: {}".format(
302
+ dataset_name, meta.category_to_class_mapping
303
+ )
304
+ )
305
+
306
+
307
+ def _maybe_add_class_to_mesh_name_map_to_metadata(dataset_names: List[str], cfg: CfgNode) -> None:
308
+ for dataset_name in dataset_names:
309
+ meta = MetadataCatalog.get(dataset_name)
310
+ if not hasattr(meta, "class_to_mesh_name"):
311
+ meta.class_to_mesh_name = get_class_to_mesh_name_mapping(cfg)
312
+
313
+
314
+ def _merge_categories(dataset_names: Collection[str]) -> _MergedCategoriesT:
315
+ merged_categories = defaultdict(list)
316
+ category_names = {}
317
+ for dataset_name in dataset_names:
318
+ meta = MetadataCatalog.get(dataset_name)
319
+ whitelisted_categories = meta.get("whitelisted_categories")
320
+ category_map = meta.get("category_map", {})
321
+ cat_ids = (
322
+ whitelisted_categories if whitelisted_categories is not None else meta.categories.keys()
323
+ )
324
+ for cat_id in cat_ids:
325
+ cat_name = meta.categories[cat_id]
326
+ cat_id_mapped = category_map.get(cat_id, cat_id)
327
+ if cat_id_mapped == cat_id or cat_id_mapped in cat_ids:
328
+ category_names[cat_id] = cat_name
329
+ else:
330
+ category_names[cat_id] = str(cat_id_mapped)
331
+ # assign temporary mapped category name, this name can be changed
332
+ # during the second pass, since mapped ID can correspond to a category
333
+ # from a different dataset
334
+ cat_name_mapped = meta.categories[cat_id_mapped]
335
+ merged_categories[cat_id_mapped].append(
336
+ _DatasetCategory(
337
+ id=cat_id,
338
+ name=cat_name,
339
+ mapped_id=cat_id_mapped,
340
+ mapped_name=cat_name_mapped,
341
+ dataset_name=dataset_name,
342
+ )
343
+ )
344
+ # second pass to assign proper mapped category names
345
+ for cat_id, categories in merged_categories.items():
346
+ for cat in categories:
347
+ if cat_id in category_names and cat.mapped_name != category_names[cat_id]:
348
+ cat.mapped_name = category_names[cat_id]
349
+
350
+ return merged_categories
351
+
352
+
353
+ def _warn_if_merged_different_categories(merged_categories: _MergedCategoriesT) -> None:
354
+ logger = logging.getLogger(__name__)
355
+ for cat_id in merged_categories:
356
+ merged_categories_i = merged_categories[cat_id]
357
+ first_cat_name = merged_categories_i[0].name
358
+ if len(merged_categories_i) > 1 and not all(
359
+ cat.name == first_cat_name for cat in merged_categories_i[1:]
360
+ ):
361
+ cat_summary_str = ", ".join(
362
+ [f"{cat.id} ({cat.name}) from {cat.dataset_name}" for cat in merged_categories_i]
363
+ )
364
+ logger.warning(
365
+ f"Merged category {cat_id} corresponds to the following categories: "
366
+ f"{cat_summary_str}"
367
+ )
368
+
369
+
370
+ def combine_detection_dataset_dicts(
371
+ dataset_names: Collection[str],
372
+ keep_instance_predicate: Optional[InstancePredicate] = None,
373
+ proposal_files: Optional[Collection[str]] = None,
374
+ ) -> List[Instance]:
375
+ """
376
+ Load and prepare dataset dicts for training / testing
377
+
378
+ Args:
379
+ dataset_names (Collection[str]): a list of dataset names
380
+ keep_instance_predicate (Callable: Dict[str, Any] -> bool): predicate
381
+ applied to instance dicts which defines whether to keep the instance
382
+ proposal_files (Collection[str]): if given, a list of object proposal files
383
+ that match each dataset in `dataset_names`.
384
+ """
385
+ assert len(dataset_names)
386
+ if proposal_files is None:
387
+ proposal_files = [None] * len(dataset_names)
388
+ assert len(dataset_names) == len(proposal_files)
389
+ # load datasets and metadata
390
+ dataset_name_to_dicts = {}
391
+ for dataset_name in dataset_names:
392
+ dataset_name_to_dicts[dataset_name] = DatasetCatalog.get(dataset_name)
393
+ assert len(dataset_name_to_dicts), f"Dataset '{dataset_name}' is empty!"
394
+ # merge categories, requires category metadata to be loaded
395
+ # cat_id -> [(orig_cat_id, cat_name, dataset_name)]
396
+ merged_categories = _merge_categories(dataset_names)
397
+ _warn_if_merged_different_categories(merged_categories)
398
+ merged_category_names = [
399
+ merged_categories[cat_id][0].mapped_name for cat_id in sorted(merged_categories)
400
+ ]
401
+ # map to contiguous category IDs
402
+ _add_category_id_to_contiguous_id_maps_to_metadata(merged_categories)
403
+ # load annotations and dataset metadata
404
+ for dataset_name, proposal_file in zip(dataset_names, proposal_files):
405
+ dataset_dicts = dataset_name_to_dicts[dataset_name]
406
+ assert len(dataset_dicts), f"Dataset '{dataset_name}' is empty!"
407
+ if proposal_file is not None:
408
+ dataset_dicts = load_proposals_into_dataset(dataset_dicts, proposal_file)
409
+ dataset_dicts = _maybe_filter_and_map_categories(dataset_name, dataset_dicts)
410
+ print_instances_class_histogram(dataset_dicts, merged_category_names)
411
+ dataset_name_to_dicts[dataset_name] = dataset_dicts
412
+
413
+ if keep_instance_predicate is not None:
414
+ all_datasets_dicts_plain = [
415
+ d
416
+ for d in itertools.chain.from_iterable(dataset_name_to_dicts.values())
417
+ if keep_instance_predicate(d)
418
+ ]
419
+ else:
420
+ all_datasets_dicts_plain = list(
421
+ itertools.chain.from_iterable(dataset_name_to_dicts.values())
422
+ )
423
+ return all_datasets_dicts_plain
424
+
425
+
426
+ def build_detection_train_loader(cfg: CfgNode, mapper=None):
427
+ """
428
+ A data loader is created in a way similar to that of Detectron2.
429
+ The main differences are:
430
+ - it allows to combine datasets with different but compatible object category sets
431
+
432
+ The data loader is created by the following steps:
433
+ 1. Use the dataset names in config to query :class:`DatasetCatalog`, and obtain a list of dicts.
434
+ 2. Start workers to work on the dicts. Each worker will:
435
+ * Map each metadata dict into another format to be consumed by the model.
436
+ * Batch them by simply putting dicts into a list.
437
+ The batched ``list[mapped_dict]`` is what this dataloader will return.
438
+
439
+ Args:
440
+ cfg (CfgNode): the config
441
+ mapper (callable): a callable which takes a sample (dict) from dataset and
442
+ returns the format to be consumed by the model.
443
+ By default it will be `DatasetMapper(cfg, True)`.
444
+
445
+ Returns:
446
+ an infinite iterator of training data
447
+ """
448
+
449
+ _add_category_whitelists_to_metadata(cfg)
450
+ _add_category_maps_to_metadata(cfg)
451
+ _maybe_add_class_to_mesh_name_map_to_metadata(cfg.DATASETS.TRAIN, cfg)
452
+ dataset_dicts = combine_detection_dataset_dicts(
453
+ cfg.DATASETS.TRAIN,
454
+ keep_instance_predicate=_get_train_keep_instance_predicate(cfg),
455
+ proposal_files=cfg.DATASETS.PROPOSAL_FILES_TRAIN if cfg.MODEL.LOAD_PROPOSALS else None,
456
+ )
457
+ if mapper is None:
458
+ mapper = DatasetMapper(cfg, True)
459
+ return d2_build_detection_train_loader(cfg, dataset=dataset_dicts, mapper=mapper)
460
+
461
+
462
+ def build_detection_test_loader(cfg, dataset_name, mapper=None):
463
+ """
464
+ Similar to `build_detection_train_loader`.
465
+ But this function uses the given `dataset_name` argument (instead of the names in cfg),
466
+ and uses batch size 1.
467
+
468
+ Args:
469
+ cfg: a detectron2 CfgNode
470
+ dataset_name (str): a name of the dataset that's available in the DatasetCatalog
471
+ mapper (callable): a callable which takes a sample (dict) from dataset
472
+ and returns the format to be consumed by the model.
473
+ By default it will be `DatasetMapper(cfg, False)`.
474
+
475
+ Returns:
476
+ DataLoader: a torch DataLoader, that loads the given detection
477
+ dataset, with test-time transformation and batching.
478
+ """
479
+ _add_category_whitelists_to_metadata(cfg)
480
+ _add_category_maps_to_metadata(cfg)
481
+ _maybe_add_class_to_mesh_name_map_to_metadata([dataset_name], cfg)
482
+ dataset_dicts = combine_detection_dataset_dicts(
483
+ [dataset_name],
484
+ keep_instance_predicate=_get_test_keep_instance_predicate(cfg),
485
+ proposal_files=[
486
+ cfg.DATASETS.PROPOSAL_FILES_TEST[list(cfg.DATASETS.TEST).index(dataset_name)]
487
+ ]
488
+ if cfg.MODEL.LOAD_PROPOSALS
489
+ else None,
490
+ )
491
+ sampler = None
492
+ if not cfg.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE:
493
+ sampler = torch.utils.data.SequentialSampler(dataset_dicts)
494
+ if mapper is None:
495
+ mapper = DatasetMapper(cfg, False)
496
+ return d2_build_detection_test_loader(
497
+ dataset_dicts, mapper=mapper, num_workers=cfg.DATALOADER.NUM_WORKERS, sampler=sampler
498
+ )
499
+
500
+
501
+ def build_frame_selector(cfg: CfgNode):
502
+ strategy = FrameSelectionStrategy(cfg.STRATEGY)
503
+ if strategy == FrameSelectionStrategy.RANDOM_K:
504
+ frame_selector = RandomKFramesSelector(cfg.NUM_IMAGES)
505
+ elif strategy == FrameSelectionStrategy.FIRST_K:
506
+ frame_selector = FirstKFramesSelector(cfg.NUM_IMAGES)
507
+ elif strategy == FrameSelectionStrategy.LAST_K:
508
+ frame_selector = LastKFramesSelector(cfg.NUM_IMAGES)
509
+ elif strategy == FrameSelectionStrategy.ALL:
510
+ frame_selector = None
511
+ # pyre-fixme[61]: `frame_selector` may not be initialized here.
512
+ return frame_selector
513
+
514
+
515
+ def build_transform(cfg: CfgNode, data_type: str):
516
+ if cfg.TYPE == "resize":
517
+ if data_type == "image":
518
+ return ImageResizeTransform(cfg.MIN_SIZE, cfg.MAX_SIZE)
519
+ raise ValueError(f"Unknown transform {cfg.TYPE} for data type {data_type}")
520
+
521
+
522
+ def build_combined_loader(cfg: CfgNode, loaders: Collection[Loader], ratios: Sequence[float]):
523
+ images_per_worker = _compute_num_images_per_worker(cfg)
524
+ return CombinedDataLoader(loaders, images_per_worker, ratios)
525
+
526
+
527
+ def build_bootstrap_dataset(dataset_name: str, cfg: CfgNode) -> Sequence[torch.Tensor]:
528
+ """
529
+ Build dataset that provides data to bootstrap on
530
+
531
+ Args:
532
+ dataset_name (str): Name of the dataset, needs to have associated metadata
533
+ to load the data
534
+ cfg (CfgNode): bootstrapping config
535
+ Returns:
536
+ Sequence[Tensor] - dataset that provides image batches, Tensors of size
537
+ [N, C, H, W] of type float32
538
+ """
539
+ logger = logging.getLogger(__name__)
540
+ _add_category_info_to_bootstrapping_metadata(dataset_name, cfg)
541
+ meta = MetadataCatalog.get(dataset_name)
542
+ factory = BootstrapDatasetFactoryCatalog.get(meta.dataset_type)
543
+ dataset = None
544
+ if factory is not None:
545
+ dataset = factory(meta, cfg)
546
+ if dataset is None:
547
+ logger.warning(f"Failed to create dataset {dataset_name} of type {meta.dataset_type}")
548
+ return dataset
549
+
550
+
551
+ def build_data_sampler(cfg: CfgNode, sampler_cfg: CfgNode, embedder: Optional[torch.nn.Module]):
552
+ if sampler_cfg.TYPE == "densepose_uniform":
553
+ data_sampler = PredictionToGroundTruthSampler()
554
+ # transform densepose pred -> gt
555
+ data_sampler.register_sampler(
556
+ "pred_densepose",
557
+ "gt_densepose",
558
+ DensePoseUniformSampler(count_per_class=sampler_cfg.COUNT_PER_CLASS),
559
+ )
560
+ data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
561
+ return data_sampler
562
+ elif sampler_cfg.TYPE == "densepose_UV_confidence":
563
+ data_sampler = PredictionToGroundTruthSampler()
564
+ # transform densepose pred -> gt
565
+ data_sampler.register_sampler(
566
+ "pred_densepose",
567
+ "gt_densepose",
568
+ DensePoseConfidenceBasedSampler(
569
+ confidence_channel="sigma_2",
570
+ count_per_class=sampler_cfg.COUNT_PER_CLASS,
571
+ search_proportion=0.5,
572
+ ),
573
+ )
574
+ data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
575
+ return data_sampler
576
+ elif sampler_cfg.TYPE == "densepose_fine_segm_confidence":
577
+ data_sampler = PredictionToGroundTruthSampler()
578
+ # transform densepose pred -> gt
579
+ data_sampler.register_sampler(
580
+ "pred_densepose",
581
+ "gt_densepose",
582
+ DensePoseConfidenceBasedSampler(
583
+ confidence_channel="fine_segm_confidence",
584
+ count_per_class=sampler_cfg.COUNT_PER_CLASS,
585
+ search_proportion=0.5,
586
+ ),
587
+ )
588
+ data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
589
+ return data_sampler
590
+ elif sampler_cfg.TYPE == "densepose_coarse_segm_confidence":
591
+ data_sampler = PredictionToGroundTruthSampler()
592
+ # transform densepose pred -> gt
593
+ data_sampler.register_sampler(
594
+ "pred_densepose",
595
+ "gt_densepose",
596
+ DensePoseConfidenceBasedSampler(
597
+ confidence_channel="coarse_segm_confidence",
598
+ count_per_class=sampler_cfg.COUNT_PER_CLASS,
599
+ search_proportion=0.5,
600
+ ),
601
+ )
602
+ data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
603
+ return data_sampler
604
+ elif sampler_cfg.TYPE == "densepose_cse_uniform":
605
+ assert embedder is not None
606
+ data_sampler = PredictionToGroundTruthSampler()
607
+ # transform densepose pred -> gt
608
+ data_sampler.register_sampler(
609
+ "pred_densepose",
610
+ "gt_densepose",
611
+ DensePoseCSEUniformSampler(
612
+ cfg=cfg,
613
+ use_gt_categories=sampler_cfg.USE_GROUND_TRUTH_CATEGORIES,
614
+ embedder=embedder,
615
+ count_per_class=sampler_cfg.COUNT_PER_CLASS,
616
+ ),
617
+ )
618
+ data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
619
+ return data_sampler
620
+ elif sampler_cfg.TYPE == "densepose_cse_coarse_segm_confidence":
621
+ assert embedder is not None
622
+ data_sampler = PredictionToGroundTruthSampler()
623
+ # transform densepose pred -> gt
624
+ data_sampler.register_sampler(
625
+ "pred_densepose",
626
+ "gt_densepose",
627
+ DensePoseCSEConfidenceBasedSampler(
628
+ cfg=cfg,
629
+ use_gt_categories=sampler_cfg.USE_GROUND_TRUTH_CATEGORIES,
630
+ embedder=embedder,
631
+ confidence_channel="coarse_segm_confidence",
632
+ count_per_class=sampler_cfg.COUNT_PER_CLASS,
633
+ search_proportion=0.5,
634
+ ),
635
+ )
636
+ data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
637
+ return data_sampler
638
+
639
+ raise ValueError(f"Unknown data sampler type {sampler_cfg.TYPE}")
640
+
641
+
642
+ def build_data_filter(cfg: CfgNode):
643
+ if cfg.TYPE == "detection_score":
644
+ min_score = cfg.MIN_VALUE
645
+ return ScoreBasedFilter(min_score=min_score)
646
+ raise ValueError(f"Unknown data filter type {cfg.TYPE}")
647
+
648
+
649
+ def build_inference_based_loader(
650
+ cfg: CfgNode,
651
+ dataset_cfg: CfgNode,
652
+ model: torch.nn.Module,
653
+ embedder: Optional[torch.nn.Module] = None,
654
+ ) -> InferenceBasedLoader:
655
+ """
656
+ Constructs data loader based on inference results of a model.
657
+ """
658
+ dataset = build_bootstrap_dataset(dataset_cfg.DATASET, dataset_cfg.IMAGE_LOADER)
659
+ meta = MetadataCatalog.get(dataset_cfg.DATASET)
660
+ training_sampler = TrainingSampler(len(dataset))
661
+ data_loader = torch.utils.data.DataLoader(
662
+ dataset, # pyre-ignore[6]
663
+ batch_size=dataset_cfg.IMAGE_LOADER.BATCH_SIZE,
664
+ sampler=training_sampler,
665
+ num_workers=dataset_cfg.IMAGE_LOADER.NUM_WORKERS,
666
+ collate_fn=trivial_batch_collator,
667
+ worker_init_fn=worker_init_reset_seed,
668
+ )
669
+ return InferenceBasedLoader(
670
+ model,
671
+ data_loader=data_loader,
672
+ data_sampler=build_data_sampler(cfg, dataset_cfg.DATA_SAMPLER, embedder),
673
+ data_filter=build_data_filter(dataset_cfg.FILTER),
674
+ shuffle=True,
675
+ batch_size=dataset_cfg.INFERENCE.OUTPUT_BATCH_SIZE,
676
+ inference_batch_size=dataset_cfg.INFERENCE.INPUT_BATCH_SIZE,
677
+ category_to_class_mapping=meta.category_to_class_mapping,
678
+ )
679
+
680
+
681
+ def has_inference_based_loaders(cfg: CfgNode) -> bool:
682
+ """
683
+ Returns True, if at least one inferense-based loader must
684
+ be instantiated for training
685
+ """
686
+ return len(cfg.BOOTSTRAP_DATASETS) > 0
687
+
688
+
689
+ def build_inference_based_loaders(
690
+ cfg: CfgNode, model: torch.nn.Module
691
+ ) -> Tuple[List[InferenceBasedLoader], List[float]]:
692
+ loaders = []
693
+ ratios = []
694
+ embedder = build_densepose_embedder(cfg).to(device=model.device) # pyre-ignore[16]
695
+ for dataset_spec in cfg.BOOTSTRAP_DATASETS:
696
+ dataset_cfg = get_bootstrap_dataset_config().clone()
697
+ dataset_cfg.merge_from_other_cfg(CfgNode(dataset_spec))
698
+ loader = build_inference_based_loader(cfg, dataset_cfg, model, embedder)
699
+ loaders.append(loader)
700
+ ratios.append(dataset_cfg.RATIO)
701
+ return loaders, ratios
702
+
703
+
704
+ def build_video_list_dataset(meta: Metadata, cfg: CfgNode):
705
+ video_list_fpath = meta.video_list_fpath
706
+ video_base_path = meta.video_base_path
707
+ category = meta.category
708
+ if cfg.TYPE == "video_keyframe":
709
+ frame_selector = build_frame_selector(cfg.SELECT)
710
+ transform = build_transform(cfg.TRANSFORM, data_type="image")
711
+ video_list = video_list_from_file(video_list_fpath, video_base_path)
712
+ keyframe_helper_fpath = getattr(cfg, "KEYFRAME_HELPER", None)
713
+ return VideoKeyframeDataset(
714
+ video_list, category, frame_selector, transform, keyframe_helper_fpath
715
+ )
716
+
717
+
718
+ class _BootstrapDatasetFactoryCatalog(UserDict):
719
+ """
720
+ A global dictionary that stores information about bootstrapped datasets creation functions
721
+ from metadata and config, for diverse DatasetType
722
+ """
723
+
724
+ def register(self, dataset_type: DatasetType, factory: Callable[[Metadata, CfgNode], Dataset]):
725
+ """
726
+ Args:
727
+ dataset_type (DatasetType): a DatasetType e.g. DatasetType.VIDEO_LIST
728
+ factory (Callable[Metadata, CfgNode]): a callable which takes Metadata and cfg
729
+ arguments and returns a dataset object.
730
+ """
731
+ assert dataset_type not in self, "Dataset '{}' is already registered!".format(dataset_type)
732
+ self[dataset_type] = factory
733
+
734
+
735
+ BootstrapDatasetFactoryCatalog = _BootstrapDatasetFactoryCatalog()
736
+ BootstrapDatasetFactoryCatalog.register(DatasetType.VIDEO_LIST, build_video_list_dataset)
densepose/data/combined_loader.py CHANGED
@@ -1,44 +1,44 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import random
4
- from collections import deque
5
- from typing import Any, Collection, Deque, Iterable, Iterator, List, Sequence
6
-
7
- Loader = Iterable[Any]
8
-
9
-
10
- def _pooled_next(iterator: Iterator[Any], pool: Deque[Any]):
11
- if not pool:
12
- pool.extend(next(iterator))
13
- return pool.popleft()
14
-
15
-
16
- class CombinedDataLoader:
17
- """
18
- Combines data loaders using the provided sampling ratios
19
- """
20
-
21
- BATCH_COUNT = 100
22
-
23
- def __init__(self, loaders: Collection[Loader], batch_size: int, ratios: Sequence[float]):
24
- self.loaders = loaders
25
- self.batch_size = batch_size
26
- self.ratios = ratios
27
-
28
- def __iter__(self) -> Iterator[List[Any]]:
29
- iters = [iter(loader) for loader in self.loaders]
30
- indices = []
31
- pool = [deque()] * len(iters)
32
- # infinite iterator, as in D2
33
- while True:
34
- if not indices:
35
- # just a buffer of indices, its size doesn't matter
36
- # as long as it's a multiple of batch_size
37
- k = self.batch_size * self.BATCH_COUNT
38
- indices = random.choices(range(len(self.loaders)), self.ratios, k=k)
39
- try:
40
- batch = [_pooled_next(iters[i], pool[i]) for i in indices[: self.batch_size]]
41
- except StopIteration:
42
- break
43
- indices = indices[self.batch_size :]
44
- yield batch
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import random
4
+ from collections import deque
5
+ from typing import Any, Collection, Deque, Iterable, Iterator, List, Sequence
6
+
7
+ Loader = Iterable[Any]
8
+
9
+
10
+ def _pooled_next(iterator: Iterator[Any], pool: Deque[Any]):
11
+ if not pool:
12
+ pool.extend(next(iterator))
13
+ return pool.popleft()
14
+
15
+
16
+ class CombinedDataLoader:
17
+ """
18
+ Combines data loaders using the provided sampling ratios
19
+ """
20
+
21
+ BATCH_COUNT = 100
22
+
23
+ def __init__(self, loaders: Collection[Loader], batch_size: int, ratios: Sequence[float]):
24
+ self.loaders = loaders
25
+ self.batch_size = batch_size
26
+ self.ratios = ratios
27
+
28
+ def __iter__(self) -> Iterator[List[Any]]:
29
+ iters = [iter(loader) for loader in self.loaders]
30
+ indices = []
31
+ pool = [deque()] * len(iters)
32
+ # infinite iterator, as in D2
33
+ while True:
34
+ if not indices:
35
+ # just a buffer of indices, its size doesn't matter
36
+ # as long as it's a multiple of batch_size
37
+ k = self.batch_size * self.BATCH_COUNT
38
+ indices = random.choices(range(len(self.loaders)), self.ratios, k=k)
39
+ try:
40
+ batch = [_pooled_next(iters[i], pool[i]) for i in indices[: self.batch_size]]
41
+ except StopIteration:
42
+ break
43
+ indices = indices[self.batch_size :]
44
+ yield batch
densepose/data/dataset_mapper.py CHANGED
@@ -1,168 +1,168 @@
1
- # -*- coding: utf-8 -*-
2
- # Copyright (c) Facebook, Inc. and its affiliates.
3
-
4
- import copy
5
- import logging
6
- from typing import Any, Dict, List, Tuple
7
- import torch
8
-
9
- from detectron2.data import MetadataCatalog
10
- from detectron2.data import detection_utils as utils
11
- from detectron2.data import transforms as T
12
- from detectron2.layers import ROIAlign
13
- from detectron2.structures import BoxMode
14
- from detectron2.utils.file_io import PathManager
15
-
16
- from densepose.structures import DensePoseDataRelative, DensePoseList, DensePoseTransformData
17
-
18
-
19
- def build_augmentation(cfg, is_train):
20
- logger = logging.getLogger(__name__)
21
- result = utils.build_augmentation(cfg, is_train)
22
- if is_train:
23
- random_rotation = T.RandomRotation(
24
- cfg.INPUT.ROTATION_ANGLES, expand=False, sample_style="choice"
25
- )
26
- result.append(random_rotation)
27
- logger.info("DensePose-specific augmentation used in training: " + str(random_rotation))
28
- return result
29
-
30
-
31
- class DatasetMapper:
32
- """
33
- A customized version of `detectron2.data.DatasetMapper`
34
- """
35
-
36
- def __init__(self, cfg, is_train=True):
37
- self.augmentation = build_augmentation(cfg, is_train)
38
-
39
- # fmt: off
40
- self.img_format = cfg.INPUT.FORMAT
41
- self.mask_on = (
42
- cfg.MODEL.MASK_ON or (
43
- cfg.MODEL.DENSEPOSE_ON
44
- and cfg.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS)
45
- )
46
- self.keypoint_on = cfg.MODEL.KEYPOINT_ON
47
- self.densepose_on = cfg.MODEL.DENSEPOSE_ON
48
- assert not cfg.MODEL.LOAD_PROPOSALS, "not supported yet"
49
- # fmt: on
50
- if self.keypoint_on and is_train:
51
- # Flip only makes sense in training
52
- self.keypoint_hflip_indices = utils.create_keypoint_hflip_indices(cfg.DATASETS.TRAIN)
53
- else:
54
- self.keypoint_hflip_indices = None
55
-
56
- if self.densepose_on:
57
- densepose_transform_srcs = [
58
- MetadataCatalog.get(ds).densepose_transform_src
59
- for ds in cfg.DATASETS.TRAIN + cfg.DATASETS.TEST
60
- ]
61
- assert len(densepose_transform_srcs) > 0
62
- # TODO: check that DensePose transformation data is the same for
63
- # all the datasets. Otherwise one would have to pass DB ID with
64
- # each entry to select proper transformation data. For now, since
65
- # all DensePose annotated data uses the same data semantics, we
66
- # omit this check.
67
- densepose_transform_data_fpath = PathManager.get_local_path(densepose_transform_srcs[0])
68
- self.densepose_transform_data = DensePoseTransformData.load(
69
- densepose_transform_data_fpath
70
- )
71
-
72
- self.is_train = is_train
73
-
74
- def __call__(self, dataset_dict):
75
- """
76
- Args:
77
- dataset_dict (dict): Metadata of one image, in Detectron2 Dataset format.
78
-
79
- Returns:
80
- dict: a format that builtin models in detectron2 accept
81
- """
82
- dataset_dict = copy.deepcopy(dataset_dict) # it will be modified by code below
83
- image = utils.read_image(dataset_dict["file_name"], format=self.img_format)
84
- utils.check_image_size(dataset_dict, image)
85
-
86
- image, transforms = T.apply_transform_gens(self.augmentation, image)
87
- image_shape = image.shape[:2] # h, w
88
- dataset_dict["image"] = torch.as_tensor(image.transpose(2, 0, 1).astype("float32"))
89
-
90
- if not self.is_train:
91
- dataset_dict.pop("annotations", None)
92
- return dataset_dict
93
-
94
- for anno in dataset_dict["annotations"]:
95
- if not self.mask_on:
96
- anno.pop("segmentation", None)
97
- if not self.keypoint_on:
98
- anno.pop("keypoints", None)
99
-
100
- # USER: Implement additional transformations if you have other types of data
101
- # USER: Don't call transpose_densepose if you don't need
102
- annos = [
103
- self._transform_densepose(
104
- utils.transform_instance_annotations(
105
- obj, transforms, image_shape, keypoint_hflip_indices=self.keypoint_hflip_indices
106
- ),
107
- transforms,
108
- )
109
- for obj in dataset_dict.pop("annotations")
110
- if obj.get("iscrowd", 0) == 0
111
- ]
112
-
113
- if self.mask_on:
114
- self._add_densepose_masks_as_segmentation(annos, image_shape)
115
-
116
- instances = utils.annotations_to_instances(annos, image_shape, mask_format="bitmask")
117
- densepose_annotations = [obj.get("densepose") for obj in annos]
118
- if densepose_annotations and not all(v is None for v in densepose_annotations):
119
- instances.gt_densepose = DensePoseList(
120
- densepose_annotations, instances.gt_boxes, image_shape
121
- )
122
-
123
- dataset_dict["instances"] = instances[instances.gt_boxes.nonempty()]
124
- return dataset_dict
125
-
126
- def _transform_densepose(self, annotation, transforms):
127
- if not self.densepose_on:
128
- return annotation
129
-
130
- # Handle densepose annotations
131
- is_valid, reason_not_valid = DensePoseDataRelative.validate_annotation(annotation)
132
- if is_valid:
133
- densepose_data = DensePoseDataRelative(annotation, cleanup=True)
134
- densepose_data.apply_transform(transforms, self.densepose_transform_data)
135
- annotation["densepose"] = densepose_data
136
- else:
137
- # logger = logging.getLogger(__name__)
138
- # logger.debug("Could not load DensePose annotation: {}".format(reason_not_valid))
139
- DensePoseDataRelative.cleanup_annotation(annotation)
140
- # NOTE: annotations for certain instances may be unavailable.
141
- # 'None' is accepted by the DensePostList data structure.
142
- annotation["densepose"] = None
143
- return annotation
144
-
145
- def _add_densepose_masks_as_segmentation(
146
- self, annotations: List[Dict[str, Any]], image_shape_hw: Tuple[int, int]
147
- ):
148
- for obj in annotations:
149
- if ("densepose" not in obj) or ("segmentation" in obj):
150
- continue
151
- # DP segmentation: torch.Tensor [S, S] of float32, S=256
152
- segm_dp = torch.zeros_like(obj["densepose"].segm)
153
- segm_dp[obj["densepose"].segm > 0] = 1
154
- segm_h, segm_w = segm_dp.shape
155
- bbox_segm_dp = torch.tensor((0, 0, segm_h - 1, segm_w - 1), dtype=torch.float32)
156
- # image bbox
157
- x0, y0, x1, y1 = (
158
- v.item() for v in BoxMode.convert(obj["bbox"], obj["bbox_mode"], BoxMode.XYXY_ABS)
159
- )
160
- segm_aligned = (
161
- ROIAlign((y1 - y0, x1 - x0), 1.0, 0, aligned=True)
162
- .forward(segm_dp.view(1, 1, *segm_dp.shape), bbox_segm_dp)
163
- .squeeze()
164
- )
165
- image_mask = torch.zeros(*image_shape_hw, dtype=torch.float32)
166
- image_mask[y0:y1, x0:x1] = segm_aligned
167
- # segmentation for BitMask: np.array [H, W] of bool
168
- obj["segmentation"] = image_mask >= 0.5
 
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) Facebook, Inc. and its affiliates.
3
+
4
+ import copy
5
+ import logging
6
+ from typing import Any, Dict, List, Tuple
7
+ import torch
8
+
9
+ from detectron2.data import MetadataCatalog
10
+ from detectron2.data import detection_utils as utils
11
+ from detectron2.data import transforms as T
12
+ from detectron2.layers import ROIAlign
13
+ from detectron2.structures import BoxMode
14
+ from detectron2.utils.file_io import PathManager
15
+
16
+ from densepose.structures import DensePoseDataRelative, DensePoseList, DensePoseTransformData
17
+
18
+
19
+ def build_augmentation(cfg, is_train):
20
+ logger = logging.getLogger(__name__)
21
+ result = utils.build_augmentation(cfg, is_train)
22
+ if is_train:
23
+ random_rotation = T.RandomRotation(
24
+ cfg.INPUT.ROTATION_ANGLES, expand=False, sample_style="choice"
25
+ )
26
+ result.append(random_rotation)
27
+ logger.info("DensePose-specific augmentation used in training: " + str(random_rotation))
28
+ return result
29
+
30
+
31
+ class DatasetMapper:
32
+ """
33
+ A customized version of `detectron2.data.DatasetMapper`
34
+ """
35
+
36
+ def __init__(self, cfg, is_train=True):
37
+ self.augmentation = build_augmentation(cfg, is_train)
38
+
39
+ # fmt: off
40
+ self.img_format = cfg.INPUT.FORMAT
41
+ self.mask_on = (
42
+ cfg.MODEL.MASK_ON or (
43
+ cfg.MODEL.DENSEPOSE_ON
44
+ and cfg.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS)
45
+ )
46
+ self.keypoint_on = cfg.MODEL.KEYPOINT_ON
47
+ self.densepose_on = cfg.MODEL.DENSEPOSE_ON
48
+ assert not cfg.MODEL.LOAD_PROPOSALS, "not supported yet"
49
+ # fmt: on
50
+ if self.keypoint_on and is_train:
51
+ # Flip only makes sense in training
52
+ self.keypoint_hflip_indices = utils.create_keypoint_hflip_indices(cfg.DATASETS.TRAIN)
53
+ else:
54
+ self.keypoint_hflip_indices = None
55
+
56
+ if self.densepose_on:
57
+ densepose_transform_srcs = [
58
+ MetadataCatalog.get(ds).densepose_transform_src
59
+ for ds in cfg.DATASETS.TRAIN + cfg.DATASETS.TEST
60
+ ]
61
+ assert len(densepose_transform_srcs) > 0
62
+ # TODO: check that DensePose transformation data is the same for
63
+ # all the datasets. Otherwise one would have to pass DB ID with
64
+ # each entry to select proper transformation data. For now, since
65
+ # all DensePose annotated data uses the same data semantics, we
66
+ # omit this check.
67
+ densepose_transform_data_fpath = PathManager.get_local_path(densepose_transform_srcs[0])
68
+ self.densepose_transform_data = DensePoseTransformData.load(
69
+ densepose_transform_data_fpath
70
+ )
71
+
72
+ self.is_train = is_train
73
+
74
+ def __call__(self, dataset_dict):
75
+ """
76
+ Args:
77
+ dataset_dict (dict): Metadata of one image, in Detectron2 Dataset format.
78
+
79
+ Returns:
80
+ dict: a format that builtin models in detectron2 accept
81
+ """
82
+ dataset_dict = copy.deepcopy(dataset_dict) # it will be modified by code below
83
+ image = utils.read_image(dataset_dict["file_name"], format=self.img_format)
84
+ utils.check_image_size(dataset_dict, image)
85
+
86
+ image, transforms = T.apply_transform_gens(self.augmentation, image)
87
+ image_shape = image.shape[:2] # h, w
88
+ dataset_dict["image"] = torch.as_tensor(image.transpose(2, 0, 1).astype("float32"))
89
+
90
+ if not self.is_train:
91
+ dataset_dict.pop("annotations", None)
92
+ return dataset_dict
93
+
94
+ for anno in dataset_dict["annotations"]:
95
+ if not self.mask_on:
96
+ anno.pop("segmentation", None)
97
+ if not self.keypoint_on:
98
+ anno.pop("keypoints", None)
99
+
100
+ # USER: Implement additional transformations if you have other types of data
101
+ # USER: Don't call transpose_densepose if you don't need
102
+ annos = [
103
+ self._transform_densepose(
104
+ utils.transform_instance_annotations(
105
+ obj, transforms, image_shape, keypoint_hflip_indices=self.keypoint_hflip_indices
106
+ ),
107
+ transforms,
108
+ )
109
+ for obj in dataset_dict.pop("annotations")
110
+ if obj.get("iscrowd", 0) == 0
111
+ ]
112
+
113
+ if self.mask_on:
114
+ self._add_densepose_masks_as_segmentation(annos, image_shape)
115
+
116
+ instances = utils.annotations_to_instances(annos, image_shape, mask_format="bitmask")
117
+ densepose_annotations = [obj.get("densepose") for obj in annos]
118
+ if densepose_annotations and not all(v is None for v in densepose_annotations):
119
+ instances.gt_densepose = DensePoseList(
120
+ densepose_annotations, instances.gt_boxes, image_shape
121
+ )
122
+
123
+ dataset_dict["instances"] = instances[instances.gt_boxes.nonempty()]
124
+ return dataset_dict
125
+
126
+ def _transform_densepose(self, annotation, transforms):
127
+ if not self.densepose_on:
128
+ return annotation
129
+
130
+ # Handle densepose annotations
131
+ is_valid, reason_not_valid = DensePoseDataRelative.validate_annotation(annotation)
132
+ if is_valid:
133
+ densepose_data = DensePoseDataRelative(annotation, cleanup=True)
134
+ densepose_data.apply_transform(transforms, self.densepose_transform_data)
135
+ annotation["densepose"] = densepose_data
136
+ else:
137
+ # logger = logging.getLogger(__name__)
138
+ # logger.debug("Could not load DensePose annotation: {}".format(reason_not_valid))
139
+ DensePoseDataRelative.cleanup_annotation(annotation)
140
+ # NOTE: annotations for certain instances may be unavailable.
141
+ # 'None' is accepted by the DensePostList data structure.
142
+ annotation["densepose"] = None
143
+ return annotation
144
+
145
+ def _add_densepose_masks_as_segmentation(
146
+ self, annotations: List[Dict[str, Any]], image_shape_hw: Tuple[int, int]
147
+ ):
148
+ for obj in annotations:
149
+ if ("densepose" not in obj) or ("segmentation" in obj):
150
+ continue
151
+ # DP segmentation: torch.Tensor [S, S] of float32, S=256
152
+ segm_dp = torch.zeros_like(obj["densepose"].segm)
153
+ segm_dp[obj["densepose"].segm > 0] = 1
154
+ segm_h, segm_w = segm_dp.shape
155
+ bbox_segm_dp = torch.tensor((0, 0, segm_h - 1, segm_w - 1), dtype=torch.float32)
156
+ # image bbox
157
+ x0, y0, x1, y1 = (
158
+ v.item() for v in BoxMode.convert(obj["bbox"], obj["bbox_mode"], BoxMode.XYXY_ABS)
159
+ )
160
+ segm_aligned = (
161
+ ROIAlign((y1 - y0, x1 - x0), 1.0, 0, aligned=True)
162
+ .forward(segm_dp.view(1, 1, *segm_dp.shape), bbox_segm_dp)
163
+ .squeeze()
164
+ )
165
+ image_mask = torch.zeros(*image_shape_hw, dtype=torch.float32)
166
+ image_mask[y0:y1, x0:x1] = segm_aligned
167
+ # segmentation for BitMask: np.array [H, W] of bool
168
+ obj["segmentation"] = image_mask >= 0.5
densepose/data/datasets/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from . import builtin # ensure the builtin datasets are registered
4
-
5
- __all__ = [k for k in globals().keys() if "builtin" not in k and not k.startswith("_")]
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from . import builtin # ensure the builtin datasets are registered
4
+
5
+ __all__ = [k for k in globals().keys() if "builtin" not in k and not k.startswith("_")]
densepose/data/datasets/builtin.py CHANGED
@@ -1,16 +1,16 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
- from .chimpnsee import register_dataset as register_chimpnsee_dataset
3
- from .coco import BASE_DATASETS as BASE_COCO_DATASETS
4
- from .coco import DATASETS as COCO_DATASETS
5
- from .coco import register_datasets as register_coco_datasets
6
- from .lvis import DATASETS as LVIS_DATASETS
7
- from .lvis import register_datasets as register_lvis_datasets
8
-
9
- DEFAULT_DATASETS_ROOT = "datasets"
10
-
11
-
12
- register_coco_datasets(COCO_DATASETS, DEFAULT_DATASETS_ROOT)
13
- register_coco_datasets(BASE_COCO_DATASETS, DEFAULT_DATASETS_ROOT)
14
- register_lvis_datasets(LVIS_DATASETS, DEFAULT_DATASETS_ROOT)
15
-
16
- register_chimpnsee_dataset(DEFAULT_DATASETS_ROOT) # pyre-ignore[19]
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ from .chimpnsee import register_dataset as register_chimpnsee_dataset
3
+ from .coco import BASE_DATASETS as BASE_COCO_DATASETS
4
+ from .coco import DATASETS as COCO_DATASETS
5
+ from .coco import register_datasets as register_coco_datasets
6
+ from .lvis import DATASETS as LVIS_DATASETS
7
+ from .lvis import register_datasets as register_lvis_datasets
8
+
9
+ DEFAULT_DATASETS_ROOT = "datasets"
10
+
11
+
12
+ register_coco_datasets(COCO_DATASETS, DEFAULT_DATASETS_ROOT)
13
+ register_coco_datasets(BASE_COCO_DATASETS, DEFAULT_DATASETS_ROOT)
14
+ register_lvis_datasets(LVIS_DATASETS, DEFAULT_DATASETS_ROOT)
15
+
16
+ register_chimpnsee_dataset(DEFAULT_DATASETS_ROOT) # pyre-ignore[19]
densepose/data/datasets/chimpnsee.py CHANGED
@@ -1,29 +1,29 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Optional
4
-
5
- from detectron2.data import DatasetCatalog, MetadataCatalog
6
-
7
- from ..utils import maybe_prepend_base_path
8
- from .dataset_type import DatasetType
9
-
10
- CHIMPNSEE_DATASET_NAME = "chimpnsee"
11
-
12
-
13
- def register_dataset(datasets_root: Optional[str] = None) -> None:
14
- def empty_load_callback():
15
- pass
16
-
17
- video_list_fpath = maybe_prepend_base_path(
18
- datasets_root,
19
- "chimpnsee/cdna.eva.mpg.de/video_list.txt",
20
- )
21
- video_base_path = maybe_prepend_base_path(datasets_root, "chimpnsee/cdna.eva.mpg.de")
22
-
23
- DatasetCatalog.register(CHIMPNSEE_DATASET_NAME, empty_load_callback)
24
- MetadataCatalog.get(CHIMPNSEE_DATASET_NAME).set(
25
- dataset_type=DatasetType.VIDEO_LIST,
26
- video_list_fpath=video_list_fpath,
27
- video_base_path=video_base_path,
28
- category="chimpanzee",
29
- )
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Optional
4
+
5
+ from detectron2.data import DatasetCatalog, MetadataCatalog
6
+
7
+ from ..utils import maybe_prepend_base_path
8
+ from .dataset_type import DatasetType
9
+
10
+ CHIMPNSEE_DATASET_NAME = "chimpnsee"
11
+
12
+
13
+ def register_dataset(datasets_root: Optional[str] = None) -> None:
14
+ def empty_load_callback():
15
+ pass
16
+
17
+ video_list_fpath = maybe_prepend_base_path(
18
+ datasets_root,
19
+ "chimpnsee/cdna.eva.mpg.de/video_list.txt",
20
+ )
21
+ video_base_path = maybe_prepend_base_path(datasets_root, "chimpnsee/cdna.eva.mpg.de")
22
+
23
+ DatasetCatalog.register(CHIMPNSEE_DATASET_NAME, empty_load_callback)
24
+ MetadataCatalog.get(CHIMPNSEE_DATASET_NAME).set(
25
+ dataset_type=DatasetType.VIDEO_LIST,
26
+ video_list_fpath=video_list_fpath,
27
+ video_base_path=video_base_path,
28
+ category="chimpanzee",
29
+ )
densepose/data/datasets/coco.py CHANGED
@@ -1,432 +1,432 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
- import contextlib
3
- import io
4
- import logging
5
- import os
6
- from collections import defaultdict
7
- from dataclasses import dataclass
8
- from typing import Any, Dict, Iterable, List, Optional
9
- from fvcore.common.timer import Timer
10
-
11
- from detectron2.data import DatasetCatalog, MetadataCatalog
12
- from detectron2.structures import BoxMode
13
- from detectron2.utils.file_io import PathManager
14
-
15
- from ..utils import maybe_prepend_base_path
16
-
17
- DENSEPOSE_MASK_KEY = "dp_masks"
18
- DENSEPOSE_IUV_KEYS_WITHOUT_MASK = ["dp_x", "dp_y", "dp_I", "dp_U", "dp_V"]
19
- DENSEPOSE_CSE_KEYS_WITHOUT_MASK = ["dp_x", "dp_y", "dp_vertex", "ref_model"]
20
- DENSEPOSE_ALL_POSSIBLE_KEYS = set(
21
- DENSEPOSE_IUV_KEYS_WITHOUT_MASK + DENSEPOSE_CSE_KEYS_WITHOUT_MASK + [DENSEPOSE_MASK_KEY]
22
- )
23
- DENSEPOSE_METADATA_URL_PREFIX = "https://dl.fbaipublicfiles.com/densepose/data/"
24
-
25
-
26
- @dataclass
27
- class CocoDatasetInfo:
28
- name: str
29
- images_root: str
30
- annotations_fpath: str
31
-
32
-
33
- DATASETS = [
34
- CocoDatasetInfo(
35
- name="densepose_coco_2014_train",
36
- images_root="coco/train2014",
37
- annotations_fpath="coco/annotations/densepose_train2014.json",
38
- ),
39
- CocoDatasetInfo(
40
- name="densepose_coco_2014_minival",
41
- images_root="coco/val2014",
42
- annotations_fpath="coco/annotations/densepose_minival2014.json",
43
- ),
44
- CocoDatasetInfo(
45
- name="densepose_coco_2014_minival_100",
46
- images_root="coco/val2014",
47
- annotations_fpath="coco/annotations/densepose_minival2014_100.json",
48
- ),
49
- CocoDatasetInfo(
50
- name="densepose_coco_2014_valminusminival",
51
- images_root="coco/val2014",
52
- annotations_fpath="coco/annotations/densepose_valminusminival2014.json",
53
- ),
54
- CocoDatasetInfo(
55
- name="densepose_coco_2014_train_cse",
56
- images_root="coco/train2014",
57
- annotations_fpath="coco_cse/densepose_train2014_cse.json",
58
- ),
59
- CocoDatasetInfo(
60
- name="densepose_coco_2014_minival_cse",
61
- images_root="coco/val2014",
62
- annotations_fpath="coco_cse/densepose_minival2014_cse.json",
63
- ),
64
- CocoDatasetInfo(
65
- name="densepose_coco_2014_minival_100_cse",
66
- images_root="coco/val2014",
67
- annotations_fpath="coco_cse/densepose_minival2014_100_cse.json",
68
- ),
69
- CocoDatasetInfo(
70
- name="densepose_coco_2014_valminusminival_cse",
71
- images_root="coco/val2014",
72
- annotations_fpath="coco_cse/densepose_valminusminival2014_cse.json",
73
- ),
74
- CocoDatasetInfo(
75
- name="densepose_chimps",
76
- images_root="densepose_chimps/images",
77
- annotations_fpath="densepose_chimps/densepose_chimps_densepose.json",
78
- ),
79
- CocoDatasetInfo(
80
- name="densepose_chimps_cse_train",
81
- images_root="densepose_chimps/images",
82
- annotations_fpath="densepose_chimps/densepose_chimps_cse_train.json",
83
- ),
84
- CocoDatasetInfo(
85
- name="densepose_chimps_cse_val",
86
- images_root="densepose_chimps/images",
87
- annotations_fpath="densepose_chimps/densepose_chimps_cse_val.json",
88
- ),
89
- CocoDatasetInfo(
90
- name="posetrack2017_train",
91
- images_root="posetrack2017/posetrack_data_2017",
92
- annotations_fpath="posetrack2017/densepose_posetrack_train2017.json",
93
- ),
94
- CocoDatasetInfo(
95
- name="posetrack2017_val",
96
- images_root="posetrack2017/posetrack_data_2017",
97
- annotations_fpath="posetrack2017/densepose_posetrack_val2017.json",
98
- ),
99
- CocoDatasetInfo(
100
- name="lvis_v05_train",
101
- images_root="coco/train2017",
102
- annotations_fpath="lvis/lvis_v0.5_plus_dp_train.json",
103
- ),
104
- CocoDatasetInfo(
105
- name="lvis_v05_val",
106
- images_root="coco/val2017",
107
- annotations_fpath="lvis/lvis_v0.5_plus_dp_val.json",
108
- ),
109
- ]
110
-
111
-
112
- BASE_DATASETS = [
113
- CocoDatasetInfo(
114
- name="base_coco_2017_train",
115
- images_root="coco/train2017",
116
- annotations_fpath="coco/annotations/instances_train2017.json",
117
- ),
118
- CocoDatasetInfo(
119
- name="base_coco_2017_val",
120
- images_root="coco/val2017",
121
- annotations_fpath="coco/annotations/instances_val2017.json",
122
- ),
123
- CocoDatasetInfo(
124
- name="base_coco_2017_val_100",
125
- images_root="coco/val2017",
126
- annotations_fpath="coco/annotations/instances_val2017_100.json",
127
- ),
128
- ]
129
-
130
-
131
- def get_metadata(base_path: Optional[str]) -> Dict[str, Any]:
132
- """
133
- Returns metadata associated with COCO DensePose datasets
134
-
135
- Args:
136
- base_path: Optional[str]
137
- Base path used to load metadata from
138
-
139
- Returns:
140
- Dict[str, Any]
141
- Metadata in the form of a dictionary
142
- """
143
- meta = {
144
- "densepose_transform_src": maybe_prepend_base_path(base_path, "UV_symmetry_transforms.mat"),
145
- "densepose_smpl_subdiv": maybe_prepend_base_path(base_path, "SMPL_subdiv.mat"),
146
- "densepose_smpl_subdiv_transform": maybe_prepend_base_path(
147
- base_path,
148
- "SMPL_SUBDIV_TRANSFORM.mat",
149
- ),
150
- }
151
- return meta
152
-
153
-
154
- def _load_coco_annotations(json_file: str):
155
- """
156
- Load COCO annotations from a JSON file
157
-
158
- Args:
159
- json_file: str
160
- Path to the file to load annotations from
161
- Returns:
162
- Instance of `pycocotools.coco.COCO` that provides access to annotations
163
- data
164
- """
165
- from pycocotools.coco import COCO
166
-
167
- logger = logging.getLogger(__name__)
168
- timer = Timer()
169
- with contextlib.redirect_stdout(io.StringIO()):
170
- coco_api = COCO(json_file)
171
- if timer.seconds() > 1:
172
- logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
173
- return coco_api
174
-
175
-
176
- def _add_categories_metadata(dataset_name: str, categories: List[Dict[str, Any]]):
177
- meta = MetadataCatalog.get(dataset_name)
178
- meta.categories = {c["id"]: c["name"] for c in categories}
179
- logger = logging.getLogger(__name__)
180
- logger.info("Dataset {} categories: {}".format(dataset_name, meta.categories))
181
-
182
-
183
- def _verify_annotations_have_unique_ids(json_file: str, anns: List[List[Dict[str, Any]]]):
184
- if "minival" in json_file:
185
- # Skip validation on COCO2014 valminusminival and minival annotations
186
- # The ratio of buggy annotations there is tiny and does not affect accuracy
187
- # Therefore we explicitly white-list them
188
- return
189
- ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
190
- assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique!".format(
191
- json_file
192
- )
193
-
194
-
195
- def _maybe_add_bbox(obj: Dict[str, Any], ann_dict: Dict[str, Any]):
196
- if "bbox" not in ann_dict:
197
- return
198
- obj["bbox"] = ann_dict["bbox"]
199
- obj["bbox_mode"] = BoxMode.XYWH_ABS
200
-
201
-
202
- def _maybe_add_segm(obj: Dict[str, Any], ann_dict: Dict[str, Any]):
203
- if "segmentation" not in ann_dict:
204
- return
205
- segm = ann_dict["segmentation"]
206
- if not isinstance(segm, dict):
207
- # filter out invalid polygons (< 3 points)
208
- segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
209
- if len(segm) == 0:
210
- return
211
- obj["segmentation"] = segm
212
-
213
-
214
- def _maybe_add_keypoints(obj: Dict[str, Any], ann_dict: Dict[str, Any]):
215
- if "keypoints" not in ann_dict:
216
- return
217
- keypts = ann_dict["keypoints"] # list[int]
218
- for idx, v in enumerate(keypts):
219
- if idx % 3 != 2:
220
- # COCO's segmentation coordinates are floating points in [0, H or W],
221
- # but keypoint coordinates are integers in [0, H-1 or W-1]
222
- # Therefore we assume the coordinates are "pixel indices" and
223
- # add 0.5 to convert to floating point coordinates.
224
- keypts[idx] = v + 0.5
225
- obj["keypoints"] = keypts
226
-
227
-
228
- def _maybe_add_densepose(obj: Dict[str, Any], ann_dict: Dict[str, Any]):
229
- for key in DENSEPOSE_ALL_POSSIBLE_KEYS:
230
- if key in ann_dict:
231
- obj[key] = ann_dict[key]
232
-
233
-
234
- def _combine_images_with_annotations(
235
- dataset_name: str,
236
- image_root: str,
237
- img_datas: Iterable[Dict[str, Any]],
238
- ann_datas: Iterable[Iterable[Dict[str, Any]]],
239
- ):
240
-
241
- ann_keys = ["iscrowd", "category_id"]
242
- dataset_dicts = []
243
- contains_video_frame_info = False
244
-
245
- for img_dict, ann_dicts in zip(img_datas, ann_datas):
246
- record = {}
247
- record["file_name"] = os.path.join(image_root, img_dict["file_name"])
248
- record["height"] = img_dict["height"]
249
- record["width"] = img_dict["width"]
250
- record["image_id"] = img_dict["id"]
251
- record["dataset"] = dataset_name
252
- if "frame_id" in img_dict:
253
- record["frame_id"] = img_dict["frame_id"]
254
- record["video_id"] = img_dict.get("vid_id", None)
255
- contains_video_frame_info = True
256
- objs = []
257
- for ann_dict in ann_dicts:
258
- assert ann_dict["image_id"] == record["image_id"]
259
- assert ann_dict.get("ignore", 0) == 0
260
- obj = {key: ann_dict[key] for key in ann_keys if key in ann_dict}
261
- _maybe_add_bbox(obj, ann_dict)
262
- _maybe_add_segm(obj, ann_dict)
263
- _maybe_add_keypoints(obj, ann_dict)
264
- _maybe_add_densepose(obj, ann_dict)
265
- objs.append(obj)
266
- record["annotations"] = objs
267
- dataset_dicts.append(record)
268
- if contains_video_frame_info:
269
- create_video_frame_mapping(dataset_name, dataset_dicts)
270
- return dataset_dicts
271
-
272
-
273
- def get_contiguous_id_to_category_id_map(metadata):
274
- cat_id_2_cont_id = metadata.thing_dataset_id_to_contiguous_id
275
- cont_id_2_cat_id = {}
276
- for cat_id, cont_id in cat_id_2_cont_id.items():
277
- if cont_id in cont_id_2_cat_id:
278
- continue
279
- cont_id_2_cat_id[cont_id] = cat_id
280
- return cont_id_2_cat_id
281
-
282
-
283
- def maybe_filter_categories_cocoapi(dataset_name, coco_api):
284
- meta = MetadataCatalog.get(dataset_name)
285
- cont_id_2_cat_id = get_contiguous_id_to_category_id_map(meta)
286
- cat_id_2_cont_id = meta.thing_dataset_id_to_contiguous_id
287
- # filter categories
288
- cats = []
289
- for cat in coco_api.dataset["categories"]:
290
- cat_id = cat["id"]
291
- if cat_id not in cat_id_2_cont_id:
292
- continue
293
- cont_id = cat_id_2_cont_id[cat_id]
294
- if (cont_id in cont_id_2_cat_id) and (cont_id_2_cat_id[cont_id] == cat_id):
295
- cats.append(cat)
296
- coco_api.dataset["categories"] = cats
297
- # filter annotations, if multiple categories are mapped to a single
298
- # contiguous ID, use only one category ID and map all annotations to that category ID
299
- anns = []
300
- for ann in coco_api.dataset["annotations"]:
301
- cat_id = ann["category_id"]
302
- if cat_id not in cat_id_2_cont_id:
303
- continue
304
- cont_id = cat_id_2_cont_id[cat_id]
305
- ann["category_id"] = cont_id_2_cat_id[cont_id]
306
- anns.append(ann)
307
- coco_api.dataset["annotations"] = anns
308
- # recreate index
309
- coco_api.createIndex()
310
-
311
-
312
- def maybe_filter_and_map_categories_cocoapi(dataset_name, coco_api):
313
- meta = MetadataCatalog.get(dataset_name)
314
- category_id_map = meta.thing_dataset_id_to_contiguous_id
315
- # map categories
316
- cats = []
317
- for cat in coco_api.dataset["categories"]:
318
- cat_id = cat["id"]
319
- if cat_id not in category_id_map:
320
- continue
321
- cat["id"] = category_id_map[cat_id]
322
- cats.append(cat)
323
- coco_api.dataset["categories"] = cats
324
- # map annotation categories
325
- anns = []
326
- for ann in coco_api.dataset["annotations"]:
327
- cat_id = ann["category_id"]
328
- if cat_id not in category_id_map:
329
- continue
330
- ann["category_id"] = category_id_map[cat_id]
331
- anns.append(ann)
332
- coco_api.dataset["annotations"] = anns
333
- # recreate index
334
- coco_api.createIndex()
335
-
336
-
337
- def create_video_frame_mapping(dataset_name, dataset_dicts):
338
- mapping = defaultdict(dict)
339
- for d in dataset_dicts:
340
- video_id = d.get("video_id")
341
- if video_id is None:
342
- continue
343
- mapping[video_id].update({d["frame_id"]: d["file_name"]})
344
- MetadataCatalog.get(dataset_name).set(video_frame_mapping=mapping)
345
-
346
-
347
- def load_coco_json(annotations_json_file: str, image_root: str, dataset_name: str):
348
- """
349
- Loads a JSON file with annotations in COCO instances format.
350
- Replaces `detectron2.data.datasets.coco.load_coco_json` to handle metadata
351
- in a more flexible way. Postpones category mapping to a later stage to be
352
- able to combine several datasets with different (but coherent) sets of
353
- categories.
354
-
355
- Args:
356
-
357
- annotations_json_file: str
358
- Path to the JSON file with annotations in COCO instances format.
359
- image_root: str
360
- directory that contains all the images
361
- dataset_name: str
362
- the name that identifies a dataset, e.g. "densepose_coco_2014_train"
363
- extra_annotation_keys: Optional[List[str]]
364
- If provided, these keys are used to extract additional data from
365
- the annotations.
366
- """
367
- coco_api = _load_coco_annotations(PathManager.get_local_path(annotations_json_file))
368
- _add_categories_metadata(dataset_name, coco_api.loadCats(coco_api.getCatIds()))
369
- # sort indices for reproducible results
370
- img_ids = sorted(coco_api.imgs.keys())
371
- # imgs is a list of dicts, each looks something like:
372
- # {'license': 4,
373
- # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
374
- # 'file_name': 'COCO_val2014_000000001268.jpg',
375
- # 'height': 427,
376
- # 'width': 640,
377
- # 'date_captured': '2013-11-17 05:57:24',
378
- # 'id': 1268}
379
- imgs = coco_api.loadImgs(img_ids)
380
- logger = logging.getLogger(__name__)
381
- logger.info("Loaded {} images in COCO format from {}".format(len(imgs), annotations_json_file))
382
- # anns is a list[list[dict]], where each dict is an annotation
383
- # record for an object. The inner list enumerates the objects in an image
384
- # and the outer list enumerates over images.
385
- anns = [coco_api.imgToAnns[img_id] for img_id in img_ids]
386
- _verify_annotations_have_unique_ids(annotations_json_file, anns)
387
- dataset_records = _combine_images_with_annotations(dataset_name, image_root, imgs, anns)
388
- return dataset_records
389
-
390
-
391
- def register_dataset(dataset_data: CocoDatasetInfo, datasets_root: Optional[str] = None):
392
- """
393
- Registers provided COCO DensePose dataset
394
-
395
- Args:
396
- dataset_data: CocoDatasetInfo
397
- Dataset data
398
- datasets_root: Optional[str]
399
- Datasets root folder (default: None)
400
- """
401
- annotations_fpath = maybe_prepend_base_path(datasets_root, dataset_data.annotations_fpath)
402
- images_root = maybe_prepend_base_path(datasets_root, dataset_data.images_root)
403
-
404
- def load_annotations():
405
- return load_coco_json(
406
- annotations_json_file=annotations_fpath,
407
- image_root=images_root,
408
- dataset_name=dataset_data.name,
409
- )
410
-
411
- DatasetCatalog.register(dataset_data.name, load_annotations)
412
- MetadataCatalog.get(dataset_data.name).set(
413
- json_file=annotations_fpath,
414
- image_root=images_root,
415
- **get_metadata(DENSEPOSE_METADATA_URL_PREFIX)
416
- )
417
-
418
-
419
- def register_datasets(
420
- datasets_data: Iterable[CocoDatasetInfo], datasets_root: Optional[str] = None
421
- ):
422
- """
423
- Registers provided COCO DensePose datasets
424
-
425
- Args:
426
- datasets_data: Iterable[CocoDatasetInfo]
427
- An iterable of dataset datas
428
- datasets_root: Optional[str]
429
- Datasets root folder (default: None)
430
- """
431
- for dataset_data in datasets_data:
432
- register_dataset(dataset_data, datasets_root)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ import contextlib
3
+ import io
4
+ import logging
5
+ import os
6
+ from collections import defaultdict
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, Iterable, List, Optional
9
+ from fvcore.common.timer import Timer
10
+
11
+ from detectron2.data import DatasetCatalog, MetadataCatalog
12
+ from detectron2.structures import BoxMode
13
+ from detectron2.utils.file_io import PathManager
14
+
15
+ from ..utils import maybe_prepend_base_path
16
+
17
+ DENSEPOSE_MASK_KEY = "dp_masks"
18
+ DENSEPOSE_IUV_KEYS_WITHOUT_MASK = ["dp_x", "dp_y", "dp_I", "dp_U", "dp_V"]
19
+ DENSEPOSE_CSE_KEYS_WITHOUT_MASK = ["dp_x", "dp_y", "dp_vertex", "ref_model"]
20
+ DENSEPOSE_ALL_POSSIBLE_KEYS = set(
21
+ DENSEPOSE_IUV_KEYS_WITHOUT_MASK + DENSEPOSE_CSE_KEYS_WITHOUT_MASK + [DENSEPOSE_MASK_KEY]
22
+ )
23
+ DENSEPOSE_METADATA_URL_PREFIX = "https://dl.fbaipublicfiles.com/densepose/data/"
24
+
25
+
26
+ @dataclass
27
+ class CocoDatasetInfo:
28
+ name: str
29
+ images_root: str
30
+ annotations_fpath: str
31
+
32
+
33
+ DATASETS = [
34
+ CocoDatasetInfo(
35
+ name="densepose_coco_2014_train",
36
+ images_root="coco/train2014",
37
+ annotations_fpath="coco/annotations/densepose_train2014.json",
38
+ ),
39
+ CocoDatasetInfo(
40
+ name="densepose_coco_2014_minival",
41
+ images_root="coco/val2014",
42
+ annotations_fpath="coco/annotations/densepose_minival2014.json",
43
+ ),
44
+ CocoDatasetInfo(
45
+ name="densepose_coco_2014_minival_100",
46
+ images_root="coco/val2014",
47
+ annotations_fpath="coco/annotations/densepose_minival2014_100.json",
48
+ ),
49
+ CocoDatasetInfo(
50
+ name="densepose_coco_2014_valminusminival",
51
+ images_root="coco/val2014",
52
+ annotations_fpath="coco/annotations/densepose_valminusminival2014.json",
53
+ ),
54
+ CocoDatasetInfo(
55
+ name="densepose_coco_2014_train_cse",
56
+ images_root="coco/train2014",
57
+ annotations_fpath="coco_cse/densepose_train2014_cse.json",
58
+ ),
59
+ CocoDatasetInfo(
60
+ name="densepose_coco_2014_minival_cse",
61
+ images_root="coco/val2014",
62
+ annotations_fpath="coco_cse/densepose_minival2014_cse.json",
63
+ ),
64
+ CocoDatasetInfo(
65
+ name="densepose_coco_2014_minival_100_cse",
66
+ images_root="coco/val2014",
67
+ annotations_fpath="coco_cse/densepose_minival2014_100_cse.json",
68
+ ),
69
+ CocoDatasetInfo(
70
+ name="densepose_coco_2014_valminusminival_cse",
71
+ images_root="coco/val2014",
72
+ annotations_fpath="coco_cse/densepose_valminusminival2014_cse.json",
73
+ ),
74
+ CocoDatasetInfo(
75
+ name="densepose_chimps",
76
+ images_root="densepose_chimps/images",
77
+ annotations_fpath="densepose_chimps/densepose_chimps_densepose.json",
78
+ ),
79
+ CocoDatasetInfo(
80
+ name="densepose_chimps_cse_train",
81
+ images_root="densepose_chimps/images",
82
+ annotations_fpath="densepose_chimps/densepose_chimps_cse_train.json",
83
+ ),
84
+ CocoDatasetInfo(
85
+ name="densepose_chimps_cse_val",
86
+ images_root="densepose_chimps/images",
87
+ annotations_fpath="densepose_chimps/densepose_chimps_cse_val.json",
88
+ ),
89
+ CocoDatasetInfo(
90
+ name="posetrack2017_train",
91
+ images_root="posetrack2017/posetrack_data_2017",
92
+ annotations_fpath="posetrack2017/densepose_posetrack_train2017.json",
93
+ ),
94
+ CocoDatasetInfo(
95
+ name="posetrack2017_val",
96
+ images_root="posetrack2017/posetrack_data_2017",
97
+ annotations_fpath="posetrack2017/densepose_posetrack_val2017.json",
98
+ ),
99
+ CocoDatasetInfo(
100
+ name="lvis_v05_train",
101
+ images_root="coco/train2017",
102
+ annotations_fpath="lvis/lvis_v0.5_plus_dp_train.json",
103
+ ),
104
+ CocoDatasetInfo(
105
+ name="lvis_v05_val",
106
+ images_root="coco/val2017",
107
+ annotations_fpath="lvis/lvis_v0.5_plus_dp_val.json",
108
+ ),
109
+ ]
110
+
111
+
112
+ BASE_DATASETS = [
113
+ CocoDatasetInfo(
114
+ name="base_coco_2017_train",
115
+ images_root="coco/train2017",
116
+ annotations_fpath="coco/annotations/instances_train2017.json",
117
+ ),
118
+ CocoDatasetInfo(
119
+ name="base_coco_2017_val",
120
+ images_root="coco/val2017",
121
+ annotations_fpath="coco/annotations/instances_val2017.json",
122
+ ),
123
+ CocoDatasetInfo(
124
+ name="base_coco_2017_val_100",
125
+ images_root="coco/val2017",
126
+ annotations_fpath="coco/annotations/instances_val2017_100.json",
127
+ ),
128
+ ]
129
+
130
+
131
+ def get_metadata(base_path: Optional[str]) -> Dict[str, Any]:
132
+ """
133
+ Returns metadata associated with COCO DensePose datasets
134
+
135
+ Args:
136
+ base_path: Optional[str]
137
+ Base path used to load metadata from
138
+
139
+ Returns:
140
+ Dict[str, Any]
141
+ Metadata in the form of a dictionary
142
+ """
143
+ meta = {
144
+ "densepose_transform_src": maybe_prepend_base_path(base_path, "UV_symmetry_transforms.mat"),
145
+ "densepose_smpl_subdiv": maybe_prepend_base_path(base_path, "SMPL_subdiv.mat"),
146
+ "densepose_smpl_subdiv_transform": maybe_prepend_base_path(
147
+ base_path,
148
+ "SMPL_SUBDIV_TRANSFORM.mat",
149
+ ),
150
+ }
151
+ return meta
152
+
153
+
154
+ def _load_coco_annotations(json_file: str):
155
+ """
156
+ Load COCO annotations from a JSON file
157
+
158
+ Args:
159
+ json_file: str
160
+ Path to the file to load annotations from
161
+ Returns:
162
+ Instance of `pycocotools.coco.COCO` that provides access to annotations
163
+ data
164
+ """
165
+ from pycocotools.coco import COCO
166
+
167
+ logger = logging.getLogger(__name__)
168
+ timer = Timer()
169
+ with contextlib.redirect_stdout(io.StringIO()):
170
+ coco_api = COCO(json_file)
171
+ if timer.seconds() > 1:
172
+ logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
173
+ return coco_api
174
+
175
+
176
+ def _add_categories_metadata(dataset_name: str, categories: List[Dict[str, Any]]):
177
+ meta = MetadataCatalog.get(dataset_name)
178
+ meta.categories = {c["id"]: c["name"] for c in categories}
179
+ logger = logging.getLogger(__name__)
180
+ logger.info("Dataset {} categories: {}".format(dataset_name, meta.categories))
181
+
182
+
183
+ def _verify_annotations_have_unique_ids(json_file: str, anns: List[List[Dict[str, Any]]]):
184
+ if "minival" in json_file:
185
+ # Skip validation on COCO2014 valminusminival and minival annotations
186
+ # The ratio of buggy annotations there is tiny and does not affect accuracy
187
+ # Therefore we explicitly white-list them
188
+ return
189
+ ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
190
+ assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique!".format(
191
+ json_file
192
+ )
193
+
194
+
195
+ def _maybe_add_bbox(obj: Dict[str, Any], ann_dict: Dict[str, Any]):
196
+ if "bbox" not in ann_dict:
197
+ return
198
+ obj["bbox"] = ann_dict["bbox"]
199
+ obj["bbox_mode"] = BoxMode.XYWH_ABS
200
+
201
+
202
+ def _maybe_add_segm(obj: Dict[str, Any], ann_dict: Dict[str, Any]):
203
+ if "segmentation" not in ann_dict:
204
+ return
205
+ segm = ann_dict["segmentation"]
206
+ if not isinstance(segm, dict):
207
+ # filter out invalid polygons (< 3 points)
208
+ segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
209
+ if len(segm) == 0:
210
+ return
211
+ obj["segmentation"] = segm
212
+
213
+
214
+ def _maybe_add_keypoints(obj: Dict[str, Any], ann_dict: Dict[str, Any]):
215
+ if "keypoints" not in ann_dict:
216
+ return
217
+ keypts = ann_dict["keypoints"] # list[int]
218
+ for idx, v in enumerate(keypts):
219
+ if idx % 3 != 2:
220
+ # COCO's segmentation coordinates are floating points in [0, H or W],
221
+ # but keypoint coordinates are integers in [0, H-1 or W-1]
222
+ # Therefore we assume the coordinates are "pixel indices" and
223
+ # add 0.5 to convert to floating point coordinates.
224
+ keypts[idx] = v + 0.5
225
+ obj["keypoints"] = keypts
226
+
227
+
228
+ def _maybe_add_densepose(obj: Dict[str, Any], ann_dict: Dict[str, Any]):
229
+ for key in DENSEPOSE_ALL_POSSIBLE_KEYS:
230
+ if key in ann_dict:
231
+ obj[key] = ann_dict[key]
232
+
233
+
234
+ def _combine_images_with_annotations(
235
+ dataset_name: str,
236
+ image_root: str,
237
+ img_datas: Iterable[Dict[str, Any]],
238
+ ann_datas: Iterable[Iterable[Dict[str, Any]]],
239
+ ):
240
+
241
+ ann_keys = ["iscrowd", "category_id"]
242
+ dataset_dicts = []
243
+ contains_video_frame_info = False
244
+
245
+ for img_dict, ann_dicts in zip(img_datas, ann_datas):
246
+ record = {}
247
+ record["file_name"] = os.path.join(image_root, img_dict["file_name"])
248
+ record["height"] = img_dict["height"]
249
+ record["width"] = img_dict["width"]
250
+ record["image_id"] = img_dict["id"]
251
+ record["dataset"] = dataset_name
252
+ if "frame_id" in img_dict:
253
+ record["frame_id"] = img_dict["frame_id"]
254
+ record["video_id"] = img_dict.get("vid_id", None)
255
+ contains_video_frame_info = True
256
+ objs = []
257
+ for ann_dict in ann_dicts:
258
+ assert ann_dict["image_id"] == record["image_id"]
259
+ assert ann_dict.get("ignore", 0) == 0
260
+ obj = {key: ann_dict[key] for key in ann_keys if key in ann_dict}
261
+ _maybe_add_bbox(obj, ann_dict)
262
+ _maybe_add_segm(obj, ann_dict)
263
+ _maybe_add_keypoints(obj, ann_dict)
264
+ _maybe_add_densepose(obj, ann_dict)
265
+ objs.append(obj)
266
+ record["annotations"] = objs
267
+ dataset_dicts.append(record)
268
+ if contains_video_frame_info:
269
+ create_video_frame_mapping(dataset_name, dataset_dicts)
270
+ return dataset_dicts
271
+
272
+
273
+ def get_contiguous_id_to_category_id_map(metadata):
274
+ cat_id_2_cont_id = metadata.thing_dataset_id_to_contiguous_id
275
+ cont_id_2_cat_id = {}
276
+ for cat_id, cont_id in cat_id_2_cont_id.items():
277
+ if cont_id in cont_id_2_cat_id:
278
+ continue
279
+ cont_id_2_cat_id[cont_id] = cat_id
280
+ return cont_id_2_cat_id
281
+
282
+
283
+ def maybe_filter_categories_cocoapi(dataset_name, coco_api):
284
+ meta = MetadataCatalog.get(dataset_name)
285
+ cont_id_2_cat_id = get_contiguous_id_to_category_id_map(meta)
286
+ cat_id_2_cont_id = meta.thing_dataset_id_to_contiguous_id
287
+ # filter categories
288
+ cats = []
289
+ for cat in coco_api.dataset["categories"]:
290
+ cat_id = cat["id"]
291
+ if cat_id not in cat_id_2_cont_id:
292
+ continue
293
+ cont_id = cat_id_2_cont_id[cat_id]
294
+ if (cont_id in cont_id_2_cat_id) and (cont_id_2_cat_id[cont_id] == cat_id):
295
+ cats.append(cat)
296
+ coco_api.dataset["categories"] = cats
297
+ # filter annotations, if multiple categories are mapped to a single
298
+ # contiguous ID, use only one category ID and map all annotations to that category ID
299
+ anns = []
300
+ for ann in coco_api.dataset["annotations"]:
301
+ cat_id = ann["category_id"]
302
+ if cat_id not in cat_id_2_cont_id:
303
+ continue
304
+ cont_id = cat_id_2_cont_id[cat_id]
305
+ ann["category_id"] = cont_id_2_cat_id[cont_id]
306
+ anns.append(ann)
307
+ coco_api.dataset["annotations"] = anns
308
+ # recreate index
309
+ coco_api.createIndex()
310
+
311
+
312
+ def maybe_filter_and_map_categories_cocoapi(dataset_name, coco_api):
313
+ meta = MetadataCatalog.get(dataset_name)
314
+ category_id_map = meta.thing_dataset_id_to_contiguous_id
315
+ # map categories
316
+ cats = []
317
+ for cat in coco_api.dataset["categories"]:
318
+ cat_id = cat["id"]
319
+ if cat_id not in category_id_map:
320
+ continue
321
+ cat["id"] = category_id_map[cat_id]
322
+ cats.append(cat)
323
+ coco_api.dataset["categories"] = cats
324
+ # map annotation categories
325
+ anns = []
326
+ for ann in coco_api.dataset["annotations"]:
327
+ cat_id = ann["category_id"]
328
+ if cat_id not in category_id_map:
329
+ continue
330
+ ann["category_id"] = category_id_map[cat_id]
331
+ anns.append(ann)
332
+ coco_api.dataset["annotations"] = anns
333
+ # recreate index
334
+ coco_api.createIndex()
335
+
336
+
337
+ def create_video_frame_mapping(dataset_name, dataset_dicts):
338
+ mapping = defaultdict(dict)
339
+ for d in dataset_dicts:
340
+ video_id = d.get("video_id")
341
+ if video_id is None:
342
+ continue
343
+ mapping[video_id].update({d["frame_id"]: d["file_name"]})
344
+ MetadataCatalog.get(dataset_name).set(video_frame_mapping=mapping)
345
+
346
+
347
+ def load_coco_json(annotations_json_file: str, image_root: str, dataset_name: str):
348
+ """
349
+ Loads a JSON file with annotations in COCO instances format.
350
+ Replaces `detectron2.data.datasets.coco.load_coco_json` to handle metadata
351
+ in a more flexible way. Postpones category mapping to a later stage to be
352
+ able to combine several datasets with different (but coherent) sets of
353
+ categories.
354
+
355
+ Args:
356
+
357
+ annotations_json_file: str
358
+ Path to the JSON file with annotations in COCO instances format.
359
+ image_root: str
360
+ directory that contains all the images
361
+ dataset_name: str
362
+ the name that identifies a dataset, e.g. "densepose_coco_2014_train"
363
+ extra_annotation_keys: Optional[List[str]]
364
+ If provided, these keys are used to extract additional data from
365
+ the annotations.
366
+ """
367
+ coco_api = _load_coco_annotations(PathManager.get_local_path(annotations_json_file))
368
+ _add_categories_metadata(dataset_name, coco_api.loadCats(coco_api.getCatIds()))
369
+ # sort indices for reproducible results
370
+ img_ids = sorted(coco_api.imgs.keys())
371
+ # imgs is a list of dicts, each looks something like:
372
+ # {'license': 4,
373
+ # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
374
+ # 'file_name': 'COCO_val2014_000000001268.jpg',
375
+ # 'height': 427,
376
+ # 'width': 640,
377
+ # 'date_captured': '2013-11-17 05:57:24',
378
+ # 'id': 1268}
379
+ imgs = coco_api.loadImgs(img_ids)
380
+ logger = logging.getLogger(__name__)
381
+ logger.info("Loaded {} images in COCO format from {}".format(len(imgs), annotations_json_file))
382
+ # anns is a list[list[dict]], where each dict is an annotation
383
+ # record for an object. The inner list enumerates the objects in an image
384
+ # and the outer list enumerates over images.
385
+ anns = [coco_api.imgToAnns[img_id] for img_id in img_ids]
386
+ _verify_annotations_have_unique_ids(annotations_json_file, anns)
387
+ dataset_records = _combine_images_with_annotations(dataset_name, image_root, imgs, anns)
388
+ return dataset_records
389
+
390
+
391
+ def register_dataset(dataset_data: CocoDatasetInfo, datasets_root: Optional[str] = None):
392
+ """
393
+ Registers provided COCO DensePose dataset
394
+
395
+ Args:
396
+ dataset_data: CocoDatasetInfo
397
+ Dataset data
398
+ datasets_root: Optional[str]
399
+ Datasets root folder (default: None)
400
+ """
401
+ annotations_fpath = maybe_prepend_base_path(datasets_root, dataset_data.annotations_fpath)
402
+ images_root = maybe_prepend_base_path(datasets_root, dataset_data.images_root)
403
+
404
+ def load_annotations():
405
+ return load_coco_json(
406
+ annotations_json_file=annotations_fpath,
407
+ image_root=images_root,
408
+ dataset_name=dataset_data.name,
409
+ )
410
+
411
+ DatasetCatalog.register(dataset_data.name, load_annotations)
412
+ MetadataCatalog.get(dataset_data.name).set(
413
+ json_file=annotations_fpath,
414
+ image_root=images_root,
415
+ **get_metadata(DENSEPOSE_METADATA_URL_PREFIX)
416
+ )
417
+
418
+
419
+ def register_datasets(
420
+ datasets_data: Iterable[CocoDatasetInfo], datasets_root: Optional[str] = None
421
+ ):
422
+ """
423
+ Registers provided COCO DensePose datasets
424
+
425
+ Args:
426
+ datasets_data: Iterable[CocoDatasetInfo]
427
+ An iterable of dataset datas
428
+ datasets_root: Optional[str]
429
+ Datasets root folder (default: None)
430
+ """
431
+ for dataset_data in datasets_data:
432
+ register_dataset(dataset_data, datasets_root)
densepose/data/datasets/dataset_type.py CHANGED
@@ -1,11 +1,11 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from enum import Enum
4
-
5
-
6
- class DatasetType(Enum):
7
- """
8
- Dataset type, mostly used for datasets that contain data to bootstrap models on
9
- """
10
-
11
- VIDEO_LIST = "video_list"
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class DatasetType(Enum):
7
+ """
8
+ Dataset type, mostly used for datasets that contain data to bootstrap models on
9
+ """
10
+
11
+ VIDEO_LIST = "video_list"
densepose/data/datasets/lvis.py CHANGED
@@ -1,257 +1,257 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
- import logging
3
- import os
4
- from typing import Any, Dict, Iterable, List, Optional
5
- from fvcore.common.timer import Timer
6
-
7
- from detectron2.data import DatasetCatalog, MetadataCatalog
8
- from detectron2.data.datasets.lvis import get_lvis_instances_meta
9
- from detectron2.structures import BoxMode
10
- from detectron2.utils.file_io import PathManager
11
-
12
- from ..utils import maybe_prepend_base_path
13
- from .coco import (
14
- DENSEPOSE_ALL_POSSIBLE_KEYS,
15
- DENSEPOSE_METADATA_URL_PREFIX,
16
- CocoDatasetInfo,
17
- get_metadata,
18
- )
19
-
20
- DATASETS = [
21
- CocoDatasetInfo(
22
- name="densepose_lvis_v1_ds1_train_v1",
23
- images_root="coco_",
24
- annotations_fpath="lvis/densepose_lvis_v1_ds1_train_v1.json",
25
- ),
26
- CocoDatasetInfo(
27
- name="densepose_lvis_v1_ds1_val_v1",
28
- images_root="coco_",
29
- annotations_fpath="lvis/densepose_lvis_v1_ds1_val_v1.json",
30
- ),
31
- CocoDatasetInfo(
32
- name="densepose_lvis_v1_ds2_train_v1",
33
- images_root="coco_",
34
- annotations_fpath="lvis/densepose_lvis_v1_ds2_train_v1.json",
35
- ),
36
- CocoDatasetInfo(
37
- name="densepose_lvis_v1_ds2_val_v1",
38
- images_root="coco_",
39
- annotations_fpath="lvis/densepose_lvis_v1_ds2_val_v1.json",
40
- ),
41
- CocoDatasetInfo(
42
- name="densepose_lvis_v1_ds1_val_animals_100",
43
- images_root="coco_",
44
- annotations_fpath="lvis/densepose_lvis_v1_val_animals_100_v2.json",
45
- ),
46
- ]
47
-
48
-
49
- def _load_lvis_annotations(json_file: str):
50
- """
51
- Load COCO annotations from a JSON file
52
-
53
- Args:
54
- json_file: str
55
- Path to the file to load annotations from
56
- Returns:
57
- Instance of `pycocotools.coco.COCO` that provides access to annotations
58
- data
59
- """
60
- from lvis import LVIS
61
-
62
- json_file = PathManager.get_local_path(json_file)
63
- logger = logging.getLogger(__name__)
64
- timer = Timer()
65
- lvis_api = LVIS(json_file)
66
- if timer.seconds() > 1:
67
- logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
68
- return lvis_api
69
-
70
-
71
- def _add_categories_metadata(dataset_name: str) -> None:
72
- metadict = get_lvis_instances_meta(dataset_name)
73
- categories = metadict["thing_classes"]
74
- metadata = MetadataCatalog.get(dataset_name)
75
- metadata.categories = {i + 1: categories[i] for i in range(len(categories))}
76
- logger = logging.getLogger(__name__)
77
- logger.info(f"Dataset {dataset_name} has {len(categories)} categories")
78
-
79
-
80
- def _verify_annotations_have_unique_ids(json_file: str, anns: List[List[Dict[str, Any]]]) -> None:
81
- ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
82
- assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique!".format(
83
- json_file
84
- )
85
-
86
-
87
- def _maybe_add_bbox(obj: Dict[str, Any], ann_dict: Dict[str, Any]) -> None:
88
- if "bbox" not in ann_dict:
89
- return
90
- obj["bbox"] = ann_dict["bbox"]
91
- obj["bbox_mode"] = BoxMode.XYWH_ABS
92
-
93
-
94
- def _maybe_add_segm(obj: Dict[str, Any], ann_dict: Dict[str, Any]) -> None:
95
- if "segmentation" not in ann_dict:
96
- return
97
- segm = ann_dict["segmentation"]
98
- if not isinstance(segm, dict):
99
- # filter out invalid polygons (< 3 points)
100
- segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
101
- if len(segm) == 0:
102
- return
103
- obj["segmentation"] = segm
104
-
105
-
106
- def _maybe_add_keypoints(obj: Dict[str, Any], ann_dict: Dict[str, Any]) -> None:
107
- if "keypoints" not in ann_dict:
108
- return
109
- keypts = ann_dict["keypoints"] # list[int]
110
- for idx, v in enumerate(keypts):
111
- if idx % 3 != 2:
112
- # COCO's segmentation coordinates are floating points in [0, H or W],
113
- # but keypoint coordinates are integers in [0, H-1 or W-1]
114
- # Therefore we assume the coordinates are "pixel indices" and
115
- # add 0.5 to convert to floating point coordinates.
116
- keypts[idx] = v + 0.5
117
- obj["keypoints"] = keypts
118
-
119
-
120
- def _maybe_add_densepose(obj: Dict[str, Any], ann_dict: Dict[str, Any]) -> None:
121
- for key in DENSEPOSE_ALL_POSSIBLE_KEYS:
122
- if key in ann_dict:
123
- obj[key] = ann_dict[key]
124
-
125
-
126
- def _combine_images_with_annotations(
127
- dataset_name: str,
128
- image_root: str,
129
- img_datas: Iterable[Dict[str, Any]],
130
- ann_datas: Iterable[Iterable[Dict[str, Any]]],
131
- ):
132
-
133
- dataset_dicts = []
134
-
135
- def get_file_name(img_root, img_dict):
136
- # Determine the path including the split folder ("train2017", "val2017", "test2017") from
137
- # the coco_url field. Example:
138
- # 'coco_url': 'http://images.cocodataset.org/train2017/000000155379.jpg'
139
- split_folder, file_name = img_dict["coco_url"].split("/")[-2:]
140
- return os.path.join(img_root + split_folder, file_name)
141
-
142
- for img_dict, ann_dicts in zip(img_datas, ann_datas):
143
- record = {}
144
- record["file_name"] = get_file_name(image_root, img_dict)
145
- record["height"] = img_dict["height"]
146
- record["width"] = img_dict["width"]
147
- record["not_exhaustive_category_ids"] = img_dict.get("not_exhaustive_category_ids", [])
148
- record["neg_category_ids"] = img_dict.get("neg_category_ids", [])
149
- record["image_id"] = img_dict["id"]
150
- record["dataset"] = dataset_name
151
-
152
- objs = []
153
- for ann_dict in ann_dicts:
154
- assert ann_dict["image_id"] == record["image_id"]
155
- obj = {}
156
- _maybe_add_bbox(obj, ann_dict)
157
- obj["iscrowd"] = ann_dict.get("iscrowd", 0)
158
- obj["category_id"] = ann_dict["category_id"]
159
- _maybe_add_segm(obj, ann_dict)
160
- _maybe_add_keypoints(obj, ann_dict)
161
- _maybe_add_densepose(obj, ann_dict)
162
- objs.append(obj)
163
- record["annotations"] = objs
164
- dataset_dicts.append(record)
165
- return dataset_dicts
166
-
167
-
168
- def load_lvis_json(annotations_json_file: str, image_root: str, dataset_name: str):
169
- """
170
- Loads a JSON file with annotations in LVIS instances format.
171
- Replaces `detectron2.data.datasets.coco.load_lvis_json` to handle metadata
172
- in a more flexible way. Postpones category mapping to a later stage to be
173
- able to combine several datasets with different (but coherent) sets of
174
- categories.
175
-
176
- Args:
177
-
178
- annotations_json_file: str
179
- Path to the JSON file with annotations in COCO instances format.
180
- image_root: str
181
- directory that contains all the images
182
- dataset_name: str
183
- the name that identifies a dataset, e.g. "densepose_coco_2014_train"
184
- extra_annotation_keys: Optional[List[str]]
185
- If provided, these keys are used to extract additional data from
186
- the annotations.
187
- """
188
- lvis_api = _load_lvis_annotations(PathManager.get_local_path(annotations_json_file))
189
-
190
- _add_categories_metadata(dataset_name)
191
-
192
- # sort indices for reproducible results
193
- img_ids = sorted(lvis_api.imgs.keys())
194
- # imgs is a list of dicts, each looks something like:
195
- # {'license': 4,
196
- # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
197
- # 'file_name': 'COCO_val2014_000000001268.jpg',
198
- # 'height': 427,
199
- # 'width': 640,
200
- # 'date_captured': '2013-11-17 05:57:24',
201
- # 'id': 1268}
202
- imgs = lvis_api.load_imgs(img_ids)
203
- logger = logging.getLogger(__name__)
204
- logger.info("Loaded {} images in LVIS format from {}".format(len(imgs), annotations_json_file))
205
- # anns is a list[list[dict]], where each dict is an annotation
206
- # record for an object. The inner list enumerates the objects in an image
207
- # and the outer list enumerates over images.
208
- anns = [lvis_api.img_ann_map[img_id] for img_id in img_ids]
209
-
210
- _verify_annotations_have_unique_ids(annotations_json_file, anns)
211
- dataset_records = _combine_images_with_annotations(dataset_name, image_root, imgs, anns)
212
- return dataset_records
213
-
214
-
215
- def register_dataset(dataset_data: CocoDatasetInfo, datasets_root: Optional[str] = None) -> None:
216
- """
217
- Registers provided LVIS DensePose dataset
218
-
219
- Args:
220
- dataset_data: CocoDatasetInfo
221
- Dataset data
222
- datasets_root: Optional[str]
223
- Datasets root folder (default: None)
224
- """
225
- annotations_fpath = maybe_prepend_base_path(datasets_root, dataset_data.annotations_fpath)
226
- images_root = maybe_prepend_base_path(datasets_root, dataset_data.images_root)
227
-
228
- def load_annotations():
229
- return load_lvis_json(
230
- annotations_json_file=annotations_fpath,
231
- image_root=images_root,
232
- dataset_name=dataset_data.name,
233
- )
234
-
235
- DatasetCatalog.register(dataset_data.name, load_annotations)
236
- MetadataCatalog.get(dataset_data.name).set(
237
- json_file=annotations_fpath,
238
- image_root=images_root,
239
- evaluator_type="lvis",
240
- **get_metadata(DENSEPOSE_METADATA_URL_PREFIX),
241
- )
242
-
243
-
244
- def register_datasets(
245
- datasets_data: Iterable[CocoDatasetInfo], datasets_root: Optional[str] = None
246
- ) -> None:
247
- """
248
- Registers provided LVIS DensePose datasets
249
-
250
- Args:
251
- datasets_data: Iterable[CocoDatasetInfo]
252
- An iterable of dataset datas
253
- datasets_root: Optional[str]
254
- Datasets root folder (default: None)
255
- """
256
- for dataset_data in datasets_data:
257
- register_dataset(dataset_data, datasets_root)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+ import logging
3
+ import os
4
+ from typing import Any, Dict, Iterable, List, Optional
5
+ from fvcore.common.timer import Timer
6
+
7
+ from detectron2.data import DatasetCatalog, MetadataCatalog
8
+ from detectron2.data.datasets.lvis import get_lvis_instances_meta
9
+ from detectron2.structures import BoxMode
10
+ from detectron2.utils.file_io import PathManager
11
+
12
+ from ..utils import maybe_prepend_base_path
13
+ from .coco import (
14
+ DENSEPOSE_ALL_POSSIBLE_KEYS,
15
+ DENSEPOSE_METADATA_URL_PREFIX,
16
+ CocoDatasetInfo,
17
+ get_metadata,
18
+ )
19
+
20
+ DATASETS = [
21
+ CocoDatasetInfo(
22
+ name="densepose_lvis_v1_ds1_train_v1",
23
+ images_root="coco_",
24
+ annotations_fpath="lvis/densepose_lvis_v1_ds1_train_v1.json",
25
+ ),
26
+ CocoDatasetInfo(
27
+ name="densepose_lvis_v1_ds1_val_v1",
28
+ images_root="coco_",
29
+ annotations_fpath="lvis/densepose_lvis_v1_ds1_val_v1.json",
30
+ ),
31
+ CocoDatasetInfo(
32
+ name="densepose_lvis_v1_ds2_train_v1",
33
+ images_root="coco_",
34
+ annotations_fpath="lvis/densepose_lvis_v1_ds2_train_v1.json",
35
+ ),
36
+ CocoDatasetInfo(
37
+ name="densepose_lvis_v1_ds2_val_v1",
38
+ images_root="coco_",
39
+ annotations_fpath="lvis/densepose_lvis_v1_ds2_val_v1.json",
40
+ ),
41
+ CocoDatasetInfo(
42
+ name="densepose_lvis_v1_ds1_val_animals_100",
43
+ images_root="coco_",
44
+ annotations_fpath="lvis/densepose_lvis_v1_val_animals_100_v2.json",
45
+ ),
46
+ ]
47
+
48
+
49
+ def _load_lvis_annotations(json_file: str):
50
+ """
51
+ Load COCO annotations from a JSON file
52
+
53
+ Args:
54
+ json_file: str
55
+ Path to the file to load annotations from
56
+ Returns:
57
+ Instance of `pycocotools.coco.COCO` that provides access to annotations
58
+ data
59
+ """
60
+ from lvis import LVIS
61
+
62
+ json_file = PathManager.get_local_path(json_file)
63
+ logger = logging.getLogger(__name__)
64
+ timer = Timer()
65
+ lvis_api = LVIS(json_file)
66
+ if timer.seconds() > 1:
67
+ logger.info("Loading {} takes {:.2f} seconds.".format(json_file, timer.seconds()))
68
+ return lvis_api
69
+
70
+
71
+ def _add_categories_metadata(dataset_name: str) -> None:
72
+ metadict = get_lvis_instances_meta(dataset_name)
73
+ categories = metadict["thing_classes"]
74
+ metadata = MetadataCatalog.get(dataset_name)
75
+ metadata.categories = {i + 1: categories[i] for i in range(len(categories))}
76
+ logger = logging.getLogger(__name__)
77
+ logger.info(f"Dataset {dataset_name} has {len(categories)} categories")
78
+
79
+
80
+ def _verify_annotations_have_unique_ids(json_file: str, anns: List[List[Dict[str, Any]]]) -> None:
81
+ ann_ids = [ann["id"] for anns_per_image in anns for ann in anns_per_image]
82
+ assert len(set(ann_ids)) == len(ann_ids), "Annotation ids in '{}' are not unique!".format(
83
+ json_file
84
+ )
85
+
86
+
87
+ def _maybe_add_bbox(obj: Dict[str, Any], ann_dict: Dict[str, Any]) -> None:
88
+ if "bbox" not in ann_dict:
89
+ return
90
+ obj["bbox"] = ann_dict["bbox"]
91
+ obj["bbox_mode"] = BoxMode.XYWH_ABS
92
+
93
+
94
+ def _maybe_add_segm(obj: Dict[str, Any], ann_dict: Dict[str, Any]) -> None:
95
+ if "segmentation" not in ann_dict:
96
+ return
97
+ segm = ann_dict["segmentation"]
98
+ if not isinstance(segm, dict):
99
+ # filter out invalid polygons (< 3 points)
100
+ segm = [poly for poly in segm if len(poly) % 2 == 0 and len(poly) >= 6]
101
+ if len(segm) == 0:
102
+ return
103
+ obj["segmentation"] = segm
104
+
105
+
106
+ def _maybe_add_keypoints(obj: Dict[str, Any], ann_dict: Dict[str, Any]) -> None:
107
+ if "keypoints" not in ann_dict:
108
+ return
109
+ keypts = ann_dict["keypoints"] # list[int]
110
+ for idx, v in enumerate(keypts):
111
+ if idx % 3 != 2:
112
+ # COCO's segmentation coordinates are floating points in [0, H or W],
113
+ # but keypoint coordinates are integers in [0, H-1 or W-1]
114
+ # Therefore we assume the coordinates are "pixel indices" and
115
+ # add 0.5 to convert to floating point coordinates.
116
+ keypts[idx] = v + 0.5
117
+ obj["keypoints"] = keypts
118
+
119
+
120
+ def _maybe_add_densepose(obj: Dict[str, Any], ann_dict: Dict[str, Any]) -> None:
121
+ for key in DENSEPOSE_ALL_POSSIBLE_KEYS:
122
+ if key in ann_dict:
123
+ obj[key] = ann_dict[key]
124
+
125
+
126
+ def _combine_images_with_annotations(
127
+ dataset_name: str,
128
+ image_root: str,
129
+ img_datas: Iterable[Dict[str, Any]],
130
+ ann_datas: Iterable[Iterable[Dict[str, Any]]],
131
+ ):
132
+
133
+ dataset_dicts = []
134
+
135
+ def get_file_name(img_root, img_dict):
136
+ # Determine the path including the split folder ("train2017", "val2017", "test2017") from
137
+ # the coco_url field. Example:
138
+ # 'coco_url': 'http://images.cocodataset.org/train2017/000000155379.jpg'
139
+ split_folder, file_name = img_dict["coco_url"].split("/")[-2:]
140
+ return os.path.join(img_root + split_folder, file_name)
141
+
142
+ for img_dict, ann_dicts in zip(img_datas, ann_datas):
143
+ record = {}
144
+ record["file_name"] = get_file_name(image_root, img_dict)
145
+ record["height"] = img_dict["height"]
146
+ record["width"] = img_dict["width"]
147
+ record["not_exhaustive_category_ids"] = img_dict.get("not_exhaustive_category_ids", [])
148
+ record["neg_category_ids"] = img_dict.get("neg_category_ids", [])
149
+ record["image_id"] = img_dict["id"]
150
+ record["dataset"] = dataset_name
151
+
152
+ objs = []
153
+ for ann_dict in ann_dicts:
154
+ assert ann_dict["image_id"] == record["image_id"]
155
+ obj = {}
156
+ _maybe_add_bbox(obj, ann_dict)
157
+ obj["iscrowd"] = ann_dict.get("iscrowd", 0)
158
+ obj["category_id"] = ann_dict["category_id"]
159
+ _maybe_add_segm(obj, ann_dict)
160
+ _maybe_add_keypoints(obj, ann_dict)
161
+ _maybe_add_densepose(obj, ann_dict)
162
+ objs.append(obj)
163
+ record["annotations"] = objs
164
+ dataset_dicts.append(record)
165
+ return dataset_dicts
166
+
167
+
168
+ def load_lvis_json(annotations_json_file: str, image_root: str, dataset_name: str):
169
+ """
170
+ Loads a JSON file with annotations in LVIS instances format.
171
+ Replaces `detectron2.data.datasets.coco.load_lvis_json` to handle metadata
172
+ in a more flexible way. Postpones category mapping to a later stage to be
173
+ able to combine several datasets with different (but coherent) sets of
174
+ categories.
175
+
176
+ Args:
177
+
178
+ annotations_json_file: str
179
+ Path to the JSON file with annotations in COCO instances format.
180
+ image_root: str
181
+ directory that contains all the images
182
+ dataset_name: str
183
+ the name that identifies a dataset, e.g. "densepose_coco_2014_train"
184
+ extra_annotation_keys: Optional[List[str]]
185
+ If provided, these keys are used to extract additional data from
186
+ the annotations.
187
+ """
188
+ lvis_api = _load_lvis_annotations(PathManager.get_local_path(annotations_json_file))
189
+
190
+ _add_categories_metadata(dataset_name)
191
+
192
+ # sort indices for reproducible results
193
+ img_ids = sorted(lvis_api.imgs.keys())
194
+ # imgs is a list of dicts, each looks something like:
195
+ # {'license': 4,
196
+ # 'url': 'http://farm6.staticflickr.com/5454/9413846304_881d5e5c3b_z.jpg',
197
+ # 'file_name': 'COCO_val2014_000000001268.jpg',
198
+ # 'height': 427,
199
+ # 'width': 640,
200
+ # 'date_captured': '2013-11-17 05:57:24',
201
+ # 'id': 1268}
202
+ imgs = lvis_api.load_imgs(img_ids)
203
+ logger = logging.getLogger(__name__)
204
+ logger.info("Loaded {} images in LVIS format from {}".format(len(imgs), annotations_json_file))
205
+ # anns is a list[list[dict]], where each dict is an annotation
206
+ # record for an object. The inner list enumerates the objects in an image
207
+ # and the outer list enumerates over images.
208
+ anns = [lvis_api.img_ann_map[img_id] for img_id in img_ids]
209
+
210
+ _verify_annotations_have_unique_ids(annotations_json_file, anns)
211
+ dataset_records = _combine_images_with_annotations(dataset_name, image_root, imgs, anns)
212
+ return dataset_records
213
+
214
+
215
+ def register_dataset(dataset_data: CocoDatasetInfo, datasets_root: Optional[str] = None) -> None:
216
+ """
217
+ Registers provided LVIS DensePose dataset
218
+
219
+ Args:
220
+ dataset_data: CocoDatasetInfo
221
+ Dataset data
222
+ datasets_root: Optional[str]
223
+ Datasets root folder (default: None)
224
+ """
225
+ annotations_fpath = maybe_prepend_base_path(datasets_root, dataset_data.annotations_fpath)
226
+ images_root = maybe_prepend_base_path(datasets_root, dataset_data.images_root)
227
+
228
+ def load_annotations():
229
+ return load_lvis_json(
230
+ annotations_json_file=annotations_fpath,
231
+ image_root=images_root,
232
+ dataset_name=dataset_data.name,
233
+ )
234
+
235
+ DatasetCatalog.register(dataset_data.name, load_annotations)
236
+ MetadataCatalog.get(dataset_data.name).set(
237
+ json_file=annotations_fpath,
238
+ image_root=images_root,
239
+ evaluator_type="lvis",
240
+ **get_metadata(DENSEPOSE_METADATA_URL_PREFIX),
241
+ )
242
+
243
+
244
+ def register_datasets(
245
+ datasets_data: Iterable[CocoDatasetInfo], datasets_root: Optional[str] = None
246
+ ) -> None:
247
+ """
248
+ Registers provided LVIS DensePose datasets
249
+
250
+ Args:
251
+ datasets_data: Iterable[CocoDatasetInfo]
252
+ An iterable of dataset datas
253
+ datasets_root: Optional[str]
254
+ Datasets root folder (default: None)
255
+ """
256
+ for dataset_data in datasets_data:
257
+ register_dataset(dataset_data, datasets_root)
densepose/data/image_list_dataset.py CHANGED
@@ -1,72 +1,72 @@
1
- # -*- coding: utf-8 -*-
2
- # Copyright (c) Facebook, Inc. and its affiliates.
3
-
4
- import logging
5
- import numpy as np
6
- from typing import Any, Callable, Dict, List, Optional, Union
7
- import torch
8
- from torch.utils.data.dataset import Dataset
9
-
10
- from detectron2.data.detection_utils import read_image
11
-
12
- ImageTransform = Callable[[torch.Tensor], torch.Tensor]
13
-
14
-
15
- class ImageListDataset(Dataset):
16
- """
17
- Dataset that provides images from a list.
18
- """
19
-
20
- _EMPTY_IMAGE = torch.empty((0, 3, 1, 1))
21
-
22
- def __init__(
23
- self,
24
- image_list: List[str],
25
- category_list: Union[str, List[str], None] = None,
26
- transform: Optional[ImageTransform] = None,
27
- ):
28
- """
29
- Args:
30
- image_list (List[str]): list of paths to image files
31
- category_list (Union[str, List[str], None]): list of animal categories for
32
- each image. If it is a string, or None, this applies to all images
33
- """
34
- if type(category_list) == list:
35
- self.category_list = category_list
36
- else:
37
- self.category_list = [category_list] * len(image_list)
38
- assert len(image_list) == len(
39
- self.category_list
40
- ), "length of image and category lists must be equal"
41
- self.image_list = image_list
42
- self.transform = transform
43
-
44
- def __getitem__(self, idx: int) -> Dict[str, Any]:
45
- """
46
- Gets selected images from the list
47
-
48
- Args:
49
- idx (int): video index in the video list file
50
- Returns:
51
- A dictionary containing two keys:
52
- images (torch.Tensor): tensor of size [N, 3, H, W] (N = 1, or 0 for _EMPTY_IMAGE)
53
- categories (List[str]): categories of the frames
54
- """
55
- categories = [self.category_list[idx]]
56
- fpath = self.image_list[idx]
57
- transform = self.transform
58
-
59
- try:
60
- image = torch.from_numpy(np.ascontiguousarray(read_image(fpath, format="BGR")))
61
- image = image.permute(2, 0, 1).unsqueeze(0).float() # HWC -> NCHW
62
- if transform is not None:
63
- image = transform(image)
64
- return {"images": image, "categories": categories}
65
- except (OSError, RuntimeError) as e:
66
- logger = logging.getLogger(__name__)
67
- logger.warning(f"Error opening image file container {fpath}: {e}")
68
-
69
- return {"images": self._EMPTY_IMAGE, "categories": []}
70
-
71
- def __len__(self):
72
- return len(self.image_list)
 
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) Facebook, Inc. and its affiliates.
3
+
4
+ import logging
5
+ import numpy as np
6
+ from typing import Any, Callable, Dict, List, Optional, Union
7
+ import torch
8
+ from torch.utils.data.dataset import Dataset
9
+
10
+ from detectron2.data.detection_utils import read_image
11
+
12
+ ImageTransform = Callable[[torch.Tensor], torch.Tensor]
13
+
14
+
15
+ class ImageListDataset(Dataset):
16
+ """
17
+ Dataset that provides images from a list.
18
+ """
19
+
20
+ _EMPTY_IMAGE = torch.empty((0, 3, 1, 1))
21
+
22
+ def __init__(
23
+ self,
24
+ image_list: List[str],
25
+ category_list: Union[str, List[str], None] = None,
26
+ transform: Optional[ImageTransform] = None,
27
+ ):
28
+ """
29
+ Args:
30
+ image_list (List[str]): list of paths to image files
31
+ category_list (Union[str, List[str], None]): list of animal categories for
32
+ each image. If it is a string, or None, this applies to all images
33
+ """
34
+ if type(category_list) == list:
35
+ self.category_list = category_list
36
+ else:
37
+ self.category_list = [category_list] * len(image_list)
38
+ assert len(image_list) == len(
39
+ self.category_list
40
+ ), "length of image and category lists must be equal"
41
+ self.image_list = image_list
42
+ self.transform = transform
43
+
44
+ def __getitem__(self, idx: int) -> Dict[str, Any]:
45
+ """
46
+ Gets selected images from the list
47
+
48
+ Args:
49
+ idx (int): video index in the video list file
50
+ Returns:
51
+ A dictionary containing two keys:
52
+ images (torch.Tensor): tensor of size [N, 3, H, W] (N = 1, or 0 for _EMPTY_IMAGE)
53
+ categories (List[str]): categories of the frames
54
+ """
55
+ categories = [self.category_list[idx]]
56
+ fpath = self.image_list[idx]
57
+ transform = self.transform
58
+
59
+ try:
60
+ image = torch.from_numpy(np.ascontiguousarray(read_image(fpath, format="BGR")))
61
+ image = image.permute(2, 0, 1).unsqueeze(0).float() # HWC -> NCHW
62
+ if transform is not None:
63
+ image = transform(image)
64
+ return {"images": image, "categories": categories}
65
+ except (OSError, RuntimeError) as e:
66
+ logger = logging.getLogger(__name__)
67
+ logger.warning(f"Error opening image file container {fpath}: {e}")
68
+
69
+ return {"images": self._EMPTY_IMAGE, "categories": []}
70
+
71
+ def __len__(self):
72
+ return len(self.image_list)
densepose/data/inference_based_loader.py CHANGED
@@ -1,172 +1,172 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import random
4
- from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple
5
- import torch
6
- from torch import nn
7
-
8
- SampledData = Any
9
- ModelOutput = Any
10
-
11
-
12
- def _grouper(iterable: Iterable[Any], n: int, fillvalue=None) -> Iterator[Tuple[Any]]:
13
- """
14
- Group elements of an iterable by chunks of size `n`, e.g.
15
- grouper(range(9), 4) ->
16
- (0, 1, 2, 3), (4, 5, 6, 7), (8, None, None, None)
17
- """
18
- it = iter(iterable)
19
- while True:
20
- values = []
21
- for _ in range(n):
22
- try:
23
- value = next(it)
24
- except StopIteration:
25
- if values:
26
- values.extend([fillvalue] * (n - len(values)))
27
- yield tuple(values)
28
- return
29
- values.append(value)
30
- yield tuple(values)
31
-
32
-
33
- class ScoreBasedFilter:
34
- """
35
- Filters entries in model output based on their scores
36
- Discards all entries with score less than the specified minimum
37
- """
38
-
39
- def __init__(self, min_score: float = 0.8):
40
- self.min_score = min_score
41
-
42
- def __call__(self, model_output: ModelOutput) -> ModelOutput:
43
- for model_output_i in model_output:
44
- instances = model_output_i["instances"]
45
- if not instances.has("scores"):
46
- continue
47
- instances_filtered = instances[instances.scores >= self.min_score]
48
- model_output_i["instances"] = instances_filtered
49
- return model_output
50
-
51
-
52
- class InferenceBasedLoader:
53
- """
54
- Data loader based on results inferred by a model. Consists of:
55
- - a data loader that provides batches of images
56
- - a model that is used to infer the results
57
- - a data sampler that converts inferred results to annotations
58
- """
59
-
60
- def __init__(
61
- self,
62
- model: nn.Module,
63
- data_loader: Iterable[List[Dict[str, Any]]],
64
- data_sampler: Optional[Callable[[ModelOutput], List[SampledData]]] = None,
65
- data_filter: Optional[Callable[[ModelOutput], ModelOutput]] = None,
66
- shuffle: bool = True,
67
- batch_size: int = 4,
68
- inference_batch_size: int = 4,
69
- drop_last: bool = False,
70
- category_to_class_mapping: Optional[dict] = None,
71
- ):
72
- """
73
- Constructor
74
-
75
- Args:
76
- model (torch.nn.Module): model used to produce data
77
- data_loader (Iterable[List[Dict[str, Any]]]): iterable that provides
78
- dictionaries with "images" and "categories" fields to perform inference on
79
- data_sampler (Callable: ModelOutput -> SampledData): functor
80
- that produces annotation data from inference results;
81
- (optional, default: None)
82
- data_filter (Callable: ModelOutput -> ModelOutput): filter
83
- that selects model outputs for further processing
84
- (optional, default: None)
85
- shuffle (bool): if True, the input images get shuffled
86
- batch_size (int): batch size for the produced annotation data
87
- inference_batch_size (int): batch size for input images
88
- drop_last (bool): if True, drop the last batch if it is undersized
89
- category_to_class_mapping (dict): category to class mapping
90
- """
91
- self.model = model
92
- self.model.eval()
93
- self.data_loader = data_loader
94
- self.data_sampler = data_sampler
95
- self.data_filter = data_filter
96
- self.shuffle = shuffle
97
- self.batch_size = batch_size
98
- self.inference_batch_size = inference_batch_size
99
- self.drop_last = drop_last
100
- if category_to_class_mapping is not None:
101
- self.category_to_class_mapping = category_to_class_mapping
102
- else:
103
- self.category_to_class_mapping = {}
104
-
105
- def __iter__(self) -> Iterator[List[SampledData]]:
106
- for batch in self.data_loader:
107
- # batch : List[Dict[str: Tensor[N, C, H, W], str: Optional[str]]]
108
- # images_batch : Tensor[N, C, H, W]
109
- # image : Tensor[C, H, W]
110
- images_and_categories = [
111
- {"image": image, "category": category}
112
- for element in batch
113
- for image, category in zip(element["images"], element["categories"])
114
- ]
115
- if not images_and_categories:
116
- continue
117
- if self.shuffle:
118
- random.shuffle(images_and_categories)
119
- yield from self._produce_data(images_and_categories) # pyre-ignore[6]
120
-
121
- def _produce_data(
122
- self, images_and_categories: List[Tuple[torch.Tensor, Optional[str]]]
123
- ) -> Iterator[List[SampledData]]:
124
- """
125
- Produce batches of data from images
126
-
127
- Args:
128
- images_and_categories (List[Tuple[torch.Tensor, Optional[str]]]):
129
- list of images and corresponding categories to process
130
-
131
- Returns:
132
- Iterator over batches of data sampled from model outputs
133
- """
134
- data_batches: List[SampledData] = []
135
- category_to_class_mapping = self.category_to_class_mapping
136
- batched_images_and_categories = _grouper(images_and_categories, self.inference_batch_size)
137
- for batch in batched_images_and_categories:
138
- batch = [
139
- {
140
- "image": image_and_category["image"].to(self.model.device),
141
- "category": image_and_category["category"],
142
- }
143
- for image_and_category in batch
144
- if image_and_category is not None
145
- ]
146
- if not batch:
147
- continue
148
- with torch.no_grad():
149
- model_output = self.model(batch)
150
- for model_output_i, batch_i in zip(model_output, batch):
151
- assert len(batch_i["image"].shape) == 3
152
- model_output_i["image"] = batch_i["image"]
153
- instance_class = category_to_class_mapping.get(batch_i["category"], 0)
154
- model_output_i["instances"].dataset_classes = torch.tensor(
155
- [instance_class] * len(model_output_i["instances"])
156
- )
157
- model_output_filtered = (
158
- model_output if self.data_filter is None else self.data_filter(model_output)
159
- )
160
- data = (
161
- model_output_filtered
162
- if self.data_sampler is None
163
- else self.data_sampler(model_output_filtered)
164
- )
165
- for data_i in data:
166
- if len(data_i["instances"]):
167
- data_batches.append(data_i)
168
- if len(data_batches) >= self.batch_size:
169
- yield data_batches[: self.batch_size]
170
- data_batches = data_batches[self.batch_size :]
171
- if not self.drop_last and data_batches:
172
- yield data_batches
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import random
4
+ from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple
5
+ import torch
6
+ from torch import nn
7
+
8
+ SampledData = Any
9
+ ModelOutput = Any
10
+
11
+
12
+ def _grouper(iterable: Iterable[Any], n: int, fillvalue=None) -> Iterator[Tuple[Any]]:
13
+ """
14
+ Group elements of an iterable by chunks of size `n`, e.g.
15
+ grouper(range(9), 4) ->
16
+ (0, 1, 2, 3), (4, 5, 6, 7), (8, None, None, None)
17
+ """
18
+ it = iter(iterable)
19
+ while True:
20
+ values = []
21
+ for _ in range(n):
22
+ try:
23
+ value = next(it)
24
+ except StopIteration:
25
+ if values:
26
+ values.extend([fillvalue] * (n - len(values)))
27
+ yield tuple(values)
28
+ return
29
+ values.append(value)
30
+ yield tuple(values)
31
+
32
+
33
+ class ScoreBasedFilter:
34
+ """
35
+ Filters entries in model output based on their scores
36
+ Discards all entries with score less than the specified minimum
37
+ """
38
+
39
+ def __init__(self, min_score: float = 0.8):
40
+ self.min_score = min_score
41
+
42
+ def __call__(self, model_output: ModelOutput) -> ModelOutput:
43
+ for model_output_i in model_output:
44
+ instances = model_output_i["instances"]
45
+ if not instances.has("scores"):
46
+ continue
47
+ instances_filtered = instances[instances.scores >= self.min_score]
48
+ model_output_i["instances"] = instances_filtered
49
+ return model_output
50
+
51
+
52
+ class InferenceBasedLoader:
53
+ """
54
+ Data loader based on results inferred by a model. Consists of:
55
+ - a data loader that provides batches of images
56
+ - a model that is used to infer the results
57
+ - a data sampler that converts inferred results to annotations
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ model: nn.Module,
63
+ data_loader: Iterable[List[Dict[str, Any]]],
64
+ data_sampler: Optional[Callable[[ModelOutput], List[SampledData]]] = None,
65
+ data_filter: Optional[Callable[[ModelOutput], ModelOutput]] = None,
66
+ shuffle: bool = True,
67
+ batch_size: int = 4,
68
+ inference_batch_size: int = 4,
69
+ drop_last: bool = False,
70
+ category_to_class_mapping: Optional[dict] = None,
71
+ ):
72
+ """
73
+ Constructor
74
+
75
+ Args:
76
+ model (torch.nn.Module): model used to produce data
77
+ data_loader (Iterable[List[Dict[str, Any]]]): iterable that provides
78
+ dictionaries with "images" and "categories" fields to perform inference on
79
+ data_sampler (Callable: ModelOutput -> SampledData): functor
80
+ that produces annotation data from inference results;
81
+ (optional, default: None)
82
+ data_filter (Callable: ModelOutput -> ModelOutput): filter
83
+ that selects model outputs for further processing
84
+ (optional, default: None)
85
+ shuffle (bool): if True, the input images get shuffled
86
+ batch_size (int): batch size for the produced annotation data
87
+ inference_batch_size (int): batch size for input images
88
+ drop_last (bool): if True, drop the last batch if it is undersized
89
+ category_to_class_mapping (dict): category to class mapping
90
+ """
91
+ self.model = model
92
+ self.model.eval()
93
+ self.data_loader = data_loader
94
+ self.data_sampler = data_sampler
95
+ self.data_filter = data_filter
96
+ self.shuffle = shuffle
97
+ self.batch_size = batch_size
98
+ self.inference_batch_size = inference_batch_size
99
+ self.drop_last = drop_last
100
+ if category_to_class_mapping is not None:
101
+ self.category_to_class_mapping = category_to_class_mapping
102
+ else:
103
+ self.category_to_class_mapping = {}
104
+
105
+ def __iter__(self) -> Iterator[List[SampledData]]:
106
+ for batch in self.data_loader:
107
+ # batch : List[Dict[str: Tensor[N, C, H, W], str: Optional[str]]]
108
+ # images_batch : Tensor[N, C, H, W]
109
+ # image : Tensor[C, H, W]
110
+ images_and_categories = [
111
+ {"image": image, "category": category}
112
+ for element in batch
113
+ for image, category in zip(element["images"], element["categories"])
114
+ ]
115
+ if not images_and_categories:
116
+ continue
117
+ if self.shuffle:
118
+ random.shuffle(images_and_categories)
119
+ yield from self._produce_data(images_and_categories) # pyre-ignore[6]
120
+
121
+ def _produce_data(
122
+ self, images_and_categories: List[Tuple[torch.Tensor, Optional[str]]]
123
+ ) -> Iterator[List[SampledData]]:
124
+ """
125
+ Produce batches of data from images
126
+
127
+ Args:
128
+ images_and_categories (List[Tuple[torch.Tensor, Optional[str]]]):
129
+ list of images and corresponding categories to process
130
+
131
+ Returns:
132
+ Iterator over batches of data sampled from model outputs
133
+ """
134
+ data_batches: List[SampledData] = []
135
+ category_to_class_mapping = self.category_to_class_mapping
136
+ batched_images_and_categories = _grouper(images_and_categories, self.inference_batch_size)
137
+ for batch in batched_images_and_categories:
138
+ batch = [
139
+ {
140
+ "image": image_and_category["image"].to(self.model.device),
141
+ "category": image_and_category["category"],
142
+ }
143
+ for image_and_category in batch
144
+ if image_and_category is not None
145
+ ]
146
+ if not batch:
147
+ continue
148
+ with torch.no_grad():
149
+ model_output = self.model(batch)
150
+ for model_output_i, batch_i in zip(model_output, batch):
151
+ assert len(batch_i["image"].shape) == 3
152
+ model_output_i["image"] = batch_i["image"]
153
+ instance_class = category_to_class_mapping.get(batch_i["category"], 0)
154
+ model_output_i["instances"].dataset_classes = torch.tensor(
155
+ [instance_class] * len(model_output_i["instances"])
156
+ )
157
+ model_output_filtered = (
158
+ model_output if self.data_filter is None else self.data_filter(model_output)
159
+ )
160
+ data = (
161
+ model_output_filtered
162
+ if self.data_sampler is None
163
+ else self.data_sampler(model_output_filtered)
164
+ )
165
+ for data_i in data:
166
+ if len(data_i["instances"]):
167
+ data_batches.append(data_i)
168
+ if len(data_batches) >= self.batch_size:
169
+ yield data_batches[: self.batch_size]
170
+ data_batches = data_batches[self.batch_size :]
171
+ if not self.drop_last and data_batches:
172
+ yield data_batches
densepose/data/meshes/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
-
3
- from . import builtin
4
-
5
- __all__ = [k for k in globals().keys() if "builtin" not in k and not k.startswith("_")]
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+
3
+ from . import builtin
4
+
5
+ __all__ = [k for k in globals().keys() if "builtin" not in k and not k.startswith("_")]
densepose/data/meshes/builtin.py CHANGED
@@ -1,101 +1,101 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
-
3
- from .catalog import MeshInfo, register_meshes
4
-
5
- DENSEPOSE_MESHES_DIR = "https://dl.fbaipublicfiles.com/densepose/meshes/"
6
-
7
- MESHES = [
8
- MeshInfo(
9
- name="smpl_27554",
10
- data="smpl_27554.pkl",
11
- geodists="geodists/geodists_smpl_27554.pkl",
12
- symmetry="symmetry/symmetry_smpl_27554.pkl",
13
- texcoords="texcoords/texcoords_smpl_27554.pkl",
14
- ),
15
- MeshInfo(
16
- name="chimp_5029",
17
- data="chimp_5029.pkl",
18
- geodists="geodists/geodists_chimp_5029.pkl",
19
- symmetry="symmetry/symmetry_chimp_5029.pkl",
20
- texcoords="texcoords/texcoords_chimp_5029.pkl",
21
- ),
22
- MeshInfo(
23
- name="cat_5001",
24
- data="cat_5001.pkl",
25
- geodists="geodists/geodists_cat_5001.pkl",
26
- symmetry="symmetry/symmetry_cat_5001.pkl",
27
- texcoords="texcoords/texcoords_cat_5001.pkl",
28
- ),
29
- MeshInfo(
30
- name="cat_7466",
31
- data="cat_7466.pkl",
32
- geodists="geodists/geodists_cat_7466.pkl",
33
- symmetry="symmetry/symmetry_cat_7466.pkl",
34
- texcoords="texcoords/texcoords_cat_7466.pkl",
35
- ),
36
- MeshInfo(
37
- name="sheep_5004",
38
- data="sheep_5004.pkl",
39
- geodists="geodists/geodists_sheep_5004.pkl",
40
- symmetry="symmetry/symmetry_sheep_5004.pkl",
41
- texcoords="texcoords/texcoords_sheep_5004.pkl",
42
- ),
43
- MeshInfo(
44
- name="zebra_5002",
45
- data="zebra_5002.pkl",
46
- geodists="geodists/geodists_zebra_5002.pkl",
47
- symmetry="symmetry/symmetry_zebra_5002.pkl",
48
- texcoords="texcoords/texcoords_zebra_5002.pkl",
49
- ),
50
- MeshInfo(
51
- name="horse_5004",
52
- data="horse_5004.pkl",
53
- geodists="geodists/geodists_horse_5004.pkl",
54
- symmetry="symmetry/symmetry_horse_5004.pkl",
55
- texcoords="texcoords/texcoords_zebra_5002.pkl",
56
- ),
57
- MeshInfo(
58
- name="giraffe_5002",
59
- data="giraffe_5002.pkl",
60
- geodists="geodists/geodists_giraffe_5002.pkl",
61
- symmetry="symmetry/symmetry_giraffe_5002.pkl",
62
- texcoords="texcoords/texcoords_giraffe_5002.pkl",
63
- ),
64
- MeshInfo(
65
- name="elephant_5002",
66
- data="elephant_5002.pkl",
67
- geodists="geodists/geodists_elephant_5002.pkl",
68
- symmetry="symmetry/symmetry_elephant_5002.pkl",
69
- texcoords="texcoords/texcoords_elephant_5002.pkl",
70
- ),
71
- MeshInfo(
72
- name="dog_5002",
73
- data="dog_5002.pkl",
74
- geodists="geodists/geodists_dog_5002.pkl",
75
- symmetry="symmetry/symmetry_dog_5002.pkl",
76
- texcoords="texcoords/texcoords_dog_5002.pkl",
77
- ),
78
- MeshInfo(
79
- name="dog_7466",
80
- data="dog_7466.pkl",
81
- geodists="geodists/geodists_dog_7466.pkl",
82
- symmetry="symmetry/symmetry_dog_7466.pkl",
83
- texcoords="texcoords/texcoords_dog_7466.pkl",
84
- ),
85
- MeshInfo(
86
- name="cow_5002",
87
- data="cow_5002.pkl",
88
- geodists="geodists/geodists_cow_5002.pkl",
89
- symmetry="symmetry/symmetry_cow_5002.pkl",
90
- texcoords="texcoords/texcoords_cow_5002.pkl",
91
- ),
92
- MeshInfo(
93
- name="bear_4936",
94
- data="bear_4936.pkl",
95
- geodists="geodists/geodists_bear_4936.pkl",
96
- symmetry="symmetry/symmetry_bear_4936.pkl",
97
- texcoords="texcoords/texcoords_bear_4936.pkl",
98
- ),
99
- ]
100
-
101
- register_meshes(MESHES, DENSEPOSE_MESHES_DIR)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+
3
+ from .catalog import MeshInfo, register_meshes
4
+
5
+ DENSEPOSE_MESHES_DIR = "https://dl.fbaipublicfiles.com/densepose/meshes/"
6
+
7
+ MESHES = [
8
+ MeshInfo(
9
+ name="smpl_27554",
10
+ data="smpl_27554.pkl",
11
+ geodists="geodists/geodists_smpl_27554.pkl",
12
+ symmetry="symmetry/symmetry_smpl_27554.pkl",
13
+ texcoords="texcoords/texcoords_smpl_27554.pkl",
14
+ ),
15
+ MeshInfo(
16
+ name="chimp_5029",
17
+ data="chimp_5029.pkl",
18
+ geodists="geodists/geodists_chimp_5029.pkl",
19
+ symmetry="symmetry/symmetry_chimp_5029.pkl",
20
+ texcoords="texcoords/texcoords_chimp_5029.pkl",
21
+ ),
22
+ MeshInfo(
23
+ name="cat_5001",
24
+ data="cat_5001.pkl",
25
+ geodists="geodists/geodists_cat_5001.pkl",
26
+ symmetry="symmetry/symmetry_cat_5001.pkl",
27
+ texcoords="texcoords/texcoords_cat_5001.pkl",
28
+ ),
29
+ MeshInfo(
30
+ name="cat_7466",
31
+ data="cat_7466.pkl",
32
+ geodists="geodists/geodists_cat_7466.pkl",
33
+ symmetry="symmetry/symmetry_cat_7466.pkl",
34
+ texcoords="texcoords/texcoords_cat_7466.pkl",
35
+ ),
36
+ MeshInfo(
37
+ name="sheep_5004",
38
+ data="sheep_5004.pkl",
39
+ geodists="geodists/geodists_sheep_5004.pkl",
40
+ symmetry="symmetry/symmetry_sheep_5004.pkl",
41
+ texcoords="texcoords/texcoords_sheep_5004.pkl",
42
+ ),
43
+ MeshInfo(
44
+ name="zebra_5002",
45
+ data="zebra_5002.pkl",
46
+ geodists="geodists/geodists_zebra_5002.pkl",
47
+ symmetry="symmetry/symmetry_zebra_5002.pkl",
48
+ texcoords="texcoords/texcoords_zebra_5002.pkl",
49
+ ),
50
+ MeshInfo(
51
+ name="horse_5004",
52
+ data="horse_5004.pkl",
53
+ geodists="geodists/geodists_horse_5004.pkl",
54
+ symmetry="symmetry/symmetry_horse_5004.pkl",
55
+ texcoords="texcoords/texcoords_zebra_5002.pkl",
56
+ ),
57
+ MeshInfo(
58
+ name="giraffe_5002",
59
+ data="giraffe_5002.pkl",
60
+ geodists="geodists/geodists_giraffe_5002.pkl",
61
+ symmetry="symmetry/symmetry_giraffe_5002.pkl",
62
+ texcoords="texcoords/texcoords_giraffe_5002.pkl",
63
+ ),
64
+ MeshInfo(
65
+ name="elephant_5002",
66
+ data="elephant_5002.pkl",
67
+ geodists="geodists/geodists_elephant_5002.pkl",
68
+ symmetry="symmetry/symmetry_elephant_5002.pkl",
69
+ texcoords="texcoords/texcoords_elephant_5002.pkl",
70
+ ),
71
+ MeshInfo(
72
+ name="dog_5002",
73
+ data="dog_5002.pkl",
74
+ geodists="geodists/geodists_dog_5002.pkl",
75
+ symmetry="symmetry/symmetry_dog_5002.pkl",
76
+ texcoords="texcoords/texcoords_dog_5002.pkl",
77
+ ),
78
+ MeshInfo(
79
+ name="dog_7466",
80
+ data="dog_7466.pkl",
81
+ geodists="geodists/geodists_dog_7466.pkl",
82
+ symmetry="symmetry/symmetry_dog_7466.pkl",
83
+ texcoords="texcoords/texcoords_dog_7466.pkl",
84
+ ),
85
+ MeshInfo(
86
+ name="cow_5002",
87
+ data="cow_5002.pkl",
88
+ geodists="geodists/geodists_cow_5002.pkl",
89
+ symmetry="symmetry/symmetry_cow_5002.pkl",
90
+ texcoords="texcoords/texcoords_cow_5002.pkl",
91
+ ),
92
+ MeshInfo(
93
+ name="bear_4936",
94
+ data="bear_4936.pkl",
95
+ geodists="geodists/geodists_bear_4936.pkl",
96
+ symmetry="symmetry/symmetry_bear_4936.pkl",
97
+ texcoords="texcoords/texcoords_bear_4936.pkl",
98
+ ),
99
+ ]
100
+
101
+ register_meshes(MESHES, DENSEPOSE_MESHES_DIR)
densepose/data/meshes/catalog.py CHANGED
@@ -1,71 +1,71 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
-
3
- import logging
4
- from collections import UserDict
5
- from dataclasses import dataclass
6
- from typing import Iterable, Optional
7
-
8
- from ..utils import maybe_prepend_base_path
9
-
10
-
11
- @dataclass
12
- class MeshInfo:
13
- name: str
14
- data: str
15
- geodists: Optional[str] = None
16
- symmetry: Optional[str] = None
17
- texcoords: Optional[str] = None
18
-
19
-
20
- class _MeshCatalog(UserDict):
21
- def __init__(self, *args, **kwargs):
22
- super().__init__(*args, **kwargs)
23
- self.mesh_ids = {}
24
- self.mesh_names = {}
25
- self.max_mesh_id = -1
26
-
27
- def __setitem__(self, key, value):
28
- if key in self:
29
- logger = logging.getLogger(__name__)
30
- logger.warning(
31
- f"Overwriting mesh catalog entry '{key}': old value {self[key]}"
32
- f", new value {value}"
33
- )
34
- mesh_id = self.mesh_ids[key]
35
- else:
36
- self.max_mesh_id += 1
37
- mesh_id = self.max_mesh_id
38
- super().__setitem__(key, value)
39
- self.mesh_ids[key] = mesh_id
40
- self.mesh_names[mesh_id] = key
41
-
42
- def get_mesh_id(self, shape_name: str) -> int:
43
- return self.mesh_ids[shape_name]
44
-
45
- def get_mesh_name(self, mesh_id: int) -> str:
46
- return self.mesh_names[mesh_id]
47
-
48
-
49
- MeshCatalog = _MeshCatalog()
50
-
51
-
52
- def register_mesh(mesh_info: MeshInfo, base_path: Optional[str]) -> None:
53
- geodists, symmetry, texcoords = mesh_info.geodists, mesh_info.symmetry, mesh_info.texcoords
54
- if geodists:
55
- geodists = maybe_prepend_base_path(base_path, geodists)
56
- if symmetry:
57
- symmetry = maybe_prepend_base_path(base_path, symmetry)
58
- if texcoords:
59
- texcoords = maybe_prepend_base_path(base_path, texcoords)
60
- MeshCatalog[mesh_info.name] = MeshInfo(
61
- name=mesh_info.name,
62
- data=maybe_prepend_base_path(base_path, mesh_info.data),
63
- geodists=geodists,
64
- symmetry=symmetry,
65
- texcoords=texcoords,
66
- )
67
-
68
-
69
- def register_meshes(mesh_infos: Iterable[MeshInfo], base_path: Optional[str]) -> None:
70
- for mesh_info in mesh_infos:
71
- register_mesh(mesh_info, base_path)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+
3
+ import logging
4
+ from collections import UserDict
5
+ from dataclasses import dataclass
6
+ from typing import Iterable, Optional
7
+
8
+ from ..utils import maybe_prepend_base_path
9
+
10
+
11
+ @dataclass
12
+ class MeshInfo:
13
+ name: str
14
+ data: str
15
+ geodists: Optional[str] = None
16
+ symmetry: Optional[str] = None
17
+ texcoords: Optional[str] = None
18
+
19
+
20
+ class _MeshCatalog(UserDict):
21
+ def __init__(self, *args, **kwargs):
22
+ super().__init__(*args, **kwargs)
23
+ self.mesh_ids = {}
24
+ self.mesh_names = {}
25
+ self.max_mesh_id = -1
26
+
27
+ def __setitem__(self, key, value):
28
+ if key in self:
29
+ logger = logging.getLogger(__name__)
30
+ logger.warning(
31
+ f"Overwriting mesh catalog entry '{key}': old value {self[key]}"
32
+ f", new value {value}"
33
+ )
34
+ mesh_id = self.mesh_ids[key]
35
+ else:
36
+ self.max_mesh_id += 1
37
+ mesh_id = self.max_mesh_id
38
+ super().__setitem__(key, value)
39
+ self.mesh_ids[key] = mesh_id
40
+ self.mesh_names[mesh_id] = key
41
+
42
+ def get_mesh_id(self, shape_name: str) -> int:
43
+ return self.mesh_ids[shape_name]
44
+
45
+ def get_mesh_name(self, mesh_id: int) -> str:
46
+ return self.mesh_names[mesh_id]
47
+
48
+
49
+ MeshCatalog = _MeshCatalog()
50
+
51
+
52
+ def register_mesh(mesh_info: MeshInfo, base_path: Optional[str]) -> None:
53
+ geodists, symmetry, texcoords = mesh_info.geodists, mesh_info.symmetry, mesh_info.texcoords
54
+ if geodists:
55
+ geodists = maybe_prepend_base_path(base_path, geodists)
56
+ if symmetry:
57
+ symmetry = maybe_prepend_base_path(base_path, symmetry)
58
+ if texcoords:
59
+ texcoords = maybe_prepend_base_path(base_path, texcoords)
60
+ MeshCatalog[mesh_info.name] = MeshInfo(
61
+ name=mesh_info.name,
62
+ data=maybe_prepend_base_path(base_path, mesh_info.data),
63
+ geodists=geodists,
64
+ symmetry=symmetry,
65
+ texcoords=texcoords,
66
+ )
67
+
68
+
69
+ def register_meshes(mesh_infos: Iterable[MeshInfo], base_path: Optional[str]) -> None:
70
+ for mesh_info in mesh_infos:
71
+ register_mesh(mesh_info, base_path)
densepose/data/samplers/__init__.py CHANGED
@@ -1,8 +1,8 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .densepose_uniform import DensePoseUniformSampler
4
- from .densepose_confidence_based import DensePoseConfidenceBasedSampler
5
- from .densepose_cse_uniform import DensePoseCSEUniformSampler
6
- from .densepose_cse_confidence_based import DensePoseCSEConfidenceBasedSampler
7
- from .mask_from_densepose import MaskFromDensePoseSampler
8
- from .prediction_to_gt import PredictionToGroundTruthSampler
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .densepose_uniform import DensePoseUniformSampler
4
+ from .densepose_confidence_based import DensePoseConfidenceBasedSampler
5
+ from .densepose_cse_uniform import DensePoseCSEUniformSampler
6
+ from .densepose_cse_confidence_based import DensePoseCSEConfidenceBasedSampler
7
+ from .mask_from_densepose import MaskFromDensePoseSampler
8
+ from .prediction_to_gt import PredictionToGroundTruthSampler
densepose/data/samplers/densepose_base.py CHANGED
@@ -1,203 +1,203 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Any, Dict, List, Tuple
4
- import torch
5
- from torch.nn import functional as F
6
-
7
- from detectron2.structures import BoxMode, Instances
8
-
9
- from densepose.converters import ToChartResultConverter
10
- from densepose.converters.base import IntTupleBox, make_int_box
11
- from densepose.structures import DensePoseDataRelative, DensePoseList
12
-
13
-
14
- class DensePoseBaseSampler:
15
- """
16
- Base DensePose sampler to produce DensePose data from DensePose predictions.
17
- Samples for each class are drawn according to some distribution over all pixels estimated
18
- to belong to that class.
19
- """
20
-
21
- def __init__(self, count_per_class: int = 8):
22
- """
23
- Constructor
24
-
25
- Args:
26
- count_per_class (int): the sampler produces at most `count_per_class`
27
- samples for each category
28
- """
29
- self.count_per_class = count_per_class
30
-
31
- def __call__(self, instances: Instances) -> DensePoseList:
32
- """
33
- Convert DensePose predictions (an instance of `DensePoseChartPredictorOutput`)
34
- into DensePose annotations data (an instance of `DensePoseList`)
35
- """
36
- boxes_xyxy_abs = instances.pred_boxes.tensor.clone().cpu()
37
- boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
38
- dp_datas = []
39
- for i in range(len(boxes_xywh_abs)):
40
- annotation_i = self._sample(instances[i], make_int_box(boxes_xywh_abs[i]))
41
- annotation_i[DensePoseDataRelative.S_KEY] = self._resample_mask( # pyre-ignore[6]
42
- instances[i].pred_densepose
43
- )
44
- dp_datas.append(DensePoseDataRelative(annotation_i))
45
- # create densepose annotations on CPU
46
- dp_list = DensePoseList(dp_datas, boxes_xyxy_abs, instances.image_size)
47
- return dp_list
48
-
49
- def _sample(self, instance: Instances, bbox_xywh: IntTupleBox) -> Dict[str, List[Any]]:
50
- """
51
- Sample DensPoseDataRelative from estimation results
52
- """
53
- labels, dp_result = self._produce_labels_and_results(instance)
54
- annotation = {
55
- DensePoseDataRelative.X_KEY: [],
56
- DensePoseDataRelative.Y_KEY: [],
57
- DensePoseDataRelative.U_KEY: [],
58
- DensePoseDataRelative.V_KEY: [],
59
- DensePoseDataRelative.I_KEY: [],
60
- }
61
- n, h, w = dp_result.shape
62
- for part_id in range(1, DensePoseDataRelative.N_PART_LABELS + 1):
63
- # indices - tuple of 3 1D tensors of size k
64
- # 0: index along the first dimension N
65
- # 1: index along H dimension
66
- # 2: index along W dimension
67
- indices = torch.nonzero(labels.expand(n, h, w) == part_id, as_tuple=True)
68
- # values - an array of size [n, k]
69
- # n: number of channels (U, V, confidences)
70
- # k: number of points labeled with part_id
71
- values = dp_result[indices].view(n, -1)
72
- k = values.shape[1]
73
- count = min(self.count_per_class, k)
74
- if count <= 0:
75
- continue
76
- index_sample = self._produce_index_sample(values, count)
77
- sampled_values = values[:, index_sample]
78
- sampled_y = indices[1][index_sample] + 0.5
79
- sampled_x = indices[2][index_sample] + 0.5
80
- # prepare / normalize data
81
- x = (sampled_x / w * 256.0).cpu().tolist()
82
- y = (sampled_y / h * 256.0).cpu().tolist()
83
- u = sampled_values[0].clamp(0, 1).cpu().tolist()
84
- v = sampled_values[1].clamp(0, 1).cpu().tolist()
85
- fine_segm_labels = [part_id] * count
86
- # extend annotations
87
- annotation[DensePoseDataRelative.X_KEY].extend(x)
88
- annotation[DensePoseDataRelative.Y_KEY].extend(y)
89
- annotation[DensePoseDataRelative.U_KEY].extend(u)
90
- annotation[DensePoseDataRelative.V_KEY].extend(v)
91
- annotation[DensePoseDataRelative.I_KEY].extend(fine_segm_labels)
92
- return annotation
93
-
94
- def _produce_index_sample(self, values: torch.Tensor, count: int):
95
- """
96
- Abstract method to produce a sample of indices to select data
97
- To be implemented in descendants
98
-
99
- Args:
100
- values (torch.Tensor): an array of size [n, k] that contains
101
- estimated values (U, V, confidences);
102
- n: number of channels (U, V, confidences)
103
- k: number of points labeled with part_id
104
- count (int): number of samples to produce, should be positive and <= k
105
-
106
- Return:
107
- list(int): indices of values (along axis 1) selected as a sample
108
- """
109
- raise NotImplementedError
110
-
111
- def _produce_labels_and_results(self, instance: Instances) -> Tuple[torch.Tensor, torch.Tensor]:
112
- """
113
- Method to get labels and DensePose results from an instance
114
-
115
- Args:
116
- instance (Instances): an instance of `DensePoseChartPredictorOutput`
117
-
118
- Return:
119
- labels (torch.Tensor): shape [H, W], DensePose segmentation labels
120
- dp_result (torch.Tensor): shape [2, H, W], stacked DensePose results u and v
121
- """
122
- converter = ToChartResultConverter
123
- chart_result = converter.convert(instance.pred_densepose, instance.pred_boxes)
124
- labels, dp_result = chart_result.labels.cpu(), chart_result.uv.cpu()
125
- return labels, dp_result
126
-
127
- def _resample_mask(self, output: Any) -> torch.Tensor:
128
- """
129
- Convert DensePose predictor output to segmentation annotation - tensors of size
130
- (256, 256) and type `int64`.
131
-
132
- Args:
133
- output: DensePose predictor output with the following attributes:
134
- - coarse_segm: tensor of size [N, D, H, W] with unnormalized coarse
135
- segmentation scores
136
- - fine_segm: tensor of size [N, C, H, W] with unnormalized fine
137
- segmentation scores
138
- Return:
139
- Tensor of size (S, S) and type `int64` with coarse segmentation annotations,
140
- where S = DensePoseDataRelative.MASK_SIZE
141
- """
142
- sz = DensePoseDataRelative.MASK_SIZE
143
- S = (
144
- F.interpolate(output.coarse_segm, (sz, sz), mode="bilinear", align_corners=False)
145
- .argmax(dim=1)
146
- .long()
147
- )
148
- I = (
149
- (
150
- F.interpolate(
151
- output.fine_segm,
152
- (sz, sz),
153
- mode="bilinear",
154
- align_corners=False,
155
- ).argmax(dim=1)
156
- * (S > 0).long()
157
- )
158
- .squeeze()
159
- .cpu()
160
- )
161
- # Map fine segmentation results to coarse segmentation ground truth
162
- # TODO: extract this into separate classes
163
- # coarse segmentation: 1 = Torso, 2 = Right Hand, 3 = Left Hand,
164
- # 4 = Left Foot, 5 = Right Foot, 6 = Upper Leg Right, 7 = Upper Leg Left,
165
- # 8 = Lower Leg Right, 9 = Lower Leg Left, 10 = Upper Arm Left,
166
- # 11 = Upper Arm Right, 12 = Lower Arm Left, 13 = Lower Arm Right,
167
- # 14 = Head
168
- # fine segmentation: 1, 2 = Torso, 3 = Right Hand, 4 = Left Hand,
169
- # 5 = Left Foot, 6 = Right Foot, 7, 9 = Upper Leg Right,
170
- # 8, 10 = Upper Leg Left, 11, 13 = Lower Leg Right,
171
- # 12, 14 = Lower Leg Left, 15, 17 = Upper Arm Left,
172
- # 16, 18 = Upper Arm Right, 19, 21 = Lower Arm Left,
173
- # 20, 22 = Lower Arm Right, 23, 24 = Head
174
- FINE_TO_COARSE_SEGMENTATION = {
175
- 1: 1,
176
- 2: 1,
177
- 3: 2,
178
- 4: 3,
179
- 5: 4,
180
- 6: 5,
181
- 7: 6,
182
- 8: 7,
183
- 9: 6,
184
- 10: 7,
185
- 11: 8,
186
- 12: 9,
187
- 13: 8,
188
- 14: 9,
189
- 15: 10,
190
- 16: 11,
191
- 17: 10,
192
- 18: 11,
193
- 19: 12,
194
- 20: 13,
195
- 21: 12,
196
- 22: 13,
197
- 23: 14,
198
- 24: 14,
199
- }
200
- mask = torch.zeros((sz, sz), dtype=torch.int64, device=torch.device("cpu"))
201
- for i in range(DensePoseDataRelative.N_PART_LABELS):
202
- mask[I == i + 1] = FINE_TO_COARSE_SEGMENTATION[i + 1]
203
- return mask
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Any, Dict, List, Tuple
4
+ import torch
5
+ from torch.nn import functional as F
6
+
7
+ from detectron2.structures import BoxMode, Instances
8
+
9
+ from densepose.converters import ToChartResultConverter
10
+ from densepose.converters.base import IntTupleBox, make_int_box
11
+ from densepose.structures import DensePoseDataRelative, DensePoseList
12
+
13
+
14
+ class DensePoseBaseSampler:
15
+ """
16
+ Base DensePose sampler to produce DensePose data from DensePose predictions.
17
+ Samples for each class are drawn according to some distribution over all pixels estimated
18
+ to belong to that class.
19
+ """
20
+
21
+ def __init__(self, count_per_class: int = 8):
22
+ """
23
+ Constructor
24
+
25
+ Args:
26
+ count_per_class (int): the sampler produces at most `count_per_class`
27
+ samples for each category
28
+ """
29
+ self.count_per_class = count_per_class
30
+
31
+ def __call__(self, instances: Instances) -> DensePoseList:
32
+ """
33
+ Convert DensePose predictions (an instance of `DensePoseChartPredictorOutput`)
34
+ into DensePose annotations data (an instance of `DensePoseList`)
35
+ """
36
+ boxes_xyxy_abs = instances.pred_boxes.tensor.clone().cpu()
37
+ boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
38
+ dp_datas = []
39
+ for i in range(len(boxes_xywh_abs)):
40
+ annotation_i = self._sample(instances[i], make_int_box(boxes_xywh_abs[i]))
41
+ annotation_i[DensePoseDataRelative.S_KEY] = self._resample_mask( # pyre-ignore[6]
42
+ instances[i].pred_densepose
43
+ )
44
+ dp_datas.append(DensePoseDataRelative(annotation_i))
45
+ # create densepose annotations on CPU
46
+ dp_list = DensePoseList(dp_datas, boxes_xyxy_abs, instances.image_size)
47
+ return dp_list
48
+
49
+ def _sample(self, instance: Instances, bbox_xywh: IntTupleBox) -> Dict[str, List[Any]]:
50
+ """
51
+ Sample DensPoseDataRelative from estimation results
52
+ """
53
+ labels, dp_result = self._produce_labels_and_results(instance)
54
+ annotation = {
55
+ DensePoseDataRelative.X_KEY: [],
56
+ DensePoseDataRelative.Y_KEY: [],
57
+ DensePoseDataRelative.U_KEY: [],
58
+ DensePoseDataRelative.V_KEY: [],
59
+ DensePoseDataRelative.I_KEY: [],
60
+ }
61
+ n, h, w = dp_result.shape
62
+ for part_id in range(1, DensePoseDataRelative.N_PART_LABELS + 1):
63
+ # indices - tuple of 3 1D tensors of size k
64
+ # 0: index along the first dimension N
65
+ # 1: index along H dimension
66
+ # 2: index along W dimension
67
+ indices = torch.nonzero(labels.expand(n, h, w) == part_id, as_tuple=True)
68
+ # values - an array of size [n, k]
69
+ # n: number of channels (U, V, confidences)
70
+ # k: number of points labeled with part_id
71
+ values = dp_result[indices].view(n, -1)
72
+ k = values.shape[1]
73
+ count = min(self.count_per_class, k)
74
+ if count <= 0:
75
+ continue
76
+ index_sample = self._produce_index_sample(values, count)
77
+ sampled_values = values[:, index_sample]
78
+ sampled_y = indices[1][index_sample] + 0.5
79
+ sampled_x = indices[2][index_sample] + 0.5
80
+ # prepare / normalize data
81
+ x = (sampled_x / w * 256.0).cpu().tolist()
82
+ y = (sampled_y / h * 256.0).cpu().tolist()
83
+ u = sampled_values[0].clamp(0, 1).cpu().tolist()
84
+ v = sampled_values[1].clamp(0, 1).cpu().tolist()
85
+ fine_segm_labels = [part_id] * count
86
+ # extend annotations
87
+ annotation[DensePoseDataRelative.X_KEY].extend(x)
88
+ annotation[DensePoseDataRelative.Y_KEY].extend(y)
89
+ annotation[DensePoseDataRelative.U_KEY].extend(u)
90
+ annotation[DensePoseDataRelative.V_KEY].extend(v)
91
+ annotation[DensePoseDataRelative.I_KEY].extend(fine_segm_labels)
92
+ return annotation
93
+
94
+ def _produce_index_sample(self, values: torch.Tensor, count: int):
95
+ """
96
+ Abstract method to produce a sample of indices to select data
97
+ To be implemented in descendants
98
+
99
+ Args:
100
+ values (torch.Tensor): an array of size [n, k] that contains
101
+ estimated values (U, V, confidences);
102
+ n: number of channels (U, V, confidences)
103
+ k: number of points labeled with part_id
104
+ count (int): number of samples to produce, should be positive and <= k
105
+
106
+ Return:
107
+ list(int): indices of values (along axis 1) selected as a sample
108
+ """
109
+ raise NotImplementedError
110
+
111
+ def _produce_labels_and_results(self, instance: Instances) -> Tuple[torch.Tensor, torch.Tensor]:
112
+ """
113
+ Method to get labels and DensePose results from an instance
114
+
115
+ Args:
116
+ instance (Instances): an instance of `DensePoseChartPredictorOutput`
117
+
118
+ Return:
119
+ labels (torch.Tensor): shape [H, W], DensePose segmentation labels
120
+ dp_result (torch.Tensor): shape [2, H, W], stacked DensePose results u and v
121
+ """
122
+ converter = ToChartResultConverter
123
+ chart_result = converter.convert(instance.pred_densepose, instance.pred_boxes)
124
+ labels, dp_result = chart_result.labels.cpu(), chart_result.uv.cpu()
125
+ return labels, dp_result
126
+
127
+ def _resample_mask(self, output: Any) -> torch.Tensor:
128
+ """
129
+ Convert DensePose predictor output to segmentation annotation - tensors of size
130
+ (256, 256) and type `int64`.
131
+
132
+ Args:
133
+ output: DensePose predictor output with the following attributes:
134
+ - coarse_segm: tensor of size [N, D, H, W] with unnormalized coarse
135
+ segmentation scores
136
+ - fine_segm: tensor of size [N, C, H, W] with unnormalized fine
137
+ segmentation scores
138
+ Return:
139
+ Tensor of size (S, S) and type `int64` with coarse segmentation annotations,
140
+ where S = DensePoseDataRelative.MASK_SIZE
141
+ """
142
+ sz = DensePoseDataRelative.MASK_SIZE
143
+ S = (
144
+ F.interpolate(output.coarse_segm, (sz, sz), mode="bilinear", align_corners=False)
145
+ .argmax(dim=1)
146
+ .long()
147
+ )
148
+ I = (
149
+ (
150
+ F.interpolate(
151
+ output.fine_segm,
152
+ (sz, sz),
153
+ mode="bilinear",
154
+ align_corners=False,
155
+ ).argmax(dim=1)
156
+ * (S > 0).long()
157
+ )
158
+ .squeeze()
159
+ .cpu()
160
+ )
161
+ # Map fine segmentation results to coarse segmentation ground truth
162
+ # TODO: extract this into separate classes
163
+ # coarse segmentation: 1 = Torso, 2 = Right Hand, 3 = Left Hand,
164
+ # 4 = Left Foot, 5 = Right Foot, 6 = Upper Leg Right, 7 = Upper Leg Left,
165
+ # 8 = Lower Leg Right, 9 = Lower Leg Left, 10 = Upper Arm Left,
166
+ # 11 = Upper Arm Right, 12 = Lower Arm Left, 13 = Lower Arm Right,
167
+ # 14 = Head
168
+ # fine segmentation: 1, 2 = Torso, 3 = Right Hand, 4 = Left Hand,
169
+ # 5 = Left Foot, 6 = Right Foot, 7, 9 = Upper Leg Right,
170
+ # 8, 10 = Upper Leg Left, 11, 13 = Lower Leg Right,
171
+ # 12, 14 = Lower Leg Left, 15, 17 = Upper Arm Left,
172
+ # 16, 18 = Upper Arm Right, 19, 21 = Lower Arm Left,
173
+ # 20, 22 = Lower Arm Right, 23, 24 = Head
174
+ FINE_TO_COARSE_SEGMENTATION = {
175
+ 1: 1,
176
+ 2: 1,
177
+ 3: 2,
178
+ 4: 3,
179
+ 5: 4,
180
+ 6: 5,
181
+ 7: 6,
182
+ 8: 7,
183
+ 9: 6,
184
+ 10: 7,
185
+ 11: 8,
186
+ 12: 9,
187
+ 13: 8,
188
+ 14: 9,
189
+ 15: 10,
190
+ 16: 11,
191
+ 17: 10,
192
+ 18: 11,
193
+ 19: 12,
194
+ 20: 13,
195
+ 21: 12,
196
+ 22: 13,
197
+ 23: 14,
198
+ 24: 14,
199
+ }
200
+ mask = torch.zeros((sz, sz), dtype=torch.int64, device=torch.device("cpu"))
201
+ for i in range(DensePoseDataRelative.N_PART_LABELS):
202
+ mask[I == i + 1] = FINE_TO_COARSE_SEGMENTATION[i + 1]
203
+ return mask
densepose/data/samplers/densepose_confidence_based.py CHANGED
@@ -1,108 +1,108 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import random
4
- from typing import Optional, Tuple
5
- import torch
6
-
7
- from densepose.converters import ToChartResultConverterWithConfidences
8
-
9
- from .densepose_base import DensePoseBaseSampler
10
-
11
-
12
- class DensePoseConfidenceBasedSampler(DensePoseBaseSampler):
13
- """
14
- Samples DensePose data from DensePose predictions.
15
- Samples for each class are drawn using confidence value estimates.
16
- """
17
-
18
- def __init__(
19
- self,
20
- confidence_channel: str,
21
- count_per_class: int = 8,
22
- search_count_multiplier: Optional[float] = None,
23
- search_proportion: Optional[float] = None,
24
- ):
25
- """
26
- Constructor
27
-
28
- Args:
29
- confidence_channel (str): confidence channel to use for sampling;
30
- possible values:
31
- "sigma_2": confidences for UV values
32
- "fine_segm_confidence": confidences for fine segmentation
33
- "coarse_segm_confidence": confidences for coarse segmentation
34
- (default: "sigma_2")
35
- count_per_class (int): the sampler produces at most `count_per_class`
36
- samples for each category (default: 8)
37
- search_count_multiplier (float or None): if not None, the total number
38
- of the most confident estimates of a given class to consider is
39
- defined as `min(search_count_multiplier * count_per_class, N)`,
40
- where `N` is the total number of estimates of the class; cannot be
41
- specified together with `search_proportion` (default: None)
42
- search_proportion (float or None): if not None, the total number of the
43
- of the most confident estimates of a given class to consider is
44
- defined as `min(max(search_proportion * N, count_per_class), N)`,
45
- where `N` is the total number of estimates of the class; cannot be
46
- specified together with `search_count_multiplier` (default: None)
47
- """
48
- super().__init__(count_per_class)
49
- self.confidence_channel = confidence_channel
50
- self.search_count_multiplier = search_count_multiplier
51
- self.search_proportion = search_proportion
52
- assert (search_count_multiplier is None) or (search_proportion is None), (
53
- f"Cannot specify both search_count_multiplier (={search_count_multiplier})"
54
- f"and search_proportion (={search_proportion})"
55
- )
56
-
57
- def _produce_index_sample(self, values: torch.Tensor, count: int):
58
- """
59
- Produce a sample of indices to select data based on confidences
60
-
61
- Args:
62
- values (torch.Tensor): an array of size [n, k] that contains
63
- estimated values (U, V, confidences);
64
- n: number of channels (U, V, confidences)
65
- k: number of points labeled with part_id
66
- count (int): number of samples to produce, should be positive and <= k
67
-
68
- Return:
69
- list(int): indices of values (along axis 1) selected as a sample
70
- """
71
- k = values.shape[1]
72
- if k == count:
73
- index_sample = list(range(k))
74
- else:
75
- # take the best count * search_count_multiplier pixels,
76
- # sample from them uniformly
77
- # (here best = smallest variance)
78
- _, sorted_confidence_indices = torch.sort(values[2])
79
- if self.search_count_multiplier is not None:
80
- search_count = min(int(count * self.search_count_multiplier), k)
81
- elif self.search_proportion is not None:
82
- search_count = min(max(int(k * self.search_proportion), count), k)
83
- else:
84
- search_count = min(count, k)
85
- sample_from_top = random.sample(range(search_count), count)
86
- index_sample = sorted_confidence_indices[:search_count][sample_from_top]
87
- return index_sample
88
-
89
- def _produce_labels_and_results(self, instance) -> Tuple[torch.Tensor, torch.Tensor]:
90
- """
91
- Method to get labels and DensePose results from an instance, with confidences
92
-
93
- Args:
94
- instance (Instances): an instance of `DensePoseChartPredictorOutputWithConfidences`
95
-
96
- Return:
97
- labels (torch.Tensor): shape [H, W], DensePose segmentation labels
98
- dp_result (torch.Tensor): shape [3, H, W], DensePose results u and v
99
- stacked with the confidence channel
100
- """
101
- converter = ToChartResultConverterWithConfidences
102
- chart_result = converter.convert(instance.pred_densepose, instance.pred_boxes)
103
- labels, dp_result = chart_result.labels.cpu(), chart_result.uv.cpu()
104
- dp_result = torch.cat(
105
- (dp_result, getattr(chart_result, self.confidence_channel)[None].cpu())
106
- )
107
-
108
- return labels, dp_result
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import random
4
+ from typing import Optional, Tuple
5
+ import torch
6
+
7
+ from densepose.converters import ToChartResultConverterWithConfidences
8
+
9
+ from .densepose_base import DensePoseBaseSampler
10
+
11
+
12
+ class DensePoseConfidenceBasedSampler(DensePoseBaseSampler):
13
+ """
14
+ Samples DensePose data from DensePose predictions.
15
+ Samples for each class are drawn using confidence value estimates.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ confidence_channel: str,
21
+ count_per_class: int = 8,
22
+ search_count_multiplier: Optional[float] = None,
23
+ search_proportion: Optional[float] = None,
24
+ ):
25
+ """
26
+ Constructor
27
+
28
+ Args:
29
+ confidence_channel (str): confidence channel to use for sampling;
30
+ possible values:
31
+ "sigma_2": confidences for UV values
32
+ "fine_segm_confidence": confidences for fine segmentation
33
+ "coarse_segm_confidence": confidences for coarse segmentation
34
+ (default: "sigma_2")
35
+ count_per_class (int): the sampler produces at most `count_per_class`
36
+ samples for each category (default: 8)
37
+ search_count_multiplier (float or None): if not None, the total number
38
+ of the most confident estimates of a given class to consider is
39
+ defined as `min(search_count_multiplier * count_per_class, N)`,
40
+ where `N` is the total number of estimates of the class; cannot be
41
+ specified together with `search_proportion` (default: None)
42
+ search_proportion (float or None): if not None, the total number of the
43
+ of the most confident estimates of a given class to consider is
44
+ defined as `min(max(search_proportion * N, count_per_class), N)`,
45
+ where `N` is the total number of estimates of the class; cannot be
46
+ specified together with `search_count_multiplier` (default: None)
47
+ """
48
+ super().__init__(count_per_class)
49
+ self.confidence_channel = confidence_channel
50
+ self.search_count_multiplier = search_count_multiplier
51
+ self.search_proportion = search_proportion
52
+ assert (search_count_multiplier is None) or (search_proportion is None), (
53
+ f"Cannot specify both search_count_multiplier (={search_count_multiplier})"
54
+ f"and search_proportion (={search_proportion})"
55
+ )
56
+
57
+ def _produce_index_sample(self, values: torch.Tensor, count: int):
58
+ """
59
+ Produce a sample of indices to select data based on confidences
60
+
61
+ Args:
62
+ values (torch.Tensor): an array of size [n, k] that contains
63
+ estimated values (U, V, confidences);
64
+ n: number of channels (U, V, confidences)
65
+ k: number of points labeled with part_id
66
+ count (int): number of samples to produce, should be positive and <= k
67
+
68
+ Return:
69
+ list(int): indices of values (along axis 1) selected as a sample
70
+ """
71
+ k = values.shape[1]
72
+ if k == count:
73
+ index_sample = list(range(k))
74
+ else:
75
+ # take the best count * search_count_multiplier pixels,
76
+ # sample from them uniformly
77
+ # (here best = smallest variance)
78
+ _, sorted_confidence_indices = torch.sort(values[2])
79
+ if self.search_count_multiplier is not None:
80
+ search_count = min(int(count * self.search_count_multiplier), k)
81
+ elif self.search_proportion is not None:
82
+ search_count = min(max(int(k * self.search_proportion), count), k)
83
+ else:
84
+ search_count = min(count, k)
85
+ sample_from_top = random.sample(range(search_count), count)
86
+ index_sample = sorted_confidence_indices[:search_count][sample_from_top]
87
+ return index_sample
88
+
89
+ def _produce_labels_and_results(self, instance) -> Tuple[torch.Tensor, torch.Tensor]:
90
+ """
91
+ Method to get labels and DensePose results from an instance, with confidences
92
+
93
+ Args:
94
+ instance (Instances): an instance of `DensePoseChartPredictorOutputWithConfidences`
95
+
96
+ Return:
97
+ labels (torch.Tensor): shape [H, W], DensePose segmentation labels
98
+ dp_result (torch.Tensor): shape [3, H, W], DensePose results u and v
99
+ stacked with the confidence channel
100
+ """
101
+ converter = ToChartResultConverterWithConfidences
102
+ chart_result = converter.convert(instance.pred_densepose, instance.pred_boxes)
103
+ labels, dp_result = chart_result.labels.cpu(), chart_result.uv.cpu()
104
+ dp_result = torch.cat(
105
+ (dp_result, getattr(chart_result, self.confidence_channel)[None].cpu())
106
+ )
107
+
108
+ return labels, dp_result
densepose/data/samplers/densepose_cse_base.py CHANGED
@@ -1,139 +1,139 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from typing import Any, Dict, List, Tuple
4
- import torch
5
- from torch.nn import functional as F
6
-
7
- from detectron2.config import CfgNode
8
- from detectron2.structures import Instances
9
-
10
- from densepose.converters.base import IntTupleBox
11
- from densepose.data.utils import get_class_to_mesh_name_mapping
12
- from densepose.modeling.cse.utils import squared_euclidean_distance_matrix
13
- from densepose.structures import DensePoseDataRelative
14
-
15
- from .densepose_base import DensePoseBaseSampler
16
-
17
-
18
- class DensePoseCSEBaseSampler(DensePoseBaseSampler):
19
- """
20
- Base DensePose sampler to produce DensePose data from DensePose predictions.
21
- Samples for each class are drawn according to some distribution over all pixels estimated
22
- to belong to that class.
23
- """
24
-
25
- def __init__(
26
- self,
27
- cfg: CfgNode,
28
- use_gt_categories: bool,
29
- embedder: torch.nn.Module,
30
- count_per_class: int = 8,
31
- ):
32
- """
33
- Constructor
34
-
35
- Args:
36
- cfg (CfgNode): the config of the model
37
- embedder (torch.nn.Module): necessary to compute mesh vertex embeddings
38
- count_per_class (int): the sampler produces at most `count_per_class`
39
- samples for each category
40
- """
41
- super().__init__(count_per_class)
42
- self.embedder = embedder
43
- self.class_to_mesh_name = get_class_to_mesh_name_mapping(cfg)
44
- self.use_gt_categories = use_gt_categories
45
-
46
- def _sample(self, instance: Instances, bbox_xywh: IntTupleBox) -> Dict[str, List[Any]]:
47
- """
48
- Sample DensPoseDataRelative from estimation results
49
- """
50
- if self.use_gt_categories:
51
- instance_class = instance.dataset_classes.tolist()[0]
52
- else:
53
- instance_class = instance.pred_classes.tolist()[0]
54
- mesh_name = self.class_to_mesh_name[instance_class]
55
-
56
- annotation = {
57
- DensePoseDataRelative.X_KEY: [],
58
- DensePoseDataRelative.Y_KEY: [],
59
- DensePoseDataRelative.VERTEX_IDS_KEY: [],
60
- DensePoseDataRelative.MESH_NAME_KEY: mesh_name,
61
- }
62
-
63
- mask, embeddings, other_values = self._produce_mask_and_results(instance, bbox_xywh)
64
- indices = torch.nonzero(mask, as_tuple=True)
65
- selected_embeddings = embeddings.permute(1, 2, 0)[indices].cpu()
66
- values = other_values[:, indices[0], indices[1]]
67
- k = values.shape[1]
68
-
69
- count = min(self.count_per_class, k)
70
- if count <= 0:
71
- return annotation
72
-
73
- index_sample = self._produce_index_sample(values, count)
74
- closest_vertices = squared_euclidean_distance_matrix(
75
- selected_embeddings[index_sample], self.embedder(mesh_name)
76
- )
77
- closest_vertices = torch.argmin(closest_vertices, dim=1)
78
-
79
- sampled_y = indices[0][index_sample] + 0.5
80
- sampled_x = indices[1][index_sample] + 0.5
81
- # prepare / normalize data
82
- _, _, w, h = bbox_xywh
83
- x = (sampled_x / w * 256.0).cpu().tolist()
84
- y = (sampled_y / h * 256.0).cpu().tolist()
85
- # extend annotations
86
- annotation[DensePoseDataRelative.X_KEY].extend(x)
87
- annotation[DensePoseDataRelative.Y_KEY].extend(y)
88
- annotation[DensePoseDataRelative.VERTEX_IDS_KEY].extend(closest_vertices.cpu().tolist())
89
- return annotation
90
-
91
- def _produce_mask_and_results(
92
- self, instance: Instances, bbox_xywh: IntTupleBox
93
- ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
94
- """
95
- Method to get labels and DensePose results from an instance
96
-
97
- Args:
98
- instance (Instances): an instance of `DensePoseEmbeddingPredictorOutput`
99
- bbox_xywh (IntTupleBox): the corresponding bounding box
100
-
101
- Return:
102
- mask (torch.Tensor): shape [H, W], DensePose segmentation mask
103
- embeddings (Tuple[torch.Tensor]): a tensor of shape [D, H, W],
104
- DensePose CSE Embeddings
105
- other_values (Tuple[torch.Tensor]): a tensor of shape [0, H, W],
106
- for potential other values
107
- """
108
- densepose_output = instance.pred_densepose
109
- S = densepose_output.coarse_segm
110
- E = densepose_output.embedding
111
- _, _, w, h = bbox_xywh
112
- embeddings = F.interpolate(E, size=(h, w), mode="bilinear")[0]
113
- coarse_segm_resized = F.interpolate(S, size=(h, w), mode="bilinear")[0]
114
- mask = coarse_segm_resized.argmax(0) > 0
115
- other_values = torch.empty((0, h, w), device=E.device)
116
- return mask, embeddings, other_values
117
-
118
- def _resample_mask(self, output: Any) -> torch.Tensor:
119
- """
120
- Convert DensePose predictor output to segmentation annotation - tensors of size
121
- (256, 256) and type `int64`.
122
-
123
- Args:
124
- output: DensePose predictor output with the following attributes:
125
- - coarse_segm: tensor of size [N, D, H, W] with unnormalized coarse
126
- segmentation scores
127
- Return:
128
- Tensor of size (S, S) and type `int64` with coarse segmentation annotations,
129
- where S = DensePoseDataRelative.MASK_SIZE
130
- """
131
- sz = DensePoseDataRelative.MASK_SIZE
132
- mask = (
133
- F.interpolate(output.coarse_segm, (sz, sz), mode="bilinear", align_corners=False)
134
- .argmax(dim=1)
135
- .long()
136
- .squeeze()
137
- .cpu()
138
- )
139
- return mask
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from typing import Any, Dict, List, Tuple
4
+ import torch
5
+ from torch.nn import functional as F
6
+
7
+ from detectron2.config import CfgNode
8
+ from detectron2.structures import Instances
9
+
10
+ from densepose.converters.base import IntTupleBox
11
+ from densepose.data.utils import get_class_to_mesh_name_mapping
12
+ from densepose.modeling.cse.utils import squared_euclidean_distance_matrix
13
+ from densepose.structures import DensePoseDataRelative
14
+
15
+ from .densepose_base import DensePoseBaseSampler
16
+
17
+
18
+ class DensePoseCSEBaseSampler(DensePoseBaseSampler):
19
+ """
20
+ Base DensePose sampler to produce DensePose data from DensePose predictions.
21
+ Samples for each class are drawn according to some distribution over all pixels estimated
22
+ to belong to that class.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ cfg: CfgNode,
28
+ use_gt_categories: bool,
29
+ embedder: torch.nn.Module,
30
+ count_per_class: int = 8,
31
+ ):
32
+ """
33
+ Constructor
34
+
35
+ Args:
36
+ cfg (CfgNode): the config of the model
37
+ embedder (torch.nn.Module): necessary to compute mesh vertex embeddings
38
+ count_per_class (int): the sampler produces at most `count_per_class`
39
+ samples for each category
40
+ """
41
+ super().__init__(count_per_class)
42
+ self.embedder = embedder
43
+ self.class_to_mesh_name = get_class_to_mesh_name_mapping(cfg)
44
+ self.use_gt_categories = use_gt_categories
45
+
46
+ def _sample(self, instance: Instances, bbox_xywh: IntTupleBox) -> Dict[str, List[Any]]:
47
+ """
48
+ Sample DensPoseDataRelative from estimation results
49
+ """
50
+ if self.use_gt_categories:
51
+ instance_class = instance.dataset_classes.tolist()[0]
52
+ else:
53
+ instance_class = instance.pred_classes.tolist()[0]
54
+ mesh_name = self.class_to_mesh_name[instance_class]
55
+
56
+ annotation = {
57
+ DensePoseDataRelative.X_KEY: [],
58
+ DensePoseDataRelative.Y_KEY: [],
59
+ DensePoseDataRelative.VERTEX_IDS_KEY: [],
60
+ DensePoseDataRelative.MESH_NAME_KEY: mesh_name,
61
+ }
62
+
63
+ mask, embeddings, other_values = self._produce_mask_and_results(instance, bbox_xywh)
64
+ indices = torch.nonzero(mask, as_tuple=True)
65
+ selected_embeddings = embeddings.permute(1, 2, 0)[indices].cpu()
66
+ values = other_values[:, indices[0], indices[1]]
67
+ k = values.shape[1]
68
+
69
+ count = min(self.count_per_class, k)
70
+ if count <= 0:
71
+ return annotation
72
+
73
+ index_sample = self._produce_index_sample(values, count)
74
+ closest_vertices = squared_euclidean_distance_matrix(
75
+ selected_embeddings[index_sample], self.embedder(mesh_name)
76
+ )
77
+ closest_vertices = torch.argmin(closest_vertices, dim=1)
78
+
79
+ sampled_y = indices[0][index_sample] + 0.5
80
+ sampled_x = indices[1][index_sample] + 0.5
81
+ # prepare / normalize data
82
+ _, _, w, h = bbox_xywh
83
+ x = (sampled_x / w * 256.0).cpu().tolist()
84
+ y = (sampled_y / h * 256.0).cpu().tolist()
85
+ # extend annotations
86
+ annotation[DensePoseDataRelative.X_KEY].extend(x)
87
+ annotation[DensePoseDataRelative.Y_KEY].extend(y)
88
+ annotation[DensePoseDataRelative.VERTEX_IDS_KEY].extend(closest_vertices.cpu().tolist())
89
+ return annotation
90
+
91
+ def _produce_mask_and_results(
92
+ self, instance: Instances, bbox_xywh: IntTupleBox
93
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
94
+ """
95
+ Method to get labels and DensePose results from an instance
96
+
97
+ Args:
98
+ instance (Instances): an instance of `DensePoseEmbeddingPredictorOutput`
99
+ bbox_xywh (IntTupleBox): the corresponding bounding box
100
+
101
+ Return:
102
+ mask (torch.Tensor): shape [H, W], DensePose segmentation mask
103
+ embeddings (Tuple[torch.Tensor]): a tensor of shape [D, H, W],
104
+ DensePose CSE Embeddings
105
+ other_values (Tuple[torch.Tensor]): a tensor of shape [0, H, W],
106
+ for potential other values
107
+ """
108
+ densepose_output = instance.pred_densepose
109
+ S = densepose_output.coarse_segm
110
+ E = densepose_output.embedding
111
+ _, _, w, h = bbox_xywh
112
+ embeddings = F.interpolate(E, size=(h, w), mode="bilinear")[0]
113
+ coarse_segm_resized = F.interpolate(S, size=(h, w), mode="bilinear")[0]
114
+ mask = coarse_segm_resized.argmax(0) > 0
115
+ other_values = torch.empty((0, h, w), device=E.device)
116
+ return mask, embeddings, other_values
117
+
118
+ def _resample_mask(self, output: Any) -> torch.Tensor:
119
+ """
120
+ Convert DensePose predictor output to segmentation annotation - tensors of size
121
+ (256, 256) and type `int64`.
122
+
123
+ Args:
124
+ output: DensePose predictor output with the following attributes:
125
+ - coarse_segm: tensor of size [N, D, H, W] with unnormalized coarse
126
+ segmentation scores
127
+ Return:
128
+ Tensor of size (S, S) and type `int64` with coarse segmentation annotations,
129
+ where S = DensePoseDataRelative.MASK_SIZE
130
+ """
131
+ sz = DensePoseDataRelative.MASK_SIZE
132
+ mask = (
133
+ F.interpolate(output.coarse_segm, (sz, sz), mode="bilinear", align_corners=False)
134
+ .argmax(dim=1)
135
+ .long()
136
+ .squeeze()
137
+ .cpu()
138
+ )
139
+ return mask
densepose/data/samplers/densepose_cse_confidence_based.py CHANGED
@@ -1,119 +1,119 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import random
4
- from typing import Optional, Tuple
5
- import torch
6
- from torch.nn import functional as F
7
-
8
- from detectron2.config import CfgNode
9
- from detectron2.structures import Instances
10
-
11
- from densepose.converters.base import IntTupleBox
12
-
13
- from .densepose_cse_base import DensePoseCSEBaseSampler
14
-
15
-
16
- class DensePoseCSEConfidenceBasedSampler(DensePoseCSEBaseSampler):
17
- """
18
- Samples DensePose data from DensePose predictions.
19
- Samples for each class are drawn using confidence value estimates.
20
- """
21
-
22
- def __init__(
23
- self,
24
- cfg: CfgNode,
25
- use_gt_categories: bool,
26
- embedder: torch.nn.Module,
27
- confidence_channel: str,
28
- count_per_class: int = 8,
29
- search_count_multiplier: Optional[float] = None,
30
- search_proportion: Optional[float] = None,
31
- ):
32
- """
33
- Constructor
34
-
35
- Args:
36
- cfg (CfgNode): the config of the model
37
- embedder (torch.nn.Module): necessary to compute mesh vertex embeddings
38
- confidence_channel (str): confidence channel to use for sampling;
39
- possible values:
40
- "coarse_segm_confidence": confidences for coarse segmentation
41
- (default: "coarse_segm_confidence")
42
- count_per_class (int): the sampler produces at most `count_per_class`
43
- samples for each category (default: 8)
44
- search_count_multiplier (float or None): if not None, the total number
45
- of the most confident estimates of a given class to consider is
46
- defined as `min(search_count_multiplier * count_per_class, N)`,
47
- where `N` is the total number of estimates of the class; cannot be
48
- specified together with `search_proportion` (default: None)
49
- search_proportion (float or None): if not None, the total number of the
50
- of the most confident estimates of a given class to consider is
51
- defined as `min(max(search_proportion * N, count_per_class), N)`,
52
- where `N` is the total number of estimates of the class; cannot be
53
- specified together with `search_count_multiplier` (default: None)
54
- """
55
- super().__init__(cfg, use_gt_categories, embedder, count_per_class)
56
- self.confidence_channel = confidence_channel
57
- self.search_count_multiplier = search_count_multiplier
58
- self.search_proportion = search_proportion
59
- assert (search_count_multiplier is None) or (search_proportion is None), (
60
- f"Cannot specify both search_count_multiplier (={search_count_multiplier})"
61
- f"and search_proportion (={search_proportion})"
62
- )
63
-
64
- def _produce_index_sample(self, values: torch.Tensor, count: int):
65
- """
66
- Produce a sample of indices to select data based on confidences
67
-
68
- Args:
69
- values (torch.Tensor): a tensor of length k that contains confidences
70
- k: number of points labeled with part_id
71
- count (int): number of samples to produce, should be positive and <= k
72
-
73
- Return:
74
- list(int): indices of values (along axis 1) selected as a sample
75
- """
76
- k = values.shape[1]
77
- if k == count:
78
- index_sample = list(range(k))
79
- else:
80
- # take the best count * search_count_multiplier pixels,
81
- # sample from them uniformly
82
- # (here best = smallest variance)
83
- _, sorted_confidence_indices = torch.sort(values[0])
84
- if self.search_count_multiplier is not None:
85
- search_count = min(int(count * self.search_count_multiplier), k)
86
- elif self.search_proportion is not None:
87
- search_count = min(max(int(k * self.search_proportion), count), k)
88
- else:
89
- search_count = min(count, k)
90
- sample_from_top = random.sample(range(search_count), count)
91
- index_sample = sorted_confidence_indices[-search_count:][sample_from_top]
92
- return index_sample
93
-
94
- def _produce_mask_and_results(
95
- self, instance: Instances, bbox_xywh: IntTupleBox
96
- ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
97
- """
98
- Method to get labels and DensePose results from an instance
99
-
100
- Args:
101
- instance (Instances): an instance of
102
- `DensePoseEmbeddingPredictorOutputWithConfidences`
103
- bbox_xywh (IntTupleBox): the corresponding bounding box
104
-
105
- Return:
106
- mask (torch.Tensor): shape [H, W], DensePose segmentation mask
107
- embeddings (Tuple[torch.Tensor]): a tensor of shape [D, H, W]
108
- DensePose CSE Embeddings
109
- other_values: a tensor of shape [1, H, W], DensePose CSE confidence
110
- """
111
- _, _, w, h = bbox_xywh
112
- densepose_output = instance.pred_densepose
113
- mask, embeddings, _ = super()._produce_mask_and_results(instance, bbox_xywh)
114
- other_values = F.interpolate(
115
- getattr(densepose_output, self.confidence_channel),
116
- size=(h, w),
117
- mode="bilinear",
118
- )[0].cpu()
119
- return mask, embeddings, other_values
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import random
4
+ from typing import Optional, Tuple
5
+ import torch
6
+ from torch.nn import functional as F
7
+
8
+ from detectron2.config import CfgNode
9
+ from detectron2.structures import Instances
10
+
11
+ from densepose.converters.base import IntTupleBox
12
+
13
+ from .densepose_cse_base import DensePoseCSEBaseSampler
14
+
15
+
16
+ class DensePoseCSEConfidenceBasedSampler(DensePoseCSEBaseSampler):
17
+ """
18
+ Samples DensePose data from DensePose predictions.
19
+ Samples for each class are drawn using confidence value estimates.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ cfg: CfgNode,
25
+ use_gt_categories: bool,
26
+ embedder: torch.nn.Module,
27
+ confidence_channel: str,
28
+ count_per_class: int = 8,
29
+ search_count_multiplier: Optional[float] = None,
30
+ search_proportion: Optional[float] = None,
31
+ ):
32
+ """
33
+ Constructor
34
+
35
+ Args:
36
+ cfg (CfgNode): the config of the model
37
+ embedder (torch.nn.Module): necessary to compute mesh vertex embeddings
38
+ confidence_channel (str): confidence channel to use for sampling;
39
+ possible values:
40
+ "coarse_segm_confidence": confidences for coarse segmentation
41
+ (default: "coarse_segm_confidence")
42
+ count_per_class (int): the sampler produces at most `count_per_class`
43
+ samples for each category (default: 8)
44
+ search_count_multiplier (float or None): if not None, the total number
45
+ of the most confident estimates of a given class to consider is
46
+ defined as `min(search_count_multiplier * count_per_class, N)`,
47
+ where `N` is the total number of estimates of the class; cannot be
48
+ specified together with `search_proportion` (default: None)
49
+ search_proportion (float or None): if not None, the total number of the
50
+ of the most confident estimates of a given class to consider is
51
+ defined as `min(max(search_proportion * N, count_per_class), N)`,
52
+ where `N` is the total number of estimates of the class; cannot be
53
+ specified together with `search_count_multiplier` (default: None)
54
+ """
55
+ super().__init__(cfg, use_gt_categories, embedder, count_per_class)
56
+ self.confidence_channel = confidence_channel
57
+ self.search_count_multiplier = search_count_multiplier
58
+ self.search_proportion = search_proportion
59
+ assert (search_count_multiplier is None) or (search_proportion is None), (
60
+ f"Cannot specify both search_count_multiplier (={search_count_multiplier})"
61
+ f"and search_proportion (={search_proportion})"
62
+ )
63
+
64
+ def _produce_index_sample(self, values: torch.Tensor, count: int):
65
+ """
66
+ Produce a sample of indices to select data based on confidences
67
+
68
+ Args:
69
+ values (torch.Tensor): a tensor of length k that contains confidences
70
+ k: number of points labeled with part_id
71
+ count (int): number of samples to produce, should be positive and <= k
72
+
73
+ Return:
74
+ list(int): indices of values (along axis 1) selected as a sample
75
+ """
76
+ k = values.shape[1]
77
+ if k == count:
78
+ index_sample = list(range(k))
79
+ else:
80
+ # take the best count * search_count_multiplier pixels,
81
+ # sample from them uniformly
82
+ # (here best = smallest variance)
83
+ _, sorted_confidence_indices = torch.sort(values[0])
84
+ if self.search_count_multiplier is not None:
85
+ search_count = min(int(count * self.search_count_multiplier), k)
86
+ elif self.search_proportion is not None:
87
+ search_count = min(max(int(k * self.search_proportion), count), k)
88
+ else:
89
+ search_count = min(count, k)
90
+ sample_from_top = random.sample(range(search_count), count)
91
+ index_sample = sorted_confidence_indices[-search_count:][sample_from_top]
92
+ return index_sample
93
+
94
+ def _produce_mask_and_results(
95
+ self, instance: Instances, bbox_xywh: IntTupleBox
96
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
97
+ """
98
+ Method to get labels and DensePose results from an instance
99
+
100
+ Args:
101
+ instance (Instances): an instance of
102
+ `DensePoseEmbeddingPredictorOutputWithConfidences`
103
+ bbox_xywh (IntTupleBox): the corresponding bounding box
104
+
105
+ Return:
106
+ mask (torch.Tensor): shape [H, W], DensePose segmentation mask
107
+ embeddings (Tuple[torch.Tensor]): a tensor of shape [D, H, W]
108
+ DensePose CSE Embeddings
109
+ other_values: a tensor of shape [1, H, W], DensePose CSE confidence
110
+ """
111
+ _, _, w, h = bbox_xywh
112
+ densepose_output = instance.pred_densepose
113
+ mask, embeddings, _ = super()._produce_mask_and_results(instance, bbox_xywh)
114
+ other_values = F.interpolate(
115
+ getattr(densepose_output, self.confidence_channel),
116
+ size=(h, w),
117
+ mode="bilinear",
118
+ )[0].cpu()
119
+ return mask, embeddings, other_values
densepose/data/samplers/densepose_cse_uniform.py CHANGED
@@ -1,12 +1,12 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .densepose_cse_base import DensePoseCSEBaseSampler
4
- from .densepose_uniform import DensePoseUniformSampler
5
-
6
-
7
- class DensePoseCSEUniformSampler(DensePoseCSEBaseSampler, DensePoseUniformSampler):
8
- """
9
- Uniform Sampler for CSE
10
- """
11
-
12
- pass
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .densepose_cse_base import DensePoseCSEBaseSampler
4
+ from .densepose_uniform import DensePoseUniformSampler
5
+
6
+
7
+ class DensePoseCSEUniformSampler(DensePoseCSEBaseSampler, DensePoseUniformSampler):
8
+ """
9
+ Uniform Sampler for CSE
10
+ """
11
+
12
+ pass
densepose/data/samplers/densepose_uniform.py CHANGED
@@ -1,41 +1,41 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import random
4
- import torch
5
-
6
- from .densepose_base import DensePoseBaseSampler
7
-
8
-
9
- class DensePoseUniformSampler(DensePoseBaseSampler):
10
- """
11
- Samples DensePose data from DensePose predictions.
12
- Samples for each class are drawn uniformly over all pixels estimated
13
- to belong to that class.
14
- """
15
-
16
- def __init__(self, count_per_class: int = 8):
17
- """
18
- Constructor
19
-
20
- Args:
21
- count_per_class (int): the sampler produces at most `count_per_class`
22
- samples for each category
23
- """
24
- super().__init__(count_per_class)
25
-
26
- def _produce_index_sample(self, values: torch.Tensor, count: int):
27
- """
28
- Produce a uniform sample of indices to select data
29
-
30
- Args:
31
- values (torch.Tensor): an array of size [n, k] that contains
32
- estimated values (U, V, confidences);
33
- n: number of channels (U, V, confidences)
34
- k: number of points labeled with part_id
35
- count (int): number of samples to produce, should be positive and <= k
36
-
37
- Return:
38
- list(int): indices of values (along axis 1) selected as a sample
39
- """
40
- k = values.shape[1]
41
- return random.sample(range(k), count)
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import random
4
+ import torch
5
+
6
+ from .densepose_base import DensePoseBaseSampler
7
+
8
+
9
+ class DensePoseUniformSampler(DensePoseBaseSampler):
10
+ """
11
+ Samples DensePose data from DensePose predictions.
12
+ Samples for each class are drawn uniformly over all pixels estimated
13
+ to belong to that class.
14
+ """
15
+
16
+ def __init__(self, count_per_class: int = 8):
17
+ """
18
+ Constructor
19
+
20
+ Args:
21
+ count_per_class (int): the sampler produces at most `count_per_class`
22
+ samples for each category
23
+ """
24
+ super().__init__(count_per_class)
25
+
26
+ def _produce_index_sample(self, values: torch.Tensor, count: int):
27
+ """
28
+ Produce a uniform sample of indices to select data
29
+
30
+ Args:
31
+ values (torch.Tensor): an array of size [n, k] that contains
32
+ estimated values (U, V, confidences);
33
+ n: number of channels (U, V, confidences)
34
+ k: number of points labeled with part_id
35
+ count (int): number of samples to produce, should be positive and <= k
36
+
37
+ Return:
38
+ list(int): indices of values (along axis 1) selected as a sample
39
+ """
40
+ k = values.shape[1]
41
+ return random.sample(range(k), count)
densepose/data/samplers/mask_from_densepose.py CHANGED
@@ -1,28 +1,28 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from detectron2.structures import BitMasks, Instances
4
-
5
- from densepose.converters import ToMaskConverter
6
-
7
-
8
- class MaskFromDensePoseSampler:
9
- """
10
- Produce mask GT from DensePose predictions
11
- This sampler simply converts DensePose predictions to BitMasks
12
- that a contain a bool tensor of the size of the input image
13
- """
14
-
15
- def __call__(self, instances: Instances) -> BitMasks:
16
- """
17
- Converts predicted data from `instances` into the GT mask data
18
-
19
- Args:
20
- instances (Instances): predicted results, expected to have `pred_densepose` field
21
-
22
- Returns:
23
- Boolean Tensor of the size of the input image that has non-zero
24
- values at pixels that are estimated to belong to the detected object
25
- """
26
- return ToMaskConverter.convert(
27
- instances.pred_densepose, instances.pred_boxes, instances.image_size
28
- )
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from detectron2.structures import BitMasks, Instances
4
+
5
+ from densepose.converters import ToMaskConverter
6
+
7
+
8
+ class MaskFromDensePoseSampler:
9
+ """
10
+ Produce mask GT from DensePose predictions
11
+ This sampler simply converts DensePose predictions to BitMasks
12
+ that a contain a bool tensor of the size of the input image
13
+ """
14
+
15
+ def __call__(self, instances: Instances) -> BitMasks:
16
+ """
17
+ Converts predicted data from `instances` into the GT mask data
18
+
19
+ Args:
20
+ instances (Instances): predicted results, expected to have `pred_densepose` field
21
+
22
+ Returns:
23
+ Boolean Tensor of the size of the input image that has non-zero
24
+ values at pixels that are estimated to belong to the detected object
25
+ """
26
+ return ToMaskConverter.convert(
27
+ instances.pred_densepose, instances.pred_boxes, instances.image_size
28
+ )
densepose/data/samplers/prediction_to_gt.py CHANGED
@@ -1,98 +1,98 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from dataclasses import dataclass
4
- from typing import Any, Callable, Dict, List, Optional
5
-
6
- from detectron2.structures import Instances
7
-
8
- ModelOutput = Dict[str, Any]
9
- SampledData = Dict[str, Any]
10
-
11
-
12
- @dataclass
13
- class _Sampler:
14
- """
15
- Sampler registry entry that contains:
16
- - src (str): source field to sample from (deleted after sampling)
17
- - dst (Optional[str]): destination field to sample to, if not None
18
- - func (Optional[Callable: Any -> Any]): function that performs sampling,
19
- if None, reference copy is performed
20
- """
21
-
22
- src: str
23
- dst: Optional[str]
24
- func: Optional[Callable[[Any], Any]]
25
-
26
-
27
- class PredictionToGroundTruthSampler:
28
- """
29
- Sampler implementation that converts predictions to GT using registered
30
- samplers for different fields of `Instances`.
31
- """
32
-
33
- def __init__(self, dataset_name: str = ""):
34
- self.dataset_name = dataset_name
35
- self._samplers = {}
36
- self.register_sampler("pred_boxes", "gt_boxes", None)
37
- self.register_sampler("pred_classes", "gt_classes", None)
38
- # delete scores
39
- self.register_sampler("scores")
40
-
41
- def __call__(self, model_output: List[ModelOutput]) -> List[SampledData]:
42
- """
43
- Transform model output into ground truth data through sampling
44
-
45
- Args:
46
- model_output (Dict[str, Any]): model output
47
- Returns:
48
- Dict[str, Any]: sampled data
49
- """
50
- for model_output_i in model_output:
51
- instances: Instances = model_output_i["instances"]
52
- # transform data in each field
53
- for _, sampler in self._samplers.items():
54
- if not instances.has(sampler.src) or sampler.dst is None:
55
- continue
56
- if sampler.func is None:
57
- instances.set(sampler.dst, instances.get(sampler.src))
58
- else:
59
- instances.set(sampler.dst, sampler.func(instances))
60
- # delete model output data that was transformed
61
- for _, sampler in self._samplers.items():
62
- if sampler.src != sampler.dst and instances.has(sampler.src):
63
- instances.remove(sampler.src)
64
- model_output_i["dataset"] = self.dataset_name
65
- return model_output
66
-
67
- def register_sampler(
68
- self,
69
- prediction_attr: str,
70
- gt_attr: Optional[str] = None,
71
- func: Optional[Callable[[Any], Any]] = None,
72
- ):
73
- """
74
- Register sampler for a field
75
-
76
- Args:
77
- prediction_attr (str): field to replace with a sampled value
78
- gt_attr (Optional[str]): field to store the sampled value to, if not None
79
- func (Optional[Callable: Any -> Any]): sampler function
80
- """
81
- self._samplers[(prediction_attr, gt_attr)] = _Sampler(
82
- src=prediction_attr, dst=gt_attr, func=func
83
- )
84
-
85
- def remove_sampler(
86
- self,
87
- prediction_attr: str,
88
- gt_attr: Optional[str] = None,
89
- ):
90
- """
91
- Remove sampler for a field
92
-
93
- Args:
94
- prediction_attr (str): field to replace with a sampled value
95
- gt_attr (Optional[str]): field to store the sampled value to, if not None
96
- """
97
- assert (prediction_attr, gt_attr) in self._samplers
98
- del self._samplers[(prediction_attr, gt_attr)]
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable, Dict, List, Optional
5
+
6
+ from detectron2.structures import Instances
7
+
8
+ ModelOutput = Dict[str, Any]
9
+ SampledData = Dict[str, Any]
10
+
11
+
12
+ @dataclass
13
+ class _Sampler:
14
+ """
15
+ Sampler registry entry that contains:
16
+ - src (str): source field to sample from (deleted after sampling)
17
+ - dst (Optional[str]): destination field to sample to, if not None
18
+ - func (Optional[Callable: Any -> Any]): function that performs sampling,
19
+ if None, reference copy is performed
20
+ """
21
+
22
+ src: str
23
+ dst: Optional[str]
24
+ func: Optional[Callable[[Any], Any]]
25
+
26
+
27
+ class PredictionToGroundTruthSampler:
28
+ """
29
+ Sampler implementation that converts predictions to GT using registered
30
+ samplers for different fields of `Instances`.
31
+ """
32
+
33
+ def __init__(self, dataset_name: str = ""):
34
+ self.dataset_name = dataset_name
35
+ self._samplers = {}
36
+ self.register_sampler("pred_boxes", "gt_boxes", None)
37
+ self.register_sampler("pred_classes", "gt_classes", None)
38
+ # delete scores
39
+ self.register_sampler("scores")
40
+
41
+ def __call__(self, model_output: List[ModelOutput]) -> List[SampledData]:
42
+ """
43
+ Transform model output into ground truth data through sampling
44
+
45
+ Args:
46
+ model_output (Dict[str, Any]): model output
47
+ Returns:
48
+ Dict[str, Any]: sampled data
49
+ """
50
+ for model_output_i in model_output:
51
+ instances: Instances = model_output_i["instances"]
52
+ # transform data in each field
53
+ for _, sampler in self._samplers.items():
54
+ if not instances.has(sampler.src) or sampler.dst is None:
55
+ continue
56
+ if sampler.func is None:
57
+ instances.set(sampler.dst, instances.get(sampler.src))
58
+ else:
59
+ instances.set(sampler.dst, sampler.func(instances))
60
+ # delete model output data that was transformed
61
+ for _, sampler in self._samplers.items():
62
+ if sampler.src != sampler.dst and instances.has(sampler.src):
63
+ instances.remove(sampler.src)
64
+ model_output_i["dataset"] = self.dataset_name
65
+ return model_output
66
+
67
+ def register_sampler(
68
+ self,
69
+ prediction_attr: str,
70
+ gt_attr: Optional[str] = None,
71
+ func: Optional[Callable[[Any], Any]] = None,
72
+ ):
73
+ """
74
+ Register sampler for a field
75
+
76
+ Args:
77
+ prediction_attr (str): field to replace with a sampled value
78
+ gt_attr (Optional[str]): field to store the sampled value to, if not None
79
+ func (Optional[Callable: Any -> Any]): sampler function
80
+ """
81
+ self._samplers[(prediction_attr, gt_attr)] = _Sampler(
82
+ src=prediction_attr, dst=gt_attr, func=func
83
+ )
84
+
85
+ def remove_sampler(
86
+ self,
87
+ prediction_attr: str,
88
+ gt_attr: Optional[str] = None,
89
+ ):
90
+ """
91
+ Remove sampler for a field
92
+
93
+ Args:
94
+ prediction_attr (str): field to replace with a sampled value
95
+ gt_attr (Optional[str]): field to store the sampled value to, if not None
96
+ """
97
+ assert (prediction_attr, gt_attr) in self._samplers
98
+ del self._samplers[(prediction_attr, gt_attr)]
densepose/data/transform/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .image import ImageResizeTransform
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .image import ImageResizeTransform
densepose/data/transform/image.py CHANGED
@@ -1,39 +1,39 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import torch
4
-
5
-
6
- class ImageResizeTransform:
7
- """
8
- Transform that resizes images loaded from a dataset
9
- (BGR data in NCHW channel order, typically uint8) to a format ready to be
10
- consumed by DensePose training (BGR float32 data in NCHW channel order)
11
- """
12
-
13
- def __init__(self, min_size: int = 800, max_size: int = 1333):
14
- self.min_size = min_size
15
- self.max_size = max_size
16
-
17
- def __call__(self, images: torch.Tensor) -> torch.Tensor:
18
- """
19
- Args:
20
- images (torch.Tensor): tensor of size [N, 3, H, W] that contains
21
- BGR data (typically in uint8)
22
- Returns:
23
- images (torch.Tensor): tensor of size [N, 3, H1, W1] where
24
- H1 and W1 are chosen to respect the specified min and max sizes
25
- and preserve the original aspect ratio, the data channels
26
- follow BGR order and the data type is `torch.float32`
27
- """
28
- # resize with min size
29
- images = images.float()
30
- min_size = min(images.shape[-2:])
31
- max_size = max(images.shape[-2:])
32
- scale = min(self.min_size / min_size, self.max_size / max_size)
33
- images = torch.nn.functional.interpolate(
34
- images,
35
- scale_factor=scale,
36
- mode="bilinear",
37
- align_corners=False,
38
- )
39
- return images
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import torch
4
+
5
+
6
+ class ImageResizeTransform:
7
+ """
8
+ Transform that resizes images loaded from a dataset
9
+ (BGR data in NCHW channel order, typically uint8) to a format ready to be
10
+ consumed by DensePose training (BGR float32 data in NCHW channel order)
11
+ """
12
+
13
+ def __init__(self, min_size: int = 800, max_size: int = 1333):
14
+ self.min_size = min_size
15
+ self.max_size = max_size
16
+
17
+ def __call__(self, images: torch.Tensor) -> torch.Tensor:
18
+ """
19
+ Args:
20
+ images (torch.Tensor): tensor of size [N, 3, H, W] that contains
21
+ BGR data (typically in uint8)
22
+ Returns:
23
+ images (torch.Tensor): tensor of size [N, 3, H1, W1] where
24
+ H1 and W1 are chosen to respect the specified min and max sizes
25
+ and preserve the original aspect ratio, the data channels
26
+ follow BGR order and the data type is `torch.float32`
27
+ """
28
+ # resize with min size
29
+ images = images.float()
30
+ min_size = min(images.shape[-2:])
31
+ max_size = max(images.shape[-2:])
32
+ scale = min(self.min_size / min_size, self.max_size / max_size)
33
+ images = torch.nn.functional.interpolate(
34
+ images,
35
+ scale_factor=scale,
36
+ mode="bilinear",
37
+ align_corners=False,
38
+ )
39
+ return images
densepose/data/utils.py CHANGED
@@ -1,38 +1,38 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import os
4
- from typing import Dict, Optional
5
-
6
- from detectron2.config import CfgNode
7
-
8
-
9
- def is_relative_local_path(path: str) -> bool:
10
- path_str = os.fsdecode(path)
11
- return ("://" not in path_str) and not os.path.isabs(path)
12
-
13
-
14
- def maybe_prepend_base_path(base_path: Optional[str], path: str):
15
- """
16
- Prepends the provided path with a base path prefix if:
17
- 1) base path is not None;
18
- 2) path is a local path
19
- """
20
- if base_path is None:
21
- return path
22
- if is_relative_local_path(path):
23
- return os.path.join(base_path, path)
24
- return path
25
-
26
-
27
- def get_class_to_mesh_name_mapping(cfg: CfgNode) -> Dict[int, str]:
28
- return {
29
- int(class_id): mesh_name
30
- for class_id, mesh_name in cfg.DATASETS.CLASS_TO_MESH_NAME_MAPPING.items()
31
- }
32
-
33
-
34
- def get_category_to_class_mapping(dataset_cfg: CfgNode) -> Dict[str, int]:
35
- return {
36
- category: int(class_id)
37
- for category, class_id in dataset_cfg.CATEGORY_TO_CLASS_MAPPING.items()
38
- }
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import os
4
+ from typing import Dict, Optional
5
+
6
+ from detectron2.config import CfgNode
7
+
8
+
9
+ def is_relative_local_path(path: str) -> bool:
10
+ path_str = os.fsdecode(path)
11
+ return ("://" not in path_str) and not os.path.isabs(path)
12
+
13
+
14
+ def maybe_prepend_base_path(base_path: Optional[str], path: str):
15
+ """
16
+ Prepends the provided path with a base path prefix if:
17
+ 1) base path is not None;
18
+ 2) path is a local path
19
+ """
20
+ if base_path is None:
21
+ return path
22
+ if is_relative_local_path(path):
23
+ return os.path.join(base_path, path)
24
+ return path
25
+
26
+
27
+ def get_class_to_mesh_name_mapping(cfg: CfgNode) -> Dict[int, str]:
28
+ return {
29
+ int(class_id): mesh_name
30
+ for class_id, mesh_name in cfg.DATASETS.CLASS_TO_MESH_NAME_MAPPING.items()
31
+ }
32
+
33
+
34
+ def get_category_to_class_mapping(dataset_cfg: CfgNode) -> Dict[str, int]:
35
+ return {
36
+ category: int(class_id)
37
+ for category, class_id in dataset_cfg.CATEGORY_TO_CLASS_MAPPING.items()
38
+ }
densepose/data/video/__init__.py CHANGED
@@ -1,17 +1,17 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .frame_selector import (
4
- FrameSelectionStrategy,
5
- RandomKFramesSelector,
6
- FirstKFramesSelector,
7
- LastKFramesSelector,
8
- FrameTsList,
9
- FrameSelector,
10
- )
11
-
12
- from .video_keyframe_dataset import (
13
- VideoKeyframeDataset,
14
- video_list_from_file,
15
- list_keyframes,
16
- read_keyframes,
17
- )
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .frame_selector import (
4
+ FrameSelectionStrategy,
5
+ RandomKFramesSelector,
6
+ FirstKFramesSelector,
7
+ LastKFramesSelector,
8
+ FrameTsList,
9
+ FrameSelector,
10
+ )
11
+
12
+ from .video_keyframe_dataset import (
13
+ VideoKeyframeDataset,
14
+ video_list_from_file,
15
+ list_keyframes,
16
+ read_keyframes,
17
+ )
densepose/data/video/frame_selector.py CHANGED
@@ -1,87 +1,87 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import random
4
- from collections.abc import Callable
5
- from enum import Enum
6
- from typing import Callable as TCallable
7
- from typing import List
8
-
9
- FrameTsList = List[int]
10
- FrameSelector = TCallable[[FrameTsList], FrameTsList]
11
-
12
-
13
- class FrameSelectionStrategy(Enum):
14
- """
15
- Frame selection strategy used with videos:
16
- - "random_k": select k random frames
17
- - "first_k": select k first frames
18
- - "last_k": select k last frames
19
- - "all": select all frames
20
- """
21
-
22
- # fmt: off
23
- RANDOM_K = "random_k"
24
- FIRST_K = "first_k"
25
- LAST_K = "last_k"
26
- ALL = "all"
27
- # fmt: on
28
-
29
-
30
- class RandomKFramesSelector(Callable): # pyre-ignore[39]
31
- """
32
- Selector that retains at most `k` random frames
33
- """
34
-
35
- def __init__(self, k: int):
36
- self.k = k
37
-
38
- def __call__(self, frame_tss: FrameTsList) -> FrameTsList:
39
- """
40
- Select `k` random frames
41
-
42
- Args:
43
- frames_tss (List[int]): timestamps of input frames
44
- Returns:
45
- List[int]: timestamps of selected frames
46
- """
47
- return random.sample(frame_tss, min(self.k, len(frame_tss)))
48
-
49
-
50
- class FirstKFramesSelector(Callable): # pyre-ignore[39]
51
- """
52
- Selector that retains at most `k` first frames
53
- """
54
-
55
- def __init__(self, k: int):
56
- self.k = k
57
-
58
- def __call__(self, frame_tss: FrameTsList) -> FrameTsList:
59
- """
60
- Select `k` first frames
61
-
62
- Args:
63
- frames_tss (List[int]): timestamps of input frames
64
- Returns:
65
- List[int]: timestamps of selected frames
66
- """
67
- return frame_tss[: self.k]
68
-
69
-
70
- class LastKFramesSelector(Callable): # pyre-ignore[39]
71
- """
72
- Selector that retains at most `k` last frames from video data
73
- """
74
-
75
- def __init__(self, k: int):
76
- self.k = k
77
-
78
- def __call__(self, frame_tss: FrameTsList) -> FrameTsList:
79
- """
80
- Select `k` last frames
81
-
82
- Args:
83
- frames_tss (List[int]): timestamps of input frames
84
- Returns:
85
- List[int]: timestamps of selected frames
86
- """
87
- return frame_tss[-self.k :]
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import random
4
+ from collections.abc import Callable
5
+ from enum import Enum
6
+ from typing import Callable as TCallable
7
+ from typing import List
8
+
9
+ FrameTsList = List[int]
10
+ FrameSelector = TCallable[[FrameTsList], FrameTsList]
11
+
12
+
13
+ class FrameSelectionStrategy(Enum):
14
+ """
15
+ Frame selection strategy used with videos:
16
+ - "random_k": select k random frames
17
+ - "first_k": select k first frames
18
+ - "last_k": select k last frames
19
+ - "all": select all frames
20
+ """
21
+
22
+ # fmt: off
23
+ RANDOM_K = "random_k"
24
+ FIRST_K = "first_k"
25
+ LAST_K = "last_k"
26
+ ALL = "all"
27
+ # fmt: on
28
+
29
+
30
+ class RandomKFramesSelector(Callable): # pyre-ignore[39]
31
+ """
32
+ Selector that retains at most `k` random frames
33
+ """
34
+
35
+ def __init__(self, k: int):
36
+ self.k = k
37
+
38
+ def __call__(self, frame_tss: FrameTsList) -> FrameTsList:
39
+ """
40
+ Select `k` random frames
41
+
42
+ Args:
43
+ frames_tss (List[int]): timestamps of input frames
44
+ Returns:
45
+ List[int]: timestamps of selected frames
46
+ """
47
+ return random.sample(frame_tss, min(self.k, len(frame_tss)))
48
+
49
+
50
+ class FirstKFramesSelector(Callable): # pyre-ignore[39]
51
+ """
52
+ Selector that retains at most `k` first frames
53
+ """
54
+
55
+ def __init__(self, k: int):
56
+ self.k = k
57
+
58
+ def __call__(self, frame_tss: FrameTsList) -> FrameTsList:
59
+ """
60
+ Select `k` first frames
61
+
62
+ Args:
63
+ frames_tss (List[int]): timestamps of input frames
64
+ Returns:
65
+ List[int]: timestamps of selected frames
66
+ """
67
+ return frame_tss[: self.k]
68
+
69
+
70
+ class LastKFramesSelector(Callable): # pyre-ignore[39]
71
+ """
72
+ Selector that retains at most `k` last frames from video data
73
+ """
74
+
75
+ def __init__(self, k: int):
76
+ self.k = k
77
+
78
+ def __call__(self, frame_tss: FrameTsList) -> FrameTsList:
79
+ """
80
+ Select `k` last frames
81
+
82
+ Args:
83
+ frames_tss (List[int]): timestamps of input frames
84
+ Returns:
85
+ List[int]: timestamps of selected frames
86
+ """
87
+ return frame_tss[-self.k :]
densepose/data/video/video_keyframe_dataset.py CHANGED
@@ -1,300 +1,300 @@
1
- # -*- coding: utf-8 -*-
2
- # Copyright (c) Facebook, Inc. and its affiliates.
3
-
4
- import csv
5
- import logging
6
- import numpy as np
7
- from typing import Any, Callable, Dict, List, Optional, Union
8
- import av
9
- import torch
10
- from torch.utils.data.dataset import Dataset
11
-
12
- from detectron2.utils.file_io import PathManager
13
-
14
- from ..utils import maybe_prepend_base_path
15
- from .frame_selector import FrameSelector, FrameTsList
16
-
17
- FrameList = List[av.frame.Frame] # pyre-ignore[16]
18
- FrameTransform = Callable[[torch.Tensor], torch.Tensor]
19
-
20
-
21
- def list_keyframes(video_fpath: str, video_stream_idx: int = 0) -> FrameTsList:
22
- """
23
- Traverses all keyframes of a video file. Returns a list of keyframe
24
- timestamps. Timestamps are counts in timebase units.
25
-
26
- Args:
27
- video_fpath (str): Video file path
28
- video_stream_idx (int): Video stream index (default: 0)
29
- Returns:
30
- List[int]: list of keyframe timestaps (timestamp is a count in timebase
31
- units)
32
- """
33
- try:
34
- with PathManager.open(video_fpath, "rb") as io:
35
- container = av.open(io, mode="r")
36
- stream = container.streams.video[video_stream_idx]
37
- keyframes = []
38
- pts = -1
39
- # Note: even though we request forward seeks for keyframes, sometimes
40
- # a keyframe in backwards direction is returned. We introduce tolerance
41
- # as a max count of ignored backward seeks
42
- tolerance_backward_seeks = 2
43
- while True:
44
- try:
45
- container.seek(pts + 1, backward=False, any_frame=False, stream=stream)
46
- except av.AVError as e:
47
- # the exception occurs when the video length is exceeded,
48
- # we then return whatever data we've already collected
49
- logger = logging.getLogger(__name__)
50
- logger.debug(
51
- f"List keyframes: Error seeking video file {video_fpath}, "
52
- f"video stream {video_stream_idx}, pts {pts + 1}, AV error: {e}"
53
- )
54
- return keyframes
55
- except OSError as e:
56
- logger = logging.getLogger(__name__)
57
- logger.warning(
58
- f"List keyframes: Error seeking video file {video_fpath}, "
59
- f"video stream {video_stream_idx}, pts {pts + 1}, OS error: {e}"
60
- )
61
- return []
62
- packet = next(container.demux(video=video_stream_idx))
63
- if packet.pts is not None and packet.pts <= pts:
64
- logger = logging.getLogger(__name__)
65
- logger.warning(
66
- f"Video file {video_fpath}, stream {video_stream_idx}: "
67
- f"bad seek for packet {pts + 1} (got packet {packet.pts}), "
68
- f"tolerance {tolerance_backward_seeks}."
69
- )
70
- tolerance_backward_seeks -= 1
71
- if tolerance_backward_seeks == 0:
72
- return []
73
- pts += 1
74
- continue
75
- tolerance_backward_seeks = 2
76
- pts = packet.pts
77
- if pts is None:
78
- return keyframes
79
- if packet.is_keyframe:
80
- keyframes.append(pts)
81
- return keyframes
82
- except OSError as e:
83
- logger = logging.getLogger(__name__)
84
- logger.warning(
85
- f"List keyframes: Error opening video file container {video_fpath}, " f"OS error: {e}"
86
- )
87
- except RuntimeError as e:
88
- logger = logging.getLogger(__name__)
89
- logger.warning(
90
- f"List keyframes: Error opening video file container {video_fpath}, "
91
- f"Runtime error: {e}"
92
- )
93
- return []
94
-
95
-
96
- def read_keyframes(
97
- video_fpath: str, keyframes: FrameTsList, video_stream_idx: int = 0
98
- ) -> FrameList: # pyre-ignore[11]
99
- """
100
- Reads keyframe data from a video file.
101
-
102
- Args:
103
- video_fpath (str): Video file path
104
- keyframes (List[int]): List of keyframe timestamps (as counts in
105
- timebase units to be used in container seek operations)
106
- video_stream_idx (int): Video stream index (default: 0)
107
- Returns:
108
- List[Frame]: list of frames that correspond to the specified timestamps
109
- """
110
- try:
111
- with PathManager.open(video_fpath, "rb") as io:
112
- container = av.open(io)
113
- stream = container.streams.video[video_stream_idx]
114
- frames = []
115
- for pts in keyframes:
116
- try:
117
- container.seek(pts, any_frame=False, stream=stream)
118
- frame = next(container.decode(video=0))
119
- frames.append(frame)
120
- except av.AVError as e:
121
- logger = logging.getLogger(__name__)
122
- logger.warning(
123
- f"Read keyframes: Error seeking video file {video_fpath}, "
124
- f"video stream {video_stream_idx}, pts {pts}, AV error: {e}"
125
- )
126
- container.close()
127
- return frames
128
- except OSError as e:
129
- logger = logging.getLogger(__name__)
130
- logger.warning(
131
- f"Read keyframes: Error seeking video file {video_fpath}, "
132
- f"video stream {video_stream_idx}, pts {pts}, OS error: {e}"
133
- )
134
- container.close()
135
- return frames
136
- except StopIteration:
137
- logger = logging.getLogger(__name__)
138
- logger.warning(
139
- f"Read keyframes: Error decoding frame from {video_fpath}, "
140
- f"video stream {video_stream_idx}, pts {pts}"
141
- )
142
- container.close()
143
- return frames
144
-
145
- container.close()
146
- return frames
147
- except OSError as e:
148
- logger = logging.getLogger(__name__)
149
- logger.warning(
150
- f"Read keyframes: Error opening video file container {video_fpath}, OS error: {e}"
151
- )
152
- except RuntimeError as e:
153
- logger = logging.getLogger(__name__)
154
- logger.warning(
155
- f"Read keyframes: Error opening video file container {video_fpath}, Runtime error: {e}"
156
- )
157
- return []
158
-
159
-
160
- def video_list_from_file(video_list_fpath: str, base_path: Optional[str] = None):
161
- """
162
- Create a list of paths to video files from a text file.
163
-
164
- Args:
165
- video_list_fpath (str): path to a plain text file with the list of videos
166
- base_path (str): base path for entries from the video list (default: None)
167
- """
168
- video_list = []
169
- with PathManager.open(video_list_fpath, "r") as io:
170
- for line in io:
171
- video_list.append(maybe_prepend_base_path(base_path, str(line.strip())))
172
- return video_list
173
-
174
-
175
- def read_keyframe_helper_data(fpath: str):
176
- """
177
- Read keyframe data from a file in CSV format: the header should contain
178
- "video_id" and "keyframes" fields. Value specifications are:
179
- video_id: int
180
- keyframes: list(int)
181
- Example of contents:
182
- video_id,keyframes
183
- 2,"[1,11,21,31,41,51,61,71,81]"
184
-
185
- Args:
186
- fpath (str): File containing keyframe data
187
-
188
- Return:
189
- video_id_to_keyframes (dict: int -> list(int)): for a given video ID it
190
- contains a list of keyframes for that video
191
- """
192
- video_id_to_keyframes = {}
193
- try:
194
- with PathManager.open(fpath, "r") as io:
195
- csv_reader = csv.reader(io)
196
- header = next(csv_reader)
197
- video_id_idx = header.index("video_id")
198
- keyframes_idx = header.index("keyframes")
199
- for row in csv_reader:
200
- video_id = int(row[video_id_idx])
201
- assert (
202
- video_id not in video_id_to_keyframes
203
- ), f"Duplicate keyframes entry for video {fpath}"
204
- video_id_to_keyframes[video_id] = (
205
- [int(v) for v in row[keyframes_idx][1:-1].split(",")]
206
- if len(row[keyframes_idx]) > 2
207
- else []
208
- )
209
- except Exception as e:
210
- logger = logging.getLogger(__name__)
211
- logger.warning(f"Error reading keyframe helper data from {fpath}: {e}")
212
- return video_id_to_keyframes
213
-
214
-
215
- class VideoKeyframeDataset(Dataset):
216
- """
217
- Dataset that provides keyframes for a set of videos.
218
- """
219
-
220
- _EMPTY_FRAMES = torch.empty((0, 3, 1, 1))
221
-
222
- def __init__(
223
- self,
224
- video_list: List[str],
225
- category_list: Union[str, List[str], None] = None,
226
- frame_selector: Optional[FrameSelector] = None,
227
- transform: Optional[FrameTransform] = None,
228
- keyframe_helper_fpath: Optional[str] = None,
229
- ):
230
- """
231
- Dataset constructor
232
-
233
- Args:
234
- video_list (List[str]): list of paths to video files
235
- category_list (Union[str, List[str], None]): list of animal categories for each
236
- video file. If it is a string, or None, this applies to all videos
237
- frame_selector (Callable: KeyFrameList -> KeyFrameList):
238
- selects keyframes to process, keyframes are given by
239
- packet timestamps in timebase counts. If None, all keyframes
240
- are selected (default: None)
241
- transform (Callable: torch.Tensor -> torch.Tensor):
242
- transforms a batch of RGB images (tensors of size [B, 3, H, W]),
243
- returns a tensor of the same size. If None, no transform is
244
- applied (default: None)
245
-
246
- """
247
- if type(category_list) == list:
248
- self.category_list = category_list
249
- else:
250
- self.category_list = [category_list] * len(video_list)
251
- assert len(video_list) == len(
252
- self.category_list
253
- ), "length of video and category lists must be equal"
254
- self.video_list = video_list
255
- self.frame_selector = frame_selector
256
- self.transform = transform
257
- self.keyframe_helper_data = (
258
- read_keyframe_helper_data(keyframe_helper_fpath)
259
- if keyframe_helper_fpath is not None
260
- else None
261
- )
262
-
263
- def __getitem__(self, idx: int) -> Dict[str, Any]:
264
- """
265
- Gets selected keyframes from a given video
266
-
267
- Args:
268
- idx (int): video index in the video list file
269
- Returns:
270
- A dictionary containing two keys:
271
- images (torch.Tensor): tensor of size [N, H, W, 3] or of size
272
- defined by the transform that contains keyframes data
273
- categories (List[str]): categories of the frames
274
- """
275
- categories = [self.category_list[idx]]
276
- fpath = self.video_list[idx]
277
- keyframes = (
278
- list_keyframes(fpath)
279
- if self.keyframe_helper_data is None or idx not in self.keyframe_helper_data
280
- else self.keyframe_helper_data[idx]
281
- )
282
- transform = self.transform
283
- frame_selector = self.frame_selector
284
- if not keyframes:
285
- return {"images": self._EMPTY_FRAMES, "categories": []}
286
- if frame_selector is not None:
287
- keyframes = frame_selector(keyframes)
288
- frames = read_keyframes(fpath, keyframes)
289
- if not frames:
290
- return {"images": self._EMPTY_FRAMES, "categories": []}
291
- frames = np.stack([frame.to_rgb().to_ndarray() for frame in frames])
292
- frames = torch.as_tensor(frames, device=torch.device("cpu"))
293
- frames = frames[..., [2, 1, 0]] # RGB -> BGR
294
- frames = frames.permute(0, 3, 1, 2).float() # NHWC -> NCHW
295
- if transform is not None:
296
- frames = transform(frames)
297
- return {"images": frames, "categories": categories}
298
-
299
- def __len__(self):
300
- return len(self.video_list)
 
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) Facebook, Inc. and its affiliates.
3
+
4
+ import csv
5
+ import logging
6
+ import numpy as np
7
+ from typing import Any, Callable, Dict, List, Optional, Union
8
+ import av
9
+ import torch
10
+ from torch.utils.data.dataset import Dataset
11
+
12
+ from detectron2.utils.file_io import PathManager
13
+
14
+ from ..utils import maybe_prepend_base_path
15
+ from .frame_selector import FrameSelector, FrameTsList
16
+
17
+ FrameList = List[av.frame.Frame] # pyre-ignore[16]
18
+ FrameTransform = Callable[[torch.Tensor], torch.Tensor]
19
+
20
+
21
+ def list_keyframes(video_fpath: str, video_stream_idx: int = 0) -> FrameTsList:
22
+ """
23
+ Traverses all keyframes of a video file. Returns a list of keyframe
24
+ timestamps. Timestamps are counts in timebase units.
25
+
26
+ Args:
27
+ video_fpath (str): Video file path
28
+ video_stream_idx (int): Video stream index (default: 0)
29
+ Returns:
30
+ List[int]: list of keyframe timestaps (timestamp is a count in timebase
31
+ units)
32
+ """
33
+ try:
34
+ with PathManager.open(video_fpath, "rb") as io:
35
+ container = av.open(io, mode="r")
36
+ stream = container.streams.video[video_stream_idx]
37
+ keyframes = []
38
+ pts = -1
39
+ # Note: even though we request forward seeks for keyframes, sometimes
40
+ # a keyframe in backwards direction is returned. We introduce tolerance
41
+ # as a max count of ignored backward seeks
42
+ tolerance_backward_seeks = 2
43
+ while True:
44
+ try:
45
+ container.seek(pts + 1, backward=False, any_frame=False, stream=stream)
46
+ except av.AVError as e:
47
+ # the exception occurs when the video length is exceeded,
48
+ # we then return whatever data we've already collected
49
+ logger = logging.getLogger(__name__)
50
+ logger.debug(
51
+ f"List keyframes: Error seeking video file {video_fpath}, "
52
+ f"video stream {video_stream_idx}, pts {pts + 1}, AV error: {e}"
53
+ )
54
+ return keyframes
55
+ except OSError as e:
56
+ logger = logging.getLogger(__name__)
57
+ logger.warning(
58
+ f"List keyframes: Error seeking video file {video_fpath}, "
59
+ f"video stream {video_stream_idx}, pts {pts + 1}, OS error: {e}"
60
+ )
61
+ return []
62
+ packet = next(container.demux(video=video_stream_idx))
63
+ if packet.pts is not None and packet.pts <= pts:
64
+ logger = logging.getLogger(__name__)
65
+ logger.warning(
66
+ f"Video file {video_fpath}, stream {video_stream_idx}: "
67
+ f"bad seek for packet {pts + 1} (got packet {packet.pts}), "
68
+ f"tolerance {tolerance_backward_seeks}."
69
+ )
70
+ tolerance_backward_seeks -= 1
71
+ if tolerance_backward_seeks == 0:
72
+ return []
73
+ pts += 1
74
+ continue
75
+ tolerance_backward_seeks = 2
76
+ pts = packet.pts
77
+ if pts is None:
78
+ return keyframes
79
+ if packet.is_keyframe:
80
+ keyframes.append(pts)
81
+ return keyframes
82
+ except OSError as e:
83
+ logger = logging.getLogger(__name__)
84
+ logger.warning(
85
+ f"List keyframes: Error opening video file container {video_fpath}, " f"OS error: {e}"
86
+ )
87
+ except RuntimeError as e:
88
+ logger = logging.getLogger(__name__)
89
+ logger.warning(
90
+ f"List keyframes: Error opening video file container {video_fpath}, "
91
+ f"Runtime error: {e}"
92
+ )
93
+ return []
94
+
95
+
96
+ def read_keyframes(
97
+ video_fpath: str, keyframes: FrameTsList, video_stream_idx: int = 0
98
+ ) -> FrameList: # pyre-ignore[11]
99
+ """
100
+ Reads keyframe data from a video file.
101
+
102
+ Args:
103
+ video_fpath (str): Video file path
104
+ keyframes (List[int]): List of keyframe timestamps (as counts in
105
+ timebase units to be used in container seek operations)
106
+ video_stream_idx (int): Video stream index (default: 0)
107
+ Returns:
108
+ List[Frame]: list of frames that correspond to the specified timestamps
109
+ """
110
+ try:
111
+ with PathManager.open(video_fpath, "rb") as io:
112
+ container = av.open(io)
113
+ stream = container.streams.video[video_stream_idx]
114
+ frames = []
115
+ for pts in keyframes:
116
+ try:
117
+ container.seek(pts, any_frame=False, stream=stream)
118
+ frame = next(container.decode(video=0))
119
+ frames.append(frame)
120
+ except av.AVError as e:
121
+ logger = logging.getLogger(__name__)
122
+ logger.warning(
123
+ f"Read keyframes: Error seeking video file {video_fpath}, "
124
+ f"video stream {video_stream_idx}, pts {pts}, AV error: {e}"
125
+ )
126
+ container.close()
127
+ return frames
128
+ except OSError as e:
129
+ logger = logging.getLogger(__name__)
130
+ logger.warning(
131
+ f"Read keyframes: Error seeking video file {video_fpath}, "
132
+ f"video stream {video_stream_idx}, pts {pts}, OS error: {e}"
133
+ )
134
+ container.close()
135
+ return frames
136
+ except StopIteration:
137
+ logger = logging.getLogger(__name__)
138
+ logger.warning(
139
+ f"Read keyframes: Error decoding frame from {video_fpath}, "
140
+ f"video stream {video_stream_idx}, pts {pts}"
141
+ )
142
+ container.close()
143
+ return frames
144
+
145
+ container.close()
146
+ return frames
147
+ except OSError as e:
148
+ logger = logging.getLogger(__name__)
149
+ logger.warning(
150
+ f"Read keyframes: Error opening video file container {video_fpath}, OS error: {e}"
151
+ )
152
+ except RuntimeError as e:
153
+ logger = logging.getLogger(__name__)
154
+ logger.warning(
155
+ f"Read keyframes: Error opening video file container {video_fpath}, Runtime error: {e}"
156
+ )
157
+ return []
158
+
159
+
160
+ def video_list_from_file(video_list_fpath: str, base_path: Optional[str] = None):
161
+ """
162
+ Create a list of paths to video files from a text file.
163
+
164
+ Args:
165
+ video_list_fpath (str): path to a plain text file with the list of videos
166
+ base_path (str): base path for entries from the video list (default: None)
167
+ """
168
+ video_list = []
169
+ with PathManager.open(video_list_fpath, "r") as io:
170
+ for line in io:
171
+ video_list.append(maybe_prepend_base_path(base_path, str(line.strip())))
172
+ return video_list
173
+
174
+
175
+ def read_keyframe_helper_data(fpath: str):
176
+ """
177
+ Read keyframe data from a file in CSV format: the header should contain
178
+ "video_id" and "keyframes" fields. Value specifications are:
179
+ video_id: int
180
+ keyframes: list(int)
181
+ Example of contents:
182
+ video_id,keyframes
183
+ 2,"[1,11,21,31,41,51,61,71,81]"
184
+
185
+ Args:
186
+ fpath (str): File containing keyframe data
187
+
188
+ Return:
189
+ video_id_to_keyframes (dict: int -> list(int)): for a given video ID it
190
+ contains a list of keyframes for that video
191
+ """
192
+ video_id_to_keyframes = {}
193
+ try:
194
+ with PathManager.open(fpath, "r") as io:
195
+ csv_reader = csv.reader(io)
196
+ header = next(csv_reader)
197
+ video_id_idx = header.index("video_id")
198
+ keyframes_idx = header.index("keyframes")
199
+ for row in csv_reader:
200
+ video_id = int(row[video_id_idx])
201
+ assert (
202
+ video_id not in video_id_to_keyframes
203
+ ), f"Duplicate keyframes entry for video {fpath}"
204
+ video_id_to_keyframes[video_id] = (
205
+ [int(v) for v in row[keyframes_idx][1:-1].split(",")]
206
+ if len(row[keyframes_idx]) > 2
207
+ else []
208
+ )
209
+ except Exception as e:
210
+ logger = logging.getLogger(__name__)
211
+ logger.warning(f"Error reading keyframe helper data from {fpath}: {e}")
212
+ return video_id_to_keyframes
213
+
214
+
215
+ class VideoKeyframeDataset(Dataset):
216
+ """
217
+ Dataset that provides keyframes for a set of videos.
218
+ """
219
+
220
+ _EMPTY_FRAMES = torch.empty((0, 3, 1, 1))
221
+
222
+ def __init__(
223
+ self,
224
+ video_list: List[str],
225
+ category_list: Union[str, List[str], None] = None,
226
+ frame_selector: Optional[FrameSelector] = None,
227
+ transform: Optional[FrameTransform] = None,
228
+ keyframe_helper_fpath: Optional[str] = None,
229
+ ):
230
+ """
231
+ Dataset constructor
232
+
233
+ Args:
234
+ video_list (List[str]): list of paths to video files
235
+ category_list (Union[str, List[str], None]): list of animal categories for each
236
+ video file. If it is a string, or None, this applies to all videos
237
+ frame_selector (Callable: KeyFrameList -> KeyFrameList):
238
+ selects keyframes to process, keyframes are given by
239
+ packet timestamps in timebase counts. If None, all keyframes
240
+ are selected (default: None)
241
+ transform (Callable: torch.Tensor -> torch.Tensor):
242
+ transforms a batch of RGB images (tensors of size [B, 3, H, W]),
243
+ returns a tensor of the same size. If None, no transform is
244
+ applied (default: None)
245
+
246
+ """
247
+ if type(category_list) == list:
248
+ self.category_list = category_list
249
+ else:
250
+ self.category_list = [category_list] * len(video_list)
251
+ assert len(video_list) == len(
252
+ self.category_list
253
+ ), "length of video and category lists must be equal"
254
+ self.video_list = video_list
255
+ self.frame_selector = frame_selector
256
+ self.transform = transform
257
+ self.keyframe_helper_data = (
258
+ read_keyframe_helper_data(keyframe_helper_fpath)
259
+ if keyframe_helper_fpath is not None
260
+ else None
261
+ )
262
+
263
+ def __getitem__(self, idx: int) -> Dict[str, Any]:
264
+ """
265
+ Gets selected keyframes from a given video
266
+
267
+ Args:
268
+ idx (int): video index in the video list file
269
+ Returns:
270
+ A dictionary containing two keys:
271
+ images (torch.Tensor): tensor of size [N, H, W, 3] or of size
272
+ defined by the transform that contains keyframes data
273
+ categories (List[str]): categories of the frames
274
+ """
275
+ categories = [self.category_list[idx]]
276
+ fpath = self.video_list[idx]
277
+ keyframes = (
278
+ list_keyframes(fpath)
279
+ if self.keyframe_helper_data is None or idx not in self.keyframe_helper_data
280
+ else self.keyframe_helper_data[idx]
281
+ )
282
+ transform = self.transform
283
+ frame_selector = self.frame_selector
284
+ if not keyframes:
285
+ return {"images": self._EMPTY_FRAMES, "categories": []}
286
+ if frame_selector is not None:
287
+ keyframes = frame_selector(keyframes)
288
+ frames = read_keyframes(fpath, keyframes)
289
+ if not frames:
290
+ return {"images": self._EMPTY_FRAMES, "categories": []}
291
+ frames = np.stack([frame.to_rgb().to_ndarray() for frame in frames])
292
+ frames = torch.as_tensor(frames, device=torch.device("cpu"))
293
+ frames = frames[..., [2, 1, 0]] # RGB -> BGR
294
+ frames = frames.permute(0, 3, 1, 2).float() # NHWC -> NCHW
295
+ if transform is not None:
296
+ frames = transform(frames)
297
+ return {"images": frames, "categories": categories}
298
+
299
+ def __len__(self):
300
+ return len(self.video_list)
densepose/engine/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .trainer import Trainer
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .trainer import Trainer
densepose/engine/trainer.py CHANGED
@@ -1,258 +1,258 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
-
3
- import logging
4
- import os
5
- from collections import OrderedDict
6
- from typing import List, Optional, Union
7
- import torch
8
- from torch import nn
9
-
10
- from detectron2.checkpoint import DetectionCheckpointer
11
- from detectron2.config import CfgNode
12
- from detectron2.engine import DefaultTrainer
13
- from detectron2.evaluation import (
14
- DatasetEvaluator,
15
- DatasetEvaluators,
16
- inference_on_dataset,
17
- print_csv_format,
18
- )
19
- from detectron2.solver.build import get_default_optimizer_params, maybe_add_gradient_clipping
20
- from detectron2.utils import comm
21
- from detectron2.utils.events import EventWriter, get_event_storage
22
-
23
- from densepose import DensePoseDatasetMapperTTA, DensePoseGeneralizedRCNNWithTTA, load_from_cfg
24
- from densepose.data import (
25
- DatasetMapper,
26
- build_combined_loader,
27
- build_detection_test_loader,
28
- build_detection_train_loader,
29
- build_inference_based_loaders,
30
- has_inference_based_loaders,
31
- )
32
- from densepose.evaluation.d2_evaluator_adapter import Detectron2COCOEvaluatorAdapter
33
- from densepose.evaluation.evaluator import DensePoseCOCOEvaluator, build_densepose_evaluator_storage
34
- from densepose.modeling.cse import Embedder
35
-
36
-
37
- class SampleCountingLoader:
38
- def __init__(self, loader):
39
- self.loader = loader
40
-
41
- def __iter__(self):
42
- it = iter(self.loader)
43
- storage = get_event_storage()
44
- while True:
45
- try:
46
- batch = next(it)
47
- num_inst_per_dataset = {}
48
- for data in batch:
49
- dataset_name = data["dataset"]
50
- if dataset_name not in num_inst_per_dataset:
51
- num_inst_per_dataset[dataset_name] = 0
52
- num_inst = len(data["instances"])
53
- num_inst_per_dataset[dataset_name] += num_inst
54
- for dataset_name in num_inst_per_dataset:
55
- storage.put_scalar(f"batch/{dataset_name}", num_inst_per_dataset[dataset_name])
56
- yield batch
57
- except StopIteration:
58
- break
59
-
60
-
61
- class SampleCountMetricPrinter(EventWriter):
62
- def __init__(self):
63
- self.logger = logging.getLogger(__name__)
64
-
65
- def write(self):
66
- storage = get_event_storage()
67
- batch_stats_strs = []
68
- for key, buf in storage.histories().items():
69
- if key.startswith("batch/"):
70
- batch_stats_strs.append(f"{key} {buf.avg(20)}")
71
- self.logger.info(", ".join(batch_stats_strs))
72
-
73
-
74
- class Trainer(DefaultTrainer):
75
- @classmethod
76
- def extract_embedder_from_model(cls, model: nn.Module) -> Optional[Embedder]:
77
- if isinstance(model, nn.parallel.DistributedDataParallel):
78
- model = model.module
79
- if hasattr(model, "roi_heads") and hasattr(model.roi_heads, "embedder"):
80
- return model.roi_heads.embedder
81
- return None
82
-
83
- # TODO: the only reason to copy the base class code here is to pass the embedder from
84
- # the model to the evaluator; that should be refactored to avoid unnecessary copy-pasting
85
- @classmethod
86
- def test(
87
- cls,
88
- cfg: CfgNode,
89
- model: nn.Module,
90
- evaluators: Optional[Union[DatasetEvaluator, List[DatasetEvaluator]]] = None,
91
- ):
92
- """
93
- Args:
94
- cfg (CfgNode):
95
- model (nn.Module):
96
- evaluators (DatasetEvaluator, list[DatasetEvaluator] or None): if None, will call
97
- :meth:`build_evaluator`. Otherwise, must have the same length as
98
- ``cfg.DATASETS.TEST``.
99
-
100
- Returns:
101
- dict: a dict of result metrics
102
- """
103
- logger = logging.getLogger(__name__)
104
- if isinstance(evaluators, DatasetEvaluator):
105
- evaluators = [evaluators]
106
- if evaluators is not None:
107
- assert len(cfg.DATASETS.TEST) == len(evaluators), "{} != {}".format(
108
- len(cfg.DATASETS.TEST), len(evaluators)
109
- )
110
-
111
- results = OrderedDict()
112
- for idx, dataset_name in enumerate(cfg.DATASETS.TEST):
113
- data_loader = cls.build_test_loader(cfg, dataset_name)
114
- # When evaluators are passed in as arguments,
115
- # implicitly assume that evaluators can be created before data_loader.
116
- if evaluators is not None:
117
- evaluator = evaluators[idx]
118
- else:
119
- try:
120
- embedder = cls.extract_embedder_from_model(model)
121
- evaluator = cls.build_evaluator(cfg, dataset_name, embedder=embedder)
122
- except NotImplementedError:
123
- logger.warn(
124
- "No evaluator found. Use `DefaultTrainer.test(evaluators=)`, "
125
- "or implement its `build_evaluator` method."
126
- )
127
- results[dataset_name] = {}
128
- continue
129
- if cfg.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE or comm.is_main_process():
130
- results_i = inference_on_dataset(model, data_loader, evaluator)
131
- else:
132
- results_i = {}
133
- results[dataset_name] = results_i
134
- if comm.is_main_process():
135
- assert isinstance(
136
- results_i, dict
137
- ), "Evaluator must return a dict on the main process. Got {} instead.".format(
138
- results_i
139
- )
140
- logger.info("Evaluation results for {} in csv format:".format(dataset_name))
141
- print_csv_format(results_i)
142
-
143
- if len(results) == 1:
144
- results = list(results.values())[0]
145
- return results
146
-
147
- @classmethod
148
- def build_evaluator(
149
- cls,
150
- cfg: CfgNode,
151
- dataset_name: str,
152
- output_folder: Optional[str] = None,
153
- embedder: Optional[Embedder] = None,
154
- ) -> DatasetEvaluators:
155
- if output_folder is None:
156
- output_folder = os.path.join(cfg.OUTPUT_DIR, "inference")
157
- evaluators = []
158
- distributed = cfg.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE
159
- # Note: we currently use COCO evaluator for both COCO and LVIS datasets
160
- # to have compatible metrics. LVIS bbox evaluator could also be used
161
- # with an adapter to properly handle filtered / mapped categories
162
- # evaluator_type = MetadataCatalog.get(dataset_name).evaluator_type
163
- # if evaluator_type == "coco":
164
- # evaluators.append(COCOEvaluator(dataset_name, output_dir=output_folder))
165
- # elif evaluator_type == "lvis":
166
- # evaluators.append(LVISEvaluator(dataset_name, output_dir=output_folder))
167
- evaluators.append(
168
- Detectron2COCOEvaluatorAdapter(
169
- dataset_name, output_dir=output_folder, distributed=distributed
170
- )
171
- )
172
- if cfg.MODEL.DENSEPOSE_ON:
173
- storage = build_densepose_evaluator_storage(cfg, output_folder)
174
- evaluators.append(
175
- DensePoseCOCOEvaluator(
176
- dataset_name,
177
- distributed,
178
- output_folder,
179
- evaluator_type=cfg.DENSEPOSE_EVALUATION.TYPE,
180
- min_iou_threshold=cfg.DENSEPOSE_EVALUATION.MIN_IOU_THRESHOLD,
181
- storage=storage,
182
- embedder=embedder,
183
- should_evaluate_mesh_alignment=cfg.DENSEPOSE_EVALUATION.EVALUATE_MESH_ALIGNMENT,
184
- mesh_alignment_mesh_names=cfg.DENSEPOSE_EVALUATION.MESH_ALIGNMENT_MESH_NAMES,
185
- )
186
- )
187
- return DatasetEvaluators(evaluators)
188
-
189
- @classmethod
190
- def build_optimizer(cls, cfg: CfgNode, model: nn.Module):
191
- params = get_default_optimizer_params(
192
- model,
193
- base_lr=cfg.SOLVER.BASE_LR,
194
- weight_decay_norm=cfg.SOLVER.WEIGHT_DECAY_NORM,
195
- bias_lr_factor=cfg.SOLVER.BIAS_LR_FACTOR,
196
- weight_decay_bias=cfg.SOLVER.WEIGHT_DECAY_BIAS,
197
- overrides={
198
- "features": {
199
- "lr": cfg.SOLVER.BASE_LR * cfg.MODEL.ROI_DENSEPOSE_HEAD.CSE.FEATURES_LR_FACTOR,
200
- },
201
- "embeddings": {
202
- "lr": cfg.SOLVER.BASE_LR * cfg.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDING_LR_FACTOR,
203
- },
204
- },
205
- )
206
- optimizer = torch.optim.SGD(
207
- params,
208
- cfg.SOLVER.BASE_LR,
209
- momentum=cfg.SOLVER.MOMENTUM,
210
- nesterov=cfg.SOLVER.NESTEROV,
211
- weight_decay=cfg.SOLVER.WEIGHT_DECAY,
212
- )
213
- # pyre-fixme[6]: For 2nd param expected `Type[Optimizer]` but got `SGD`.
214
- return maybe_add_gradient_clipping(cfg, optimizer)
215
-
216
- @classmethod
217
- def build_test_loader(cls, cfg: CfgNode, dataset_name):
218
- return build_detection_test_loader(cfg, dataset_name, mapper=DatasetMapper(cfg, False))
219
-
220
- @classmethod
221
- def build_train_loader(cls, cfg: CfgNode):
222
- data_loader = build_detection_train_loader(cfg, mapper=DatasetMapper(cfg, True))
223
- if not has_inference_based_loaders(cfg):
224
- return data_loader
225
- model = cls.build_model(cfg)
226
- model.to(cfg.BOOTSTRAP_MODEL.DEVICE)
227
- DetectionCheckpointer(model).resume_or_load(cfg.BOOTSTRAP_MODEL.WEIGHTS, resume=False)
228
- inference_based_loaders, ratios = build_inference_based_loaders(cfg, model)
229
- loaders = [data_loader] + inference_based_loaders
230
- ratios = [1.0] + ratios
231
- combined_data_loader = build_combined_loader(cfg, loaders, ratios)
232
- sample_counting_loader = SampleCountingLoader(combined_data_loader)
233
- return sample_counting_loader
234
-
235
- def build_writers(self):
236
- writers = super().build_writers()
237
- writers.append(SampleCountMetricPrinter())
238
- return writers
239
-
240
- @classmethod
241
- def test_with_TTA(cls, cfg: CfgNode, model):
242
- logger = logging.getLogger("detectron2.trainer")
243
- # In the end of training, run an evaluation with TTA
244
- # Only support some R-CNN models.
245
- logger.info("Running inference with test-time augmentation ...")
246
- transform_data = load_from_cfg(cfg)
247
- model = DensePoseGeneralizedRCNNWithTTA(
248
- cfg, model, transform_data, DensePoseDatasetMapperTTA(cfg)
249
- )
250
- evaluators = [
251
- cls.build_evaluator(
252
- cfg, name, output_folder=os.path.join(cfg.OUTPUT_DIR, "inference_TTA")
253
- )
254
- for name in cfg.DATASETS.TEST
255
- ]
256
- res = cls.test(cfg, model, evaluators) # pyre-ignore[6]
257
- res = OrderedDict({k + "_TTA": v for k, v in res.items()})
258
- return res
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+
3
+ import logging
4
+ import os
5
+ from collections import OrderedDict
6
+ from typing import List, Optional, Union
7
+ import torch
8
+ from torch import nn
9
+
10
+ from detectron2.checkpoint import DetectionCheckpointer
11
+ from detectron2.config import CfgNode
12
+ from detectron2.engine import DefaultTrainer
13
+ from detectron2.evaluation import (
14
+ DatasetEvaluator,
15
+ DatasetEvaluators,
16
+ inference_on_dataset,
17
+ print_csv_format,
18
+ )
19
+ from detectron2.solver.build import get_default_optimizer_params, maybe_add_gradient_clipping
20
+ from detectron2.utils import comm
21
+ from detectron2.utils.events import EventWriter, get_event_storage
22
+
23
+ from densepose import DensePoseDatasetMapperTTA, DensePoseGeneralizedRCNNWithTTA, load_from_cfg
24
+ from densepose.data import (
25
+ DatasetMapper,
26
+ build_combined_loader,
27
+ build_detection_test_loader,
28
+ build_detection_train_loader,
29
+ build_inference_based_loaders,
30
+ has_inference_based_loaders,
31
+ )
32
+ from densepose.evaluation.d2_evaluator_adapter import Detectron2COCOEvaluatorAdapter
33
+ from densepose.evaluation.evaluator import DensePoseCOCOEvaluator, build_densepose_evaluator_storage
34
+ from densepose.modeling.cse import Embedder
35
+
36
+
37
+ class SampleCountingLoader:
38
+ def __init__(self, loader):
39
+ self.loader = loader
40
+
41
+ def __iter__(self):
42
+ it = iter(self.loader)
43
+ storage = get_event_storage()
44
+ while True:
45
+ try:
46
+ batch = next(it)
47
+ num_inst_per_dataset = {}
48
+ for data in batch:
49
+ dataset_name = data["dataset"]
50
+ if dataset_name not in num_inst_per_dataset:
51
+ num_inst_per_dataset[dataset_name] = 0
52
+ num_inst = len(data["instances"])
53
+ num_inst_per_dataset[dataset_name] += num_inst
54
+ for dataset_name in num_inst_per_dataset:
55
+ storage.put_scalar(f"batch/{dataset_name}", num_inst_per_dataset[dataset_name])
56
+ yield batch
57
+ except StopIteration:
58
+ break
59
+
60
+
61
+ class SampleCountMetricPrinter(EventWriter):
62
+ def __init__(self):
63
+ self.logger = logging.getLogger(__name__)
64
+
65
+ def write(self):
66
+ storage = get_event_storage()
67
+ batch_stats_strs = []
68
+ for key, buf in storage.histories().items():
69
+ if key.startswith("batch/"):
70
+ batch_stats_strs.append(f"{key} {buf.avg(20)}")
71
+ self.logger.info(", ".join(batch_stats_strs))
72
+
73
+
74
+ class Trainer(DefaultTrainer):
75
+ @classmethod
76
+ def extract_embedder_from_model(cls, model: nn.Module) -> Optional[Embedder]:
77
+ if isinstance(model, nn.parallel.DistributedDataParallel):
78
+ model = model.module
79
+ if hasattr(model, "roi_heads") and hasattr(model.roi_heads, "embedder"):
80
+ return model.roi_heads.embedder
81
+ return None
82
+
83
+ # TODO: the only reason to copy the base class code here is to pass the embedder from
84
+ # the model to the evaluator; that should be refactored to avoid unnecessary copy-pasting
85
+ @classmethod
86
+ def test(
87
+ cls,
88
+ cfg: CfgNode,
89
+ model: nn.Module,
90
+ evaluators: Optional[Union[DatasetEvaluator, List[DatasetEvaluator]]] = None,
91
+ ):
92
+ """
93
+ Args:
94
+ cfg (CfgNode):
95
+ model (nn.Module):
96
+ evaluators (DatasetEvaluator, list[DatasetEvaluator] or None): if None, will call
97
+ :meth:`build_evaluator`. Otherwise, must have the same length as
98
+ ``cfg.DATASETS.TEST``.
99
+
100
+ Returns:
101
+ dict: a dict of result metrics
102
+ """
103
+ logger = logging.getLogger(__name__)
104
+ if isinstance(evaluators, DatasetEvaluator):
105
+ evaluators = [evaluators]
106
+ if evaluators is not None:
107
+ assert len(cfg.DATASETS.TEST) == len(evaluators), "{} != {}".format(
108
+ len(cfg.DATASETS.TEST), len(evaluators)
109
+ )
110
+
111
+ results = OrderedDict()
112
+ for idx, dataset_name in enumerate(cfg.DATASETS.TEST):
113
+ data_loader = cls.build_test_loader(cfg, dataset_name)
114
+ # When evaluators are passed in as arguments,
115
+ # implicitly assume that evaluators can be created before data_loader.
116
+ if evaluators is not None:
117
+ evaluator = evaluators[idx]
118
+ else:
119
+ try:
120
+ embedder = cls.extract_embedder_from_model(model)
121
+ evaluator = cls.build_evaluator(cfg, dataset_name, embedder=embedder)
122
+ except NotImplementedError:
123
+ logger.warn(
124
+ "No evaluator found. Use `DefaultTrainer.test(evaluators=)`, "
125
+ "or implement its `build_evaluator` method."
126
+ )
127
+ results[dataset_name] = {}
128
+ continue
129
+ if cfg.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE or comm.is_main_process():
130
+ results_i = inference_on_dataset(model, data_loader, evaluator)
131
+ else:
132
+ results_i = {}
133
+ results[dataset_name] = results_i
134
+ if comm.is_main_process():
135
+ assert isinstance(
136
+ results_i, dict
137
+ ), "Evaluator must return a dict on the main process. Got {} instead.".format(
138
+ results_i
139
+ )
140
+ logger.info("Evaluation results for {} in csv format:".format(dataset_name))
141
+ print_csv_format(results_i)
142
+
143
+ if len(results) == 1:
144
+ results = list(results.values())[0]
145
+ return results
146
+
147
+ @classmethod
148
+ def build_evaluator(
149
+ cls,
150
+ cfg: CfgNode,
151
+ dataset_name: str,
152
+ output_folder: Optional[str] = None,
153
+ embedder: Optional[Embedder] = None,
154
+ ) -> DatasetEvaluators:
155
+ if output_folder is None:
156
+ output_folder = os.path.join(cfg.OUTPUT_DIR, "inference")
157
+ evaluators = []
158
+ distributed = cfg.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE
159
+ # Note: we currently use COCO evaluator for both COCO and LVIS datasets
160
+ # to have compatible metrics. LVIS bbox evaluator could also be used
161
+ # with an adapter to properly handle filtered / mapped categories
162
+ # evaluator_type = MetadataCatalog.get(dataset_name).evaluator_type
163
+ # if evaluator_type == "coco":
164
+ # evaluators.append(COCOEvaluator(dataset_name, output_dir=output_folder))
165
+ # elif evaluator_type == "lvis":
166
+ # evaluators.append(LVISEvaluator(dataset_name, output_dir=output_folder))
167
+ evaluators.append(
168
+ Detectron2COCOEvaluatorAdapter(
169
+ dataset_name, output_dir=output_folder, distributed=distributed
170
+ )
171
+ )
172
+ if cfg.MODEL.DENSEPOSE_ON:
173
+ storage = build_densepose_evaluator_storage(cfg, output_folder)
174
+ evaluators.append(
175
+ DensePoseCOCOEvaluator(
176
+ dataset_name,
177
+ distributed,
178
+ output_folder,
179
+ evaluator_type=cfg.DENSEPOSE_EVALUATION.TYPE,
180
+ min_iou_threshold=cfg.DENSEPOSE_EVALUATION.MIN_IOU_THRESHOLD,
181
+ storage=storage,
182
+ embedder=embedder,
183
+ should_evaluate_mesh_alignment=cfg.DENSEPOSE_EVALUATION.EVALUATE_MESH_ALIGNMENT,
184
+ mesh_alignment_mesh_names=cfg.DENSEPOSE_EVALUATION.MESH_ALIGNMENT_MESH_NAMES,
185
+ )
186
+ )
187
+ return DatasetEvaluators(evaluators)
188
+
189
+ @classmethod
190
+ def build_optimizer(cls, cfg: CfgNode, model: nn.Module):
191
+ params = get_default_optimizer_params(
192
+ model,
193
+ base_lr=cfg.SOLVER.BASE_LR,
194
+ weight_decay_norm=cfg.SOLVER.WEIGHT_DECAY_NORM,
195
+ bias_lr_factor=cfg.SOLVER.BIAS_LR_FACTOR,
196
+ weight_decay_bias=cfg.SOLVER.WEIGHT_DECAY_BIAS,
197
+ overrides={
198
+ "features": {
199
+ "lr": cfg.SOLVER.BASE_LR * cfg.MODEL.ROI_DENSEPOSE_HEAD.CSE.FEATURES_LR_FACTOR,
200
+ },
201
+ "embeddings": {
202
+ "lr": cfg.SOLVER.BASE_LR * cfg.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDING_LR_FACTOR,
203
+ },
204
+ },
205
+ )
206
+ optimizer = torch.optim.SGD(
207
+ params,
208
+ cfg.SOLVER.BASE_LR,
209
+ momentum=cfg.SOLVER.MOMENTUM,
210
+ nesterov=cfg.SOLVER.NESTEROV,
211
+ weight_decay=cfg.SOLVER.WEIGHT_DECAY,
212
+ )
213
+ # pyre-fixme[6]: For 2nd param expected `Type[Optimizer]` but got `SGD`.
214
+ return maybe_add_gradient_clipping(cfg, optimizer)
215
+
216
+ @classmethod
217
+ def build_test_loader(cls, cfg: CfgNode, dataset_name):
218
+ return build_detection_test_loader(cfg, dataset_name, mapper=DatasetMapper(cfg, False))
219
+
220
+ @classmethod
221
+ def build_train_loader(cls, cfg: CfgNode):
222
+ data_loader = build_detection_train_loader(cfg, mapper=DatasetMapper(cfg, True))
223
+ if not has_inference_based_loaders(cfg):
224
+ return data_loader
225
+ model = cls.build_model(cfg)
226
+ model.to(cfg.BOOTSTRAP_MODEL.DEVICE)
227
+ DetectionCheckpointer(model).resume_or_load(cfg.BOOTSTRAP_MODEL.WEIGHTS, resume=False)
228
+ inference_based_loaders, ratios = build_inference_based_loaders(cfg, model)
229
+ loaders = [data_loader] + inference_based_loaders
230
+ ratios = [1.0] + ratios
231
+ combined_data_loader = build_combined_loader(cfg, loaders, ratios)
232
+ sample_counting_loader = SampleCountingLoader(combined_data_loader)
233
+ return sample_counting_loader
234
+
235
+ def build_writers(self):
236
+ writers = super().build_writers()
237
+ writers.append(SampleCountMetricPrinter())
238
+ return writers
239
+
240
+ @classmethod
241
+ def test_with_TTA(cls, cfg: CfgNode, model):
242
+ logger = logging.getLogger("detectron2.trainer")
243
+ # In the end of training, run an evaluation with TTA
244
+ # Only support some R-CNN models.
245
+ logger.info("Running inference with test-time augmentation ...")
246
+ transform_data = load_from_cfg(cfg)
247
+ model = DensePoseGeneralizedRCNNWithTTA(
248
+ cfg, model, transform_data, DensePoseDatasetMapperTTA(cfg)
249
+ )
250
+ evaluators = [
251
+ cls.build_evaluator(
252
+ cfg, name, output_folder=os.path.join(cfg.OUTPUT_DIR, "inference_TTA")
253
+ )
254
+ for name in cfg.DATASETS.TEST
255
+ ]
256
+ res = cls.test(cfg, model, evaluators) # pyre-ignore[6]
257
+ res = OrderedDict({k + "_TTA": v for k, v in res.items()})
258
+ return res
densepose/evaluation/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .evaluator import DensePoseCOCOEvaluator
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .evaluator import DensePoseCOCOEvaluator
densepose/evaluation/d2_evaluator_adapter.py CHANGED
@@ -1,50 +1,50 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from detectron2.data.catalog import Metadata
4
- from detectron2.evaluation import COCOEvaluator
5
-
6
- from densepose.data.datasets.coco import (
7
- get_contiguous_id_to_category_id_map,
8
- maybe_filter_categories_cocoapi,
9
- )
10
-
11
-
12
- def _maybe_add_iscrowd_annotations(cocoapi) -> None:
13
- for ann in cocoapi.dataset["annotations"]:
14
- if "iscrowd" not in ann:
15
- ann["iscrowd"] = 0
16
-
17
-
18
- class Detectron2COCOEvaluatorAdapter(COCOEvaluator):
19
- def __init__(
20
- self,
21
- dataset_name,
22
- output_dir=None,
23
- distributed=True,
24
- ):
25
- super().__init__(dataset_name, output_dir=output_dir, distributed=distributed)
26
- maybe_filter_categories_cocoapi(dataset_name, self._coco_api)
27
- _maybe_add_iscrowd_annotations(self._coco_api)
28
- # substitute category metadata to account for categories
29
- # that are mapped to the same contiguous id
30
- if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
31
- self._maybe_substitute_metadata()
32
-
33
- def _maybe_substitute_metadata(self):
34
- cont_id_2_cat_id = get_contiguous_id_to_category_id_map(self._metadata)
35
- cat_id_2_cont_id = self._metadata.thing_dataset_id_to_contiguous_id
36
- if len(cont_id_2_cat_id) == len(cat_id_2_cont_id):
37
- return
38
-
39
- cat_id_2_cont_id_injective = {}
40
- for cat_id, cont_id in cat_id_2_cont_id.items():
41
- if (cont_id in cont_id_2_cat_id) and (cont_id_2_cat_id[cont_id] == cat_id):
42
- cat_id_2_cont_id_injective[cat_id] = cont_id
43
-
44
- metadata_new = Metadata(name=self._metadata.name)
45
- for key, value in self._metadata.__dict__.items():
46
- if key == "thing_dataset_id_to_contiguous_id":
47
- setattr(metadata_new, key, cat_id_2_cont_id_injective)
48
- else:
49
- setattr(metadata_new, key, value)
50
- self._metadata = metadata_new
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from detectron2.data.catalog import Metadata
4
+ from detectron2.evaluation import COCOEvaluator
5
+
6
+ from densepose.data.datasets.coco import (
7
+ get_contiguous_id_to_category_id_map,
8
+ maybe_filter_categories_cocoapi,
9
+ )
10
+
11
+
12
+ def _maybe_add_iscrowd_annotations(cocoapi) -> None:
13
+ for ann in cocoapi.dataset["annotations"]:
14
+ if "iscrowd" not in ann:
15
+ ann["iscrowd"] = 0
16
+
17
+
18
+ class Detectron2COCOEvaluatorAdapter(COCOEvaluator):
19
+ def __init__(
20
+ self,
21
+ dataset_name,
22
+ output_dir=None,
23
+ distributed=True,
24
+ ):
25
+ super().__init__(dataset_name, output_dir=output_dir, distributed=distributed)
26
+ maybe_filter_categories_cocoapi(dataset_name, self._coco_api)
27
+ _maybe_add_iscrowd_annotations(self._coco_api)
28
+ # substitute category metadata to account for categories
29
+ # that are mapped to the same contiguous id
30
+ if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
31
+ self._maybe_substitute_metadata()
32
+
33
+ def _maybe_substitute_metadata(self):
34
+ cont_id_2_cat_id = get_contiguous_id_to_category_id_map(self._metadata)
35
+ cat_id_2_cont_id = self._metadata.thing_dataset_id_to_contiguous_id
36
+ if len(cont_id_2_cat_id) == len(cat_id_2_cont_id):
37
+ return
38
+
39
+ cat_id_2_cont_id_injective = {}
40
+ for cat_id, cont_id in cat_id_2_cont_id.items():
41
+ if (cont_id in cont_id_2_cat_id) and (cont_id_2_cat_id[cont_id] == cat_id):
42
+ cat_id_2_cont_id_injective[cat_id] = cont_id
43
+
44
+ metadata_new = Metadata(name=self._metadata.name)
45
+ for key, value in self._metadata.__dict__.items():
46
+ if key == "thing_dataset_id_to_contiguous_id":
47
+ setattr(metadata_new, key, cat_id_2_cont_id_injective)
48
+ else:
49
+ setattr(metadata_new, key, value)
50
+ self._metadata = metadata_new
densepose/evaluation/densepose_coco_evaluation.py CHANGED
The diff for this file is too large to render. See raw diff
 
densepose/evaluation/evaluator.py CHANGED
@@ -1,421 +1,421 @@
1
- # -*- coding: utf-8 -*-
2
- # Copyright (c) Facebook, Inc. and its affiliates.
3
-
4
- import contextlib
5
- import copy
6
- import io
7
- import itertools
8
- import logging
9
- import numpy as np
10
- import os
11
- from collections import OrderedDict
12
- from typing import Dict, Iterable, List, Optional
13
- import pycocotools.mask as mask_utils
14
- import torch
15
- from pycocotools.coco import COCO
16
- from tabulate import tabulate
17
-
18
- from detectron2.config import CfgNode
19
- from detectron2.data import MetadataCatalog
20
- from detectron2.evaluation import DatasetEvaluator
21
- from detectron2.structures import BoxMode
22
- from detectron2.utils.comm import gather, get_rank, is_main_process, synchronize
23
- from detectron2.utils.file_io import PathManager
24
- from detectron2.utils.logger import create_small_table
25
-
26
- from densepose.converters import ToChartResultConverter, ToMaskConverter
27
- from densepose.data.datasets.coco import maybe_filter_and_map_categories_cocoapi
28
- from densepose.structures import (
29
- DensePoseChartPredictorOutput,
30
- DensePoseEmbeddingPredictorOutput,
31
- quantize_densepose_chart_result,
32
- )
33
-
34
- from .densepose_coco_evaluation import DensePoseCocoEval, DensePoseEvalMode
35
- from .mesh_alignment_evaluator import MeshAlignmentEvaluator
36
- from .tensor_storage import (
37
- SingleProcessFileTensorStorage,
38
- SingleProcessRamTensorStorage,
39
- SingleProcessTensorStorage,
40
- SizeData,
41
- storage_gather,
42
- )
43
-
44
-
45
- class DensePoseCOCOEvaluator(DatasetEvaluator):
46
- def __init__(
47
- self,
48
- dataset_name,
49
- distributed,
50
- output_dir=None,
51
- evaluator_type: str = "iuv",
52
- min_iou_threshold: float = 0.5,
53
- storage: Optional[SingleProcessTensorStorage] = None,
54
- embedder=None,
55
- should_evaluate_mesh_alignment: bool = False,
56
- mesh_alignment_mesh_names: Optional[List[str]] = None,
57
- ):
58
- self._embedder = embedder
59
- self._distributed = distributed
60
- self._output_dir = output_dir
61
- self._evaluator_type = evaluator_type
62
- self._storage = storage
63
- self._should_evaluate_mesh_alignment = should_evaluate_mesh_alignment
64
-
65
- assert not (
66
- should_evaluate_mesh_alignment and embedder is None
67
- ), "Mesh alignment evaluation is activated, but no vertex embedder provided!"
68
- if should_evaluate_mesh_alignment:
69
- self._mesh_alignment_evaluator = MeshAlignmentEvaluator(
70
- embedder,
71
- mesh_alignment_mesh_names,
72
- )
73
-
74
- self._cpu_device = torch.device("cpu")
75
- self._logger = logging.getLogger(__name__)
76
-
77
- self._metadata = MetadataCatalog.get(dataset_name)
78
- self._min_threshold = min_iou_threshold
79
- json_file = PathManager.get_local_path(self._metadata.json_file)
80
- with contextlib.redirect_stdout(io.StringIO()):
81
- self._coco_api = COCO(json_file)
82
- maybe_filter_and_map_categories_cocoapi(dataset_name, self._coco_api)
83
-
84
- def reset(self):
85
- self._predictions = []
86
-
87
- def process(self, inputs, outputs):
88
- """
89
- Args:
90
- inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).
91
- It is a list of dict. Each dict corresponds to an image and
92
- contains keys like "height", "width", "file_name", "image_id".
93
- outputs: the outputs of a COCO model. It is a list of dicts with key
94
- "instances" that contains :class:`Instances`.
95
- The :class:`Instances` object needs to have `densepose` field.
96
- """
97
- for input, output in zip(inputs, outputs):
98
- instances = output["instances"].to(self._cpu_device)
99
- if not instances.has("pred_densepose"):
100
- continue
101
- prediction_list = prediction_to_dict(
102
- instances,
103
- input["image_id"],
104
- self._embedder,
105
- self._metadata.class_to_mesh_name,
106
- self._storage is not None,
107
- )
108
- if self._storage is not None:
109
- for prediction_dict in prediction_list:
110
- dict_to_store = {}
111
- for field_name in self._storage.data_schema:
112
- dict_to_store[field_name] = prediction_dict[field_name]
113
- record_id = self._storage.put(dict_to_store)
114
- prediction_dict["record_id"] = record_id
115
- prediction_dict["rank"] = get_rank()
116
- for field_name in self._storage.data_schema:
117
- del prediction_dict[field_name]
118
- self._predictions.extend(prediction_list)
119
-
120
- def evaluate(self, img_ids=None):
121
- if self._distributed:
122
- synchronize()
123
- predictions = gather(self._predictions)
124
- predictions = list(itertools.chain(*predictions))
125
- else:
126
- predictions = self._predictions
127
-
128
- multi_storage = storage_gather(self._storage) if self._storage is not None else None
129
-
130
- if not is_main_process():
131
- return
132
- return copy.deepcopy(self._eval_predictions(predictions, multi_storage, img_ids))
133
-
134
- def _eval_predictions(self, predictions, multi_storage=None, img_ids=None):
135
- """
136
- Evaluate predictions on densepose.
137
- Return results with the metrics of the tasks.
138
- """
139
- self._logger.info("Preparing results for COCO format ...")
140
-
141
- if self._output_dir:
142
- PathManager.mkdirs(self._output_dir)
143
- file_path = os.path.join(self._output_dir, "coco_densepose_predictions.pth")
144
- with PathManager.open(file_path, "wb") as f:
145
- torch.save(predictions, f)
146
-
147
- self._logger.info("Evaluating predictions ...")
148
- res = OrderedDict()
149
- results_gps, results_gpsm, results_segm = _evaluate_predictions_on_coco(
150
- self._coco_api,
151
- predictions,
152
- multi_storage,
153
- self._embedder,
154
- class_names=self._metadata.get("thing_classes"),
155
- min_threshold=self._min_threshold,
156
- img_ids=img_ids,
157
- )
158
- res["densepose_gps"] = results_gps
159
- res["densepose_gpsm"] = results_gpsm
160
- res["densepose_segm"] = results_segm
161
- if self._should_evaluate_mesh_alignment:
162
- res["densepose_mesh_alignment"] = self._evaluate_mesh_alignment()
163
- return res
164
-
165
- def _evaluate_mesh_alignment(self):
166
- self._logger.info("Mesh alignment evaluation ...")
167
- mean_ge, mean_gps, per_mesh_metrics = self._mesh_alignment_evaluator.evaluate()
168
- results = {
169
- "GE": mean_ge * 100,
170
- "GPS": mean_gps * 100,
171
- }
172
- mesh_names = set()
173
- for metric_name in per_mesh_metrics:
174
- for mesh_name, value in per_mesh_metrics[metric_name].items():
175
- results[f"{metric_name}-{mesh_name}"] = value * 100
176
- mesh_names.add(mesh_name)
177
- self._print_mesh_alignment_results(results, mesh_names)
178
- return results
179
-
180
- def _print_mesh_alignment_results(self, results: Dict[str, float], mesh_names: Iterable[str]):
181
- self._logger.info("Evaluation results for densepose, mesh alignment:")
182
- self._logger.info(f'| {"Mesh":13s} | {"GErr":7s} | {"GPS":7s} |')
183
- self._logger.info("| :-----------: | :-----: | :-----: |")
184
- for mesh_name in mesh_names:
185
- ge_key = f"GE-{mesh_name}"
186
- ge_str = f"{results[ge_key]:.4f}" if ge_key in results else " "
187
- gps_key = f"GPS-{mesh_name}"
188
- gps_str = f"{results[gps_key]:.4f}" if gps_key in results else " "
189
- self._logger.info(f"| {mesh_name:13s} | {ge_str:7s} | {gps_str:7s} |")
190
- self._logger.info("| :-------------------------------: |")
191
- ge_key = "GE"
192
- ge_str = f"{results[ge_key]:.4f}" if ge_key in results else " "
193
- gps_key = "GPS"
194
- gps_str = f"{results[gps_key]:.4f}" if gps_key in results else " "
195
- self._logger.info(f'| {"MEAN":13s} | {ge_str:7s} | {gps_str:7s} |')
196
-
197
-
198
- def prediction_to_dict(instances, img_id, embedder, class_to_mesh_name, use_storage):
199
- """
200
- Args:
201
- instances (Instances): the output of the model
202
- img_id (str): the image id in COCO
203
-
204
- Returns:
205
- list[dict]: the results in densepose evaluation format
206
- """
207
- scores = instances.scores.tolist()
208
- classes = instances.pred_classes.tolist()
209
- raw_boxes_xywh = BoxMode.convert(
210
- instances.pred_boxes.tensor.clone(), BoxMode.XYXY_ABS, BoxMode.XYWH_ABS
211
- )
212
-
213
- if isinstance(instances.pred_densepose, DensePoseEmbeddingPredictorOutput):
214
- results_densepose = densepose_cse_predictions_to_dict(
215
- instances, embedder, class_to_mesh_name, use_storage
216
- )
217
- elif isinstance(instances.pred_densepose, DensePoseChartPredictorOutput):
218
- if not use_storage:
219
- results_densepose = densepose_chart_predictions_to_dict(instances)
220
- else:
221
- results_densepose = densepose_chart_predictions_to_storage_dict(instances)
222
-
223
- results = []
224
- for k in range(len(instances)):
225
- result = {
226
- "image_id": img_id,
227
- "category_id": classes[k],
228
- "bbox": raw_boxes_xywh[k].tolist(),
229
- "score": scores[k],
230
- }
231
- results.append({**result, **results_densepose[k]})
232
- return results
233
-
234
-
235
- def densepose_chart_predictions_to_dict(instances):
236
- segmentations = ToMaskConverter.convert(
237
- instances.pred_densepose, instances.pred_boxes, instances.image_size
238
- )
239
-
240
- results = []
241
- for k in range(len(instances)):
242
- densepose_results_quantized = quantize_densepose_chart_result(
243
- ToChartResultConverter.convert(instances.pred_densepose[k], instances.pred_boxes[k])
244
- )
245
- densepose_results_quantized.labels_uv_uint8 = (
246
- densepose_results_quantized.labels_uv_uint8.cpu()
247
- )
248
- segmentation = segmentations.tensor[k]
249
- segmentation_encoded = mask_utils.encode(
250
- np.require(segmentation.numpy(), dtype=np.uint8, requirements=["F"])
251
- )
252
- segmentation_encoded["counts"] = segmentation_encoded["counts"].decode("utf-8")
253
- result = {
254
- "densepose": densepose_results_quantized,
255
- "segmentation": segmentation_encoded,
256
- }
257
- results.append(result)
258
- return results
259
-
260
-
261
- def densepose_chart_predictions_to_storage_dict(instances):
262
- results = []
263
- for k in range(len(instances)):
264
- densepose_predictor_output = instances.pred_densepose[k]
265
- result = {
266
- "coarse_segm": densepose_predictor_output.coarse_segm.squeeze(0).cpu(),
267
- "fine_segm": densepose_predictor_output.fine_segm.squeeze(0).cpu(),
268
- "u": densepose_predictor_output.u.squeeze(0).cpu(),
269
- "v": densepose_predictor_output.v.squeeze(0).cpu(),
270
- }
271
- results.append(result)
272
- return results
273
-
274
-
275
- def densepose_cse_predictions_to_dict(instances, embedder, class_to_mesh_name, use_storage):
276
- results = []
277
- for k in range(len(instances)):
278
- cse = instances.pred_densepose[k]
279
- results.append(
280
- {
281
- "coarse_segm": cse.coarse_segm[0].cpu(),
282
- "embedding": cse.embedding[0].cpu(),
283
- }
284
- )
285
- return results
286
-
287
-
288
- def _evaluate_predictions_on_coco(
289
- coco_gt,
290
- coco_results,
291
- multi_storage=None,
292
- embedder=None,
293
- class_names=None,
294
- min_threshold: float = 0.5,
295
- img_ids=None,
296
- ):
297
- logger = logging.getLogger(__name__)
298
-
299
- densepose_metrics = _get_densepose_metrics(min_threshold)
300
- if len(coco_results) == 0: # cocoapi does not handle empty results very well
301
- logger.warn("No predictions from the model! Set scores to -1")
302
- results_gps = {metric: -1 for metric in densepose_metrics}
303
- results_gpsm = {metric: -1 for metric in densepose_metrics}
304
- results_segm = {metric: -1 for metric in densepose_metrics}
305
- return results_gps, results_gpsm, results_segm
306
-
307
- coco_dt = coco_gt.loadRes(coco_results)
308
-
309
- results = []
310
- for eval_mode_name in ["GPS", "GPSM", "IOU"]:
311
- eval_mode = getattr(DensePoseEvalMode, eval_mode_name)
312
- coco_eval = DensePoseCocoEval(
313
- coco_gt, coco_dt, "densepose", multi_storage, embedder, dpEvalMode=eval_mode
314
- )
315
- result = _derive_results_from_coco_eval(
316
- coco_eval, eval_mode_name, densepose_metrics, class_names, min_threshold, img_ids
317
- )
318
- results.append(result)
319
- return results
320
-
321
-
322
- def _get_densepose_metrics(min_threshold: float = 0.5):
323
- metrics = ["AP"]
324
- if min_threshold <= 0.201:
325
- metrics += ["AP20"]
326
- if min_threshold <= 0.301:
327
- metrics += ["AP30"]
328
- if min_threshold <= 0.401:
329
- metrics += ["AP40"]
330
- metrics.extend(["AP50", "AP75", "APm", "APl", "AR", "AR50", "AR75", "ARm", "ARl"])
331
- return metrics
332
-
333
-
334
- def _derive_results_from_coco_eval(
335
- coco_eval, eval_mode_name, metrics, class_names, min_threshold: float, img_ids
336
- ):
337
- if img_ids is not None:
338
- coco_eval.params.imgIds = img_ids
339
- coco_eval.params.iouThrs = np.linspace(
340
- min_threshold, 0.95, int(np.round((0.95 - min_threshold) / 0.05)) + 1, endpoint=True
341
- )
342
- coco_eval.evaluate()
343
- coco_eval.accumulate()
344
- coco_eval.summarize()
345
- results = {metric: float(coco_eval.stats[idx] * 100) for idx, metric in enumerate(metrics)}
346
- logger = logging.getLogger(__name__)
347
- logger.info(
348
- f"Evaluation results for densepose, {eval_mode_name} metric: \n"
349
- + create_small_table(results)
350
- )
351
- if class_names is None or len(class_names) <= 1:
352
- return results
353
-
354
- # Compute per-category AP, the same way as it is done in D2
355
- # (see detectron2/evaluation/coco_evaluation.py):
356
- precisions = coco_eval.eval["precision"]
357
- # precision has dims (iou, recall, cls, area range, max dets)
358
- assert len(class_names) == precisions.shape[2]
359
-
360
- results_per_category = []
361
- for idx, name in enumerate(class_names):
362
- # area range index 0: all area ranges
363
- # max dets index -1: typically 100 per image
364
- precision = precisions[:, :, idx, 0, -1]
365
- precision = precision[precision > -1]
366
- ap = np.mean(precision) if precision.size else float("nan")
367
- results_per_category.append((f"{name}", float(ap * 100)))
368
-
369
- # tabulate it
370
- n_cols = min(6, len(results_per_category) * 2)
371
- results_flatten = list(itertools.chain(*results_per_category))
372
- results_2d = itertools.zip_longest(*[results_flatten[i::n_cols] for i in range(n_cols)])
373
- table = tabulate(
374
- results_2d,
375
- tablefmt="pipe",
376
- floatfmt=".3f",
377
- headers=["category", "AP"] * (n_cols // 2),
378
- numalign="left",
379
- )
380
- logger.info(f"Per-category {eval_mode_name} AP: \n" + table)
381
-
382
- results.update({"AP-" + name: ap for name, ap in results_per_category})
383
- return results
384
-
385
-
386
- def build_densepose_evaluator_storage(cfg: CfgNode, output_folder: str):
387
- storage_spec = cfg.DENSEPOSE_EVALUATION.STORAGE
388
- if storage_spec == "none":
389
- return None
390
- evaluator_type = cfg.DENSEPOSE_EVALUATION.TYPE
391
- # common output tensor sizes
392
- hout = cfg.MODEL.ROI_DENSEPOSE_HEAD.HEATMAP_SIZE
393
- wout = cfg.MODEL.ROI_DENSEPOSE_HEAD.HEATMAP_SIZE
394
- n_csc = cfg.MODEL.ROI_DENSEPOSE_HEAD.NUM_COARSE_SEGM_CHANNELS
395
- # specific output tensors
396
- if evaluator_type == "iuv":
397
- n_fsc = cfg.MODEL.ROI_DENSEPOSE_HEAD.NUM_PATCHES + 1
398
- schema = {
399
- "coarse_segm": SizeData(dtype="float32", shape=(n_csc, hout, wout)),
400
- "fine_segm": SizeData(dtype="float32", shape=(n_fsc, hout, wout)),
401
- "u": SizeData(dtype="float32", shape=(n_fsc, hout, wout)),
402
- "v": SizeData(dtype="float32", shape=(n_fsc, hout, wout)),
403
- }
404
- elif evaluator_type == "cse":
405
- embed_size = cfg.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_SIZE
406
- schema = {
407
- "coarse_segm": SizeData(dtype="float32", shape=(n_csc, hout, wout)),
408
- "embedding": SizeData(dtype="float32", shape=(embed_size, hout, wout)),
409
- }
410
- else:
411
- raise ValueError(f"Unknown evaluator type: {evaluator_type}")
412
- # storage types
413
- if storage_spec == "ram":
414
- storage = SingleProcessRamTensorStorage(schema, io.BytesIO())
415
- elif storage_spec == "file":
416
- fpath = os.path.join(output_folder, f"DensePoseEvaluatorStorage.{get_rank()}.bin")
417
- PathManager.mkdirs(output_folder)
418
- storage = SingleProcessFileTensorStorage(schema, fpath, "wb")
419
- else:
420
- raise ValueError(f"Unknown storage specification: {storage_spec}")
421
- return storage
 
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) Facebook, Inc. and its affiliates.
3
+
4
+ import contextlib
5
+ import copy
6
+ import io
7
+ import itertools
8
+ import logging
9
+ import numpy as np
10
+ import os
11
+ from collections import OrderedDict
12
+ from typing import Dict, Iterable, List, Optional
13
+ import pycocotools.mask as mask_utils
14
+ import torch
15
+ from pycocotools.coco import COCO
16
+ from tabulate import tabulate
17
+
18
+ from detectron2.config import CfgNode
19
+ from detectron2.data import MetadataCatalog
20
+ from detectron2.evaluation import DatasetEvaluator
21
+ from detectron2.structures import BoxMode
22
+ from detectron2.utils.comm import gather, get_rank, is_main_process, synchronize
23
+ from detectron2.utils.file_io import PathManager
24
+ from detectron2.utils.logger import create_small_table
25
+
26
+ from densepose.converters import ToChartResultConverter, ToMaskConverter
27
+ from densepose.data.datasets.coco import maybe_filter_and_map_categories_cocoapi
28
+ from densepose.structures import (
29
+ DensePoseChartPredictorOutput,
30
+ DensePoseEmbeddingPredictorOutput,
31
+ quantize_densepose_chart_result,
32
+ )
33
+
34
+ from .densepose_coco_evaluation import DensePoseCocoEval, DensePoseEvalMode
35
+ from .mesh_alignment_evaluator import MeshAlignmentEvaluator
36
+ from .tensor_storage import (
37
+ SingleProcessFileTensorStorage,
38
+ SingleProcessRamTensorStorage,
39
+ SingleProcessTensorStorage,
40
+ SizeData,
41
+ storage_gather,
42
+ )
43
+
44
+
45
+ class DensePoseCOCOEvaluator(DatasetEvaluator):
46
+ def __init__(
47
+ self,
48
+ dataset_name,
49
+ distributed,
50
+ output_dir=None,
51
+ evaluator_type: str = "iuv",
52
+ min_iou_threshold: float = 0.5,
53
+ storage: Optional[SingleProcessTensorStorage] = None,
54
+ embedder=None,
55
+ should_evaluate_mesh_alignment: bool = False,
56
+ mesh_alignment_mesh_names: Optional[List[str]] = None,
57
+ ):
58
+ self._embedder = embedder
59
+ self._distributed = distributed
60
+ self._output_dir = output_dir
61
+ self._evaluator_type = evaluator_type
62
+ self._storage = storage
63
+ self._should_evaluate_mesh_alignment = should_evaluate_mesh_alignment
64
+
65
+ assert not (
66
+ should_evaluate_mesh_alignment and embedder is None
67
+ ), "Mesh alignment evaluation is activated, but no vertex embedder provided!"
68
+ if should_evaluate_mesh_alignment:
69
+ self._mesh_alignment_evaluator = MeshAlignmentEvaluator(
70
+ embedder,
71
+ mesh_alignment_mesh_names,
72
+ )
73
+
74
+ self._cpu_device = torch.device("cpu")
75
+ self._logger = logging.getLogger(__name__)
76
+
77
+ self._metadata = MetadataCatalog.get(dataset_name)
78
+ self._min_threshold = min_iou_threshold
79
+ json_file = PathManager.get_local_path(self._metadata.json_file)
80
+ with contextlib.redirect_stdout(io.StringIO()):
81
+ self._coco_api = COCO(json_file)
82
+ maybe_filter_and_map_categories_cocoapi(dataset_name, self._coco_api)
83
+
84
+ def reset(self):
85
+ self._predictions = []
86
+
87
+ def process(self, inputs, outputs):
88
+ """
89
+ Args:
90
+ inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).
91
+ It is a list of dict. Each dict corresponds to an image and
92
+ contains keys like "height", "width", "file_name", "image_id".
93
+ outputs: the outputs of a COCO model. It is a list of dicts with key
94
+ "instances" that contains :class:`Instances`.
95
+ The :class:`Instances` object needs to have `densepose` field.
96
+ """
97
+ for input, output in zip(inputs, outputs):
98
+ instances = output["instances"].to(self._cpu_device)
99
+ if not instances.has("pred_densepose"):
100
+ continue
101
+ prediction_list = prediction_to_dict(
102
+ instances,
103
+ input["image_id"],
104
+ self._embedder,
105
+ self._metadata.class_to_mesh_name,
106
+ self._storage is not None,
107
+ )
108
+ if self._storage is not None:
109
+ for prediction_dict in prediction_list:
110
+ dict_to_store = {}
111
+ for field_name in self._storage.data_schema:
112
+ dict_to_store[field_name] = prediction_dict[field_name]
113
+ record_id = self._storage.put(dict_to_store)
114
+ prediction_dict["record_id"] = record_id
115
+ prediction_dict["rank"] = get_rank()
116
+ for field_name in self._storage.data_schema:
117
+ del prediction_dict[field_name]
118
+ self._predictions.extend(prediction_list)
119
+
120
+ def evaluate(self, img_ids=None):
121
+ if self._distributed:
122
+ synchronize()
123
+ predictions = gather(self._predictions)
124
+ predictions = list(itertools.chain(*predictions))
125
+ else:
126
+ predictions = self._predictions
127
+
128
+ multi_storage = storage_gather(self._storage) if self._storage is not None else None
129
+
130
+ if not is_main_process():
131
+ return
132
+ return copy.deepcopy(self._eval_predictions(predictions, multi_storage, img_ids))
133
+
134
+ def _eval_predictions(self, predictions, multi_storage=None, img_ids=None):
135
+ """
136
+ Evaluate predictions on densepose.
137
+ Return results with the metrics of the tasks.
138
+ """
139
+ self._logger.info("Preparing results for COCO format ...")
140
+
141
+ if self._output_dir:
142
+ PathManager.mkdirs(self._output_dir)
143
+ file_path = os.path.join(self._output_dir, "coco_densepose_predictions.pth")
144
+ with PathManager.open(file_path, "wb") as f:
145
+ torch.save(predictions, f)
146
+
147
+ self._logger.info("Evaluating predictions ...")
148
+ res = OrderedDict()
149
+ results_gps, results_gpsm, results_segm = _evaluate_predictions_on_coco(
150
+ self._coco_api,
151
+ predictions,
152
+ multi_storage,
153
+ self._embedder,
154
+ class_names=self._metadata.get("thing_classes"),
155
+ min_threshold=self._min_threshold,
156
+ img_ids=img_ids,
157
+ )
158
+ res["densepose_gps"] = results_gps
159
+ res["densepose_gpsm"] = results_gpsm
160
+ res["densepose_segm"] = results_segm
161
+ if self._should_evaluate_mesh_alignment:
162
+ res["densepose_mesh_alignment"] = self._evaluate_mesh_alignment()
163
+ return res
164
+
165
+ def _evaluate_mesh_alignment(self):
166
+ self._logger.info("Mesh alignment evaluation ...")
167
+ mean_ge, mean_gps, per_mesh_metrics = self._mesh_alignment_evaluator.evaluate()
168
+ results = {
169
+ "GE": mean_ge * 100,
170
+ "GPS": mean_gps * 100,
171
+ }
172
+ mesh_names = set()
173
+ for metric_name in per_mesh_metrics:
174
+ for mesh_name, value in per_mesh_metrics[metric_name].items():
175
+ results[f"{metric_name}-{mesh_name}"] = value * 100
176
+ mesh_names.add(mesh_name)
177
+ self._print_mesh_alignment_results(results, mesh_names)
178
+ return results
179
+
180
+ def _print_mesh_alignment_results(self, results: Dict[str, float], mesh_names: Iterable[str]):
181
+ self._logger.info("Evaluation results for densepose, mesh alignment:")
182
+ self._logger.info(f'| {"Mesh":13s} | {"GErr":7s} | {"GPS":7s} |')
183
+ self._logger.info("| :-----------: | :-----: | :-----: |")
184
+ for mesh_name in mesh_names:
185
+ ge_key = f"GE-{mesh_name}"
186
+ ge_str = f"{results[ge_key]:.4f}" if ge_key in results else " "
187
+ gps_key = f"GPS-{mesh_name}"
188
+ gps_str = f"{results[gps_key]:.4f}" if gps_key in results else " "
189
+ self._logger.info(f"| {mesh_name:13s} | {ge_str:7s} | {gps_str:7s} |")
190
+ self._logger.info("| :-------------------------------: |")
191
+ ge_key = "GE"
192
+ ge_str = f"{results[ge_key]:.4f}" if ge_key in results else " "
193
+ gps_key = "GPS"
194
+ gps_str = f"{results[gps_key]:.4f}" if gps_key in results else " "
195
+ self._logger.info(f'| {"MEAN":13s} | {ge_str:7s} | {gps_str:7s} |')
196
+
197
+
198
+ def prediction_to_dict(instances, img_id, embedder, class_to_mesh_name, use_storage):
199
+ """
200
+ Args:
201
+ instances (Instances): the output of the model
202
+ img_id (str): the image id in COCO
203
+
204
+ Returns:
205
+ list[dict]: the results in densepose evaluation format
206
+ """
207
+ scores = instances.scores.tolist()
208
+ classes = instances.pred_classes.tolist()
209
+ raw_boxes_xywh = BoxMode.convert(
210
+ instances.pred_boxes.tensor.clone(), BoxMode.XYXY_ABS, BoxMode.XYWH_ABS
211
+ )
212
+
213
+ if isinstance(instances.pred_densepose, DensePoseEmbeddingPredictorOutput):
214
+ results_densepose = densepose_cse_predictions_to_dict(
215
+ instances, embedder, class_to_mesh_name, use_storage
216
+ )
217
+ elif isinstance(instances.pred_densepose, DensePoseChartPredictorOutput):
218
+ if not use_storage:
219
+ results_densepose = densepose_chart_predictions_to_dict(instances)
220
+ else:
221
+ results_densepose = densepose_chart_predictions_to_storage_dict(instances)
222
+
223
+ results = []
224
+ for k in range(len(instances)):
225
+ result = {
226
+ "image_id": img_id,
227
+ "category_id": classes[k],
228
+ "bbox": raw_boxes_xywh[k].tolist(),
229
+ "score": scores[k],
230
+ }
231
+ results.append({**result, **results_densepose[k]})
232
+ return results
233
+
234
+
235
+ def densepose_chart_predictions_to_dict(instances):
236
+ segmentations = ToMaskConverter.convert(
237
+ instances.pred_densepose, instances.pred_boxes, instances.image_size
238
+ )
239
+
240
+ results = []
241
+ for k in range(len(instances)):
242
+ densepose_results_quantized = quantize_densepose_chart_result(
243
+ ToChartResultConverter.convert(instances.pred_densepose[k], instances.pred_boxes[k])
244
+ )
245
+ densepose_results_quantized.labels_uv_uint8 = (
246
+ densepose_results_quantized.labels_uv_uint8.cpu()
247
+ )
248
+ segmentation = segmentations.tensor[k]
249
+ segmentation_encoded = mask_utils.encode(
250
+ np.require(segmentation.numpy(), dtype=np.uint8, requirements=["F"])
251
+ )
252
+ segmentation_encoded["counts"] = segmentation_encoded["counts"].decode("utf-8")
253
+ result = {
254
+ "densepose": densepose_results_quantized,
255
+ "segmentation": segmentation_encoded,
256
+ }
257
+ results.append(result)
258
+ return results
259
+
260
+
261
+ def densepose_chart_predictions_to_storage_dict(instances):
262
+ results = []
263
+ for k in range(len(instances)):
264
+ densepose_predictor_output = instances.pred_densepose[k]
265
+ result = {
266
+ "coarse_segm": densepose_predictor_output.coarse_segm.squeeze(0).cpu(),
267
+ "fine_segm": densepose_predictor_output.fine_segm.squeeze(0).cpu(),
268
+ "u": densepose_predictor_output.u.squeeze(0).cpu(),
269
+ "v": densepose_predictor_output.v.squeeze(0).cpu(),
270
+ }
271
+ results.append(result)
272
+ return results
273
+
274
+
275
+ def densepose_cse_predictions_to_dict(instances, embedder, class_to_mesh_name, use_storage):
276
+ results = []
277
+ for k in range(len(instances)):
278
+ cse = instances.pred_densepose[k]
279
+ results.append(
280
+ {
281
+ "coarse_segm": cse.coarse_segm[0].cpu(),
282
+ "embedding": cse.embedding[0].cpu(),
283
+ }
284
+ )
285
+ return results
286
+
287
+
288
+ def _evaluate_predictions_on_coco(
289
+ coco_gt,
290
+ coco_results,
291
+ multi_storage=None,
292
+ embedder=None,
293
+ class_names=None,
294
+ min_threshold: float = 0.5,
295
+ img_ids=None,
296
+ ):
297
+ logger = logging.getLogger(__name__)
298
+
299
+ densepose_metrics = _get_densepose_metrics(min_threshold)
300
+ if len(coco_results) == 0: # cocoapi does not handle empty results very well
301
+ logger.warn("No predictions from the model! Set scores to -1")
302
+ results_gps = {metric: -1 for metric in densepose_metrics}
303
+ results_gpsm = {metric: -1 for metric in densepose_metrics}
304
+ results_segm = {metric: -1 for metric in densepose_metrics}
305
+ return results_gps, results_gpsm, results_segm
306
+
307
+ coco_dt = coco_gt.loadRes(coco_results)
308
+
309
+ results = []
310
+ for eval_mode_name in ["GPS", "GPSM", "IOU"]:
311
+ eval_mode = getattr(DensePoseEvalMode, eval_mode_name)
312
+ coco_eval = DensePoseCocoEval(
313
+ coco_gt, coco_dt, "densepose", multi_storage, embedder, dpEvalMode=eval_mode
314
+ )
315
+ result = _derive_results_from_coco_eval(
316
+ coco_eval, eval_mode_name, densepose_metrics, class_names, min_threshold, img_ids
317
+ )
318
+ results.append(result)
319
+ return results
320
+
321
+
322
+ def _get_densepose_metrics(min_threshold: float = 0.5):
323
+ metrics = ["AP"]
324
+ if min_threshold <= 0.201:
325
+ metrics += ["AP20"]
326
+ if min_threshold <= 0.301:
327
+ metrics += ["AP30"]
328
+ if min_threshold <= 0.401:
329
+ metrics += ["AP40"]
330
+ metrics.extend(["AP50", "AP75", "APm", "APl", "AR", "AR50", "AR75", "ARm", "ARl"])
331
+ return metrics
332
+
333
+
334
+ def _derive_results_from_coco_eval(
335
+ coco_eval, eval_mode_name, metrics, class_names, min_threshold: float, img_ids
336
+ ):
337
+ if img_ids is not None:
338
+ coco_eval.params.imgIds = img_ids
339
+ coco_eval.params.iouThrs = np.linspace(
340
+ min_threshold, 0.95, int(np.round((0.95 - min_threshold) / 0.05)) + 1, endpoint=True
341
+ )
342
+ coco_eval.evaluate()
343
+ coco_eval.accumulate()
344
+ coco_eval.summarize()
345
+ results = {metric: float(coco_eval.stats[idx] * 100) for idx, metric in enumerate(metrics)}
346
+ logger = logging.getLogger(__name__)
347
+ logger.info(
348
+ f"Evaluation results for densepose, {eval_mode_name} metric: \n"
349
+ + create_small_table(results)
350
+ )
351
+ if class_names is None or len(class_names) <= 1:
352
+ return results
353
+
354
+ # Compute per-category AP, the same way as it is done in D2
355
+ # (see detectron2/evaluation/coco_evaluation.py):
356
+ precisions = coco_eval.eval["precision"]
357
+ # precision has dims (iou, recall, cls, area range, max dets)
358
+ assert len(class_names) == precisions.shape[2]
359
+
360
+ results_per_category = []
361
+ for idx, name in enumerate(class_names):
362
+ # area range index 0: all area ranges
363
+ # max dets index -1: typically 100 per image
364
+ precision = precisions[:, :, idx, 0, -1]
365
+ precision = precision[precision > -1]
366
+ ap = np.mean(precision) if precision.size else float("nan")
367
+ results_per_category.append((f"{name}", float(ap * 100)))
368
+
369
+ # tabulate it
370
+ n_cols = min(6, len(results_per_category) * 2)
371
+ results_flatten = list(itertools.chain(*results_per_category))
372
+ results_2d = itertools.zip_longest(*[results_flatten[i::n_cols] for i in range(n_cols)])
373
+ table = tabulate(
374
+ results_2d,
375
+ tablefmt="pipe",
376
+ floatfmt=".3f",
377
+ headers=["category", "AP"] * (n_cols // 2),
378
+ numalign="left",
379
+ )
380
+ logger.info(f"Per-category {eval_mode_name} AP: \n" + table)
381
+
382
+ results.update({"AP-" + name: ap for name, ap in results_per_category})
383
+ return results
384
+
385
+
386
+ def build_densepose_evaluator_storage(cfg: CfgNode, output_folder: str):
387
+ storage_spec = cfg.DENSEPOSE_EVALUATION.STORAGE
388
+ if storage_spec == "none":
389
+ return None
390
+ evaluator_type = cfg.DENSEPOSE_EVALUATION.TYPE
391
+ # common output tensor sizes
392
+ hout = cfg.MODEL.ROI_DENSEPOSE_HEAD.HEATMAP_SIZE
393
+ wout = cfg.MODEL.ROI_DENSEPOSE_HEAD.HEATMAP_SIZE
394
+ n_csc = cfg.MODEL.ROI_DENSEPOSE_HEAD.NUM_COARSE_SEGM_CHANNELS
395
+ # specific output tensors
396
+ if evaluator_type == "iuv":
397
+ n_fsc = cfg.MODEL.ROI_DENSEPOSE_HEAD.NUM_PATCHES + 1
398
+ schema = {
399
+ "coarse_segm": SizeData(dtype="float32", shape=(n_csc, hout, wout)),
400
+ "fine_segm": SizeData(dtype="float32", shape=(n_fsc, hout, wout)),
401
+ "u": SizeData(dtype="float32", shape=(n_fsc, hout, wout)),
402
+ "v": SizeData(dtype="float32", shape=(n_fsc, hout, wout)),
403
+ }
404
+ elif evaluator_type == "cse":
405
+ embed_size = cfg.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_SIZE
406
+ schema = {
407
+ "coarse_segm": SizeData(dtype="float32", shape=(n_csc, hout, wout)),
408
+ "embedding": SizeData(dtype="float32", shape=(embed_size, hout, wout)),
409
+ }
410
+ else:
411
+ raise ValueError(f"Unknown evaluator type: {evaluator_type}")
412
+ # storage types
413
+ if storage_spec == "ram":
414
+ storage = SingleProcessRamTensorStorage(schema, io.BytesIO())
415
+ elif storage_spec == "file":
416
+ fpath = os.path.join(output_folder, f"DensePoseEvaluatorStorage.{get_rank()}.bin")
417
+ PathManager.mkdirs(output_folder)
418
+ storage = SingleProcessFileTensorStorage(schema, fpath, "wb")
419
+ else:
420
+ raise ValueError(f"Unknown storage specification: {storage_spec}")
421
+ return storage
densepose/evaluation/mesh_alignment_evaluator.py CHANGED
@@ -1,66 +1,66 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
-
3
- import json
4
- import logging
5
- from typing import List, Optional
6
- import torch
7
- from torch import nn
8
-
9
- from detectron2.utils.file_io import PathManager
10
-
11
- from densepose.structures.mesh import create_mesh
12
-
13
-
14
- class MeshAlignmentEvaluator:
15
- """
16
- Class for evaluation of 3D mesh alignment based on the learned vertex embeddings
17
- """
18
-
19
- def __init__(self, embedder: nn.Module, mesh_names: Optional[List[str]]):
20
- self.embedder = embedder
21
- # use the provided mesh names if not None and not an empty list
22
- self.mesh_names = mesh_names if mesh_names else embedder.mesh_names
23
- self.logger = logging.getLogger(__name__)
24
- with PathManager.open(
25
- "https://dl.fbaipublicfiles.com/densepose/data/cse/mesh_keyvertices_v0.json", "r"
26
- ) as f:
27
- self.mesh_keyvertices = json.load(f)
28
-
29
- def evaluate(self):
30
- ge_per_mesh = {}
31
- gps_per_mesh = {}
32
- for mesh_name_1 in self.mesh_names:
33
- avg_errors = []
34
- avg_gps = []
35
- embeddings_1 = self.embedder(mesh_name_1)
36
- keyvertices_1 = self.mesh_keyvertices[mesh_name_1]
37
- keyvertex_names_1 = list(keyvertices_1.keys())
38
- keyvertex_indices_1 = [keyvertices_1[name] for name in keyvertex_names_1]
39
- for mesh_name_2 in self.mesh_names:
40
- if mesh_name_1 == mesh_name_2:
41
- continue
42
- embeddings_2 = self.embedder(mesh_name_2)
43
- keyvertices_2 = self.mesh_keyvertices[mesh_name_2]
44
- sim_matrix_12 = embeddings_1[keyvertex_indices_1].mm(embeddings_2.T)
45
- vertices_2_matching_keyvertices_1 = sim_matrix_12.argmax(axis=1)
46
- mesh_2 = create_mesh(mesh_name_2, embeddings_2.device)
47
- geodists = mesh_2.geodists[
48
- vertices_2_matching_keyvertices_1,
49
- [keyvertices_2[name] for name in keyvertex_names_1],
50
- ]
51
- Current_Mean_Distances = 0.255
52
- gps = (-(geodists**2) / (2 * (Current_Mean_Distances**2))).exp()
53
- avg_errors.append(geodists.mean().item())
54
- avg_gps.append(gps.mean().item())
55
-
56
- ge_mean = torch.as_tensor(avg_errors).mean().item()
57
- gps_mean = torch.as_tensor(avg_gps).mean().item()
58
- ge_per_mesh[mesh_name_1] = ge_mean
59
- gps_per_mesh[mesh_name_1] = gps_mean
60
- ge_mean_global = torch.as_tensor(list(ge_per_mesh.values())).mean().item()
61
- gps_mean_global = torch.as_tensor(list(gps_per_mesh.values())).mean().item()
62
- per_mesh_metrics = {
63
- "GE": ge_per_mesh,
64
- "GPS": gps_per_mesh,
65
- }
66
- return ge_mean_global, gps_mean_global, per_mesh_metrics
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+
3
+ import json
4
+ import logging
5
+ from typing import List, Optional
6
+ import torch
7
+ from torch import nn
8
+
9
+ from detectron2.utils.file_io import PathManager
10
+
11
+ from densepose.structures.mesh import create_mesh
12
+
13
+
14
+ class MeshAlignmentEvaluator:
15
+ """
16
+ Class for evaluation of 3D mesh alignment based on the learned vertex embeddings
17
+ """
18
+
19
+ def __init__(self, embedder: nn.Module, mesh_names: Optional[List[str]]):
20
+ self.embedder = embedder
21
+ # use the provided mesh names if not None and not an empty list
22
+ self.mesh_names = mesh_names if mesh_names else embedder.mesh_names
23
+ self.logger = logging.getLogger(__name__)
24
+ with PathManager.open(
25
+ "https://dl.fbaipublicfiles.com/densepose/data/cse/mesh_keyvertices_v0.json", "r"
26
+ ) as f:
27
+ self.mesh_keyvertices = json.load(f)
28
+
29
+ def evaluate(self):
30
+ ge_per_mesh = {}
31
+ gps_per_mesh = {}
32
+ for mesh_name_1 in self.mesh_names:
33
+ avg_errors = []
34
+ avg_gps = []
35
+ embeddings_1 = self.embedder(mesh_name_1)
36
+ keyvertices_1 = self.mesh_keyvertices[mesh_name_1]
37
+ keyvertex_names_1 = list(keyvertices_1.keys())
38
+ keyvertex_indices_1 = [keyvertices_1[name] for name in keyvertex_names_1]
39
+ for mesh_name_2 in self.mesh_names:
40
+ if mesh_name_1 == mesh_name_2:
41
+ continue
42
+ embeddings_2 = self.embedder(mesh_name_2)
43
+ keyvertices_2 = self.mesh_keyvertices[mesh_name_2]
44
+ sim_matrix_12 = embeddings_1[keyvertex_indices_1].mm(embeddings_2.T)
45
+ vertices_2_matching_keyvertices_1 = sim_matrix_12.argmax(axis=1)
46
+ mesh_2 = create_mesh(mesh_name_2, embeddings_2.device)
47
+ geodists = mesh_2.geodists[
48
+ vertices_2_matching_keyvertices_1,
49
+ [keyvertices_2[name] for name in keyvertex_names_1],
50
+ ]
51
+ Current_Mean_Distances = 0.255
52
+ gps = (-(geodists**2) / (2 * (Current_Mean_Distances**2))).exp()
53
+ avg_errors.append(geodists.mean().item())
54
+ avg_gps.append(gps.mean().item())
55
+
56
+ ge_mean = torch.as_tensor(avg_errors).mean().item()
57
+ gps_mean = torch.as_tensor(avg_gps).mean().item()
58
+ ge_per_mesh[mesh_name_1] = ge_mean
59
+ gps_per_mesh[mesh_name_1] = gps_mean
60
+ ge_mean_global = torch.as_tensor(list(ge_per_mesh.values())).mean().item()
61
+ gps_mean_global = torch.as_tensor(list(gps_per_mesh.values())).mean().item()
62
+ per_mesh_metrics = {
63
+ "GE": ge_per_mesh,
64
+ "GPS": gps_per_mesh,
65
+ }
66
+ return ge_mean_global, gps_mean_global, per_mesh_metrics
densepose/evaluation/tensor_storage.py CHANGED
@@ -1,239 +1,239 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- import io
4
- import numpy as np
5
- import os
6
- from dataclasses import dataclass
7
- from functools import reduce
8
- from operator import mul
9
- from typing import BinaryIO, Dict, Optional, Tuple
10
- import torch
11
-
12
- from detectron2.utils.comm import gather, get_rank
13
- from detectron2.utils.file_io import PathManager
14
-
15
-
16
- @dataclass
17
- class SizeData:
18
- dtype: str
19
- shape: Tuple[int]
20
-
21
-
22
- def _calculate_record_field_size_b(data_schema: Dict[str, SizeData], field_name: str) -> int:
23
- schema = data_schema[field_name]
24
- element_size_b = np.dtype(schema.dtype).itemsize
25
- record_field_size_b = reduce(mul, schema.shape) * element_size_b
26
- return record_field_size_b
27
-
28
-
29
- def _calculate_record_size_b(data_schema: Dict[str, SizeData]) -> int:
30
- record_size_b = 0
31
- for field_name in data_schema:
32
- record_field_size_b = _calculate_record_field_size_b(data_schema, field_name)
33
- record_size_b += record_field_size_b
34
- return record_size_b
35
-
36
-
37
- def _calculate_record_field_sizes_b(data_schema: Dict[str, SizeData]) -> Dict[str, int]:
38
- field_sizes_b = {}
39
- for field_name in data_schema:
40
- field_sizes_b[field_name] = _calculate_record_field_size_b(data_schema, field_name)
41
- return field_sizes_b
42
-
43
-
44
- class SingleProcessTensorStorage:
45
- """
46
- Compact tensor storage to keep tensor data of predefined size and type.
47
- """
48
-
49
- def __init__(self, data_schema: Dict[str, SizeData], storage_impl: BinaryIO):
50
- """
51
- Construct tensor storage based on information on data shape and size.
52
- Internally uses numpy to interpret the type specification.
53
- The storage must support operations `seek(offset, whence=os.SEEK_SET)` and
54
- `read(size)` to be able to perform the `get` operation.
55
- The storage must support operation `write(bytes)` to be able to perform
56
- the `put` operation.
57
-
58
- Args:
59
- data_schema (dict: str -> SizeData): dictionary which maps tensor name
60
- to its size data (shape and data type), e.g.
61
- ```
62
- {
63
- "coarse_segm": SizeData(dtype="float32", shape=(112, 112)),
64
- "embedding": SizeData(dtype="float32", shape=(16, 112, 112)),
65
- }
66
- ```
67
- storage_impl (BinaryIO): io instance that handles file-like seek, read
68
- and write operations, e.g. a file handle or a memory buffer like io.BytesIO
69
- """
70
- self.data_schema = data_schema
71
- self.record_size_b = _calculate_record_size_b(data_schema)
72
- self.record_field_sizes_b = _calculate_record_field_sizes_b(data_schema)
73
- self.storage_impl = storage_impl
74
- self.next_record_id = 0
75
-
76
- def get(self, record_id: int) -> Dict[str, torch.Tensor]:
77
- """
78
- Load tensors from the storage by record ID
79
-
80
- Args:
81
- record_id (int): Record ID, for which to load the data
82
-
83
- Return:
84
- dict: str -> tensor: tensor name mapped to tensor data, recorded under the provided ID
85
- """
86
- self.storage_impl.seek(record_id * self.record_size_b, os.SEEK_SET)
87
- data_bytes = self.storage_impl.read(self.record_size_b)
88
- assert len(data_bytes) == self.record_size_b, (
89
- f"Expected data size {self.record_size_b} B could not be read: "
90
- f"got {len(data_bytes)} B"
91
- )
92
- record = {}
93
- cur_idx = 0
94
- # it's important to read and write in the same order
95
- for field_name in sorted(self.data_schema):
96
- schema = self.data_schema[field_name]
97
- field_size_b = self.record_field_sizes_b[field_name]
98
- chunk = data_bytes[cur_idx : cur_idx + field_size_b]
99
- data_np = np.frombuffer(
100
- chunk, dtype=schema.dtype, count=reduce(mul, schema.shape)
101
- ).reshape(schema.shape)
102
- record[field_name] = torch.from_numpy(data_np)
103
- cur_idx += field_size_b
104
- return record
105
-
106
- def put(self, data: Dict[str, torch.Tensor]) -> int:
107
- """
108
- Store tensors in the storage
109
-
110
- Args:
111
- data (dict: str -> tensor): data to store, a dictionary which maps
112
- tensor names into tensors; tensor shapes must match those specified
113
- in data schema.
114
- Return:
115
- int: record ID, under which the data is stored
116
- """
117
- # it's important to read and write in the same order
118
- for field_name in sorted(self.data_schema):
119
- assert (
120
- field_name in data
121
- ), f"Field '{field_name}' not present in data: data keys are {data.keys()}"
122
- value = data[field_name]
123
- assert value.shape == self.data_schema[field_name].shape, (
124
- f"Mismatched tensor shapes for field '{field_name}': "
125
- f"expected {self.data_schema[field_name].shape}, got {value.shape}"
126
- )
127
- data_bytes = value.cpu().numpy().tobytes()
128
- assert len(data_bytes) == self.record_field_sizes_b[field_name], (
129
- f"Expected field {field_name} to be of size "
130
- f"{self.record_field_sizes_b[field_name]} B, got {len(data_bytes)} B"
131
- )
132
- self.storage_impl.write(data_bytes)
133
- record_id = self.next_record_id
134
- self.next_record_id += 1
135
- return record_id
136
-
137
-
138
- class SingleProcessFileTensorStorage(SingleProcessTensorStorage):
139
- """
140
- Implementation of a single process tensor storage which stores data in a file
141
- """
142
-
143
- def __init__(self, data_schema: Dict[str, SizeData], fpath: str, mode: str):
144
- self.fpath = fpath
145
- assert "b" in mode, f"Tensor storage should be opened in binary mode, got '{mode}'"
146
- if "w" in mode:
147
- # pyre-fixme[6]: For 2nd argument expected `Union[typing_extensions.Liter...
148
- file_h = PathManager.open(fpath, mode)
149
- elif "r" in mode:
150
- local_fpath = PathManager.get_local_path(fpath)
151
- file_h = open(local_fpath, mode)
152
- else:
153
- raise ValueError(f"Unsupported file mode {mode}, supported modes: rb, wb")
154
- super().__init__(data_schema, file_h) # pyre-ignore[6]
155
-
156
-
157
- class SingleProcessRamTensorStorage(SingleProcessTensorStorage):
158
- """
159
- Implementation of a single process tensor storage which stores data in RAM
160
- """
161
-
162
- def __init__(self, data_schema: Dict[str, SizeData], buf: io.BytesIO):
163
- super().__init__(data_schema, buf)
164
-
165
-
166
- class MultiProcessTensorStorage:
167
- """
168
- Representation of a set of tensor storages created by individual processes,
169
- allows to access those storages from a single owner process. The storages
170
- should either be shared or broadcasted to the owner process.
171
- The processes are identified by their rank, data is uniquely defined by
172
- the rank of the process and the record ID.
173
- """
174
-
175
- def __init__(self, rank_to_storage: Dict[int, SingleProcessTensorStorage]):
176
- self.rank_to_storage = rank_to_storage
177
-
178
- def get(self, rank: int, record_id: int) -> Dict[str, torch.Tensor]:
179
- storage = self.rank_to_storage[rank]
180
- return storage.get(record_id)
181
-
182
- def put(self, rank: int, data: Dict[str, torch.Tensor]) -> int:
183
- storage = self.rank_to_storage[rank]
184
- return storage.put(data)
185
-
186
-
187
- class MultiProcessFileTensorStorage(MultiProcessTensorStorage):
188
- def __init__(self, data_schema: Dict[str, SizeData], rank_to_fpath: Dict[int, str], mode: str):
189
- rank_to_storage = {
190
- rank: SingleProcessFileTensorStorage(data_schema, fpath, mode)
191
- for rank, fpath in rank_to_fpath.items()
192
- }
193
- super().__init__(rank_to_storage) # pyre-ignore[6]
194
-
195
-
196
- class MultiProcessRamTensorStorage(MultiProcessTensorStorage):
197
- def __init__(self, data_schema: Dict[str, SizeData], rank_to_buffer: Dict[int, io.BytesIO]):
198
- rank_to_storage = {
199
- rank: SingleProcessRamTensorStorage(data_schema, buf)
200
- for rank, buf in rank_to_buffer.items()
201
- }
202
- super().__init__(rank_to_storage) # pyre-ignore[6]
203
-
204
-
205
- def _ram_storage_gather(
206
- storage: SingleProcessRamTensorStorage, dst_rank: int = 0
207
- ) -> Optional[MultiProcessRamTensorStorage]:
208
- storage.storage_impl.seek(0, os.SEEK_SET)
209
- # TODO: overhead, pickling a bytes object, can just pass bytes in a tensor directly
210
- # see detectron2/utils.comm.py
211
- data_list = gather(storage.storage_impl.read(), dst=dst_rank)
212
- if get_rank() != dst_rank:
213
- return None
214
- rank_to_buffer = {i: io.BytesIO(data_list[i]) for i in range(len(data_list))}
215
- multiprocess_storage = MultiProcessRamTensorStorage(storage.data_schema, rank_to_buffer)
216
- return multiprocess_storage
217
-
218
-
219
- def _file_storage_gather(
220
- storage: SingleProcessFileTensorStorage,
221
- dst_rank: int = 0,
222
- mode: str = "rb",
223
- ) -> Optional[MultiProcessFileTensorStorage]:
224
- storage.storage_impl.close()
225
- fpath_list = gather(storage.fpath, dst=dst_rank)
226
- if get_rank() != dst_rank:
227
- return None
228
- rank_to_fpath = {i: fpath_list[i] for i in range(len(fpath_list))}
229
- return MultiProcessFileTensorStorage(storage.data_schema, rank_to_fpath, mode)
230
-
231
-
232
- def storage_gather(
233
- storage: SingleProcessTensorStorage, dst_rank: int = 0
234
- ) -> Optional[MultiProcessTensorStorage]:
235
- if isinstance(storage, SingleProcessRamTensorStorage):
236
- return _ram_storage_gather(storage, dst_rank)
237
- elif isinstance(storage, SingleProcessFileTensorStorage):
238
- return _file_storage_gather(storage, dst_rank)
239
- raise Exception(f"Unsupported storage for gather operation: {storage}")
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ import io
4
+ import numpy as np
5
+ import os
6
+ from dataclasses import dataclass
7
+ from functools import reduce
8
+ from operator import mul
9
+ from typing import BinaryIO, Dict, Optional, Tuple
10
+ import torch
11
+
12
+ from detectron2.utils.comm import gather, get_rank
13
+ from detectron2.utils.file_io import PathManager
14
+
15
+
16
+ @dataclass
17
+ class SizeData:
18
+ dtype: str
19
+ shape: Tuple[int]
20
+
21
+
22
+ def _calculate_record_field_size_b(data_schema: Dict[str, SizeData], field_name: str) -> int:
23
+ schema = data_schema[field_name]
24
+ element_size_b = np.dtype(schema.dtype).itemsize
25
+ record_field_size_b = reduce(mul, schema.shape) * element_size_b
26
+ return record_field_size_b
27
+
28
+
29
+ def _calculate_record_size_b(data_schema: Dict[str, SizeData]) -> int:
30
+ record_size_b = 0
31
+ for field_name in data_schema:
32
+ record_field_size_b = _calculate_record_field_size_b(data_schema, field_name)
33
+ record_size_b += record_field_size_b
34
+ return record_size_b
35
+
36
+
37
+ def _calculate_record_field_sizes_b(data_schema: Dict[str, SizeData]) -> Dict[str, int]:
38
+ field_sizes_b = {}
39
+ for field_name in data_schema:
40
+ field_sizes_b[field_name] = _calculate_record_field_size_b(data_schema, field_name)
41
+ return field_sizes_b
42
+
43
+
44
+ class SingleProcessTensorStorage:
45
+ """
46
+ Compact tensor storage to keep tensor data of predefined size and type.
47
+ """
48
+
49
+ def __init__(self, data_schema: Dict[str, SizeData], storage_impl: BinaryIO):
50
+ """
51
+ Construct tensor storage based on information on data shape and size.
52
+ Internally uses numpy to interpret the type specification.
53
+ The storage must support operations `seek(offset, whence=os.SEEK_SET)` and
54
+ `read(size)` to be able to perform the `get` operation.
55
+ The storage must support operation `write(bytes)` to be able to perform
56
+ the `put` operation.
57
+
58
+ Args:
59
+ data_schema (dict: str -> SizeData): dictionary which maps tensor name
60
+ to its size data (shape and data type), e.g.
61
+ ```
62
+ {
63
+ "coarse_segm": SizeData(dtype="float32", shape=(112, 112)),
64
+ "embedding": SizeData(dtype="float32", shape=(16, 112, 112)),
65
+ }
66
+ ```
67
+ storage_impl (BinaryIO): io instance that handles file-like seek, read
68
+ and write operations, e.g. a file handle or a memory buffer like io.BytesIO
69
+ """
70
+ self.data_schema = data_schema
71
+ self.record_size_b = _calculate_record_size_b(data_schema)
72
+ self.record_field_sizes_b = _calculate_record_field_sizes_b(data_schema)
73
+ self.storage_impl = storage_impl
74
+ self.next_record_id = 0
75
+
76
+ def get(self, record_id: int) -> Dict[str, torch.Tensor]:
77
+ """
78
+ Load tensors from the storage by record ID
79
+
80
+ Args:
81
+ record_id (int): Record ID, for which to load the data
82
+
83
+ Return:
84
+ dict: str -> tensor: tensor name mapped to tensor data, recorded under the provided ID
85
+ """
86
+ self.storage_impl.seek(record_id * self.record_size_b, os.SEEK_SET)
87
+ data_bytes = self.storage_impl.read(self.record_size_b)
88
+ assert len(data_bytes) == self.record_size_b, (
89
+ f"Expected data size {self.record_size_b} B could not be read: "
90
+ f"got {len(data_bytes)} B"
91
+ )
92
+ record = {}
93
+ cur_idx = 0
94
+ # it's important to read and write in the same order
95
+ for field_name in sorted(self.data_schema):
96
+ schema = self.data_schema[field_name]
97
+ field_size_b = self.record_field_sizes_b[field_name]
98
+ chunk = data_bytes[cur_idx : cur_idx + field_size_b]
99
+ data_np = np.frombuffer(
100
+ chunk, dtype=schema.dtype, count=reduce(mul, schema.shape)
101
+ ).reshape(schema.shape)
102
+ record[field_name] = torch.from_numpy(data_np)
103
+ cur_idx += field_size_b
104
+ return record
105
+
106
+ def put(self, data: Dict[str, torch.Tensor]) -> int:
107
+ """
108
+ Store tensors in the storage
109
+
110
+ Args:
111
+ data (dict: str -> tensor): data to store, a dictionary which maps
112
+ tensor names into tensors; tensor shapes must match those specified
113
+ in data schema.
114
+ Return:
115
+ int: record ID, under which the data is stored
116
+ """
117
+ # it's important to read and write in the same order
118
+ for field_name in sorted(self.data_schema):
119
+ assert (
120
+ field_name in data
121
+ ), f"Field '{field_name}' not present in data: data keys are {data.keys()}"
122
+ value = data[field_name]
123
+ assert value.shape == self.data_schema[field_name].shape, (
124
+ f"Mismatched tensor shapes for field '{field_name}': "
125
+ f"expected {self.data_schema[field_name].shape}, got {value.shape}"
126
+ )
127
+ data_bytes = value.cpu().numpy().tobytes()
128
+ assert len(data_bytes) == self.record_field_sizes_b[field_name], (
129
+ f"Expected field {field_name} to be of size "
130
+ f"{self.record_field_sizes_b[field_name]} B, got {len(data_bytes)} B"
131
+ )
132
+ self.storage_impl.write(data_bytes)
133
+ record_id = self.next_record_id
134
+ self.next_record_id += 1
135
+ return record_id
136
+
137
+
138
+ class SingleProcessFileTensorStorage(SingleProcessTensorStorage):
139
+ """
140
+ Implementation of a single process tensor storage which stores data in a file
141
+ """
142
+
143
+ def __init__(self, data_schema: Dict[str, SizeData], fpath: str, mode: str):
144
+ self.fpath = fpath
145
+ assert "b" in mode, f"Tensor storage should be opened in binary mode, got '{mode}'"
146
+ if "w" in mode:
147
+ # pyre-fixme[6]: For 2nd argument expected `Union[typing_extensions.Liter...
148
+ file_h = PathManager.open(fpath, mode)
149
+ elif "r" in mode:
150
+ local_fpath = PathManager.get_local_path(fpath)
151
+ file_h = open(local_fpath, mode)
152
+ else:
153
+ raise ValueError(f"Unsupported file mode {mode}, supported modes: rb, wb")
154
+ super().__init__(data_schema, file_h) # pyre-ignore[6]
155
+
156
+
157
+ class SingleProcessRamTensorStorage(SingleProcessTensorStorage):
158
+ """
159
+ Implementation of a single process tensor storage which stores data in RAM
160
+ """
161
+
162
+ def __init__(self, data_schema: Dict[str, SizeData], buf: io.BytesIO):
163
+ super().__init__(data_schema, buf)
164
+
165
+
166
+ class MultiProcessTensorStorage:
167
+ """
168
+ Representation of a set of tensor storages created by individual processes,
169
+ allows to access those storages from a single owner process. The storages
170
+ should either be shared or broadcasted to the owner process.
171
+ The processes are identified by their rank, data is uniquely defined by
172
+ the rank of the process and the record ID.
173
+ """
174
+
175
+ def __init__(self, rank_to_storage: Dict[int, SingleProcessTensorStorage]):
176
+ self.rank_to_storage = rank_to_storage
177
+
178
+ def get(self, rank: int, record_id: int) -> Dict[str, torch.Tensor]:
179
+ storage = self.rank_to_storage[rank]
180
+ return storage.get(record_id)
181
+
182
+ def put(self, rank: int, data: Dict[str, torch.Tensor]) -> int:
183
+ storage = self.rank_to_storage[rank]
184
+ return storage.put(data)
185
+
186
+
187
+ class MultiProcessFileTensorStorage(MultiProcessTensorStorage):
188
+ def __init__(self, data_schema: Dict[str, SizeData], rank_to_fpath: Dict[int, str], mode: str):
189
+ rank_to_storage = {
190
+ rank: SingleProcessFileTensorStorage(data_schema, fpath, mode)
191
+ for rank, fpath in rank_to_fpath.items()
192
+ }
193
+ super().__init__(rank_to_storage) # pyre-ignore[6]
194
+
195
+
196
+ class MultiProcessRamTensorStorage(MultiProcessTensorStorage):
197
+ def __init__(self, data_schema: Dict[str, SizeData], rank_to_buffer: Dict[int, io.BytesIO]):
198
+ rank_to_storage = {
199
+ rank: SingleProcessRamTensorStorage(data_schema, buf)
200
+ for rank, buf in rank_to_buffer.items()
201
+ }
202
+ super().__init__(rank_to_storage) # pyre-ignore[6]
203
+
204
+
205
+ def _ram_storage_gather(
206
+ storage: SingleProcessRamTensorStorage, dst_rank: int = 0
207
+ ) -> Optional[MultiProcessRamTensorStorage]:
208
+ storage.storage_impl.seek(0, os.SEEK_SET)
209
+ # TODO: overhead, pickling a bytes object, can just pass bytes in a tensor directly
210
+ # see detectron2/utils.comm.py
211
+ data_list = gather(storage.storage_impl.read(), dst=dst_rank)
212
+ if get_rank() != dst_rank:
213
+ return None
214
+ rank_to_buffer = {i: io.BytesIO(data_list[i]) for i in range(len(data_list))}
215
+ multiprocess_storage = MultiProcessRamTensorStorage(storage.data_schema, rank_to_buffer)
216
+ return multiprocess_storage
217
+
218
+
219
+ def _file_storage_gather(
220
+ storage: SingleProcessFileTensorStorage,
221
+ dst_rank: int = 0,
222
+ mode: str = "rb",
223
+ ) -> Optional[MultiProcessFileTensorStorage]:
224
+ storage.storage_impl.close()
225
+ fpath_list = gather(storage.fpath, dst=dst_rank)
226
+ if get_rank() != dst_rank:
227
+ return None
228
+ rank_to_fpath = {i: fpath_list[i] for i in range(len(fpath_list))}
229
+ return MultiProcessFileTensorStorage(storage.data_schema, rank_to_fpath, mode)
230
+
231
+
232
+ def storage_gather(
233
+ storage: SingleProcessTensorStorage, dst_rank: int = 0
234
+ ) -> Optional[MultiProcessTensorStorage]:
235
+ if isinstance(storage, SingleProcessRamTensorStorage):
236
+ return _ram_storage_gather(storage, dst_rank)
237
+ elif isinstance(storage, SingleProcessFileTensorStorage):
238
+ return _file_storage_gather(storage, dst_rank)
239
+ raise Exception(f"Unsupported storage for gather operation: {storage}")
densepose/modeling/__init__.py CHANGED
@@ -1,13 +1,13 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
-
3
- from .confidence import DensePoseConfidenceModelConfig, DensePoseUVConfidenceType
4
- from .filter import DensePoseDataFilter
5
- from .inference import densepose_inference
6
- from .utils import initialize_module_params
7
- from .build import (
8
- build_densepose_data_filter,
9
- build_densepose_embedder,
10
- build_densepose_head,
11
- build_densepose_losses,
12
- build_densepose_predictor,
13
- )
 
1
+ # Copyright (c) Facebook, Inc. and its affiliates.
2
+
3
+ from .confidence import DensePoseConfidenceModelConfig, DensePoseUVConfidenceType
4
+ from .filter import DensePoseDataFilter
5
+ from .inference import densepose_inference
6
+ from .utils import initialize_module_params
7
+ from .build import (
8
+ build_densepose_data_filter,
9
+ build_densepose_embedder,
10
+ build_densepose_head,
11
+ build_densepose_losses,
12
+ build_densepose_predictor,
13
+ )