/* ******** tenGradientJitter ** ** moves all gradients by amount dist on tangent plane, in a random ** direction, and then renormalizes. The distance is a fraction ** of the ideal edge length (via tenGradientIdealEdge) */ int tenGradientJitter(Nrrd *nout, const Nrrd *nin, double dist) { static const char me[]="tenGradientJitter"; double *grad, perp0[3], perp1[3], len, theta, cc, ss, edge; unsigned int gi, num; if (nrrdConvert(nout, nin, nrrdTypeDouble)) { biffMovef(TEN, NRRD, "%s: trouble converting input to double", me); return 1; } if (tenGradientCheck(nout, nrrdTypeDouble, 3)) { biffAddf(TEN, "%s: didn't get valid gradients", me); return 1; } grad = AIR_CAST(double*, nout->data); num = AIR_UINT(nout->axis[1].size); /* HEY: possible confusion between single and not */ edge = tenGradientIdealEdge(num, AIR_FALSE); for (gi=0; gi<num; gi++) { ELL_3V_NORM(grad, grad, len); ell_3v_perp_d(perp0, grad); ELL_3V_CROSS(perp1, perp0, grad); theta = AIR_AFFINE(0, airDrandMT(), 1, 0, 2*AIR_PI); cc = dist*edge*cos(theta); ss = dist*edge*sin(theta); ELL_3V_SCALE_ADD3(grad, 1.0, grad, cc, perp0, ss, perp1); ELL_3V_NORM(grad, grad, len); grad += 3; } return 0; }
int tend_msimMain(int argc, const char **argv, const char *me, hestParm *hparm) { int pret; hestOpt *hopt = NULL; char *perr, *err; airArray *mop; tenExperSpec *espec; const tenModel *model; int E, seed, keyValueSet, outType, plusB0, insertB0; Nrrd *nin, *nT2, *_ngrad, *ngrad, *nout; char *outS, *modS; double bval, sigma; /* maybe this can go in tend.c, but for some reason its explicitly set to AIR_FALSE there */ hparm->elideSingleOtherDefault = AIR_TRUE; hestOptAdd(&hopt, "sigma", "sigma", airTypeDouble, 1, 1, &sigma, "0.0", "Gaussian/Rician noise parameter"); hestOptAdd(&hopt, "seed", "seed", airTypeInt, 1, 1, &seed, "42", "seed value for RNG which creates noise"); hestOptAdd(&hopt, "g", "grad list", airTypeOther, 1, 1, &_ngrad, NULL, "gradient list, one row per diffusion-weighted image", NULL, NULL, nrrdHestNrrd); hestOptAdd(&hopt, "b0", "b0 image", airTypeOther, 1, 1, &nT2, "", "reference non-diffusion-weighted (\"B0\") image, which " "may be needed if it isn't part of give model param image", NULL, NULL, nrrdHestNrrd); hestOptAdd(&hopt, "i", "model image", airTypeOther, 1, 1, &nin, "-", "input model image", NULL, NULL, nrrdHestNrrd); hestOptAdd(&hopt, "m", "model", airTypeString, 1, 1, &modS, NULL, "model with which to simulate DWIs, which must be specified if " "it is not indicated by the first axis in input model image."); hestOptAdd(&hopt, "ib0", "bool", airTypeBool, 1, 1, &insertB0, "false", "insert a non-DW B0 image at the beginning of the experiment " "specification (useful if the given gradient list doesn't " "already have one) and hence also insert a B0 image at the " "beginning of the output simulated DWIs"); hestOptAdd(&hopt, "b", "b", airTypeDouble, 1, 1, &bval, "1000", "b value for simulated scan"); hestOptAdd(&hopt, "kvp", "bool", airTypeBool, 1, 1, &keyValueSet, "true", "generate key/value pairs in the NRRD header corresponding " "to the input b-value and gradients."); hestOptAdd(&hopt, "t", "type", airTypeEnum, 1, 1, &outType, "float", "output type of DWIs", NULL, nrrdType); hestOptAdd(&hopt, "o", "nout", airTypeString, 1, 1, &outS, "-", "output dwis"); mop = airMopNew(); airMopAdd(mop, hopt, (airMopper)hestOptFree, airMopAlways); USAGE(_tend_msimInfoL); PARSE(); airMopAdd(mop, hopt, (airMopper)hestParseFree, airMopAlways); nout = nrrdNew(); airMopAdd(mop, nout, (airMopper)nrrdNuke, airMopAlways); espec = tenExperSpecNew(); airMopAdd(mop, espec, (airMopper)tenExperSpecNix, airMopAlways); airSrandMT(seed); if (nrrdTypeDouble == _ngrad->type) { ngrad = _ngrad; } else { ngrad = nrrdNew(); airMopAdd(mop, ngrad, (airMopper)nrrdNuke, airMopAlways); if (nrrdConvert(ngrad, _ngrad, nrrdTypeDouble)) { airMopAdd(mop, err=biffGetDone(NRRD), airFree, airMopAlways); fprintf(stderr, "%s: trouble converting grads to %s:\n%s\n", me, airEnumStr(nrrdType, nrrdTypeDouble), err); airMopError(mop); return 1; } } plusB0 = AIR_FALSE; if (airStrlen(modS)) { if (tenModelParse(&model, &plusB0, AIR_FALSE, modS)) { airMopAdd(mop, err=biffGetDone(TEN), airFree, airMopAlways); fprintf(stderr, "%s: trouble parsing model \"%s\":\n%s\n", me, modS, err); airMopError(mop); return 1; } } else if (tenModelFromAxisLearnPossible(nin->axis + 0)) { if (tenModelFromAxisLearn(&model, &plusB0, nin->axis + 0)) { airMopAdd(mop, err=biffGetDone(TEN), airFree, airMopAlways); fprintf(stderr, "%s: trouble parsing model frmo axis 0 of nin:\n%s\n", me, err); airMopError(mop); return 1; } } else { fprintf(stderr, "%s: need model specified either via \"-m\" or input " "model image axis 0\n", me); airMopError(mop); return 1; } /* we have learned plusB0, but we don't actually need it; either: it describes the given model param image (which is courteous but not necessary since the logic inside tenModeSimulate will see this), or: it is trying to say something about including B0 amongst model parameters (which isn't actually meaningful in the context of simulated DWIs */ E = 0; if (!E) E |= tenGradientCheck(ngrad, nrrdTypeDouble, 1); if (!E) E |= tenExperSpecGradSingleBValSet(espec, insertB0, bval, AIR_CAST(const double *, ngrad->data), ngrad->axis[1].size); if (!E) E |= tenModelSimulate(nout, outType, espec, model, nT2, nin, keyValueSet); if (E) { airMopAdd(mop, err=biffGetDone(TEN), airFree, airMopAlways); fprintf(stderr, "%s: trouble:\n%s\n", me, err); airMopError(mop); return 1; } if (nrrdSave(outS, nout, NULL)) { airMopAdd(mop, err=biffGetDone(NRRD), airFree, airMopAlways); fprintf(stderr, "%s: trouble writing:\n%s\n", me, err); airMopError(mop); return 1; } airMopOkay(mop); return 0; }
/* ******** tenGradientDistribute ** ** Takes the given list of gradients, normalizes their lengths, ** optionally jitters their positions, does point repulsion, and then ** (optionally) selects a combination of directions with minimum vector sum. ** ** The complicated part of this is the point repulsion, which uses a ** gradient descent with variable set size. The progress of the system ** is measured by decrease in potential (when its measurement doesn't ** overflow to infinity) or an increase in the minimum angle. When a ** step results in negative progress, the step size is halved, and the ** iteration is attempted again. Based on the observation that at ** some points the step size must be made very small to get progress, ** the step size is cautiously increased ("nudged") at every ** iteration, to try to avoid using an overly small step. The amount ** by which the step is nudged is halved everytime the step is halved, ** to avoid endless cycling through step sizes. */ int tenGradientDistribute(Nrrd *nout, const Nrrd *nin, tenGradientParm *tgparm) { static const char me[]="tenGradientDistribute"; char filename[AIR_STRLEN_SMALL]; unsigned int ii, num, iter, oldIdx, newIdx, edgeShrink; airArray *mop; Nrrd *npos[2]; double *pos, len, meanVelocity, pot, potNew, potD, edge, edgeMin, angle, angleNew; int E; if (!nout || tenGradientCheck(nin, nrrdTypeUnknown, 2) || !tgparm) { biffAddf(TEN, "%s: got NULL pointer or invalid input", me); return 1; } num = AIR_UINT(nin->axis[1].size); mop = airMopNew(); npos[0] = nrrdNew(); npos[1] = nrrdNew(); airMopAdd(mop, npos[0], (airMopper)nrrdNuke, airMopAlways); airMopAdd(mop, npos[1], (airMopper)nrrdNuke, airMopAlways); if (nrrdConvert(npos[0], nin, nrrdTypeDouble) || nrrdConvert(npos[1], nin, nrrdTypeDouble)) { biffMovef(TEN, NRRD, "%s: trouble allocating temp buffers", me); airMopError(mop); return 1; } pos = (double*)(npos[0]->data); for (ii=0; ii<num; ii++) { ELL_3V_NORM(pos, pos, len); pos += 3; } if (tgparm->jitter) { if (tenGradientJitter(npos[0], npos[0], tgparm->jitter)) { biffAddf(TEN, "%s: problem jittering input", me); airMopError(mop); return 1; } } /* initialize things prior to first iteration; have to make sure that loop body tests pass 1st time around */ meanVelocity = 2*tgparm->minVelocity; potD = -2*tgparm->minPotentialChange; oldIdx = 0; newIdx = 1; tgparm->step = tgparm->initStep; tgparm->nudge = 0.1; tenGradientMeasure(&pot, &angle, NULL, npos[oldIdx], tgparm, AIR_TRUE); for (iter = 0; ((!!tgparm->minIteration && iter < tgparm->minIteration) || (iter < tgparm->maxIteration && (!tgparm->minPotentialChange || !AIR_EXISTS(potD) || -potD > tgparm->minPotentialChange) && (!tgparm->minVelocity || meanVelocity > tgparm->minVelocity) && tgparm->step > FLT_MIN)); iter++) { /* copy positions from old to new */ memcpy(npos[newIdx]->data, npos[oldIdx]->data, 3*num*sizeof(double)); edge = tenGradientIdealEdge(num, tgparm->single); edgeShrink = 0; /* try to do a position update, which will fail if repulsion values explode, from having an insufficiently small edge normalization, so retry with smaller edge next time */ do { E = _tenGradientUpdate(&meanVelocity, &edgeMin, npos[newIdx], edge, tgparm); if (E) { if (edgeShrink > tgparm->maxEdgeShrink) { biffAddf(TEN, "%s: %u > %u edge shrinks (%g), update still failed", me, edgeShrink, tgparm->maxEdgeShrink, edge); airMopError(mop); return 1; } edgeShrink++; /* re-initialize positions (HEY ugly code logic) */ memcpy(npos[newIdx]->data, npos[oldIdx]->data, 3*num*sizeof(double)); edge = edgeMin; } } while (E); tenGradientMeasure(&potNew, &angleNew, NULL, npos[newIdx], tgparm, AIR_TRUE); if ((AIR_EXISTS(pot) && AIR_EXISTS(potNew) && potNew <= pot) || angleNew >= angle) { /* there was progress of some kind, either through potential decrease, or angle increase */ potD = 2*(potNew - pot)/(potNew + pot); if (!(iter % tgparm->report) && tgparm->verbose) { fprintf(stderr, "%s(%d): . . . . . . step = %g, edgeShrink = %u\n" " velo = %g<>%g, phi = %g ~ %g<>%g, angle = %g ~ %g\n", me, iter, tgparm->step, edgeShrink, meanVelocity, tgparm->minVelocity, pot, potD, tgparm->minPotentialChange, angle, angleNew - angle); } if (tgparm->snap && !(iter % tgparm->snap)) { sprintf(filename, "%05d.nrrd", iter/tgparm->snap); if (tgparm->verbose) { fprintf(stderr, "%s(%d): . . . . . . saving %s\n", me, iter, filename); } if (nrrdSave(filename, npos[newIdx], NULL)) { char *serr; serr = biffGetDone(NRRD); if (tgparm->verbose) { /* perhaps shouldn't have this check */ fprintf(stderr, "%s: iter=%d, couldn't save snapshot:\n%s" "continuing ...\n", me, iter, serr); } free(serr); } } tgparm->step *= 1 + tgparm->nudge; tgparm->step = AIR_MIN(tgparm->initStep, tgparm->step); pot = potNew; angle = angleNew; /* swap buffers */ newIdx = 1 - newIdx; oldIdx = 1 - oldIdx; } else { /* oops, did not make progress; back off and try again */ if (tgparm->verbose) { fprintf(stderr, "%s(%d): ######## step %g --> %g\n" " phi = %g --> %g ~ %g, angle = %g --> %g\n", me, iter, tgparm->step, tgparm->step/2, pot, potNew, potD, angle, angleNew); } tgparm->step /= 2; tgparm->nudge /= 2; } } /* when the for-loop test fails, we stop before computing the next iteration (which starts with copying from npos[oldIdx] to npos[newIdx]) ==> the final results are in npos[oldIdx] */ if (tgparm->verbose) { fprintf(stderr, "%s: .......................... done distribution:\n" " (%d && %d) || (%d \n" " && (%d || %d || %d) \n" " && (%d || %d) \n" " && %d) is false\n", me, !!tgparm->minIteration, iter < tgparm->minIteration, iter < tgparm->maxIteration, !tgparm->minPotentialChange, !AIR_EXISTS(potD), AIR_ABS(potD) > tgparm->minPotentialChange, !tgparm->minVelocity, meanVelocity > tgparm->minVelocity, tgparm->step > FLT_MIN); fprintf(stderr, " iter=%d, velo = %g<>%g, phi = %g ~ %g<>%g;\n", iter, meanVelocity, tgparm->minVelocity, pot, potD, tgparm->minPotentialChange); fprintf(stderr, " minEdge = %g; idealEdge = %g\n", 2*sin(angle/2), tenGradientIdealEdge(num, tgparm->single)); } tenGradientMeasure(&pot, NULL, NULL, npos[oldIdx], tgparm, AIR_FALSE); tgparm->potential = pot; tenGradientMeasure(&pot, &angle, &edge, npos[oldIdx], tgparm, AIR_TRUE); tgparm->potentialNorm = pot; tgparm->angle = angle; tgparm->edge = edge; tgparm->itersUsed = iter; if ((tgparm->minMeanImprovement || tgparm->minMean) && !tgparm->single) { if (tgparm->verbose) { fprintf(stderr, "%s: optimizing balance:\n", me); } if (tenGradientBalance(nout, npos[oldIdx], tgparm)) { biffAddf(TEN, "%s: failed to minimize vector sum of gradients", me); airMopError(mop); return 1; } if (tgparm->verbose) { fprintf(stderr, "%s: .......................... done balancing.\n", me); } } else { if (tgparm->verbose) { fprintf(stderr, "%s: .......................... (no balancing)\n", me); } if (nrrdConvert(nout, npos[oldIdx], nrrdTypeDouble)) { biffMovef(TEN, NRRD, "%s: couldn't set output", me); airMopError(mop); return 1; } } airMopOkay(mop); return 0; }
/* ** parties until the gradients settle down */ int tenGradientBalance(Nrrd *nout, const Nrrd *nin, tenGradientParm *tgparm) { static const char me[]="tenGradientBalance"; double len, lastLen, improv; airRandMTState *rstate; Nrrd *ncopy; unsigned int iter, maxIter; int done; airArray *mop; if (!nout || tenGradientCheck(nin, nrrdTypeUnknown, 2) || !tgparm) { biffAddf(TEN, "%s: got NULL pointer (%p,%p) or invalid nin", me, AIR_VOIDP(nout), AIR_VOIDP(tgparm)); return 1; } if (nrrdConvert(nout, nin, nrrdTypeDouble)) { biffMovef(TEN, NRRD, "%s: can't initialize output with input", me); return 1; } mop = airMopNew(); ncopy = nrrdNew(); airMopAdd(mop, ncopy, (airMopper)nrrdNuke, airMopAlways); rstate = airRandMTStateNew(tgparm->seed); airMopAdd(mop, rstate, (airMopper)airRandMTStateNix, airMopAlways); /* HEY: factor of 100 is an approximate hack */ maxIter = 100*tgparm->maxIteration; lastLen = 1.0; done = AIR_FALSE; do { iter = 0; do { iter++; len = party(nout, rstate); } while (len > lastLen && iter < maxIter); if (iter >= maxIter) { if (tgparm->verbose) { fprintf(stderr, "%s: stopping at max iter %u\n", me, maxIter); } if (nrrdCopy(nout, ncopy)) { biffMovef(TEN, NRRD, "%s: trouble copying", me); airMopError(mop); return 1; } done = AIR_TRUE; } else { if (nrrdCopy(ncopy, nout)) { biffMovef(TEN, NRRD, "%s: trouble copying", me); airMopError(mop); return 1; } improv = lastLen - len; lastLen = len; if (tgparm->verbose) { fprintf(stderr, "%s: (iter %u) improvement: %g (mean length = %g)\n", me, iter, improv, len); } done = (improv <= tgparm->minMeanImprovement || len < tgparm->minMean); } } while (!done); airMopOkay(mop); return 0; }
int main(int argc, char *argv[]) { char *me, *err; hestOpt *hopt=NULL; airArray *mop; char *outTenS, *outCovarS, *outRmvS; int seed, E; unsigned int NN; Nrrd *_ninTen, *ninTen, *ngrad, *_ninB0, *ninB0, *nmask, *noutCovar, *noutTen, *noutRmv, *ntbuff; float sigma, bval; size_t sizeX, sizeY, sizeZ; tenEstimateContext *tec; int axmap[NRRD_DIM_MAX], randrot; mop = airMopNew(); me = argv[0]; hestOptAdd(&hopt, "i", "ten", airTypeOther, 1, 1, &_ninTen, NULL, "input tensor volume", NULL, NULL, nrrdHestNrrd); hestOptAdd(&hopt, "n", "#sim", airTypeUInt, 1, 1, &NN, "100", "number of simulations to run"); hestOptAdd(&hopt, "seed", "seed", airTypeInt, 1, 1, &seed, "42", "seed value for RNG which creates noise"); hestOptAdd(&hopt, "r", "reference field", airTypeOther, 1, 1, &_ninB0, NULL, "reference anatomical scan, with no diffusion weighting", NULL, NULL, nrrdHestNrrd); hestOptAdd(&hopt, "rr", NULL, airTypeOther, 0, 0, &randrot, NULL, "randomize gradient set orientation"); hestOptAdd(&hopt, "g", "grad list", airTypeOther, 1, 1, &ngrad, "", "gradient list, one row per diffusion-weighted image", NULL, NULL, nrrdHestNrrd); hestOptAdd(&hopt, "b", "b", airTypeFloat, 1, 1, &bval, "1000", "b value for simulated scan"); hestOptAdd(&hopt, "sigma", "sigma", airTypeFloat, 1, 1, &sigma, "0.0", "Rician noise parameter"); hestOptAdd(&hopt, "ot", "filename", airTypeString, 1, 1, &outTenS, "tout.nrrd", "file to write output tensor nrrd to"); hestOptAdd(&hopt, "oc", "filename", airTypeString, 1, 1, &outCovarS, "cout.nrrd", "file to write output covariance nrrd to"); hestOptAdd(&hopt, "or", "filename", airTypeString, 1, 1, &outRmvS, "rout.nrrd", "file to write output R_i means, variances to"); hestParseOrDie(hopt, argc-1, argv+1, NULL, me, info, AIR_TRUE, AIR_TRUE, AIR_TRUE); airMopAdd(mop, hopt, (airMopper)hestOptFree, airMopAlways); airMopAdd(mop, hopt, (airMopper)hestParseFree, airMopAlways); if (tenGradientCheck(ngrad, nrrdTypeDefault, 7)) { airMopAdd(mop, err = biffGetDone(TEN), airFree, airMopAlways); fprintf(stderr, "%s: problem with gradient list:\n%s\n", me, err); airMopError(mop); return 1; } if (tenTensorCheck(_ninTen, nrrdTypeDefault, AIR_TRUE, AIR_TRUE)) { airMopAdd(mop, err = biffGetDone(TEN), airFree, airMopAlways); fprintf(stderr, "%s: didn't like input:\n%s\n", me, err); airMopError(mop); return 1; } sizeX = _ninTen->axis[1].size; sizeY = _ninTen->axis[2].size; sizeZ = _ninTen->axis[3].size; if (!(3 == _ninB0->dim && sizeX == _ninB0->axis[0].size && sizeY == _ninB0->axis[1].size && sizeZ == _ninB0->axis[2].size)) { fprintf(stderr, "%s: given B0 (%u-D) volume not 3-D " _AIR_SIZE_T_CNV "x" _AIR_SIZE_T_CNV "x" _AIR_SIZE_T_CNV, me, _ninB0->dim, sizeX, sizeY, sizeZ); airMopError(mop); return 1; } ninTen = nrrdNew(); airMopAdd(mop, ninTen, (airMopper)nrrdNuke, airMopOnError); nmask = nrrdNew(); airMopAdd(mop, nmask, (airMopper)nrrdNuke, airMopOnError); ninB0 = nrrdNew(); airMopAdd(mop, ninB0, (airMopper)nrrdNuke, airMopOnError); noutCovar = nrrdNew(); airMopAdd(mop, noutCovar, (airMopper)nrrdNuke, airMopOnError); noutTen = nrrdNew(); airMopAdd(mop, noutTen, (airMopper)nrrdNuke, airMopOnError); noutRmv = nrrdNew(); airMopAdd(mop, noutRmv, (airMopper)nrrdNuke, airMopOnError); ntbuff = nrrdNew(); airMopAdd(mop, ntbuff, (airMopper)nrrdNuke, airMopOnError); if (nrrdConvert(ninTen, _ninTen, nrrdTypeDouble) || nrrdSlice(nmask, ninTen, 0, 0) || nrrdConvert(ninB0, _ninB0, nrrdTypeDouble) || nrrdMaybeAlloc_va(noutTen, nrrdTypeDouble, 4, AIR_CAST(size_t, 7), sizeX, sizeY, sizeZ) || nrrdMaybeAlloc_va(noutCovar, nrrdTypeDouble, 4, AIR_CAST(size_t, 21), sizeX, sizeY, sizeZ) || nrrdMaybeAlloc_va(noutRmv, nrrdTypeDouble, 4, AIR_CAST(size_t, 6), sizeX, sizeY, sizeZ) || nrrdMaybeAlloc_va(ntbuff, nrrdTypeDouble, 2, AIR_CAST(size_t, 7), NN)) { airMopAdd(mop, err=biffGetDone(NRRD), airFree, airMopAlways); fprintf(stderr, "%s: trouble setting up tec:\n%s\n", me, err); airMopError(mop); return 1; } tec = tenEstimateContextNew(); airMopAdd(mop, tec, (airMopper)tenEstimateContextNix, airMopAlways); E = 0; if (!E) E |= tenEstimateMethodSet(tec, tenEstimate1MethodLLS); if (!E) E |= tenEstimateValueMinSet(tec, 0.000000001); if (!E) E |= tenEstimateGradientsSet(tec, ngrad, bval, AIR_TRUE); if (!E) E |= tenEstimateThresholdSet(tec, 0, 0); if (!E) E |= tenEstimateUpdate(tec); if (E) { airMopAdd(mop, err=biffGetDone(TEN), airFree, airMopAlways); fprintf(stderr, "%s: trouble setting up tec:\n%s\n", me, err); airMopError(mop); return 1; } airSrandMT(seed); fprintf(stderr, "!%s: randrot = %d\n", me, randrot); if (1) { unsigned int II; unsigned int nsamp; double *inTen, *outTen, *outCovar, *outRmv, *dwibuff, (*lup)(const void *, size_t); char doneStr[AIR_STRLEN_SMALL]; dwibuff = AIR_CAST(double *, calloc(ngrad->axis[1].size, sizeof(double))); airMopAdd(mop, dwibuff, airFree, airMopAlways); nsamp = sizeX*sizeY*sizeZ; inTen = AIR_CAST(double *, ninTen->data); lup = nrrdDLookup[nrrdTypeDouble]; outTen = AIR_CAST(double *, noutTen->data); outCovar = AIR_CAST(double *, noutCovar->data); outRmv = AIR_CAST(double *, noutRmv->data); fprintf(stderr, "!%s: simulating ... ", me); fflush(stderr); for (II=0; II<nsamp; II++) { if (!(II % sizeX)) { fprintf(stderr, "%s", airDoneStr(0, II, nsamp, doneStr)); fflush(stderr); } if (csimDo(outTen, outCovar, outRmv + 0, outRmv + 3, ntbuff, tec, dwibuff, sigma, bval, lup(ninB0->data, II), NN, randrot, inTen)) { airMopAdd(mop, err=biffGetDone(TEN), airFree, airMopAlways); fprintf(stderr, "%s: trouble:\n%s\n", me, err); airMopError(mop); return 1; } inTen += 7; outTen += 7; outCovar += 21; outRmv += 6; } fprintf(stderr, "%s\n", airDoneStr(0, II, nsamp, doneStr)); }