Source code for pulserver.mrd._images

"""Image-space operations on reconstruction outputs, for NumPy and Torch arrays."""

from __future__ import annotations

__all__ = ["as_numpy", "center_crop", "coil_combine"]

from typing import Any


[docs] def center_crop(image: Any, shape: Any) -> Any: """Crop the trailing axes of an image to a centred window. Parameters ---------- image NumPy or Torch array. shape Extent of each trailing axis to keep; leading axes pass through. Returns ------- array A view of ``image``. Along each axis the sample at index ``n // 2``, the centre of a centred FFT of ``n`` samples, is at index ``size // 2`` of the window. Raises ------ ValueError If ``shape`` has more axes than ``image``, or an extent is not in ``1..image.shape[axis]``. Examples -------- >>> import numpy as np >>> from pulserver.mrd import center_crop >>> image = np.arange(2 * 8).reshape(2, 8) >>> center_crop(image, (4,)).shape (2, 4) >>> center_crop(np.arange(8), (4,)) array([2, 3, 4, 5]) """ extents = tuple(int(size) for size in shape) if len(extents) > image.ndim: raise ValueError(f"shape has {len(extents)} axes, image has {image.ndim}") selection = [slice(None)] * image.ndim for axis, size in enumerate(extents, start=image.ndim - len(extents)): current = image.shape[axis] if not 0 < size <= current: raise ValueError(f"cannot crop axis {axis} of {current} samples to {size}") start = current // 2 - size // 2 selection[axis] = slice(start, start + size) return image[tuple(selection)]
[docs] def coil_combine( coil_images: Any, coil_maps: Any | None = None, *, coil_axis: int = -3, ) -> Any: """Combine the coil axis of a stack of coil images. With sensitivity maps, returns the matched filter ``sum(conj(s) * x)``, complex and not normalised by the map energy. Without maps, returns the root sum of squares, real. Parameters ---------- coil_images NumPy or Torch array with coils along ``coil_axis``. coil_maps Sensitivities broadcastable against ``coil_images``. coil_axis Coil axis; the default fits ``(..., coil, y, x)``. Returns ------- array ``coil_images`` without ``coil_axis``. Examples -------- >>> import numpy as np >>> from pulserver.mrd import coil_combine >>> coil_images = np.ones((4, 8, 8), dtype=complex) >>> float(coil_combine(coil_images)[0, 0]) 2.0 >>> maps = np.full((4, 8, 8), 0.5, dtype=complex) >>> complex(coil_combine(coil_images, maps)[0, 0]) (2+0j) """ if coil_maps is None: squared = (coil_images * coil_images.conj()).real return squared.sum(coil_axis) ** 0.5 return (coil_maps.conj() * coil_images).sum(coil_axis)
[docs] def as_numpy(array: Any) -> Any: """Return an array as NumPy on the host; Torch tensors are detached first. Examples -------- >>> import pulserver.mrd as mrd >>> mrd.as_numpy([1.0, 2.0]) array([1., 2.]) """ import numpy as np detach = getattr(array, "detach", None) if callable(detach): array = detach() return array.cpu().numpy() if hasattr(array, "cpu") else np.asarray(array)