1 |
|
|
/* |
2 |
|
|
Teem: Tools to process and visualize scientific data and images . |
3 |
|
|
Copyright (C) 2013, 2012, 2011, 2010, 2009 University of Chicago |
4 |
|
|
Copyright (C) 2008, 2007, 2006, 2005 Gordon Kindlmann |
5 |
|
|
Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998 University of Utah |
6 |
|
|
|
7 |
|
|
This library is free software; you can redistribute it and/or |
8 |
|
|
modify it under the terms of the GNU Lesser General Public License |
9 |
|
|
(LGPL) as published by the Free Software Foundation; either |
10 |
|
|
version 2.1 of the License, or (at your option) any later version. |
11 |
|
|
The terms of redistributing and/or modifying this software also |
12 |
|
|
include exceptions to the LGPL that facilitate static linking. |
13 |
|
|
|
14 |
|
|
This library is distributed in the hope that it will be useful, |
15 |
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 |
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 |
|
|
Lesser General Public License for more details. |
18 |
|
|
|
19 |
|
|
You should have received a copy of the GNU Lesser General Public License |
20 |
|
|
along with this library; if not, write to Free Software Foundation, Inc., |
21 |
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 |
|
|
*/ |
23 |
|
|
|
24 |
|
|
#include "ten.h" |
25 |
|
|
#include "privateTen.h" |
26 |
|
|
|
27 |
|
|
const char * |
28 |
|
|
tenModelPrefixStr = "DWMRI_model:"; |
29 |
|
|
|
30 |
|
|
static const tenModel * |
31 |
|
|
str2model(const char *str) { |
32 |
|
|
const tenModel *ret; |
33 |
|
|
|
34 |
|
|
if (!strcmp(str, TEN_MODEL_STR_ZERO)) { |
35 |
|
|
ret = tenModelZero; |
36 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_B0)) { |
37 |
|
|
ret = tenModelB0; |
38 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_BALL)) { |
39 |
|
|
ret = tenModelBall; |
40 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_1STICK)) { |
41 |
|
|
ret = tenModel1Stick; |
42 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_1VECTOR2D)) { |
43 |
|
|
ret = tenModel1Vector2D; |
44 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_1UNIT2D)) { |
45 |
|
|
ret = tenModel1Unit2D; |
46 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_2UNIT2D)) { |
47 |
|
|
ret = tenModel2Unit2D; |
48 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_BALL1STICKEMD)) { |
49 |
|
|
ret = tenModelBall1StickEMD; |
50 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_BALL1STICK)) { |
51 |
|
|
ret = tenModelBall1Stick; |
52 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_BALL1CYLINDER)) { |
53 |
|
|
ret = tenModelBall1Cylinder; |
54 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_1CYLINDER)) { |
55 |
|
|
ret = tenModel1Cylinder; |
56 |
|
|
} else if (!strcmp(str, TEN_MODEL_STR_1TENSOR2)) { |
57 |
|
|
ret = tenModel1Tensor2; |
58 |
|
|
} else { |
59 |
|
|
/* we don't currently have a tenModelUnknown */ |
60 |
|
|
ret = NULL; |
61 |
|
|
} |
62 |
|
|
return ret; |
63 |
|
|
} |
64 |
|
|
|
65 |
|
|
int |
66 |
|
|
tenModelParse(const tenModel **model, int *plusB0, |
67 |
|
|
int requirePrefix, const char *_str) { |
68 |
|
|
static const char me[]="tenModelParse"; |
69 |
|
|
char *str, *modstr, *pre; |
70 |
|
|
airArray *mop; |
71 |
|
|
|
72 |
|
|
if (!( model && plusB0 && _str)) { |
73 |
|
|
biffAddf(TEN, "%s: got NULL pointer", me); |
74 |
|
|
return 1; |
75 |
|
|
} |
76 |
|
|
str = airStrdup(_str); |
77 |
|
|
if (!str) { |
78 |
|
|
biffAddf(TEN, "%s: couldn't strdup", me); |
79 |
|
|
return 1; |
80 |
|
|
} |
81 |
|
|
mop = airMopNew(); |
82 |
|
|
airMopAdd(mop, str, airFree, airMopAlways); |
83 |
|
|
pre = strstr(str, tenModelPrefixStr); |
84 |
|
|
if (pre) { |
85 |
|
|
str += strlen(tenModelPrefixStr); |
86 |
|
|
} else { |
87 |
|
|
if (requirePrefix) { |
88 |
|
|
biffAddf(TEN, "%s: didn't see prefix \"%s\" in \"%s\"", me, |
89 |
|
|
tenModelPrefixStr, _str); |
90 |
|
|
airMopError(mop); return 1; |
91 |
|
|
} |
92 |
|
|
} |
93 |
|
|
airToLower(str); /* for sake of "b0" and str2model below */ |
94 |
|
|
|
95 |
|
|
if ((modstr = strchr(str, '+'))) { |
96 |
|
|
*modstr = '\0'; |
97 |
|
|
++modstr; |
98 |
|
|
if (!strcmp(str, "b0")) { |
99 |
|
|
*plusB0 = AIR_TRUE; |
100 |
|
|
} else { |
101 |
|
|
biffAddf(TEN, "%s: string (\"%s\") prior to \"+\" not \"b0\"", me, str); |
102 |
|
|
airMopError(mop); return 1; |
103 |
|
|
} |
104 |
|
|
} else { |
105 |
|
|
*plusB0 = AIR_FALSE; |
106 |
|
|
modstr = str; |
107 |
|
|
} |
108 |
|
|
if (!(*model = str2model(modstr))) { |
109 |
|
|
biffAddf(TEN, "%s: didn't recognize \"%s\" as model", me, modstr); |
110 |
|
|
airMopError(mop); return 1; |
111 |
|
|
} |
112 |
|
|
airMopOkay(mop); |
113 |
|
|
return 0; |
114 |
|
|
} |
115 |
|
|
|
116 |
|
|
int |
117 |
|
|
tenModelFromAxisLearnPossible(const NrrdAxisInfo *axinfo) { |
118 |
|
|
|
119 |
|
|
/* HEY keep in synch with nrrdKind* code below */ |
120 |
|
|
return (nrrdKind3DSymMatrix == axinfo->kind |
121 |
|
|
|| nrrdKind3DMaskedSymMatrix == axinfo->kind |
122 |
|
|
|| airStrlen(axinfo->label)); |
123 |
|
|
} |
124 |
|
|
|
125 |
|
|
int |
126 |
|
|
tenModelFromAxisLearn(const tenModel **modelP, |
127 |
|
|
int *plusB0, |
128 |
|
|
const NrrdAxisInfo *axinfo) { |
129 |
|
|
static const char me[]="tenModelFromAxisLearn"; |
130 |
|
|
|
131 |
|
|
if (!(modelP && plusB0 && axinfo)) { |
132 |
|
|
biffAddf(TEN, "%s: got NULL pointer", me); |
133 |
|
|
return 1; |
134 |
|
|
} |
135 |
|
|
*plusB0 = AIR_FALSE; |
136 |
|
|
/* first try to learn model from axis "kind" */ |
137 |
|
|
/* HEY should probably also support 3 vector for stick? */ |
138 |
|
|
/* HEY keep in synch with nrrdKind* code above */ |
139 |
|
|
if (nrrdKind3DSymMatrix == axinfo->kind |
140 |
|
|
|| nrrdKind3DMaskedSymMatrix == axinfo->kind) { |
141 |
|
|
*modelP = tenModel1Tensor2; |
142 |
|
|
} else if (airStrlen(axinfo->label)) { |
143 |
|
|
/* try to parse from label */ |
144 |
|
|
if (tenModelParse(modelP, plusB0, AIR_TRUE, axinfo->label)) { |
145 |
|
|
biffAddf(TEN, "%s: couldn't parse label \"%s\"", me, axinfo->label); |
146 |
|
|
*modelP = NULL; |
147 |
|
|
return 1; |
148 |
|
|
} |
149 |
|
|
} else { |
150 |
|
|
biffAddf(TEN, "%s: don't have kind or label info to learn model", me); |
151 |
|
|
*modelP = NULL; |
152 |
|
|
return 1; |
153 |
|
|
} |
154 |
|
|
|
155 |
|
|
return 0; |
156 |
|
|
} |
157 |
|
|
|
158 |
|
|
/* |
159 |
|
|
** If nB0 is given, then those B0 image values will be used. |
160 |
|
|
** In this case, either the parm vector can be short by one (seems to be |
161 |
|
|
** missing B0), or the parm vector includes B0, but these will be ignored |
162 |
|
|
** and over-written with the B0 values from nB0. |
163 |
|
|
** |
164 |
|
|
** basic and axis info is derived from _nparm |
165 |
|
|
*/ |
166 |
|
|
int |
167 |
|
|
tenModelSimulate(Nrrd *ndwi, int typeOut, |
168 |
|
|
tenExperSpec *espec, |
169 |
|
|
const tenModel *model, |
170 |
|
|
const Nrrd *_nB0, |
171 |
|
|
const Nrrd *_nparm, |
172 |
|
|
int keyValueSet) { |
173 |
|
|
static const char me[]="tenModelSimulate"; |
174 |
|
|
double *ddwi, *parm, (*ins)(void *v, size_t I, double d); |
175 |
|
|
char *dwi; |
176 |
|
16 |
size_t szOut[NRRD_DIM_MAX], II, numSamp; |
177 |
|
|
const Nrrd *nB0, /* B0 as doubles */ |
178 |
|
|
*ndparm, /* parm as doubles */ |
179 |
|
|
*ndpparm, /* parm as doubles, padded */ |
180 |
|
|
*nparm; /* final parm as doubles, padded, w/ correct B0 values */ |
181 |
|
|
Nrrd *ntmp; /* non-const pointer for working */ |
182 |
|
|
airArray *mop; |
183 |
|
|
unsigned int gpsze, /* given parm size */ |
184 |
|
|
ii; |
185 |
|
8 |
int useB0Img, needPad, axmap[NRRD_DIM_MAX]; |
186 |
|
|
|
187 |
✗✓ |
8 |
if (!(ndwi && espec && model /* _nB0 can be NULL */ && _nparm)) { |
188 |
|
|
biffAddf(TEN, "%s: got NULL pointer", me); |
189 |
|
|
return 1; |
190 |
|
|
} |
191 |
✗✓ |
8 |
if (!espec->imgNum) { |
192 |
|
|
biffAddf(TEN, "%s: given espec wants 0 images, unset?", me); |
193 |
|
|
return 1; |
194 |
|
|
} |
195 |
|
|
|
196 |
|
8 |
gpsze = _nparm->axis[0].size; |
197 |
✓✗ |
8 |
if (model->parmNum - 1 == gpsze) { |
198 |
|
|
/* got one less than needed parm #, see if we got B0 */ |
199 |
✗✓ |
8 |
if (!_nB0) { |
200 |
|
|
biffAddf(TEN, "%s: got %u parms, need %u (for %s), " |
201 |
|
|
"but didn't get B0 vol", |
202 |
|
|
me, gpsze, model->parmNum, model->name); |
203 |
|
|
return 1; |
204 |
|
|
} |
205 |
|
|
useB0Img = AIR_TRUE; |
206 |
|
|
needPad = AIR_TRUE; |
207 |
✗✗ |
8 |
} else if (model->parmNum != gpsze) { |
208 |
|
|
biffAddf(TEN, "%s: mismatch between getting %u parms, " |
209 |
|
|
"needing %u (for %s)\n", |
210 |
|
|
me, gpsze, model->parmNum, model->name); |
211 |
|
|
return 1; |
212 |
|
|
} else { |
213 |
|
|
/* model->parmNum == gpsze */ |
214 |
|
|
needPad = AIR_FALSE; |
215 |
|
|
useB0Img = !!_nB0; |
216 |
|
|
} |
217 |
|
|
|
218 |
|
8 |
mop = airMopNew(); |
219 |
|
|
/* get parms as doubles */ |
220 |
✗✓ |
8 |
if (nrrdTypeDouble == _nparm->type) { |
221 |
|
|
ndparm = _nparm; |
222 |
|
|
} else { |
223 |
|
8 |
ntmp = nrrdNew(); |
224 |
|
8 |
airMopAdd(mop, ntmp, (airMopper)nrrdNuke, airMopAlways); |
225 |
✗✓ |
8 |
if (nrrdConvert(ntmp, _nparm, nrrdTypeDouble)) { |
226 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't convert parm to %s", me, |
227 |
|
|
airEnumStr(nrrdType, nrrdTypeDouble)); |
228 |
|
|
airMopError(mop); return 1; |
229 |
|
|
} |
230 |
|
|
ndparm = ntmp; |
231 |
|
|
} |
232 |
|
|
/* get parms the right length */ |
233 |
✗✓ |
8 |
if (!needPad) { |
234 |
|
|
ndpparm = ndparm; |
235 |
|
|
} else { |
236 |
|
8 |
ptrdiff_t min[NRRD_DIM_MAX], max[NRRD_DIM_MAX]; |
237 |
|
|
unsigned int ax; |
238 |
|
|
|
239 |
|
8 |
ntmp = nrrdNew(); |
240 |
|
8 |
airMopAdd(mop, ntmp, (airMopper)nrrdNuke, airMopAlways); |
241 |
✓✓ |
80 |
for (ax=0; ax<ndparm->dim; ax++) { |
242 |
|
32 |
min[ax] = (!ax ? -1 : 0); |
243 |
|
32 |
max[ax] = ndparm->axis[ax].size-1; |
244 |
|
|
} |
245 |
✗✓ |
8 |
if (nrrdPad_nva(ntmp, ndparm, min, max, nrrdBoundaryBleed, 0.0)) { |
246 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't pad", me); |
247 |
|
|
airMopError(mop); return 1; |
248 |
|
|
} |
249 |
|
|
ndpparm = ntmp; |
250 |
✓✗ |
16 |
} |
251 |
|
|
/* put in B0 values if needed */ |
252 |
✗✓ |
8 |
if (!useB0Img) { |
253 |
|
|
nparm = ndpparm; |
254 |
|
|
} else { |
255 |
✗✓ |
8 |
if (nrrdTypeDouble == _nB0->type) { |
256 |
|
|
nB0 = _nB0; |
257 |
|
|
} else { |
258 |
|
8 |
ntmp = nrrdNew(); |
259 |
|
8 |
airMopAdd(mop, ntmp, (airMopper)nrrdNuke, airMopAlways); |
260 |
✗✓ |
8 |
if (nrrdConvert(ntmp, _nB0, nrrdTypeDouble)) { |
261 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't convert B0 to %s", me, |
262 |
|
|
airEnumStr(nrrdType, nrrdTypeDouble)); |
263 |
|
|
airMopError(mop); return 1; |
264 |
|
|
} |
265 |
|
|
nB0 = ntmp; |
266 |
|
|
} |
267 |
|
|
/* HEY: this is mostly likely a waste of memory, |
268 |
|
|
but its all complicated by const-correctness */ |
269 |
|
8 |
ntmp = nrrdNew(); |
270 |
|
8 |
airMopAdd(mop, ntmp, (airMopper)nrrdNuke, airMopAlways); |
271 |
✗✓ |
8 |
if (nrrdSplice(ntmp, ndpparm, nB0, 0, 0)) { |
272 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't splice in B0", me); |
273 |
|
|
airMopError(mop); return 1; |
274 |
|
|
} |
275 |
|
|
nparm = ntmp; |
276 |
|
|
} |
277 |
|
|
|
278 |
|
|
/* allocate output (and set axmap) */ |
279 |
✓✓ |
80 |
for (ii=0; ii<nparm->dim; ii++) { |
280 |
✓✓ |
96 |
szOut[ii] = (!ii |
281 |
|
8 |
? espec->imgNum |
282 |
|
24 |
: nparm->axis[ii].size); |
283 |
|
32 |
axmap[ii] = (!ii |
284 |
|
|
? -1 |
285 |
|
|
: AIR_CAST(int, ii)); |
286 |
|
|
} |
287 |
✗✓ |
8 |
if (nrrdMaybeAlloc_nva(ndwi, typeOut, nparm->dim, szOut)) { |
288 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't allocate output", me); |
289 |
|
|
airMopError(mop); return 1; |
290 |
|
|
} |
291 |
✗✓ |
8 |
if (!( ddwi = AIR_CALLOC(espec->imgNum, double))) { |
292 |
|
|
biffAddf(TEN, "%s: couldn't allocate dwi buffer", me); |
293 |
|
|
airMopError(mop); return 1; |
294 |
|
|
} |
295 |
|
8 |
airMopAdd(mop, ddwi, airFree, airMopAlways); |
296 |
|
8 |
numSamp = nrrdElementNumber(nparm)/nparm->axis[0].size; |
297 |
|
|
|
298 |
|
|
/* set output */ |
299 |
|
8 |
ins = nrrdDInsert[typeOut]; |
300 |
|
8 |
parm = AIR_CAST(double *, nparm->data); |
301 |
|
8 |
dwi = AIR_CAST(char *, ndwi->data); |
302 |
✓✓ |
1556656 |
for (II=0; II<numSamp; II++) { |
303 |
|
778320 |
model->simulate(ddwi, parm, espec); |
304 |
✓✓ |
18679680 |
for (ii=0; ii<espec->imgNum; ii++) { |
305 |
|
8561520 |
ins(dwi, ii, ddwi[ii]); |
306 |
|
|
} |
307 |
|
778320 |
parm += model->parmNum; |
308 |
|
778320 |
dwi += espec->imgNum*nrrdTypeSize[typeOut]; |
309 |
|
|
} |
310 |
|
|
|
311 |
✓✗ |
8 |
if (keyValueSet) { |
312 |
✗✓ |
8 |
if (tenDWMRIKeyValueFromExperSpecSet(ndwi, espec)) { |
313 |
|
|
biffAddf(TEN, "%s: trouble", me); |
314 |
|
|
airMopError(mop); return 1; |
315 |
|
|
} |
316 |
|
|
} |
317 |
|
|
|
318 |
✗✓ |
16 |
if (nrrdAxisInfoCopy(ndwi, _nparm, axmap, NRRD_AXIS_INFO_SIZE_BIT) |
319 |
✓✗ |
16 |
|| nrrdBasicInfoCopy(ndwi, _nparm, |
320 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
321 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
322 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
323 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
324 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
325 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
326 |
|
8 |
| (nrrdStateKeyValuePairsPropagate |
327 |
|
|
? 0 |
328 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
329 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't copy axis or basic info", me); |
330 |
|
|
airMopError(mop); return 1; |
331 |
|
|
} |
332 |
|
8 |
ndwi->axis[0].kind = nrrdKindList; |
333 |
|
|
|
334 |
|
8 |
airMopOkay(mop); |
335 |
|
8 |
return 0; |
336 |
|
8 |
} |
337 |
|
|
|
338 |
|
|
/* |
339 |
|
|
** _tenModelSqeFitSingle |
340 |
|
|
** |
341 |
|
|
** callable function (as opposed to tenModel method) for doing |
342 |
|
|
** sqe fitting. Returns the sqe at the converged fit location |
343 |
|
|
** Requires PARM_NUM length buffers testParm and grad |
344 |
|
|
*/ |
345 |
|
|
double |
346 |
|
|
_tenModelSqeFitSingle(const tenModel *model, |
347 |
|
|
double *testParm, double *grad, |
348 |
|
|
double *parm, double *convFrac, unsigned int *itersTaken, |
349 |
|
|
const tenExperSpec *espec, |
350 |
|
|
double *dwiBuff, const double *dwiMeas, |
351 |
|
|
const double *parmInit, int knownB0, |
352 |
|
|
unsigned int minIter, unsigned int maxIter, |
353 |
|
|
double convEps, int verbose) { |
354 |
|
|
static const char me[]="_tenModelSqeFitSingle"; |
355 |
|
|
unsigned int iter, subIter; |
356 |
|
|
double step, bak, opp, val, testval, dist, td; |
357 |
|
|
int done; |
358 |
|
|
char pstr[AIR_STRLEN_MED]; |
359 |
|
|
|
360 |
|
|
step = 1; |
361 |
|
|
model->copy(parm, parmInit); |
362 |
|
|
val = model->sqe(parm, espec, dwiBuff, dwiMeas, knownB0); |
363 |
|
|
model->sqeGrad(grad, parm, espec, dwiBuff, dwiMeas, knownB0); |
364 |
|
|
if (verbose > 1) { |
365 |
|
|
model->sprint(pstr, parm); |
366 |
|
|
fprintf(stderr, "\n"); |
367 |
|
|
fprintf(stderr, "%s(%s): minIter = %u, maxIter = %u\n", me, model->name, |
368 |
|
|
minIter, maxIter); |
369 |
|
|
fprintf(stderr, "%s(%s): starting at %s -> %g (step %g)\n", me, |
370 |
|
|
model->name, pstr, val, step); |
371 |
|
|
} |
372 |
|
|
|
373 |
|
|
opp = 1.2; /* opportunistic step size increase */ |
374 |
|
|
bak = 0.5; /* scaling back because of bad step */ |
375 |
|
|
iter = 0; |
376 |
|
|
dist = convEps*8; |
377 |
|
|
do { |
378 |
|
|
subIter = 0; |
379 |
|
|
do { |
380 |
|
|
model->step(testParm, -step, grad, parm); |
381 |
|
|
testval = model->sqe(testParm, espec, dwiBuff, dwiMeas, knownB0); |
382 |
|
|
if (verbose > 1) { |
383 |
|
|
model->sprint(pstr, testParm); |
384 |
|
|
fprintf(stderr, "%s(%s): (iter %u/%u) tried %s -> %g (step %g)\n", |
385 |
|
|
me, model->name, iter, subIter, pstr, testval, step); |
386 |
|
|
} |
387 |
|
|
if (testval > val) { |
388 |
|
|
step *= bak; |
389 |
|
|
} |
390 |
|
|
subIter++; |
391 |
|
|
} while (testval > val && subIter <= maxIter); |
392 |
|
|
if (subIter > maxIter) { |
393 |
|
|
/* something went wrong with merely trying to find a downhill step; |
394 |
|
|
this has occurred previously when (because of a bug) the |
395 |
|
|
per-parameter bounds put the test location inside the bounding |
396 |
|
|
box while the initial location was outside => could never converge. |
397 |
|
|
Not using biff, so this is one way of trying to signal the problem */ |
398 |
|
|
model->copy(parm, parmInit); |
399 |
|
|
*convFrac = AIR_POS_INF; |
400 |
|
|
*itersTaken = maxIter; |
401 |
|
|
return AIR_POS_INF; |
402 |
|
|
} |
403 |
|
|
td = model->dist(testParm, parm); |
404 |
|
|
dist = (td + dist)/2; |
405 |
|
|
val = testval; |
406 |
|
|
model->copy(parm, testParm); |
407 |
|
|
model->sqeGrad(grad, parm, espec, dwiBuff, dwiMeas, knownB0); |
408 |
|
|
step *= opp; |
409 |
|
|
iter++; |
410 |
|
|
done = (iter < minIter |
411 |
|
|
? AIR_FALSE |
412 |
|
|
: (iter > maxIter) || dist < convEps); |
413 |
|
|
} while (!done); |
414 |
|
|
*convFrac = dist/convEps; |
415 |
|
|
*itersTaken = iter; |
416 |
|
|
return val; |
417 |
|
|
} |
418 |
|
|
|
419 |
|
|
int |
420 |
|
|
tenModelSqeFit(Nrrd *nparm, |
421 |
|
|
Nrrd **nsqeP, Nrrd **nconvP, Nrrd **niterP, |
422 |
|
|
const tenModel *model, |
423 |
|
|
const tenExperSpec *espec, const Nrrd *ndwi, |
424 |
|
|
int knownB0, int saveB0, int typeOut, |
425 |
|
|
unsigned int minIter, unsigned int maxIter, |
426 |
|
|
unsigned int starts, double convEps, |
427 |
|
|
airRandMTState *_rng, int verbose) { |
428 |
|
|
static const char me[]="tenModelSqeFit"; |
429 |
|
|
char doneStr[13]; |
430 |
|
|
double *ddwi, *dwibuff, sqe, sqeBest, |
431 |
|
|
*dparm, *dparmBest, |
432 |
|
|
(*ins)(void *v, size_t I, double d), |
433 |
|
|
(*lup)(const void *v, size_t I); |
434 |
|
|
airArray *mop; |
435 |
|
|
unsigned int saveParmNum, dwiNum, ii, lablen, itersTaken; |
436 |
|
|
size_t szOut[NRRD_DIM_MAX], II, numSamp; |
437 |
|
|
int axmap[NRRD_DIM_MAX], erraxmap[NRRD_DIM_MAX], fitVerbose; |
438 |
|
|
const char *dwi; |
439 |
|
|
char *parm; |
440 |
|
|
airRandMTState *rng; |
441 |
|
|
Nrrd *nsqe, *nconv, *niter; |
442 |
|
|
|
443 |
|
|
/* nsqeP, nconvP, niterP can be NULL */ |
444 |
|
|
if (!( nparm && model && espec && ndwi )) { |
445 |
|
|
biffAddf(TEN, "%s: got NULL pointer", me); |
446 |
|
|
return 1; |
447 |
|
|
} |
448 |
|
|
if (!( starts > 0 )) { |
449 |
|
|
biffAddf(TEN, "%s: need non-zero starts", me); |
450 |
|
|
return 1; |
451 |
|
|
} |
452 |
|
|
if (!( nrrdTypeFloat == typeOut || nrrdTypeDouble == typeOut )) { |
453 |
|
|
biffAddf(TEN, "%s: typeOut must be %s or %s, not %s", me, |
454 |
|
|
airEnumStr(nrrdType, nrrdTypeFloat), |
455 |
|
|
airEnumStr(nrrdType, nrrdTypeDouble), |
456 |
|
|
airEnumStr(nrrdType, typeOut)); |
457 |
|
|
return 1; |
458 |
|
|
} |
459 |
|
|
dwiNum = ndwi->axis[0].size; |
460 |
|
|
if (espec->imgNum != dwiNum) { |
461 |
|
|
biffAddf(TEN, "%s: espec expects %u images but dwi has %u on axis 0", |
462 |
|
|
me, espec->imgNum, AIR_CAST(unsigned int, dwiNum)); |
463 |
|
|
return 1; |
464 |
|
|
} |
465 |
|
|
|
466 |
|
|
/* allocate output (and set axmap) */ |
467 |
|
|
dparm = model->alloc(); |
468 |
|
|
dparmBest = model->alloc(); |
469 |
|
|
if (!( dparm && dparmBest )) { |
470 |
|
|
biffAddf(TEN, "%s: couldn't allocate parm vecs", me); |
471 |
|
|
return 1; |
472 |
|
|
} |
473 |
|
|
mop = airMopNew(); |
474 |
|
|
airMopAdd(mop, dparm, airFree, airMopAlways); |
475 |
|
|
airMopAdd(mop, dparmBest, airFree, airMopAlways); |
476 |
|
|
saveParmNum = saveB0 ? model->parmNum : model->parmNum-1; |
477 |
|
|
for (ii=0; ii<ndwi->dim; ii++) { |
478 |
|
|
szOut[ii] = (!ii |
479 |
|
|
? saveParmNum |
480 |
|
|
: ndwi->axis[ii].size); |
481 |
|
|
axmap[ii] = (!ii |
482 |
|
|
? -1 |
483 |
|
|
: AIR_CAST(int, ii)); |
484 |
|
|
if (ii) { |
485 |
|
|
erraxmap[ii-1] = AIR_CAST(int, ii); |
486 |
|
|
} |
487 |
|
|
} |
488 |
|
|
if (nrrdMaybeAlloc_nva(nparm, typeOut, ndwi->dim, szOut)) { |
489 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't allocate output " |
490 |
|
|
"(saveB0 %d, knownB0 %d)", me, saveB0, knownB0); |
491 |
|
|
airMopError(mop); return 1; |
492 |
|
|
} |
493 |
|
|
if (nsqeP) { |
494 |
|
|
nsqe = *nsqeP; |
495 |
|
|
if (!nsqe) { |
496 |
|
|
nsqe = nrrdNew(); |
497 |
|
|
*nsqeP = nsqe; |
498 |
|
|
} |
499 |
|
|
if (nrrdMaybeAlloc_nva(nsqe, typeOut, ndwi->dim-1, szOut+1)) { |
500 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't allocate error output", me); |
501 |
|
|
airMopError(mop); return 1; |
502 |
|
|
} |
503 |
|
|
} else { |
504 |
|
|
nsqe = NULL; |
505 |
|
|
} |
506 |
|
|
if (nconvP) { |
507 |
|
|
nconv = *nconvP; |
508 |
|
|
if (!nconv) { |
509 |
|
|
nconv = nrrdNew(); |
510 |
|
|
*nconvP = nconv; |
511 |
|
|
} |
512 |
|
|
if (nrrdMaybeAlloc_nva(nconv, nrrdTypeDouble, ndwi->dim-1, szOut+1)) { |
513 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't allocate conv output", me); |
514 |
|
|
airMopError(mop); return 1; |
515 |
|
|
} |
516 |
|
|
} else { |
517 |
|
|
nconv = NULL; |
518 |
|
|
} |
519 |
|
|
if (niterP) { |
520 |
|
|
niter = *niterP; |
521 |
|
|
if (!niter) { |
522 |
|
|
niter = nrrdNew(); |
523 |
|
|
*niterP = niter; |
524 |
|
|
} |
525 |
|
|
if (nrrdMaybeAlloc_nva(niter, nrrdTypeUInt, ndwi->dim-1, szOut+1)) { |
526 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't allocate iter output", me); |
527 |
|
|
airMopError(mop); return 1; |
528 |
|
|
} |
529 |
|
|
} else { |
530 |
|
|
niter = NULL; |
531 |
|
|
} |
532 |
|
|
ddwi = AIR_CALLOC(espec->imgNum, double); |
533 |
|
|
dwibuff = AIR_CALLOC(espec->imgNum, double); |
534 |
|
|
if (!(ddwi && dwibuff)) { |
535 |
|
|
biffAddf(TEN, "%s: couldn't allocate dwi buffers", me); |
536 |
|
|
airMopError(mop); return 1; |
537 |
|
|
} |
538 |
|
|
airMopAdd(mop, ddwi, airFree, airMopAlways); |
539 |
|
|
airMopAdd(mop, dwibuff, airFree, airMopAlways); |
540 |
|
|
|
541 |
|
|
/* set output */ |
542 |
|
|
if (_rng) { |
543 |
|
|
rng = _rng; |
544 |
|
|
} else { |
545 |
|
|
airRandMTStateGlobalInit(); |
546 |
|
|
rng = airRandMTStateGlobal; |
547 |
|
|
} |
548 |
|
|
numSamp = nrrdElementNumber(ndwi)/ndwi->axis[0].size; |
549 |
|
|
lup = nrrdDLookup[ndwi->type]; |
550 |
|
|
ins = nrrdDInsert[typeOut]; |
551 |
|
|
parm = AIR_CAST(char *, nparm->data); |
552 |
|
|
dwi = AIR_CAST(char *, ndwi->data); |
553 |
|
|
itersTaken = 0; |
554 |
|
|
if (verbose) { |
555 |
|
|
fprintf(stderr, "%s: fitting ... ", me); |
556 |
|
|
fflush(stderr); |
557 |
|
|
} |
558 |
|
|
for (II=0; II<numSamp; II++) { |
559 |
|
|
double cvf, convFrac=0; |
560 |
|
|
unsigned int ss, itak; |
561 |
|
|
if (verbose) { |
562 |
|
|
fprintf(stderr, "%s", airDoneStr(0, II, numSamp, doneStr)); |
563 |
|
|
fflush(stderr); |
564 |
|
|
} |
565 |
|
|
for (ii=0; ii<dwiNum; ii++) { |
566 |
|
|
ddwi[ii] = lup(dwi, ii); |
567 |
|
|
} |
568 |
|
|
sqeBest = DBL_MAX; /* forces at least one improvement */ |
569 |
|
|
for (ss=0; ss<starts; ss++) { |
570 |
|
|
/* can add other debugging conditions here */ |
571 |
|
|
fitVerbose = verbose; |
572 |
|
|
if (knownB0) { |
573 |
|
|
dparm[0] = tenExperSpecKnownB0Get(espec, ddwi); |
574 |
|
|
} |
575 |
|
|
model->rand(dparm, rng, knownB0); |
576 |
|
|
sqe = model->sqeFit(dparm, &cvf, &itak, |
577 |
|
|
espec, dwibuff, ddwi, |
578 |
|
|
dparm, knownB0, minIter, maxIter, |
579 |
|
|
convEps, fitVerbose); |
580 |
|
|
if (sqe <= sqeBest) { |
581 |
|
|
sqeBest = sqe; |
582 |
|
|
model->copy(dparmBest, dparm); |
583 |
|
|
itersTaken = itak; |
584 |
|
|
convFrac = cvf; |
585 |
|
|
} |
586 |
|
|
} |
587 |
|
|
for (ii=0; ii<saveParmNum; ii++) { |
588 |
|
|
ins(parm, ii, saveB0 ? dparmBest[ii] : dparmBest[ii+1]); |
589 |
|
|
} |
590 |
|
|
/* save things about fitting into nrrds */ |
591 |
|
|
if (nsqeP) { |
592 |
|
|
ins(nsqe->data, II, sqeBest); |
593 |
|
|
} |
594 |
|
|
if (nconvP) { |
595 |
|
|
nrrdDInsert[nrrdTypeDouble](nconv->data, II, convFrac); |
596 |
|
|
} |
597 |
|
|
if (niterP) { |
598 |
|
|
nrrdDInsert[nrrdTypeUInt](niter->data, II, itersTaken); |
599 |
|
|
} |
600 |
|
|
parm += saveParmNum*nrrdTypeSize[typeOut]; |
601 |
|
|
dwi += espec->imgNum*nrrdTypeSize[ndwi->type]; |
602 |
|
|
} |
603 |
|
|
if (verbose) { |
604 |
|
|
fprintf(stderr, "%s\n", airDoneStr(0, II, numSamp, doneStr)); |
605 |
|
|
} |
606 |
|
|
|
607 |
|
|
if (nrrdAxisInfoCopy(nparm, ndwi, axmap, NRRD_AXIS_INFO_SIZE_BIT) |
608 |
|
|
|| nrrdBasicInfoCopy(nparm, ndwi, |
609 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
610 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
611 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
612 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
613 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
614 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
615 |
|
|
| (nrrdStateKeyValuePairsPropagate |
616 |
|
|
? 0 |
617 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
618 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't copy axis or basic info", me); |
619 |
|
|
airMopError(mop); return 1; |
620 |
|
|
} |
621 |
|
|
if (nsqeP) { |
622 |
|
|
if (nrrdAxisInfoCopy(nsqe, ndwi, erraxmap, NRRD_AXIS_INFO_SIZE_BIT) |
623 |
|
|
|| nrrdBasicInfoCopy(nsqe, ndwi, |
624 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
625 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
626 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
627 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
628 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
629 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
630 |
|
|
| (nrrdStateKeyValuePairsPropagate |
631 |
|
|
? 0 |
632 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
633 |
|
|
biffMovef(TEN, NRRD, |
634 |
|
|
"%s: couldn't copy axis or basic info to error out", me); |
635 |
|
|
airMopError(mop); return 1; |
636 |
|
|
} |
637 |
|
|
} |
638 |
|
|
if (nconvP) { |
639 |
|
|
if (nrrdAxisInfoCopy(nconv, ndwi, erraxmap, NRRD_AXIS_INFO_SIZE_BIT) |
640 |
|
|
|| nrrdBasicInfoCopy(nconv, ndwi, |
641 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
642 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
643 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
644 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
645 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
646 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
647 |
|
|
| (nrrdStateKeyValuePairsPropagate |
648 |
|
|
? 0 |
649 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
650 |
|
|
biffMovef(TEN, NRRD, |
651 |
|
|
"%s: couldn't copy axis or basic info to conv out", me); |
652 |
|
|
airMopError(mop); return 1; |
653 |
|
|
} |
654 |
|
|
} |
655 |
|
|
if (niterP) { |
656 |
|
|
if (nrrdAxisInfoCopy(niter, ndwi, erraxmap, NRRD_AXIS_INFO_SIZE_BIT) |
657 |
|
|
|| nrrdBasicInfoCopy(niter, ndwi, |
658 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
659 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
660 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
661 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
662 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
663 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
664 |
|
|
| (nrrdStateKeyValuePairsPropagate |
665 |
|
|
? 0 |
666 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
667 |
|
|
biffMovef(TEN, NRRD, |
668 |
|
|
"%s: couldn't copy axis or basic info to iter out", me); |
669 |
|
|
airMopError(mop); return 1; |
670 |
|
|
} |
671 |
|
|
} |
672 |
|
|
lablen = (strlen(tenModelPrefixStr) |
673 |
|
|
+ (saveB0 ? strlen("B0+") : 0) |
674 |
|
|
+ strlen(model->name) |
675 |
|
|
+ 1); |
676 |
|
|
nparm->axis[0].label = AIR_CALLOC(lablen, char); |
677 |
|
|
sprintf(nparm->axis[0].label, "%s%s%s", |
678 |
|
|
tenModelPrefixStr, |
679 |
|
|
saveB0 ? "B0+" : "", |
680 |
|
|
model->name); |
681 |
|
|
|
682 |
|
|
airMopOkay(mop); |
683 |
|
|
return 0; |
684 |
|
|
} |
685 |
|
|
|
686 |
|
|
int |
687 |
|
|
tenModelNllFit(Nrrd *nparm, Nrrd **nnllP, |
688 |
|
|
const tenModel *model, |
689 |
|
|
const tenExperSpec *espec, const Nrrd *ndwi, |
690 |
|
|
int rician, double sigma, int knownB0) { |
691 |
|
|
|
692 |
|
|
AIR_UNUSED(nparm); |
693 |
|
|
AIR_UNUSED(nnllP); |
694 |
|
|
AIR_UNUSED(model); |
695 |
|
|
AIR_UNUSED(espec); |
696 |
|
|
AIR_UNUSED(ndwi); |
697 |
|
|
AIR_UNUSED(rician); |
698 |
|
|
AIR_UNUSED(sigma); |
699 |
|
|
AIR_UNUSED(knownB0); |
700 |
|
|
|
701 |
|
|
return 0; |
702 |
|
|
} |
703 |
|
|
|
704 |
|
|
/* |
705 |
|
|
** copy the B0 info if we have it |
706 |
|
|
** use the same type on the way out. |
707 |
|
|
*/ |
708 |
|
|
int |
709 |
|
|
tenModelConvert(Nrrd *nparmDst, int *convRetP, const tenModel *modelDst, |
710 |
|
|
const Nrrd *nparmSrc, const tenModel *_modelSrc) { |
711 |
|
|
static char me[]="tenModelConvert"; |
712 |
|
|
const tenModel *modelSrc; |
713 |
|
|
double *dpdst, *dpsrc, (*lup)(const void *v, size_t I), |
714 |
|
|
(*ins)(void *v, size_t I, double d); |
715 |
|
|
size_t szOut[NRRD_DIM_MAX], II, NN, tsize; |
716 |
|
|
airArray *mop; |
717 |
|
|
int withB0, axmap[NRRD_DIM_MAX], convRet=0; |
718 |
|
|
unsigned int parmNumDst, parmNumSrc, ii, lablen; |
719 |
|
|
const char *parmSrc; |
720 |
|
|
char *parmDst; |
721 |
|
|
|
722 |
|
|
if (!( nparmDst && modelDst && nparmSrc )) { |
723 |
|
|
biffAddf(TEN, "%s: got NULL pointer", me); |
724 |
|
|
return 1; |
725 |
|
|
} |
726 |
|
|
if (!_modelSrc) { |
727 |
|
|
/* we have to try to learn the source model from the nrrd */ |
728 |
|
|
if (tenModelFromAxisLearn(&modelSrc, &withB0, nparmSrc->axis + 0)) { |
729 |
|
|
biffAddf(TEN, "%s: couldn't learn model from src nparm", me); |
730 |
|
|
return 1; |
731 |
|
|
} |
732 |
|
|
} else { |
733 |
|
|
modelSrc = _modelSrc; |
734 |
|
|
if (modelSrc->parmNum == nparmSrc->axis[0].size) { |
735 |
|
|
withB0 = AIR_TRUE; |
736 |
|
|
} if (modelSrc->parmNum-1 == nparmSrc->axis[0].size) { |
737 |
|
|
withB0 = AIR_FALSE; |
738 |
|
|
} else { |
739 |
|
|
biffAddf(TEN, "%s: axis[0].size %u is not \"%s\" parmnum %u or 1 less", |
740 |
|
|
me, AIR_CAST(unsigned int, nparmSrc->axis[0].size), |
741 |
|
|
modelSrc->name, modelSrc->parmNum); |
742 |
|
|
return 1; |
743 |
|
|
} |
744 |
|
|
} |
745 |
|
|
|
746 |
|
|
mop = airMopNew(); |
747 |
|
|
dpdst = modelDst->alloc(); |
748 |
|
|
airMopAdd(mop, dpdst, airFree, airMopAlways); |
749 |
|
|
dpsrc = modelSrc->alloc(); |
750 |
|
|
airMopAdd(mop, dpsrc, airFree, airMopAlways); |
751 |
|
|
lup = nrrdDLookup[nparmSrc->type]; |
752 |
|
|
ins = nrrdDInsert[nparmSrc->type]; |
753 |
|
|
parmNumDst = withB0 ? modelDst->parmNum : modelDst->parmNum-1; |
754 |
|
|
parmNumSrc = nparmSrc->axis[0].size; |
755 |
|
|
for (ii=0; ii<nparmSrc->dim; ii++) { |
756 |
|
|
szOut[ii] = (!ii |
757 |
|
|
? parmNumDst |
758 |
|
|
: nparmSrc->axis[ii].size); |
759 |
|
|
axmap[ii] = (!ii |
760 |
|
|
? -1 |
761 |
|
|
: AIR_CAST(int, ii)); |
762 |
|
|
} |
763 |
|
|
if (nrrdMaybeAlloc_nva(nparmDst, nparmSrc->type, nparmSrc->dim, szOut)) { |
764 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't allocate output", me); |
765 |
|
|
airMopError(mop); return 1; |
766 |
|
|
} |
767 |
|
|
|
768 |
|
|
NN = nrrdElementNumber(nparmSrc)/nparmSrc->axis[0].size; |
769 |
|
|
tsize = nrrdTypeSize[nparmSrc->type]; |
770 |
|
|
parmSrc = AIR_CAST(char *, nparmSrc->data); |
771 |
|
|
parmDst = AIR_CAST(char *, nparmDst->data); |
772 |
|
|
if (!withB0) { |
773 |
|
|
dpsrc[0] = 0; |
774 |
|
|
} |
775 |
|
|
for (II=0; II<NN; II++) { |
776 |
|
|
for (ii=0; ii<parmNumSrc; ii++) { |
777 |
|
|
dpsrc[withB0 ? ii : ii+1] = lup(parmSrc, ii); |
778 |
|
|
} |
779 |
|
|
convRet = modelDst->convert(dpdst, dpsrc, modelSrc); |
780 |
|
|
if (2 == convRet) { /* HEY should be enum for this value */ |
781 |
|
|
biffAddf(TEN, "%s: error converting from \"%s\" to \"%s\"", me, |
782 |
|
|
modelSrc->name, modelDst->name); |
783 |
|
|
airMopError(mop); return 1; |
784 |
|
|
} |
785 |
|
|
for (ii=0; ii<parmNumDst; ii++) { |
786 |
|
|
ins(parmDst, ii, dpdst[withB0 ? ii : ii+1]); |
787 |
|
|
} |
788 |
|
|
parmSrc += parmNumSrc*tsize; |
789 |
|
|
parmDst += parmNumDst*tsize; |
790 |
|
|
} |
791 |
|
|
if (convRetP) { |
792 |
|
|
*convRetP = convRet; |
793 |
|
|
} |
794 |
|
|
|
795 |
|
|
if (nrrdAxisInfoCopy(nparmDst, nparmSrc, axmap, NRRD_AXIS_INFO_SIZE_BIT) |
796 |
|
|
|| nrrdBasicInfoCopy(nparmDst, nparmSrc, |
797 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
798 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
799 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
800 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
801 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
802 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
803 |
|
|
| (nrrdStateKeyValuePairsPropagate |
804 |
|
|
? 0 |
805 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
806 |
|
|
biffMovef(TEN, NRRD, "%s: couldn't copy axis or basic info", me); |
807 |
|
|
airMopError(mop); return 1; |
808 |
|
|
} |
809 |
|
|
/* HEY: COPY AND PASTE! from above. perhaps make helper functions? */ |
810 |
|
|
lablen = (strlen(tenModelPrefixStr) |
811 |
|
|
+ (withB0 ? strlen("B0+") : 0) |
812 |
|
|
+ strlen(modelDst->name) |
813 |
|
|
+ 1); |
814 |
|
|
nparmDst->axis[0].label = AIR_CALLOC(lablen, char); |
815 |
|
|
sprintf(nparmDst->axis[0].label, "%s%s%s", |
816 |
|
|
tenModelPrefixStr, |
817 |
|
|
withB0 ? "B0+" : "", |
818 |
|
|
modelDst->name); |
819 |
|
|
|
820 |
|
|
airMopOkay(mop); |
821 |
|
|
return 0; |
822 |
|
|
} |