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/Retina.H"
00039
00040 #include "Component/OptionManager.H"
00041 #include "Component/ModelOptionDef.H"
00042 #include "Image/CutPaste.H"
00043 #include "Image/DrawOps.H"
00044 #include "Image/ColorOps.H"
00045 #include "Image/MatrixOps.H"
00046 #include "Image/Point2DT.H"
00047 #include "Image/PyramidOps.H"
00048 #include "SpaceVariant/SpaceVariantOpts.H"
00049 #include "Media/MediaSimEvents.H"
00050 #include "Neuro/NeuroOpts.H"
00051 #include "Neuro/NeuroSimEvents.H"
00052 #include "Neuro/SpatialMetrics.H"
00053 #include "Channels/ChannelOpts.H"
00054 #include "Psycho/EyeData.H"
00055 #include "Raster/Raster.H"
00056 #include "Simulation/SimEventQueue.H"
00057 #include "Transport/FrameInfo.H"
00058 #include "Transport/FrameOstream.H"
00059 #include "Util/sformat.H"
00060
00061 #include <cstdlib>
00062 #include <iostream>
00063 #include <exception>
00064 #include <vector>
00065
00066
00067
00068 static const ModelOptionDef OPT_ClipMaskFname =
00069 { MODOPT_ARG_STRING, "ClipMaskFname", &MOC_BRAIN, OPTEXP_CORE,
00070 "Name of a grayscale image file to be loaded and used as a "
00071 "clipmask for Brain",
00072 "clip-mask", '\0', "<filename>", "" };
00073
00074 static const ModelOptionDef OPT_RawInpRectBorder =
00075 { MODOPT_ARG(int), "RawInpRectBorder", &MOC_BRAIN, OPTEXP_CORE,
00076 "Border size to use for the Retina's raw input rectangle (used to "
00077 "select random samples in SimulationViewerCompress), in pixels.",
00078 "rawinput-rect-border", '\0', "<int>", "128" };
00079
00080 static const ModelOptionDef OPT_EnablePyramidCaches =
00081 { MODOPT_FLAG, "EnablePyramidCaches", &MOC_BRAIN, OPTEXP_CORE,
00082 "Whether to allow caching of commonly-needed image pyramids based "
00083 "on the current input image, such as the intensity pyramid shared "
00084 "between the intensity channel and the motion channels, or the "
00085 "laplacian pyramid shared among the oriented gabor channels. There "
00086 "should be no reason to disable pyramid caching except for "
00087 "debugging or profiling.",
00088 "enable-pyramid-caches", '\0', "", "true" };
00089
00090
00091
00092
00093
00094
00095 Retina::Retina(OptionManager& mgr, const std::string& descrName, const std::string& tagName) :
00096 SimModule(mgr, descrName, tagName)
00097 { }
00098
00099 Retina::~Retina()
00100 { }
00101
00102
00103
00104
00105
00106
00107 RetinaAdapter::RetinaAdapter(OptionManager& mgr, const std::string& descrName, const std::string& tagName) :
00108 Retina(mgr, descrName, tagName),
00109 SIMCALLBACK_INIT(SimEventInputFrame),
00110 SIMCALLBACK_INIT(SimEventSaccadeStatusEye),
00111 SIMCALLBACK_INIT(SimEventSaveOutput),
00112 itsClipMaskFname(&OPT_ClipMaskFname, this),
00113 itsRawInpRectBorder(&OPT_RawInpRectBorder, this),
00114 itsInitialEyePosition(&OPT_SCeyeInitialPosition, this),
00115 itsEnablePyramidCaches(&OPT_EnablePyramidCaches, this),
00116 itsFoveaRadius(&OPT_FoveaRadius, this),
00117 itsSaveInput(&OPT_RetinaSaveInput, this),
00118 itsSaveOutput(&OPT_RetinaSaveOutput, this),
00119 itsFramingImageName(&OPT_InputFramingImageName, this),
00120 itsFramingImagePos(&OPT_InputFramingImagePos, this),
00121 itsFoveateInputDepth(&OPT_FoveateInputDepth, this),
00122 itsShiftInput(&OPT_ShiftInputToEye, this),
00123 itsShiftInputBGcol(&OPT_ShiftInputToEyeBGcol, this),
00124 itsInputFOV(&OPT_InputFOV, this),
00125 itsSavePyr(&OPT_RetinaStdSavePyramid, this),
00126 itsBlankBlink(&OPT_BlankBlink, this),
00127 itsRetMaskFname(&OPT_RetinaMaskFname, this),
00128 itsFlipHoriz(&OPT_RetinaFlipHoriz, this),
00129 itsFlipVertic(&OPT_RetinaFlipVertic, this),
00130 itsClipMask(),
00131 itsEyePos(-1, -1),
00132 itsEyeBlinkStatus(false),
00133 itsRawInput(),
00134 itsOutput(),
00135 itsFramingImage(),
00136 itsRetinalShift(0, 0),
00137 itsRetMask()
00138 { }
00139
00140
00141 RetinaAdapter::~RetinaAdapter()
00142 { }
00143
00144
00145 void RetinaAdapter::onSimEventSaccadeStatusEye(SimEventQueue& q, rutz::shared_ptr<SimEventSaccadeStatusEye>& e)
00146 {
00147
00148 const TransientStatus bs = e->blinkStatus();
00149 if (transientStatusIsOn(bs)) itsEyeBlinkStatus = true;
00150 else if (transientStatusIsOff(bs)) itsEyeBlinkStatus = false;
00151 itsEyePos = e->position();
00152
00153 if (itsEyePos.isValid()) LDEBUG("Using eye position (%d, %d).", itsEyePos.i, itsEyePos.j);
00154
00155
00156
00157
00158 if (itsShiftInput.getVal() && itsRawInput.initialized() && q.now() != SimTime::ZERO()) {
00159
00160 const Image<PixRGB<byte> > outimg = getOutput(itsRawInput, itsEyePos, itsEyeBlinkStatus);
00161
00162
00163 InputFrame ifr = InputFrame::fromRgb(&outimg, q.now(), &itsClipMask,
00164 InputFrame::emptyCache, !itsEnablePyramidCaches.getVal());
00165 postInputFrame(q, ifr);
00166 }
00167 }
00168
00169
00170 void RetinaAdapter::onSimEventInputFrame(SimEventQueue& q, rutz::shared_ptr<SimEventInputFrame>& e)
00171 {
00172
00173 const Image<PixRGB<byte> > inimg = e->frame().asRgb();
00174
00175
00176 itsRawInput = inimg;
00177
00178
00179
00180 if (q.now() == SimTime::ZERO()) {
00181 itsEyePos = itsInitialEyePosition.getVal();
00182 if (itsEyePos.i == -2 && itsEyePos.j == -2) {
00183
00184 itsEyePos.i = inimg.getWidth() / 2; itsEyePos.j = inimg.getHeight() / 2;
00185
00186
00187 itsEyePos += getRawToRetinalOffset();
00188 }
00189 if (itsEyePos.isValid()) LDEBUG("Using eye position (%d, %d).", itsEyePos.i, itsEyePos.j);
00190 }
00191
00192
00193
00194
00195 if (itsShiftInput.getVal() == false || q.now() == SimTime::ZERO()) {
00196
00197 const Image<PixRGB<byte> > outimg = getOutput(inimg, itsEyePos, itsEyeBlinkStatus);
00198
00199
00200 InputFrame ifr =
00201 InputFrame::fromRgb(&outimg, q.now(), &itsClipMask, InputFrame::emptyCache, !itsEnablePyramidCaches.getVal());
00202 postInputFrame(q, ifr);
00203 }
00204 }
00205
00206
00207 void RetinaAdapter::postInputFrame(SimEventQueue& q, InputFrame& ifr)
00208 {
00209 rutz::shared_ptr<SimEventRetinaImage>
00210 eri(new SimEventRetinaImage(this, ifr, getRawInputRectangle(itsRawInput.getDims(), ifr.getDims()),
00211 getRawToRetinalOffset()));
00212 q.post(eri);
00213 }
00214
00215
00216 Rectangle RetinaAdapter::getRawInputRectangle(const Dims& indims, const Dims& outdims) const
00217 {
00218 const int border = itsRawInpRectBorder.getVal();
00219 Point2D<int> fpos = itsFramingImagePos.getVal();
00220 Rectangle r = Rectangle::tlbrI(itsRetinalShift.j + fpos.j + border,
00221 itsRetinalShift.i + fpos.i + border,
00222 itsRetinalShift.j + fpos.j + indims.h()-1 - border,
00223 itsRetinalShift.i + fpos.i + indims.w()-1 - border);
00224 return r.getOverlap(Rectangle(Point2D<int>(0,0), outdims));
00225 }
00226
00227
00228 Point2D<int> RetinaAdapter::getRawToRetinalOffset() const
00229 { return itsFramingImagePos.getVal() + itsRetinalShift; }
00230
00231
00232 void RetinaAdapter::start1()
00233 {
00234 if (!itsClipMaskFname.getVal().empty()) {
00235 itsClipMask = Raster::ReadGray(itsClipMaskFname.getVal());
00236 LINFO("Using clipmask from image file %s", itsClipMaskFname.getVal().c_str());
00237 }
00238
00239
00240 if (itsFramingImageName.getVal().empty() == false) {
00241 itsFramingImage = Raster::ReadRGB(itsFramingImageName.getVal());
00242 LINFO("Using %dx%d framing image %s", itsFramingImage.getWidth(),
00243 itsFramingImage.getHeight(), itsFramingImageName.getVal().c_str());
00244 }
00245
00246
00247 if (itsRetMaskFname.getVal().empty() == false) {
00248 itsRetMask = Raster::ReadGray(itsRetMaskFname.getVal());
00249 LINFO("Using %dx%d retinal mask %s", itsRetMask.getWidth(),
00250 itsRetMask.getHeight(), itsRetMaskFname.getVal().c_str());
00251 }
00252
00253 Retina::start1();
00254 }
00255
00256
00257 void RetinaAdapter::
00258 onSimEventSaveOutput(SimEventQueue& q, rutz::shared_ptr<SimEventSaveOutput>& e)
00259 {
00260 this->save1(e->sinfo());
00261 }
00262
00263
00264 void RetinaAdapter::save1(const ModelComponentSaveInfo& sinfo)
00265 {
00266
00267
00268 nub::ref<FrameOstream> ofs = dynamic_cast<const SimModuleSaveInfo&>(sinfo).ofs;
00269
00270
00271 if (itsSaveInput.getVal() && itsRawInput.initialized())
00272 ofs->writeRGB(itsRawInput, "RETIN-", FrameInfo("retinal input", SRC_POS));
00273
00274
00275 if (itsSaveOutput.getVal() && itsOutput.initialized())
00276 ofs->writeRGB(itsOutput, "RETOUT-", FrameInfo("retinal output", SRC_POS));
00277
00278
00279 if (itsSavePyr.getVal() && itsOutput.initialized())
00280 for (uint i = 0; i < itsMultiRetina.size(); i ++)
00281 ofs->writeRGB(itsMultiRetina[i], sformat("RET%d-", i),
00282 FrameInfo(sformat("pyramid level %d in RetinaStd", i), SRC_POS));
00283 }
00284
00285
00286 Image< PixRGB<byte> > RetinaAdapter::getOutput(const Image<PixRGB<byte> >& inp,
00287 const Point2D<int>& eye, const bool inBlink)
00288 {
00289
00290 Image< PixRGB<byte> > ret = inp;
00291
00292
00293 if (itsRetMask.initialized())
00294 {
00295 if (ret.isSameSize(itsRetMask) == false)
00296 LFATAL("Retina (%dx%d) and retinal mask (%dx%d) dim mismatch",
00297 ret.getWidth(), ret.getHeight(), itsRetMask.getWidth(), itsRetMask.getHeight());
00298
00299
00300 ret = (ret * itsRetMask) / 255;
00301 }
00302
00303
00304 if (itsFlipHoriz.getVal()) ret = flipHoriz(ret);
00305 if (itsFlipVertic.getVal()) ret = flipVertic(ret);
00306
00307
00308 if (itsFramingImage.initialized())
00309 {
00310 ret = itsFramingImage;
00311 inplacePaste(ret, inp, itsFramingImagePos.getVal());
00312 }
00313
00314
00315 if (itsShiftInput.getVal())
00316 {
00317
00318 if (eye.isValid())
00319 {
00320 LINFO("Shifting input to eye position (%d, %d)", eye.i, eye.j);
00321
00322 itsRetinalShift.i += ret.getWidth() / 2 - eye.i;
00323 itsRetinalShift.j += ret.getHeight() / 2 - eye.j;
00324
00325 ret = shiftClean(ret, itsRetinalShift.i, itsRetinalShift.j, itsShiftInputBGcol.getVal());
00326 }
00327
00328
00329 Dims fovd(itsInputFOV.getVal());
00330 if (fovd.isEmpty() == false)
00331 {
00332 Point2D<int> fovoff( (ret.getWidth() - fovd.w()) / 2, (ret.getHeight() - fovd.h()) / 2);
00333 if (eye.i != -1 || eye.j != -1) itsRetinalShift -= fovoff;
00334 ret = crop(ret, fovoff, fovd);
00335 }
00336 }
00337
00338
00339
00340 const uint fid = itsFoveateInputDepth.getVal();
00341 if (fid > 0)
00342 {
00343 LINFO("Initializing multiretina (depth %d)", fid);
00344 itsMultiRetina = buildPyrGaussian(ret, 0, fid, 9);
00345
00346
00347 Image<byte> mask(ret.getDims(), ZEROS);
00348
00349
00350 if (eye.isValid())
00351 {
00352 drawDisk(mask, eye, itsFoveaRadius.getVal(), byte(255));
00353 LINFO("Foveating input at (%d, %d)", eye.i, eye.j);
00354 }
00355 else
00356 LINFO("Foveating input with uniform medium blur");
00357
00358
00359
00360
00361 ret = foveate(mask, itsMultiRetina);
00362 }
00363
00364
00365 if (inBlink && itsBlankBlink.getVal()) {
00366 LINFO("#### Visual input blanked out while in blink ####");
00367 ret.clear(PixRGB<byte>(0));
00368 }
00369
00370
00371 ret = transform(ret);
00372
00373
00374 itsOutput = ret;
00375
00376
00377 return ret;
00378 }
00379
00380
00381
00382
00383
00384
00385 RetinaConfigurator::RetinaConfigurator(OptionManager& mgr, const std::string& descrName, const std::string& tagName) :
00386 ModelComponent(mgr, descrName, tagName),
00387 itsType(&OPT_RetinaType, this),
00388 itsRET(new RetinaStub(mgr))
00389 {
00390 addSubComponent(itsRET);
00391 }
00392
00393
00394 RetinaConfigurator::~RetinaConfigurator()
00395 { }
00396
00397
00398 nub::ref<Retina> RetinaConfigurator::getRET() const
00399 { return itsRET; }
00400
00401
00402 void RetinaConfigurator::paramChanged(ModelParamBase* const param,
00403 const bool valueChanged,
00404 ParamClient::ChangeStatus* status)
00405 {
00406 ModelComponent::paramChanged(param, valueChanged, status);
00407
00408
00409 if (param == &itsType) {
00410
00411 removeSubComponent(*itsRET);
00412
00413
00414
00415
00416 if (itsType.getVal().compare("Stub") == 0)
00417 itsRET.reset(new RetinaStub(getManager()));
00418 else if (itsType.getVal().compare("Std") == 0)
00419 itsRET.reset(new RetinaStd(getManager()));
00420 else if (itsType.getVal().compare("CT") == 0)
00421 itsRET.reset(new RetinaCT(getManager()));
00422 else
00423 LFATAL("Unknown Retina type %s", itsType.getVal().c_str());
00424
00425
00426
00427
00428
00429 addSubComponent(itsRET);
00430
00431
00432 itsRET->exportOptions(MC_RECURSE);
00433
00434
00435 LINFO("Selected RET of type %s", itsType.getVal().c_str());
00436 }
00437 }
00438
00439
00440
00441
00442
00443
00444 RetinaStub::RetinaStub(OptionManager& mgr, const std::string& descrName, const std::string& tagName) :
00445 Retina(mgr, descrName, tagName),
00446 SIMCALLBACK_INIT(SimEventInputFrame)
00447 { }
00448
00449
00450 RetinaStub::~RetinaStub()
00451 { }
00452
00453
00454 void RetinaStub::onSimEventInputFrame(SimEventQueue& q, rutz::shared_ptr<SimEventInputFrame>& e)
00455 {
00456 GenericFrame fr = e->frame();
00457 InputFrame ifr;
00458
00459 switch(fr.nativeType()) {
00460 case GenericFrame::RGBD:
00461 {
00462 const Image<PixRGB<byte> > inimg = fr.asRgb();
00463 const Image<uint16> dimg = fr.asGrayU16();
00464 ifr = InputFrame::fromRgbDepth(&inimg, &dimg, q.now());
00465 }
00466 break;
00467 default:
00468 {
00469 const Image<PixRGB<byte> > inimg = fr.asRgb();
00470 ifr = InputFrame::fromRgb(&inimg, q.now());
00471 }
00472 break;
00473 }
00474
00475
00476 Rectangle rect(Point2D<int>(0,0), ifr.getDims());
00477 rutz::shared_ptr<SimEventRetinaImage> eri(new SimEventRetinaImage(this, ifr, rect, Point2D<int>(0,0)));
00478 q.post(eri);
00479 }
00480
00481
00482
00483
00484
00485
00486 RetinaStd::RetinaStd(OptionManager& mgr, const std::string& descrName,
00487 const std::string& tagName) :
00488 RetinaAdapter(mgr, descrName, tagName)
00489 { }
00490
00491
00492 RetinaStd::~RetinaStd()
00493 { }
00494
00495
00496 Image<PixRGB<byte> > RetinaStd::transform(const Image<PixRGB<byte> >& image)
00497 { return image; }
00498
00499
00500
00501
00502
00503
00504 RetinaCT::RetinaCT(OptionManager& mgr, const std::string& descrName, const std::string& tagName) :
00505 RetinaAdapter(mgr, descrName, tagName),
00506 itsSurrFac(&OPT_SpaceVariantDogSize, this),
00507 itsLevels(&OPT_SpaceVariantChanScales, this),
00508 itsTransform(new SpaceVariantModule(mgr)),
00509 itsRgbCache(new PyramidCache<PixRGB<float> >), itsFloatCache(new PyramidCache<float>)
00510 {
00511 this->addSubComponent(itsTransform);
00512 }
00513
00514
00515 RetinaCT::~RetinaCT()
00516 { }
00517
00518
00519 void RetinaCT::start1()
00520 {
00521 getRootObject()->setModelParamVal("UseSpaceVariantBoundary", true, MC_RECURSE | MC_IGNORE_MISSING);
00522 RetinaAdapter::start1();
00523 }
00524
00525
00526 Rectangle RetinaCT::getRawInputRectangle(const Dims& indims, const Dims& outdims) const
00527 {
00528 Rectangle r(Point2D<int>(0,0), indims);
00529 return r;
00530 }
00531
00532
00533 void RetinaCT::postInputFrame(SimEventQueue& q, InputFrame& ifr)
00534 {
00535 ifr.setPyrCacheRgb(itsRgbCache);
00536 ifr.setPyrCache(itsFloatCache);
00537
00538 rutz::shared_ptr<SimEventRetinaImage>
00539 eri(new SimEventRetinaImage(this, ifr, getRawInputRectangle(itsRawInput.getDims(), ifr.getDims()),
00540 getRawToRetinalOffset(),itsTransform->getTransform(),itsTransform->getMapTransform()));
00541 q.post(eri);
00542 }
00543
00544
00545 Image<PixRGB<byte> > RetinaCT::transform(const Image<PixRGB<byte> >& inp)
00546 {
00547
00548 ImageSet<PixRGB<float> > rgbcache;
00549 rutz::mutex_lock_class lock;
00550 if (itsRgbCache->gaussian5.beginSet(inp, &lock))
00551 {
00552 const float maxrf = itsTransform->getMaxRf(itsLevels.getVal().getMaxVariance(), itsSurrFac.getVal());
00553 rgbcache = itsTransform->getScaleSpace(inp, maxrf);
00554 itsRgbCache->gaussian5.endSet(inp, rgbcache, &lock);
00555 }
00556
00557 Image<float> inpl = luminance(inp);
00558 if (itsFloatCache->gaussian5.beginSet(inpl, &lock))
00559 {
00560 ImageSet<float> floatcache(rgbcache.size());
00561 for (uint ii = 0; ii < floatcache.size(); ++ii)
00562 floatcache[ii] = luminance(rgbcache[ii]);
00563
00564 itsFloatCache->gaussian5.endSet(inpl, floatcache, &lock);
00565 }
00566
00567 Image<PixRGB<byte> > output = itsTransform->transform(inp, &rgbcache);
00568 return output;
00569 }
00570
00571
00572
00573
00574
00575