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{
47
48struct PlaneSpec
49{
50 int columnAxis;
51 int rowAxis;
52 int fixedAxis;
53};
54
55Vector3f anatomicalNormal(SliceOrientation orientation)
56{
57 switch (orientation) {
59 return Vector3f::UnitZ();
61 return Vector3f::UnitY();
63 return Vector3f::UnitX();
64 }
65 return Vector3f::UnitZ();
66}
67
68Vector3f anatomicalColumnDirection(SliceOrientation orientation)
69{
70 switch (orientation) {
72 return Vector3f::UnitX();
74 return Vector3f::UnitX();
76 return Vector3f::UnitY();
77 }
78 return Vector3f::UnitX();
79}
80
81Vector3f anatomicalRowDirection(SliceOrientation orientation)
82{
83 switch (orientation) {
85 return Vector3f::UnitY();
87 return Vector3f::UnitZ();
89 return Vector3f::UnitZ();
90 }
91 return Vector3f::UnitY();
92}
93
94int bestVoxelAxisForDirection(const Matrix4f& vox2ras,
95 const Vector3f& target,
96 const std::array<bool, 3>& used)
97{
98 int bestAxis = 0;
99 float bestScore = -1.0f;
100
101 for (int axis = 0; axis < 3; ++axis) {
102 if (used[axis]) {
103 continue;
104 }
105 Vector3f axisDirection = vox2ras.block<3, 1>(0, axis);
106 const float norm = axisDirection.norm();
107 if (norm > 0.0f) {
108 axisDirection /= norm;
109 }
110 const float score = std::abs(axisDirection.dot(target));
111 if (score > bestScore) {
112 bestScore = score;
113 bestAxis = axis;
114 }
115 }
116
117 return bestAxis;
118}
119
120PlaneSpec planeSpecForOrientation(const Matrix4f& vox2ras,
121 SliceOrientation orientation)
122{
123 std::array<bool, 3> used = {false, false, false};
124 const int fixedAxis = bestVoxelAxisForDirection(vox2ras,
125 anatomicalNormal(orientation),
126 used);
127 used[fixedAxis] = true;
128
129 const int columnAxis = bestVoxelAxisForDirection(vox2ras,
130 anatomicalColumnDirection(orientation),
131 used);
132 used[columnAxis] = true;
133
134 const int rowAxis = bestVoxelAxisForDirection(vox2ras,
135 anatomicalRowDirection(orientation),
136 used);
137
138 return {columnAxis, rowAxis, fixedAxis};
139}
140
141int axisDimension(const QVector<int>& dims, int axis)
142{
143 return dims[axis];
144}
145
146int flatIndex(int x, int y, int z, int dimX, int dimY)
147{
148 return x + dimX * (y + dimY * z);
149}
150
151} // anonymous namespace
152
153//=============================================================================================================
154// STATIC METHODS
155//=============================================================================================================
156
158 const QVector<float>& volData,
159 const QVector<int>& dims,
160 const Matrix4f& vox2ras,
161 SliceOrientation orientation,
162 int sliceIndex)
163{
164 const int dimX = dims[0];
165 const int dimY = dims[1];
166 const PlaneSpec spec = planeSpecForOrientation(vox2ras, orientation);
167 const int width = axisDimension(dims, spec.columnAxis);
168 const int height = axisDimension(dims, spec.rowAxis);
169 const int fixedDim = axisDimension(dims, spec.fixedAxis);
170
171 sliceIndex = std::clamp(sliceIndex, 0, fixedDim - 1);
172
173 MriSliceImage result;
174 result.orientation = orientation;
175 result.width = width;
176 result.height = height;
177 result.sliceIndex = sliceIndex;
178 result.pixels.resize(width, height);
179
180 for (int row = 0; row < height; ++row) {
181 for (int col = 0; col < width; ++col) {
182 std::array<int, 3> voxel = {0, 0, 0};
183 voxel[spec.columnAxis] = col;
184 voxel[spec.rowAxis] = row;
185 voxel[spec.fixedAxis] = sliceIndex;
186 result.pixels(col, row) = volData[flatIndex(voxel[0], voxel[1], voxel[2], dimX, dimY)];
187 }
188 }
189
190 result.sliceToRas = Matrix4f::Zero();
191 result.sliceToRas.col(0) = vox2ras.col(spec.columnAxis);
192 result.sliceToRas.col(1) = vox2ras.col(spec.rowAxis);
193 result.sliceToRas.col(2) = vox2ras.col(spec.fixedAxis);
194 Vector4f origin = Vector4f::Zero();
195 origin(spec.fixedAxis) = static_cast<float>(sliceIndex);
196 origin.w() = 1.0f;
197 result.sliceToRas.col(3) = vox2ras * origin;
198
199 // Normalize pixels to [0, 1]
200 float minVal = result.pixels.minCoeff();
201 float maxVal = result.pixels.maxCoeff();
202 if (maxVal > minVal) {
203 result.pixels = (result.pixels.array() - minVal) / (maxVal - minVal);
204 } else {
205 result.pixels.setZero();
206 }
207
208 return result;
209}
210
211//=============================================================================================================
212
213int MriSlicer::voxelAxisForOrientation(const Matrix4f& vox2ras,
214 SliceOrientation orientation)
215{
216 return planeSpecForOrientation(vox2ras, orientation).fixedAxis;
217}
218
219//=============================================================================================================
220
221int MriSlicer::dimensionForOrientation(const QVector<int>& dims,
222 const Matrix4f& vox2ras,
223 SliceOrientation orientation)
224{
225 return axisDimension(dims, voxelAxisForOrientation(vox2ras, orientation));
226}
227
228//=============================================================================================================
229
230int MriSlicer::sliceIndexForOrientation(const Matrix4f& vox2ras,
231 SliceOrientation orientation,
232 const Vector3i& voxel)
233{
234 return voxel(voxelAxisForOrientation(vox2ras, orientation));
235}
236
237//=============================================================================================================
238
239QVector<MriSliceImage> MriSlicer::extractOrthogonal(
240 const QVector<float>& volData,
241 const QVector<int>& dims,
242 const Matrix4f& vox2ras,
243 const Vector3f& rasPoint)
244{
245 Vector3i voxel = rasToVoxel(vox2ras, rasPoint);
246
247 QVector<MriSliceImage> slices;
248 slices.reserve(3);
249 slices.append(extractSlice(volData, dims, vox2ras, SliceOrientation::Axial,
251 slices.append(extractSlice(volData, dims, vox2ras, SliceOrientation::Coronal,
253 slices.append(extractSlice(volData, dims, vox2ras, SliceOrientation::Sagittal,
255
256 return slices;
257}
258
259//=============================================================================================================
260
261Vector3i MriSlicer::rasToVoxel(const Matrix4f& vox2ras,
262 const Vector3f& rasPoint)
263{
264 Matrix4f ras2vox = vox2ras.inverse();
265 Vector4f rasH;
266 rasH << rasPoint, 1.0f;
267 Vector4f voxH = ras2vox * rasH;
268
269 return Vector3i(
270 static_cast<int>(std::round(voxH.x())),
271 static_cast<int>(std::round(voxH.y())),
272 static_cast<int>(std::round(voxH.z())));
273}
274
275//=============================================================================================================
276
277Vector3f MriSlicer::voxelToRas(const Matrix4f& vox2ras,
278 const Vector3i& voxel)
279{
280 Vector4f voxH;
281 voxH << static_cast<float>(voxel.x()),
282 static_cast<float>(voxel.y()),
283 static_cast<float>(voxel.z()),
284 1.0f;
285 Vector4f rasH = vox2ras * voxH;
286
287 return rasH.head<3>();
288}
289
290//=============================================================================================================
291// MriVolData convenience overloads
292//=============================================================================================================
293
295 SliceOrientation orientation,
296 int sliceIndex)
297{
298 return extractSlice(vol.voxelDataAsFloat(), vol.dims(),
299 vol.computeVox2RasTkr(), orientation, sliceIndex);
300}
301
302//=============================================================================================================
303
305 SliceOrientation orientation)
306{
307 return voxelAxisForOrientation(vol.computeVox2RasTkr(), orientation);
308}
309
310//=============================================================================================================
311
313 SliceOrientation orientation)
314{
315 return dimensionForOrientation(vol.dims(), vol.computeVox2RasTkr(), orientation);
316}
317
318//=============================================================================================================
319
321 SliceOrientation orientation,
322 const Vector3i& voxel)
323{
324 return sliceIndexForOrientation(vol.computeVox2RasTkr(), orientation, voxel);
325}
326
327//=============================================================================================================
328
329QVector<MriSliceImage> MriSlicer::extractOrthogonal(const MriVolData& vol,
330 const Vector3f& rasPoint)
331{
332 return extractOrthogonal(vol.voxelDataAsFloat(), vol.dims(),
333 vol.computeVox2RasTkr(), rasPoint);
334}
335
336//=============================================================================================================
337
338Vector3i MriSlicer::rasToVoxel(const MriVolData& vol,
339 const Vector3f& rasPoint)
340{
341 return rasToVoxel(vol.computeVox2RasTkr(), rasPoint);
342}
343
344//=============================================================================================================
345
346Vector3f MriSlicer::voxelToRas(const MriVolData& vol,
347 const Vector3i& voxel)
348{
349 return voxelToRas(vol.computeVox2RasTkr(), voxel);
350}
Orthogonal-plane resampler that turns a 3D MRILIB::MriVolData into the 2D textures consumed by the sl...
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:77
Single 2D MRI cross-section produced by MriSlicer (pixel buffer + RAS metadata).
Definition mri_slicer.h:95
Eigen::Matrix4f sliceToRas
Definition mri_slicer.h:101
SliceOrientation orientation
Definition mri_slicer.h:99
Eigen::MatrixXf pixels
Definition mri_slicer.h:96
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