|
23 | 23 | """Image processing tools.""" |
24 | 24 |
|
25 | 25 |
|
| 26 | +def deoblique_and_zooms(in_reference, oblique, factor=4, padding=1): |
| 27 | + """ |
| 28 | + Generate a sampling reference aligned with in_reference fully covering oblique. |
| 29 | +
|
| 30 | + Parameters |
| 31 | + ---------- |
| 32 | + in_reference : :obj:`~nibabel.spatialimages.SpatialImage` |
| 33 | + The sampling reference. |
| 34 | + oblique : :obj:`~nibabel.spatialimages.SpatialImage` |
| 35 | + The rotated coordinate system whose extent should be fully covered by the |
| 36 | + generated reference. |
| 37 | + factor : :obj:`int` |
| 38 | + A factor to increase the resolution of the generated reference. |
| 39 | + padding : :obj:`int` |
| 40 | + Number of additional voxels around the most extreme positions of the projection of |
| 41 | + oblique on to the reference. |
| 42 | +
|
| 43 | + """ |
| 44 | + from itertools import product |
| 45 | + import numpy as np |
| 46 | + from nibabel.affines import apply_affine, rescale_affine |
| 47 | + |
| 48 | + # Reference space metadata |
| 49 | + hdr = in_reference.header.copy() |
| 50 | + affine = in_reference.affine |
| 51 | + ref_shape = np.array(in_reference.shape[:3]) |
| 52 | + ref_zooms = np.array(hdr.get_zooms()[:3]) |
| 53 | + _, scode = in_reference.get_sform(coded=True) |
| 54 | + _, qcode = in_reference.get_qform(coded=True) |
| 55 | + |
| 56 | + # Calculate the 8 most extreme coordinates of oblique in in_reference space |
| 57 | + extent = apply_affine( |
| 58 | + oblique.affine, |
| 59 | + np.array(list(product((0, 1), repeat=3))) * (ref_shape - 1), |
| 60 | + ) |
| 61 | + extent_ijk = apply_affine(np.linalg.inv(affine), extent) |
| 62 | + |
| 63 | + if isinstance(padding, int): |
| 64 | + padding = tuple([padding] * 3) |
| 65 | + |
| 66 | + underflow = np.clip(extent_ijk.min(0) - padding, None, 0).astype(int) |
| 67 | + overflow = np.ceil(np.clip(extent_ijk.max(0) + padding + 1 - ref_shape, 0, None)).astype(int) |
| 68 | + if np.any(underflow < 0) or np.any(overflow > 0): |
| 69 | + # Add under/overflow voxels |
| 70 | + ref_shape += np.abs(underflow) + overflow |
| 71 | + # Consistently update origin |
| 72 | + affine[:-1, -1] = apply_affine(affine, underflow) |
| 73 | + |
| 74 | + # Make grid denser |
| 75 | + if abs(1.0 - factor) > 1e-5: |
| 76 | + new_shape = np.rint(ref_shape * factor) |
| 77 | + affine = rescale_affine(affine, ref_shape, ref_zooms / factor, new_shape) |
| 78 | + ref_shape = new_shape |
| 79 | + |
| 80 | + # Generate new reference |
| 81 | + hdr.set_sform(affine, scode) |
| 82 | + hdr.set_qform(affine, qcode) |
| 83 | + |
| 84 | + return in_reference.__class__( |
| 85 | + np.zeros(ref_shape.astype(int), dtype=hdr.get_data_dtype()), |
| 86 | + affine, |
| 87 | + hdr, |
| 88 | + ) |
| 89 | + |
| 90 | + |
26 | 91 | def resample_to_zooms(in_file, zooms, order=3, prefilter=True): |
27 | 92 | """Resample the input data to a new grid with the requested zooms.""" |
28 | 93 | from pathlib import Path |
|
0 commit comments