Add at new repo again
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
import logging
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
from detectron2.config import get_cfg
|
||||
from detectron2.layers import ShapeSpec
|
||||
from detectron2.modeling.anchor_generator import DefaultAnchorGenerator, RotatedAnchorGenerator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestAnchorGenerator(unittest.TestCase):
|
||||
def test_default_anchor_generator(self):
|
||||
cfg = get_cfg()
|
||||
cfg.MODEL.ANCHOR_GENERATOR.SIZES = [[32, 64]]
|
||||
cfg.MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS = [[0.25, 1, 4]]
|
||||
|
||||
anchor_generator = DefaultAnchorGenerator(cfg, [ShapeSpec(stride=4)])
|
||||
|
||||
# only the last two dimensions of features matter here
|
||||
num_images = 2
|
||||
features = {"stage3": torch.rand(num_images, 96, 1, 2)}
|
||||
anchors = anchor_generator([features["stage3"]])
|
||||
expected_anchor_tensor = torch.tensor(
|
||||
[
|
||||
[-32.0, -8.0, 32.0, 8.0],
|
||||
[-16.0, -16.0, 16.0, 16.0],
|
||||
[-8.0, -32.0, 8.0, 32.0],
|
||||
[-64.0, -16.0, 64.0, 16.0],
|
||||
[-32.0, -32.0, 32.0, 32.0],
|
||||
[-16.0, -64.0, 16.0, 64.0],
|
||||
[-28.0, -8.0, 36.0, 8.0], # -28.0 == -32.0 + STRIDE (4)
|
||||
[-12.0, -16.0, 20.0, 16.0],
|
||||
[-4.0, -32.0, 12.0, 32.0],
|
||||
[-60.0, -16.0, 68.0, 16.0],
|
||||
[-28.0, -32.0, 36.0, 32.0],
|
||||
[-12.0, -64.0, 20.0, 64.0],
|
||||
]
|
||||
)
|
||||
|
||||
assert torch.allclose(anchors[0].tensor, expected_anchor_tensor)
|
||||
|
||||
def test_default_anchor_generator_centered(self):
|
||||
# test explicit args
|
||||
anchor_generator = DefaultAnchorGenerator(
|
||||
sizes=[32, 64], aspect_ratios=[0.25, 1, 4], strides=[4]
|
||||
)
|
||||
|
||||
# only the last two dimensions of features matter here
|
||||
num_images = 2
|
||||
features = {"stage3": torch.rand(num_images, 96, 1, 2)}
|
||||
expected_anchor_tensor = torch.tensor(
|
||||
[
|
||||
[-30.0, -6.0, 34.0, 10.0],
|
||||
[-14.0, -14.0, 18.0, 18.0],
|
||||
[-6.0, -30.0, 10.0, 34.0],
|
||||
[-62.0, -14.0, 66.0, 18.0],
|
||||
[-30.0, -30.0, 34.0, 34.0],
|
||||
[-14.0, -62.0, 18.0, 66.0],
|
||||
[-26.0, -6.0, 38.0, 10.0],
|
||||
[-10.0, -14.0, 22.0, 18.0],
|
||||
[-2.0, -30.0, 14.0, 34.0],
|
||||
[-58.0, -14.0, 70.0, 18.0],
|
||||
[-26.0, -30.0, 38.0, 34.0],
|
||||
[-10.0, -62.0, 22.0, 66.0],
|
||||
]
|
||||
)
|
||||
|
||||
anchors = anchor_generator([features["stage3"]])
|
||||
assert torch.allclose(anchors[0].tensor, expected_anchor_tensor)
|
||||
|
||||
# doesn't work yet
|
||||
# anchors = torch.jit.script(anchor_generator)([features["stage3"]])
|
||||
# assert torch.allclose(anchors[0].tensor, expected_anchor_tensor)
|
||||
|
||||
def test_rrpn_anchor_generator(self):
|
||||
cfg = get_cfg()
|
||||
cfg.MODEL.ANCHOR_GENERATOR.SIZES = [[32, 64]]
|
||||
cfg.MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS = [[0.25, 1, 4]]
|
||||
cfg.MODEL.ANCHOR_GENERATOR.ANGLES = [0, 45] # test single list[float]
|
||||
anchor_generator = RotatedAnchorGenerator(cfg, [ShapeSpec(stride=4)])
|
||||
|
||||
# only the last two dimensions of features matter here
|
||||
num_images = 2
|
||||
features = {"stage3": torch.rand(num_images, 96, 1, 2)}
|
||||
anchors = anchor_generator([features["stage3"]])
|
||||
expected_anchor_tensor = torch.tensor(
|
||||
[
|
||||
[0.0, 0.0, 64.0, 16.0, 0.0],
|
||||
[0.0, 0.0, 64.0, 16.0, 45.0],
|
||||
[0.0, 0.0, 32.0, 32.0, 0.0],
|
||||
[0.0, 0.0, 32.0, 32.0, 45.0],
|
||||
[0.0, 0.0, 16.0, 64.0, 0.0],
|
||||
[0.0, 0.0, 16.0, 64.0, 45.0],
|
||||
[0.0, 0.0, 128.0, 32.0, 0.0],
|
||||
[0.0, 0.0, 128.0, 32.0, 45.0],
|
||||
[0.0, 0.0, 64.0, 64.0, 0.0],
|
||||
[0.0, 0.0, 64.0, 64.0, 45.0],
|
||||
[0.0, 0.0, 32.0, 128.0, 0.0],
|
||||
[0.0, 0.0, 32.0, 128.0, 45.0],
|
||||
[4.0, 0.0, 64.0, 16.0, 0.0], # 4.0 == 0.0 + STRIDE (4)
|
||||
[4.0, 0.0, 64.0, 16.0, 45.0],
|
||||
[4.0, 0.0, 32.0, 32.0, 0.0],
|
||||
[4.0, 0.0, 32.0, 32.0, 45.0],
|
||||
[4.0, 0.0, 16.0, 64.0, 0.0],
|
||||
[4.0, 0.0, 16.0, 64.0, 45.0],
|
||||
[4.0, 0.0, 128.0, 32.0, 0.0],
|
||||
[4.0, 0.0, 128.0, 32.0, 45.0],
|
||||
[4.0, 0.0, 64.0, 64.0, 0.0],
|
||||
[4.0, 0.0, 64.0, 64.0, 45.0],
|
||||
[4.0, 0.0, 32.0, 128.0, 0.0],
|
||||
[4.0, 0.0, 32.0, 128.0, 45.0],
|
||||
]
|
||||
)
|
||||
|
||||
assert torch.allclose(anchors[0].tensor, expected_anchor_tensor)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
import logging
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
from detectron2.modeling.box_regression import Box2BoxTransform, Box2BoxTransformRotated
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def random_boxes(mean_box, stdev, N):
|
||||
return torch.rand(N, 4) * stdev + torch.tensor(mean_box, dtype=torch.float)
|
||||
|
||||
|
||||
class TestBox2BoxTransform(unittest.TestCase):
|
||||
def test_reconstruction(self):
|
||||
weights = (5, 5, 10, 10)
|
||||
b2b_tfm = Box2BoxTransform(weights=weights)
|
||||
src_boxes = random_boxes([10, 10, 20, 20], 1, 10)
|
||||
dst_boxes = random_boxes([10, 10, 20, 20], 1, 10)
|
||||
|
||||
devices = [torch.device("cpu")]
|
||||
if torch.cuda.is_available():
|
||||
devices.append(torch.device("cuda"))
|
||||
for device in devices:
|
||||
src_boxes = src_boxes.to(device=device)
|
||||
dst_boxes = dst_boxes.to(device=device)
|
||||
deltas = b2b_tfm.get_deltas(src_boxes, dst_boxes)
|
||||
dst_boxes_reconstructed = b2b_tfm.apply_deltas(deltas, src_boxes)
|
||||
assert torch.allclose(dst_boxes, dst_boxes_reconstructed)
|
||||
|
||||
|
||||
def random_rotated_boxes(mean_box, std_length, std_angle, N):
|
||||
return torch.cat(
|
||||
[torch.rand(N, 4) * std_length, torch.rand(N, 1) * std_angle], dim=1
|
||||
) + torch.tensor(mean_box, dtype=torch.float)
|
||||
|
||||
|
||||
class TestBox2BoxTransformRotated(unittest.TestCase):
|
||||
def test_reconstruction(self):
|
||||
weights = (5, 5, 10, 10, 1)
|
||||
b2b_transform = Box2BoxTransformRotated(weights=weights)
|
||||
src_boxes = random_rotated_boxes([10, 10, 20, 20, -30], 5, 60.0, 10)
|
||||
dst_boxes = random_rotated_boxes([10, 10, 20, 20, -30], 5, 60.0, 10)
|
||||
|
||||
devices = [torch.device("cpu")]
|
||||
if torch.cuda.is_available():
|
||||
devices.append(torch.device("cuda"))
|
||||
for device in devices:
|
||||
src_boxes = src_boxes.to(device=device)
|
||||
dst_boxes = dst_boxes.to(device=device)
|
||||
deltas = b2b_transform.get_deltas(src_boxes, dst_boxes)
|
||||
dst_boxes_reconstructed = b2b_transform.apply_deltas(deltas, src_boxes)
|
||||
assert torch.allclose(dst_boxes[:, :4], dst_boxes_reconstructed[:, :4], atol=1e-5)
|
||||
# angle difference has to be normalized
|
||||
assert torch.allclose(
|
||||
(dst_boxes[:, 4] - dst_boxes_reconstructed[:, 4] + 180.0) % 360.0 - 180.0,
|
||||
torch.zeros_like(dst_boxes[:, 4]),
|
||||
atol=1e-4,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@@ -0,0 +1,106 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
import logging
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
from detectron2.layers import ShapeSpec
|
||||
from detectron2.modeling.box_regression import Box2BoxTransform, Box2BoxTransformRotated
|
||||
from detectron2.modeling.roi_heads.fast_rcnn import FastRCNNOutputLayers
|
||||
from detectron2.modeling.roi_heads.rotated_fast_rcnn import RotatedFastRCNNOutputLayers
|
||||
from detectron2.structures import Boxes, Instances, RotatedBoxes
|
||||
from detectron2.utils.events import EventStorage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FastRCNNTest(unittest.TestCase):
|
||||
def test_fast_rcnn(self):
|
||||
torch.manual_seed(132)
|
||||
|
||||
box_head_output_size = 8
|
||||
|
||||
box_predictor = FastRCNNOutputLayers(
|
||||
ShapeSpec(channels=box_head_output_size),
|
||||
box2box_transform=Box2BoxTransform(weights=(10, 10, 5, 5)),
|
||||
num_classes=5,
|
||||
)
|
||||
feature_pooled = torch.rand(2, box_head_output_size)
|
||||
predictions = box_predictor(feature_pooled)
|
||||
|
||||
proposal_boxes = torch.tensor([[0.8, 1.1, 3.2, 2.8], [2.3, 2.5, 7, 8]], dtype=torch.float32)
|
||||
gt_boxes = torch.tensor([[1, 1, 3, 3], [2, 2, 6, 6]], dtype=torch.float32)
|
||||
proposal = Instances((10, 10))
|
||||
proposal.proposal_boxes = Boxes(proposal_boxes)
|
||||
proposal.gt_boxes = Boxes(gt_boxes)
|
||||
proposal.gt_classes = torch.tensor([1, 2])
|
||||
|
||||
with EventStorage(): # capture events in a new storage to discard them
|
||||
losses = box_predictor.losses(predictions, [proposal])
|
||||
|
||||
expected_losses = {
|
||||
"loss_cls": torch.tensor(1.7951188087),
|
||||
"loss_box_reg": torch.tensor(4.0357131958),
|
||||
}
|
||||
for name in expected_losses.keys():
|
||||
assert torch.allclose(losses[name], expected_losses[name])
|
||||
|
||||
def test_fast_rcnn_empty_batch(self, device="cpu"):
|
||||
box_predictor = FastRCNNOutputLayers(
|
||||
ShapeSpec(channels=10),
|
||||
box2box_transform=Box2BoxTransform(weights=(10, 10, 5, 5)),
|
||||
num_classes=8,
|
||||
).to(device=device)
|
||||
|
||||
logits = torch.randn(0, 100, requires_grad=True, device=device)
|
||||
deltas = torch.randn(0, 4, requires_grad=True, device=device)
|
||||
losses = box_predictor.losses([logits, deltas], [])
|
||||
for value in losses.values():
|
||||
self.assertTrue(torch.allclose(value, torch.zeros_like(value)))
|
||||
sum(losses.values()).backward()
|
||||
self.assertTrue(logits.grad is not None)
|
||||
self.assertTrue(deltas.grad is not None)
|
||||
|
||||
predictions, _ = box_predictor.inference([logits, deltas], [])
|
||||
self.assertEqual(len(predictions), 0)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "CUDA not available")
|
||||
def test_fast_rcnn_empty_batch_cuda(self):
|
||||
self.test_fast_rcnn_empty_batch(device=torch.device("cuda"))
|
||||
|
||||
def test_fast_rcnn_rotated(self):
|
||||
torch.manual_seed(132)
|
||||
box_head_output_size = 8
|
||||
|
||||
box_predictor = RotatedFastRCNNOutputLayers(
|
||||
ShapeSpec(channels=box_head_output_size),
|
||||
box2box_transform=Box2BoxTransformRotated(weights=(10, 10, 5, 5, 1)),
|
||||
num_classes=5,
|
||||
)
|
||||
feature_pooled = torch.rand(2, box_head_output_size)
|
||||
predictions = box_predictor(feature_pooled)
|
||||
proposal_boxes = torch.tensor(
|
||||
[[2, 1.95, 2.4, 1.7, 0], [4.65, 5.25, 4.7, 5.5, 0]], dtype=torch.float32
|
||||
)
|
||||
gt_boxes = torch.tensor([[2, 2, 2, 2, 0], [4, 4, 4, 4, 0]], dtype=torch.float32)
|
||||
proposal = Instances((10, 10))
|
||||
proposal.proposal_boxes = RotatedBoxes(proposal_boxes)
|
||||
proposal.gt_boxes = RotatedBoxes(gt_boxes)
|
||||
proposal.gt_classes = torch.tensor([1, 2])
|
||||
|
||||
with EventStorage(): # capture events in a new storage to discard them
|
||||
losses = box_predictor.losses(predictions, [proposal])
|
||||
|
||||
# Note: the expected losses are slightly different even if
|
||||
# the boxes are essentially the same as in the FastRCNNOutput test, because
|
||||
# bbox_pred in FastRCNNOutputLayers have different Linear layers/initialization
|
||||
# between the two cases.
|
||||
expected_losses = {
|
||||
"loss_cls": torch.tensor(1.7920907736),
|
||||
"loss_box_reg": torch.tensor(4.0410838127),
|
||||
}
|
||||
for name in expected_losses.keys():
|
||||
assert torch.allclose(losses[name], expected_losses[name])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@@ -0,0 +1,154 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
import detectron2.model_zoo as model_zoo
|
||||
from detectron2.config import get_cfg
|
||||
from detectron2.modeling import build_model
|
||||
from detectron2.structures import BitMasks, Boxes, ImageList, Instances
|
||||
from detectron2.utils.events import EventStorage
|
||||
|
||||
|
||||
def get_model_zoo(config_path):
|
||||
"""
|
||||
Like model_zoo.get, but do not load any weights (even pretrained)
|
||||
"""
|
||||
cfg_file = model_zoo.get_config_file(config_path)
|
||||
cfg = get_cfg()
|
||||
cfg.merge_from_file(cfg_file)
|
||||
if not torch.cuda.is_available():
|
||||
cfg.MODEL.DEVICE = "cpu"
|
||||
return build_model(cfg)
|
||||
|
||||
|
||||
def create_model_input(img, inst=None):
|
||||
if inst is not None:
|
||||
return {"image": img, "instances": inst}
|
||||
else:
|
||||
return {"image": img}
|
||||
|
||||
|
||||
def get_empty_instance(h, w):
|
||||
inst = Instances((h, w))
|
||||
inst.gt_boxes = Boxes(torch.rand(0, 4))
|
||||
inst.gt_classes = torch.tensor([]).to(dtype=torch.int64)
|
||||
inst.gt_masks = BitMasks(torch.rand(0, h, w))
|
||||
return inst
|
||||
|
||||
|
||||
def get_regular_bitmask_instances(h, w):
|
||||
inst = Instances((h, w))
|
||||
inst.gt_boxes = Boxes(torch.rand(3, 4))
|
||||
inst.gt_boxes.tensor[:, 2:] += inst.gt_boxes.tensor[:, :2]
|
||||
inst.gt_classes = torch.tensor([3, 4, 5]).to(dtype=torch.int64)
|
||||
inst.gt_masks = BitMasks((torch.rand(3, h, w) > 0.5))
|
||||
return inst
|
||||
|
||||
|
||||
class ModelE2ETest:
|
||||
def setUp(self):
|
||||
torch.manual_seed(43)
|
||||
self.model = get_model_zoo(self.CONFIG_PATH)
|
||||
|
||||
def _test_eval(self, input_sizes):
|
||||
inputs = [create_model_input(torch.rand(3, s[0], s[1])) for s in input_sizes]
|
||||
self.model.eval()
|
||||
self.model(inputs)
|
||||
|
||||
def _test_train(self, input_sizes, instances):
|
||||
assert len(input_sizes) == len(instances)
|
||||
inputs = [
|
||||
create_model_input(torch.rand(3, s[0], s[1]), inst)
|
||||
for s, inst in zip(input_sizes, instances)
|
||||
]
|
||||
self.model.train()
|
||||
with EventStorage():
|
||||
losses = self.model(inputs)
|
||||
sum(losses.values()).backward()
|
||||
del losses
|
||||
|
||||
def _inf_tensor(self, *shape):
|
||||
return 1.0 / torch.zeros(*shape, device=self.model.device)
|
||||
|
||||
def _nan_tensor(self, *shape):
|
||||
return torch.zeros(*shape, device=self.model.device).fill_(float("nan"))
|
||||
|
||||
def test_empty_data(self):
|
||||
instances = [get_empty_instance(200, 250), get_empty_instance(200, 249)]
|
||||
self._test_eval([(200, 250), (200, 249)])
|
||||
self._test_train([(200, 250), (200, 249)], instances)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "CUDA unavailable")
|
||||
def test_eval_tocpu(self):
|
||||
model = get_model_zoo(self.CONFIG_PATH).cpu()
|
||||
model.eval()
|
||||
input_sizes = [(200, 250), (200, 249)]
|
||||
inputs = [create_model_input(torch.rand(3, s[0], s[1])) for s in input_sizes]
|
||||
model(inputs)
|
||||
|
||||
|
||||
class MaskRCNNE2ETest(ModelE2ETest, unittest.TestCase):
|
||||
CONFIG_PATH = "COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml"
|
||||
|
||||
def test_half_empty_data(self):
|
||||
instances = [get_empty_instance(200, 250), get_regular_bitmask_instances(200, 249)]
|
||||
self._test_train([(200, 250), (200, 249)], instances)
|
||||
|
||||
# This test is flaky because in some environment the output features are zero due to relu
|
||||
# def test_rpn_inf_nan_data(self):
|
||||
# self.model.eval()
|
||||
# for tensor in [self._inf_tensor, self._nan_tensor]:
|
||||
# images = ImageList(tensor(1, 3, 512, 512), [(510, 510)])
|
||||
# features = {
|
||||
# "p2": tensor(1, 256, 256, 256),
|
||||
# "p3": tensor(1, 256, 128, 128),
|
||||
# "p4": tensor(1, 256, 64, 64),
|
||||
# "p5": tensor(1, 256, 32, 32),
|
||||
# "p6": tensor(1, 256, 16, 16),
|
||||
# }
|
||||
# props, _ = self.model.proposal_generator(images, features)
|
||||
# self.assertEqual(len(props[0]), 0)
|
||||
|
||||
def test_roiheads_inf_nan_data(self):
|
||||
self.model.eval()
|
||||
for tensor in [self._inf_tensor, self._nan_tensor]:
|
||||
images = ImageList(tensor(1, 3, 512, 512), [(510, 510)])
|
||||
features = {
|
||||
"p2": tensor(1, 256, 256, 256),
|
||||
"p3": tensor(1, 256, 128, 128),
|
||||
"p4": tensor(1, 256, 64, 64),
|
||||
"p5": tensor(1, 256, 32, 32),
|
||||
"p6": tensor(1, 256, 16, 16),
|
||||
}
|
||||
props = [Instances((510, 510))]
|
||||
props[0].proposal_boxes = Boxes([[10, 10, 20, 20]]).to(device=self.model.device)
|
||||
props[0].objectness_logits = torch.tensor([1.0]).reshape(1, 1)
|
||||
det, _ = self.model.roi_heads(images, features, props)
|
||||
self.assertEqual(len(det[0]), 0)
|
||||
|
||||
|
||||
class RetinaNetE2ETest(ModelE2ETest, unittest.TestCase):
|
||||
CONFIG_PATH = "COCO-Detection/retinanet_R_50_FPN_1x.yaml"
|
||||
|
||||
def test_inf_nan_data(self):
|
||||
self.model.eval()
|
||||
self.model.score_threshold = -999999999
|
||||
for tensor in [self._inf_tensor, self._nan_tensor]:
|
||||
images = ImageList(tensor(1, 3, 512, 512), [(510, 510)])
|
||||
features = [
|
||||
tensor(1, 256, 128, 128),
|
||||
tensor(1, 256, 64, 64),
|
||||
tensor(1, 256, 32, 32),
|
||||
tensor(1, 256, 16, 16),
|
||||
tensor(1, 256, 8, 8),
|
||||
]
|
||||
anchors = self.model.anchor_generator(features)
|
||||
box_cls, box_delta = self.model.head(features)
|
||||
box_cls = [tensor(*k.shape) for k in box_cls]
|
||||
box_delta = [tensor(*k.shape) for k in box_delta]
|
||||
det = self.model.inference(box_cls, box_delta, anchors, images.image_sizes)
|
||||
# all predictions (if any) are infinite or nan
|
||||
if len(det[0]):
|
||||
self.assertTrue(torch.isfinite(det[0].pred_boxes.tensor).sum() == 0)
|
@@ -0,0 +1,108 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
import logging
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
from detectron2.config import get_cfg
|
||||
from detectron2.modeling.backbone import build_backbone
|
||||
from detectron2.modeling.proposal_generator.build import build_proposal_generator
|
||||
from detectron2.modeling.roi_heads import build_roi_heads
|
||||
from detectron2.structures import Boxes, ImageList, Instances, RotatedBoxes
|
||||
from detectron2.utils.events import EventStorage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ROIHeadsTest(unittest.TestCase):
|
||||
def test_roi_heads(self):
|
||||
torch.manual_seed(121)
|
||||
cfg = get_cfg()
|
||||
cfg.MODEL.ROI_HEADS.NAME = "StandardROIHeads"
|
||||
cfg.MODEL.ROI_BOX_HEAD.NAME = "FastRCNNConvFCHead"
|
||||
cfg.MODEL.ROI_BOX_HEAD.NUM_FC = 2
|
||||
cfg.MODEL.ROI_BOX_HEAD.POOLER_TYPE = "ROIAlignV2"
|
||||
cfg.MODEL.ROI_BOX_HEAD.BBOX_REG_WEIGHTS = (10, 10, 5, 5)
|
||||
backbone = build_backbone(cfg)
|
||||
num_images = 2
|
||||
images_tensor = torch.rand(num_images, 20, 30)
|
||||
image_sizes = [(10, 10), (20, 30)]
|
||||
images = ImageList(images_tensor, image_sizes)
|
||||
num_channels = 1024
|
||||
features = {"res4": torch.rand(num_images, num_channels, 1, 2)}
|
||||
|
||||
image_shape = (15, 15)
|
||||
gt_boxes0 = torch.tensor([[1, 1, 3, 3], [2, 2, 6, 6]], dtype=torch.float32)
|
||||
gt_instance0 = Instances(image_shape)
|
||||
gt_instance0.gt_boxes = Boxes(gt_boxes0)
|
||||
gt_instance0.gt_classes = torch.tensor([2, 1])
|
||||
gt_boxes1 = torch.tensor([[1, 5, 2, 8], [7, 3, 10, 5]], dtype=torch.float32)
|
||||
gt_instance1 = Instances(image_shape)
|
||||
gt_instance1.gt_boxes = Boxes(gt_boxes1)
|
||||
gt_instance1.gt_classes = torch.tensor([1, 2])
|
||||
gt_instances = [gt_instance0, gt_instance1]
|
||||
|
||||
proposal_generator = build_proposal_generator(cfg, backbone.output_shape())
|
||||
roi_heads = build_roi_heads(cfg, backbone.output_shape())
|
||||
|
||||
with EventStorage(): # capture events in a new storage to discard them
|
||||
proposals, proposal_losses = proposal_generator(images, features, gt_instances)
|
||||
_, detector_losses = roi_heads(images, features, proposals, gt_instances)
|
||||
|
||||
expected_losses = {
|
||||
"loss_cls": torch.tensor(4.4236516953),
|
||||
"loss_box_reg": torch.tensor(0.0091214813),
|
||||
}
|
||||
for name in expected_losses.keys():
|
||||
self.assertTrue(torch.allclose(detector_losses[name], expected_losses[name]))
|
||||
|
||||
def test_rroi_heads(self):
|
||||
torch.manual_seed(121)
|
||||
cfg = get_cfg()
|
||||
cfg.MODEL.PROPOSAL_GENERATOR.NAME = "RRPN"
|
||||
cfg.MODEL.ANCHOR_GENERATOR.NAME = "RotatedAnchorGenerator"
|
||||
cfg.MODEL.ROI_HEADS.NAME = "RROIHeads"
|
||||
cfg.MODEL.ROI_BOX_HEAD.NAME = "FastRCNNConvFCHead"
|
||||
cfg.MODEL.ROI_BOX_HEAD.NUM_FC = 2
|
||||
cfg.MODEL.RPN.BBOX_REG_WEIGHTS = (1, 1, 1, 1, 1)
|
||||
cfg.MODEL.RPN.HEAD_NAME = "StandardRPNHead"
|
||||
cfg.MODEL.ROI_BOX_HEAD.POOLER_TYPE = "ROIAlignRotated"
|
||||
cfg.MODEL.ROI_BOX_HEAD.BBOX_REG_WEIGHTS = (10, 10, 5, 5, 1)
|
||||
backbone = build_backbone(cfg)
|
||||
num_images = 2
|
||||
images_tensor = torch.rand(num_images, 20, 30)
|
||||
image_sizes = [(10, 10), (20, 30)]
|
||||
images = ImageList(images_tensor, image_sizes)
|
||||
num_channels = 1024
|
||||
features = {"res4": torch.rand(num_images, num_channels, 1, 2)}
|
||||
|
||||
image_shape = (15, 15)
|
||||
gt_boxes0 = torch.tensor([[2, 2, 2, 2, 30], [4, 4, 4, 4, 0]], dtype=torch.float32)
|
||||
gt_instance0 = Instances(image_shape)
|
||||
gt_instance0.gt_boxes = RotatedBoxes(gt_boxes0)
|
||||
gt_instance0.gt_classes = torch.tensor([2, 1])
|
||||
gt_boxes1 = torch.tensor([[1.5, 5.5, 1, 3, 0], [8.5, 4, 3, 2, -50]], dtype=torch.float32)
|
||||
gt_instance1 = Instances(image_shape)
|
||||
gt_instance1.gt_boxes = RotatedBoxes(gt_boxes1)
|
||||
gt_instance1.gt_classes = torch.tensor([1, 2])
|
||||
gt_instances = [gt_instance0, gt_instance1]
|
||||
|
||||
proposal_generator = build_proposal_generator(cfg, backbone.output_shape())
|
||||
roi_heads = build_roi_heads(cfg, backbone.output_shape())
|
||||
|
||||
with EventStorage(): # capture events in a new storage to discard them
|
||||
proposals, proposal_losses = proposal_generator(images, features, gt_instances)
|
||||
_, detector_losses = roi_heads(images, features, proposals, gt_instances)
|
||||
|
||||
expected_losses = {
|
||||
"loss_cls": torch.tensor(4.381618499755859),
|
||||
"loss_box_reg": torch.tensor(0.0011829272843897343),
|
||||
}
|
||||
for name in expected_losses.keys():
|
||||
err_msg = "detector_losses[{}] = {}, expected losses = {}".format(
|
||||
name, detector_losses[name], expected_losses[name]
|
||||
)
|
||||
self.assertTrue(torch.allclose(detector_losses[name], expected_losses[name]), err_msg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@@ -0,0 +1,85 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
import logging
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
from detectron2.modeling.poolers import ROIPooler
|
||||
from detectron2.structures import Boxes, RotatedBoxes
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestROIPooler(unittest.TestCase):
|
||||
def _rand_boxes(self, num_boxes, x_max, y_max):
|
||||
coords = torch.rand(num_boxes, 4)
|
||||
coords[:, 0] *= x_max
|
||||
coords[:, 1] *= y_max
|
||||
coords[:, 2] *= x_max
|
||||
coords[:, 3] *= y_max
|
||||
boxes = torch.zeros(num_boxes, 4)
|
||||
boxes[:, 0] = torch.min(coords[:, 0], coords[:, 2])
|
||||
boxes[:, 1] = torch.min(coords[:, 1], coords[:, 3])
|
||||
boxes[:, 2] = torch.max(coords[:, 0], coords[:, 2])
|
||||
boxes[:, 3] = torch.max(coords[:, 1], coords[:, 3])
|
||||
return boxes
|
||||
|
||||
def _test_roialignv2_roialignrotated_match(self, device):
|
||||
pooler_resolution = 14
|
||||
canonical_level = 4
|
||||
canonical_scale_factor = 2 ** canonical_level
|
||||
pooler_scales = (1.0 / canonical_scale_factor,)
|
||||
sampling_ratio = 0
|
||||
|
||||
N, C, H, W = 2, 4, 10, 8
|
||||
N_rois = 10
|
||||
std = 11
|
||||
mean = 0
|
||||
feature = (torch.rand(N, C, H, W) - 0.5) * 2 * std + mean
|
||||
|
||||
features = [feature.to(device)]
|
||||
|
||||
rois = []
|
||||
rois_rotated = []
|
||||
for _ in range(N):
|
||||
boxes = self._rand_boxes(
|
||||
num_boxes=N_rois, x_max=W * canonical_scale_factor, y_max=H * canonical_scale_factor
|
||||
)
|
||||
|
||||
rotated_boxes = torch.zeros(N_rois, 5)
|
||||
rotated_boxes[:, 0] = (boxes[:, 0] + boxes[:, 2]) / 2.0
|
||||
rotated_boxes[:, 1] = (boxes[:, 1] + boxes[:, 3]) / 2.0
|
||||
rotated_boxes[:, 2] = boxes[:, 2] - boxes[:, 0]
|
||||
rotated_boxes[:, 3] = boxes[:, 3] - boxes[:, 1]
|
||||
rois.append(Boxes(boxes).to(device))
|
||||
rois_rotated.append(RotatedBoxes(rotated_boxes).to(device))
|
||||
|
||||
roialignv2_pooler = ROIPooler(
|
||||
output_size=pooler_resolution,
|
||||
scales=pooler_scales,
|
||||
sampling_ratio=sampling_ratio,
|
||||
pooler_type="ROIAlignV2",
|
||||
)
|
||||
|
||||
roialignv2_out = roialignv2_pooler(features, rois)
|
||||
|
||||
roialignrotated_pooler = ROIPooler(
|
||||
output_size=pooler_resolution,
|
||||
scales=pooler_scales,
|
||||
sampling_ratio=sampling_ratio,
|
||||
pooler_type="ROIAlignRotated",
|
||||
)
|
||||
|
||||
roialignrotated_out = roialignrotated_pooler(features, rois_rotated)
|
||||
|
||||
self.assertTrue(torch.allclose(roialignv2_out, roialignrotated_out, atol=1e-4))
|
||||
|
||||
def test_roialignv2_roialignrotated_match_cpu(self):
|
||||
self._test_roialignv2_roialignrotated_match(device="cpu")
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "CUDA not available")
|
||||
def test_roialignv2_roialignrotated_match_cuda(self):
|
||||
self._test_roialignv2_roialignrotated_match(device="cuda")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@@ -0,0 +1,234 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
import logging
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
from detectron2.config import get_cfg
|
||||
from detectron2.modeling.backbone import build_backbone
|
||||
from detectron2.modeling.proposal_generator.build import build_proposal_generator
|
||||
from detectron2.modeling.proposal_generator.rpn_outputs import find_top_rpn_proposals
|
||||
from detectron2.structures import Boxes, ImageList, Instances, RotatedBoxes
|
||||
from detectron2.utils.events import EventStorage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RPNTest(unittest.TestCase):
|
||||
def test_rpn(self):
|
||||
torch.manual_seed(121)
|
||||
cfg = get_cfg()
|
||||
cfg.MODEL.PROPOSAL_GENERATOR.NAME = "RPN"
|
||||
cfg.MODEL.ANCHOR_GENERATOR.NAME = "DefaultAnchorGenerator"
|
||||
cfg.MODEL.RPN.BBOX_REG_WEIGHTS = (1, 1, 1, 1)
|
||||
backbone = build_backbone(cfg)
|
||||
proposal_generator = build_proposal_generator(cfg, backbone.output_shape())
|
||||
num_images = 2
|
||||
images_tensor = torch.rand(num_images, 20, 30)
|
||||
image_sizes = [(10, 10), (20, 30)]
|
||||
images = ImageList(images_tensor, image_sizes)
|
||||
image_shape = (15, 15)
|
||||
num_channels = 1024
|
||||
features = {"res4": torch.rand(num_images, num_channels, 1, 2)}
|
||||
gt_boxes = torch.tensor([[1, 1, 3, 3], [2, 2, 6, 6]], dtype=torch.float32)
|
||||
gt_instances = Instances(image_shape)
|
||||
gt_instances.gt_boxes = Boxes(gt_boxes)
|
||||
with EventStorage(): # capture events in a new storage to discard them
|
||||
proposals, proposal_losses = proposal_generator(
|
||||
images, features, [gt_instances[0], gt_instances[1]]
|
||||
)
|
||||
|
||||
expected_losses = {
|
||||
"loss_rpn_cls": torch.tensor(0.0804563984),
|
||||
"loss_rpn_loc": torch.tensor(0.0990132466),
|
||||
}
|
||||
for name in expected_losses.keys():
|
||||
err_msg = "proposal_losses[{}] = {}, expected losses = {}".format(
|
||||
name, proposal_losses[name], expected_losses[name]
|
||||
)
|
||||
self.assertTrue(torch.allclose(proposal_losses[name], expected_losses[name]), err_msg)
|
||||
|
||||
expected_proposal_boxes = [
|
||||
Boxes(torch.tensor([[0, 0, 10, 10], [7.3365392685, 0, 10, 10]])),
|
||||
Boxes(
|
||||
torch.tensor(
|
||||
[
|
||||
[0, 0, 30, 20],
|
||||
[0, 0, 16.7862777710, 13.1362524033],
|
||||
[0, 0, 30, 13.3173446655],
|
||||
[0, 0, 10.8602609634, 20],
|
||||
[7.7165775299, 0, 27.3875980377, 20],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
expected_objectness_logits = [
|
||||
torch.tensor([0.1225359365, -0.0133192837]),
|
||||
torch.tensor([0.1415634006, 0.0989848152, 0.0565387346, -0.0072308783, -0.0428492837]),
|
||||
]
|
||||
|
||||
for proposal, expected_proposal_box, im_size, expected_objectness_logit in zip(
|
||||
proposals, expected_proposal_boxes, image_sizes, expected_objectness_logits
|
||||
):
|
||||
self.assertEqual(len(proposal), len(expected_proposal_box))
|
||||
self.assertEqual(proposal.image_size, im_size)
|
||||
self.assertTrue(
|
||||
torch.allclose(proposal.proposal_boxes.tensor, expected_proposal_box.tensor)
|
||||
)
|
||||
self.assertTrue(torch.allclose(proposal.objectness_logits, expected_objectness_logit))
|
||||
|
||||
def test_rrpn(self):
|
||||
torch.manual_seed(121)
|
||||
cfg = get_cfg()
|
||||
cfg.MODEL.PROPOSAL_GENERATOR.NAME = "RRPN"
|
||||
cfg.MODEL.ANCHOR_GENERATOR.NAME = "RotatedAnchorGenerator"
|
||||
cfg.MODEL.ANCHOR_GENERATOR.SIZES = [[32, 64]]
|
||||
cfg.MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS = [[0.25, 1]]
|
||||
cfg.MODEL.ANCHOR_GENERATOR.ANGLES = [[0, 60]]
|
||||
cfg.MODEL.RPN.BBOX_REG_WEIGHTS = (1, 1, 1, 1, 1)
|
||||
cfg.MODEL.RPN.HEAD_NAME = "StandardRPNHead"
|
||||
backbone = build_backbone(cfg)
|
||||
proposal_generator = build_proposal_generator(cfg, backbone.output_shape())
|
||||
num_images = 2
|
||||
images_tensor = torch.rand(num_images, 20, 30)
|
||||
image_sizes = [(10, 10), (20, 30)]
|
||||
images = ImageList(images_tensor, image_sizes)
|
||||
image_shape = (15, 15)
|
||||
num_channels = 1024
|
||||
features = {"res4": torch.rand(num_images, num_channels, 1, 2)}
|
||||
gt_boxes = torch.tensor([[2, 2, 2, 2, 0], [4, 4, 4, 4, 0]], dtype=torch.float32)
|
||||
gt_instances = Instances(image_shape)
|
||||
gt_instances.gt_boxes = RotatedBoxes(gt_boxes)
|
||||
with EventStorage(): # capture events in a new storage to discard them
|
||||
proposals, proposal_losses = proposal_generator(
|
||||
images, features, [gt_instances[0], gt_instances[1]]
|
||||
)
|
||||
|
||||
expected_losses = {
|
||||
"loss_rpn_cls": torch.tensor(0.043263837695121765),
|
||||
"loss_rpn_loc": torch.tensor(0.14432406425476074),
|
||||
}
|
||||
for name in expected_losses.keys():
|
||||
err_msg = "proposal_losses[{}] = {}, expected losses = {}".format(
|
||||
name, proposal_losses[name], expected_losses[name]
|
||||
)
|
||||
self.assertTrue(torch.allclose(proposal_losses[name], expected_losses[name]), err_msg)
|
||||
|
||||
expected_proposal_boxes = [
|
||||
RotatedBoxes(
|
||||
torch.tensor(
|
||||
[
|
||||
[0.60189795, 1.24095452, 61.98131943, 18.03621292, -4.07244873],
|
||||
[15.64940453, 1.69624567, 59.59749603, 16.34339333, 2.62692475],
|
||||
[-3.02982378, -2.69752932, 67.90952301, 59.62455750, 59.97010040],
|
||||
[16.71863365, 1.98309708, 35.61507797, 32.81484985, 62.92267227],
|
||||
[0.49432933, -7.92979717, 67.77606201, 62.93098450, -1.85656738],
|
||||
[8.00880814, 1.36017394, 121.81007385, 32.74150467, 50.44297409],
|
||||
[16.44299889, -4.82221127, 63.39775848, 61.22503662, 54.12270737],
|
||||
[5.00000000, 5.00000000, 10.00000000, 10.00000000, -0.76943970],
|
||||
[17.64130402, -0.98095351, 61.40377808, 16.28918839, 55.53118134],
|
||||
[0.13016054, 4.60568953, 35.80157471, 32.30180359, 62.52872086],
|
||||
[-4.26460743, 0.39604485, 124.30079651, 31.84611320, -1.58203125],
|
||||
[7.52815342, -0.91636634, 62.39784622, 15.45565224, 60.79549789],
|
||||
]
|
||||
)
|
||||
),
|
||||
RotatedBoxes(
|
||||
torch.tensor(
|
||||
[
|
||||
[0.07734215, 0.81635046, 65.33510590, 17.34688377, -1.51821899],
|
||||
[-3.41833067, -3.11320257, 64.17595673, 60.55617905, 58.27033234],
|
||||
[20.67383385, -6.16561556, 63.60531998, 62.52315903, 54.85546494],
|
||||
[15.00000000, 10.00000000, 30.00000000, 20.00000000, -0.18218994],
|
||||
[9.22646523, -6.84775209, 62.09895706, 65.46472931, -2.74307251],
|
||||
[15.00000000, 4.93451595, 30.00000000, 9.86903191, -0.60272217],
|
||||
[8.88342094, 2.65560246, 120.95362854, 32.45022202, 55.75970078],
|
||||
[16.39088631, 2.33887148, 34.78761292, 35.61492920, 60.81977463],
|
||||
[9.78298569, 10.00000000, 19.56597137, 20.00000000, -0.86660767],
|
||||
[1.28576660, 5.49873352, 34.93610382, 33.22600174, 60.51599884],
|
||||
[17.58912468, -1.63270092, 62.96052551, 16.45713997, 52.91245270],
|
||||
[5.64749718, -1.90428460, 62.37649155, 16.19474792, 61.09543991],
|
||||
[0.82255805, 2.34931135, 118.83985901, 32.83671188, 56.50753784],
|
||||
[-5.33874989, 1.64404404, 125.28501892, 33.35424042, -2.80731201],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
expected_objectness_logits = [
|
||||
torch.tensor(
|
||||
[
|
||||
0.10111768,
|
||||
0.09112845,
|
||||
0.08466332,
|
||||
0.07589971,
|
||||
0.06650183,
|
||||
0.06350251,
|
||||
0.04299347,
|
||||
0.01864817,
|
||||
0.00986163,
|
||||
0.00078543,
|
||||
-0.04573630,
|
||||
-0.04799230,
|
||||
]
|
||||
),
|
||||
torch.tensor(
|
||||
[
|
||||
0.11373727,
|
||||
0.09377633,
|
||||
0.05281663,
|
||||
0.05143715,
|
||||
0.04040275,
|
||||
0.03250912,
|
||||
0.01307789,
|
||||
0.01177734,
|
||||
0.00038105,
|
||||
-0.00540255,
|
||||
-0.01194804,
|
||||
-0.01461012,
|
||||
-0.03061717,
|
||||
-0.03599222,
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
torch.set_printoptions(precision=8, sci_mode=False)
|
||||
|
||||
for proposal, expected_proposal_box, im_size, expected_objectness_logit in zip(
|
||||
proposals, expected_proposal_boxes, image_sizes, expected_objectness_logits
|
||||
):
|
||||
self.assertEqual(len(proposal), len(expected_proposal_box))
|
||||
self.assertEqual(proposal.image_size, im_size)
|
||||
# It seems that there's some randomness in the result across different machines:
|
||||
# This test can be run on a local machine for 100 times with exactly the same result,
|
||||
# However, a different machine might produce slightly different results,
|
||||
# thus the atol here.
|
||||
err_msg = "computed proposal boxes = {}, expected {}".format(
|
||||
proposal.proposal_boxes.tensor, expected_proposal_box.tensor
|
||||
)
|
||||
self.assertTrue(
|
||||
torch.allclose(
|
||||
proposal.proposal_boxes.tensor, expected_proposal_box.tensor, atol=1e-5
|
||||
),
|
||||
err_msg,
|
||||
)
|
||||
|
||||
err_msg = "computed objectness logits = {}, expected {}".format(
|
||||
proposal.objectness_logits, expected_objectness_logit
|
||||
)
|
||||
self.assertTrue(
|
||||
torch.allclose(proposal.objectness_logits, expected_objectness_logit, atol=1e-5),
|
||||
err_msg,
|
||||
)
|
||||
|
||||
def test_rpn_proposals_inf(self):
|
||||
N, Hi, Wi, A = 3, 3, 3, 3
|
||||
proposals = [torch.rand(N, Hi * Wi * A, 4)]
|
||||
pred_logits = [torch.rand(N, Hi * Wi * A)]
|
||||
pred_logits[0][1][3:5].fill_(float("inf"))
|
||||
images = ImageList.from_tensors([torch.rand(3, 10, 10)] * 3)
|
||||
find_top_rpn_proposals(proposals, pred_logits, images, 0.5, 1000, 1000, 0, False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Reference in New Issue
Block a user