From 9f83c6f9f479e29f43decd28bde5d39abc346048 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauldmccarthy@gmail.com> Date: Wed, 7 Sep 2022 16:26:46 +0100 Subject: [PATCH 1/5] RF: avoid pointless loops depending on the value of isComplex --- generalio.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/generalio.cc b/generalio.cc index ad8fc0e..081d1fa 100644 --- a/generalio.cc +++ b/generalio.cc @@ -576,11 +576,16 @@ int read_complexvolume(volume<float>& realvols, volume<float>& imagvols, extensions.clear(); } - for ( size_t voxel=0;voxel<volsize && !isComplex;voxel++) //Only copy real buffer for non-complex - rbuffer[voxel]=((float*)buffer)[voxel]; - for ( size_t voxel=0;voxel<volsize && isComplex;voxel++) { - rbuffer[voxel]=((float *)buffer)[2*voxel]; - ibuffer[voxel]=((float *)buffer)[2*voxel+1]; + //Only copy real buffer for non-complex + if (!isComplex) { + for ( size_t voxel=0;voxel<volsize && !isComplex;voxel++) + rbuffer[voxel]=((float*)buffer)[voxel]; + } + else { + for ( size_t voxel=0;voxel<volsize && isComplex;voxel++) { + rbuffer[voxel]=((float *)buffer)[2*voxel]; + ibuffer[voxel]=((float *)buffer)[2*voxel+1]; + } } realvols.reinitialize(sx,sy,sz,st,rbuffer,true); imagvols.reinitialize(sx,sy,sz,st,ibuffer,true); -- GitLab From 209e5e08bc20ed0e44106cde159f21dfc8d65cfd Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauldmccarthy@gmail.com> Date: Wed, 7 Sep 2022 16:27:24 +0100 Subject: [PATCH 2/5] BF: Actually copy orientation info from complex input file to real/imag outputs, instead of just doing L/R flip --- generalio.cc | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/generalio.cc b/generalio.cc index 081d1fa..c5e770d 100644 --- a/generalio.cc +++ b/generalio.cc @@ -593,16 +593,10 @@ int read_complexvolume(volume<float>& realvols, volume<float>& imagvols, realvols.setdims(niihdr.pixdim[1],niihdr.pixdim[2],niihdr.pixdim[3],niihdr.pixdim[4]); imagvols.setdims(niihdr.pixdim[1],niihdr.pixdim[2],niihdr.pixdim[3],niihdr.pixdim[4]); - // swap to Radiological when necessary - if ( NiftiGetLeftRightOrder(niihdr) != FSL_RADIOLOGICAL ) { - realvols.RadiologicalFile = false; - realvols.makeradiological(); - imagvols.RadiologicalFile = false; - imagvols.makeradiological(); - } else { - realvols.RadiologicalFile = true; - imagvols.RadiologicalFile = true; - } + + set_volume_properties(niihdr,realvols); + set_volume_properties(niihdr,imagvols); + delete [] buffer; return 0; } -- GitLab From b932d06df9ba1063ff328fc1d87c2ff4dbdbb4fa Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauldmccarthy@gmail.com> Date: Wed, 7 Sep 2022 17:14:57 +0100 Subject: [PATCH 3/5] RF: remove isComplex check in for-loop conditionals --- generalio.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/generalio.cc b/generalio.cc index c5e770d..7352e31 100644 --- a/generalio.cc +++ b/generalio.cc @@ -578,13 +578,13 @@ int read_complexvolume(volume<float>& realvols, volume<float>& imagvols, //Only copy real buffer for non-complex if (!isComplex) { - for ( size_t voxel=0;voxel<volsize && !isComplex;voxel++) - rbuffer[voxel]=((float*)buffer)[voxel]; + for ( size_t voxel=0; voxel < volsize; voxel++) + rbuffer[voxel] = ((float*)buffer)[voxel]; } else { - for ( size_t voxel=0;voxel<volsize && isComplex;voxel++) { - rbuffer[voxel]=((float *)buffer)[2*voxel]; - ibuffer[voxel]=((float *)buffer)[2*voxel+1]; + for ( size_t voxel=0; voxel < volsize; voxel++) { + rbuffer[voxel] = ((float *)buffer)[2*voxel]; + ibuffer[voxel] = ((float *)buffer)[2*voxel+1]; } } realvols.reinitialize(sx,sy,sz,st,rbuffer,true); -- GitLab From 33a28d4f2b0a6662fce8fe21971bb58921793126 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauldmccarthy@gmail.com> Date: Thu, 8 Sep 2022 15:37:59 +0100 Subject: [PATCH 4/5] BF: Accidentally removed l/r flips in read_complexvolume --- generalio.cc | 3 +++ test/{test.cc => test_volume_constructor.cc} | 0 2 files changed, 3 insertions(+) rename test/{test.cc => test_volume_constructor.cc} (100%) diff --git a/generalio.cc b/generalio.cc index 7352e31..55cf824 100644 --- a/generalio.cc +++ b/generalio.cc @@ -597,6 +597,9 @@ int read_complexvolume(volume<float>& realvols, volume<float>& imagvols, set_volume_properties(niihdr,realvols); set_volume_properties(niihdr,imagvols); + if (!realvols.RadiologicalFile) realvols.makeradiological(); + if (!imagvols.RadiologicalFile) imagvols.makeradiological(); + delete [] buffer; return 0; } diff --git a/test/test.cc b/test/test_volume_constructor.cc similarity index 100% rename from test/test.cc rename to test/test_volume_constructor.cc -- GitLab From 397d9a14b60afd8604caa2845258c574550ef6ac Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauldmccarthy@gmail.com> Date: Thu, 8 Sep 2022 15:38:56 +0100 Subject: [PATCH 5/5] TEST: Re-arrange test code, and add unit tests for read/save_complexvolume --- test/Makefile | 11 +++- test/feedsRun | 59 ++++++++++++++++-- test/test_complexvolume.cc | 103 ++++++++++++++++++++++++++++++++ test/test_main.cc | 4 ++ test/test_volume_constructor.cc | 9 ++- 5 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 test/test_complexvolume.cc create mode 100644 test/test_main.cc diff --git a/test/Makefile b/test/Makefile index 621c2b7..7d85699 100644 --- a/test/Makefile +++ b/test/Makefile @@ -5,7 +5,7 @@ PROJNAME = test-newimage TESTXFILES = test-newimage LIBS = -lfsl-newimage -lfsl-miscmaths -lfsl-NewNifti \ - -lfsl-cprob -lfsl-utils -lfsl-znz + -lfsl-cprob -lfsl-utils -lfsl-znz -lboost_unit_test_framework # The test program can be run against # an in-source checkout, or against @@ -30,8 +30,13 @@ endif all: ${TESTXFILES} +OBJS = test_main.o test_volume_constructor.o test_complexvolume.o + # We add -I.., -L.., -Wl,-rpath so that # in-source builds take precedence over # $FSLDEVDIR/$FSLDIR -test-newimage: test.cc - $(CXX) -I.. ${CXXFLAGS} -o $@ $< -L.. ${RPATH} ${LDFLAGS} +%.o: %.cc + $(CXX) -I.. ${CXXFLAGS} -c -o $@ $< + +test-newimage: ${OBJS} + $(CXX) -o $@ $^ -L.. ${RPATH} ${LDFLAGS} diff --git a/test/feedsRun b/test/feedsRun index dcf6266..b8d681d 100755 --- a/test/feedsRun +++ b/test/feedsRun @@ -1,6 +1,57 @@ -#!/usr/bin/env bash +#!/usr/bin/env python -set -e +import numpy as np +import nibabel as nib +import subprocess as sp +import os.path as op +import shlex +import sys +import traceback -make -./test-newimage -l all +from fsl.utils.tempdir import tempdir +from fsl.data.image import addExt + + +def sprun(cmd): + print(f'RUN {cmd}') + sp.run(shlex.split(cmd), check=True) + + +# radio=[True|False] -> whether or not +# to add a flip on the affine X axis +def create_test_input_data(seed=1): + + np.random.seed(seed) + + rneuro = np.random.randint(0, 100, (10, 10, 10)).astype(np.float32) + ineuro = np.random.randint(0, 100, (10, 10, 10)).astype(np.float32) + rradio = np.flip(rneuro, 0) + iradio = np.flip(ineuro, 0) + + # test_complexvolume.cc has these + # same values hard-coded, so if + # you change one, you must change + # the other. + affneuro = np.diag([ 3, 3, 3, 1]) + affradio = np.diag([-3, 3, 3, 1]) + affneuro[:3, 3] = [10, 20, 30] + affradio[:3, 3] = [37, 20, 30] + + cneuro = rneuro + ineuro * 1j + cradio = rradio + iradio * 1j + + cneuro = nib.Nifti1Image(cneuro, affneuro) + cradio = nib.Nifti1Image(cradio, affradio) + + cneuro.set_qform(*cneuro.get_sform(coded=True)) + cradio.set_qform(*cradio.get_sform(coded=True)) + + cneuro.to_filename(f'complex_neuro.nii.gz') + cradio.to_filename(f'complex_radio.nii.gz') + + +if __name__ == '__main__': + create_test_input_data() + sprun('make clean') + sprun('make') + sprun('./test-newimage -l test_suite') diff --git a/test/test_complexvolume.cc b/test/test_complexvolume.cc new file mode 100644 index 0000000..4b95e94 --- /dev/null +++ b/test/test_complexvolume.cc @@ -0,0 +1,103 @@ +#define EXPOSE_TREACHEROUS + +#include "newimage/newimageall.h" +#include "armawrap/newmat.h" +#include <stdlib.h> +#include <vector> +#include <boost/test/unit_test.hpp> + + +BOOST_AUTO_TEST_SUITE(test_complexvolume) + + +using namespace NEWIMAGE; +using namespace NEWMAT; +using namespace std; + +// Test data used by these tests is generated in the +// feedsRun script before execution. + +// Test that neurologically and radiologically stored images +// (which are otherwise identical) are loaded correctly +BOOST_AUTO_TEST_CASE(read_complexvolume_handle_neuro_radio) +{ + complexvolume neuro, radio; + + BOOST_CHECK(read_complexvolume(neuro, string("complex_neuro")) == 0); + BOOST_CHECK(read_complexvolume(radio, string("complex_radio")) == 0); + + BOOST_CHECK(neuro.xsize() == radio.xsize()); + BOOST_CHECK(neuro.ysize() == radio.ysize()); + BOOST_CHECK(neuro.zsize() == radio.zsize()); + + for (int x = 0; x < neuro.xsize(); x++) { + for (int y = 0; y < neuro.ysize(); y++) { + for (int z = 0; z < neuro.zsize(); z++) { + BOOST_CHECK(neuro.re(x, y, z) == radio.re(x, y, z)); + BOOST_CHECK(neuro.im(x, y, z) == radio.im(x, y, z)); + }}} +} + + +BOOST_AUTO_TEST_CASE(read_save_complexvolume_round_trip) +{ + volume<float> rneuro, ineuro, rradio, iradio, + rneuro2, ineuro2, rradio2, iradio2; + + // defined in feedsRun + Matrix neuromat(4, 4), radiomat(4, 4); + neuromat << 3 << 0 << 0 << 10 + << 0 << 3 << 0 << 20 + << 0 << 0 << 3 << 30 + << 0 << 0 << 0 << 1; + radiomat << -3 << 0 << 0 << 37 + << 0 << 3 << 0 << 20 + << 0 << 0 << 3 << 30 + << 0 << 0 << 0 << 1; + + BOOST_CHECK(read_complexvolume(rneuro, ineuro, string("complex_neuro")) == 0); + BOOST_CHECK(read_complexvolume(rradio, iradio, string("complex_radio")) == 0); + BOOST_CHECK(save_complexvolume(rneuro, ineuro, string("complex_neuro2")) == 0); + BOOST_CHECK(save_complexvolume(rradio, iradio, string("complex_radio2")) == 0); + BOOST_CHECK(read_complexvolume(rneuro2, ineuro2, string("complex_neuro2")) == 0); + BOOST_CHECK(read_complexvolume(rradio2, iradio2, string("complex_radio2")) == 0); + + BOOST_CHECK(rneuro.sform_code() == rneuro2.sform_code()); + BOOST_CHECK(rneuro.qform_code() == rneuro2.qform_code()); + BOOST_CHECK(ineuro.sform_code() == ineuro2.sform_code()); + BOOST_CHECK(ineuro.qform_code() == ineuro2.qform_code()); + BOOST_CHECK(rradio.sform_code() == rradio2.sform_code()); + BOOST_CHECK(rradio.qform_code() == rradio2.qform_code()); + + // All volumes are presented as radiological - the + // L/R flip on neurological volumes is also applied + // to the s/qform affines, so we compare all volumes + // against the known radiological affine. + BOOST_CHECK(rradio .sform_mat() == radiomat); + BOOST_CHECK(rradio2.sform_mat() == radiomat); + BOOST_CHECK(iradio .sform_mat() == radiomat); + BOOST_CHECK(iradio2.sform_mat() == radiomat); + BOOST_CHECK(rneuro .sform_mat() == radiomat); + BOOST_CHECK(rneuro2.sform_mat() == radiomat); + BOOST_CHECK(ineuro .sform_mat() == radiomat); + BOOST_CHECK(ineuro2.sform_mat() == radiomat); + BOOST_CHECK(rradio .qform_mat() == radiomat); + BOOST_CHECK(rradio2.qform_mat() == radiomat); + BOOST_CHECK(iradio .qform_mat() == radiomat); + BOOST_CHECK(iradio2.qform_mat() == radiomat); + BOOST_CHECK(rneuro .qform_mat() == radiomat); + BOOST_CHECK(rneuro2.qform_mat() == radiomat); + BOOST_CHECK(ineuro .qform_mat() == radiomat); + BOOST_CHECK(ineuro2.qform_mat() == radiomat); + + for (int x = 0; x < rneuro.xsize(); x++) { + for (int y = 0; y < rneuro.ysize(); y++) { + for (int z = 0; z < rneuro.zsize(); z++) { + BOOST_CHECK(rneuro(x, y, z) == rneuro2(x, y, z)); + BOOST_CHECK(ineuro(x, y, z) == ineuro2(x, y, z)); + BOOST_CHECK(rradio(x, y, z) == rradio2(x, y, z)); + BOOST_CHECK(iradio(x, y, z) == iradio2(x, y, z)); + }}} +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/test_main.cc b/test/test_main.cc new file mode 100644 index 0000000..58fcf6c --- /dev/null +++ b/test/test_main.cc @@ -0,0 +1,4 @@ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN +#define BOOST_TEST_MODULE newimage +#include <boost/test/unit_test.hpp> diff --git a/test/test_volume_constructor.cc b/test/test_volume_constructor.cc index 08a9395..c06a804 100644 --- a/test/test_volume_constructor.cc +++ b/test/test_volume_constructor.cc @@ -1,8 +1,11 @@ #include "newimage/newimageall.h" #include <stdlib.h> #include <vector> -#define BOOST_TEST_MODULE newimage -#include <boost/test/included/unit_test.hpp> +#include <boost/test/unit_test.hpp> + + +BOOST_AUTO_TEST_SUITE(test_volume_constructor) + using namespace NEWIMAGE; @@ -76,3 +79,5 @@ BOOST_AUTO_TEST_CASE(volume_4D_reinitialize) BOOST_TEST(v(xi, yi, zi, ti) == i); } } + +BOOST_AUTO_TEST_SUITE_END() -- GitLab