From 972ee73bc81a98c6b8a70fac3fba3669b28347b9 Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:00 -0400 Subject: [PATCH 01/12] ENH: add multishell DWI test dataset Small patch of multi-shell phantom dataset from Tristan Kuder. Used with permission to distribute for testing (personal correspondence). No PHI issues because this is from a phantom, and only a few voxels. --- .../TestData/multishell-DWI-451dir.nhdr | 467 ++++++++++++++++++ .../TestData/multishell-DWI-451dir.raw.gz | Bin 0 -> 49880 bytes 2 files changed, 467 insertions(+) create mode 100644 Libs/MRML/Core/Testing/TestData/multishell-DWI-451dir.nhdr create mode 100644 Libs/MRML/Core/Testing/TestData/multishell-DWI-451dir.raw.gz diff --git a/Libs/MRML/Core/Testing/TestData/multishell-DWI-451dir.nhdr b/Libs/MRML/Core/Testing/TestData/multishell-DWI-451dir.nhdr new file mode 100644 index 000000000..3199e786e --- /dev/null +++ b/Libs/MRML/Core/Testing/TestData/multishell-DWI-451dir.nhdr @@ -0,0 +1,467 @@ +NRRD0005 +# Complete NRRD file format specification at: +# http://teem.sourceforge.net/nrrd/format.html +type: short +dimension: 4 +space: right-anterior-superior +sizes: 451 7 7 2 +space directions: none (-2.4904873929542752,0.001526874873563903,0.2178766031241865) (2.6429097682729909e-08,-2.4999386104169656,0.017519821531186731) (0.21788201640672655,0.017453167118519793,2.4904271887081069) +kinds: list domain domain domain +endian: little +encoding: gzip +space origin: (9.1900975709163664,98.098053174005756,9.8750159998483902) +measurement frame: (-1,0,0) (0,-1,0) (0,0,1) +data file: multishell-DWI-451dir.raw.gz +modality:=DWMRI +DWMRI_b-value:=10000 +DWMRI_gradient_0000:=0 0 0 +DWMRI_gradient_0001:=-0.0435405933728607 -0.08253993587221661 -0.127637672255725 +DWMRI_gradient_0002:=0.019808779758852785 -0.08236379602390693 -0.13350586883320079 +DWMRI_gradient_0003:=0.05080403555446169 -0.028030237301649163 -0.14708247833139243 +DWMRI_gradient_0004:=-0.07127480876410455 -0.1156972833518431 -0.08083340846799643 +DWMRI_gradient_0005:=-0.03550577181866828 -0.14896142375685542 -0.039367931921825736 +DWMRI_gradient_0006:=-0.1354225383313697 -0.08157129416774117 0.0026191112230762223 +DWMRI_gradient_0007:=-0.12138675083097808 -0.0820846816605407 -0.05939159387738341 +DWMRI_gradient_0008:=-0.07635146390615032 -0.028018593683141906 -0.13559281703437362 +DWMRI_gradient_0009:=-0.12456935077162568 -0.027843181760964994 -0.09331255939942835 +DWMRI_gradient_0010:=-0.09959284031365688 -0.1147493362544074 0.043747635951462506 +DWMRI_gradient_0011:=-0.04914768787611955 -0.14849992593393874 0.023071116963782706 +DWMRI_gradient_0012:=-0.03996209622465154 -0.08027147869389462 0.1302287227824093 +DWMRI_gradient_0013:=-0.09466969047450009 -0.08072730123066364 0.09757434194185656 +DWMRI_gradient_0014:=-0.1526104111205339 -0.027156004686520706 0.03118675185070729 +DWMRI_gradient_0015:=-0.12773979422051843 -0.026395244965366403 0.08936350306264973 +DWMRI_gradient_0016:=0.009535439920981431 -0.11414700003182758 0.1089932907713021 +DWMRI_gradient_0017:=0.005411147615978945 -0.1481464581367289 0.05498496237091392 +DWMRI_gradient_0018:=0.11064111433671167 -0.08099254652004932 0.07873214648692521 +DWMRI_gradient_0019:=0.0628949878120545 -0.08037436692862567 0.1207649838026506 +DWMRI_gradient_0020:=0.01809211645521744 0.02580870271120508 -0.15494058813121173 +DWMRI_gradient_0021:=0.04548288844650951 -0.025988992432039046 0.14918404336536828 +DWMRI_gradient_0022:=0.10594735047745904 -0.11469463852801046 0.02490579217061331 +DWMRI_gradient_0023:=0.052785443271180864 -0.14849781525152084 0.012731673421055852 +DWMRI_gradient_0024:=0.027996174799976632 -0.14878577027266218 -0.04559614484635787 +DWMRI_gradient_0025:=0.05593561942904512 -0.11577331320945371 -0.09202035694076104 +DWMRI_gradient_0026:=0.10601294465588054 -0.027857716213258322 -0.11395263435603882 +DWMRI_gradient_0027:=0.10889171205826474 -0.08198051468445483 -0.08013607128504707 +DWMRI_gradient_0028:=0.13368893953315997 -0.08156135835876928 -0.021794768479656165 +DWMRI_gradient_0029:=0.14149412627559807 -0.02685656948095772 0.06525439884888913 +DWMRI_gradient_0030:=0.15573156628933804 -0.027119798587823 0.003492198381495499 +DWMRI_gradient_0031:=-0.06164219464482649 -0.11638798730716488 -0.18070438581444667 +DWMRI_gradient_0032:=0.028078368266032265 -0.11670667624389976 -0.18865618399150996 +DWMRI_gradient_0033:=0.07207638550787794 -0.04004145530409182 -0.20785012704318945 +DWMRI_gradient_0034:=-0.10082282331018944 -0.16352816890853175 -0.11442593707636764 +DWMRI_gradient_0035:=-0.0506145355675154 -0.21065383808855018 -0.05534553360902229 +DWMRI_gradient_0036:=-0.19172071484165362 -0.11501840251434411 0.0037327451391003107 +DWMRI_gradient_0037:=-0.1716967527588114 -0.11593202609554756 -0.0841426718144883 +DWMRI_gradient_0038:=-0.10827801413964402 -0.03981492511354331 -0.1915480153768183 +DWMRI_gradient_0039:=-0.17587159737155433 -0.03938737788796415 -0.132354880921364 +DWMRI_gradient_0040:=-0.14106492090108647 -0.1622429583849325 0.06146470001793797 +DWMRI_gradient_0041:=-0.0699082131329457 -0.20994023985539517 0.03221701768689335 +DWMRI_gradient_0042:=-0.05672703605187412 -0.11373621261502757 0.1839731396790804 +DWMRI_gradient_0043:=-0.13381370700355508 -0.11435720681878117 0.13789967320190813 +DWMRI_gradient_0044:=-0.21588102020550604 -0.038091814519146394 0.04409532383698598 +DWMRI_gradient_0045:=-0.18037458026540057 -0.03775485962721007 0.12664746413264685 +DWMRI_gradient_0046:=0.013485148388621667 -0.16142823470139028 0.15413978920690635 +DWMRI_gradient_0047:=0.0072310941397070545 -0.20951155027051543 0.07779858856950933 +DWMRI_gradient_0048:=0.15645938102556564 -0.1141586697467961 0.11175087925094195 +DWMRI_gradient_0049:=0.0887530817140321 -0.11393362555728564 0.17071033435692867 +DWMRI_gradient_0050:=0.025337947765010523 0.03675848421076293 -0.21910454364116802 +DWMRI_gradient_0051:=0.06394762191979594 -0.036768840187391454 0.2110894427599275 +DWMRI_gradient_0052:=0.14970501161354594 -0.16225000808946774 0.035543539150956246 +DWMRI_gradient_0053:=0.07469633599075422 -0.2099830288798696 0.018099275527270243 +DWMRI_gradient_0054:=0.03927081229507744 -0.21043519377332578 -0.06461293466394662 +DWMRI_gradient_0055:=0.07942178819154533 -0.1635281831443905 -0.13019489882699625 +DWMRI_gradient_0056:=0.1500592224032128 -0.03935485648531481 -0.16103857874864394 +DWMRI_gradient_0057:=0.1537212066728381 -0.11583687666979026 -0.11380512916847906 +DWMRI_gradient_0058:=0.1890642624419567 -0.11530511771980552 -0.030974720297696223 +DWMRI_gradient_0059:=0.2002163334930603 -0.03776032298108295 0.0921280446638253 +DWMRI_gradient_0060:=0.22029399780670564 -0.03807782601815774 0.0045423010347048105 +DWMRI_gradient_0061:=-0.07535451657102352 -0.14269716333112586 -0.22126730900808086 +DWMRI_gradient_0062:=0.03436248554566541 -0.14284361960041161 -0.23111669250111952 +DWMRI_gradient_0063:=0.08805718583376586 -0.04882626598861433 -0.2546800608581812 +DWMRI_gradient_0064:=-0.12338394786809032 -0.2004432810414723 -0.1399960646585964 +DWMRI_gradient_0065:=-0.06180214624863405 -0.2579995963755464 -0.06794636572472271 +DWMRI_gradient_0066:=-0.23474236588963474 -0.1409893136136397 0.004247437977391619 +DWMRI_gradient_0067:=-0.2101110854993156 -0.14223799098260892 -0.10306158982671847 +DWMRI_gradient_0068:=-0.13232607485403577 -0.04894564844555471 -0.23472140728542093 +DWMRI_gradient_0069:=-0.21539933074016962 -0.0481477733897098 -0.1621262651774239 +DWMRI_gradient_0070:=-0.17255596441170093 -0.1988477943195271 0.07539230376031128 +DWMRI_gradient_0071:=-0.08548766373663456 -0.2572107663955452 0.03917252385471977 +DWMRI_gradient_0072:=-0.06947366706395741 -0.1393302485540371 0.22530090334625114 +DWMRI_gradient_0073:=-0.16400270419860832 -0.14004051677607113 0.16879506006608994 +DWMRI_gradient_0074:=-0.2644435536743765 -0.046796998772346936 0.053662402592954406 +DWMRI_gradient_0075:=-0.2209379327291914 -0.04604989519832049 0.15513169456837372 +DWMRI_gradient_0076:=0.017021872767417352 -0.19783494497138357 0.18860433722939596 +DWMRI_gradient_0077:=0.009131596117518879 -0.25669555892503426 0.09499478726009616 +DWMRI_gradient_0078:=0.19175766435225572 -0.13987625208935528 0.1366149255743419 +DWMRI_gradient_0079:=0.10874281252543104 -0.13968693687788247 0.20895589490563213 +DWMRI_gradient_0080:=0.03078200473441319 0.045332673141582604 -0.26832335447450184 +DWMRI_gradient_0081:=0.07854003089989477 -0.0450849183653179 0.2584546755396705 +DWMRI_gradient_0082:=0.18318703257612026 -0.19882913156983048 0.043697747782039026 +DWMRI_gradient_0083:=0.09130163476193216 -0.2572408137064588 0.02216260091951948 +DWMRI_gradient_0084:=0.048214371926680474 -0.2576949174435143 -0.07917518228318952 +DWMRI_gradient_0085:=0.09720007301616003 -0.20044328271857065 -0.15929419903039946 +DWMRI_gradient_0086:=0.18370984624303588 -0.048155973553261344 -0.19731117614209767 +DWMRI_gradient_0087:=0.1883155613019827 -0.14187426724158334 -0.13931599797676922 +DWMRI_gradient_0088:=0.2315556429872094 -0.14123581183858117 -0.03787393934035816 +DWMRI_gradient_0089:=0.2452171413366311 -0.04616895910749692 0.11285825816350858 +DWMRI_gradient_0090:=0.26978559612678416 -0.04676420434565768 0.005370934789536939 +DWMRI_gradient_0091:=-0.08702919249905602 -0.16484918269524795 -0.25544209769204496 +DWMRI_gradient_0092:=0.039625126432284066 -0.16489202422230043 -0.2669091013036816 +DWMRI_gradient_0093:=0.10186285485420807 -0.05625971735920944 -0.2940387757749879 +DWMRI_gradient_0094:=-0.1426045962162091 -0.23138210319663938 -0.16163616586009727 +DWMRI_gradient_0095:=-0.07127502867422089 -0.2979149116020049 -0.07852753904395476 +DWMRI_gradient_0096:=-0.27092702924056533 -0.1630139455450491 0.004999704027122781 +DWMRI_gradient_0097:=-0.24274120239060498 -0.16399530103560625 -0.1190892461805238 +DWMRI_gradient_0098:=-0.15295757190697923 -0.05635690708652583 -0.27097579020200896 +DWMRI_gradient_0099:=-0.24872621100755893 -0.055815451645712555 -0.18713606272188582 +DWMRI_gradient_0100:=-0.19944464706545803 -0.22955507331683445 0.08675424904336879 +DWMRI_gradient_0101:=-0.09884642477189538 -0.2969223070283711 0.04545906460107797 +DWMRI_gradient_0102:=-0.08029704128935039 -0.16095101934548425 0.2600906633992599 +DWMRI_gradient_0103:=-0.18932757611875436 -0.16170255161097546 0.19495474443864735 +DWMRI_gradient_0104:=-0.3053200579575673 -0.054105531478854635 0.06206651535103009 +DWMRI_gradient_0105:=-0.25520771846625817 -0.053078101982127264 0.17902998123460556 +DWMRI_gradient_0106:=0.01950918560759315 -0.22840368713964385 0.2178328396582696 +DWMRI_gradient_0107:=0.010613930480301419 -0.2963780727231232 0.10976056279159473 +DWMRI_gradient_0108:=0.22148639622943986 -0.16146454935315982 0.15771168074406472 +DWMRI_gradient_0109:=0.1256551041521829 -0.16103881447735585 0.24140690480570978 +DWMRI_gradient_0110:=0.035816442716266654 0.05227373081495207 -0.3098138766315665 +DWMRI_gradient_0111:=0.09075929336556052 -0.05203910822898737 0.29842030852165047 +DWMRI_gradient_0112:=0.21161847219019125 -0.22953844317377475 0.05029636158699937 +DWMRI_gradient_0113:=0.10546226328662543 -0.29702598098412125 0.02555927030500116 +DWMRI_gradient_0114:=0.055802539292673654 -0.2975283972897679 -0.09144904630619646 +DWMRI_gradient_0115:=0.11237105319924057 -0.2313821066502574 -0.18394310473791495 +DWMRI_gradient_0116:=0.21222204844954162 -0.055603113697575215 -0.2277500703724505 +DWMRI_gradient_0117:=0.21739462315544272 -0.16381806907937743 -0.1609447629525377 +DWMRI_gradient_0118:=0.26726424165992707 -0.16320367228868424 -0.04398164545663403 +DWMRI_gradient_0119:=0.2831373922942583 -0.05324142984762755 0.13037854842046928 +DWMRI_gradient_0120:=0.31150056452887814 -0.05409036278555934 0.006452101018890364 +DWMRI_gradient_0121:=-0.10661730950027032 -0.2017028711773885 -0.3129675854648912 +DWMRI_gradient_0122:=0.04856482018161502 -0.2020144510893584 -0.3268510796521793 +DWMRI_gradient_0123:=0.12472869386518388 -0.06911626995752577 -0.3600912420926761 +DWMRI_gradient_0124:=-0.17464631519842963 -0.2833356492451853 -0.19803934692990355 +DWMRI_gradient_0125:=-0.08741127326951775 -0.36479641968891496 -0.09634754779211196 +DWMRI_gradient_0126:=-0.33191278726221735 -0.19949363934355094 0.006016476651149365 +DWMRI_gradient_0127:=-0.2972437649792216 -0.20090735193856776 -0.1458848470404393 +DWMRI_gradient_0128:=-0.18732264085780567 -0.0689666245681091 -0.33189432263258395 +DWMRI_gradient_0129:=-0.30454943647819244 -0.06829466834932084 -0.22931525787412024 +DWMRI_gradient_0130:=-0.24428977177657205 -0.2811020092184974 0.10632109995987046 +DWMRI_gradient_0131:=-0.12109215977607642 -0.36368641885082686 0.05539752758227258 +DWMRI_gradient_0132:=-0.09827467994530596 -0.1971376986015449 0.3185574101315754 +DWMRI_gradient_0133:=-0.2319195178331428 -0.19798696785062492 0.2387771058693875 +DWMRI_gradient_0134:=-0.37393180996141284 -0.06616954319365938 0.07613541574409922 +DWMRI_gradient_0135:=-0.312583666933829 -0.06517418633077454 0.21918892554215696 +DWMRI_gradient_0136:=0.023714218549932328 -0.27970184621873023 0.26684175373880775 +DWMRI_gradient_0137:=0.012825322605173942 -0.36302283240425737 0.1343500752931172 +DWMRI_gradient_0138:=0.27121826354934536 -0.1977865919205903 0.193186764690851 +DWMRI_gradient_0139:=0.1539839780236933 -0.19739227521210348 0.2955084308045889 +DWMRI_gradient_0140:=0.04392218393741768 0.06394561885287031 -0.3794493484850035 +DWMRI_gradient_0141:=0.11107237586072549 -0.06375970242111817 0.3655101040269977 +DWMRI_gradient_0142:=0.25919354249537785 -0.28113691951248404 0.061487791420170725 +DWMRI_gradient_0143:=0.12931529779908077 -0.36370623872832186 0.03154891667291467 +DWMRI_gradient_0144:=0.06838930642552103 -0.36439417197039453 -0.11198124253038663 +DWMRI_gradient_0145:=0.13747024634142554 -0.2833425677534917 -0.22543054173666957 +DWMRI_gradient_0146:=0.25987262272626965 -0.06846227960066191 -0.2788891261904151 +DWMRI_gradient_0147:=0.26620635910067997 -0.20060874206637058 -0.19720627641865465 +DWMRI_gradient_0148:=0.3273766489886954 -0.19982677457672363 -0.05379403626052265 +DWMRI_gradient_0149:=0.3468199975713337 -0.0652836906490088 0.15954290174704477 +DWMRI_gradient_0150:=0.3815291817605719 -0.06615980878186926 0.007640133837676735 +DWMRI_gradient_0151:=-0.12319461666641202 -0.2329319746509491 -0.3613388670211172 +DWMRI_gradient_0152:=0.05610502411743374 -0.2332178050137576 -0.37744095816938905 +DWMRI_gradient_0153:=0.1440078512746057 -0.07961746098527506 -0.4158398656790959 +DWMRI_gradient_0154:=-0.2015019972088169 -0.3272225910327197 -0.2287407183891199 +DWMRI_gradient_0155:=-0.100746873597869 -0.4212779137986792 -0.11124289647508712 +DWMRI_gradient_0156:=-0.3832133960476074 -0.23043497091002552 0.006871133955507228 +DWMRI_gradient_0157:=-0.34324342585435064 -0.23193436645142718 -0.16849449687459597 +DWMRI_gradient_0158:=-0.21626450711695822 -0.07979369887356572 -0.3832265958829229 +DWMRI_gradient_0159:=-0.35164306128568185 -0.07890389375032804 -0.2648043206051414 +DWMRI_gradient_0160:=-0.28201831853963616 -0.32464034131408237 0.12277748983438137 +DWMRI_gradient_0161:=-0.13976355429000226 -0.41995940879928534 0.06403310912067042 +DWMRI_gradient_0162:=-0.11336034889214539 -0.2276450214943984 0.36786841492352296 +DWMRI_gradient_0163:=-0.26775510103027467 -0.22864031788049993 0.2757368421561591 +DWMRI_gradient_0164:=-0.43177004087322635 -0.07659134664975159 0.08779745231605111 +DWMRI_gradient_0165:=-0.36095708226934065 -0.0752547955415867 0.2530744865016578 +DWMRI_gradient_0166:=0.0275827222732003 -0.3230982777868819 0.30797190055852464 +DWMRI_gradient_0167:=0.014779249931607244 -0.41922779516328484 0.15501491781267127 +DWMRI_gradient_0168:=0.3131497852540416 -0.22839458538444177 0.22309890311898878 +DWMRI_gradient_0169:=0.17766641884128737 -0.22792600031061885 0.3412980767730684 +DWMRI_gradient_0170:=0.05070594444514501 0.07379531846907662 -0.4381588220958842 +DWMRI_gradient_0171:=0.1283602266849844 -0.0735870180369772 0.42202914377123796 +DWMRI_gradient_0172:=0.29922093226125707 -0.32468224396027323 0.07105117426267547 +DWMRI_gradient_0173:=0.14927221539110186 -0.4199951981369137 0.036357041472682095 +DWMRI_gradient_0174:=0.07886237164040218 -0.420792189401262 -0.12928516886566213 +DWMRI_gradient_0175:=0.15872141072907534 -0.32722259073921706 -0.26025542279953806 +DWMRI_gradient_0176:=0.3000737623262345 -0.07893029625425067 -0.32206480824063033 +DWMRI_gradient_0177:=0.3074020775359278 -0.23165069192128027 -0.2276881965967203 +DWMRI_gradient_0178:=0.3780250758214732 -0.23071566171951066 -0.062187785761972855 +DWMRI_gradient_0179:=0.40049348087649705 -0.07531433730240077 0.1842083537264436 +DWMRI_gradient_0180:=0.44052421378429657 -0.07654178533049413 0.008931258513069753 +DWMRI_gradient_0181:=-0.13775578629717292 -0.26039103596920354 -0.4040047669626343 +DWMRI_gradient_0182:=0.06264040809987523 -0.2607707460700034 -0.4219890961472521 +DWMRI_gradient_0183:=0.1609696781012842 -0.08909122840344762 -0.4649209779667225 +DWMRI_gradient_0184:=-0.22530004147890145 -0.365764286291542 -0.25584444137648177 +DWMRI_gradient_0185:=-0.11274645758712232 -0.47100974303925697 -0.12424998295230573 +DWMRI_gradient_0186:=-0.4284966316406468 -0.25754526416246826 0.007814958739762633 +DWMRI_gradient_0187:=-0.383704340863291 -0.25933066440717156 -0.18846375062807752 +DWMRI_gradient_0188:=-0.24177562048205267 -0.08903691300704049 -0.4285055162570197 +DWMRI_gradient_0189:=-0.3931759320502539 -0.088154837997076 -0.2960429208647342 +DWMRI_gradient_0190:=-0.3153405258815403 -0.3629314383158261 0.13726297322570008 +DWMRI_gradient_0191:=-0.1561394173900406 -0.46959316401855067 0.07143348405436457 +DWMRI_gradient_0192:=-0.12680780450524642 -0.25454192476301923 0.4112519778122613 +DWMRI_gradient_0193:=-0.2993782059325791 -0.2556770951707159 0.3082238031328128 +DWMRI_gradient_0194:=-0.4827847504214065 -0.08548291069049567 0.09803854721700524 +DWMRI_gradient_0195:=-0.4035818704367852 -0.08406211701479535 0.2829403370637291 +DWMRI_gradient_0196:=0.03070096400814271 -0.3611889005218447 0.3443835491843237 +DWMRI_gradient_0197:=0.01664150367030084 -0.4686648084069295 0.1734253664158948 +DWMRI_gradient_0198:=0.35014460374350204 -0.25539953161045026 0.24933879706629786 +DWMRI_gradient_0199:=0.19870433165346452 -0.25490224302435366 0.38149893195565215 +DWMRI_gradient_0200:=0.056723148457959505 0.08244215817286303 -0.4898834299961745 +DWMRI_gradient_0201:=0.14345839285847356 -0.08251543151075878 0.4718165883911429 +DWMRI_gradient_0202:=0.33463838642439314 -0.3629440063337457 0.07930194573709043 +DWMRI_gradient_0203:=0.16680701471653372 -0.46960575499601337 0.04056913501644258 +DWMRI_gradient_0204:=0.08823549920132281 -0.47045404639564853 -0.14452503904394634 +DWMRI_gradient_0205:=0.17745152042469944 -0.365764288582441 -0.2910797881968846 +DWMRI_gradient_0206:=0.3354320001424081 -0.08818289080075435 -0.3601515667749978 +DWMRI_gradient_0207:=0.3436588586788565 -0.25897791543161763 -0.2546154515543776 +DWMRI_gradient_0208:=0.4225985429102583 -0.2580117596262961 -0.06957300787423887 +DWMRI_gradient_0209:=0.44775707283790095 -0.08421647690524289 0.205964047205582 +DWMRI_gradient_0210:=0.4925394380878641 -0.08547043843090789 0.00998529331163515 +DWMRI_gradient_0211:=-0.15084788471209937 -0.2852944036484605 -0.44255173870303033 +DWMRI_gradient_0212:=0.06851557293260434 -0.28558782333084026 -0.4623258958085633 +DWMRI_gradient_0213:=0.17632819248088213 -0.09749437480594739 -0.5093164418335061 +DWMRI_gradient_0214:=-0.24678657353304453 -0.40071833927949646 -0.28021638459094844 +DWMRI_gradient_0215:=-0.12356673119564145 -0.5159444392635841 -0.1361345695954213 +DWMRI_gradient_0216:=-0.4693102928146301 -0.28227162810171846 0.008402352728375442 +DWMRI_gradient_0217:=-0.4204038104035923 -0.284056367476596 -0.2063313810317332 +DWMRI_gradient_0218:=-0.2647259778261163 -0.09768709828024345 -0.46944372399459494 +DWMRI_gradient_0219:=-0.430660779251521 -0.0966672110845936 -0.3243250931086277 +DWMRI_gradient_0220:=-0.34541636388120683 -0.3976015046415838 0.15033496457629228 +DWMRI_gradient_0221:=-0.17107491151285778 -0.5143858346293447 0.0783620462204833 +DWMRI_gradient_0222:=-0.13895193819693596 -0.2787975175100607 0.4505156219332057 +DWMRI_gradient_0223:=-0.32804445724175807 -0.28002517764209534 0.33759851371680255 +DWMRI_gradient_0224:=-0.5288249079994926 -0.09375554901280139 0.1074901679827383 +DWMRI_gradient_0225:=-0.4421318599977444 -0.09209323882224675 0.3099004295616326 +DWMRI_gradient_0226:=0.033519704769112715 -0.3957486218387528 0.37717301097671363 +DWMRI_gradient_0227:=0.018190404568125366 -0.5134326568508469 0.18988427677153588 +DWMRI_gradient_0228:=0.3835730451831964 -0.27980352889489823 0.273096552030911 +DWMRI_gradient_0229:=0.21764410377990437 -0.27913623093556605 0.41798807539599664 +DWMRI_gradient_0230:=0.06208473045998043 0.09042897567664963 -0.5366266011912793 +DWMRI_gradient_0231:=0.15725817703797926 -0.09035408385714651 0.5168230139896521 +DWMRI_gradient_0232:=0.36650179199803484 -0.3976668927092877 0.0868187848883514 +DWMRI_gradient_0233:=0.1827689894533363 -0.5144126906468925 0.04444212753628538 +DWMRI_gradient_0234:=0.09657291924484927 -0.5153605122720848 -0.15835793685036126 +DWMRI_gradient_0235:=0.1943795961710207 -0.4007183717645608 -0.3188124552571167 +DWMRI_gradient_0236:=0.36746759550927977 -0.09656616231514072 -0.39451560812048364 +DWMRI_gradient_0237:=0.3764722055357021 -0.2837662347815696 -0.27882866596047123 +DWMRI_gradient_0238:=0.46300439809393995 -0.2825349571298158 -0.07616395374218934 +DWMRI_gradient_0239:=0.49053681643404395 -0.09226780733497751 0.2255222959192047 +DWMRI_gradient_0240:=0.5395618539023956 -0.09357644007361858 0.01079250577387872 +DWMRI_gradient_0241:=-0.174145541090509 -0.3293800494262352 -0.5110597892427358 +DWMRI_gradient_0242:=0.07911797164963137 -0.32977567366859867 -0.5338429977446879 +DWMRI_gradient_0243:=0.2036302842209438 -0.11269353308405082 -0.5880772649675352 +DWMRI_gradient_0244:=-0.2849635114736244 -0.46268314466105037 -0.32360485070182105 +DWMRI_gradient_0245:=-0.14265662907487553 -0.5957245409284968 -0.1573574025557423 +DWMRI_gradient_0246:=-0.5419801143623331 -0.325829261770931 0.009635477786144207 +DWMRI_gradient_0247:=-0.4854195105880537 -0.32800472833870303 -0.23828720391677105 +DWMRI_gradient_0248:=-0.3057404907671426 -0.1127513883851191 -0.5420423150873552 +DWMRI_gradient_0249:=-0.49727911967507377 -0.11147642745935948 -0.37454836520621276 +DWMRI_gradient_0250:=-0.39878081616788325 -0.4591655353203526 0.17361125094018479 +DWMRI_gradient_0251:=-0.19759251473014022 -0.5939575446589992 0.0903970568538438 +DWMRI_gradient_0252:=-0.1605077205815083 -0.3219471673355463 0.5201800531555502 +DWMRI_gradient_0253:=-0.3786900554395371 -0.3234320906482386 0.3898532022090811 +DWMRI_gradient_0254:=-0.6106650948436265 -0.10825440286612308 0.12397225827695735 +DWMRI_gradient_0255:=-0.510528773151259 -0.10642922250222007 0.35781725364326417 +DWMRI_gradient_0256:=0.0387831384814108 -0.456936543411862 0.4355512117844654 +DWMRI_gradient_0257:=0.02105064578025935 -0.5928772980072334 0.21921079910086771 +DWMRI_gradient_0258:=0.4429566466038691 -0.322988840582325 0.31538486442964453 +DWMRI_gradient_0259:=0.25142944872073403 -0.32233642582694755 0.4825789629932716 +DWMRI_gradient_0260:=0.0715703476783268 0.10434177191727766 -0.6196696495625158 +DWMRI_gradient_0261:=0.18162749269070422 -0.10431943111524422 0.596765368309225 +DWMRI_gradient_0262:=0.42320909132351703 -0.45915884593361134 0.1003355064002869 +DWMRI_gradient_0263:=0.21101016422890867 -0.59402275447428 0.051104524079997334 +DWMRI_gradient_0264:=0.11153588757236818 -0.5950794632210522 -0.18286654384701795 +DWMRI_gradient_0265:=0.22444245175795352 -0.4626831663980482 -0.36817097659011566 +DWMRI_gradient_0266:=0.42425677081710195 -0.11160118913595186 -0.4555780521731169 +DWMRI_gradient_0267:=0.43470281223830987 -0.32763169425654104 -0.322010764685372 +DWMRI_gradient_0268:=0.5345898697442978 -0.32627217141121123 -0.08809163383632272 +DWMRI_gradient_0269:=0.5664443837750432 -0.10651428021327813 0.26037561871827325 +DWMRI_gradient_0270:=0.6230264836619488 -0.10809143682721667 0.012419201384154836 +DWMRI_gradient_0271:=-0.19481992666006587 -0.36821618909131876 -0.5713685475706217 +DWMRI_gradient_0272:=0.08853308990141766 -0.368664550733228 -0.5968654152410775 +DWMRI_gradient_0273:=0.22763846860036965 -0.1259367266223498 -0.6575109515336266 +DWMRI_gradient_0274:=-0.318600351249333 -0.5172666205824635 -0.36184118380959396 +DWMRI_gradient_0275:=-0.1594711761505868 -0.6660638342691484 -0.17586328650846364 +DWMRI_gradient_0276:=-0.6059304334021373 -0.3643255265418356 0.010733288072223053 +DWMRI_gradient_0277:=-0.5427143819565083 -0.3666554920566887 -0.2665048454493118 +DWMRI_gradient_0278:=-0.341835675497033 -0.1261102594941734 -0.6060070596070052 +DWMRI_gradient_0279:=-0.5559409608713415 -0.12474743406229913 -0.4187692784888246 +DWMRI_gradient_0280:=-0.4458748009395038 -0.5133502621457553 0.1940802749579955 +DWMRI_gradient_0281:=-0.22096030502105934 -0.6640539671908363 0.10103887939224726 +DWMRI_gradient_0282:=-0.17946283657340378 -0.3599918619508966 0.5815487361381532 +DWMRI_gradient_0283:=-0.423469645259782 -0.3615882113821413 0.4358066174563522 +DWMRI_gradient_0284:=-0.6827275431602937 -0.1210695784072898 0.13865511847861683 +DWMRI_gradient_0285:=-0.5708345081804402 -0.11895770857053611 0.3999962633922612 +DWMRI_gradient_0286:=0.04340874890411187 -0.5108965538480681 0.48692953768535835 +DWMRI_gradient_0287:=0.023432854834614446 -0.6628682674674555 0.24506436743174817 +DWMRI_gradient_0288:=0.4952092353446183 -0.3612509164931913 0.3525132216640345 +DWMRI_gradient_0289:=0.2810444172364893 -0.3604556550801526 0.5395236224176556 +DWMRI_gradient_0290:=0.0800585921421424 0.11675028981136384 -0.6927914365369348 +DWMRI_gradient_0291:=0.20306749051709172 -0.11660127458169998 0.6672089027783897 +DWMRI_gradient_0292:=0.47318872861927436 -0.5133392066135684 0.11213950662793158 +DWMRI_gradient_0293:=0.23600178291259297 -0.6641086475724707 0.05712132322758379 +DWMRI_gradient_0294:=0.12467657909857668 -0.6653186603948177 -0.20446714562601645 +DWMRI_gradient_0295:=0.2509287625505557 -0.5172666235159562 -0.41166731617530405 +DWMRI_gradient_0296:=0.4743676473260879 -0.12466214302077612 -0.5093472962071252 +DWMRI_gradient_0297:=0.4860063190673166 -0.36628979534362405 -0.3600411456201813 +DWMRI_gradient_0298:=0.5976957857352455 -0.3647653309729926 -0.09851895208648796 +DWMRI_gradient_0299:=0.6332866792668793 -0.11910952887716257 0.29113722688711224 +DWMRI_gradient_0300:=0.6965591399032316 -0.12086987838420858 0.013993570037194886 +DWMRI_gradient_0301:=-0.21335953791464166 -0.4033870110704929 -0.625904673886108 +DWMRI_gradient_0302:=0.09697110792722347 -0.40386274747963663 -0.6538283561742918 +DWMRI_gradient_0303:=0.24934578004029823 -0.13791406628416172 -0.7202821851254365 +DWMRI_gradient_0304:=-0.3490511959461475 -0.5666106554312642 -0.3963781801163221 +DWMRI_gradient_0305:=-0.17464487945159252 -0.7296320097065424 -0.1927078887978932 +DWMRI_gradient_0306:=-0.6637733050368033 -0.3990801685094859 0.011834379365626138 +DWMRI_gradient_0307:=-0.5944588905482845 -0.4017452552053027 -0.29192363946782274 +DWMRI_gradient_0308:=-0.3744736165832915 -0.1380021547356803 -0.663871184555671 +DWMRI_gradient_0309:=-0.609010824704722 -0.13656596270825985 -0.4587543865011548 +DWMRI_gradient_0310:=-0.4884326426558975 -0.5623428625627044 0.2126125406107785 +DWMRI_gradient_0311:=-0.24204517126705571 -0.7274396653969123 0.11066030085599915 +DWMRI_gradient_0312:=-0.1965325285142462 -0.39434206088886864 0.6370787535004815 +DWMRI_gradient_0313:=-0.46394249707279117 -0.39611613403940726 0.4773357326615597 +DWMRI_gradient_0314:=-0.7479191522856983 -0.1326017745765285 0.15176872186128632 +DWMRI_gradient_0315:=-0.6253173749709214 -0.1302677931599773 0.43818776401984993 +DWMRI_gradient_0316:=0.04758720109910647 -0.5596728763647636 0.5333870673958602 +DWMRI_gradient_0317:=0.025717199153192517 -0.7261381571393711 0.26844373027873675 +DWMRI_gradient_0318:=0.5425053076898178 -0.3957139460404167 0.3861327465386855 +DWMRI_gradient_0319:=0.3079509737631084 -0.39485686235887635 0.590977402206266 +DWMRI_gradient_0320:=0.08773670408660034 0.1278213769000712 -0.7589229211688104 +DWMRI_gradient_0321:=0.2223182484104924 -0.127691358478348 0.7309374441808648 +DWMRI_gradient_0322:=0.5183132173537264 -0.562390374972419 0.12275385438682232 +DWMRI_gradient_0323:=0.2584690850181157 -0.7275131004919079 0.06259756077164043 +DWMRI_gradient_0324:=0.13659490409548386 -0.7288155835636982 -0.2239859621535725 +DWMRI_gradient_0325:=0.27487491394753283 -0.566626233712685 -0.4509750845256581 +DWMRI_gradient_0326:=0.5196063491483992 -0.13668297216884315 -0.5579668815940365 +DWMRI_gradient_0327:=0.5323887315897045 -0.40124044665171893 -0.3944215718733512 +DWMRI_gradient_0328:=0.6547001104599284 -0.3996605668143983 -0.10788526595120394 +DWMRI_gradient_0329:=0.6937493632488455 -0.13047029682619823 0.31888768641939674 +DWMRI_gradient_0330:=0.7630397895765301 -0.13242635919608622 0.0152830895137172 +DWMRI_gradient_0331:=-0.23042124995645655 -0.43568253069459695 -0.6760819341167124 +DWMRI_gradient_0332:=0.10473541215530176 -0.43618459207210153 -0.706238979880389 +DWMRI_gradient_0333:=0.26932301959722854 -0.1489968519479155 -0.7779878217861645 +DWMRI_gradient_0334:=-0.37705053939353295 -0.6119890965027557 -0.42813810637191096 +DWMRI_gradient_0335:=-0.18862578340796693 -0.7880762719000488 -0.20822128883663626 +DWMRI_gradient_0336:=-0.7169395367430371 -0.4310880007901389 0.012682186419522545 +DWMRI_gradient_0337:=-0.6421104194178042 -0.4338879852755916 -0.31533383442237645 +DWMRI_gradient_0338:=-0.40448262671897534 -0.14909578527079412 -0.7170524758481365 +DWMRI_gradient_0339:=-0.6578023534616918 -0.14752317706849238 -0.49551284305335297 +DWMRI_gradient_0340:=-0.5276241567568416 -0.6073504899768932 0.22964784596071983 +DWMRI_gradient_0341:=-0.26134359768680815 -0.7857568527398291 0.11952277583562546 +DWMRI_gradient_0342:=-0.21232549944679951 -0.42592424973722237 0.6881180257727524 +DWMRI_gradient_0343:=-0.501067298762128 -0.4278956186463297 0.5155937385940973 +DWMRI_gradient_0344:=-0.8078512867659556 -0.143124106549223 0.1639871647965633 +DWMRI_gradient_0345:=-0.6754585877197897 -0.1407504126107696 0.4732282950632014 +DWMRI_gradient_0346:=0.05143083213843501 -0.6044897155711622 0.5761484660199858 +DWMRI_gradient_0347:=0.027813875173994737 -0.7843258393262019 0.289929935908649 +DWMRI_gradient_0348:=0.5859641049953632 -0.4274329620835501 0.4170696970584226 +DWMRI_gradient_0349:=0.3326212894283419 -0.4264342464199101 0.6383705138258264 +DWMRI_gradient_0350:=0.09481138274742848 0.13813589676690216 -0.8197129237415154 +DWMRI_gradient_0351:=0.2401735713751109 -0.137946544798163 0.7894855337586062 +DWMRI_gradient_0352:=0.559890308815755 -0.607384230319764 0.13269227737892353 +DWMRI_gradient_0353:=0.2792452129826264 -0.7857814511800263 0.06759899134335043 +DWMRI_gradient_0354:=0.14750951590155145 -0.787186783522694 -0.24202874248449013 +DWMRI_gradient_0355:=0.2969374783460515 -0.6120034710017194 -0.48711383377838385 +DWMRI_gradient_0356:=0.5612678273059114 -0.14754000713733068 -0.6026693736603485 +DWMRI_gradient_0357:=0.5749981341098892 -0.4334074220529689 -0.42606942380612584 +DWMRI_gradient_0358:=0.7071941429087747 -0.43159891999296573 -0.11661397051540695 +DWMRI_gradient_0359:=0.7493234554971193 -0.14092065527873557 0.3444644097444717 +DWMRI_gradient_0360:=0.8241743325920375 -0.1430609284293623 0.016439035140095804 +DWMRI_gradient_0361:=-0.24638048482381333 -0.46576669809463894 -0.7227433987840477 +DWMRI_gradient_0362:=0.11201398230750398 -0.46632393107164577 -0.7549800261724933 +DWMRI_gradient_0363:=0.28786621911176796 -0.15923340448620998 -0.8317317738501663 +DWMRI_gradient_0364:=-0.4030550030098881 -0.6542472700001349 -0.4577195157386088 +DWMRI_gradient_0365:=-0.20164297898318015 -0.8425066127912046 -0.22253695709206947 +DWMRI_gradient_0366:=-0.7664550668007076 -0.4608291697559966 0.013530991073782334 +DWMRI_gradient_0367:=-0.6864261990884374 -0.46383990222000465 -0.3371521899016154 +DWMRI_gradient_0368:=-0.43240251882237984 -0.15937603759890454 -0.7665685365296774 +DWMRI_gradient_0369:=-0.7031704180171602 -0.1578016393675544 -0.5297640858725717 +DWMRI_gradient_0370:=-0.5640614893743962 -0.6492843713975831 0.24548813027415545 +DWMRI_gradient_0371:=-0.27942978280096686 -0.8399976519603708 0.12776119139545458 +DWMRI_gradient_0372:=-0.22701046901229546 -0.4553816205373317 0.7355907880591819 +DWMRI_gradient_0373:=-0.5356827674458104 -0.45742739116207887 0.551184302496152 +DWMRI_gradient_0374:=-0.863622624310415 -0.15300950252966355 0.17533977231858527 +DWMRI_gradient_0375:=-0.7221169467669905 -0.1504661782981715 0.5058725385135858 +DWMRI_gradient_0376:=0.05492592392698272 -0.6462446993391064 0.6159146956347056 +DWMRI_gradient_0377:=0.029657744099444713 -0.8384854617181761 0.30993955788847893 +DWMRI_gradient_0378:=0.6264456353088292 -0.4569266550732165 0.44585185737869987 +DWMRI_gradient_0379:=0.355546798852838 -0.4559556840267565 0.6824154664653411 +DWMRI_gradient_0380:=0.10135998676218283 0.14759035285530314 -0.8763236968549545 +DWMRI_gradient_0381:=0.2567920660073086 -0.14745305287972776 0.8439878037472244 +DWMRI_gradient_0382:=0.5985738946413125 -0.6493078448074816 0.14180478189155396 +DWMRI_gradient_0383:=0.2984960407903775 -0.8400469340408746 0.07225816966207313 +DWMRI_gradient_0384:=0.157684476665634 -0.841549711499336 -0.2587076916830777 +DWMRI_gradient_0385:=0.3174128633357809 -0.6542607285644119 -0.5207609375608574 +DWMRI_gradient_0386:=0.5999440652020176 -0.15780119745261115 -0.6443336725279195 +DWMRI_gradient_0387:=0.6147218909630658 -0.4633722327390399 -0.4554153615537651 +DWMRI_gradient_0388:=0.7559864820712768 -0.46146761974923606 -0.12462766823778343 +DWMRI_gradient_0389:=0.8010944933345742 -0.15065037282948354 0.36817395176366924 +DWMRI_gradient_0390:=0.881074488697678 -0.1529683352750741 0.017561737135448122 +DWMRI_gradient_0391:=-0.2612742408846108 -0.49400341655060814 -0.7666136184109954 +DWMRI_gradient_0392:=0.118799087879937 -0.4945806363752914 -0.8007976119396271 +DWMRI_gradient_0393:=0.3053877770411869 -0.1688521002374165 -0.8821719324065792 +DWMRI_gradient_0394:=-0.42749724955423973 -0.693930821205759 -0.4854957893236185 +DWMRI_gradient_0395:=-0.21396335172952047 -0.8935827197189607 -0.23607128496078747 +DWMRI_gradient_0396:=-0.8129298222290111 -0.4888119321303101 0.014423716041429191 +DWMRI_gradient_0397:=-0.7280826446297302 -0.49197748213102516 -0.3575665869620606 +DWMRI_gradient_0398:=-0.4586242905519082 -0.16904987340420907 -0.8130719186298847 +DWMRI_gradient_0399:=-0.7458629688851396 -0.16729124244360052 -0.5618737547654493 +DWMRI_gradient_0400:=-0.5982652930552783 -0.688669911675262 0.2604082109407669 +DWMRI_gradient_0401:=-0.29637779328377106 -0.8909600114225336 0.13546404002872361 +DWMRI_gradient_0402:=-0.24074077806137884 -0.48301208490674774 0.7802199997815619 +DWMRI_gradient_0403:=-0.5681802080793281 -0.4851802780242171 0.5846121735047748 +DWMRI_gradient_0404:=-0.9160157797103989 -0.16233368919904773 0.1859110247383916 +DWMRI_gradient_0405:=-0.765913054863417 -0.15955824105286906 0.5365802871412679 +DWMRI_gradient_0406:=0.058284205356476215 -0.6854608368483011 0.6532583233299774 +DWMRI_gradient_0407:=0.03149683055643594 -0.8893362879474291 0.3287688565717387 +DWMRI_gradient_0408:=0.6644366395836395 -0.48465603045797057 0.4728980107446277 +DWMRI_gradient_0409:=0.3771464116291017 -0.48355372699875576 0.7238345264959442 +DWMRI_gradient_0410:=0.10742596779142062 0.15661570479323267 -0.9294790083175076 +DWMRI_gradient_0411:=0.27237350497383334 -0.15647611190421923 0.8951692268159746 +DWMRI_gradient_0412:=0.6348866031101201 -0.6887097934781525 0.15032586605529755 +DWMRI_gradient_0413:=0.3165475437386691 -0.891024263391925 0.07663849585289974 +DWMRI_gradient_0414:=0.16726318062989168 -0.8925892564166048 -0.2744221058550618 +DWMRI_gradient_0415:=0.33666291133761017 -0.693943487055211 -0.5523590909454107 +DWMRI_gradient_0416:=0.6363676616225468 -0.16729329530370782 -0.6834099772714621 +DWMRI_gradient_0417:=0.6520111942644069 -0.4914667160048629 -0.48305477335391184 +DWMRI_gradient_0418:=0.8018426295494818 -0.4894352910756585 -0.1322933775121033 +DWMRI_gradient_0419:=0.8496735246324469 -0.15980410270611914 0.3905350109064095 +DWMRI_gradient_0420:=0.9345368318406979 -0.1621564737852442 0.018607320854685704 +DWMRI_gradient_0421:=-0.27535635467838543 -0.5207860514247633 -0.8080598780905005 +DWMRI_gradient_0422:=0.1251986308383404 -0.5213851264351731 -0.8440869936021898 +DWMRI_gradient_0423:=0.32187259592379036 -0.177988218465993 -0.9299022669509855 +DWMRI_gradient_0424:=-0.45066365463316155 -0.7314541317749341 -0.5117393120545456 +DWMRI_gradient_0425:=-0.22549987882876316 -0.9419191293164335 -0.24887378020993775 +DWMRI_gradient_0426:=-0.8569154629569048 -0.5152350598113161 0.015123573869378392 +DWMRI_gradient_0427:=-0.7674614149833409 -0.5185952158729682 -0.3769111017009679 +DWMRI_gradient_0428:=-0.48343759755790006 -0.17822116606246616 -0.8570445176505013 +DWMRI_gradient_0429:=-0.7861782669887688 -0.17634236277511753 -0.5923065959496184 +DWMRI_gradient_0430:=-0.6305958099950751 -0.7259523946086852 0.2744850545632146 +DWMRI_gradient_0431:=-0.31244138011860934 -0.9391430059009637 0.14279635309377092 +DWMRI_gradient_0432:=-0.2537636703202753 -0.5091444742237354 0.8224207584886328 +DWMRI_gradient_0433:=-0.5989294113271988 -0.5114147119283124 0.6162293020220118 +DWMRI_gradient_0434:=-0.9655647235493465 -0.17110262736157963 0.19598126325503154 +DWMRI_gradient_0435:=-0.807356352746493 -0.16825538773056212 0.5655668344055529 +DWMRI_gradient_0436:=0.06138623917451572 -0.7225516484497474 0.6885861202221272 +DWMRI_gradient_0437:=0.03322792813563007 -0.9374595923624337 0.346504570647304 +DWMRI_gradient_0438:=0.7003968239876176 -0.5108609113336778 0.4984630560225926 +DWMRI_gradient_0439:=0.39759199296221553 -0.5097837039921421 0.7629162354258723 +DWMRI_gradient_0440:=0.11325844819092336 0.16512544011027339 -0.9797479844030157 +DWMRI_gradient_0441:=0.28711516519172425 -0.16491863622127612 0.9435924572313211 +DWMRI_gradient_0442:=0.6692036610275418 -0.7259866237857513 0.15846097990199576 +DWMRI_gradient_0443:=0.3337268480455391 -0.9392036056415041 0.08076495554129723 +DWMRI_gradient_0444:=0.1763260545684712 -0.940871255858654 -0.28925836613532024 +DWMRI_gradient_0445:=0.35488598859803666 -0.7314990022892535 -0.5822071321674274 +DWMRI_gradient_0446:=0.6707680233305371 -0.17643314620857228 -0.7203760155614486 +DWMRI_gradient_0447:=0.6872437043525724 -0.5180612835109211 -0.5092235239605204 +DWMRI_gradient_0448:=0.8452117317543049 -0.5159263534207851 -0.13941709490166473 +DWMRI_gradient_0449:=0.8956628142670163 -0.16844656131756755 0.4115991728849497 +DWMRI_gradient_0450:=0.9850878565136942 -0.17093448079953444 0.01957851432178666 diff --git a/Libs/MRML/Core/Testing/TestData/multishell-DWI-451dir.raw.gz b/Libs/MRML/Core/Testing/TestData/multishell-DWI-451dir.raw.gz new file mode 100644 index 0000000000000000000000000000000000000000..39c23a1e473edf1ddf47497fec13ed8e538bfbdf GIT binary patch literal 49880 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 ^9QDhy;LLA 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 literal 0 HcmV?d00001 -- GitLab From 9b96229924b1fbaca61c10a99d1e8265c023d28e Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:06 -0400 Subject: [PATCH 02/12] ENH: vtkNRRDWriter: don't dup 'space' field as k-v from node attribute --- Libs/vtkTeem/vtkTeemNRRDWriter.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Libs/vtkTeem/vtkTeemNRRDWriter.cxx b/Libs/vtkTeem/vtkTeemNRRDWriter.cxx index 2b41c7cfd..991f7278c 100644 --- a/Libs/vtkTeem/vtkTeemNRRDWriter.cxx +++ b/Libs/vtkTeem/vtkTeemNRRDWriter.cxx @@ -279,6 +279,9 @@ void* vtkTeemNRRDWriter::MakeNRRD() AttributeMapType::iterator ait; for (ait = this->Attributes->begin(); ait != this->Attributes->end(); ++ait) { + // Don't set `space` as k-v. it is handled above, and needs to be a nrrd *field*. + if (ait->first == "space") { continue; } + nrrdKeyValueAdd(nrrd, (*ait).first.c_str(), (*ait).second.c_str()); } -- GitLab From 6d1a78814febc0a8289585c50f00681bdb5d0c74 Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:12 -0400 Subject: [PATCH 03/12] ENH: dmri multishell i/o test for MRMLNRRDStorageNode load path --- .../SlicerApp/Testing/Python/CMakeLists.txt | 10 ++ .../Testing/Python/DWMRIMultishellIOTests.py | 121 ++++++++++++++++++ Libs/vtkTeem/vtkTeemNRRDWriter.cxx | 8 +- Libs/vtkTeem/vtkTeemNRRDWriter.h | 2 +- 4 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py diff --git a/Applications/SlicerApp/Testing/Python/CMakeLists.txt b/Applications/SlicerApp/Testing/Python/CMakeLists.txt index cb76e0ba1..1fbba93b5 100644 --- a/Applications/SlicerApp/Testing/Python/CMakeLists.txt +++ b/Applications/SlicerApp/Testing/Python/CMakeLists.txt @@ -289,6 +289,16 @@ slicer_add_python_unittest( TESTNAME_PREFIX nomainwindow_ ) +# Test multi-shell DWI I/O +slicer_add_python_test( + SCRIPT DWMRIMultishellIOTests.py + SLICER_ARGS --no-main-window --disable-modules + SCRIPT_ARGS + ${Slicer_SOURCE_DIR}/ + ${Slicer_BINARY_DIR}/Testing/Temporary/ + TESTNAME_PREFIX nomainwindow_ + ) + # DCMTKPrivateDictionary test slicer_add_python_test( SCRIPT DCMTKPrivateDictTest.py diff --git a/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py b/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py new file mode 100644 index 000000000..6c2fbc018 --- /dev/null +++ b/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py @@ -0,0 +1,121 @@ +import sys, os, re, nose +from nose.tools import assert_equal +from collections import namedtuple + +import numpy as np +from numpy.testing import assert_allclose +import slicer + +Context = namedtuple('Ctx', ['data_dir', 'temp_dir']) + +#=============================================================================== + +mrmlcore_testdata_path = "Libs/MRML/Core/Testing/TestData/" + + +multishell_dwi_451 = os.path.join(mrmlcore_testdata_path, "multishell-DWI-451dir.nhdr") + +#================================================================================ +NRRD = namedtuple('NRRD', ['header', 'bvalue', 'gradients']) + +def parse_nhdr(path): + # TODO: NRRD b-matrix form? + dwmri_bval_key = "DWMRI_b-value" + dwmri_grad_keybase = "DWMRI_gradient_" + dwmri_grad_key_n = "DWMRI_gradient_{:04d}" + + kvdict = {} + grad_count = 0 + + with open(path, "rU") as f: + magic = f.readline().strip() + assert(magic == "NRRD0005") + + while True: + line = f.readline() + # NRRD data section is separated by a newline + if (line == "\n") or (line == "") or (line is None): + break + + # careful about precedence -- ":=" must match first + key, val = [x.strip() for x in re.split(":=|=|:", line)] + assert(not kvdict.has_key(key)) + kvdict[key] = val + + if key.startswith(dwmri_grad_keybase): + _gn = int(key[ len(dwmri_grad_keybase):None ]) + # monotonic keys + assert( _gn == grad_count ) # offset + grad_count += 1 + + bvalue = float(kvdict[dwmri_bval_key]) + grads = np.zeros((grad_count, 3)) + + # parse gradients + for i in range(0, grad_count): + grad_str = kvdict[dwmri_grad_key_n.format(i)] + grads[i] = np.fromstring(grad_str, count=3, dtype=np.float64, sep=" ") + + return NRRD(header=kvdict, bvalue=bvalue, gradients=grads) + + +#================================================================================ +def test_vtkMRMLNRRDStorageNode(ctx): + testnrrd_path = os.path.join(ctx.data_dir, multishell_dwi_451) + + # load NRRD into Slicer + storagenode = slicer.vtkMRMLNRRDStorageNode() + storagenode.SetFileName(testnrrd_path) + dw_node = slicer.vtkMRMLDiffusionWeightedVolumeNode() + storagenode.ReadData(dw_node) + + slicer_grads = dw_node.GetDiffusionGradients() + slicer_numgrads = slicer_grads.GetNumberOfTuples() + + # load NRRD with direct parser + ext_nrrd = parse_nhdr(testnrrd_path) + + # basic assertions + assert( len(ext_nrrd.gradients) == slicer_numgrads ) + + + _ext_bval = ext_nrrd.bvalue + # Note: vtkDataArray.GetMaxNorm gives max for scalar array. + _node_bval = dw_node.GetBValues().GetMaxNorm() + _attr_bval = float(dw_node.GetAttribute("DWMRI_b-value")) + nose.tools.assert_equal(_ext_bval, _node_bval) + nose.tools.assert_equal(_ext_bval, _attr_bval) + + # Gradients in the node attribute dictionary must exactly + # match those on-disk. + for i in range(0, slicer_numgrads): + g_from_nhdr = ext_nrrd.gradients[i] + g_from_attr = np.fromstring( + dw_node.GetAttribute("DWMRI_gradient_{:04d}".format(i)), + sep=" ", dtype=np.float64) + + assert_allclose(np.linalg.norm(g_from_nhdr - g_from_attr), 0.0, atol=1e-12) + + # Gradients in the node DiffusionGradients API must be + # match *normalized* gradient. + for i in range(0, slicer_numgrads): + _g_tmp = ext_nrrd.gradients[i] + _g_tmp_norm = np.linalg.norm(_g_tmp) + # normalize + g_from_nhdr = _g_tmp if _g_tmp_norm == 0.0 \ + else (_g_tmp * 1/_g_tmp_norm) + + g_from_node = np.array(slicer_grads.GetTuple3(i)) + print _g_tmp, g_from_nhdr, g_from_node + + assert_allclose(np.linalg.norm(g_from_nhdr - g_from_node), 0.0, atol=1e-12) + + +if __name__ == '__main__': + # TODO make sure data paths exist + ctx = Context(data_dir = sys.argv[1], + temp_dir = sys.argv[2]) + + success = test_vtkMRMLNRRDStorageNode(ctx) + + sys.exit(success) diff --git a/Libs/vtkTeem/vtkTeemNRRDWriter.cxx b/Libs/vtkTeem/vtkTeemNRRDWriter.cxx index 991f7278c..721659e08 100644 --- a/Libs/vtkTeem/vtkTeemNRRDWriter.cxx +++ b/Libs/vtkTeem/vtkTeemNRRDWriter.cxx @@ -23,7 +23,7 @@ vtkTeemNRRDWriter::vtkTeemNRRDWriter() this->IJKToRASMatrix = vtkMatrix4x4::New(); this->MeasurementFrameMatrix = vtkMatrix4x4::New(); this->UseCompression = 1; - this->DiffusionWeigthedData = 0; + this->DiffusionWeightedData = 0; this->FileType = VTK_BINARY; this->WriteErrorOff(); this->Attributes = new AttributeMapType; @@ -75,7 +75,7 @@ void vtkTeemNRRDWriter::vtkImageDataInfoToNrrdInfo(vtkImageData *in, int &kind, { vtkDataArray *array; - this->DiffusionWeigthedData = 0; + this->DiffusionWeightedData = 0; if ((array = static_cast (in->GetPointData()->GetScalars()))) { numComp = array->GetNumberOfComponents(); @@ -102,7 +102,7 @@ void vtkTeemNRRDWriter::vtkImageDataInfoToNrrdInfo(vtkImageData *in, int &kind, if (numGrad == numBValues && numGrad == numComp && numGrad>6) { kind = nrrdKindList; - this->DiffusionWeigthedData = 1; + this->DiffusionWeightedData = 1; } else { @@ -300,7 +300,7 @@ void* vtkTeemNRRDWriter::MakeNRRD() } // 2. Take care about diffusion data - if (this->DiffusionWeigthedData) + if (this->DiffusionWeightedData) { unsigned int numGrad = this->DiffusionGradients->GetNumberOfTuples(); unsigned int numBValues = this->BValues->GetNumberOfTuples(); diff --git a/Libs/vtkTeem/vtkTeemNRRDWriter.h b/Libs/vtkTeem/vtkTeemNRRDWriter.h index b414af1c3..5e69546f9 100644 --- a/Libs/vtkTeem/vtkTeemNRRDWriter.h +++ b/Libs/vtkTeem/vtkTeemNRRDWriter.h @@ -117,7 +117,7 @@ private: void operator=(const vtkTeemNRRDWriter&); /// Not implemented. void vtkImageDataInfoToNrrdInfo(vtkImageData *in, int &nrrdKind, size_t &numComp, int &vtkType, void **buffer); int VTKToNrrdPixelType( const int vtkPixelType ); - int DiffusionWeigthedData; + int DiffusionWeightedData; }; #endif -- GitLab From 200e7989d6ff697d8f05c5a7e51f33b493500416 Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:19 -0400 Subject: [PATCH 04/12] ENH: minor code cleanup, vtkMRMLDiffusionWeightedVolumeNode - remove unused and private vtkImageExtractComponents - clean up constructor --- .../vtkMRMLDiffusionWeightedVolumeNode.cxx | 26 ++++++------------- .../Core/vtkMRMLDiffusionWeightedVolumeNode.h | 2 -- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx b/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx index 2f6212585..6f31695b1 100644 --- a/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx +++ b/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx @@ -20,7 +20,6 @@ Version: $Revision: 1.14 $ #include #include -#include #include #include #include @@ -29,12 +28,11 @@ Version: $Revision: 1.14 $ vtkMRMLNodeNewMacro(vtkMRMLDiffusionWeightedVolumeNode); //---------------------------------------------------------------------------- -vtkMRMLDiffusionWeightedVolumeNode::vtkMRMLDiffusionWeightedVolumeNode() +vtkMRMLDiffusionWeightedVolumeNode::vtkMRMLDiffusionWeightedVolumeNode() : + DiffusionGradients(vtkDoubleArray::New()), + BValues(vtkDoubleArray::New()) { - this->DiffusionGradients = vtkDoubleArray::New(); this->DiffusionGradients->SetNumberOfComponents(3); - this->BValues = vtkDoubleArray::New(); - this->SetNumberOfGradientsInternal(7); //6 gradients + 1 baseline for(int i=0; i<3; i++) @@ -44,8 +42,6 @@ vtkMRMLDiffusionWeightedVolumeNode::vtkMRMLDiffusionWeightedVolumeNode() this->MeasurementFrameMatrix[i][j] = (i == j) ? 1.0 : 0.0; } } - - this->ExtractComponents = NULL; } //---------------------------------------------------------------------------- @@ -53,12 +49,6 @@ vtkMRMLDiffusionWeightedVolumeNode::~vtkMRMLDiffusionWeightedVolumeNode() { this->DiffusionGradients->Delete(); this->BValues->Delete(); - - if (this->ExtractComponents) - { - this->ExtractComponents->Delete(); - this->ExtractComponents = NULL; - } } //---------------------------------------------------------------------------- @@ -276,17 +266,17 @@ int vtkMRMLDiffusionWeightedVolumeNode::GetNumberOfGradients() } //---------------------------------------------------------------------------- -void vtkMRMLDiffusionWeightedVolumeNode::SetDiffusionGradient(int num,const double grad[3]) +void vtkMRMLDiffusionWeightedVolumeNode::SetDiffusionGradient(int num, const double grad[3]) { - if (num < 0 || num >= this->DiffusionGradients->GetNumberOfTuples()) + if ((num < 0) || + (num >= this->DiffusionGradients->GetNumberOfTuples())) { vtkErrorMacro(<< "Gradient number is out of range. " "Allocate first the number of gradients with SetNumberOfGradients"); return; } - this->DiffusionGradients->SetComponent(num,0,grad[0]); - this->DiffusionGradients->SetComponent(num,1,grad[1]); - this->DiffusionGradients->SetComponent(num,2,grad[2]); + this->DiffusionGradients->SetTuple3(num, grad[0], grad[1], grad[2]); + this->Modified(); } diff --git a/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.h b/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.h index e287400fb..1a9e113cf 100644 --- a/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.h +++ b/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.h @@ -113,8 +113,6 @@ protected: vtkDoubleArray *DiffusionGradients; vtkDoubleArray *BValues; - vtkImageExtractComponents *ExtractComponents; - }; #endif -- GitLab From 28ec33a9a38c3e8579f43f9f8ad1bfdf1e57f28e Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:29 -0400 Subject: [PATCH 05/12] ENH: DiffusionWeightedVolumeNode must only accept unit-length gradients --- .../vtkMRMLDiffusionWeightedVolumeNode.cxx | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx b/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx index 6f31695b1..a7e5d987d 100644 --- a/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx +++ b/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx @@ -24,6 +24,8 @@ Version: $Revision: 1.14 $ #include #include +#include + //------------------------------------------------------------------------------ vtkMRMLNodeNewMacro(vtkMRMLDiffusionWeightedVolumeNode); @@ -265,6 +267,13 @@ int vtkMRMLDiffusionWeightedVolumeNode::GetNumberOfGradients() return this->DiffusionGradients->GetNumberOfTuples(); } +//------------------------------------------------------------------------------ + +inline bool valid_grad_length(vnl_double_3 grad) { + // returns true if grad length is: within GRAD_EPS of 0.0 or 1.0 + return (grad.two_norm() < 1e-6) || (fabs(1.0 - grad.two_norm()) < 1e-6); +} + //---------------------------------------------------------------------------- void vtkMRMLDiffusionWeightedVolumeNode::SetDiffusionGradient(int num, const double grad[3]) { @@ -275,14 +284,36 @@ void vtkMRMLDiffusionWeightedVolumeNode::SetDiffusionGradient(int num, const dou "Allocate first the number of gradients with SetNumberOfGradients"); return; } - this->DiffusionGradients->SetTuple3(num, grad[0], grad[1], grad[2]); + vnl_double_3 tmp_grad(grad[0], grad[1], grad[2]); + if (!valid_grad_length(tmp_grad)) + { + vtkErrorMacro(<< "vtkMRMLDiffusionWeightedVolumeNode only accepts gradient vectors with length 0.0 or 1.0!" + << " Got vector with length: " << tmp_grad.two_norm()); + return; + } + + this->DiffusionGradients->SetTuple3(num, grad[0], grad[1], grad[2]); this->Modified(); } //---------------------------------------------------------------------------- void vtkMRMLDiffusionWeightedVolumeNode::SetDiffusionGradients(vtkDoubleArray *grad) { + // gradients must all be length 0 (baseline) or 1. + vnl_double_3 tmp_grad; + for (int i = 0; i < grad->GetNumberOfTuples(); i++) + { + tmp_grad.copy_in(grad->GetTuple3(i)); + double grad_norm = tmp_grad.two_norm(); + if (!valid_grad_length(tmp_grad)) + { + vtkErrorMacro(<< "vtkMRMLDiffusionWeightedVolumeNode only accepts gradient vectors with length 0.0 or 1.0!" + << " Got vector with length: " << tmp_grad.two_norm()); + return; + } + } + this->DiffusionGradients->DeepCopy(grad); this->Modified(); } -- GitLab From 760559e4bfd3a491fc8eb3f52e8d0efd13a7ceac Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:37 -0400 Subject: [PATCH 06/12] ENH: add vtkNRRDReader::GetHeaderKeysMap --- Libs/vtkTeem/vtkTeemNRRDReader.cxx | 6 ++++++ Libs/vtkTeem/vtkTeemNRRDReader.h | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Libs/vtkTeem/vtkTeemNRRDReader.cxx b/Libs/vtkTeem/vtkTeemNRRDReader.cxx index 8fcaf526c..3424e05d0 100644 --- a/Libs/vtkTeem/vtkTeemNRRDReader.cxx +++ b/Libs/vtkTeem/vtkTeemNRRDReader.cxx @@ -104,6 +104,12 @@ const char* vtkTeemNRRDReader::GetHeaderKeys() return this->HeaderKeys.c_str(); } +//---------------------------------------------------------------------------- +const std::map vtkTeemNRRDReader::GetHeaderKeysMap() +{ + return this->HeaderKeyValue; +} + //---------------------------------------------------------------------------- std::vector vtkTeemNRRDReader::GetHeaderKeysVector() { diff --git a/Libs/vtkTeem/vtkTeemNRRDReader.h b/Libs/vtkTeem/vtkTeemNRRDReader.h index 3908c9243..b87092007 100644 --- a/Libs/vtkTeem/vtkTeemNRRDReader.h +++ b/Libs/vtkTeem/vtkTeemNRRDReader.h @@ -75,7 +75,12 @@ public: /// /// Get a list of keys in the header. Preferred method to use as it /// supports spaces in key names. - std::vector GetHeaderKeysVector(); + const std::vector GetHeaderKeysVector(); + + /// + /// Get a map of keys in the header. Preferred method to use as it + /// supports spaces in key names. + const std::map GetHeaderKeysMap(); /// /// Get a value given a key in the header -- GitLab From 63ec50b67d45e59116a3c627a370da959abcd4b2 Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:41 -0400 Subject: [PATCH 07/12] ENH: refactor vtkMRMLNRRDStorageNode::ParseDiffusionGradients - Improve correctness and readability - Raise error for unsupported DWMRI_NEX_ key - Raise error for non-consecutive DWMRI_gradient_ keys - Detect DWMRI_gradient_ padding width, and - Raise error for padding width inconsistency --- Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx | 225 ++++++++++++++-------- Libs/vtkTeem/vtkTeemNRRDReader.cxx | 2 +- 2 files changed, 145 insertions(+), 82 deletions(-) diff --git a/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx b/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx index ea66a57a8..86df0d6fd 100644 --- a/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx +++ b/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx @@ -32,6 +32,9 @@ Version: $Revision: 1.6 $ #include #include +// vnl includes +#include + //---------------------------------------------------------------------------- vtkMRMLNodeNewMacro(vtkMRMLNRRDStorageNode); @@ -401,104 +404,164 @@ int vtkMRMLNRRDStorageNode::WriteDataInternal(vtkMRMLNode *refNode) } //---------------------------------------------------------------------------- -int vtkMRMLNRRDStorageNode::ParseDiffusionInformation(vtkTeemNRRDReader *reader,vtkDoubleArray *grad,vtkDoubleArray *bvalues) +// internal + +const std::string dwmri_grad_tag("DWMRI_gradient_"); +const std::string dwmri_bvalue_tag("DWMRI_b-value"); + +bool parse_gradient_key(std::string key, size_t &grad_number, size_t &gradkey_pad_width, std::string err) +{ + std::stringstream err_stream; + + // note: Slicer no longer supports NRRDs with DWMRI_NEX_ keys. This was removed + // from Dicom2Nrrd in the following commit: + // Slicer3/4 SVN: r26101, git-svn: 63a18f7d6900a + // and never un-commented in Dicom2Nrrd (or DWIConvert). + // If such a key is found, we print an error and fail. + if (key.find("DWMRI_NEX_") != std::string::npos) + { + err_stream << "DWMRI_NEX_ NRRD tag is no longer supported (since SVN r6101)." + << " Please adjust header manually to encode repeat excitations" + << " as unique DWMRI_gradient_###N keys, and re-load."; + err = err_stream.str(); + return false; + } + + if (key.find(dwmri_grad_tag) == std::string::npos) + { + return false; + } + // below here key is: DWMRI_gradient_#### + + // we enforce the constraint that the padding of the grad key must be consistent + if (gradkey_pad_width == 0) { + gradkey_pad_width = key.size() - dwmri_grad_tag.size(); + } + else if (gradkey_pad_width != key.size() - dwmri_grad_tag.size()) + { + err_stream << "DWMRI NRRD gradient key-numbers must have consistent padding (####N)" + << " Found tag: '" << key << "' but previous key had padding: " << gradkey_pad_width; + err = err_stream.str(); + return false; + } + + // slices key string from `dwmri_grad_tag.size()` to `key.size()`. + // e.g.: "DWMRI_gradient_000001" -> "000001" + std::string cur_grad_num_str = key.substr(dwmri_grad_tag.size(), key.size()); + size_t cur_grad_num = atol(cur_grad_num_str.c_str()); + + // enforce monotonic order + if (cur_grad_num != grad_number) + { + err_stream << "DWMRI NRRD gradient key-numbers must be consecutive." + << " Found tag: '" << key << "' but previous key was: " << grad_number; + err = err_stream.str(); + return false; + } + + grad_number += 1; + return true; +} + +//---------------------------------------------------------------------------- +int vtkMRMLNRRDStorageNode::ParseDiffusionInformation( + vtkTeemNRRDReader* reader, + vtkDoubleArray* gradients_array, + vtkDoubleArray* bvalues_array) { - std::string keys(reader->GetHeaderKeys()); - std::string key,value,num; - std::string tag,tagnex; - const char *tmp; - vtkNew factor; - grad->SetNumberOfComponents(3); - double g[3]; - int rep; - - // search for modality tag - key = "modality"; - tmp = reader->GetHeaderValue(key.c_str()); - if (tmp == NULL) + // Validate modality tag + std::string modality(reader->GetHeaderValue("modality")); + if (modality != "DWMRI") { + vtkErrorMacro(<< "NRRD header missing 'modality: DWMRI' tag!") return 0; } - if (strcmp(tmp,"DWMRI") != 0) + + std::map nrrd_keys = reader->GetHeaderKeysMap(); + + /* + Step 1: get DWMRI_b-value + */ + std::string ref_bvalue_str(reader->GetHeaderValue(dwmri_bvalue_tag.c_str())); + if (ref_bvalue_str.empty()) { + vtkErrorMacro(<< "Missing 'DWMRI_b-value' tag!") return 0; } - // search for tag DWMRI_gradient_ - tag = "DWMRI_gradient_"; - tagnex = "DWMRI_NEX_"; - unsigned int pos = 0; - int gbeginpos =0; - int gendpos = 0; - pos = (unsigned int)keys.find(tag,pos); - while ( pos < keys.size() ) - { - num = keys.substr(pos+tag.size(),4); - // Insert gradient - key = tag+num; - tmp = reader->GetHeaderValue(key.c_str()); - if (tmp == NULL) - { - continue; - } - else - { - value = tmp; - } - gbeginpos = -1; - gendpos = 0; - for (int i=0 ;i<3; i++) + double ref_bvalue = atof(ref_bvalue_str.c_str()); + + + /* + Step 2: loop over all keys + - for all DWMRI_gradient_ keys, validate + - consecutive + - consistent padding + - save each gradient to tmp_grads + - record maximum gradient length + */ + vtkNew tmp_grads; + tmp_grads->SetNumberOfComponents(3); + size_t grad_idx = 0; + size_t gradkey_pad_width = 0; + double max_grad_norm = 0; + std::string err; + + std::map::iterator nrrd_keys_iter = nrrd_keys.begin(); + for (; nrrd_keys_iter != nrrd_keys.end(); nrrd_keys_iter++) + { + std::string key = nrrd_keys_iter->first; + + std::cout << "key a: " << key << std::endl; + + if (!parse_gradient_key(key, grad_idx, gradkey_pad_width, err)) { - do + if (err.empty()) { - gbeginpos++; - gendpos=(int)value.find(" ",gbeginpos); + continue; + } + else + { + vtkErrorMacro(<< err); + return 0; } - while(gendpos==gbeginpos); - g[i] = atof(value.substr(gbeginpos,gendpos).c_str()); - gbeginpos = gendpos; - } - grad->InsertNextTuple3(g[0],g[1],g[2]); - factor->InsertNextValue(sqrt(g[0]*g[0]+g[1]*g[1]+g[2]*g[2])); - // find repetitions of this gradient - key = tagnex+num; - tmp = reader->GetHeaderValue(key.c_str()); - if (tmp == NULL) - { - value = ""; - } - else - { - value = tmp; - } - if (value.size()>0) { - rep = atoi(value.c_str()); - for (int i=0;iInsertNextTuple3(g[0],g[1],g[2]); - factor->InsertNextValue(sqrt( g[0]*g[0]+g[1]*g[1]+g[2]*g[2] )); } - } - pos = (unsigned int)keys.find(tag,pos+1); - } + std::cout << "key d: " << key << std::endl; + // parse the gradient vector into double[3] + vnl_double_3 cur_grad(0,0,0); + std::stringstream grad_value_stream(nrrd_keys_iter->second); + grad_value_stream >> cur_grad[0] >> cur_grad[1] >> cur_grad[2]; - grad->Modified(); - factor->Modified(); - double range[2]; - // search for tag DWMRI_b-value - key = "DWMRI_b-value"; - tmp = reader->GetHeaderValue(key.c_str()); - if (tmp == NULL) - { - return 0; + max_grad_norm = std::max(cur_grad.two_norm(), max_grad_norm); + tmp_grads->InsertNextTuple(cur_grad.data_block()); } - double bval = atof(tmp); - factor->GetRange(range); - bvalues->SetNumberOfTuples(grad->GetNumberOfTuples()); - for (int i=0; iGetNumberOfTuples();i++) + + assert(grad_idx == tmp_grads->GetNumberOfTuples()); + + /* + Step 3: loop over gradients + - calculate each b-value based on NA-MIC DWI gradient length-encoding + - then normalize each gradient to unit-length + */ + bvalues_array->SetNumberOfTuples(tmp_grads->GetNumberOfTuples()); + // calculate the b-values + for (int i=0; i < tmp_grads->GetNumberOfTuples(); i++) { + vnl_double_3 cur_grad(0,0,0); + cur_grad.copy_in(tmp_grads->GetTuple3(i)); + // note: this is norm^2, per the NA-MIC NRRD DWI convention // http://wiki.na-mic.org/Wiki/index.php/NAMIC_Wiki:DTI:Nrrd_format - bvalues->SetValue(i, bval * (pow(factor->GetValue(i)/range[1], 2))); + double cur_bval = ref_bvalue * pow(cur_grad.two_norm() / max_grad_norm, 2); + bvalues_array->SetValue(i, cur_bval); + + // normalize gradient vector to unit-length + // must be done *after* bvalue extraction + cur_grad.normalize(); + tmp_grads->InsertTuple(i, cur_grad.data_block()); } + + // Step 4: copy tmp_grads to output + gradients_array->DeepCopy(tmp_grads); return 1; } diff --git a/Libs/vtkTeem/vtkTeemNRRDReader.cxx b/Libs/vtkTeem/vtkTeemNRRDReader.cxx index 3424e05d0..6ed57223a 100644 --- a/Libs/vtkTeem/vtkTeemNRRDReader.cxx +++ b/Libs/vtkTeem/vtkTeemNRRDReader.cxx @@ -111,7 +111,7 @@ const std::map vtkTeemNRRDReader::GetHeaderKeysMap() } //---------------------------------------------------------------------------- -std::vector vtkTeemNRRDReader::GetHeaderKeysVector() +const std::vector vtkTeemNRRDReader::GetHeaderKeysVector() { std::vector keys; for (std::map::iterator i = HeaderKeyValue.begin(); i != HeaderKeyValue.end(); i++) -- GitLab From d9556eef5be58b9c1a46c4152c706b013a7c1626 Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:47 -0400 Subject: [PATCH 08/12] STYLE: remove debugging output --- Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx | 3 --- 1 file changed, 3 deletions(-) diff --git a/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx b/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx index 86df0d6fd..5ca75edd0 100644 --- a/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx +++ b/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx @@ -511,8 +511,6 @@ int vtkMRMLNRRDStorageNode::ParseDiffusionInformation( { std::string key = nrrd_keys_iter->first; - std::cout << "key a: " << key << std::endl; - if (!parse_gradient_key(key, grad_idx, gradkey_pad_width, err)) { if (err.empty()) @@ -525,7 +523,6 @@ int vtkMRMLNRRDStorageNode::ParseDiffusionInformation( return 0; } } - std::cout << "key d: " << key << std::endl; // parse the gradient vector into double[3] vnl_double_3 cur_grad(0,0,0); std::stringstream grad_value_stream(nrrd_keys_iter->second); -- GitLab From cb45c5ca992d548249d602eb5ea2ea76e6a93f9c Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:51 -0400 Subject: [PATCH 09/12] STYLE: remove unused line --- Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx b/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx index a7e5d987d..196b38e12 100644 --- a/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx +++ b/Libs/MRML/Core/vtkMRMLDiffusionWeightedVolumeNode.cxx @@ -305,7 +305,6 @@ void vtkMRMLDiffusionWeightedVolumeNode::SetDiffusionGradients(vtkDoubleArray *g for (int i = 0; i < grad->GetNumberOfTuples(); i++) { tmp_grad.copy_in(grad->GetTuple3(i)); - double grad_norm = tmp_grad.two_norm(); if (!valid_grad_length(tmp_grad)) { vtkErrorMacro(<< "vtkMRMLDiffusionWeightedVolumeNode only accepts gradient vectors with length 0.0 or 1.0!" -- GitLab From 0650f935e37b454d8947c4ce0e96c679a571786e Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:51:59 -0400 Subject: [PATCH 10/12] ENH: use DoubleConvert from ITK to improve float-string representation --- Libs/vtkTeem/vtkTeemNRRDWriter.cxx | 58 +++++++++++++++++++----------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/Libs/vtkTeem/vtkTeemNRRDWriter.cxx b/Libs/vtkTeem/vtkTeemNRRDWriter.cxx index 721659e08..286afe05d 100644 --- a/Libs/vtkTeem/vtkTeemNRRDWriter.cxx +++ b/Libs/vtkTeem/vtkTeemNRRDWriter.cxx @@ -9,6 +9,12 @@ #include "vtkInformation.h" #include +#include +#include + +#include "itkNumberToString.h" + + class AttributeMapType: public std::map {}; class AxisInfoMapType : public std::map {}; @@ -299,6 +305,10 @@ void* vtkTeemNRRDWriter::MakeNRRD() } } + // use double-conversion library (via ITK) for better + // float64 string representation. + itk::NumberToString DoubleConvert; + // 2. Take care about diffusion data if (this->DiffusionWeightedData) { @@ -308,30 +318,38 @@ void* vtkTeemNRRDWriter::MakeNRRD() if (kind[0] == nrrdKindList && numGrad == size[0] && numBValues == size[0]) { // This is diffusion Data - double *grad; - double bVal,factor; + vnl_double_3 grad; + double bVal, factor; double maxbVal = this->BValues->GetRange()[1]; - char value[1024]; - char key[1024]; - strcpy(key,"modality"); - strcpy(value,"DWMRI"); - nrrdKeyValueAdd(nrrd, key, value); - - strcpy(key,"DWMRI_b-value"); - //sprintf(value,"%f",maxbVal,1024); - sprintf(value,"%f",maxbVal); - nrrdKeyValueAdd(nrrd,key, value); - for (unsigned int ig =0; ig< numGrad; ig++) + + std::string modality_key("modality"); + std::string modality_value("DWMRI"); + nrrdKeyValueAdd(nrrd, modality_key.c_str(), modality_value.c_str()); + + std::string bval_key("DWMRI_b-value"); + std::stringstream bval_value; + bval_value << DoubleConvert(maxbVal); + nrrdKeyValueAdd(nrrd, bval_key.c_str(), bval_value.str().c_str()); + + for (unsigned int ig =0; ig < numGrad; ig++) { - grad=this->DiffusionGradients->GetTuple3(ig); + // key + std::stringstream key_stream; + key_stream << "DWMRI_gradient_" << setfill('0') << setw(4) << ig; + + // gradient value + grad.copy_in(this->DiffusionGradients->GetTuple3(ig)); + bVal = this->BValues->GetValue(ig); - // for multiple b-values, scale factor is `sqrt(b/b_max)` - // per NA-MIC DWI convention. so we take `norm^2 * b_max` - // to get back the original b-values. + // per NA-MIC DWI convention factor = sqrt(bVal/maxbVal); - sprintf(key,"%s%04d","DWMRI_gradient_",ig); - sprintf(value,"%f %f %f",grad[0]*factor, grad[1]*factor, grad[2]*factor); - nrrdKeyValueAdd(nrrd,key, value); + std::stringstream value_stream; + value_stream << std::setprecision(17) << + DoubleConvert(grad[0] * factor) << " " << + DoubleConvert(grad[1] * factor) << " " << + DoubleConvert(grad[2] * factor); + + nrrdKeyValueAdd(nrrd, key_stream.str().c_str(), value_stream.str().c_str()); } } } -- GitLab From cc05b9ace597887020d1fb38796a821a791ce8fc Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:52:04 -0400 Subject: [PATCH 11/12] ENH: address review comments --- .../SlicerApp/Testing/Python/CMakeLists.txt | 2 +- .../Testing/Python/DWMRIMultishellIOTests.py | 142 +++++++++++++----- Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx | 5 +- Libs/vtkTeem/CMakeLists.txt | 2 + 4 files changed, 110 insertions(+), 41 deletions(-) diff --git a/Applications/SlicerApp/Testing/Python/CMakeLists.txt b/Applications/SlicerApp/Testing/Python/CMakeLists.txt index 1fbba93b5..4e5507633 100644 --- a/Applications/SlicerApp/Testing/Python/CMakeLists.txt +++ b/Applications/SlicerApp/Testing/Python/CMakeLists.txt @@ -292,7 +292,7 @@ slicer_add_python_unittest( # Test multi-shell DWI I/O slicer_add_python_test( SCRIPT DWMRIMultishellIOTests.py - SLICER_ARGS --no-main-window --disable-modules + SLICER_ARGS --no-main-window --disable-modules --testing SCRIPT_ARGS ${Slicer_SOURCE_DIR}/ ${Slicer_BINARY_DIR}/Testing/Temporary/ diff --git a/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py b/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py index 6c2fbc018..3f17da86e 100644 --- a/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py +++ b/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py @@ -3,11 +3,10 @@ from nose.tools import assert_equal from collections import namedtuple import numpy as np -from numpy.testing import assert_allclose +import numpy.testing +from vtk.util import numpy_support import slicer -Context = namedtuple('Ctx', ['data_dir', 'temp_dir']) - #=============================================================================== mrmlcore_testdata_path = "Libs/MRML/Core/Testing/TestData/" @@ -19,7 +18,6 @@ multishell_dwi_451 = os.path.join(mrmlcore_testdata_path, "multishell-DWI-451dir NRRD = namedtuple('NRRD', ['header', 'bvalue', 'gradients']) def parse_nhdr(path): - # TODO: NRRD b-matrix form? dwmri_bval_key = "DWMRI_b-value" dwmri_grad_keybase = "DWMRI_gradient_" dwmri_grad_key_n = "DWMRI_gradient_{:04d}" @@ -60,62 +58,128 @@ def parse_nhdr(path): #================================================================================ -def test_vtkMRMLNRRDStorageNode(ctx): - testnrrd_path = os.path.join(ctx.data_dir, multishell_dwi_451) +def normalize(vec): + norm = np.linalg.norm(vec) + if norm == 0.0: + return vec + else: + return vec * 1/norm + + +def test_nrrd_dwi_load(first_file, second_file=None): + """ + - load a DWI NRRD file into Slicer + - validate b values and gradient vectors against original header + - check the values in the vtkMRMLDiffusionWeightedVolumeNode + - check the values in the vtkMRMLDWVNode attribute dictionary + """ + if second_file is None: + second_file = first_file # load NRRD into Slicer storagenode = slicer.vtkMRMLNRRDStorageNode() - storagenode.SetFileName(testnrrd_path) + storagenode.SetFileName(first_file) dw_node = slicer.vtkMRMLDiffusionWeightedVolumeNode() storagenode.ReadData(dw_node) - slicer_grads = dw_node.GetDiffusionGradients() - slicer_numgrads = slicer_grads.GetNumberOfTuples() + slicer_grads = numpy_support.vtk_to_numpy(dw_node.GetDiffusionGradients()) + slicer_numgrads = slicer_grads.shape[0] + + # load NRRD with pure-python parser + parsed_nrrd = parse_nhdr(second_file) - # load NRRD with direct parser - ext_nrrd = parse_nhdr(testnrrd_path) + ################################## + # 1) check the number of gradients - # basic assertions - assert( len(ext_nrrd.gradients) == slicer_numgrads ) + assert( len(parsed_nrrd.gradients) == slicer_numgrads ) + ################################## + # 2) check the node b values and gradients are correct - _ext_bval = ext_nrrd.bvalue # Note: vtkDataArray.GetMaxNorm gives max for scalar array. - _node_bval = dw_node.GetBValues().GetMaxNorm() - _attr_bval = float(dw_node.GetAttribute("DWMRI_b-value")) - nose.tools.assert_equal(_ext_bval, _node_bval) - nose.tools.assert_equal(_ext_bval, _attr_bval) + # max b value from the node + nose.tools.assert_equal(parsed_nrrd.bvalue, dw_node.GetBValues().GetMaxNorm()) + + max_parsed_grad_norm = np.max(np.apply_along_axis(np.linalg.norm, 1, parsed_nrrd.gradients)) - # Gradients in the node attribute dictionary must exactly - # match those on-disk. for i in range(0, slicer_numgrads): - g_from_nhdr = ext_nrrd.gradients[i] - g_from_attr = np.fromstring( - dw_node.GetAttribute("DWMRI_gradient_{:04d}".format(i)), - sep=" ", dtype=np.float64) + g_parsed_raw = parsed_nrrd.gradients[i] + g_parsed_normed = normalize(g_parsed_raw) + + bval_parsed = parsed_nrrd.bvalue * pow(np.linalg.norm(g_parsed_raw) / max_parsed_grad_norm, 2) + np.testing.assert_almost_equal(bval_parsed, dw_node.GetBValue(i), decimal=7, + err_msg="MRMLNode b value does not match NRRD header") - assert_allclose(np.linalg.norm(g_from_nhdr - g_from_attr), 0.0, atol=1e-12) + g_from_node = slicer_grads[i, :] - # Gradients in the node DiffusionGradients API must be - # match *normalized* gradient. + # gradients stored in the vtkMRMLDiffusionWeightedVolumeNode must be *normalized*. + np.testing.assert_allclose(np.linalg.norm(g_parsed_normed - g_from_node), 0.0, atol=1e-15) + + # b value from the node attribute dictionary + np.testing.assert_equal(parsed_nrrd.bvalue, float(dw_node.GetAttribute("DWMRI_b-value"))) + + # 3) check gradients in the node attribute dictionary + # gradients must match the value on-disk. for i in range(0, slicer_numgrads): - _g_tmp = ext_nrrd.gradients[i] - _g_tmp_norm = np.linalg.norm(_g_tmp) - # normalize - g_from_nhdr = _g_tmp if _g_tmp_norm == 0.0 \ - else (_g_tmp * 1/_g_tmp_norm) + grad_key = "DWMRI_gradient_{:04d}".format(i) + parsed_gradient = np.fromstring(parsed_nrrd.header[grad_key], count=3, sep=' ', dtype=np.float64) + attr_gradient = np.fromstring(dw_node.GetAttribute(grad_key), count=3, sep=' ', dtype=np.float64) + + np.testing.assert_array_almost_equal(parsed_gradient, attr_gradient, decimal=12, + err_msg="NHDR gradient does not match gradient in node attribute dictionary") + + return (parsed_nrrd, dw_node) + +def test_nrrd_dwi_roundtrip(test_nrrd_path): + """DWI NRRD round-trip test + - loads and saves a NRRD file via Slicer's I/O, twice + - checks the node values against the original file each time + """ + + import tempfile + + # load and re-save NRRD once + storagenode1 = slicer.vtkMRMLNRRDStorageNode() + storagenode1.SetFileName(test_nrrd_path) + dw_node1 = slicer.vtkMRMLDiffusionWeightedVolumeNode() + storagenode1.ReadData(dw_node1) + __f_tmp_nrrd1 = tempfile.NamedTemporaryFile(suffix=".nhdr", dir=tmp_dir, delete=False) + tmp_nrrd1 = __f_tmp_nrrd1.name + storagenode1.SetFileName(tmp_nrrd1) + storagenode1.WriteData(dw_node1) + + parsed_nrrd2, dw_node2 = test_nrrd_dwi_load(test_nrrd_path, tmp_nrrd1) + + # re-save NRRD again + storagenode2 = slicer.vtkMRMLNRRDStorageNode() + __f_tmp_nrrd2 = tempfile.NamedTemporaryFile(suffix=".nhdr", dir=tmp_dir, delete=False) + tmp_nrrd2 = __f_tmp_nrrd2.name + storagenode2.SetFileName(tmp_nrrd2) + storagenode2.WriteData(dw_node2) + + # test twice-saved file against original NRRD + parsed_nrrd3, dw_node3 = test_nrrd_dwi_load(test_nrrd_path, tmp_nrrd2) + + print(test_nrrd_path, tmp_nrrd1, tmp_nrrd2) + - g_from_node = np.array(slicer_grads.GetTuple3(i)) - print _g_tmp, g_from_nhdr, g_from_node +def run_tests(data_dir, tmp_dir): + # construct path to test data + testnrrd_path = os.path.join(data_dir, multishell_dwi_451) - assert_allclose(np.linalg.norm(g_from_nhdr - g_from_node), 0.0, atol=1e-12) + test_nrrd_dwi_load(testnrrd_path) + test_nrrd_dwi_roundtrip(testnrrd_path) if __name__ == '__main__': # TODO make sure data paths exist - ctx = Context(data_dir = sys.argv[1], - temp_dir = sys.argv[2]) + data_dir = sys.argv[1] + tmp_dir = sys.argv[2] - success = test_vtkMRMLNRRDStorageNode(ctx) + try: + run_tests(data_dir, tmp_dir) + exit(slicer.util.EXIT_SUCCESS) + except: + raise - sys.exit(success) + exit(slicer.util.EXIT_SUCCESS) diff --git a/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx b/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx index 5ca75edd0..00c8460a5 100644 --- a/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx +++ b/Libs/MRML/Core/vtkMRMLNRRDStorageNode.cxx @@ -433,7 +433,10 @@ bool parse_gradient_key(std::string key, size_t &grad_number, size_t &gradkey_pa } // below here key is: DWMRI_gradient_#### - // we enforce the constraint that the padding of the grad key must be consistent + // padding is the extra zeros to give a specific digit count + // 0001 + // ^^^ <- zeros here are padding to 4 digits + // we enforce the constraint that the padding of the grad keys must be consistent if (gradkey_pad_width == 0) { gradkey_pad_width = key.size() - dwmri_grad_tag.size(); } diff --git a/Libs/vtkTeem/CMakeLists.txt b/Libs/vtkTeem/CMakeLists.txt index 89b565038..83e4ea32c 100644 --- a/Libs/vtkTeem/CMakeLists.txt +++ b/Libs/vtkTeem/CMakeLists.txt @@ -87,6 +87,8 @@ set(srcs ${vtkTeem_SRCS}) add_library(${lib_name} ${srcs}) set(libs + itkvnl + ITKCommon ${Teem_LIBRARIES} ${VTK_LIBRARIES} ) -- GitLab From 53c55d598b6d149fa984d9d668861ec0cd751859 Mon Sep 17 00:00:00 2001 From: Isaiah Norton Date: Tue, 28 Aug 2018 17:55:08 -0400 Subject: [PATCH 12/12] STYLE: remove unnecessary print statement --- Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py b/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py index 3f17da86e..53aa0fa58 100644 --- a/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py +++ b/Applications/SlicerApp/Testing/Python/DWMRIMultishellIOTests.py @@ -160,8 +160,6 @@ def test_nrrd_dwi_roundtrip(test_nrrd_path): # test twice-saved file against original NRRD parsed_nrrd3, dw_node3 = test_nrrd_dwi_load(test_nrrd_path, tmp_nrrd2) - print(test_nrrd_path, tmp_nrrd1, tmp_nrrd2) - def run_tests(data_dir, tmp_dir): # construct path to test data -- GitLab