Skip to content

Commit

Permalink
Tolerance for dw_scheme differences on header merge
Browse files Browse the repository at this point in the history
This prevents the diffusion gradient table from being discarded when merging the key-value contents of two image headers (eg. mrcalc, mrmath) due to inconsequential differences in their string representations from floating-point imprecision.
Closes #3015.
  • Loading branch information
Lestropie committed Oct 22, 2024
1 parent fb66ec3 commit 2991bed
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 22 deletions.
1 change: 1 addition & 0 deletions cmd/dwiextract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "phase_encoding.h"
#include "progressbar.h"
#include "dwi/gradient.h"
#include "dwi/shells.h"
#include "algo/loop.h"
#include "adapter/extract.h"

Expand Down
1 change: 1 addition & 0 deletions cmd/mrinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "file/json.h"
#include "file/json_utils.h"
#include "dwi/gradient.h"
#include "dwi/shells.h"
#include "image_io/pipe.h"


Expand Down
6 changes: 3 additions & 3 deletions cmd/mrtransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ void run ()

if (interp == 0)
output_header.datatype() = DataType::from_command_line (input_header.datatype());
auto output = Image<float>::create (argument[1], output_header).with_direct_io();
auto output = Image<float>::create (argument[1], output_header);

switch (interp) {
case 0:
Expand Down Expand Up @@ -630,7 +630,7 @@ void run ()
add_line (output_header.keyval()["comments"], std::string ("resliced using warp image \"" + warp.name() + "\""));
}

auto output = Image<float>::create(argument[1], output_header).with_direct_io();
auto output = Image<float>::create(argument[1], output_header);

if (warp.ndim() == 5) {
Image<default_type> warp_deform;
Expand Down Expand Up @@ -684,7 +684,7 @@ void run ()
output_header.transform() = linear_transform;
else
output_header.transform() = linear_transform.inverse() * output_header.transform();
auto output = Image<float>::create (argument[1], output_header).with_direct_io();
auto output = Image<float>::create (argument[1], output_header);
copy_with_progress (input, output);

if (fod_reorientation || modulate_jac) {
Expand Down
13 changes: 12 additions & 1 deletion core/dwi/gradient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/

#include "dwi/gradient.h"
#include "file/config.h"
#include "file/nifti_utils.h"
#include "dwi/shells.h"

namespace MR
{
Expand Down Expand Up @@ -85,6 +85,17 @@ namespace MR



//CONF option: BZeroThreshold
//CONF default: 10.0
//CONF Specifies the b-value threshold for determining those image
//CONF volumes that correspond to b=0.
default_type bzero_threshold () {
static const default_type value = File::Config::get_float ("BZeroThreshold", DWI_BZERO_THREHSOLD_DEFAULT);
return value;
}



BValueScalingBehaviour get_cmdline_bvalue_scaling_behaviour ()
{
auto opt = App::get_options ("bvalue_scaling");
Expand Down
63 changes: 62 additions & 1 deletion core/dwi/gradient.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@

#include "app.h"
#include "header.h"
#include "dwi/shells.h"
#include "types.h"
#include "file/config.h"
#include "file/path.h"
#include "math/condition_number.h"
#include "math/sphere.h"
#include "math/SH.h"


#define DWI_BZERO_THREHSOLD_DEFAULT 10.0

namespace MR
{
namespace App { class OptionGroup; }
Expand All @@ -40,6 +42,7 @@ namespace MR
extern App::Option bvalue_scaling_option;
extern const char* const bvalue_scaling_description;

default_type bzero_threshold ();


//! check that the DW scheme matches the DWI data in \a header
Expand Down Expand Up @@ -168,6 +171,64 @@ namespace MR
}
}

//! store the DW gradient encoding matrix in a KeyValues structure
/*! this will store the DW gradient encoding matrix under the key 'dw_scheme'.
*/
template <class MatrixType>
void set_DW_scheme (KeyValues& keyval, const MatrixType& G)
{
if (!G.rows()) {
auto it = keyval.find ("dw_scheme");
if (it != keyval.end())
keyval.erase (it);
return;
}
keyval["dw_scheme"] = scheme2str (G);
}

template <class MatrixType1, class MatrixType2>
Eigen::MatrixXd resolve_DW_scheme (const MatrixType1& one, const MatrixType2& two)
{
if (one.rows() != two.rows())
throw Exception ("Unequal numbers of rows between gradient tables");
if (one.cols() != two.cols())
throw Exception ("Unequal numbers of rows between gradient tables");
Eigen::MatrixXd result (one.rows(), one.cols());
// Don't know how to intellegently combine data beyond four columns;
// therefore don't proceed if such data are present and are not precisely equivalent
if (one.cols() > 4) {
if (one.rightCols(one.cols()-4) != two.rightCols(two.cols()-4))
throw Exception ("Unequal dw_scheme contents beyond standard four columns");
result.rightCols(one.cols()-4) = one.rightCols(one.cols()-4);
}
for (ssize_t rowindex = 0; rowindex != one.rows(); ++rowindex) {
auto one_dir = one.template block<1,3>(rowindex, 0);
auto two_dir = two.template block<1,3>(rowindex, 0);
const default_type one_bvalue = one(rowindex, 3);
const default_type two_bvalue = two(rowindex, 3);
const bool is_bzero = std::max(one_bvalue, two_bvalue) <= bzero_threshold();
if (one_dir == two_dir) {
result.block<1,3>(rowindex, 0) = one_dir;
} else {
const Eigen::Vector3d mean_dir = (one_dir + two_dir).normalized();
if (!is_bzero && mean_dir.dot (one_dir) < 1.0 - 1e-3) {
throw Exception("Diffusion vector directions not equal within permissible imprecision "
"(row " + str(rowindex) + ": " + str(one_dir.transpose()) + " <--> " + str(two_dir.transpose())
+ "; dot product " + str(mean_dir.dot(one_dir)) + ")");
}
result.block<1,3>(rowindex, 0) = mean_dir;
}
if (one_bvalue == two_bvalue) {
result(rowindex, 3) = one_bvalue;
} else if (is_bzero || std::abs(one_bvalue - two_bvalue) <= 1.0) {
result(rowindex, 3) = 0.5 * (one_bvalue + two_bvalue);
} else {
throw Exception("Diffusion gradient table b-values not equivalent");
}
}
return result;
}



//! parse the DW gradient encoding matrix from a header
Expand Down
16 changes: 2 additions & 14 deletions core/dwi/shells.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

#include "app.h"
#include "types.h"
#include "file/config.h"
#include "dwi/gradient.h"
#include "misc/bitset.h"


Expand All @@ -37,16 +37,8 @@
// Default number of volumes necessary for a shell to be retained
// (note: only applies if function reject_small_shells() is called explicitly)
#define DWI_SHELLS_MIN_DIRECTIONS 6
// Default b-value threshold for a shell to be classified as "b=0"
#define DWI_SHELLS_BZERO_THREHSOLD 10.0



//CONF option: BZeroThreshold
//CONF default: 10.0
//CONF Specifies the b-value threshold for determining those image
//CONF volumes that correspond to b=0.

//CONF option: BValueEpsilon
//CONF default: 80.0
//CONF Specifies the difference between b-values necessary for image
Expand All @@ -64,10 +56,6 @@ namespace MR

extern const App::OptionGroup ShellsOption;

FORCE_INLINE default_type bzero_threshold () {
static const default_type value = File::Config::get_float ("BZeroThreshold", DWI_SHELLS_BZERO_THREHSOLD);
return value;
}



Expand All @@ -87,7 +75,7 @@ namespace MR
default_type get_min() const { return min; }
default_type get_max() const { return max; }

bool is_bzero() const { return (mean < bzero_threshold()); }
bool is_bzero() const { return (mean <= MR::DWI::bzero_threshold()); }


bool operator< (const Shell& rhs) const { return (mean < rhs.mean); }
Expand Down
1 change: 1 addition & 0 deletions core/filter/dwi_brain_mask.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "algo/copy.h"
#include "algo/loop.h"
#include "dwi/gradient.h"
#include "dwi/shells.h"


namespace MR
Expand Down
16 changes: 13 additions & 3 deletions core/header.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "app.h"
#include "axes.h"
#include "math/math.h"
#include "mrtrix.h"
#include "phase_encoding.h"
#include "stride.h"
Expand Down Expand Up @@ -128,12 +129,21 @@ namespace MR
}
} else {
auto it = keyval().find (item.first);
if (it == keyval().end() || it->second == item.second)
if (it == keyval().end() || it->second == item.second) {
new_keyval.insert (item);
else if (item.first == "SliceTiming")
} else if (item.first == "SliceTiming") {
new_keyval["SliceTiming"] = resolve_slice_timing (item.second, it->second);
else
} else if (item.first == "dw_scheme") {
try {
auto scheme = DWI::resolve_DW_scheme (parse_matrix (item.second), parse_matrix (it->second));
DWI::set_DW_scheme (new_keyval, scheme);
} catch (Exception& e) {
WARN("Error merging DW gradient tables between headers");
new_keyval["dw_scheme"] = "variable";
}
} else {
new_keyval[item.first] = "variable";
}
}
}
std::swap (keyval_, new_keyval);
Expand Down
1 change: 1 addition & 0 deletions src/dwi/sdeconv/csd.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "app.h"
#include "header.h"
#include "dwi/gradient.h"
#include "dwi/shells.h"
#include "math/SH.h"
#include "math/ZSH.h"
#include "dwi/directions/predefined.h"
Expand Down
2 changes: 2 additions & 0 deletions testing/binaries/tests/mrcalc
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ mrcalc mrcalc/in.mif 2 -mult -neg -exp 10 -add - | testing_diff_image - mrcalc/o
mrcalc mrcalc/in.mif 1.224 -div -cos mrcalc/in.mif -abs -sqrt -log -atanh -sub - | testing_diff_image - mrcalc/out2.mif -frac 1e-5
mrcalc mrcalc/in.mif 0.2 -gt mrcalc/in.mif mrcalc/in.mif -1.123 -mult 0.9324 -add -exp -neg -if - | testing_diff_image - mrcalc/out3.mif -frac 1e-5
mrcalc mrcalc/in.mif 0+1j -mult -exp mrcalc/in.mif -mult 1.34+5.12j -mult - | testing_diff_image - mrcalc/out4.mif -frac 1e-5
mrinfo dwi.mif -dwgrad > tmp.txt && truncate -s-1 tmp.txt && mrconvert dwi.mif -grad tmp.txt - | mrcalc - dwi.mif -add 0.5 -mult - | mrinfo - -dwgrad
if [ mrtransform dwi.mif -flip 0 - | mrcalc - dwi.mif -add 0.5 -mult - | mrinfo - -property dw_scheme ]; then exit 1; else exit 0; fi

0 comments on commit 2991bed

Please sign in to comment.