|
18 | 18 |
|
19 | 19 | from monai.networks import eval_mode |
20 | 20 | from monai.networks.nets import Quicknat |
| 21 | +from monai.networks.nets.quicknat import SkipConnectionWithIdx |
21 | 22 | from monai.utils import optional_import |
22 | 23 | from tests.test_utils import test_script_save |
23 | 24 |
|
24 | 25 | _, has_se = optional_import("squeeze_and_excitation") |
25 | 26 |
|
| 27 | + |
| 28 | +class _DoubleWithIdx(torch.nn.Module): |
| 29 | + """Double tensor values using QuickNAT's indexed-module signature.""" |
| 30 | + |
| 31 | + def forward(self, input, indices): |
| 32 | + """Return the doubled tensor and unchanged indices.""" |
| 33 | + return input * 2, indices |
| 34 | + |
| 35 | + |
26 | 36 | TEST_CASES = [ |
27 | 37 | # params, input_shape, expected_shape |
28 | 38 | [{"num_classes": 1, "num_channels": 1, "num_filters": 1, "se_block": None}, (1, 1, 32, 32), (1, 1, 32, 32)], |
|
36 | 46 | ] |
37 | 47 |
|
38 | 48 |
|
| 49 | +class TestQuicknatCore(unittest.TestCase): |
| 50 | + """Test QuickNAT paths that do not require optional dependencies.""" |
| 51 | + |
| 52 | + @parameterized.expand(["cat", "add", "mul"]) |
| 53 | + def test_skip_connection_modes_preserve_indices(self, mode): |
| 54 | + """Verify each fusion mode and preservation of pooling indices.""" |
| 55 | + skip = SkipConnectionWithIdx(_DoubleWithIdx(), mode=mode) |
| 56 | + input = torch.tensor([[[[2.0]]]]) |
| 57 | + indices = torch.tensor([[[[3]]]]) |
| 58 | + |
| 59 | + output, returned_indices = skip(input, indices) |
| 60 | + |
| 61 | + expected = {"cat": torch.cat([input, input * 2], dim=1), "add": input * 3, "mul": input * input * 2}[mode] |
| 62 | + self.assertTrue(torch.equal(output, expected)) |
| 63 | + self.assertIs(returned_indices, indices) |
| 64 | + |
| 65 | + def test_skip_connection_rejects_unsupported_mode(self): |
| 66 | + """Verify unsupported fusion modes fail explicitly.""" |
| 67 | + skip = SkipConnectionWithIdx(_DoubleWithIdx(), mode="cat") |
| 68 | + skip.mode = "unsupported" |
| 69 | + |
| 70 | + with self.assertRaisesRegex(NotImplementedError, "Unsupported mode"): |
| 71 | + skip(torch.ones((1, 1, 1, 1)), torch.ones((1, 1, 1, 1))) |
| 72 | + |
| 73 | + def test_forward_without_optional_se_dependency(self): |
| 74 | + """Verify QuickNAT runs when squeeze-and-excitation is disabled.""" |
| 75 | + net = Quicknat(num_classes=2, num_channels=1, num_filters=4, se_block=None) |
| 76 | + with eval_mode(net): |
| 77 | + result = net(torch.randn(1, 1, 32, 32)) |
| 78 | + self.assertEqual(result.shape, (1, 2, 32, 32)) |
| 79 | + |
| 80 | + |
39 | 81 | @unittest.skipUnless(has_se, "squeeze_and_excitation not installed") |
40 | 82 | class TestQuicknat(unittest.TestCase): |
41 | 83 | @parameterized.expand(TEST_CASES) |
|
0 commit comments