v2.0.0
Loading...
Searching...
No Matches
mri_slicer.cpp
Go to the documentation of this file.
1//=============================================================================================================
24
25//=============================================================================================================
26// INCLUDES
27//=============================================================================================================
28
29#include "mri_slicer.h"
30#include "mri_vol_data.h"
31
32#include <Eigen/LU>
33
34#include <algorithm>
35#include <array>
36#include <cmath>
37
38//=============================================================================================================
39// USED NAMESPACES
40//=============================================================================================================
41
42using namespace MRILIB;
43using namespace Eigen;
44
45namespace {
46
47struct PlaneSpec
48{
49 int columnAxis;
50 int rowAxis;
51 int fixedAxis;
52};
53
54Vector3f anatomicalNormal(SliceOrientation orientation)
55{
56 switch (orientation) {
57 case SliceOrientation::Axial: return Vector3f::UnitZ();
58 case SliceOrientation::Coronal: return Vector3f::UnitY();
59 case SliceOrientation::Sagittal: return Vector3f::UnitX();
60 }
61 return Vector3f::UnitZ();
62}
63
64Vector3f anatomicalColumnDirection(SliceOrientation orientation)
65{
66 switch (orientation) {
67 case SliceOrientation::Axial: return Vector3f::UnitX();
68 case SliceOrientation::Coronal: return Vector3f::UnitX();
69 case SliceOrientation::Sagittal: return Vector3f::UnitY();
70 }
71 return Vector3f::UnitX();
72}
73
74Vector3f anatomicalRowDirection(SliceOrientation orientation)
75{
76 switch (orientation) {
77 case SliceOrientation::Axial: return Vector3f::UnitY();
78 case SliceOrientation::Coronal: return Vector3f::UnitZ();
79 case SliceOrientation::Sagittal: return Vector3f::UnitZ();
80 }
81 return Vector3f::UnitY();
82}
83
84int bestVoxelAxisForDirection(const Matrix4f& vox2ras,
85 const Vector3f& target,
86 const std::array<bool, 3>& used)
87{
88 int bestAxis = 0;
89 float bestScore = -1.0f;
90
91 for (int axis = 0; axis < 3; ++axis) {
92 if (used[axis]) {
93 continue;
94 }
95 Vector3f axisDirection = vox2ras.block<3, 1>(0, axis);
96 const float norm = axisDirection.norm();
97 if (norm > 0.0f) {
98 axisDirection /= norm;
99 }
100 const float score = std::abs(axisDirection.dot(target));
101 if (score > bestScore) {
102 bestScore = score;
103 bestAxis = axis;
104 }
105 }
106
107 return bestAxis;
108}
109
110PlaneSpec planeSpecForOrientation(const Matrix4f& vox2ras,
111 SliceOrientation orientation)
112{
113 std::array<bool, 3> used = {false, false, false};
114 const int fixedAxis = bestVoxelAxisForDirection(vox2ras,
115 anatomicalNormal(orientation),
116 used);
117 used[fixedAxis] = true;
118
119 const int columnAxis = bestVoxelAxisForDirection(vox2ras,
120 anatomicalColumnDirection(orientation),
121 used);
122 used[columnAxis] = true;
123
124 const int rowAxis = bestVoxelAxisForDirection(vox2ras,
125 anatomicalRowDirection(orientation),
126 used);
127
128 return {columnAxis, rowAxis, fixedAxis};
129}
130
131int axisDimension(const QVector<int>& dims, int axis)
132{
133 return dims[axis];
134}
135
136int flatIndex(int x, int y, int z, int dimX, int dimY)
137{
138 return x + dimX * (y + dimY * z);
139}
140
141} // anonymous namespace
142
143//=============================================================================================================
144// STATIC METHODS
145//=============================================================================================================
146
148 const QVector<float>& volData,
149 const QVector<int>& dims,
150 const Matrix4f& vox2ras,
151 SliceOrientation orientation,
152 int sliceIndex)
153{
154 const int dimX = dims[0];
155 const int dimY = dims[1];
156 const PlaneSpec spec = planeSpecForOrientation(vox2ras, orientation);
157 const int width = axisDimension(dims, spec.columnAxis);
158 const int height = axisDimension(dims, spec.rowAxis);
159 const int fixedDim = axisDimension(dims, spec.fixedAxis);
160
161 sliceIndex = std::clamp(sliceIndex, 0, fixedDim - 1);
162
163 MriSliceImage result;
164 result.orientation = orientation;
165 result.width = width;
166 result.height = height;
167 result.sliceIndex = sliceIndex;
168 result.pixels.resize(width, height);
169
170 for (int row = 0; row < height; ++row) {
171 for (int col = 0; col < width; ++col) {
172 std::array<int, 3> voxel = {0, 0, 0};
173 voxel[spec.columnAxis] = col;
174 voxel[spec.rowAxis] = row;
175 voxel[spec.fixedAxis] = sliceIndex;
176 result.pixels(col, row) = volData[flatIndex(voxel[0], voxel[1], voxel[2], dimX, dimY)];
177 }
178 }
179
180 result.sliceToRas = Matrix4f::Zero();
181 result.sliceToRas.col(0) = vox2ras.col(spec.columnAxis);
182 result.sliceToRas.col(1) = vox2ras.col(spec.rowAxis);
183 result.sliceToRas.col(2) = vox2ras.col(spec.fixedAxis);
184 Vector4f origin = Vector4f::Zero();
185 origin(spec.fixedAxis) = static_cast<float>(sliceIndex);
186 origin.w() = 1.0f;
187 result.sliceToRas.col(3) = vox2ras * origin;
188
189 // Normalize pixels to [0, 1]
190 float minVal = result.pixels.minCoeff();
191 float maxVal = result.pixels.maxCoeff();
192 if (maxVal > minVal) {
193 result.pixels = (result.pixels.array() - minVal) / (maxVal - minVal);
194 } else {
195 result.pixels.setZero();
196 }
197
198 return result;
199}
200
201//=============================================================================================================
202
203int MriSlicer::voxelAxisForOrientation(const Matrix4f& vox2ras,
204 SliceOrientation orientation)
205{
206 return planeSpecForOrientation(vox2ras, orientation).fixedAxis;
207}
208
209//=============================================================================================================
210
211int MriSlicer::dimensionForOrientation(const QVector<int>& dims,
212 const Matrix4f& vox2ras,
213 SliceOrientation orientation)
214{
215 return axisDimension(dims, voxelAxisForOrientation(vox2ras, orientation));
216}
217
218//=============================================================================================================
219
220int MriSlicer::sliceIndexForOrientation(const Matrix4f& vox2ras,
221 SliceOrientation orientation,
222 const Vector3i& voxel)
223{
224 return voxel(voxelAxisForOrientation(vox2ras, orientation));
225}
226
227//=============================================================================================================
228
229QVector<MriSliceImage> MriSlicer::extractOrthogonal(
230 const QVector<float>& volData,
231 const QVector<int>& dims,
232 const Matrix4f& vox2ras,
233 const Vector3f& rasPoint)
234{
235 Vector3i voxel = rasToVoxel(vox2ras, rasPoint);
236
237 QVector<MriSliceImage> slices;
238 slices.reserve(3);
239 slices.append(extractSlice(volData, dims, vox2ras, SliceOrientation::Axial,
241 slices.append(extractSlice(volData, dims, vox2ras, SliceOrientation::Coronal,
243 slices.append(extractSlice(volData, dims, vox2ras, SliceOrientation::Sagittal,
245
246 return slices;
247}
248
249//=============================================================================================================
250
251Vector3i MriSlicer::rasToVoxel(const Matrix4f& vox2ras,
252 const Vector3f& rasPoint)
253{
254 Matrix4f ras2vox = vox2ras.inverse();
255 Vector4f rasH;
256 rasH << rasPoint, 1.0f;
257 Vector4f voxH = ras2vox * rasH;
258
259 return Vector3i(
260 static_cast<int>(std::round(voxH.x())),
261 static_cast<int>(std::round(voxH.y())),
262 static_cast<int>(std::round(voxH.z()))
263 );
264}
265
266//=============================================================================================================
267
268Vector3f MriSlicer::voxelToRas(const Matrix4f& vox2ras,
269 const Vector3i& voxel)
270{
271 Vector4f voxH;
272 voxH << static_cast<float>(voxel.x()),
273 static_cast<float>(voxel.y()),
274 static_cast<float>(voxel.z()),
275 1.0f;
276 Vector4f rasH = vox2ras * voxH;
277
278 return rasH.head<3>();
279}
280
281//=============================================================================================================
282// MriVolData convenience overloads
283//=============================================================================================================
284
286 SliceOrientation orientation,
287 int sliceIndex)
288{
289 return extractSlice(vol.voxelDataAsFloat(), vol.dims(),
290 vol.computeVox2RasTkr(), orientation, sliceIndex);
291}
292
293//=============================================================================================================
294
296 SliceOrientation orientation)
297{
298 return voxelAxisForOrientation(vol.computeVox2RasTkr(), orientation);
299}
300
301//=============================================================================================================
302
304 SliceOrientation orientation)
305{
306 return dimensionForOrientation(vol.dims(), vol.computeVox2RasTkr(), orientation);
307}
308
309//=============================================================================================================
310
312 SliceOrientation orientation,
313 const Vector3i& voxel)
314{
315 return sliceIndexForOrientation(vol.computeVox2RasTkr(), orientation, voxel);
316}
317
318//=============================================================================================================
319
320QVector<MriSliceImage> MriSlicer::extractOrthogonal(const MriVolData& vol,
321 const Vector3f& rasPoint)
322{
323 return extractOrthogonal(vol.voxelDataAsFloat(), vol.dims(),
324 vol.computeVox2RasTkr(), rasPoint);
325}
326
327//=============================================================================================================
328
329Vector3i MriSlicer::rasToVoxel(const MriVolData& vol,
330 const Vector3f& rasPoint)
331{
332 return rasToVoxel(vol.computeVox2RasTkr(), rasPoint);
333}
334
335//=============================================================================================================
336
337Vector3f MriSlicer::voxelToRas(const MriVolData& vol,
338 const Vector3i& voxel)
339{
340 return voxelToRas(vol.computeVox2RasTkr(), voxel);
341}
Orthogonal-plane resampler that turns a 3D MriVolData into the 2D textures consumed by the slice view...
Format-agnostic in-memory representation of a 3D MRI volume plus its slice decomposition.
Volume I/O, voxel geometry and slice resampling for structural MRI data inside mne-cpp.
SliceOrientation
Definition mri_slicer.h:75
Single 2D MRI cross-section produced by MriSlicer (pixel buffer + RAS metadata).
Definition mri_slicer.h:87
Eigen::Matrix4f sliceToRas
Definition mri_slicer.h:93
SliceOrientation orientation
Definition mri_slicer.h:91
Eigen::MatrixXf pixels
Definition mri_slicer.h:88
static Eigen::Vector3f voxelToRas(const Eigen::Matrix4f &vox2ras, const Eigen::Vector3i &voxel)
static MriSliceImage extractSlice(const QVector< float > &volData, const QVector< int > &dims, const Eigen::Matrix4f &vox2ras, SliceOrientation orientation, int sliceIndex)
static Eigen::Vector3i rasToVoxel(const Eigen::Matrix4f &vox2ras, const Eigen::Vector3f &rasPoint)
static int voxelAxisForOrientation(const Eigen::Matrix4f &vox2ras, SliceOrientation orientation)
static int sliceIndexForOrientation(const Eigen::Matrix4f &vox2ras, SliceOrientation orientation, const Eigen::Vector3i &voxel)
static QVector< MriSliceImage > extractOrthogonal(const QVector< float > &volData, const QVector< int > &dims, const Eigen::Matrix4f &vox2ras, const Eigen::Vector3f &rasPoint)
static int dimensionForOrientation(const QVector< int > &dims, const Eigen::Matrix4f &vox2ras, SliceOrientation orientation)
Format-agnostic 3D MRI volume: header geometry, voxel buffer (as a vector of MriSlice),...
Eigen::Matrix4f computeVox2RasTkr() const
QVector< int > dims() const
QVector< float > voxelDataAsFloat() const