From d22e4fcc38727eb16f7be4b0ada9996c083b8f56 Mon Sep 17 00:00:00 2001 From: Kheil-Z Date: Wed, 12 Nov 2025 12:36:02 +0100 Subject: [PATCH 01/12] WIP: placeholder for optional moving_seg/fixed_seg support in VoxelMorph.forward --- monai/networks/nets/voxelmorph.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index 4923b6ad60..56d7ea43fb 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -441,6 +441,7 @@ def __init__( self.warp = Warp(mode="bilinear", padding_mode="zeros") def forward(self, moving: torch.Tensor, fixed: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: + # TODO: add optional moving_seg, fixed_seg arguments and handle warping of segmentation maps if moving.shape != fixed.shape: raise ValueError( "The spatial shape of the moving image should be the same as the spatial shape of the fixed image." From 7677f8fe36723b2e86f73976779fef7a80abc694 Mon Sep 17 00:00:00 2001 From: Kaibo Tang Date: Wed, 12 Nov 2025 10:31:06 -0500 Subject: [PATCH 02/12] add --- monai/networks/nets/voxelmorph.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index 56d7ea43fb..c89b224f6c 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -440,14 +440,20 @@ def __init__( self.dvf2ddf = DVF2DDF(num_steps=self.integration_steps, mode="bilinear", padding_mode="zeros") self.warp = Warp(mode="bilinear", padding_mode="zeros") - def forward(self, moving: torch.Tensor, fixed: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: - # TODO: add optional moving_seg, fixed_seg arguments and handle warping of segmentation maps + def forward(self, moving: torch.Tensor, fixed: torch.Tensor, moving_seg: torch.Tensor | None = None) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]: if moving.shape != fixed.shape: raise ValueError( "The spatial shape of the moving image should be the same as the spatial shape of the fixed image." f" Got {moving.shape} and {fixed.shape} instead." ) + if moving_seg is not None: + if moving_seg[-3:] != moving.shape[-3:]: + raise ValueError( + "The spatial shape of the moving segmentation should be the same as the spatial shape of the" + f" moving image. Got {moving_seg.shape} and {moving.shape} instead." + ) + x = self.backbone(torch.cat([moving, fixed], dim=1)) if x.shape[1] != self.spatial_dims: @@ -471,7 +477,10 @@ def forward(self, moving: torch.Tensor, fixed: torch.Tensor) -> tuple[torch.Tens if self.half_res: x = F.interpolate(x * 0.5, scale_factor=2.0, mode="trilinear", align_corners=True) - return self.warp(moving, x), x + if moving_seg is None: + return self.warp(moving, x), x + else: + return self.warp(moving, x), x, self.warp(moving_seg, x) voxelmorph = VoxelMorph From ec1fe51fc09405d5922f4781a8d665e3852033d6 Mon Sep 17 00:00:00 2001 From: Kaibo Tang Date: Wed, 12 Nov 2025 10:42:45 -0500 Subject: [PATCH 03/12] fix typing --- monai/networks/nets/voxelmorph.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index c89b224f6c..bfa398e456 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -440,7 +440,12 @@ def __init__( self.dvf2ddf = DVF2DDF(num_steps=self.integration_steps, mode="bilinear", padding_mode="zeros") self.warp = Warp(mode="bilinear", padding_mode="zeros") - def forward(self, moving: torch.Tensor, fixed: torch.Tensor, moving_seg: torch.Tensor | None = None) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]: + def forward( + self, + moving: torch.Tensor, + fixed: torch.Tensor, + moving_seg: torch.Tensor | None = None + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor] | tuple[torch.Tensor, torch.Tensor]: if moving.shape != fixed.shape: raise ValueError( "The spatial shape of the moving image should be the same as the spatial shape of the fixed image." From 3d18f078883ab66f3668d64adcc2df74b07751e2 Mon Sep 17 00:00:00 2001 From: Kaibo Tang Date: Wed, 12 Nov 2025 10:50:07 -0500 Subject: [PATCH 04/12] fix output order --- monai/networks/nets/voxelmorph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index bfa398e456..2178753192 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -485,7 +485,7 @@ def forward( if moving_seg is None: return self.warp(moving, x), x else: - return self.warp(moving, x), x, self.warp(moving_seg, x) + return self.warp(moving, x), self.warp(moving_seg, x), x voxelmorph = VoxelMorph From dfcc2ce2dbd56fb63274af476b7273bb630a0a8f Mon Sep 17 00:00:00 2001 From: Kaibo Tang Date: Wed, 12 Nov 2025 11:24:49 -0500 Subject: [PATCH 05/12] add unittest --- tests/networks/nets/test_voxelmorph.py | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/networks/nets/test_voxelmorph.py b/tests/networks/nets/test_voxelmorph.py index 1a04bab568..d178c6b6bd 100644 --- a/tests/networks/nets/test_voxelmorph.py +++ b/tests/networks/nets/test_voxelmorph.py @@ -171,6 +171,18 @@ TEST_CASE_9, ] +TEST_CASE_SEG_0 = [ + {"spatial_dims": 3}, + (1, 1, 96, 96, 48), # moving image + (1, 1, 96, 96, 48), # fixed image + (1, 2, 96, 96, 48), # moving label + (1, 1, 96, 96, 48), # expected warped moving image + (1, 2, 96, 96, 48), # expected warped moving label + (1, 3, 96, 96, 48), # expected ddf +] + +CASES_SEG = [TEST_CASE_SEG_0] + ILL_CASE_0 = [ # spatial_dims = 1 { "spatial_dims": 1, @@ -243,6 +255,15 @@ ILL_CASES_IN_SHAPE = [ILL_CASES_IN_SHAPE_0, ILL_CASES_IN_SHAPE_1] +ILL_CASE_SEG_SHAPE_0 = [ # moving_seg and moving image shape not match + {"spatial_dims": 3}, + (1, 1, 96, 96, 48), + (1, 1, 96, 96, 48), + (1, 2, 80, 96, 48), +] + +ILL_CASES_SEG_SHAPE = [ILL_CASE_SEG_SHAPE_0] + class TestVOXELMORPH(unittest.TestCase): @parameterized.expand(CASES) @@ -252,6 +273,24 @@ def test_shape(self, input_param, input_shape, expected_shape): result = net.forward(torch.randn(input_shape).to(device)) self.assertEqual(result.shape, expected_shape) + @parameterized.expand(CASES_SEG) + def test_shape_seg( + self, + input_param, + moving_shape, fixed_shape, moving_seg_shape, + expected_warped_moving_shape, expected_warped_moving_seg_shape, expected_ddf_shape + ): + net = VoxelMorph(**input_param).to(device) + with eval_mode(net): + warped_moving, warped_moving_seg, ddf = net.forward( + torch.randn(moving_shape).to(device), + torch.randn(fixed_shape).to(device), + torch.randn(moving_seg_shape).to(device), + ) + self.assertEqual(warped_moving.shape, expected_warped_moving_shape) + self.assertEqual(warped_moving_seg.shape, expected_warped_moving_seg_shape) + self.assertEqual(ddf.shape, expected_ddf_shape) + def test_script(self): net = VoxelMorphUNet( spatial_dims=2, @@ -275,6 +314,16 @@ def test_ill_input_shape(self, input_param, moving_shape, fixed_shape): with eval_mode(net): _ = net.forward(torch.randn(moving_shape).to(device), torch.randn(fixed_shape).to(device)) + @parameterized.expand(ILL_CASES_SEG_SHAPE) + def test_ill_input_seg_shape(self, input_param, moving_shape, fixed_shape, moving_seg_shape): + with self.assertRaises((ValueError, RuntimeError)): + net = VoxelMorph(**input_param).to(device) + with eval_mode(net): + _ = net.forward( + torch.randn(moving_shape).to(device), + torch.randn(fixed_shape).to(device), + torch.randn(moving_seg_shape).to(device), + ) if __name__ == "__main__": unittest.main() From f50e8e8939b44d7a1eeaac086b0e8d5f831c8b1e Mon Sep 17 00:00:00 2001 From: Kaibo Tang Date: Wed, 12 Nov 2025 17:35:23 -0500 Subject: [PATCH 06/12] placeholder for keypoints --- monai/networks/nets/voxelmorph.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index 2178753192..84fdf05eee 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -444,8 +444,9 @@ def forward( self, moving: torch.Tensor, fixed: torch.Tensor, - moving_seg: torch.Tensor | None = None - ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor] | tuple[torch.Tensor, torch.Tensor]: + moving_seg: torch.Tensor | None = None, + fixed_keypoints: torch.Tensor | None = None + ) -> tuple[torch.Tensor, ...]: if moving.shape != fixed.shape: raise ValueError( "The spatial shape of the moving image should be the same as the spatial shape of the fixed image." @@ -458,6 +459,13 @@ def forward( "The spatial shape of the moving segmentation should be the same as the spatial shape of the" f" moving image. Got {moving_seg.shape} and {moving.shape} instead." ) + + if fixed_keypoints is not None: + if fixed_keypoints.shape[-1] != self.spatial_dims: + raise ValueError( + "The last dimension of the fixed keypoints should be equal to the number of spatial dimensions." + f" Got {fixed_keypoints.shape[-1]} and {self.spatial_dims} instead." + ) x = self.backbone(torch.cat([moving, fixed], dim=1)) @@ -482,10 +490,16 @@ def forward( if self.half_res: x = F.interpolate(x * 0.5, scale_factor=2.0, mode="trilinear", align_corners=True) - if moving_seg is None: + if moving_seg is None and fixed_keypoints is None: return self.warp(moving, x), x - else: + elif moving_seg is None and fixed_keypoints is not None: + # TODO: implement keypoint warping + pass + elif moving_seg is not None and fixed_keypoints is None: return self.warp(moving, x), self.warp(moving_seg, x), x + else: + # TODO: implement keypoint warping + pass voxelmorph = VoxelMorph From 52311f071c6d1fbda4ea29997c7f9d4208baf10d Mon Sep 17 00:00:00 2001 From: Kaibo Tang Date: Thu, 13 Nov 2025 20:18:03 -0500 Subject: [PATCH 07/12] make `VoxelMorph.forward` and `Warp.forward` able to handle keypoints --- monai/networks/blocks/warp.py | 34 +++++++++++++++++++++++++------ monai/networks/nets/voxelmorph.py | 6 ++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/monai/networks/blocks/warp.py b/monai/networks/blocks/warp.py index 8a570e42c4..3b536800f0 100644 --- a/monai/networks/blocks/warp.py +++ b/monai/networks/blocks/warp.py @@ -110,18 +110,29 @@ def get_reference_grid(self, ddf: torch.Tensor, jitter: bool = False, seed: int self.ref_grid.requires_grad = False return self.ref_grid - def forward(self, image: torch.Tensor, ddf: torch.Tensor): + def forward( + self, image: torch.Tensor, ddf: torch.Tensor, keypoints: torch.Tensor | None = None + ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """ Args: image: Tensor in shape (batch, num_channels, H, W[, D]) ddf: Tensor in the same spatial size as image, in shape (batch, ``spatial_dims``, H, W[, D]) + keypoints: Tensor in shape (batch, N, ``spatial_dims``), optional Returns: warped_image in the same shape as image (batch, num_channels, H, W[, D]) + warped_keypoints in the same shape as keypoints (batch, N, ``spatial_dims``), if keypoints is not None """ + batch_size = image.shape[0] spatial_dims = len(image.shape) - 2 if spatial_dims not in (2, 3): raise NotImplementedError(f"got unsupported spatial_dims={spatial_dims}, currently support 2 or 3.") + if keypoints is not None: + if keypoints.shape[-1] != spatial_dims: + raise ValueError( + f"Given input {spatial_dims}-d image, the last dimension of the input keypoints must be {spatial_dims}, " + f"got {keypoints.shape}." + ) ddf_shape = (image.shape[0], spatial_dims) + tuple(image.shape[2:]) if ddf.shape != ddf_shape: raise ValueError( @@ -136,13 +147,24 @@ def forward(self, image: torch.Tensor, ddf: torch.Tensor): grid[..., i] = grid[..., i] * 2 / (dim - 1) - 1 index_ordering: list[int] = list(range(spatial_dims - 1, -1, -1)) grid = grid[..., index_ordering] # z, y, x -> x, y, z - return F.grid_sample( + warped_image = F.grid_sample( image, grid, mode=self._interp_mode, padding_mode=f"{self._padding_mode}", align_corners=True ) - - # using csrc resampling - return grid_pull(image, grid, bound=self._padding_mode, extrapolate=True, interpolation=self._interp_mode) - + else: + # using csrc resampling + warped_image = grid_pull(image, grid, bound=self._padding_mode, extrapolate=True, interpolation=self._interp_mode) + if keypoints is not None: + with torch.no_grad(): + offset = torch.as_tensor(image.shape[2:]).to(keypoints) / 2.0 + offset = offset.unsqueeze(0).unsqueeze(0) + normalized_keypoints = torch.flip((keypoints - offset) / offset, (-1,)) + ddf_keypoints = F.grid_sample( + ddf, normalized_keypoints.view(batch_size, -1, 1, 1, spatial_dims), + mode=self._interp_mode, padding_mode=f"{self._padding_mode}", align_corners=True + ).view(batch_size, 3, -1).permute((0, 2, 1)) + warped_keypoints = keypoints + ddf_keypoints + return warped_image, warped_keypoints + return warped_image class DVF2DDF(nn.Module): """ diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index 84fdf05eee..3fcb7476cc 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -493,13 +493,11 @@ def forward( if moving_seg is None and fixed_keypoints is None: return self.warp(moving, x), x elif moving_seg is None and fixed_keypoints is not None: - # TODO: implement keypoint warping - pass + return *self.warp(moving, x, fixed_keypoints), x elif moving_seg is not None and fixed_keypoints is None: return self.warp(moving, x), self.warp(moving_seg, x), x else: - # TODO: implement keypoint warping - pass + return self.warp(moving, x), *self.warp(moving_seg, x, fixed_keypoints), x voxelmorph = VoxelMorph From bf43e59b83440cca3e47df10f24a1b10e0464abe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:54:33 +0000 Subject: [PATCH 08/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/networks/nets/voxelmorph.py | 8 ++++---- tests/networks/nets/test_voxelmorph.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index 3fcb7476cc..4a4e575a70 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -441,9 +441,9 @@ def __init__( self.warp = Warp(mode="bilinear", padding_mode="zeros") def forward( - self, - moving: torch.Tensor, - fixed: torch.Tensor, + self, + moving: torch.Tensor, + fixed: torch.Tensor, moving_seg: torch.Tensor | None = None, fixed_keypoints: torch.Tensor | None = None ) -> tuple[torch.Tensor, ...]: @@ -459,7 +459,7 @@ def forward( "The spatial shape of the moving segmentation should be the same as the spatial shape of the" f" moving image. Got {moving_seg.shape} and {moving.shape} instead." ) - + if fixed_keypoints is not None: if fixed_keypoints.shape[-1] != self.spatial_dims: raise ValueError( diff --git a/tests/networks/nets/test_voxelmorph.py b/tests/networks/nets/test_voxelmorph.py index d178c6b6bd..a8a7022c83 100644 --- a/tests/networks/nets/test_voxelmorph.py +++ b/tests/networks/nets/test_voxelmorph.py @@ -275,9 +275,9 @@ def test_shape(self, input_param, input_shape, expected_shape): @parameterized.expand(CASES_SEG) def test_shape_seg( - self, - input_param, - moving_shape, fixed_shape, moving_seg_shape, + self, + input_param, + moving_shape, fixed_shape, moving_seg_shape, expected_warped_moving_shape, expected_warped_moving_seg_shape, expected_ddf_shape ): net = VoxelMorph(**input_param).to(device) From 2631920239c6a9e9e586eae9966ec4bca2affa86 Mon Sep 17 00:00:00 2001 From: Kheil-Z Date: Sat, 15 Nov 2025 16:13:20 +0100 Subject: [PATCH 09/12] Autofix linting and type checks Signed-off-by: Kheil-Z --- monai/networks/blocks/warp.py | 20 +++++++++++++++----- monai/networks/nets/voxelmorph.py | 2 +- tests/networks/nets/test_voxelmorph.py | 9 +++++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/monai/networks/blocks/warp.py b/monai/networks/blocks/warp.py index 3b536800f0..5fd82c19f2 100644 --- a/monai/networks/blocks/warp.py +++ b/monai/networks/blocks/warp.py @@ -152,20 +152,30 @@ def forward( ) else: # using csrc resampling - warped_image = grid_pull(image, grid, bound=self._padding_mode, extrapolate=True, interpolation=self._interp_mode) + warped_image = grid_pull( + image, grid, bound=self._padding_mode, extrapolate=True, interpolation=self._interp_mode + ) if keypoints is not None: with torch.no_grad(): offset = torch.as_tensor(image.shape[2:]).to(keypoints) / 2.0 offset = offset.unsqueeze(0).unsqueeze(0) normalized_keypoints = torch.flip((keypoints - offset) / offset, (-1,)) - ddf_keypoints = F.grid_sample( - ddf, normalized_keypoints.view(batch_size, -1, 1, 1, spatial_dims), - mode=self._interp_mode, padding_mode=f"{self._padding_mode}", align_corners=True - ).view(batch_size, 3, -1).permute((0, 2, 1)) + ddf_keypoints = ( + F.grid_sample( + ddf, + normalized_keypoints.view(batch_size, -1, 1, 1, spatial_dims), + mode=self._interp_mode, + padding_mode=f"{self._padding_mode}", + align_corners=True, + ) + .view(batch_size, 3, -1) + .permute((0, 2, 1)) + ) warped_keypoints = keypoints + ddf_keypoints return warped_image, warped_keypoints return warped_image + class DVF2DDF(nn.Module): """ Layer calculates a dense displacement field (DDF) from a dense velocity field (DVF) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index 4a4e575a70..c695366120 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -445,7 +445,7 @@ def forward( moving: torch.Tensor, fixed: torch.Tensor, moving_seg: torch.Tensor | None = None, - fixed_keypoints: torch.Tensor | None = None + fixed_keypoints: torch.Tensor | None = None, ) -> tuple[torch.Tensor, ...]: if moving.shape != fixed.shape: raise ValueError( diff --git a/tests/networks/nets/test_voxelmorph.py b/tests/networks/nets/test_voxelmorph.py index a8a7022c83..340db771af 100644 --- a/tests/networks/nets/test_voxelmorph.py +++ b/tests/networks/nets/test_voxelmorph.py @@ -277,8 +277,12 @@ def test_shape(self, input_param, input_shape, expected_shape): def test_shape_seg( self, input_param, - moving_shape, fixed_shape, moving_seg_shape, - expected_warped_moving_shape, expected_warped_moving_seg_shape, expected_ddf_shape + moving_shape, + fixed_shape, + moving_seg_shape, + expected_warped_moving_shape, + expected_warped_moving_seg_shape, + expected_ddf_shape, ): net = VoxelMorph(**input_param).to(device) with eval_mode(net): @@ -325,5 +329,6 @@ def test_ill_input_seg_shape(self, input_param, moving_shape, fixed_shape, movin torch.randn(moving_seg_shape).to(device), ) + if __name__ == "__main__": unittest.main() From 930f21bf18335ffc421c8f5835f0d7440f325cba Mon Sep 17 00:00:00 2001 From: Kheil-Z Date: Sat, 15 Nov 2025 16:58:54 +0100 Subject: [PATCH 10/12] Fix segmentation shape validation in VoxelMorph Signed-off-by: Kheil-Z --- monai/networks/nets/voxelmorph.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index c695366120..3195d0fe63 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -454,10 +454,14 @@ def forward( ) if moving_seg is not None: - if moving_seg[-3:] != moving.shape[-3:]: + if moving_seg.shape[0] != moving.shape[0]: raise ValueError( - "The spatial shape of the moving segmentation should be the same as the spatial shape of the" - f" moving image. Got {moving_seg.shape} and {moving.shape} instead." + f"Batch dimension mismatch: moving_seg={moving_seg.shape[0]}, moving={moving.shape[0]}" + ) + if moving_seg.shape[2:] != moving.shape[2:]: + raise ValueError( + "The spatial shape of the moving segmentation must match the spatial shape of the moving image. " + f"Got {moving_seg.shape[2:]} vs {moving.shape[2:]}." ) if fixed_keypoints is not None: From a24fb5e244f8d9fcbe929adbbcd3bc7761ab595a Mon Sep 17 00:00:00 2001 From: Kheil-Z Date: Sat, 15 Nov 2025 17:01:46 +0100 Subject: [PATCH 11/12] Add docstring usage for optional arguments Signed-off-by: Kheil-Z --- monai/networks/nets/voxelmorph.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index 3195d0fe63..0a95564f2a 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -405,6 +405,12 @@ class serves as a wrapper that concatenates the input pair of moving and fixed i fixed = torch.randn(1, 1, 160, 192, 224) warped, ddf = net(moving, fixed) + # Example with optional moving_seg and fixed_keypoints + moving_seg = torch.randint(0, 4, (1, 1, 160, 192, 224)).float() + moving_seg = one_hot(moving_seg, num_classes=4) + fixed_keypoints = torch.tensor([[[80, 96, 112], [40, 48, 56]]]).float() + warped_img, warped_seg, warped_keypoints, ddf = net( moving, fixed, moving_seg=moving_seg, fixed_keypoints=fixed_keypoints ) + """ def __init__( From aaea3f4ad4bd33b04612681ec28b6a3a719240ff Mon Sep 17 00:00:00 2001 From: Kheil-Z Date: Sat, 15 Nov 2025 17:03:40 +0100 Subject: [PATCH 12/12] DCO Remediation Commit for Kheil-Z I, Kheil-Z , hereby add my Signed-off-by to this commit: d22e4fcc38727eb16f7be4b0ada9996c083b8f56 Signed-off-by: Kheil-Z