GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/pull/volumePull.c Lines: 0 281 0.0 %
Date: 2017-05-26 Branches: 0 184 0.0 %

Line Branch Exec Source
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
25
#include "pull.h"
26
#include "privatePull.h"
27
28
pullVolume *
29
pullVolumeNew() {
30
  pullVolume *vol;
31
32
  vol = AIR_CAST(pullVolume *, calloc(1, sizeof(pullVolume)));
33
  if (vol) {
34
    vol->verbose = 0;
35
    vol->name = NULL;
36
    vol->kind = NULL;
37
    vol->ninSingle = NULL;
38
    vol->ninScale = NULL;
39
    vol->scaleNum = 0;
40
    vol->scalePos = NULL;
41
    vol->scaleDerivNorm = AIR_FALSE;
42
    vol->scaleDerivNormBias = 0.0;
43
    vol->ksp00 = nrrdKernelSpecNew();
44
    vol->ksp11 = nrrdKernelSpecNew();
45
    vol->ksp22 = nrrdKernelSpecNew();
46
    vol->kspSS = nrrdKernelSpecNew();
47
    GAGE_QUERY_RESET(vol->pullValQuery);
48
    vol->gctx = NULL;
49
    vol->gpvl = NULL;
50
    vol->gpvlSS = NULL;
51
    /* this is turned OFF in volumes that have infos that aren't seedthresh,
52
       see pullInfoSpecAdd() */
53
    vol->seedOnly = AIR_TRUE;
54
    vol->forSeedPreThresh = AIR_FALSE;
55
  }
56
  return vol;
57
}
58
59
pullVolume *
60
pullVolumeNix(pullVolume *vol) {
61
62
  if (vol) {
63
    airFree(vol->name);
64
    airFree(vol->scalePos);
65
    vol->ksp00 = nrrdKernelSpecNix(vol->ksp00);
66
    vol->ksp11 = nrrdKernelSpecNix(vol->ksp11);
67
    vol->ksp22 = nrrdKernelSpecNix(vol->ksp22);
68
    vol->kspSS = nrrdKernelSpecNix(vol->kspSS);
69
    if (vol->gctx) {
70
      vol->gctx = gageContextNix(vol->gctx);
71
    }
72
    airFree(vol->gpvlSS);
73
    airFree(vol);
74
  }
75
  return NULL;
76
}
77
78
/*
79
** used to set all the fields of pullVolume at once, including the
80
** gageContext inside the pullVolume
81
**
82
** OLD description ...
83
** used both for top-level volumes in the pullContext (pctx->vol[i])
84
** in which case pctx is non-NULL,
85
** and for the per-task volumes (task->vol[i]),
86
** in which case pctx is NULL
87
** ...................
88
*/
89
int
90
_pullVolumeSet(const pullContext *pctx, int taskCopy, pullVolume *vol,
91
               const gageKind *kind,
92
               int verbose, const char *name,
93
               const Nrrd *ninSingle,
94
               const Nrrd *const *ninScale,
95
               double *scalePos,
96
               unsigned int ninNum,
97
               int scaleDerivNorm,
98
               double scaleDerivNormBias,
99
               const NrrdKernelSpec *ksp00,
100
               const NrrdKernelSpec *ksp11,
101
               const NrrdKernelSpec *ksp22,
102
               const NrrdKernelSpec *kspSS) {
103
  static const char me[]="_pullVolumeSet";
104
  int E;
105
  unsigned int vi;
106
107
  if (!( vol && kind && airStrlen(name) && ksp00 && ksp11 && ksp22 )) {
108
    biffAddf(PULL, "%s: got NULL pointer", me);
109
    return 1;
110
  }
111
  if (!ninSingle) {
112
    biffAddf(PULL, "%s: needed non-NULL ninSingle", me);
113
    return 1;
114
  }
115
  if (!taskCopy) {
116
    for (vi=0; vi<pctx->volNum; vi++) {
117
      if (pctx->vol[vi] == vol) {
118
        biffAddf(PULL, "%s: already got vol %p as vol[%u]", me,
119
                 AIR_VOIDP(vol), vi);
120
        return 1;
121
      }
122
    }
123
  }
124
  if (ninNum) {
125
    if (!( ninNum >= 2 )) {
126
      biffAddf(PULL, "%s: need at least 2 volumes (not %u)", me, ninNum);
127
      return 1;
128
    }
129
    if (!scalePos) {
130
      biffAddf(PULL, "%s: need non-NULL scalePos with ninNum %u", me, ninNum);
131
      return 1;
132
    }
133
    if (!ninScale) {
134
      biffAddf(PULL, "%s: need non-NULL ninScale with ninNum %u", me, ninNum);
135
      return 1;
136
    }
137
  }
138
139
  vol->verbose = verbose;
140
  vol->kind = kind;
141
  vol->gctx = gageContextNew();
142
  gageParmSet(vol->gctx, gageParmVerbose,
143
              vol->verbose > 0 ? vol->verbose - 1 : 0);
144
  gageParmSet(vol->gctx, gageParmRenormalize, AIR_FALSE);
145
  /* because we're likely only using accurate kernels */
146
  gageParmSet(vol->gctx, gageParmStackNormalizeRecon, AIR_FALSE);
147
  vol->scaleDerivNorm = scaleDerivNorm;
148
  gageParmSet(vol->gctx, gageParmStackNormalizeDeriv, scaleDerivNorm);
149
  vol->scaleDerivNormBias = scaleDerivNormBias;
150
  gageParmSet(vol->gctx, gageParmStackNormalizeDerivBias,
151
              scaleDerivNormBias);
152
  gageParmSet(vol->gctx, gageParmTwoDimZeroZ, pctx->flag.zeroZ);
153
  gageParmSet(vol->gctx, gageParmCheckIntegrals, AIR_TRUE);
154
  E = 0;
155
  if (!E) E |= gageKernelSet(vol->gctx, gageKernel00,
156
                             ksp00->kernel, ksp00->parm);
157
  if (!E) E |= gageKernelSet(vol->gctx, gageKernel11,
158
                             ksp11->kernel, ksp11->parm);
159
  if (!E) E |= gageKernelSet(vol->gctx, gageKernel22,
160
                             ksp22->kernel, ksp22->parm);
161
  if (ninScale) {
162
    if (!kspSS) {
163
      biffAddf(PULL, "%s: got NULL kspSS", me);
164
      return 1;
165
    }
166
    gageParmSet(vol->gctx, gageParmStackUse, AIR_TRUE);
167
    if (!E) E |= !(vol->gpvl = gagePerVolumeNew(vol->gctx, ninSingle, kind));
168
    vol->gpvlSS = AIR_CAST(gagePerVolume **,
169
                           calloc(ninNum, sizeof(gagePerVolume *)));
170
    if (!E) E |= gageStackPerVolumeNew(vol->gctx, vol->gpvlSS,
171
                                       ninScale, ninNum, kind);
172
    if (!E) E |= gageStackPerVolumeAttach(vol->gctx, vol->gpvl, vol->gpvlSS,
173
                                          scalePos, ninNum);
174
    if (!E) E |= gageKernelSet(vol->gctx, gageKernelStack,
175
                               kspSS->kernel, kspSS->parm);
176
  } else {
177
    vol->gpvlSS = NULL;
178
    if (!E) E |= !(vol->gpvl = gagePerVolumeNew(vol->gctx, ninSingle, kind));
179
    if (!E) E |= gagePerVolumeAttach(vol->gctx, vol->gpvl);
180
  }
181
  if (E) {
182
    biffMovef(PULL, GAGE, "%s: trouble (%s %s)", me,
183
              ninSingle ? "ninSingle" : "",
184
              ninScale ? "ninScale" : "");
185
    return 1;
186
  }
187
  gageQueryReset(vol->gctx, vol->gpvl);
188
  /* the query is the single thing remaining unset in the gageContext */
189
190
  vol->name = airStrdup(name);
191
  if (!vol->name) {
192
    biffAddf(PULL, "%s: couldn't strdup name (len %u)", me,
193
             AIR_CAST(unsigned int, airStrlen(name)));
194
    return 1;
195
  }
196
  if (vol->verbose) {
197
    printf("%s: ---- vol=%p, name = %p = |%s|\n", me, AIR_VOIDP(vol),
198
           AIR_VOIDP(vol->name), vol->name);
199
    if (0 != vol->scaleDerivNormBias) {
200
      printf("%s: ---- scale deriv norm bias = %g\n", me,
201
             vol->scaleDerivNormBias);
202
    }
203
  }
204
  nrrdKernelSpecSet(vol->ksp00, ksp00->kernel, ksp00->parm);
205
  nrrdKernelSpecSet(vol->ksp11, ksp11->kernel, ksp11->parm);
206
  nrrdKernelSpecSet(vol->ksp22, ksp22->kernel, ksp22->parm);
207
  if (ninScale) {
208
    vol->ninSingle = ninSingle;
209
    vol->ninScale = ninScale;
210
    vol->scaleNum = ninNum;
211
    vol->scalePos = AIR_CAST(double *, calloc(ninNum, sizeof(double)));
212
    if (!vol->scalePos) {
213
      biffAddf(PULL, "%s: couldn't calloc scalePos", me);
214
      return 1;
215
    }
216
    for (vi=0; vi<ninNum; vi++) {
217
      vol->scalePos[vi] = scalePos[vi];
218
    }
219
    nrrdKernelSpecSet(vol->kspSS, kspSS->kernel, kspSS->parm);
220
  } else {
221
    vol->ninSingle = ninSingle;
222
    vol->ninScale = NULL;
223
    vol->scaleNum = 0;
224
    /* leave kspSS as is (unset) */
225
  }
226
227
  return 0;
228
}
229
230
/*
231
** the effect is to give pctx ownership of the vol
232
*/
233
int
234
pullVolumeSingleAdd(pullContext *pctx,
235
                    const gageKind *kind,
236
                    char *name, const Nrrd *nin,
237
                    const NrrdKernelSpec *ksp00,
238
                    const NrrdKernelSpec *ksp11,
239
                    const NrrdKernelSpec *ksp22) {
240
  static const char me[]="pullVolumeSingleSet";
241
  pullVolume *vol;
242
243
  vol = pullVolumeNew();
244
  if (_pullVolumeSet(pctx, AIR_FALSE /* taskCopy */, vol, kind,
245
                     pctx->verbose, name,
246
                     nin,
247
                     NULL, NULL, 0, AIR_FALSE, 0.0,
248
                     ksp00, ksp11, ksp22, NULL)) {
249
    biffAddf(PULL, "%s: trouble", me);
250
    return 1;
251
  }
252
253
  /* add this volume to context */
254
  if (pctx->verbose) {
255
    printf("%s: adding pctx->vol[%u] = %p\n", me, pctx->volNum,
256
           AIR_VOIDP(vol));
257
  }
258
  pctx->vol[pctx->volNum] = vol;
259
  pctx->volNum++;
260
  return 0;
261
}
262
263
/*
264
** the effect is to give pctx ownership of the vol
265
*/
266
int
267
pullVolumeStackAdd(pullContext *pctx,
268
                   const gageKind *kind,
269
                   char *name,
270
                   const Nrrd *nin,
271
                   const Nrrd *const *ninSS,
272
                   double *scalePos,
273
                   unsigned int ninNum,
274
                   int scaleDerivNorm,
275
                   double scaleDerivNormBias,
276
                   const NrrdKernelSpec *ksp00,
277
                   const NrrdKernelSpec *ksp11,
278
                   const NrrdKernelSpec *ksp22,
279
                   const NrrdKernelSpec *kspSS) {
280
  static const char me[]="pullVolumeStackAdd";
281
  pullVolume *vol;
282
283
  vol = pullVolumeNew();
284
  if (_pullVolumeSet(pctx, AIR_FALSE /* taskCopy */, vol, kind,
285
                     pctx->verbose, name,
286
                     nin,
287
                     ninSS, scalePos, ninNum,
288
                     scaleDerivNorm, scaleDerivNormBias,
289
                     ksp00, ksp11, ksp22, kspSS)) {
290
    biffAddf(PULL, "%s: trouble", me);
291
    return 1;
292
  }
293
294
  /* add this volume to context */
295
  pctx->vol[pctx->volNum++] = vol;
296
  return 0;
297
}
298
299
/*
300
** this is only used to create pullVolumes for the pullTasks
301
**
302
** DOES use biff
303
*/
304
pullVolume *
305
_pullVolumeCopy(const pullContext *pctx, const pullVolume *volOrig) {
306
  static const char me[]="pullVolumeCopy";
307
  pullVolume *volNew;
308
309
  volNew = pullVolumeNew();
310
  if (_pullVolumeSet(pctx, AIR_TRUE /* taskCopy */, volNew, volOrig->kind,
311
                     volOrig->verbose, volOrig->name,
312
                     volOrig->ninSingle,
313
                     volOrig->ninScale,
314
                     volOrig->scalePos,
315
                     volOrig->scaleNum,
316
                     volOrig->scaleDerivNorm,
317
                     volOrig->scaleDerivNormBias,
318
                     volOrig->ksp00, volOrig->ksp11,
319
                     volOrig->ksp22, volOrig->kspSS)) {
320
    biffAddf(PULL, "%s: trouble creating new volume", me);
321
    return NULL;
322
  }
323
  volNew->seedOnly = volOrig->seedOnly;
324
  volNew->forSeedPreThresh = volOrig->forSeedPreThresh;
325
  /* _pullVolumeSet just created a new (per-task) gageContext, and
326
     it will not learn the items from the info specs, so we have to
327
     add query here */
328
  if (gageQuerySet(volNew->gctx, volNew->gpvl, volOrig->gpvl->query)
329
      || gageUpdate(volNew->gctx)) {
330
    biffMovef(PULL, GAGE, "%s: trouble with new volume gctx", me);
331
    return NULL;
332
  }
333
  return volNew;
334
}
335
336
int
337
_pullInsideBBox(pullContext *pctx, double pos[4]) {
338
339
  return (AIR_IN_CL(pctx->bboxMin[0], pos[0], pctx->bboxMax[0]) &&
340
          AIR_IN_CL(pctx->bboxMin[1], pos[1], pctx->bboxMax[1]) &&
341
          AIR_IN_CL(pctx->bboxMin[2], pos[2], pctx->bboxMax[2]) &&
342
          AIR_IN_CL(pctx->bboxMin[3], pos[3], pctx->bboxMax[3]));
343
}
344
345
/*
346
** sets:
347
** pctx->haveScale
348
** pctx->voxelSizeSpace, voxelSizeScale
349
** pctx->bboxMin  ([0] through [3], always)
350
** pctx->bboxMax  (same)
351
*/
352
int
353
_pullVolumeSetup(pullContext *pctx) {
354
  static const char me[]="_pullVolumeSetup";
355
  unsigned int ii, numScale;
356
357
  /* first see if there are any gage problems */
358
  for (ii=0; ii<pctx->volNum; ii++) {
359
    if (pctx->verbose) {
360
      printf("%s: gageUpdate(vol[%u])\n", me, ii);
361
    }
362
    if (pctx->vol[ii]->gctx) {
363
      if (gageUpdate(pctx->vol[ii]->gctx)) {
364
        biffMovef(PULL, GAGE, "%s: trouble setting up gage on vol "
365
                  "%u/%u (\"%s\")",  me, ii, pctx->volNum,
366
                  pctx->vol[ii]->name);
367
        return 1;
368
      }
369
    } else {
370
      biffAddf(PULL, "%s: vol[%u] has NULL gctx", me, ii);
371
    }
372
  }
373
374
  pctx->voxelSizeSpace = 0.0;
375
  for (ii=0; ii<pctx->volNum; ii++) {
376
    double min[3], max[3];
377
    gageContext *gctx;
378
    gctx = pctx->vol[ii]->gctx;
379
    gageShapeBoundingBox(min, max, gctx->shape);
380
    if (!ii) {
381
      ELL_3V_COPY(pctx->bboxMin, min);
382
      ELL_3V_COPY(pctx->bboxMax, max);
383
    } else {
384
      ELL_3V_MIN(pctx->bboxMin, pctx->bboxMin, min);
385
      ELL_3V_MIN(pctx->bboxMax, pctx->bboxMax, max);
386
    }
387
    pctx->voxelSizeSpace += ELL_3V_LEN(gctx->shape->spacing)/sqrt(3.0);
388
    if (ii && !pctx->initParm.unequalShapesAllow) {
389
      if (!gageShapeEqual(pctx->vol[0]->gctx->shape, pctx->vol[0]->name,
390
                          pctx->vol[ii]->gctx->shape, pctx->vol[ii]->name)) {
391
        biffMovef(PULL, GAGE,
392
                  "%s: need equal shapes, but vol 0 and %u different",
393
                  me, ii);
394
        return 1;
395
      }
396
    }
397
  }
398
  pctx->voxelSizeSpace /= pctx->volNum;
399
  /* have now computed bbox{Min,Max}[0,1,2]; now do bbox{Min,Max}[3] */
400
  pctx->bboxMin[3] = pctx->bboxMax[3] = 0.0;
401
  pctx->haveScale = AIR_FALSE;
402
  pctx->voxelSizeScale = 0.0;
403
  numScale = 0;
404
  for (ii=0; ii<pctx->volNum; ii++) {
405
    if (pctx->vol[ii]->ninScale) {
406
      double sclMin, sclMax, sclStep;
407
      unsigned int si;
408
      numScale ++;
409
      sclMin = pctx->vol[ii]->scalePos[0];
410
      if (pctx->flag.scaleIsTau) {
411
        sclMin = gageTauOfSig(sclMin);
412
      }
413
      sclMax = pctx->vol[ii]->scalePos[pctx->vol[ii]->scaleNum-1];
414
      if (pctx->flag.scaleIsTau) {
415
        sclMax = gageTauOfSig(sclMax);
416
      }
417
      sclStep = 0;
418
      for (si=0; si<pctx->vol[ii]->scaleNum-1; si++) {
419
        double scl0, scl1;
420
        scl1 = pctx->vol[ii]->scalePos[si+1];
421
        scl0 = pctx->vol[ii]->scalePos[si];
422
        if (pctx->flag.scaleIsTau) {
423
          scl1 = gageTauOfSig(scl1);
424
          scl0 = gageTauOfSig(scl0);
425
        }
426
        sclStep += (scl1 - scl0);
427
      }
428
      sclStep /= pctx->vol[ii]->scaleNum-1;
429
      pctx->voxelSizeScale += sclStep;
430
      if (!pctx->haveScale) {
431
        pctx->bboxMin[3] = sclMin;
432
        pctx->bboxMax[3] = sclMax;
433
        pctx->haveScale = AIR_TRUE;
434
      } else {
435
        /* we already know haveScale; expand existing range */
436
        pctx->bboxMin[3] = AIR_MIN(sclMin, pctx->bboxMin[3]);
437
        pctx->bboxMax[3] = AIR_MAX(sclMax, pctx->bboxMax[3]);
438
      }
439
    }
440
  }
441
  if (numScale) {
442
    pctx->voxelSizeScale /= numScale;
443
  }
444
  if (pctx->verbose) {
445
    printf("%s: bboxMin (%g,%g,%g,%g) max (%g,%g,%g,%g)\n", me,
446
           pctx->bboxMin[0], pctx->bboxMin[1],
447
           pctx->bboxMin[2], pctx->bboxMin[3],
448
           pctx->bboxMax[0], pctx->bboxMax[1],
449
           pctx->bboxMax[2], pctx->bboxMax[3]);
450
    printf("%s: voxelSizeSpace %g Scale %g\n", me,
451
           pctx->voxelSizeSpace, pctx->voxelSizeScale);
452
  }
453
454
  /* _energyInterParticle() depends on this error checking */
455
  if (pctx->haveScale) {
456
    if (pullInterTypeJustR == pctx->interType) {
457
      biffAddf(PULL, "%s: need scale-aware intertype (not %s) with "
458
               "a scale-space volume",
459
               me, airEnumStr(pullInterType, pullInterTypeJustR));
460
      return 1;
461
    }
462
  } else {
463
    /* don't have scale */
464
    if (pullInterTypeJustR != pctx->interType) {
465
      biffAddf(PULL, "%s: can't use scale-aware intertype (%s) without "
466
               "a scale-space volume",
467
               me, airEnumStr(pullInterType, pctx->interType));
468
      return 1;
469
    }
470
  }
471
  if (pctx->flag.energyFromStrength
472
      && !(pctx->ispec[pullInfoStrength] && pctx->haveScale)) {
473
    biffAddf(PULL, "%s: sorry, can use energyFromStrength only with both "
474
             "a scale-space volume, and a strength info", me);
475
    return 1;
476
  }
477
478
  return 0;
479
}
480
481
/*
482
** basis of pullVolumeLookup
483
**
484
** uses biff, returns UINT_MAX in case of error
485
*/
486
unsigned int
487
_pullVolumeIndex(const pullContext *pctx,
488
                 const char *volName) {
489
  static const char me[]="_pullVolumeIndex";
490
  unsigned int vi;
491
492
  if (!( pctx && volName )) {
493
    biffAddf(PULL, "%s: got NULL pointer", me);
494
    return UINT_MAX;
495
  }
496
  if (0 == pctx->volNum) {
497
    biffAddf(PULL, "%s: given context has no volumes", me);
498
    return UINT_MAX;
499
  }
500
  for (vi=0; vi<pctx->volNum; vi++) {
501
    if (!strcmp(pctx->vol[vi]->name, volName)) {
502
      break;
503
    }
504
  }
505
  if (vi == pctx->volNum) {
506
    biffAddf(PULL, "%s: no volume has name \"%s\"", me, volName);
507
    return UINT_MAX;
508
  }
509
  return vi;
510
}
511
512
const pullVolume *
513
pullVolumeLookup(const pullContext *pctx,
514
                 const char *volName) {
515
  static const char me[]="pullVolumeLookup";
516
  unsigned int vi;
517
518
  vi = _pullVolumeIndex(pctx, volName);
519
  if (UINT_MAX == vi) {
520
    biffAddf(PULL, "%s: trouble looking up \"%s\"", me, volName);
521
    return NULL;
522
  }
523
  return pctx->vol[vi];
524
}
525
526
/*
527
******** pullConstraintScaleRange
528
**
529
** returns scale range from a scale-space volume,
530
** either in terms of sigma, or (if pctx->flag.scaleIsTau), tau
531
*/
532
int
533
pullConstraintScaleRange(pullContext *pctx, double ssrange[2]) {
534
  static const char me[]="pullConstraintScaleRange";
535
  pullVolume *cvol;
536
537
  if (!(pctx && ssrange)) {
538
    biffAddf(PULL, "%s: got NULL pointer", me);
539
    return 1;
540
  }
541
  if (!(pctx->constraint)) {
542
    biffAddf(PULL, "%s: given context doesn't have constraint set", me);
543
    return 1;
544
  }
545
  if (!(pctx->ispec[pctx->constraint])) {
546
    biffAddf(PULL, "%s: info %s not set for constriant", me,
547
             airEnumStr(pullInfo, pctx->constraint));
548
    return 1;
549
  }
550
  cvol = pctx->vol[pctx->ispec[pctx->constraint]->volIdx];
551
  if (!cvol->ninScale) {
552
    biffAddf(PULL, "%s: volume \"%s\" has constraint but no scale-space",
553
             me, cvol->name);
554
    return 1;
555
  }
556
  ssrange[0] = cvol->scalePos[0];
557
  ssrange[1] = cvol->scalePos[cvol->scaleNum-1];
558
  if (pctx->flag.scaleIsTau) {
559
    ssrange[0] = gageTauOfSig(ssrange[0]);
560
    ssrange[1] = gageTauOfSig(ssrange[1]);
561
  }
562
563
  return 0;
564
}