123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651 |
- # Copyright (c) OpenMMLab. All rights reserved.
- import pytest
- import torch
- from torch.nn.modules.batchnorm import _BatchNorm
- from mmdet.models.necks import (FPG, FPN, FPN_CARAFE, NASFCOS_FPN, NASFPN, SSH,
- YOLOXPAFPN, ChannelMapper, DilatedEncoder,
- DyHead, SSDNeck, YOLOV3Neck)
- def test_fpn():
- """Tests fpn."""
- s = 64
- in_channels = [8, 16, 32, 64]
- feat_sizes = [s // 2**i for i in range(4)] # [64, 32, 16, 8]
- out_channels = 8
- # end_level=-1 is equal to end_level=3
- FPN(in_channels=in_channels,
- out_channels=out_channels,
- start_level=0,
- end_level=-1,
- num_outs=5)
- FPN(in_channels=in_channels,
- out_channels=out_channels,
- start_level=0,
- end_level=3,
- num_outs=5)
- # `num_outs` is not equal to end_level - start_level + 1
- with pytest.raises(AssertionError):
- FPN(in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- end_level=2,
- num_outs=3)
- # `num_outs` is not equal to len(in_channels) - start_level
- with pytest.raises(AssertionError):
- FPN(in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- num_outs=2)
- # `end_level` is larger than len(in_channels) - 1
- with pytest.raises(AssertionError):
- FPN(in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- end_level=4,
- num_outs=2)
- # `num_outs` is not equal to end_level - start_level
- with pytest.raises(AssertionError):
- FPN(in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- end_level=3,
- num_outs=1)
- # Invalid `add_extra_convs` option
- with pytest.raises(AssertionError):
- FPN(in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- add_extra_convs='on_xxx',
- num_outs=5)
- fpn_model = FPN(
- in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- add_extra_convs=True,
- num_outs=5)
- # FPN expects a multiple levels of features per image
- feats = [
- torch.rand(1, in_channels[i], feat_sizes[i], feat_sizes[i])
- for i in range(len(in_channels))
- ]
- outs = fpn_model(feats)
- assert fpn_model.add_extra_convs == 'on_input'
- assert len(outs) == fpn_model.num_outs
- for i in range(fpn_model.num_outs):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- # Tests for fpn with no extra convs (pooling is used instead)
- fpn_model = FPN(
- in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- add_extra_convs=False,
- num_outs=5)
- outs = fpn_model(feats)
- assert len(outs) == fpn_model.num_outs
- assert not fpn_model.add_extra_convs
- for i in range(fpn_model.num_outs):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- # Tests for fpn with lateral bns
- fpn_model = FPN(
- in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- add_extra_convs=True,
- no_norm_on_lateral=False,
- norm_cfg=dict(type='BN', requires_grad=True),
- num_outs=5)
- outs = fpn_model(feats)
- assert len(outs) == fpn_model.num_outs
- assert fpn_model.add_extra_convs == 'on_input'
- for i in range(fpn_model.num_outs):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- bn_exist = False
- for m in fpn_model.modules():
- if isinstance(m, _BatchNorm):
- bn_exist = True
- assert bn_exist
- # Bilinear upsample
- fpn_model = FPN(
- in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- add_extra_convs=True,
- upsample_cfg=dict(mode='bilinear', align_corners=True),
- num_outs=5)
- fpn_model(feats)
- outs = fpn_model(feats)
- assert len(outs) == fpn_model.num_outs
- assert fpn_model.add_extra_convs == 'on_input'
- for i in range(fpn_model.num_outs):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- # Scale factor instead of fixed upsample size upsample
- fpn_model = FPN(
- in_channels=in_channels,
- out_channels=out_channels,
- start_level=1,
- add_extra_convs=True,
- upsample_cfg=dict(scale_factor=2),
- num_outs=5)
- outs = fpn_model(feats)
- assert len(outs) == fpn_model.num_outs
- for i in range(fpn_model.num_outs):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- # Extra convs source is 'inputs'
- fpn_model = FPN(
- in_channels=in_channels,
- out_channels=out_channels,
- add_extra_convs='on_input',
- start_level=1,
- num_outs=5)
- assert fpn_model.add_extra_convs == 'on_input'
- outs = fpn_model(feats)
- assert len(outs) == fpn_model.num_outs
- for i in range(fpn_model.num_outs):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- # Extra convs source is 'laterals'
- fpn_model = FPN(
- in_channels=in_channels,
- out_channels=out_channels,
- add_extra_convs='on_lateral',
- start_level=1,
- num_outs=5)
- assert fpn_model.add_extra_convs == 'on_lateral'
- outs = fpn_model(feats)
- assert len(outs) == fpn_model.num_outs
- for i in range(fpn_model.num_outs):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- # Extra convs source is 'outputs'
- fpn_model = FPN(
- in_channels=in_channels,
- out_channels=out_channels,
- add_extra_convs='on_output',
- start_level=1,
- num_outs=5)
- assert fpn_model.add_extra_convs == 'on_output'
- outs = fpn_model(feats)
- assert len(outs) == fpn_model.num_outs
- for i in range(fpn_model.num_outs):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- def test_channel_mapper():
- """Tests ChannelMapper."""
- s = 64
- in_channels = [8, 16, 32, 64]
- feat_sizes = [s // 2**i for i in range(4)] # [64, 32, 16, 8]
- out_channels = 8
- kernel_size = 3
- feats = [
- torch.rand(1, in_channels[i], feat_sizes[i], feat_sizes[i])
- for i in range(len(in_channels))
- ]
- # in_channels must be a list
- with pytest.raises(AssertionError):
- channel_mapper = ChannelMapper(
- in_channels=10, out_channels=out_channels, kernel_size=kernel_size)
- # the length of channel_mapper's inputs must be equal to the length of
- # in_channels
- with pytest.raises(AssertionError):
- channel_mapper = ChannelMapper(
- in_channels=in_channels[:-1],
- out_channels=out_channels,
- kernel_size=kernel_size)
- channel_mapper(feats)
- channel_mapper = ChannelMapper(
- in_channels=in_channels,
- out_channels=out_channels,
- kernel_size=kernel_size)
- outs = channel_mapper(feats)
- assert len(outs) == len(feats)
- for i in range(len(feats)):
- outs[i].shape[1] == out_channels
- outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- def test_dilated_encoder():
- in_channels = 16
- out_channels = 32
- out_shape = 34
- dilated_encoder = DilatedEncoder(in_channels, out_channels, 16, 2,
- [2, 4, 6, 8])
- feat = [torch.rand(1, in_channels, 34, 34)]
- out_feat = dilated_encoder(feat)[0]
- assert out_feat.shape == (1, out_channels, out_shape, out_shape)
- def test_yolov3_neck():
- # num_scales, in_channels, out_channels must be same length
- with pytest.raises(AssertionError):
- YOLOV3Neck(num_scales=3, in_channels=[16, 8, 4], out_channels=[8, 4])
- # len(feats) must equal to num_scales
- with pytest.raises(AssertionError):
- neck = YOLOV3Neck(
- num_scales=3, in_channels=[16, 8, 4], out_channels=[8, 4, 2])
- feats = (torch.rand(1, 4, 16, 16), torch.rand(1, 8, 16, 16))
- neck(feats)
- # test normal channels
- s = 32
- in_channels = [16, 8, 4]
- out_channels = [8, 4, 2]
- feat_sizes = [s // 2**i for i in range(len(in_channels) - 1, -1, -1)]
- feats = [
- torch.rand(1, in_channels[i], feat_sizes[i], feat_sizes[i])
- for i in range(len(in_channels) - 1, -1, -1)
- ]
- neck = YOLOV3Neck(
- num_scales=3, in_channels=in_channels, out_channels=out_channels)
- outs = neck(feats)
- assert len(outs) == len(feats)
- for i in range(len(outs)):
- assert outs[i].shape == \
- (1, out_channels[i], feat_sizes[i], feat_sizes[i])
- # test more flexible setting
- s = 32
- in_channels = [32, 8, 16]
- out_channels = [19, 21, 5]
- feat_sizes = [s // 2**i for i in range(len(in_channels) - 1, -1, -1)]
- feats = [
- torch.rand(1, in_channels[i], feat_sizes[i], feat_sizes[i])
- for i in range(len(in_channels) - 1, -1, -1)
- ]
- neck = YOLOV3Neck(
- num_scales=3, in_channels=in_channels, out_channels=out_channels)
- outs = neck(feats)
- assert len(outs) == len(feats)
- for i in range(len(outs)):
- assert outs[i].shape == \
- (1, out_channels[i], feat_sizes[i], feat_sizes[i])
- def test_ssd_neck():
- # level_strides/level_paddings must be same length
- with pytest.raises(AssertionError):
- SSDNeck(
- in_channels=[8, 16],
- out_channels=[8, 16, 32],
- level_strides=[2],
- level_paddings=[2, 1])
- # length of out_channels must larger than in_channels
- with pytest.raises(AssertionError):
- SSDNeck(
- in_channels=[8, 16],
- out_channels=[8],
- level_strides=[2],
- level_paddings=[2])
- # len(out_channels) - len(in_channels) must equal to len(level_strides)
- with pytest.raises(AssertionError):
- SSDNeck(
- in_channels=[8, 16],
- out_channels=[4, 16, 64],
- level_strides=[2, 2],
- level_paddings=[2, 2])
- # in_channels must be same with out_channels[:len(in_channels)]
- with pytest.raises(AssertionError):
- SSDNeck(
- in_channels=[8, 16],
- out_channels=[4, 16, 64],
- level_strides=[2],
- level_paddings=[2])
- ssd_neck = SSDNeck(
- in_channels=[4],
- out_channels=[4, 8, 16],
- level_strides=[2, 1],
- level_paddings=[1, 0])
- feats = (torch.rand(1, 4, 16, 16), )
- outs = ssd_neck(feats)
- assert outs[0].shape == (1, 4, 16, 16)
- assert outs[1].shape == (1, 8, 8, 8)
- assert outs[2].shape == (1, 16, 6, 6)
- # test SSD-Lite Neck
- ssd_neck = SSDNeck(
- in_channels=[4, 8],
- out_channels=[4, 8, 16],
- level_strides=[1],
- level_paddings=[1],
- l2_norm_scale=None,
- use_depthwise=True,
- norm_cfg=dict(type='BN'),
- act_cfg=dict(type='ReLU6'))
- assert not hasattr(ssd_neck, 'l2_norm')
- from mmcv.cnn.bricks import DepthwiseSeparableConvModule
- assert isinstance(ssd_neck.extra_layers[0][-1],
- DepthwiseSeparableConvModule)
- feats = (torch.rand(1, 4, 8, 8), torch.rand(1, 8, 8, 8))
- outs = ssd_neck(feats)
- assert outs[0].shape == (1, 4, 8, 8)
- assert outs[1].shape == (1, 8, 8, 8)
- assert outs[2].shape == (1, 16, 8, 8)
- def test_yolox_pafpn():
- s = 64
- in_channels = [8, 16, 32, 64]
- feat_sizes = [s // 2**i for i in range(4)] # [64, 32, 16, 8]
- out_channels = 24
- feats = [
- torch.rand(1, in_channels[i], feat_sizes[i], feat_sizes[i])
- for i in range(len(in_channels))
- ]
- neck = YOLOXPAFPN(in_channels=in_channels, out_channels=out_channels)
- outs = neck(feats)
- assert len(outs) == len(feats)
- for i in range(len(feats)):
- assert outs[i].shape[1] == out_channels
- assert outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- # test depth-wise
- neck = YOLOXPAFPN(
- in_channels=in_channels, out_channels=out_channels, use_depthwise=True)
- from mmcv.cnn.bricks import DepthwiseSeparableConvModule
- assert isinstance(neck.downsamples[0], DepthwiseSeparableConvModule)
- outs = neck(feats)
- assert len(outs) == len(feats)
- for i in range(len(feats)):
- assert outs[i].shape[1] == out_channels
- assert outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- def test_dyhead():
- s = 64
- in_channels = 8
- out_channels = 16
- feat_sizes = [s // 2**i for i in range(4)] # [64, 32, 16, 8]
- feats = [
- torch.rand(1, in_channels, feat_sizes[i], feat_sizes[i])
- for i in range(len(feat_sizes))
- ]
- neck = DyHead(
- in_channels=in_channels, out_channels=out_channels, num_blocks=3)
- outs = neck(feats)
- assert len(outs) == len(feats)
- for i in range(len(outs)):
- assert outs[i].shape[1] == out_channels
- assert outs[i].shape[2] == outs[i].shape[3] == s // (2**i)
- feat = torch.rand(1, 8, 4, 4)
- # input feat must be tuple or list
- with pytest.raises(AssertionError):
- neck(feat)
- def test_fpg():
- # end_level=-1 is equal to end_level=3
- norm_cfg = dict(type='BN', requires_grad=True)
- FPG(in_channels=[8, 16, 32, 64],
- out_channels=8,
- inter_channels=8,
- num_outs=5,
- add_extra_convs=True,
- start_level=1,
- end_level=-1,
- stack_times=9,
- paths=['bu'] * 9,
- same_down_trans=None,
- same_up_trans=dict(
- type='conv',
- kernel_size=3,
- stride=2,
- padding=1,
- norm_cfg=norm_cfg,
- inplace=False,
- order=('act', 'conv', 'norm')),
- across_lateral_trans=dict(
- type='conv',
- kernel_size=1,
- norm_cfg=norm_cfg,
- inplace=False,
- order=('act', 'conv', 'norm')),
- across_down_trans=dict(
- type='interpolation_conv',
- mode='nearest',
- kernel_size=3,
- norm_cfg=norm_cfg,
- order=('act', 'conv', 'norm'),
- inplace=False),
- across_up_trans=None,
- across_skip_trans=dict(
- type='conv',
- kernel_size=1,
- norm_cfg=norm_cfg,
- inplace=False,
- order=('act', 'conv', 'norm')),
- output_trans=dict(
- type='last_conv',
- kernel_size=3,
- order=('act', 'conv', 'norm'),
- inplace=False),
- norm_cfg=norm_cfg,
- skip_inds=[(0, 1, 2, 3), (0, 1, 2), (0, 1), (0, ), ()])
- FPG(in_channels=[8, 16, 32, 64],
- out_channels=8,
- inter_channels=8,
- num_outs=5,
- add_extra_convs=True,
- start_level=1,
- end_level=3,
- stack_times=9,
- paths=['bu'] * 9,
- same_down_trans=None,
- same_up_trans=dict(
- type='conv',
- kernel_size=3,
- stride=2,
- padding=1,
- norm_cfg=norm_cfg,
- inplace=False,
- order=('act', 'conv', 'norm')),
- across_lateral_trans=dict(
- type='conv',
- kernel_size=1,
- norm_cfg=norm_cfg,
- inplace=False,
- order=('act', 'conv', 'norm')),
- across_down_trans=dict(
- type='interpolation_conv',
- mode='nearest',
- kernel_size=3,
- norm_cfg=norm_cfg,
- order=('act', 'conv', 'norm'),
- inplace=False),
- across_up_trans=None,
- across_skip_trans=dict(
- type='conv',
- kernel_size=1,
- norm_cfg=norm_cfg,
- inplace=False,
- order=('act', 'conv', 'norm')),
- output_trans=dict(
- type='last_conv',
- kernel_size=3,
- order=('act', 'conv', 'norm'),
- inplace=False),
- norm_cfg=norm_cfg,
- skip_inds=[(0, 1, 2, 3), (0, 1, 2), (0, 1), (0, ), ()])
- # `end_level` is larger than len(in_channels) - 1
- with pytest.raises(AssertionError):
- FPG(in_channels=[8, 16, 32, 64],
- out_channels=8,
- stack_times=9,
- paths=['bu'] * 9,
- start_level=1,
- end_level=4,
- num_outs=2,
- skip_inds=[(0, 1, 2, 3), (0, 1, 2), (0, 1), (0, ), ()])
- # `num_outs` is not equal to end_level - start_level + 1
- with pytest.raises(AssertionError):
- FPG(in_channels=[8, 16, 32, 64],
- out_channels=8,
- stack_times=9,
- paths=['bu'] * 9,
- start_level=1,
- end_level=2,
- num_outs=3,
- skip_inds=[(0, 1, 2, 3), (0, 1, 2), (0, 1), (0, ), ()])
- def test_fpn_carafe():
- # end_level=-1 is equal to end_level=3
- FPN_CARAFE(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- start_level=0,
- end_level=3,
- num_outs=4)
- FPN_CARAFE(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- start_level=0,
- end_level=-1,
- num_outs=4)
- # `end_level` is larger than len(in_channels) - 1
- with pytest.raises(AssertionError):
- FPN_CARAFE(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- start_level=1,
- end_level=4,
- num_outs=2)
- # `num_outs` is not equal to end_level - start_level + 1
- with pytest.raises(AssertionError):
- FPN_CARAFE(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- start_level=1,
- end_level=2,
- num_outs=3)
- def test_nas_fpn():
- # end_level=-1 is equal to end_level=3
- NASFPN(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- stack_times=9,
- start_level=0,
- end_level=3,
- num_outs=4)
- NASFPN(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- stack_times=9,
- start_level=0,
- end_level=-1,
- num_outs=4)
- # `end_level` is larger than len(in_channels) - 1
- with pytest.raises(AssertionError):
- NASFPN(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- stack_times=9,
- start_level=1,
- end_level=4,
- num_outs=2)
- # `num_outs` is not equal to end_level - start_level + 1
- with pytest.raises(AssertionError):
- NASFPN(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- stack_times=9,
- start_level=1,
- end_level=2,
- num_outs=3)
- def test_nasfcos_fpn():
- # end_level=-1 is equal to end_level=3
- NASFCOS_FPN(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- start_level=0,
- end_level=3,
- num_outs=4)
- NASFCOS_FPN(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- start_level=0,
- end_level=-1,
- num_outs=4)
- # `end_level` is larger than len(in_channels) - 1
- with pytest.raises(AssertionError):
- NASFCOS_FPN(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- start_level=1,
- end_level=4,
- num_outs=2)
- # `num_outs` is not equal to end_level - start_level + 1
- with pytest.raises(AssertionError):
- NASFCOS_FPN(
- in_channels=[8, 16, 32, 64],
- out_channels=8,
- start_level=1,
- end_level=2,
- num_outs=3)
- def test_ssh_neck():
- """Tests ssh."""
- s = 64
- in_channels = [8, 16, 32, 64]
- feat_sizes = [s // 2**i for i in range(4)] # [64, 32, 16, 8]
- out_channels = [16, 32, 64, 128]
- ssh_model = SSH(
- num_scales=4, in_channels=in_channels, out_channels=out_channels)
- feats = [
- torch.rand(1, in_channels[i], feat_sizes[i], feat_sizes[i])
- for i in range(len(in_channels))
- ]
- outs = ssh_model(feats)
- assert len(outs) == len(feats)
- for i in range(len(outs)):
- assert outs[i].shape == \
- (1, out_channels[i], feat_sizes[i], feat_sizes[i])
|