From 88973568c07ce8e269c6dec543c8f4d63ca878d5 Mon Sep 17 00:00:00 2001 From: weinmusn Date: Fri, 28 Aug 2026 11:28:10 +0200 Subject: [PATCH 1/3] Connect size and affine parameter to avoid errors --- .../MRzeroCore/phantom/voxel_grid_phantom.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/python/MRzeroCore/phantom/voxel_grid_phantom.py b/python/MRzeroCore/phantom/voxel_grid_phantom.py index 595fb1b..f4c1ff3 100644 --- a/python/MRzeroCore/phantom/voxel_grid_phantom.py +++ b/python/MRzeroCore/phantom/voxel_grid_phantom.py @@ -139,12 +139,40 @@ def __init__( if self.tissue_masks is None: self.tissue_masks = {} self.coil_sens = torch.as_tensor(coil_sens, dtype=torch.complex64) - self.size = torch.as_tensor(size, dtype=torch.float32) + self._size = torch.as_tensor(size, dtype=torch.float32) self.affine = torch.as_tensor(affine, dtype=torch.float32) self.phantom_motion = phantom_motion self.voxel_motion = voxel_motion + + @property + def size(self): + shape = torch.tensor(self.PD.shape, dtype=torch.float32) + return shape * torch.linalg.norm(self.affine[:3, :3], dim=0) / 1000 + + @size.setter + def size(self, value): + value = torch.as_tensor(value, dtype=torch.float32) + shape = torch.tensor(self.PD.shape, dtype=torch.float32) + + old_affine = self.affine + directions = old_affine[:3, :3] + old_norms = torch.linalg.norm(directions, dim=0) + unit_dirs = directions / old_norms + + voxel_size = value / shape * 1000 + new_affine = old_affine.clone() + new_affine[:3, :3] = unit_dirs * voxel_size + + # Corner-referenced translation: for even resolutions the shift is exactly + # FOV/2, for odd resolutions it's FOV/2 - voxel_size/2 + half_index = torch.floor(shape / 2) + shift = half_index * voxel_size + new_affine[:3, 3] = -unit_dirs @ shift + + self.affine = new_affine + def build(self, PD_threshold: float = 1e-6, voxel_shape: Literal["sinc", "box", "point"] = "sinc" ) -> SimData: From ce0af6938fce027f6aa1ad1ca3bb3bbaaf414516 Mon Sep 17 00:00:00 2001 From: weinmusn Date: Fri, 28 Aug 2026 11:42:40 +0200 Subject: [PATCH 2/3] Remove size variable from voxel_grid_phantom --- python/MRzeroCore/phantom/custom_voxel_phantom.py | 2 +- python/MRzeroCore/phantom/tissue_dict.py | 3 --- python/MRzeroCore/phantom/voxel_grid_phantom.py | 13 +------------ 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/python/MRzeroCore/phantom/custom_voxel_phantom.py b/python/MRzeroCore/phantom/custom_voxel_phantom.py index 65bc84a..a0426df 100644 --- a/python/MRzeroCore/phantom/custom_voxel_phantom.py +++ b/python/MRzeroCore/phantom/custom_voxel_phantom.py @@ -220,7 +220,7 @@ def build_dephasing_func(shape: str, size: torch.Tensor, elif shape == "gauss": return lambda t, _: gauss(t, size) else: - raise ValueError("shape not implemented:", self.voxel_shape) + raise ValueError("shape not implemented:", shape) def recover(voxel_size: torch.Tensor, voxel_shape: str, sim_data: SimData diff --git a/python/MRzeroCore/phantom/tissue_dict.py b/python/MRzeroCore/phantom/tissue_dict.py index f7cafa9..b5e0298 100644 --- a/python/MRzeroCore/phantom/tissue_dict.py +++ b/python/MRzeroCore/phantom/tissue_dict.py @@ -248,7 +248,6 @@ def to_full(sparse): to_full(sim_data.B0[tissue_begin:tissue_end]), to_full(sim_data.B1[:, tissue_begin:tissue_end]), to_full(sim_data.coil_sens[:, tissue_begin:tissue_end]), - sim_data.size, sim_data.affine, ) ) @@ -322,7 +321,6 @@ def lp(cfg): def rs(arr): return _resample_nifti(arr, nifti_affine, target_shape, aff_mm) - size = target_shape * np.linalg.norm(aff_mm[:3, :3], axis=0) /1000 # np.abs(target_shape @ aff_mm[:3, :3]) / 1000 density = rs(density) T1, T2, T2dash, ADC, B0 = rs(T1), rs(T2), rs(T2dash), rs(ADC), rs(B0) B1 = [rs(b) for b in B1] @@ -330,7 +328,6 @@ def rs(arr): return VoxelGridPhantom( PD=torch.as_tensor(density), - size=torch.as_tensor(size), T1=torch.as_tensor(T1), T2=torch.as_tensor(T2), T2dash=torch.as_tensor(T2dash), diff --git a/python/MRzeroCore/phantom/voxel_grid_phantom.py b/python/MRzeroCore/phantom/voxel_grid_phantom.py index f4c1ff3..f2284c1 100644 --- a/python/MRzeroCore/phantom/voxel_grid_phantom.py +++ b/python/MRzeroCore/phantom/voxel_grid_phantom.py @@ -99,8 +99,6 @@ class VoxelGridPhantom: (coil_count, sx, sy, sz) tensor of RF coil profiles coil_sens : torch.Tensor (coil_count, sx, sy, sz) tensor of coil sensitivities - size : torch.Tensor - Size of the data, in meters. affine : torch.Tensor Affine matrix of the phantom data, in millimeters. tissue_masks : Dict[str, torch.Tensor] | None @@ -117,7 +115,6 @@ def __init__( B0: torch.Tensor, B1: torch.Tensor, coil_sens: torch.Tensor, - size: torch.Tensor, affine: torch.Tensor, phantom_motion=None, voxel_motion=None, @@ -139,7 +136,6 @@ def __init__( if self.tissue_masks is None: self.tissue_masks = {} self.coil_sens = torch.as_tensor(coil_sens, dtype=torch.complex64) - self._size = torch.as_tensor(size, dtype=torch.float32) self.affine = torch.as_tensor(affine, dtype=torch.float32) self.phantom_motion = phantom_motion @@ -279,7 +275,7 @@ def load(cls, file_name: str) -> VoxelGridPhantom: return cls( PD, T1, T2, T2dash, D, B0, B1, - torch.ones(1, *PD.shape), size, affine, + torch.ones(1, *PD.shape), affine, tissue_masks=tissue_masks ) @@ -356,7 +352,6 @@ def load_mat( data[..., 3], # B0 data[..., 4][None, ...], # B1 coil_sens=torch.ones(1, *data.shape[:-1]), - size=torch.as_tensor(size), affine=affine, ) @@ -391,8 +386,6 @@ def select_multicoil(tensor: torch.Tensor): # reindexed to start at 0, so shift the origin to the first selected # slice's world position (assumes contiguous slices; for a sparse list # slices[0] is used as the reference). - new_size = self.size.clone() - new_size[2] = self.size[2] * len(slices) / self.PD.shape[2] new_affine = self.affine.clone() new_affine[:3, 3] = self.affine[:3, 3] + self.affine[:3, 2] * slices[0] @@ -405,7 +398,6 @@ def select_multicoil(tensor: torch.Tensor): select(self.B0), select_multicoil(self.B1), select_multicoil(self.coil_sens), - new_size, new_affine, tissue_masks={ key: mask[..., slices] for key, mask in self.tissue_masks.items() @@ -448,7 +440,6 @@ def scale(map: torch.Tensor) -> torch.Tensor: scale(self.B0), scale(self.B1.squeeze()).unsqueeze(0), scale(self.coil_sens.squeeze()).unsqueeze(0), - self.size.clone(), rescale_affine(self.affine, self.PD.shape, (x, y, z)), tissue_masks={ key: scale(mask) for key, mask in self.tissue_masks.items() @@ -531,7 +522,6 @@ def resample_masks(tensors: Dict) -> Optional[Dict]: resample(self.B0), resample_multicoil(self.B1), resample_multicoil(self.coil_sens), - self.size.clone(), rescale_affine(self.affine, self.PD.shape, (x, y, z)), tissue_masks=resample_masks(self.tissue_masks) ) @@ -667,7 +657,6 @@ def to_full(sparse): to_full(sim_data.B0), to_full(sim_data.B1), to_full(sim_data.coil_sens), - sim_data.size, sim_data.affine, ) From 8b65e8c0c5c3fd34f585c7e908eecd3c205aaedf Mon Sep 17 00:00:00 2001 From: J-Endres Date: Fri, 28 Aug 2026 12:20:35 +0200 Subject: [PATCH 3/3] publish to v1.0.6 --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- documentation/src/api-phantom.md | 2 +- pyproject.toml | 2 +- python/MRzeroCore/phantom/voxel_grid_phantom.py | 9 ++++----- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d15e7b..063a57f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- 1.0.6 + - Replace `VoxelGridPhantom.size` attribute with a property that sets `affine` correctly - 1.0.5 - fix .seq soft-delays: default to 0.0 for missing values, add documentation - 1.0.4 diff --git a/Cargo.lock b/Cargo.lock index d7f6a7d..84b8105 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "mrzero_core" -version = "1.0.5" +version = "1.0.6" dependencies = [ "num-complex", "pulseq-rs", diff --git a/Cargo.toml b/Cargo.toml index 20ad512..8ae9434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrzero_core" -version = "1.0.5" +version = "1.0.6" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/documentation/src/api-phantom.md b/documentation/src/api-phantom.md index 2fe3175..8d4215a 100644 --- a/documentation/src/api-phantom.md +++ b/documentation/src/api-phantom.md @@ -42,7 +42,7 @@ Contains the data used for simulation (together with the [`Sequence`](api-sequen ```python # Usually not constructed directly but via the `.build()` function on a phantom -SimData(PD, T1, T2, T2dash, D, B0, B1, coil_sens, size, voxel_pos, nyquist, dephasing_func, recover_func=None, phantom_motion=None, voxel_motion=None, tissue_masks=None) +SimData(PD, T1, T2, T2dash, D, B0, B1, coil_sens, voxel_pos, nyquist, dephasing_func, recover_func=None, phantom_motion=None, voxel_motion=None, tissue_masks=None) ``` | Parameter | Description | diff --git a/pyproject.toml b/pyproject.toml index 390e875..a37e5cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "mrzerocore" -version = "1.0.5" +version = "1.0.6" description = "Core functionality of MRzero" authors = [ {name = "Jonathan Endres", email = "jonathan.endres@uk-erlangen.de"}, diff --git a/python/MRzeroCore/phantom/voxel_grid_phantom.py b/python/MRzeroCore/phantom/voxel_grid_phantom.py index f2284c1..96e8a25 100644 --- a/python/MRzeroCore/phantom/voxel_grid_phantom.py +++ b/python/MRzeroCore/phantom/voxel_grid_phantom.py @@ -150,25 +150,24 @@ def size(self): def size(self, value): value = torch.as_tensor(value, dtype=torch.float32) shape = torch.tensor(self.PD.shape, dtype=torch.float32) - + old_affine = self.affine directions = old_affine[:3, :3] old_norms = torch.linalg.norm(directions, dim=0) unit_dirs = directions / old_norms - + voxel_size = value / shape * 1000 new_affine = old_affine.clone() new_affine[:3, :3] = unit_dirs * voxel_size - + # Corner-referenced translation: for even resolutions the shift is exactly # FOV/2, for odd resolutions it's FOV/2 - voxel_size/2 half_index = torch.floor(shape / 2) shift = half_index * voxel_size new_affine[:3, 3] = -unit_dirs @ shift - + self.affine = new_affine - def build(self, PD_threshold: float = 1e-6, voxel_shape: Literal["sinc", "box", "point"] = "sinc" ) -> SimData: