To make column norm of `affine` the same as `scale`. If diagonal is False, returns an affine that combines orthogonal rotation and the new scale. This is done by first decomposing `affine`, then setting the zoom factors to `scale`, and composing a new affine; the shearing factors a
(affine: np.ndarray, scale: np.ndarray | Sequence[float], diagonal: bool = True)
| 796 | |
| 797 | |
| 798 | def zoom_affine(affine: np.ndarray, scale: np.ndarray | Sequence[float], diagonal: bool = True): |
| 799 | """ |
| 800 | To make column norm of `affine` the same as `scale`. If diagonal is False, |
| 801 | returns an affine that combines orthogonal rotation and the new scale. |
| 802 | This is done by first decomposing `affine`, then setting the zoom factors to |
| 803 | `scale`, and composing a new affine; the shearing factors are removed. If |
| 804 | diagonal is True, returns a diagonal matrix, the scaling factors are set |
| 805 | to the diagonal elements. This function always return an affine with zero |
| 806 | translations. |
| 807 | |
| 808 | Args: |
| 809 | affine (nxn matrix): a square matrix. |
| 810 | scale: new scaling factor along each dimension. if the components of the `scale` are non-positive values, |
| 811 | will use the corresponding components of the original pixdim, which is computed from the `affine`. |
| 812 | diagonal: whether to return a diagonal scaling matrix. |
| 813 | Defaults to True. |
| 814 | |
| 815 | Raises: |
| 816 | ValueError: When ``affine`` is not a square matrix. |
| 817 | ValueError: When ``scale`` contains a nonpositive scalar. |
| 818 | |
| 819 | Returns: |
| 820 | the updated `n x n` affine. |
| 821 | |
| 822 | """ |
| 823 | |
| 824 | affine = np.array(affine, dtype=float, copy=True) |
| 825 | if len(affine) != len(affine[0]): |
| 826 | raise ValueError(f"affine must be n x n, got {len(affine)} x {len(affine[0])}.") |
| 827 | scale_np = np.array(scale, dtype=float, copy=True) |
| 828 | |
| 829 | d = len(affine) - 1 |
| 830 | # compute original pixdim |
| 831 | norm = affine_to_spacing(affine, r=d) |
| 832 | if len(scale_np) < d: # defaults based on affine |
| 833 | scale_np = np.append(scale_np, norm[len(scale_np) :]) |
| 834 | scale_np = scale_np[:d] |
| 835 | scale_np = np.asarray(fall_back_tuple(scale_np, norm)) |
| 836 | |
| 837 | scale_np[scale_np == 0] = 1.0 |
| 838 | if diagonal: |
| 839 | return np.diag(np.append(scale_np, [1.0])) |
| 840 | rzs = affine[:-1, :-1] # rotation zoom scale |
| 841 | zs = np.linalg.cholesky(rzs.T @ rzs).T |
| 842 | rotation = rzs @ np.linalg.inv(zs) |
| 843 | s = np.sign(np.diag(zs)) * np.abs(scale_np) |
| 844 | # construct new affine with rotation and zoom |
| 845 | new_affine = np.eye(len(affine)) |
| 846 | new_affine[:-1, :-1] = rotation @ np.diag(s) |
| 847 | return new_affine |
| 848 | |
| 849 | |
| 850 | def compute_shape_offset( |
searching dependent graphs…