00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038 #include "Neuro/SimulationViewerStats.H"
00039 #include "Channels/ChannelMaps.H"
00040 #include "Channels/ChannelOpts.H"
00041 #include "Image/colorDefs.H"
00042 #include "Image/CutPaste.H"
00043 #include "Image/ColorOps.H"
00044 #include "Image/DrawOps.H"
00045 #include "Image/fancynorm.H"
00046 #include "Image/FFTWWrapper.H"
00047 #include "Image/ImageSet.H"
00048 #include "Image/MathOps.H"
00049 #include "Image/ShapeOps.H"
00050 #include "Image/NamedImage.H"
00051 #include "Media/MediaSimEvents.H"
00052 #include "Neuro/Brain.H"
00053 #include "Neuro/NeuroOpts.H"
00054 #include "Neuro/NeuroSimEvents.H"
00055 #include "Neuro/VisualCortex.H"
00056 #include "Transport/FrameInfo.H"
00057 #include "Transport/FrameOstream.H"
00058 #include <fcntl.h>
00059 #include <cerrno>
00060
00061
00062
00063 SimulationViewerStats::SimulationViewerStats(OptionManager& mgr,
00064 const std::string& descrName,
00065 const std::string& tagName) :
00066 SimulationViewer(mgr, descrName, tagName),
00067 SIMCALLBACK_INIT(SimEventSaveOutput),
00068 itsStatsFname(&OPT_SVstatsFname, this),
00069 itsSaveXcombo(&OPT_SVsaveXcombo, this),
00070 itsSaveYcombo(&OPT_SVsaveYcombo, this),
00071 itsComputeAGStats(&OPT_ComputeAGStats, this),
00072 itsAGTargetFrame(&OPT_AGTargetFrame, this),
00073 itsAGMaskFile(&OPT_AGMaskFile, this),
00074 itsAGStatsSaveFile(&OPT_AGStatsSaveFile, this),
00075 itsGetSingleChannelStats(&OPT_GetSingleChannelStats, this),
00076 itsGetSingleChannelStatsFile(&OPT_GetSingleChannelStatsFile, this),
00077 itsSaveStatsPerChannelFreq(&OPT_SaveStatsPerChannelFreq, this),
00078 itsStatsFile(NULL)
00079 { }
00080
00081
00082 SimulationViewerStats::~SimulationViewerStats()
00083 { }
00084
00085
00086 void SimulationViewerStats::start2()
00087 {
00088
00089 itsFrameIdx = 0;
00090
00091
00092 itsMaskCount = 0; itsLamCount = 0; itsOverlapCount = 0;
00093 }
00094
00095
00096 void SimulationViewerStats::stop2()
00097 {
00098
00099 if (itsStatsFile)
00100 {
00101 itsStatsFile->flush();
00102 itsStatsFile->close();
00103 delete itsStatsFile;
00104 itsStatsFile = NULL;
00105 }
00106 }
00107
00108
00109 void SimulationViewerStats::lockFile(const std::string fileName,
00110 int &fd,
00111 struct flock &fl) const
00112 {
00113
00114 fd = open(fileName.c_str(), O_RDWR);
00115 if (fd < 0)
00116 {
00117 LINFO("lockFile: Open failure on file %s",fileName.c_str());
00118 }
00119 else
00120 {
00121 fl.l_type = F_WRLCK;
00122 fl.l_whence = SEEK_SET;
00123 fl.l_start = 0;
00124 fl.l_len = 0;
00125 if (fcntl(fd, F_SETLK, &fl) == -1)
00126 {
00127 if (errno == EACCES || errno == EAGAIN)
00128 LINFO("'%s' Already locked by another process",fileName.c_str());
00129 else if(errno == EBADF)
00130 LINFO("'%s' not a valid open file descriptor",fileName.c_str());
00131 else if(errno == EINVAL)
00132 LINFO("'%s In a locking operation, fildes refers to a file with a type that does not support locking, or the struct flock pointed to by the third argument has an incorrect form",fileName.c_str());
00133 else if(errno == EMFILE)
00134 LINFO("'%s' process has already reached its maximum number of file descriptors",fileName.c_str());
00135 else
00136 LINFO("Cannot lock file '%s' Error code '%d' \(Is this an NFS mount?)",fileName.c_str(),errno);
00137 }
00138 }
00139 }
00140
00141
00142 void SimulationViewerStats::unlockFile(const std::string fileName,
00143 const int fd,
00144 struct flock &fl) const
00145 {
00146
00147 fl.l_type = F_UNLCK;
00148 fl.l_whence = SEEK_SET;
00149 fl.l_start = 0;
00150 fl.l_len = 0;
00151 if (fcntl(fd, F_SETLK, &fl) == -1)
00152 {
00153 LINFO("Cannot unlock file '%s'",fileName.c_str());
00154 }
00155 close(fd);
00156 }
00157
00158
00159 void SimulationViewerStats::
00160 onSimEventSaveOutput(SimEventQueue& q, rutz::shared_ptr<SimEventSaveOutput>& e)
00161 {
00162 this->save1(e->sinfo());
00163 }
00164
00165
00166 void SimulationViewerStats::save1(const ModelComponentSaveInfo& sinfo)
00167 {
00168
00169
00170 LINFO("SAVING STATS TO %s",itsStatsFname.getVal().c_str());
00171
00172
00173
00174 struct flock fl; int fd;
00175 lockFile(itsStatsFname.getVal().c_str(),fd,fl);
00176
00177
00178
00179 std::ofstream statsFile;
00180 statsFile.open(itsStatsFname.getVal().c_str(),std::ios_base::app);
00181
00182
00183
00184 nub::ref<FrameOstream> ofs =
00185 dynamic_cast<const SimModuleSaveInfo&>(sinfo).ofs;
00186
00187
00188 SimEventQueue *q = dynamic_cast<const SimModuleSaveInfo&>(sinfo).q;
00189
00190
00191 if(itsFrameIdx == 0)
00192 {
00193 rutz::shared_ptr<SimReqVCXmaps> vcxm(new SimReqVCXmaps(this));
00194 q->request(vcxm);
00195
00196 if (itsStatsFname.getVal().size())
00197 {
00198 itsStatsFile = new std::ofstream(itsStatsFname.getVal().c_str());
00199 if (itsStatsFile->is_open() == false)
00200 LFATAL("Failed to open '%s' for writing.",
00201 itsStatsFname.getVal().c_str());
00202
00203
00204 getRootObject()->printout(*itsStatsFile, "# ");
00205
00206
00207
00208
00209
00210 rutz::shared_ptr<ChannelMaps> chm = vcxm->channelmaps();
00211 uint numSubmaps = chm->numSubmaps();
00212
00213 *itsStatsFile << "# CHANNELS: ";
00214
00215 for(uint i = 0; i < numSubmaps; i++)
00216 {
00217 NamedImage<float> smap = chm->getRawCSmap(i);
00218 *itsStatsFile << smap.name() << ", ";
00219 }
00220 *itsStatsFile << std::endl;
00221 }
00222 }
00223
00224
00225
00226
00227 (*itsStatsFile).flush();
00228 (*itsStatsFile).close();
00229
00230
00231 if (SeC<SimEventInputFrame> e = q->check<SimEventInputFrame>(this))
00232 {
00233 itsSizeX = e->frame().getWidth();
00234 itsSizeY = e->frame().getHeight();
00235 itsFrameNumber = (unsigned int)e->frameNum();
00236 itsFrameIdx = itsFrameNumber;
00237 }
00238 else
00239 {
00240 itsFrameNumber = itsFrameIdx;
00241 itsFrameIdx++;
00242 }
00243
00244
00245
00246
00247 Image< PixRGB<byte> > input;
00248 if (SeC<SimEventRetinaImage> e = q->check<SimEventRetinaImage>(this,SEQ_ANY))
00249 input = e->frame().colorByte();
00250 else
00251 LINFO("No input? Check the SimEventCue.");
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262 Image<float> agm;
00263 if (SeC<SimEventAttentionGuidanceMapOutput> e =
00264 q->check<SimEventAttentionGuidanceMapOutput>(this))
00265 agm = e->agm(1.0F);
00266 else
00267 LINFO("No AGM? Check the SimEventCue.");
00268
00269
00270
00271 bool quit = false;
00272 if (input.initialized() == false)
00273 {
00274 LINFO("WARNING!!! Input seems not to be initialized, so detailed stats cannot be saved.");
00275 quit = true;
00276 }
00277 if(agm.initialized() == false)
00278 {
00279 LINFO("WARNING!!! NO Attention Guidance MAP \"AGM\" so detailed stats cannot be saved.");
00280 quit = true;
00281 }
00282
00283 if(quit == true) return;
00284
00285
00286 Image< PixRGB<byte> > res;
00287 const int w = input.getWidth();
00288
00289
00290 if (itsSaveXcombo.getVal() || itsSaveYcombo.getVal())
00291 {
00292 Image<float> nagm = getMap(*q);
00293 res = colGreyCombo(input, nagm, itsSaveXcombo.getVal(),
00294 itsDisplayInterp.getVal());
00295 }
00296
00297
00298
00299 if (itsGetSingleChannelStats.getVal())
00300 saveCompat(agm);
00301
00302
00303 if (statsFile)
00304 {
00305
00306 statsFile <<std::endl<<"= "<<q->now().msecs()
00307 <<" # time of frame in ms"<<std::endl;
00308
00309
00310 float mi, ma, av; getMinMaxAvg(agm, mi, ma, av);
00311 double sdev = stdev(agm);
00312 double peaksum; int npeaks = findPeaks(agm, 0.0f, 255.0f, peaksum);
00313
00314
00315 float maxval; Point2D<int> maxloc;
00316 findMax(agm, maxloc, maxval);
00317 float scale = float(w) / float(agm.getWidth());
00318 maxloc.i = int(maxloc.i * scale + 0.4999F);
00319 maxloc.j = int(maxloc.j * scale + 0.4999F);
00320 if (res.initialized())
00321 {
00322 drawPatch(res, maxloc, 4, COL_YELLOW);
00323 drawPatch(res, maxloc + Point2D<int>(w, 0), 4, COL_YELLOW);
00324 }
00325
00326
00327 float minval; Point2D<int> minloc;
00328 findMin(agm, minloc, minval);
00329 minloc.i = int(minloc.i * scale + 0.4999F);
00330 minloc.j = int(minloc.j * scale + 0.4999F);
00331 if (res.initialized())
00332 {
00333 drawPatch(res, minloc, 4, COL_GREEN);
00334 drawPatch(res, minloc + Point2D<int>(w, 0), 4, COL_GREEN);
00335 }
00336
00337
00338 statsFile <<maxloc.i<<' '<<maxloc.j<<' '<<minloc.i<<' '
00339 <<minloc.j<<' '<<ma<<' '<<mi<<' '<<av<<' '<<sdev
00340 <<' '<<npeaks<<' '<<peaksum
00341 <<" # Xmax Ymax Xmin Ymin max min avg std npeaks peaksum"
00342 <<std::endl;
00343
00344
00345
00346 std::vector<Point2D<int> > loc;
00347 loc.push_back(maxloc);
00348 loc.push_back(minloc);
00349 for (uint n = 0; n < 100; n ++)
00350 loc.push_back(Point2D<int>(randomUpToNotIncluding(input.getWidth()),
00351 randomUpToNotIncluding(input.getHeight())));
00352
00353
00354 ImageSet<float> cmap;
00355
00356 rutz::shared_ptr<SimReqVCXmaps> vcxm(new SimReqVCXmaps(this));
00357 q->request(vcxm);
00358 rutz::shared_ptr<ChannelMaps> chm = vcxm->channelmaps();
00359 uint numSubmaps = chm->numSubmaps();
00360 for(uint i=0;i < numSubmaps; i++)
00361 {
00362 NamedImage<float> tempMap = chm->getRawCSmap(i);
00363 Image<float> m = tempMap;
00364 cmap.push_back(m);
00365
00366
00367 Point2D<int> p; float v;
00368 findMax(m, p, v); loc.push_back(p);
00369 findMin(m, p, v); loc.push_back(p);
00370 }
00371
00372
00373
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386 for (uint i = 0; i < loc.size(); i ++)
00387 {
00388 Point2D<int> p = loc[i];
00389 Point2D<int> pp(int(p.i / scale), int(p.j / scale));
00390
00391 statsFile <<p.i<<' '<<p.j<<" ";
00392
00393
00394
00395 for (uint j = 0; j < cmap.size(); j ++)
00396 {
00397 if((int(p.i / scale) < agm.getWidth()) &&
00398 (int(p.j / scale) < agm.getHeight()))
00399 {
00400 (statsFile)<<cmap[j].getVal(pp)<<' ';
00401 (statsFile)<<" ";
00402 }
00403 else
00404 {
00405 (statsFile)<<"-1"<<' ';
00406 (statsFile)<<" ";
00407 }
00408 }
00409
00410
00411
00412
00413
00414
00415
00416
00417 statsFile <<"# features "<<i<<" at ("<<p.i
00418 <<", "<<p.j<<')'<<std::endl;
00419 }
00420 }
00421
00422 statsFile.flush();
00423 statsFile.close();
00424 unlockFile(itsStatsFname.getVal().c_str(),fd,fl);
00425
00426 if (res.initialized())
00427 ofs->writeRGB(res, "T",
00428 FrameInfo("SimulationViewerStats trajectory", SRC_POS));
00429
00430
00431
00432 if(itsComputeAGStats.getVal())
00433 computeAGStats(*q);
00434 else if (SeC<SimEventAttentionGateOutput> ag =
00435 q->check<SimEventAttentionGateOutput>(this))
00436 computeLAMStats(ag->lam());
00437
00438
00439 if(itsOverlap.initialized())
00440 ofs->writeRGB(itsOverlap, "AG-STAT-MASK",
00441 FrameInfo("Stats mask overlap", SRC_POS));
00442
00443 if(itsVisualSegments.initialized())
00444 ofs->writeRGB(itsVisualSegments, "AG-STAT-SEGS",
00445 FrameInfo("Stats segments", SRC_POS));
00446
00447 if(itsVisualCandidates.initialized())
00448 ofs->writeGray(itsVisualCandidates, "AG-STAT-CAND",
00449 FrameInfo("Stats candidates", SRC_POS));
00450
00451 }
00452
00453
00454 void SimulationViewerStats::computeLAMStats(const Image<float> &img)
00455 {
00456
00457 saveCompat(img,".final-lam.txt",-1);
00458
00459 Image<float> lamr = rescale(img,itsSizeX,itsSizeY);
00460
00461 itsMaskCount = 0; itsLamCount = 0; itsOverlapCount = 0;
00462 itsTotalCount = 0;
00463
00464
00465 for(Image<float>::iterator litr = lamr.beginw();
00466 litr != lamr.endw(); litr++)
00467 {
00468 if(*litr > 32) itsLamCount++;
00469 itsTotalCount++;
00470 }
00471 saveAGMaskStats(lamr,"LAM");
00472
00473 }
00474
00475
00476 void SimulationViewerStats::computeAGStats(SimEventQueue& q)
00477 {
00478
00479 if (SeC<SimEventAttentionGateOutput> ag =
00480 q.check<SimEventAttentionGateOutput>(this))
00481 {
00482 Image<float> lam = ag->lam();
00483 saveCompat(lam,".final-lam.txt",-1);
00484
00485
00486
00487
00488 if(!itsMask.initialized())
00489 {
00490
00491 const Image< PixRGB<byte> > cmask =
00492 Raster::ReadRGB(itsAGMaskFile.getVal());
00493
00494 LINFO("Target Mask File Read %s",itsAGMaskFile.getVal().c_str());
00495
00496
00497 itsMask.resize(cmask.getWidth(),cmask.getHeight());
00498 Image<bool>::iterator mitr = itsMask.beginw();
00499
00500 for(Image<PixRGB<byte> >::const_iterator citr = cmask.begin();
00501 citr != cmask.end(); citr++)
00502 {
00503
00504 if(citr->p[0] < 230) *mitr++ = true;
00505 else *mitr++ = false;
00506 }
00507
00508 itsLamMask.resize(itsMask.getWidth(),itsMask.getHeight(),true);
00509 itsOverlap.resize(itsMask.getWidth(),itsMask.getHeight(),true);
00510 }
00511
00512
00513
00514
00515 if(itsMask.initialized())
00516 {
00517 LINFO("FRAME %d",itsFrameNumber);
00518
00519
00520 if((itsFrameNumber == (unsigned int)(itsAGTargetFrame.getVal() + 1)) ||
00521 ((itsAGTargetFrame.getVal() == 0) && (itsFrameIdx > 0)))
00522 {
00523
00524 LINFO("Checking Mask file to target Attention Gate Frame %d",
00525 (itsFrameNumber + 1));
00526
00527
00528
00529
00530
00531
00532
00533
00534
00535 Image<float> lamr = rescale(lam,itsMask.getWidth(),
00536 itsMask.getHeight());
00537 Image<bool>::iterator mitr = itsLamMask.beginw();
00538
00539
00540 for(Image<float>::iterator litr = lamr.beginw();
00541 litr != lamr.endw();
00542 litr++)
00543 {
00544
00545 if(*litr > 32) *mitr++ = true;
00546 else *mitr++ = false;
00547 }
00548
00549 itsMaskCount = 0; itsLamCount = 0; itsOverlapCount = 0;
00550 itsTotalCount = 0;
00551
00552 Image<bool>::iterator aitr = itsMask.beginw();
00553 Image<bool>::iterator litr = itsLamMask.beginw();
00554 Image<PixRGB<byte> >::iterator oitr = itsOverlap.beginw();
00555
00556
00557
00558 while(litr != itsLamMask.endw())
00559 {
00560 if(*aitr++)
00561 {
00562 itsMaskCount++;
00563 if(*litr++)
00564 {
00565 itsLamCount++;
00566 itsOverlapCount++;
00567 *oitr++ = PixRGB<byte>(0,0,0);
00568 }
00569 else *oitr++ = PixRGB<byte>(255,0,0);
00570 }
00571 else if(*litr++)
00572 {
00573 itsLamCount++;
00574 *oitr++ = PixRGB<byte>(0,0,255);
00575 }
00576 else
00577 {
00578 *oitr++ = PixRGB<byte>(255,255,255);
00579 }
00580 itsTotalCount++;
00581 }
00582
00583
00584
00585
00586
00587
00588 if(itsMaskCount == itsTotalCount)
00589 LFATAL("Constancy Check failed for mask - Too Large!");
00590 else if(itsMaskCount == 0)
00591 LFATAL("Constancy Check failed for mask - Too Small!");
00592
00593 saveAGMaskStats(lamr,"AG-MASK");
00594 }
00595 }
00596
00597
00598 if (SeC<SimEventAttentionGateStageTwoSegments> agst =
00599 q.check<SimEventAttentionGateStageTwoSegments>(this))
00600 {
00601 LINFO("Getting Segments Image");
00602 const Image<int> segments = agst->obj().segments;
00603 Image<float> newSegments = static_cast<Image<float> >(segments);
00604
00605 float min,max,avg;
00606
00607 getMinMaxAvg(newSegments,min,max,avg);
00608
00609 LINFO("Segments min %f max %f",min,max);
00610
00611 Image<PixHSV<float> > classImage;
00612
00613 classImage.resize(segments.getWidth(),segments.getHeight());
00614
00615 if((max-min) > 0.0f)
00616 {
00617
00618
00619 newSegments = ((newSegments - min) / (max - min)) * 300.0f;
00620
00621 Image<PixHSV<float> >::iterator classImageItr = classImage.beginw();
00622
00623 for(Image<float>::iterator segmentsItr = newSegments.beginw();
00624 segmentsItr != newSegments.endw(); ++segmentsItr)
00625 {
00626 classImageItr->p[0] = *segmentsItr;
00627
00628 classImageItr->p[1] = 100.0f;
00629 classImageItr->p[2] = 255.0f;
00630 ++classImageItr;
00631 }
00632
00633
00634 Image<PixRGB<float> > temp =
00635 static_cast<Image<PixRGB<float> > >(classImage);
00636
00637 itsVisualSegments = temp;
00638 }
00639
00640 const Image<bool> cand = agst->candidates();
00641 Image<float> newCand = static_cast<Image<float> >(cand);
00642
00643 newCand = newCand * 255.0f;
00644
00645 itsVisualCandidates = newCand;
00646
00647 }
00648 }
00649 }
00650
00651
00652
00653 void SimulationViewerStats::saveCompat(const Image<float>& img,
00654 const std::string suffix,
00655 const int frameOffset)
00656 {
00657
00658
00659 unsigned int frameIdx = (unsigned int)((int)itsFrameNumber + frameOffset);
00660
00661 std::string fileName;
00662 std::string txt = suffix;
00663 fileName = itsGetSingleChannelStatsFile.getVal() + txt;
00664
00665 ushort minx = 0, miny = 0, maxx = 0, maxy = 0;
00666 float min, max, avg, std;
00667 uint N;
00668
00669 getMinMaxAvgEtc(img, min, max, avg, std, minx, miny, maxx, maxy, N);
00670
00671 LINFO("SAVING COMPAT STATS TO %s",fileName.c_str());
00672
00673 std::ofstream statsFile(fileName.c_str(), std::ios::app);
00674
00675 statsFile << "final" << "\t";
00676
00677 statsFile << frameIdx << "\t";
00678
00679
00680
00681
00682 statsFile << "COMBINED\t-1\t";
00683
00684
00685 statsFile << "final" << "\t" << "FINAL" << "\t";
00686 statsFile << min << "\t" << max << "\t" << avg << "\t" << std << "\t"
00687 << minx << "\t" << miny << "\t" << maxx << "\t" << maxy << "\t"
00688 << N << "\n";
00689 statsFile.close();
00690
00691 if(itsSaveStatsPerChannelFreq.getVal())
00692 {
00693 std::string txt = ".final.freq.txt";
00694 fileName = itsGetSingleChannelStatsFile.getVal() + txt;
00695
00696 FFTWWrapper fft(img.getWidth(), img.getHeight());
00697 double dimg[img.getHeight() * img.getWidth()];
00698 Image<float>::const_iterator itr = img.begin();
00699 double *ditr = &dimg[0];
00700 while(itr != img.end()) *ditr++ = double(*itr++);
00701 fft.init(dimg);
00702 double mag[img.getHeight() * (img.getWidth()/2 + 1)];
00703 fft.compute(mag);
00704
00705 std::ofstream freqFile(fileName.c_str(), std::ios::app);
00706 freqFile << "final" << "\t";
00707 freqFile << frameIdx << "\t";
00708
00709 freqFile << "COMBINED\t-1\t";
00710
00711 freqFile << "SIZE\t" << img.getWidth() <<"\t"<< img.getHeight() << "\n";
00712
00713 for(int i = 0; i < img.getHeight(); i++)
00714 {
00715 for(int j = 0; j < (img.getWidth()/2 + 1); j++)
00716 freqFile << mag[i * (img.getWidth()/2 + 1) + j] << "\t";
00717 freqFile << "\n";
00718 }
00719 freqFile.close();
00720 }
00721 }
00722
00723
00724 void SimulationViewerStats::saveAGMaskStats(const Image<float> &img,
00725 const std::string caller,
00726 const std::string suffix)
00727 {
00728
00729 ushort minx = 0, miny = 0, maxx = 0, maxy = 0;
00730 float min, max, avg, std;
00731 uint N;
00732
00733 getMinMaxAvgEtc(img, min, max, avg, std, minx, miny, maxx, maxy, N);
00734
00735 std::string fileName;
00736 std::string txt = suffix;
00737 fileName = itsGetSingleChannelStatsFile.getVal() + txt;
00738
00739 LINFO("SAVING AG STATS TO %s",fileName.c_str());
00740
00741 std::ofstream statsFile(fileName.c_str(), std::ios::app);
00742
00743 statsFile << "AGstats" << "\t";
00744
00745 statsFile << (itsFrameNumber - 1) << "\t";
00746
00747
00748
00749
00750 statsFile << "COMBINED\t" << caller << "\t";
00751
00752
00753 statsFile << "final" << "\t" << "FINAL" << "\t";
00754 statsFile << itsTotalCount << "\t"
00755 << itsMaskCount << "\t"
00756 << itsLamCount << "\t"
00757 << itsOverlapCount << "\t";
00758 statsFile << min << "\t" << max << "\t" << avg << "\t" << std << "\t"
00759 << minx << "\t" << miny << "\t" << maxx << "\t" << maxy << "\t"
00760 << N << "\n";
00761
00762 statsFile.close();
00763 }
00764
00765
00766
00767
00768
00769
00770