Developer documentation
Version 3.0.3-105-gd3941f44
phase_encoding.h
Go to the documentation of this file.
1/* Copyright (c) 2008-2022 the MRtrix3 contributors.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 *
7 * Covered Software is provided under this License on an "as is"
8 * basis, without warranty of any kind, either expressed, implied, or
9 * statutory, including, without limitation, warranties that the
10 * Covered Software is free of defects, merchantable, fit for a
11 * particular purpose or non-infringing.
12 * See the Mozilla Public License v. 2.0 for more details.
13 *
14 * For more details, see http://www.mrtrix.org/.
15 */
16
17#ifndef __phaseencoding_h__
18#define __phaseencoding_h__
19
20
21#include <Eigen/Dense>
22
23#include "app.h"
24#include "axes.h"
25#include "header.h"
26#include "file/nifti_utils.h"
27#include "file/ofstream.h"
28
29
30
31namespace MR
32{
33 namespace PhaseEncoding
34 {
35
36
37
41
42
43
45 template <class MatrixType>
46 void check (const MatrixType& PE)
47 {
48 if (!PE.rows())
49 throw Exception ("No valid phase encoding table found");
50
51 if (PE.cols() < 3)
52 throw Exception ("Phase-encoding matrix must have at least 3 columns");
53
54 for (ssize_t row = 0; row != PE.rows(); ++row) {
55 for (ssize_t axis = 0; axis != 3; ++axis) {
56 if (std::round (PE(row, axis)) != PE(row, axis))
57 throw Exception ("Phase-encoding matrix contains non-integral axis designation");
58 }
59 }
60 }
61
62
63
65 template <class MatrixType, class HeaderType>
66 void check (const MatrixType& PE, const HeaderType& header)
67 {
68 check (PE);
69 const ssize_t num_volumes = (header.ndim() > 3) ? header.size (3) : 1;
70 if (num_volumes != PE.rows())
71 throw Exception ("Number of volumes in image \"" + header.name() + "\" (" + str(num_volumes) + ") does not match that in phase encoding table (" + str(PE.rows()) + ")");
72 }
73
74
75
76
77
78
79
81
89 template <class MatrixType>
90 void set_scheme (Header& header, const MatrixType& PE)
91 {
92 auto erase = [&] (const std::string& s) { auto it = header.keyval().find (s); if (it != header.keyval().end()) header.keyval().erase (it); };
93 if (!PE.rows()) {
94 erase ("pe_scheme");
95 erase ("PhaseEncodingDirection");
96 erase ("TotalReadoutTime");
97 return;
98 }
99 check (PE, header);
100 std::string pe_scheme;
101 std::string first_line;
102 bool variation = false;
103 for (ssize_t row = 0; row < PE.rows(); ++row) {
104 std::string line = str(PE(row,0));
105 for (ssize_t col = 1; col < PE.cols(); ++col)
106 line += "," + str(PE(row,col), 3);
107 add_line (pe_scheme, line);
108 if (first_line.empty())
109 first_line = line;
110 else if (line != first_line)
111 variation = true;
112 }
113 if (variation) {
114 header.keyval()["pe_scheme"] = pe_scheme;
115 erase ("PhaseEncodingDirection");
116 erase ("TotalReadoutTime");
117 } else {
118 erase ("pe_scheme");
119 const Eigen::Vector3d dir { PE(0, 0), PE(0, 1), PE(0, 2) };
120 header.keyval()["PhaseEncodingDirection"] = Axes::dir2id (dir);
121 if (PE.cols() >= 4)
122 header.keyval()["TotalReadoutTime"] = str(PE(0, 3), 3);
123 else
124 erase ("TotalReadoutTime");
125 }
126 }
127
128
129
131
134 void clear_scheme (Header& header);
135
136
137
139
146 Eigen::MatrixXd parse_scheme (const Header&);
147
148
149
151
154 Eigen::MatrixXd get_scheme (const Header&);
155
156
157
159 template <class MatrixType>
160 void scheme2eddy (const MatrixType& PE, Eigen::MatrixXd& config, Eigen::Array<int, Eigen::Dynamic, 1>& indices)
161 {
162 try {
163 check (PE);
164 } catch (Exception& e) {
165 throw Exception (e, "Cannot convert phase-encoding scheme to eddy format");
166 }
167 if (PE.cols() != 4)
168 throw Exception ("Phase-encoding matrix requires 4 columns to convert to eddy format");
169 config.resize (0, 0);
170 indices = Eigen::Array<int, Eigen::Dynamic, 1>::Constant (PE.rows(), PE.rows());
171 for (ssize_t PE_row = 0; PE_row != PE.rows(); ++PE_row) {
172 for (ssize_t config_row = 0; config_row != config.rows(); ++config_row) {
173 bool dir_match = PE.template block<1,3>(PE_row, 0).isApprox (config.block<1,3>(config_row, 0));
174 bool time_match = abs (PE(PE_row, 3) - config(config_row, 3)) < 1e-3;
175 if (dir_match && time_match) {
176 // FSL-style index file indexes from 1
177 indices[PE_row] = config_row + 1;
178 break;
179 }
180 }
181 if (indices[PE_row] == PE.rows()) {
182 // No corresponding match found in config matrix; create a new entry
183 config.conservativeResize (config.rows()+1, 4);
184 config.row(config.rows()-1) = PE.row(PE_row);
185 indices[PE_row] = config.rows();
186 }
187 }
188 }
189
191 Eigen::MatrixXd eddy2scheme (const Eigen::MatrixXd&, const Eigen::Array<int, Eigen::Dynamic, 1>&);
192
193
194
195
196
198 template <class MatrixType, class HeaderType>
199 Eigen::MatrixXd transform_for_image_load (const MatrixType& pe_scheme, const HeaderType& H)
200 {
201 std::array<size_t, 3> perm;
202 std::array<bool, 3> flip;
203 H.realignment (perm, flip);
204 if (perm[0] == 0 && perm[1] == 1 && perm[2] == 2 && !flip[0] && !flip[1] && !flip[2]) {
205 INFO ("No transformation of external phase encoding data required to accompany image \"" + H.name() + "\"");
206 return pe_scheme;
207 }
208 Eigen::MatrixXd result (pe_scheme.rows(), pe_scheme.cols());
209 for (ssize_t row = 0; row != pe_scheme.rows(); ++row) {
210 Eigen::VectorXd new_line = pe_scheme.row (row);
211 for (ssize_t axis = 0; axis != 3; ++axis) {
212 new_line[axis] = pe_scheme(row, perm[axis]);
213 if (new_line[axis] && flip[perm[axis]])
214 new_line[axis] = -new_line[axis];
215 }
216 result.row (row) = new_line;
217 }
218 INFO ("External phase encoding data transformed to match RAS realignment of image \"" + H.name() + "\"");
219 return result;
220 }
221
222
223
224
226 template <class MatrixType, class HeaderType>
227 Eigen::MatrixXd transform_for_nifti_write (const MatrixType& pe_scheme, const HeaderType& H)
228 {
230 vector<bool> flip;
232 if (order[0] == 0 && order[1] == 1 && order[2] == 2 && !flip[0] && !flip[1] && !flip[2]) {
233 INFO ("No transformation of phase encoding data required for export to file");
234 return pe_scheme;
235 }
236 Eigen::Matrix<default_type, Eigen::Dynamic, Eigen::Dynamic> result (pe_scheme.rows(), pe_scheme.cols());
237 for (ssize_t row = 0; row != pe_scheme.rows(); ++row) {
238 Eigen::VectorXd new_line = pe_scheme.row (row);
239 for (ssize_t axis = 0; axis != 3; ++axis)
240 new_line[axis] = pe_scheme(row, order[axis]) && flip[axis] ?
241 -pe_scheme(row, order[axis]) :
242 pe_scheme(row, order[axis]);
243 result.row (row) = new_line;
244 }
245 INFO ("Phase encoding data transformed to match NIfTI / MGH image export prior to writing to file");
246 return result;
247 }
248
249
250
251
252 namespace
253 {
254 template <class MatrixType>
255 void __save (const MatrixType& PE, const std::string& path)
256 {
257 File::OFStream out (path);
258 for (ssize_t row = 0; row != PE.rows(); ++row) {
259 // Write phase-encode direction as integers; other information as floating-point
260 out << PE.template block<1, 3>(row, 0).template cast<int>();
261 if (PE.cols() > 3)
262 out << " " << PE.block(row, 3, 1, PE.cols()-3);
263 out << "\n";
264 }
265 }
266 }
267
269 // Note that because the output table requires permutation / sign flipping
270 // only if the output target image is a NIfTI, the output file name must have
271 // already been set
272 template <class MatrixType, class HeaderType>
273 void save (const MatrixType& PE, const HeaderType& header, const std::string& path)
274 {
275 try {
276 check (PE, header);
277 } catch (Exception& e) {
278 throw Exception (e, "Cannot export phase-encoding table to file \"" + path + "\"");
279 }
280
281 if (Path::has_suffix (header.name(), {".mgh", ".mgz", ".nii", ".nii.gz", ".img"})) {
282 __save (transform_for_nifti_write (PE, header), path);
283 } else {
284 __save (PE, path);
285 }
286 }
287
289 template <class MatrixType, class HeaderType>
290 void save_eddy (const MatrixType& PE, const HeaderType& header, const std::string& config_path, const std::string& index_path)
291 {
292 Eigen::MatrixXd config;
293 Eigen::Array<int, Eigen::Dynamic, 1> indices;
294 scheme2eddy (transform_for_nifti_write (PE, header), config, indices);
295 save_matrix (config, config_path, KeyValues(), false);
296 save_vector (indices, index_path, KeyValues(), false);
297 }
298
299
300
303
304
305
307 template <class HeaderType>
308 Eigen::MatrixXd load (const std::string& path, const HeaderType& header)
309 {
310 const Eigen::MatrixXd PE = load_matrix (path);
311 check (PE, header);
312 // As with JSON import, need to query the header to discover if the
313 // strides / transform were modified on image load to make the image
314 // data appear approximately axial, in which case we need to apply the
315 // same transforms to the phase encoding data on load
316 return transform_for_image_load (PE, header);
317 }
318
320 template <class HeaderType>
321 Eigen::MatrixXd load_eddy (const std::string& config_path, const std::string& index_path, const HeaderType& header)
322 {
323 const Eigen::MatrixXd config = load_matrix (config_path);
324 const Eigen::Array<int, Eigen::Dynamic, 1> indices = load_vector<int> (index_path);
325 const Eigen::MatrixXd PE = eddy2scheme (config, indices);
326 check (PE, header);
327 return transform_for_image_load (PE, header);
328 }
329
330
331
332 }
333}
334
335#endif
336
a class to hold a named list of Option's
open output files for writing, checking for pre-existing file if necessary
Definition: ofstream.h:39
#define INFO(msg)
Definition: exception.h:74
constexpr I round(const T x)
Definition: math.h:64
constexpr double e
Definition: math.h:39
std::string dir2id(const Eigen::Vector3d &)
convert axis directions between formats
void axes_on_write(const Header &H, vector< size_t > &order, vector< bool > &flip)
bool has_suffix(const std::string &name, const std::string &suffix)
Definition: path.h:130
void save_eddy(const MatrixType &PE, const HeaderType &header, const std::string &config_path, const std::string &index_path)
Save a phase-encoding scheme to EDDY format config / index files.
void save(const MatrixType &PE, const HeaderType &header, const std::string &path)
Save a phase-encoding scheme to file.
Eigen::MatrixXd parse_scheme(const Header &)
parse the phase encoding matrix from a header
void scheme2eddy(const MatrixType &PE, Eigen::MatrixXd &config, Eigen::Array< int, Eigen::Dynamic, 1 > &indices)
Convert a phase-encoding scheme into the EDDY config / indices format.
Eigen::MatrixXd get_scheme(const Header &)
get a phase encoding matrix
const App::OptionGroup ExportOptions
Eigen::MatrixXd transform_for_nifti_write(const MatrixType &pe_scheme, const HeaderType &H)
Modifies a phase encoding scheme if being exported alongside a NIfTI image.
void export_commandline(const Header &)
Save the phase-encoding scheme from a header to file depending on command-line options.
void clear_scheme(Header &header)
clear the phase encoding matrix from a header
const App::OptionGroup ImportOptions
Eigen::MatrixXd load(const std::string &path, const HeaderType &header)
Load a phase-encoding scheme from a matrix text file.
Eigen::MatrixXd transform_for_image_load(const MatrixType &pe_scheme, const HeaderType &H)
Modifies a phase encoding scheme if being imported alongside a non-RAS image.
Eigen::MatrixXd eddy2scheme(const Eigen::MatrixXd &, const Eigen::Array< int, Eigen::Dynamic, 1 > &)
Convert phase-encoding infor from the EDDY config / indices format into a standard scheme.
Eigen::MatrixXd load_eddy(const std::string &config_path, const std::string &index_path, const HeaderType &header)
Load a phase-encoding scheme from an EDDY-format config / indices file pair.
void check(const MatrixType &PE)
check that a phase-encoding table is valid
const App::OptionGroup SelectOptions
void set_scheme(Header &header, const MatrixType &PE)
store the phase encoding matrix in a header
vector< size_t > order(const HeaderType &header, size_t from_axis=0, size_t to_axis=std::numeric_limits< size_t >::max())
sort range of axes with respect to their absolute stride.
Definition: stride.h:159
Definition: base.h:24
std::map< std::string, std::string > KeyValues
used in various places for storing key-value pairs
Definition: types.h:238
Eigen::Matrix< ValueType, Eigen::Dynamic, Eigen::Dynamic > load_matrix(const std::string &filename)
read matrix data into an Eigen::Matrix filename
Definition: math.h:196
constexpr std::enable_if< std::is_arithmetic< X >::value &&std::is_unsigned< X >::value, X >::type abs(X x)
Definition: types.h:297
std::string str(const T &value, int precision=0)
Definition: mrtrix.h:247
void save_matrix(const MatrixType &M, const std::string &filename, const KeyValues &keyvals=KeyValues(), const bool add_to_command_history=true)
write the matrix M to file
Definition: math.h:136
void save_vector(const VectorType &V, const std::string &filename, const KeyValues &keyvals=KeyValues(), const bool add_to_command_history=true)
write the vector V to file
Definition: math.h:314
std::string & add_line(std::string &original, const std::string &new_line)
add a line to a string, taking care of inserting a newline if needed
Definition: mrtrix.h:81
int axis
Eigen::MatrixXd H