1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
|
/* Copyright (c) 2008-2025 the MRtrix3 contributors.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Covered Software is provided under this License on an "as is"
* basis, without warranty of any kind, either expressed, implied, or
* statutory, including, without limitation, warranties that the
* Covered Software is free of defects, merchantable, fit for a
* particular purpose or non-infringing.
* See the Mozilla Public License v. 2.0 for more details.
*
* For more details, see http://www.mrtrix.org/.
*/
#include "command.h"
#include "progressbar.h"
#include "image.h"
#include "math/math.h"
#include "math/sphere.h"
#include "interp/nearest.h"
#include "interp/linear.h"
#include "interp/cubic.h"
#include "interp/sinc.h"
#include "filter/reslice.h"
#include "filter/warp.h"
#include "algo/loop.h"
#include "algo/copy.h"
#include "algo/threaded_copy.h"
#include "dwi/directions/predefined.h"
#include "dwi/gradient.h"
#include "registration/transform/reorient.h"
#include "registration/warp/helpers.h"
#include "registration/warp/compose.h"
#include "math/average_space.h"
#include "math/SH.h"
#include "adapter/jacobian.h"
#include "file/nifti_utils.h"
using namespace MR;
using namespace App;
const char* interp_choices[] = { "nearest", "linear", "cubic", "sinc", nullptr };
const char* modulation_choices[] = { "fod", "jac", nullptr };
void usage ()
{
AUTHOR = "J-Donald Tournier (jdtournier@gmail.com) and David Raffelt (david.raffelt@florey.edu.au) and Max Pietsch (maximilian.pietsch@kcl.ac.uk)";
SYNOPSIS = "Apply spatial transformations to an image";
DESCRIPTION
+ "If a linear transform is applied without a template image the command "
"will modify the image header transform matrix"
+ "FOD reorientation (with apodised point spread functions) can be performed "
"if the number of volumes in the 4th dimension equals the number "
"of coefficients in an antipodally symmetric spherical harmonic series (e.g. "
"6, 15, 28 etc). For such data, the -reorient_fod yes/no option must be used to specify "
"if reorientation is required."
+ "The output image intensity can be modulated using the (local or global) volume change "
"if a linear or nonlinear transformation is applied. 'FOD' modulation preserves the "
"apparent fibre density across the fibre bundle width and can only be applied if FOD reorientation "
"is used. Alternatively, non-directional scaling by the Jacobian determinant can be applied to "
"any image type. "
+ "If a DW scheme is contained in the header (or specified separately), and "
"the number of directions matches the number of volumes in the images, any "
"transformation applied using the -linear option will also be applied to the directions."
+ "When the -template option is used to specify the target image grid, the "
"image provided via this option will not influence the axis data strides "
"of the output image; these are determined based on the input image, or the "
"input to the -strides option.";
REFERENCES
+ "* If FOD reorientation is being performed:\n"
"Raffelt, D.; Tournier, J.-D.; Crozier, S.; Connelly, A. & Salvado, O. " // Internal
"Reorientation of fiber orientation distributions using apodized point spread functions. "
"Magnetic Resonance in Medicine, 2012, 67, 844-855"
+ "* If FOD modulation is being performed:\n"
"Raffelt, D.; Tournier, J.-D.; Rose, S.; Ridgway, G.R.; Henderson, R.; Crozier, S.; Salvado, O.; Connelly, A.; " // Internal
"Apparent Fibre Density: a novel measure for the analysis of diffusion-weighted magnetic resonance images. "
"NeuroImage, 2012, 15;59(4), 3976-94";
ARGUMENTS
+ Argument ("input", "input image to be transformed.").type_image_in ()
+ Argument ("output", "the output image.").type_image_out ();
OPTIONS
+ OptionGroup ("Affine transformation options")
+ Option ("linear",
"specify a linear transform to apply, in the form of a 3x4 "
"or 4x4 ascii file. Note the standard 'reverse' convention "
"is used, where the transform maps points in the template image "
"to the moving image. Note that the reverse convention is still "
"assumed even if no -template image is supplied")
+ Argument ("transform").type_file_in ()
+ Option ("flip",
"flip the specified axes, provided as a comma-separated list of indices (0:x, 1:y, 2:z).")
+ Argument ("axes").type_sequence_int()
+ Option ("inverse",
"apply the inverse transformation")
+ Option ("half",
"apply the matrix square root of the transformation. This can be combined with the inverse option.")
+ Option ("replace",
"replace the linear transform of the original image by that specified, "
"rather than applying it to the original image. The specified transform "
"can be either a template image, or a 3x4 or 4x4 ascii file.")
+ Argument ("file").type_file_in()
+ Option ("identity",
"set the header transform of the image to the identity matrix")
+ OptionGroup ("Regridding options")
+ Option ("template",
"reslice the input image to match the specified template image grid.")
+ Argument ("image").type_image_in ()
+ Option ("midway_space",
"reslice the input image to the midway space. Requires either the -template or -warp option. If "
"used with -template and -linear option the input image will be resliced onto the grid halfway between the input and template. "
"If used with the -warp option the input will be warped to the midway space defined by the grid of the input warp "
"(i.e. half way between image1 and image2)")
+ Option ("interp",
"set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: cubic).")
+ Argument ("method").type_choice (interp_choices)
+ Option ("oversample",
"set the amount of over-sampling (in the target space) to perform when regridding. This is particularly "
"relevant when downsamping a high-resolution image to a low-resolution image, to avoid aliasing artefacts. "
"This can consist of a single integer, or a comma-separated list of 3 integers if different oversampling "
"factors are desired along the different axes. Default is determined from ratio of voxel dimensions (disabled "
"for nearest-neighbour interpolation).")
+ Argument ("factor").type_sequence_int()
+ OptionGroup ("Non-linear transformation options")
// TODO point users to a documentation page describing the warp field format
+ Option ("warp",
"apply a non-linear 4D deformation field to warp the input image. Each voxel in the deformation field must define "
"the scanner space position that will be used to interpolate the input image during warping (i.e. pull-back/reverse warp convention). "
"If the -template image is also supplied the deformation field will be resliced first to the template image grid. If no -template "
"option is supplied then the output image will have the same image grid as the deformation field. This option can be used in "
"combination with the -affine option, in which case the affine will be applied first)")
+ Argument ("image").type_image_in ()
+ Option ("warp_full",
"warp the input image using a 5D warp file output from mrregister. Any linear transforms in the warp image header "
"will also be applied. The -warp_full option must be used in combination with either the -template option or the -midway_space option. "
"If a -template image is supplied then the full warp will be used. By default the image1->image2 transform will be applied, "
"however the -from 2 option can be used to apply the image2->image1 transform. Use the -midway_space option to warp the input "
"image to the midway space. The -from option can also be used to define which warp to use when transforming to midway space")
+ Argument ("image").type_image_in ()
+ Option ("from",
"used to define which space the input image is when using the -warp_mid option. "
"Use -from 1 to warp from image1 or -from 2 to warp from image2")
+ Argument ("image").type_integer (1,2)
+ OptionGroup ("Fibre orientation distribution handling options")
+ Option ("modulate",
"Valid choices are: fod and jac. \n"
"fod: modulate FODs during reorientation to preserve the apparent fibre density across fibre bundle widths before and after the transformation. \n"
"jac: modulate the image intensity with the determinant of the Jacobian of the warp of linear transformation "
"to preserve the total intensity before and after the transformation.")
+ Argument ("intensity modulation method").type_choice (modulation_choices)
+ Option ("directions",
"directions defining the number and orientation of the apodised point spread functions used in FOD reorientation "
"(Default: 300 directions)")
+ Argument ("file", "a list of directions [az el] generated using the dirgen command.").type_file_in()
+ Option ("reorient_fod",
"specify whether to perform FOD reorientation. This is required if the number "
"of volumes in the 4th dimension corresponds to the number of coefficients in an "
"antipodally symmetric spherical harmonic series with lmax >= 2 (i.e. 6, 15, 28, 45, 66 volumes).")
+ Argument ("boolean").type_bool()
+ DWI::GradImportOptions()
+ DWI::GradExportOptions()
+ DataType::options ()
+ Stride::Options
+ OptionGroup ("Additional generic options for mrtransform")
+ Option ("nan",
"Use NaN as the out of bounds value (Default: 0.0)")
+ Option ("no_reorientation", "deprecated, use -reorient_fod instead");
}
void apply_warp (Image<float>& input, Image<float>& output, Image<default_type>& warp,
const int interp, const float out_of_bounds_value, const vector<uint32_t>& oversample, const bool jacobian_modulate = false) {
switch (interp) {
case 0:
Filter::warp<Interp::Nearest> (input, output, warp, out_of_bounds_value, oversample, jacobian_modulate);
break;
case 1:
Filter::warp<Interp::Linear> (input, output, warp, out_of_bounds_value, oversample, jacobian_modulate);
break;
case 2:
Filter::warp<Interp::Cubic> (input, output, warp, out_of_bounds_value, oversample, jacobian_modulate);
break;
case 3:
Filter::warp<Interp::Sinc> (input, output, warp, out_of_bounds_value, oversample, jacobian_modulate);
break;
default:
assert (0);
break;
}
}
void apply_linear_jacobian (Image<float> & image, transform_type trafo) {
const float det = trafo.linear().topLeftCorner<3,3>().determinant();
INFO("global intensity modulation with scale factor " + str(det));
for (auto i = Loop ("applying global intensity modulation", image, 0, image.ndim()) (image); i; ++i) {
image.value() *= det;
}
}
void run ()
{
auto input_header = Header::open (argument[0]);
Header output_header (input_header);
output_header.datatype() = DataType::from_command_line (DataType::from<float> ());
Stride::set_from_command_line (output_header);
// Linear
transform_type linear_transform = transform_type::Identity();
bool linear = false;
auto opt = get_options ("linear");
if (opt.size()) {
linear = true;
linear_transform = load_transform (opt[0][0]);
}
// Replace
bool replace = false;
opt = get_options ("replace");
if (opt.size()) {
linear = replace = true;
try {
auto template_header = Header::open (opt[0][0]);
linear_transform = template_header.transform();
} catch (...) {
try {
linear_transform = load_transform (opt[0][0]);
} catch (...) {
throw Exception ("Unable to extract transform matrix from -replace file \"" + str(opt[0][0]) + "\"");
}
}
}
if (get_options ("identity").size()) {
linear = replace = true;
linear_transform.setIdentity();
}
// Template
opt = get_options ("template");
Header template_header;
if (opt.size()) {
if (replace)
throw Exception ("you cannot use the -replace option with the -template option");
if (!linear)
linear_transform.setIdentity();
template_header = Header::open (opt[0][0]);
for (size_t i = 0; i < 3; ++i) {
output_header.size(i) = template_header.size(i);
output_header.spacing(i) = template_header.spacing(i);
}
output_header.transform() = template_header.transform();
add_line (output_header.keyval()["comments"], std::string ("regridded to template image \"" + template_header.name() + "\""));
}
// Warp 5D warp
// TODO add reference to warp format documentation
opt = get_options ("warp_full");
Image<default_type> warp;
if (opt.size()) {
if (!Path::is_mrtrix_image (opt[0][0]) && !(Path::has_suffix (opt[0][0], {".nii", ".nii.gz"}) &&
File::Config::get_bool ("NIfTIAutoLoadJSON", false) &&
Path::exists(File::NIfTI::get_json_path(opt[0][0]))))
WARN ("warp_full image is not in original .mif/.mih file format or in NIfTI file format with associated JSON. "
"Converting to other file formats may remove linear transformations stored in the image header.");
warp = Image<default_type>::open (opt[0][0]).with_direct_io();
Registration::Warp::check_warp_full (warp);
if (linear)
throw Exception ("the -warp_full option cannot be applied in combination with -linear since the "
"linear transform is already included in the warp header");
}
// Warp from image1 or image2
int from = 1;
opt = get_options ("from");
if (opt.size()) {
from = opt[0][0];
if (!warp.valid())
WARN ("-from option ignored since no 5D warp was input");
}
// Warp deformation field
opt = get_options ("warp");
if (opt.size()) {
if (warp.valid())
throw Exception ("only one warp field can be input with either -warp or -warp_mid");
warp = Image<default_type>::open (opt[0][0]).with_direct_io (Stride::contiguous_along_axis(3));
if (warp.ndim() != 4)
throw Exception ("the input -warp file must be a 4D deformation field");
if (warp.size(3) != 3)
throw Exception ("the input -warp file must have 3 volumes in the 4th dimension (x,y,z positions)");
}
// Inverse
const bool inverse = get_options ("inverse").size();
if (inverse) {
if (!(linear || warp.valid()))
throw Exception ("no linear or warp transformation provided for option '-inverse'");
if (replace)
throw Exception ("cannot use -inverse option in conjunction with -replace or -identity options");
if (warp.valid())
if (warp.ndim() == 4)
throw Exception ("cannot apply -inverse with the input -warp_df deformation field.");
linear_transform = linear_transform.inverse();
}
// Half
const bool half = get_options ("half").size();
if (half) {
if (!(linear))
throw Exception ("no linear transformation provided for option '-half'");
if (replace)
throw Exception ("cannot use -half option in conjunction with -replace or -identity options");
Eigen::Matrix<default_type, 4, 4> temp;
temp.row(3) << 0, 0, 0, 1.0;
temp.topLeftCorner(3,4) = linear_transform.matrix().topLeftCorner(3,4);
linear_transform.matrix() = temp.sqrt().topLeftCorner(3,4);
}
// Flip
opt = get_options ("flip");
if (opt.size()) {
vector<int32_t> axes = parse_ints<int32_t> (opt[0][0]);
transform_type flip;
flip.setIdentity();
for (size_t i = 0; i < axes.size(); ++i) {
if (axes[i] < 0 || axes[i] > 2)
throw Exception ("axes supplied to -flip are out of bounds (" + std::string (opt[0][0]) + ")");
flip(axes[i],3) += flip(axes[i],axes[i]) * input_header.spacing(axes[i]) * (input_header.size(axes[i])-1);
flip(axes[i], axes[i]) *= -1.0;
}
if (!replace)
flip = input_header.transform() * flip * input_header.transform().inverse();
// For flipping an axis in the absence of any other linear transform
if (!linear) {
linear_transform.setIdentity();
linear = true;
}
linear_transform = linear_transform * flip;
}
Stride::List stride = Stride::get (input_header);
// Detect FOD image
bool is_possible_fod_image = input_header.ndim() == 4 && input_header.size(3) >= 6 &&
input_header.size(3) == (int) Math::SH::NforL (Math::SH::LforN (input_header.size(3)));
// reorientation
if (get_options ("no_reorientation").size())
throw Exception ("The -no_reorientation option is deprecated. Use -reorient_fod no instead.");
opt = get_options ("reorient_fod");
bool fod_reorientation = opt.size() && bool(opt[0][0]);
if (is_possible_fod_image && !opt.size())
throw Exception("-reorient_fod yes/no needs to be explicitly specified for images with " +
str(input_header.size(3)) + " volumes");
else if (!is_possible_fod_image && fod_reorientation)
throw Exception("Apodised PSF reorientation requires SH series images");
Eigen::MatrixXd directions_cartesian;
if (fod_reorientation && (linear || warp.valid() || template_header.valid()) && is_possible_fod_image) {
CONSOLE ("performing apodised PSF reorientation");
Eigen::MatrixXd directions_az_el;
opt = get_options ("directions");
if (opt.size())
directions_az_el = load_matrix (opt[0][0]);
else
directions_az_el = DWI::Directions::electrostatic_repulsion_300();
Math::Sphere::spherical2cartesian (directions_az_el, directions_cartesian);
// load with SH coeffients contiguous in RAM
stride = Stride::contiguous_along_axis (3, input_header);
}
// Intensity / FOD modulation
opt = get_options ("modulate");
bool modulate_fod = opt.size() && (int) opt[0][0] == 0;
bool modulate_jac = opt.size() && (int) opt[0][0] == 1;
const std::string reorient_msg = str("reorienting") + str((modulate_fod ? " with FOD modulation" : ""));
if (modulate_fod)
add_line (output_header.keyval()["comments"], std::string ("FOD modulation applied"));
if (modulate_jac)
add_line (output_header.keyval()["comments"], std::string ("Jacobian determinant modulation applied"));
if (modulate_fod) {
if (!is_possible_fod_image)
throw Exception ("FOD modulation can only be performed with SH series image");
if (!fod_reorientation)
throw Exception ("FOD modulation can only be performed with FOD reorientation");
}
if (modulate_jac) {
if (fod_reorientation) {
WARN ("Input image being interpreted as FOD data (user requested FOD reorientation); "
"FOD-based modulation would be more appropriate for such data than the requested Jacobian modulation.");
} else if (is_possible_fod_image) {
WARN ("Jacobian modulation performed on possible SH series image. Did you mean FOD modulation?");
}
if (!linear && !warp.valid())
throw Exception ("Jacobian modulation requires linear or nonlinear transformation");
}
// Rotate/Flip direction information if present
if (linear && input_header.ndim() == 4 && !warp && !fod_reorientation) {
Eigen::MatrixXd rotation = linear_transform.linear().inverse();
Eigen::MatrixXd test = rotation.transpose() * rotation;
test = test.array() / test.diagonal().mean();
if (replace)
rotation = linear_transform.linear() * input_header.transform().linear().inverse();
// Diffusion gradient table
Eigen::MatrixXd grad;
try {
grad = DWI::get_DW_scheme (input_header);
} catch (Exception&) {}
if (grad.rows()) {
try {
if (input_header.size(3) != (ssize_t) grad.rows()) {
throw Exception ("DW gradient table of different length ("
+ str(grad.rows())
+ ") to number of image volumes ("
+ str(input_header.size(3))
+ ")");
}
INFO ("DW gradients detected and will be reoriented");
if (!test.isIdentity (0.001)) {
WARN ("the input linear transform contains shear or anisotropic scaling and "
"therefore should not be used to reorient directions / diffusion gradients");
}
for (ssize_t n = 0; n < grad.rows(); ++n) {
Eigen::Vector3d grad_vector = grad.block<1,3>(n,0);
grad.block<1,3>(n,0) = rotation * grad_vector;
}
DWI::set_DW_scheme (output_header, grad);
}
catch (Exception& e) {
e.display (2);
WARN ("DW gradients not correctly reoriented");
}
}
// Also look for key 'directions', and rotate those if present
auto hit = input_header.keyval().find ("directions");
if (hit != input_header.keyval().end()) {
INFO ("Header entry \"directions\" detected and will be reoriented");
if (!test.isIdentity (0.001)) {
WARN ("the input linear transform contains shear or anisotropic scaling and "
"therefore should not be used to reorient directions / diffusion gradients");
}
try {
const auto lines = split_lines (hit->second);
if (lines.size() != size_t(input_header.size(3)))
throw Exception ("Number of lines in header entry \"directions\" (" + str(lines.size()) + ") does not match number of volumes in image (" + str(input_header.size(3)) + ")");
Eigen::Matrix<default_type, Eigen::Dynamic, Eigen::Dynamic> result;
for (size_t l = 0; l != lines.size(); ++l) {
const auto v = parse_floats (lines[l]);
if (!result.cols()) {
if (!(v.size() == 2 || v.size() == 3))
throw Exception ("Malformed \"directions\" field (expected matrix with 2 or 3 columns; data has " + str(v.size()) + " columns)");
result.resize (lines.size(), v.size());
} else {
if (v.size() != size_t(result.cols()))
throw Exception ("Inconsistent number of columns in \"directions\" field");
}
if (result.cols() == 2) {
Eigen::Matrix<default_type, 2, 1> azel (v.data());
Eigen::Vector3d dir;
Math::Sphere::spherical2cartesian (azel, dir);
dir = rotation * dir;
Math::Sphere::cartesian2spherical (dir, azel);
result.row (l) = azel;
} else {
const Eigen::Vector3d dir = rotation * Eigen::Vector3d (v.data());
result.row (l) = dir;
}
std::stringstream s;
Eigen::IOFormat format (6, Eigen::DontAlignCols, ",", "\n", "", "", "", "");
s << result.format (format);
output_header.keyval()["directions"] = s.str();
}
} catch (Exception& e) {
e.display (2);
WARN ("Header entry \"directions\" not correctly reoriented");
}
}
}
// Interpolator
int interp = 2; // cubic
opt = get_options ("interp");
if (opt.size()) {
interp = opt[0][0];
if (!warp && !template_header)
WARN ("interpolator choice ignored since the input image will not be regridded");
}
// over-sampling
vector<uint32_t> oversample = Adapter::AutoOverSample;
opt = get_options ("oversample");
if (opt.size()) {
if (!template_header.valid() && !warp)
throw Exception ("-oversample option applies only to regridding using the template option or to non-linear transformations");
oversample = parse_ints<uint32_t> (opt[0][0]);
if (oversample.size() == 1)
oversample.resize (3, oversample[0]);
else if (oversample.size() != 3)
throw Exception ("-oversample option requires either a single integer, or a comma-separated list of 3 integers");
for (const auto x : oversample)
if (x < 1)
throw Exception ("-oversample factors must be positive integers");
} else if (interp == 0)
// default for nearest-neighbour is no oversampling
oversample = { 1, 1, 1 };
// Out of bounds value
float out_of_bounds_value = 0.0;
opt = get_options ("nan");
if (opt.size()) {
out_of_bounds_value = NAN;
if (!warp && !template_header)
WARN ("Out of bounds value ignored since the input image will not be regridded");
}
auto input = input_header.get_image<float>().with_direct_io (stride);
// Reslice the image onto template
if (template_header.valid() && !warp) {
INFO ("image will be regridded");
if (get_options ("midway_space").size()) {
INFO("regridding to midway space");
if (!half)
WARN ("regridding to midway_space assumes the linear transformation to be a transformation from input to midway space. Use -half if the input transformation is a full transformation.");
transform_type linear_transform_inverse;
linear_transform_inverse.matrix() = linear_transform.inverse().matrix();
auto midway_header = compute_minimum_average_header (input_header, template_header, linear_transform_inverse, linear_transform);
for (size_t i = 0; i < 3; ++i) {
output_header.size(i) = midway_header.size(i);
output_header.spacing(i) = midway_header.spacing(i);
}
output_header.transform() = midway_header.transform();
}
if (interp == 0)
output_header.datatype() = DataType::from_command_line (input_header.datatype());
auto output = Image<float>::create (argument[1], output_header);
switch (interp) {
case 0:
Filter::reslice<Interp::Nearest> (input, output, linear_transform, oversample, out_of_bounds_value);
break;
case 1:
Filter::reslice<Interp::Linear> (input, output, linear_transform, oversample, out_of_bounds_value);
break;
case 2:
Filter::reslice<Interp::Cubic> (input, output, linear_transform, oversample, out_of_bounds_value);
break;
case 3:
Filter::reslice<Interp::Sinc> (input, output, linear_transform, oversample, out_of_bounds_value);
break;
default:
assert (0);
break;
}
if (fod_reorientation)
Registration::Transform::reorient (reorient_msg.c_str(), output, output, linear_transform, directions_cartesian.transpose(), modulate_fod);
if (modulate_jac)
apply_linear_jacobian (output, linear_transform);
DWI::export_grad_commandline (output);
} else if (warp.valid()) {
if (replace)
throw Exception ("you cannot use the -replace option with the -warp or -warp_df option");
if (!template_header) {
for (size_t i = 0; i < 3; ++i) {
output_header.size(i) = warp.size(i);
output_header.spacing(i) = warp.spacing(i);
}
output_header.transform() = warp.transform();
add_line (output_header.keyval()["comments"], std::string ("resliced using warp image \"" + warp.name() + "\""));
}
auto output = Image<float>::create(argument[1], output_header);
if (warp.ndim() == 5) {
Image<default_type> warp_deform;
// Warp to the midway space defined by the warp grid
if (get_options ("midway_space").size()) {
warp_deform = Registration::Warp::compute_midway_deformation (warp, from);
// Use the full transform to warp from the image image to the template
} else {
warp_deform = Registration::Warp::compute_full_deformation (warp, template_header, from);
}
apply_warp (input, output, warp_deform, interp, out_of_bounds_value, oversample, modulate_jac);
if (fod_reorientation)
Registration::Transform::reorient_warp (reorient_msg.c_str(),
output, warp_deform, directions_cartesian.transpose(), modulate_fod);
// Compose and apply input linear and 4D deformation field
} else if (warp.ndim() == 4 && linear) {
auto warp_composed = Image<default_type>::scratch (warp);
Registration::Warp::compose_linear_deformation (linear_transform, warp, warp_composed);
apply_warp (input, output, warp_composed, interp, out_of_bounds_value, oversample, modulate_jac);
if (fod_reorientation)
Registration::Transform::reorient_warp (reorient_msg.c_str(),
output, warp_composed, directions_cartesian.transpose(), modulate_fod);
// Apply 4D deformation field only
} else {
apply_warp (input, output, warp, interp, out_of_bounds_value, oversample, modulate_jac);
if (fod_reorientation)
Registration::Transform::reorient_warp (reorient_msg.c_str(),
output, warp, directions_cartesian.transpose(), modulate_fod);
}
DWI::export_grad_commandline (output);
// No reslicing required, so just modify the header and do a straight copy of the data
} else {
if (get_options ("midway").size())
throw Exception ("midway option given but no template image defined");
INFO ("image will not be regridded");
Eigen::MatrixXd rotation = linear_transform.linear();
Eigen::MatrixXd temp = rotation.transpose() * rotation;
if (!temp.isIdentity (0.001))
WARN("the input linear transform is not orthonormal and therefore applying this without the -template "
"option will mean the output header transform will also be not orthonormal");
add_line (output_header.keyval()["comments"], std::string ("transform modified"));
if (replace)
output_header.transform() = linear_transform;
else
output_header.transform() = linear_transform.inverse() * output_header.transform();
auto output = Image<float>::create (argument[1], output_header);
copy_with_progress (input, output);
if (fod_reorientation || modulate_jac) {
transform_type transform = linear_transform;
if (replace)
transform = linear_transform * output_header.transform().inverse();
if (fod_reorientation)
Registration::Transform::reorient ("reorienting", output, output, transform, directions_cartesian.transpose());
if (modulate_jac)
apply_linear_jacobian (output, transform);
}
DWI::export_grad_commandline (output);
}
}
|