00001 #include <stdio.h>
00002 #include <stdlib.h>
00003 #include <stdint.h>
00004 #include <endian.h>
00005 #include <string.h>
00006 #include <math.h>
00007 #include <drms_types.h>
00008 #include "imgdecode.h"
00009
00010 #define MASK(n) ((1u << (n)) - 1u)
00011 #define MAX(a,b) ((a) < (b) ? (b) : (a))
00012 #define MIN(a,b) ((a) > (b) ? (b) : (a))
00013
00015 int imgstat(IMG *img, STAT *stat)
00017 {
00018 double s = 0.0, s2 = 0.0, s3 = 0.0, s4 = 0.0, t, ss;
00019 unsigned *h = img->hist;
00020 unsigned n = img->datavals;
00021 unsigned u;
00022 int i;
00023
00024
00025 if (n == 0) {
00026 stat->min = DRMS_MISSING_SHORT;
00027 stat->max = DRMS_MISSING_SHORT;
00028 stat->median = DRMS_MISSING_SHORT;
00029 stat->mean = DRMS_MISSING_DOUBLE;
00030 stat->rms = DRMS_MISSING_DOUBLE;
00031 stat->skew = DRMS_MISSING_DOUBLE;
00032 stat->kurt = DRMS_MISSING_DOUBLE;
00033 return 0;
00034 }
00035
00036 if (img->reopened) {
00037
00038
00039
00040 for (i = 0; i < MAXHIST; ++i)
00041 h[i] = 0;
00042 n = 0;
00043 for (i = 0; i < MAXPIXELS; ++i) {
00044 if (img->dat[i] == BLANK)
00045 continue;
00046 ++h[img->dat[i]];
00047 ++n;
00048 }
00049 img->datavals = n;
00050 }
00051
00052 memset(stat, 0, sizeof(STAT));
00053
00054 while (h[stat->min] == 0)
00055 ++stat->min;
00056
00057 stat->max = MAXHIST - 1;
00058 while (h[stat->max] == 0)
00059 --stat->max;
00060
00061 stat->median = stat->min;
00062 i = h[stat->median];
00063 while (2*i < n)
00064 i += h[++stat->median];
00065
00066 for (i = stat->min; i <= stat->max; ++i) {
00067 double x = i;
00068 s += (t = x*h[i]);
00069 s2 += (t *= x);
00070 s3 += (t *= x);
00071 s4 += (t *= x);
00072 }
00073
00074 s /= n;
00075 ss = s*s;
00076 stat->mean = s;
00077 s2 /= n;
00078 s3 /= n;
00079 s4 /= n;
00080 if (n > 1) {
00081 t = n * (s2 - ss) / (n-1);
00082 stat->rms = sqrt(t);
00083 }
00084 if (stat->rms > 0.0) {
00085 stat->skew = (s3 - s * (3*s2 - 2*ss)) / (t*stat->rms);
00086 stat->kurt = (s4 - 4*s*s3 + 3*ss*(2*s2-ss)) / (t*t) - 3;
00087 }
00088
00089 return 0;
00090 }
00091
00093 static void put(short *dat, int TAP, int r, int c, int n, unsigned short *pix)
00095 {
00096 int i;
00097
00098 switch (TAP) {
00099 case 0:
00100 if (r < 2048)
00101 memcpy(dat+4096*r+c, pix, 2*n);
00102 else if (r < 4096)
00103 for (i=c; i<c+n; ++i)
00104 dat[4096*(r-2048)+4095-i] = *pix++;
00105 else if (r < 6144)
00106 for (i=c; i<c+n; ++i)
00107 dat[4096*(8191-r)+4095-i] = *pix++;
00108 else
00109 memcpy(dat+4096*(10239-r)+c, pix, 2*n);
00110 break;
00111 case 1:
00112 if (r < 4096)
00113 memcpy(dat+4096*r+c, pix, 2*n);
00114 else
00115 for (i=c; i<c+n; ++i)
00116 dat[4096*(r-4096)+4095-i] = *pix++;
00117 break;
00118 case 2:
00119 if (r < 2048)
00120 for (i=c; i<c+n; ++i)
00121 dat[4096*r+4095-i] = *pix++;
00122 else
00123 for (i=c; i<c+n; ++i)
00124 dat[4096*(6143-r)+4095-i] = *pix++;
00125 break;
00126 case 3:
00127 if (r < 4096)
00128 for (i=c; i<c+n; ++i)
00129 dat[4096*(4095-r)+4095-i] = *pix++;
00130 else
00131 memcpy(dat+4096*(8191-r)+c, pix, 2*n);
00132 break;
00133 case 4:
00134 if (r < 2048)
00135 memcpy(dat+4096*r+c, pix, 2*n);
00136 else
00137 memcpy(dat+4096*(6143-r)+c, pix, 2*n);
00138 break;
00139 case 5:
00140 memcpy(dat+4096*r+c, pix, 2*n);
00141 break;
00142 case 6:
00143 for (i=c; i<c+n; ++i)
00144 dat[4096*r+4095-i] = *pix++;
00145 break;
00146 case 7:
00147 for (i=c; i<c+n; ++i)
00148 dat[4096*(4095-r)+4095-i] = *pix++;
00149
00150 break;
00151 case 8:
00152 memcpy(dat+4096*(4095-r)+c, pix, 2*n);
00153 break;
00154 }
00155 }
00156
00158 int imgdecode_init_hack(IMG *img)
00160 {
00161 int i;
00162
00163 img->datavals = 0;
00164 img->npackets = 0;
00165 img->nerrors = 0;
00166 img->last_pix_err = 0;
00167 img->first_packet_time = UINT64_MAX;
00168 for (i = 0; i < MAXPIXELS; ++i)
00169 img->dat[i] = BLANK;
00170 for (i = 0; i < MAXHIST; ++i)
00171 img->hist[i] = 0;
00172 }
00173
00175 int imgdecode(unsigned short *impdu, IMG *img)
00177 {
00178 unsigned short old, final, diff, w, h, skp, tak;
00179 int i, N, K, bits2go, wordcnt, nzero;
00180 unsigned u, bitbuf, sgn, low, fs, kmask, nmask, nmk;
00181 unsigned offset, ndecoded;
00182 uint64_t uu;
00183 const unsigned short *p;
00184 static unsigned short pix[7000];
00185
00186 static unsigned short *lut[256];
00187 static CROPTABLE cropt[4096];
00188 char fname[64];
00189 FILE *fp;
00190
00191
00192
00193
00194
00195 static char nz[256] = {
00196 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00197 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00198 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00199 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00200 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00201 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00202 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00203 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00204 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00205 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00206 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00207 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00208 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00209 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00210 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
00211 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
00212 };
00213
00214
00215
00216
00217 #if __BYTE_ORDER == __LITTLE_ENDIAN
00218 for (i=0; i<PACKETWORDS; ++i)
00219 impdu[i] = (impdu[i]>>8) + (impdu[i]<<8);
00220 #endif
00221
00222
00223
00224
00225 if (img->initialized)
00226 goto ___DECODE_START___;
00227
00228 u = impdu[4] & 0x7ffu;
00229 if (u != APID_HMI_SCIENCE_1 && u != APID_HMI_SCIENCE_2 &&
00230 u != APID_AIA_SCIENCE_1 && u != APID_AIA_SCIENCE_2) {
00231 return IMGDECODE_BAD_APID;
00232 }
00233 img->apid = u;
00234 img->telnum = impdu[11] >> 14;
00235 img->fsn = ((impdu[11] & 0x3fff) << 16) + impdu[12];
00236
00237 img->fid = ((impdu[14] & 0xff) << 16) + impdu[13];
00238 img->cropid = impdu[15] >> 4;
00239 img->overflow = impdu[15] & 1;
00240 img->headerr = (impdu[15] >> 1) & 1;
00241 img->luid = impdu[17] >> 8;
00242 img->tap = impdu[16] >> 12;
00243 u = impdu[16] & 0xff;
00244 if (u == 0 || u == 128) {
00245
00246 img->N = 16;
00247 img->K = 0;
00248 img->R = 0;
00249 } else {
00250
00251 img->N = u >> 3;
00252 if (img->N > 14)
00253 return IMGDECODE_BAD_N;
00254 img->K = u & 0x7;
00255 img->R = (impdu[16] >> 8) & 0xf;
00256 }
00257
00258
00259
00260 img->datavals = 0;
00261 img->npackets = 0;
00262 img->nerrors = 0;
00263 img->last_pix_err = 0;
00264 img->first_packet_time = UINT64_MAX;
00265
00266 for (i = 0; i < MAXPIXELS; ++i)
00267 img->dat[i] = BLANK;
00268
00269 for (i = 0; i < MAXHIST; ++i)
00270 img->hist[i] = 0;
00271
00272
00273 img->initialized = 1;
00274
00275 ___DECODE_START___:
00276
00277
00278
00279
00280 if (img->luid && !lut[img->luid]) {
00281 if (isAIA(img->apid))
00282 snprintf(fname, 64, TABLE_DIR "/lu/aia/ilu%u", img->luid);
00283 else if (isHMI(img->apid))
00284 snprintf(fname, 64, TABLE_DIR "/lu/hmi/ilu%u", img->luid);
00285 else
00286 return IMGDECODE_BAD_APID;
00287 fp = fopen(fname, "r");
00288 if (!fp)
00289 return IMGDECODE_NO_LOOKUP_TABLE;
00290 fscanf(fp, "%u", &u);
00291 if (u != img->luid)
00292 return IMGDECODE_LOOKUP_ID_MISMATCH;
00293 lut[img->luid] = (unsigned short *) malloc(16384*2);
00294 if (!lut[img->luid])
00295 return IMGDECODE_OUT_OF_MEMORY;
00296 for (i=0; i<16384; ++i)
00297 if (1 != fscanf(fp, "%hu", lut[img->luid] + i)) {
00298 fclose(fp);
00299 free(lut[img->luid]);
00300 lut[img->luid] = 0;
00301 return IMGDECODE_BAD_LOOKUP_TABLE;
00302 }
00303 fclose(fp);
00304 }
00305
00306
00307
00308
00309 if (cropt[img->cropid].totalpix)
00310 img->totalvals = cropt[img->cropid].totalpix;
00311 else if (img->cropid) {
00312 if (isAIA(img->apid))
00313 snprintf(fname, 64, TABLE_DIR "/crop/aia/crop%u", img->cropid);
00314 else if (isHMI(img->apid))
00315 snprintf(fname, 64, TABLE_DIR "/crop/hmi/crop%u", img->cropid);
00316 else
00317 return IMGDECODE_BAD_APID;
00318 fp = fopen(fname, "r");
00319 if (!fp)
00320 return IMGDECODE_NO_CROP_TABLE;
00321 fscanf(fp, "%u", &u);
00322 if (u != img->cropid)
00323 return IMGDECODE_CROP_ID_MISMATCH;
00324 fscanf(fp, "%hu %hu", &w, &h);
00325 if (!(w == 4096 && h == 4096) && !(w == 2048 && h == 8192))
00326 return IMGDECODE_BAD_CROP_GEOMETRY;
00327 cropt[img->cropid].width = w;
00328 cropt[img->cropid].height = h;
00329 cropt[img->cropid].skip = (unsigned short *) malloc(2*h);
00330 cropt[img->cropid].take = (unsigned short *) malloc(2*h);
00331 cropt[img->cropid].offset = (unsigned *) malloc(4*h);
00332 if (!cropt[img->cropid].offset)
00333 return IMGDECODE_OUT_OF_MEMORY;
00334 offset = 0;
00335 for (i=0; i<h; ++i) {
00336 if (2 != fscanf(fp, "%hu %hu", &skp, &tak)) {
00337 fclose(fp);
00338 free(cropt[img->cropid].skip);
00339 free(cropt[img->cropid].take);
00340 free(cropt[img->cropid].offset);
00341 return IMGDECODE_BAD_CROP_TABLE;
00342 }
00343 if (skp + tak > w)
00344 return IMGDECODE_BAD_CROP_SKIP_TAKE;
00345 cropt[img->cropid].skip[i] = skp;
00346 cropt[img->cropid].take[i] = tak;
00347 cropt[img->cropid].offset[i] = offset;
00348 offset += tak;
00349 }
00350 fclose(fp);
00351 cropt[img->cropid].totalpix = offset;
00352 img->totalvals = cropt[img->cropid].totalpix;
00353 } else
00354 img->totalvals = MAXPIXELS;
00355
00356
00357
00358
00359 uu = ((uint64_t)impdu[7] << 32) + ((uint64_t)impdu[8] << 16) + impdu[9];
00360 if (uu < img->first_packet_time)
00361 img->first_packet_time = uu;
00362
00363
00364
00365
00366 if (!img->overflow)
00367 img->overflow = impdu[15] & 1;
00368 if (!img->headerr)
00369 img->headerr = (impdu[15] >> 1) & 1;
00370
00371
00372
00373
00374
00375 offset = ((impdu[17] & 0xff) << 16) + impdu[18];
00376 if (offset >= img->totalvals)
00377 return IMGDECODE_BAD_OFFSET;
00378
00379 p = impdu + PACKETHEADERWORDS;
00380
00381
00382
00383
00384 if (img->N == 16) {
00385 for (i = 0; i < PACKETDATAWORDS && img->totalvals > offset+i; ++i)
00386 pix[i] = p[i] & 0x3fff;
00387 ndecoded = i;
00388 }
00389
00390
00391
00392
00393 else {
00394 old = pix[0] = *p++;
00395 ndecoded = 1;
00396 bitbuf = *p++;
00397 bits2go = 16;
00398 K = img->K;
00399 N = img->N;
00400 nmk = N - K;
00401 kmask = MASK(K);
00402 nmask = MASK(nmk);
00403 final = impdu[PACKETWORDS - 1];
00404
00405 wordcnt = PACKETDATAWORDS - 3;
00406 for (i = PACKETWORDS - 2; i > PACKETHEADERWORDS + 1; --i) {
00407 if (impdu[i]) break;
00408 --wordcnt;
00409 }
00410
00411
00412
00413
00414
00415
00416
00417
00418 while (wordcnt > 0 || (bits2go && bitbuf)) {
00419 if (bits2go < 2 + K) {
00420 bitbuf += *p++ << bits2go;
00421 --wordcnt;
00422 bits2go += 16;
00423 }
00424
00425
00426
00427
00428 low = bitbuf & kmask;
00429 bitbuf >>= K;
00430 sgn = bitbuf & 0x1;
00431 bitbuf >>= 1;
00432 bits2go -= 1 + K;
00433
00434
00435
00436
00437 if (bitbuf == 0) {
00438 if (bits2go < 9) {
00439 fs = bits2go;
00440 if (wordcnt > 0) {
00441 bitbuf = *p++;
00442 --wordcnt;
00443 bits2go = 16;
00444 } else
00445 goto __DECOMPRESS_FAILURE__;
00446 if (bitbuf & 0xff) {
00447 nzero = nz[bitbuf & 0xff];
00448 fs += nzero;
00449 if (fs > 8)
00450 goto __DECOMPRESS_FAILURE__;
00451 } else
00452 goto __DECOMPRESS_FAILURE__;
00453 } else
00454 goto __DECOMPRESS_FAILURE__;
00455 } else if (bitbuf & 0xff) {
00456 nzero = nz[bitbuf & 0xff];
00457 fs = nzero;
00458 } else if (bitbuf & 0x1ff) {
00459 nzero = 8;
00460 fs = nzero;
00461 } else
00462 goto __DECOMPRESS_FAILURE__;
00463 bitbuf >>= (nzero + 1);
00464 bits2go -= nzero + 1;
00465
00466
00467
00468
00469 if (fs == 8) {
00470 if (bits2go < nmk) {
00471 u = nmk - bits2go;
00472 fs = bitbuf;
00473
00474
00475
00476 if (wordcnt >= 0) {
00477 bitbuf = *p++;
00478 --wordcnt;
00479 fs += (bitbuf & MASK(u)) << bits2go;
00480 bitbuf >>= u;
00481 bits2go = 16 - u;
00482 } else
00483 goto __DECOMPRESS_FAILURE__;
00484 } else {
00485 fs = bitbuf & nmask;
00486 bitbuf >>= nmk;
00487 bits2go -= nmk;
00488 }
00489 }
00490
00491
00492
00493
00494 diff = (fs << K) + low;
00495 if (sgn)
00496 old -= diff;
00497 else
00498 old += diff;
00499
00500
00501 if (old > 16383)
00502 goto __DECOMPRESS_FAILURE__;
00503
00504 pix[ndecoded++] = old;
00505 }
00506
00507 if (old != final)
00508 goto __DECOMPRESS_FAILURE__;
00509 }
00510
00511 goto __DECOMPRESS_SUCCESS__;
00512
00513 __DECOMPRESS_FAILURE__:
00514
00515 ++img->nerrors;
00516 u = offset + ndecoded;
00517 if (u < img->totalvals && u > img->totalvals - 4) {
00518 img->last_pix_err = 1;
00519 img->datavals += ndecoded;
00520 ++img->npackets;
00521 goto __POST_PROCESSING__;
00522 }
00523 return IMGDECODE_DECOMPRESS_ERROR;
00524
00525 __DECOMPRESS_SUCCESS__:
00526
00527 u = img->datavals + ndecoded;
00528 if (u > img->totalvals && !img->reopened) {
00529 ++img->nerrors;
00530 return IMGDECODE_TOO_MANY_PIXELS;
00531 }
00532 if (offset + ndecoded > img->totalvals) {
00533 ++img->nerrors;
00534 return IMGDECODE_BAD_OFFSET;
00535 }
00536 img->datavals = u;
00537 ++img->npackets;
00538
00539 __POST_PROCESSING__:
00540
00541 for (i = 0; i < ndecoded; ++i) {
00542
00543
00544
00545 if (img->luid)
00546 pix[i] = lut[img->luid][pix[i]];
00547
00548
00549
00550 if (img->R)
00551 pix[i] <<= img->R;
00552
00553
00554
00555 if (img->hist)
00556 ++img->hist[pix[i]];
00557 }
00558
00559
00560
00561
00562 if (img->cropid) {
00563 unsigned *o = cropt[img->cropid].offset;
00564 unsigned short *s = cropt[img->cropid].skip;
00565 unsigned short *t = cropt[img->cropid].take;
00566 int done = 0;
00567 h = cropt[img->cropid].height;
00568 for (i = 0; ndecoded > 0 && i < h; ++i) {
00569 if (o[i] > offset || (h > i+1 && o[i+1] <= offset))
00570 continue;
00571 skp = offset - o[i];
00572 tak = MIN(ndecoded, t[i] - skp);
00573 if (tak) {
00574 skp += s[i];
00575 put(img->dat, img->tap, i, skp, tak, pix+done);
00576 ndecoded -= tak;
00577 offset += tak;
00578 done += tak;
00579 }
00580 }
00581 } else {
00582 int done = 0;
00583 w = h = 4096;
00584 switch(img->tap) {
00585 case 0:
00586 case 1:
00587 case 3:
00588 w = 2048; h = 8192;
00589 }
00590 i = offset / w;
00591 while (ndecoded) {
00592 skp = offset % w;
00593 tak = MIN(ndecoded, w - skp);
00594 if (tak) {
00595 put(img->dat, img->tap, i, skp, tak, pix+done);
00596 ndecoded -= tak;
00597 offset += tak;
00598 done += tak;
00599 ++i;
00600 }
00601 }
00602 }
00603
00604 return 0;
00605 }
00606
00607 #ifdef MAKE_IMGDECODE_MAIN
00608
00609 #include <fitsio.h>
00610
00611 void img2fits(IMG *i, STAT *s)
00612 {
00613 int status = 0;
00614 char fn[64];
00615 long naxes[] = {4096,4096};
00616 long fpix[] = {1,1};
00617 short blank = BLANK;
00618 fitsfile *fp;
00619 int missvals;
00620
00621 fn[0] = '!';
00622 snprintf(fn+1, 63, "%08u.fits[compress R]", i->fsn);
00623 fits_create_file(&fp, fn, &status);
00624 if (status) {
00625 fits_report_error(stderr, status);
00626 return;
00627 }
00628 fits_create_img(fp, 16, 2, naxes, &status);
00629 fits_write_pix(fp, TSHORT, fpix, MAXPIXELS, i->dat, &status);
00630 fits_write_key(fp, TSHORT, "BLANK", &blank, "", &status);
00631 fits_write_key(fp, TINT, "FSN", &i->fsn, "", &status);
00632 fits_write_key(fp, TINT, "DATAVALS", &i->datavals, "", &status);
00633 missvals = i->totalvals - i->datavals;
00634 fits_write_key(fp, TINT, "MISSVALS", &missvals, "", &status);
00635 fits_write_key(fp, TSHORT, "DATAMIN", &s->min, "", &status);
00636 fits_write_key(fp, TSHORT, "DATAMAX", &s->max, "", &status);
00637 fits_write_key(fp, TSHORT, "DATAMEDN", &s->median, "", &status);
00638 fits_write_key(fp, TDOUBLE, "DATAMEAN", &s->mean, "", &status);
00639 fits_write_key(fp, TDOUBLE, "DATA_RMS", &s->rms, "", &status);
00640 fits_write_key(fp, TDOUBLE, "DATASKEW", &s->skew, "", &status);
00641 fits_write_key(fp, TDOUBLE, "DATAKURT", &s->kurt, "", &status);
00642 fits_write_key(fp, TINT, "NUMPKTS", &i->npackets, "", &status);
00643 fits_write_key(fp, TINT, "NUMERRS", &i->nerrors, "", &status);
00644 fits_write_key(fp, TLOGICAL, "EOIERR", &i->last_pix_err,
00645 "decompression error at end of image?", &status);
00646 fits_write_chksum(fp, &status);
00647 fits_close_file(fp, &status);
00648 fits_report_error(stderr, status);
00649 }
00650
00651 int main()
00652 {
00653 IMG img;
00654 STAT imstat;
00655 unsigned char buf[1788];
00656 unsigned short *buf2 = (unsigned short *)buf;
00657 unsigned fsn, ofsn, apid;
00658 int npkt, ierr;
00659
00660 img.initialized = 0;
00661 img.reopened = 0;
00662 ofsn = 0;
00663 npkt = 0;
00664
00665 while(fread(buf,1,1788,stdin) == 1788) {
00666 apid = ((buf[18] << 8) + buf[19]) & 0x7ff;
00667 if (!(apid==400 || apid==410 || apid==500 || apid==510))
00668 continue;
00669 fsn = (buf[32] << 24) + (buf[33] << 16) + (buf[34] << 8) + buf[35];
00670 if (fsn != ofsn && img.initialized) {
00671 imgstat(&img, &imstat);
00672 img2fits(&img, &imstat);
00673 img.initialized = 0;
00674 img.reopened = 0;
00675 }
00676
00677 ierr = imgdecode(buf2+5,&img);
00678 if (ierr)
00679 fprintf(stderr,"packet %d return code %d\n", npkt, ierr);
00680 ++npkt;
00681 ofsn = fsn;
00682 }
00683
00684
00685 if (img.initialized) {
00686 imgstat(&img, &imstat);
00687 img2fits(&img, &imstat);
00688 }
00689
00690 return 0;
00691 }
00692
00693 #endif