diff --git a/brukerapi/dataset.py b/brukerapi/dataset.py index 2df6dc1..8645782 100644 --- a/brukerapi/dataset.py +++ b/brukerapi/dataset.py @@ -1577,9 +1577,10 @@ def acquisition_affines(self): the reconstruction's ``VisuFGOrderDesc`` records; for ``NI == NSLICES`` object *k* is slice *k*. - :raise: :UnsupportedDatasetType: for anything but a 2-D or 3-D spatial + :raises UnsupportedDatasetType: for anything but a 2-D or 3-D spatial fid/rawdata acquisition, one without a gradient matrix, or a PV360 one without a known subject position + """ if self.type not in ("fid", "rawdata"): raise UnsupportedDatasetType(f"an acquisition affine for {self.path}, which is not a raw acquisition,") diff --git a/docs/source/compatibility.rst b/docs/source/compatibility.rst index 16e5c19..091b53a 100644 --- a/docs/source/compatibility.rst +++ b/docs/source/compatibility.rst @@ -113,3 +113,12 @@ Data contract and limitations ``VisuCoreFrameThickness`` stays the source for the thickness (for a 3-D acquisition that parameter is the whole slab, not a plane step). It is available wherever ``affine_of_package`` is. +* ``Dataset.acquisition_affines()`` generates the acquisition-side equivalent + for 2-D/3-D spatial FID and rawdata datasets, one transform per 2-D slice or + one per 3-D volume. It uses the encoded matrix size rather than the raw sample + array shape and returns the same Visu/DICOM patient frame as ``affine``. + ParaVision 5.1/6/7 gradient matrices already encode the subject position; + PV360 matrices require the declared ``ACQ_patient_pos`` conversion. Method + slice-package geometry is a warning-emitting fallback for missing ``acqp`` + values. Spectroscopy/CSI and incomplete or unknown geometry raise + ``UnsupportedDatasetType``. diff --git a/docs/source/dataset.rst b/docs/source/dataset.rst index dacc658..fc710de 100644 --- a/docs/source/dataset.rst +++ b/docs/source/dataset.rst @@ -36,6 +36,53 @@ Accessing the latter emits ``FutureWarning``; use ``raw`` or ``kspace`` for new code. EPI and non-Cartesian PV360 jobs are intentionally not reconstructed by ``kspace``. +.. _raw-acquisition-geometry: + +Raw acquisition geometry +------------------------ + +Spatial FID and PV360 rawdata datasets can generate affine matrices before an +image has been reconstructed. ``acquisition_affines()`` returns one matrix per +2-D slice; ``acquisition_affine(index)`` returns a selected matrix. A 3-D +acquisition has one matrix whose third column is the partition step. + +.. code-block:: python + + acquisition = Dataset("path/to/fid") + affines = acquisition.acquisition_affines() + first_slice_affine = acquisition.acquisition_affine(0) + +Each 4x4 matrix maps a voxel index in the image produced by Fourier transforming +the encoded k-space matrix to millimetres in the Visu/DICOM patient frame: + +.. code-block:: python + + patient_position = first_slice_affine @ [i, j, 0, 1] + +The first two columns contain the read and phase voxel steps, the third contains +the slice or partition step, and the last column locates voxel ``(0, 0, 0)``. +This is the same patient frame used by a reconstructed 2dseq +``Dataset.affine``, so acquisition and reconstruction geometry can be compared +directly. To produce a NIfTI RAS affine, negate the patient x and y axes: + +.. code-block:: python + + import numpy as np + + nifti_affine = np.diag([-1, -1, 1, 1]) @ first_slice_affine + +Geometry is derived primarily from ``ACQ_grad_matrix``, the ``ACQ_*_offset`` +values and ``ACQ_fov``. Method slice-package parameters are used as fallbacks +when acquisition parameters are absent, with a ``RuntimeWarning`` because they +are not equivalent for every ParaVision version. Spectroscopy/CSI, non-spatial +or non-2-D/3-D acquisitions, missing orientation metadata, and PV360 data with +an unknown subject position raise ``UnsupportedDatasetType``. + +The returned list is indexed by physical slice, not by every acquisition +object. If echoes, repetitions, or other dimensions make ``NI`` differ from +``NSLICES``, their object-to-slice nesting can only be recovered from the +reconstruction metadata. + Metadata views -------------- diff --git a/docs/source/tutorials/how-to-fid.rst b/docs/source/tutorials/how-to-fid.rst index 5d62735..43cbac9 100644 --- a/docs/source/tutorials/how-to-fid.rst +++ b/docs/source/tutorials/how-to-fid.rst @@ -58,6 +58,19 @@ metadata. An explicit override is available when inference is ambiguous: dataset = Dataset('path/to/fid', scheme_id='RADIAL') +For a 2-D or 3-D spatial acquisition, generate the geometry of the image that +a Fourier transform of the encoded k-space matrix would produce: + +.. code-block:: python + + slice_affines = dataset.acquisition_affines() + first_slice_affine = dataset.acquisition_affine(0) + +The matrices map voxel indices to millimetres in the Visu/DICOM patient frame, +the same frame as a reconstructed 2dseq ``affine``. There is one matrix per +2-D slice and one for a 3-D volume. See :ref:`raw-acquisition-geometry` for the +coordinate convention, limitations, and NIfTI conversion. + Random-access ``mmap=True`` is currently supported for ``2dseq`` only. Load a FID normally, then select the desired k-space array slice. diff --git a/docs/source/tutorials/how-to-rawdata.rst b/docs/source/tutorials/how-to-rawdata.rst index 26db5f1..1c4b90b 100644 --- a/docs/source/tutorials/how-to-rawdata.rst +++ b/docs/source/tutorials/how-to-rawdata.rst @@ -27,6 +27,19 @@ metadata-reordered array. BART consumers can request its 16-axis layout: kspace = rawdata.kspace bart_kspace = rawdata.to_kspace(bart=True) +Generate patient-space geometry for a spatial rawdata acquisition in the same +way as for a FID: + +.. code-block:: python + + slice_affines = rawdata.acquisition_affines() + first_slice_affine = rawdata.acquisition_affine(0) + +The declared ``ACQ_patient_pos`` is applied when converting PV360 gradient +directions to the Visu/DICOM patient frame. An unknown position raises +``UnsupportedDatasetType``. See :ref:`raw-acquisition-geometry` for the full +coordinate convention and limitations. + ``rawdata.data`` is retained for compatibility and exposes the historical decoded job layout. It emits ``FutureWarning`` because that layout differs from FID ``data``. Prefer ``raw`` or ``kspace`` in new applications. EPI and