12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361 |
- /*!
- * MARTINS.js Free Edition version 0.1.2-wip
- * GPU-accelerated Augmented Reality for the web
- * Copyright 2022-2024 Alexandre Martins <alemartf(at)gmail.com> (https://github.com/alemart)
- * https://github.com/alemart/martins-js
- *
- * @license AGPL-3.0-only
- * Date: 2024-01-10T13:47:03.478Z
- */
- (function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object')
- module.exports = factory();
- else if(typeof define === 'function' && define.amd)
- define([], factory);
- else if(typeof exports === 'object')
- exports["Martins"] = factory();
- else
- root["Martins"] = factory();
- })(self, function() {
- return /******/ (() => { // webpackBootstrap
- /******/ var __webpack_modules__ = ({
-
- /***/ 528:
- /***/ ((module) => {
-
- /*!
- * Speedy Vision version 0.9.1-wip
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2024 Alexandre Martins <alemartf(at)gmail.com> (https://github.com/alemart)
- * https://github.com/alemart/speedy-vision
- *
- * @license Apache-2.0
- * Date: 2024-01-10T13:44:25.122Z
- */
- (function webpackUniversalModuleDefinition(root, factory) {
- if(true)
- module.exports = factory();
- else {}
- })(self, function() {
- return /******/ (() => { // webpackBootstrap
- /******/ var __webpack_modules__ = ({
-
- /***/ 3135:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_800__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_800__.d(__webpack_exports__, {
- /* harmony export */ "Z": () => (/* binding */ Settings)
- /* harmony export */ });
- /* harmony import */ var _speedy_namespace__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_800__(2411);
- /* harmony import */ var _gpu_speedy_gl__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_800__(7905);
- /* harmony import */ var _utils_utils__WEBPACK_IMPORTED_MODULE_2__ = __nested_webpack_require_800__(5484);
- /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_3__ = __nested_webpack_require_800__(3841);
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * settings.js
- * Global settings
- */
-
-
-
-
-
-
- /** @typedef {import('../gpu/speedy-gl').PowerPreference} PowerPreference */
- /** @typedef {"raf" | "asap"} GPUPollingMode */
- /** @typedef {"default" | "none" | "diagnostic"} LoggingMode */
-
- /** @type {GPUPollingMode} Default GPU polling mode */
- const DEFAULT_GPU_POLLING_MODE = 'raf';
-
- /** @type {GPUPollingMode} GPU polling mode */
- let gpuPollingMode = DEFAULT_GPU_POLLING_MODE;
-
- /** @type {LoggingMode} logging mode */
- let loggingMode = 'default';
-
-
- /**
- * Global settings
- */
- class Settings extends _speedy_namespace__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyNamespace */ .R
- {
- /**
- * Power preference of the WebGL context
- * @returns {PowerPreference}
- */
- static get powerPreference()
- {
- return _gpu_speedy_gl__WEBPACK_IMPORTED_MODULE_1__/* .SpeedyGL.powerPreference */ .$.powerPreference;
- }
-
- /**
- * Power preference of the WebGL context
- * @param {PowerPreference} value
- */
- static set powerPreference(value)
- {
- _gpu_speedy_gl__WEBPACK_IMPORTED_MODULE_1__/* .SpeedyGL.powerPreference */ .$.powerPreference = value;
- }
-
- /**
- * GPU polling mode
- * @returns {GPUPollingMode}
- */
- static get gpuPollingMode()
- {
- return gpuPollingMode;
- }
-
- /**
- * GPU polling mode
- * @param {GPUPollingMode} value
- */
- static set gpuPollingMode(value)
- {
- if(value !== 'raf' && value !== 'asap')
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_3__/* .IllegalArgumentError */ .mG(`Invalid GPU polling mode: "${value}"`);
-
- gpuPollingMode = value;
- }
-
- /**
- * Logging mode
- * @returns {LoggingMode}
- */
- static get logging()
- {
- return loggingMode;
- }
-
- /**
- * Logging mode
- * @param {LoggingMode} mode
- */
- static set logging(mode)
- {
- if(mode !== 'default' && mode !== 'none' && mode !== 'diagnostic')
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_3__/* .IllegalArgumentError */ .mG(`Invalid logging mode: "${mode}"`);
- else if(mode === 'diagnostic')
- _utils_utils__WEBPACK_IMPORTED_MODULE_2__/* .Utils.log */ .c.log('%c DIAGNOSTIC MODE ', 'background:red;color:white;font-size:36pt;font-weight:bold');
-
- loggingMode = mode;
- }
- }
-
- /***/ }),
-
- /***/ 5137:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_4472__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_4472__.d(__webpack_exports__, {
- /* harmony export */ "N": () => (/* binding */ SpeedyMatrixExpr)
- /* harmony export */ });
- /* harmony import */ var _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_4472__(4368);
- /* harmony import */ var _utils_utils__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_4472__(5484);
- /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_2__ = __nested_webpack_require_4472__(3841);
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-matrix-expr.js
- * Symbolic matrix expressions
- */
-
-
-
-
-
- /** @typedef {import('./speedy-matrix').SpeedyMatrixDtype} SpeedyMatrixDtype */
- /** @typedef {import('./speedy-matrix').SpeedyMatrixBufferType} SpeedyMatrixBufferType */
- /** @typedef {import('./speedy-matrix').SpeedyMatrixBufferTypeConstructor} SpeedyMatrixBufferTypeConstructor */
- /** @typedef {import('./speedy-matrix-wasm').SpeedyMatrixWASMMemory} SpeedyMatrixWASMMemory */
-
- /** @typedef {Object<SpeedyMatrixDtype,SpeedyMatrixBufferTypeConstructor>} Dtype2BufferType */
-
- /** @const {Dtype2BufferType} */
- const DTYPE_TO_BUFFER_TYPE = Object.freeze({
- 'float32': Float32Array
- });
-
-
- /**
- * @abstract Matrix expression
- * It's an opaque object representing an algebraic
- * expression. It has no data attached to it.
- */
- class SpeedyMatrixExpr
- {
- /**
- * Constructor
- * @param {number} rows
- * @param {number} columns
- * @param {SpeedyMatrixDtype} dtype
- */
- constructor(rows, columns, dtype)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(rows > 0 && columns > 0);
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(dtype === SpeedyMatrixExpr.DEFAULT_DTYPE); // we only support float32 for now
-
- /** @type {number} number of rows */
- this._rows = rows | 0;
-
- /** @type {number} number of columns */
- this._columns = columns | 0;
-
- /** @type {SpeedyMatrixDtype} data type */
- this._dtype = dtype;
- }
-
- /**
- * Number of rows
- * @returns {number}
- */
- get rows()
- {
- return this._rows;
- }
-
- /**
- * Number of columns
- * @returns {number}
- */
- get columns()
- {
- return this._columns;
- }
-
- /**
- * Data type
- * @returns {SpeedyMatrixDtype}
- */
- get dtype()
- {
- return this._dtype;
- }
-
- /**
- * Default data type
- * @returns {SpeedyMatrixDtype}
- */
- static get DEFAULT_DTYPE()
- {
- return 'float32';
- }
-
- /**
- * Buffer types
- * @returns {Dtype2BufferType}
- */
- static get BUFFER_TYPE()
- {
- return DTYPE_TO_BUFFER_TYPE;
- }
-
- /**
- * Matrix addition
- * @param {SpeedyMatrixExpr} expr
- * @returns {SpeedyMatrixExpr}
- */
- plus(expr)
- {
- return new SpeedyMatrixAddExpr(this, expr);
- }
-
- /**
- * Matrix subtraction
- * @param {SpeedyMatrixExpr} expr
- * @returns {SpeedyMatrixExpr}
- */
- minus(expr)
- {
- return new SpeedyMatrixSubtractExpr(this, expr);
- }
-
- /**
- * Matrix multiplication
- * @param {SpeedyMatrixExpr|number} expr
- * @returns {SpeedyMatrixExpr}
- */
- times(expr)
- {
- if(typeof expr === 'number')
- return new SpeedyMatrixScaleExpr(this, expr);
- else
- return new SpeedyMatrixMultiplyExpr(this, expr);
- }
-
- /**
- * Matrix transposition
- * @returns {SpeedyMatrixExpr}
- */
- transpose()
- {
- return new SpeedyMatrixTransposeExpr(this);
- }
-
- /**
- * Matrix inversion
- * @returns {SpeedyMatrixExpr}
- */
- inverse()
- {
- return new SpeedyMatrixInvertExpr(this);
- }
-
- /**
- * Component-wise multiplication
- * @param {SpeedyMatrixExpr} expr
- * @returns {SpeedyMatrixExpr}
- */
- compMult(expr)
- {
- return new SpeedyMatrixCompMultExpr(this, expr);
- }
-
- /**
- * Left division: A \ b, which is equivalent to (pseudo-)inverse(A) * b
- * @param {SpeedyMatrixExpr} expr
- * @returns {SpeedyMatrixExpr}
- */
- ldiv(expr)
- {
- return new SpeedyMatrixLdivExpr(this, expr);
- }
-
- /**
- * Returns a human-readable string representation of the matrix expression
- * @returns {string}
- */
- toString()
- {
- return `SpeedyMatrixExpr(rows=${this.rows}, columns=${this.columns})`;
- }
-
- /**
- * Evaluate this expression
- * @abstract
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @returns {SpeedyMatrix}
- */
- _evaluate(wasm, memory)
- {
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .AbstractMethodError */ .Mi();
- }
- }
-
- const { SpeedyMatrix } = __nested_webpack_require_4472__(8007);
-
- /**
- * @abstract operation storing a temporary matrix
- */
- class SpeedyMatrixTempExpr extends SpeedyMatrixExpr
- {
- /**
- * Constructor
- * @param {number} rows
- * @param {number} columns
- * @param {SpeedyMatrixDtype} dtype
- */
- constructor(rows, columns, dtype)
- {
- super(rows, columns, dtype);
-
- /** @type {SpeedyMatrix} holds the results of a computation */
- this._tempMatrix = SpeedyMatrix.Zeros(this.rows, this.columns, this.dtype);
- }
- }
-
- /**
- * @abstract unary operation
- */
- class SpeedyMatrixUnaryOperationExpr extends SpeedyMatrixTempExpr
- {
- /**
- * Constructor
- * @param {number} rows rows of the output matrix
- * @param {number} columns columns of the output matrix
- * @param {SpeedyMatrixExpr} operand
- */
- constructor(rows, columns, operand)
- {
- super(rows, columns, operand.dtype);
-
- /** @type {SpeedyMatrixExpr} operand */
- this._operand = operand;
- }
-
- /**
- * Evaluate this expression
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @returns {SpeedyMatrix}
- */
- _evaluate(wasm, memory)
- {
- const operand = this._operand._evaluate(wasm, memory);
- const result = this._tempMatrix;
-
- // allocate matrices
- const resultptr = _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.allocateMat32 */ .r.allocateMat32(wasm, memory, result);
- const operandptr = _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.allocateMat32 */ .r.allocateMat32(wasm, memory, operand);
-
- // copy operand to WASM memory
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.copyToMat32 */ .r.copyToMat32(wasm, memory, operandptr, operand);
-
- // run the WASM routine
- this._compute(wasm, memory, resultptr, operandptr);
-
- // copy result from WASM memory
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.copyFromMat32 */ .r.copyFromMat32(wasm, memory, resultptr, result);
-
- // deallocate matrices
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.deallocateMat32 */ .r.deallocateMat32(wasm, memory, operandptr);
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.deallocateMat32 */ .r.deallocateMat32(wasm, memory, resultptr);
-
- // done!
- return result;
- }
-
- /**
- * Compute the result of this operation
- * @abstract
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} operandptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, operandptr)
- {
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .AbstractMethodError */ .Mi();
- }
- }
-
- /**
- * @abstract binary operation
- */
- class SpeedyMatrixBinaryOperationExpr extends SpeedyMatrixTempExpr
- {
- /**
- * Constructor
- * @param {number} rows rows of the output matrix
- * @param {number} columns columns of the output matrix
- * @param {SpeedyMatrixExpr} left left operand
- * @param {SpeedyMatrixExpr} right right operand
- */
- constructor(rows, columns, left, right)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(left.dtype === right.dtype);
- super(rows, columns, left.dtype);
-
- /** @type {SpeedyMatrixExpr} left operand */
- this._left = left;
-
- /** @type {SpeedyMatrixExpr} right operand */
- this._right = right;
- }
-
- /**
- * Evaluate this expression
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @returns {SpeedyMatrix}
- */
- _evaluate(wasm, memory)
- {
- const left = this._left._evaluate(wasm, memory);
- const right = this._right._evaluate(wasm, memory);
- const result = this._tempMatrix;
-
- // allocate matrices
- const resultptr = _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.allocateMat32 */ .r.allocateMat32(wasm, memory, result);
- const leftptr = _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.allocateMat32 */ .r.allocateMat32(wasm, memory, left);
- const rightptr = _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.allocateMat32 */ .r.allocateMat32(wasm, memory, right);
-
- // copy input matrices to WASM memory
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.copyToMat32 */ .r.copyToMat32(wasm, memory, leftptr, left);
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.copyToMat32 */ .r.copyToMat32(wasm, memory, rightptr, right);
-
- // run the WASM routine
- this._compute(wasm, memory, resultptr, leftptr, rightptr);
-
- // copy output matrix from WASM memory
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.copyFromMat32 */ .r.copyFromMat32(wasm, memory, resultptr, result);
-
- // deallocate matrices
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.deallocateMat32 */ .r.deallocateMat32(wasm, memory, rightptr);
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.deallocateMat32 */ .r.deallocateMat32(wasm, memory, leftptr);
- _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixWASM.deallocateMat32 */ .r.deallocateMat32(wasm, memory, resultptr);
-
- // done!
- return result;
- }
-
- /**
- * Compute the result of this operation
- * @abstract
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} leftptr pointer to Mat32
- * @param {number} rightptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, leftptr, rightptr)
- {
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .AbstractMethodError */ .Mi();
- }
- }
-
- /**
- * Transpose matrix
- */
- class SpeedyMatrixTransposeExpr extends SpeedyMatrixUnaryOperationExpr
- {
- /**
- * Constructor
- * @param {SpeedyMatrixExpr} operand
- */
- constructor(operand)
- {
- super(operand.columns, operand.rows, operand);
- }
-
- /**
- * Compute result = operand^T
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} operandptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, operandptr)
- {
- wasm.exports.Mat32_transpose(resultptr, operandptr);
- }
- }
-
- /**
- * Invert square matrix
- */
- class SpeedyMatrixInvertExpr extends SpeedyMatrixUnaryOperationExpr
- {
- /**
- * Constructor
- * @param {SpeedyMatrixExpr} operand
- */
- constructor(operand)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(operand.rows === operand.columns);
- super(operand.rows, operand.columns, operand);
-
- /** @type {number} size of the matrix */
- this._size = operand.rows;
- }
-
- /**
- * Compute result = operand ^ (-1)
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} operandptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, operandptr)
- {
- switch(this._size) {
- case 0: break;
- case 1:
- wasm.exports.Mat32_inverse1(resultptr, operandptr);
- break;
-
- case 2:
- wasm.exports.Mat32_inverse2(resultptr, operandptr);
- break;
-
- case 3:
- wasm.exports.Mat32_inverse3(resultptr, operandptr);
- break;
-
- default:
- wasm.exports.Mat32_qr_inverse(resultptr, operandptr);
- break;
- }
- }
- }
-
- /**
- * Multiply matrix by a scalar value
- */
- class SpeedyMatrixScaleExpr extends SpeedyMatrixUnaryOperationExpr
- {
- /**
- * Constructor
- * @param {SpeedyMatrixExpr} operand
- * @param {number} scalar
- */
- constructor(operand, scalar)
- {
- super(operand.rows, operand.columns, operand);
-
- /** @type {number} scalar value */
- this._scalar = +scalar;
- }
-
- /**
- * Compute result = scalar * operand
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} operandptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, operandptr)
- {
- wasm.exports.Mat32_scale(resultptr, operandptr, this._scalar);
- }
- }
-
- /**
- * Matrix addition
- */
- class SpeedyMatrixAddExpr extends SpeedyMatrixBinaryOperationExpr
- {
- /**
- * Constructor
- * @param {SpeedyMatrixExpr} left left operand
- * @param {SpeedyMatrixExpr} right right operand
- */
- constructor(left, right)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(left.rows === right.rows && left.columns === right.columns);
- super(left.rows, left.columns, left, right);
- }
-
- /**
- * Compute result = left + right
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} leftptr pointer to Mat32
- * @param {number} rightptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, leftptr, rightptr)
- {
- wasm.exports.Mat32_add(resultptr, leftptr, rightptr);
- }
- }
-
- /**
- * Matrix subtraction
- */
- class SpeedyMatrixSubtractExpr extends SpeedyMatrixBinaryOperationExpr
- {
- /**
- * Constructor
- * @param {SpeedyMatrixExpr} left left operand
- * @param {SpeedyMatrixExpr} right right operand
- */
- constructor(left, right)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(left.rows === right.rows && left.columns === right.columns);
- super(left.rows, left.columns, left, right);
- }
-
- /**
- * Compute result = left - right
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} leftptr pointer to Mat32
- * @param {number} rightptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, leftptr, rightptr)
- {
- wasm.exports.Mat32_subtract(resultptr, leftptr, rightptr);
- }
- }
-
- /**
- * Matrix multiplication
- */
- class SpeedyMatrixMultiplyExpr extends SpeedyMatrixBinaryOperationExpr
- {
- /**
- * Constructor
- * @param {SpeedyMatrixExpr} left left operand
- * @param {SpeedyMatrixExpr} right right operand
- */
- constructor(left, right)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(left.columns === right.rows);
- super(left.rows, right.columns, left, right);
- }
-
- /**
- * Compute result = left * right
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} leftptr pointer to Mat32
- * @param {number} rightptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, leftptr, rightptr)
- {
- wasm.exports.Mat32_multiply(resultptr, leftptr, rightptr);
- }
- }
-
- /**
- * Component-wise multiplication
- */
- class SpeedyMatrixCompMultExpr extends SpeedyMatrixBinaryOperationExpr
- {
- /**
- * Constructor
- * @param {SpeedyMatrixExpr} left left operand
- * @param {SpeedyMatrixExpr} right right operand
- */
- constructor(left, right)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(left.rows === right.rows && left.columns === right.columns);
- super(right.rows, right.columns, left, right);
- }
-
- /**
- * Compute result = left <compMult> right
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} leftptr pointer to Mat32
- * @param {number} rightptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, leftptr, rightptr)
- {
- wasm.exports.Mat32_compmult(resultptr, leftptr, rightptr);
- }
- }
-
- /**
- * Left-division. A \ b is equivalent to (pseudo-)inverse(A) * b
- */
- class SpeedyMatrixLdivExpr extends SpeedyMatrixBinaryOperationExpr
- {
- /**
- * Constructor
- * @param {SpeedyMatrixExpr} left left operand
- * @param {SpeedyMatrixExpr} right right operand
- */
- constructor(left, right)
- {
- const m = left.rows, n = left.columns;
-
- // TODO right doesn't need to be a column vector
- _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.assert */ .c.assert(m >= n && right.rows === m && right.columns === 1);
- super(n, 1, left, right);
- }
-
- /**
- * Compute result = left \ right
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} resultptr pointer to Mat32
- * @param {number} leftptr pointer to Mat32
- * @param {number} rightptr pointer to Mat32
- */
- _compute(wasm, memory, resultptr, leftptr, rightptr)
- {
- wasm.exports.Mat32_qr_ols(resultptr, leftptr, rightptr, 2);
- }
- }
-
- /***/ }),
-
- /***/ 4368:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_23353__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_23353__.d(__webpack_exports__, {
- /* harmony export */ "r": () => (/* binding */ SpeedyMatrixWASM)
- /* harmony export */ });
- /* harmony import */ var _speedy_promise__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_23353__(4500);
- /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_23353__(3841);
- /* harmony import */ var _utils_utils__WEBPACK_IMPORTED_MODULE_2__ = __nested_webpack_require_23353__(5484);
- /* harmony import */ var _utils_globals__WEBPACK_IMPORTED_MODULE_3__ = __nested_webpack_require_23353__(3020);
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-matrix-wasm.js
- * WebAssembly bridge
- */
-
-
-
-
-
-
- /** @typedef {import('./speedy-matrix').SpeedyMatrix} SpeedyMatrix */
-
- /**
- * @typedef {object} SpeedyMatrixWASMMemory a union-like helper for accessing a WebAssembly.Memory object
- * @property {object} as
- * @property {WebAssembly.Memory} as.object
- * @property {Uint8Array} as.uint8
- * @property {Int32Array} as.int32
- * @property {Uint32Array} as.uint32
- * @property {Float32Array} as.float32
- * @property {Float64Array} as.float64
- */
-
- /**
- * @typedef {object} SpeedyMatrixWASMHandle
- * @property {WebAssembly.Instance} wasm
- * @property {SpeedyMatrixWASMMemory} memory
- * @property {WebAssembly.Module} module
- */
-
- /** @type {Uint8Array} WebAssembly binary */
- const WASM_BINARY = __nested_webpack_require_23353__(4209);
-
- /** @type {WebAssembly.Instance|null} WebAssembly Instance, to be loaded asynchronously */
- let _instance = null;
-
- /** @type {WebAssembly.Module|null} WebAssembly Module, to be loaded asynchronously */
- let _module = null;
-
- /** @type {SpeedyMatrixWASMMemory} Augmented WebAssembly Memory object */
- const _memory = (mem => ({
- as: {
- object: mem,
- uint8: new Uint8Array(mem.buffer),
- int32: new Int32Array(mem.buffer),
- uint32: new Uint32Array(mem.buffer),
- float32: new Float32Array(mem.buffer),
- float64: new Float64Array(mem.buffer),
- },
- }))(new WebAssembly.Memory({
- initial: 16, // 1 MB
- maximum: 256
- }));
-
- /**
- * WebAssembly utilities
- */
- class SpeedyMatrixWASM
- {
- /**
- * Gets you the WASM instance, augmented memory & module
- * @returns {SpeedyPromise<SpeedyMatrixWASMHandle>}
- */
- static ready()
- {
- return new _speedy_promise__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyPromise */ .s((resolve, reject) => {
- SpeedyMatrixWASM._ready(resolve, reject);
- });
- }
-
- /**
- * Synchronously gets you the WASM instance, augmented memory & module
- * @returns {SpeedyMatrixWASMHandle}
- */
- static get handle()
- {
- if(!_instance || !_module)
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__/* .WebAssemblyError */ .IT(`Can't get WASM handle: routines not yet loaded`);
-
- return {
- wasm: _instance,
- memory: _memory,
- module: _module,
- };
- }
-
- /**
- * Gets you the WASM imports bound to a memory object
- * @param {SpeedyMatrixWASMMemory} memory
- * @returns {Object<string,Function>}
- */
- static imports(memory)
- {
- const obj = new SpeedyMatrixWASMImports(memory);
-
- return Object.getOwnPropertyNames(SpeedyMatrixWASMImports.prototype)
- .filter(property => typeof obj[property] === 'function' && property !== 'constructor')
- .reduce(
- (imports, methodName) => ((imports[methodName] = obj[methodName]), imports),
- Object.create(null)
- );
- }
-
- /**
- * Allocate a Mat32 in WebAssembly memory without copying any data
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {SpeedyMatrix} matrix
- * @returns {number} pointer to the new Mat32
- */
- static allocateMat32(wasm, memory, matrix)
- {
- const dataptr = wasm.exports.malloc(matrix.data.byteLength);
- const matptr = wasm.exports.Mat32_create(matrix.rows, matrix.columns, matrix.step0, matrix.step1, matrix._data.length, dataptr);
-
- return matptr;
- }
-
- /**
- * Deallocate a Mat32 in WebAssembly
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} matptr pointer to the allocated Mat32
- * @returns {number} NULL
- */
- static deallocateMat32(wasm, memory, matptr)
- {
- const dataptr = wasm.exports.Mat32_data(matptr);
-
- wasm.exports.free(matptr);
- wasm.exports.free(dataptr);
-
- return 0;
- }
-
- /**
- * Copy the data of a matrix to a WebAssembly Mat32
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} matptr pointer to a Mat32
- * @param {SpeedyMatrix} matrix
- * @returns {number} matptr
- */
- static copyToMat32(wasm, memory, matptr, matrix)
- {
- // We assume the following:
- // 1. the host uses little-endian byte ordering (just like WebAssembly)
- // 2. the allocated pointers are 4-byte aligned (the bump allocator guarantees this)
- // 3. the data type is float32
-
- _utils_utils__WEBPACK_IMPORTED_MODULE_2__/* .Utils.assert */ .c.assert(
- //matrix.dtype === 'float32' &&
- matrix.data.byteLength === wasm.exports.Mat32_dataSize(matptr)
- );
-
- const dataptr = wasm.exports.Mat32_data(matptr);
- memory.as.float32.set(matrix.data, dataptr / Float32Array.BYTES_PER_ELEMENT);
-
- return matptr;
- }
-
- /**
- * Copy the data of a WebAssembly Mat32 to a matrix
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @param {number} matptr pointer to a Mat32
- * @param {SpeedyMatrix} matrix
- * @returns {number} matptr
- */
- static copyFromMat32(wasm, memory, matptr, matrix)
- {
- // We assume the following:
- // 1. the host uses little-endian byte ordering (just like WebAssembly)
- // 2. the allocated pointers are 4-byte aligned (the bump allocator guarantees this)
- // 3. the data type is float32
-
- _utils_utils__WEBPACK_IMPORTED_MODULE_2__/* .Utils.assert */ .c.assert(
- //matrix.dtype === 'float32' &&
- matrix.data.byteLength === wasm.exports.Mat32_dataSize(matptr)
- );
-
- const base = wasm.exports.Mat32_data(matptr) / Float32Array.BYTES_PER_ELEMENT;
- for(let offset = matrix.data.length - 1; offset >= 0; offset--)
- matrix.data[offset] = memory.as.float32[base + offset];
-
- return matptr;
- }
-
- /**
- * Polls the WebAssembly instance until it's ready
- * @param {function(SpeedyMatrixWASMHandle): void} resolve
- * @param {function(Error): void} reject
- * @param {number} [counter]
- */
- static _ready(resolve, reject, counter = 1000)
- {
- if(_instance !== null && _module !== null)
- resolve({ wasm: _instance, memory: _memory, module: _module });
- else if(counter <= 0)
- reject(new _utils_errors__WEBPACK_IMPORTED_MODULE_1__/* .TimeoutError */ .W5(`Can't load WASM routines`));
- else
- setTimeout(SpeedyMatrixWASM._ready, 0, resolve, reject, counter - 1);
- }
- }
-
- /**
- * Methods called from WASM
- */
- class SpeedyMatrixWASMImports
- {
- /**
- * Constructor
- * @param {SpeedyMatrixWASMMemory} memory will be bound to this object
- */
- constructor(memory)
- {
- // find all methods of this object
- const methodNames = Object.getOwnPropertyNames(this.constructor.prototype)
- .filter(property => typeof this[property] === 'function')
- .filter(property => property !== 'constructor');
-
- // bind all methods to this object
- methodNames.forEach(methodName => {
- this[methodName] = this[methodName].bind(this);
- });
-
- /** @type {SpeedyMatrixWASMMemory} WASM memory */
- this.memory = memory;
-
- /** @type {CStringUtils} utilities related to C strings */
- this.cstring = new CStringUtils(memory);
-
- // done!
- return Object.freeze(this);
- }
-
- /**
- * Prints a message
- * @param {number} ptr pointer to char
- */
- print(ptr)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_2__/* .Utils.log */ .c.log(this.cstring.get(ptr));
- }
-
- /**
- * Throws an error
- * @param {number} ptr pointer to char
- */
- fatal(ptr)
- {
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__/* .WebAssemblyError */ .IT(this.cstring.get(ptr));
- }
-
- /**
- * Fills a memory segment with a byte
- * @param {number} value byte
- * @param {number} start memory address, inclusive
- * @param {number} end memory address greater than start, exclusive
- */
- bytefill(value, start, end)
- {
- this.memory.as.uint8.fill(value, start, end);
- }
-
- /**
- * Copy a memory segment to another segment
- * @param {number} target memory address, where we'll start writing
- * @param {number} start memory address, where we'll start copying (inclusive)
- * @param {number} end memory address, where we'll end the copy (exclusive)
- */
- copyWithin(target, start, end)
- {
- this.memory.as.uint8.copyWithin(target, start, end);
- }
- }
-
- /**
- * Utilities related to C strings
- */
- class CStringUtils
- {
- /**
- * Constructor
- * @param {SpeedyMatrixWASMMemory} memory
- */
- constructor(memory)
- {
- /** @type {TextDecoder} */
- this._decoder = new TextDecoder('utf-8');
-
- /** @type {SpeedyMatrixWASMMemory} */
- this._memory = memory;
- }
-
- /**
- * Convert a C string to a JavaScript string
- * @param {number} ptr pointer to char
- * @returns {string}
- */
- get(ptr)
- {
- const byte = this._memory.as.uint8;
- const size = this._memory.as.uint8.byteLength;
-
- let p = ptr;
- while(p < size && 0 !== byte[p])
- ++p;
-
- return this._decoder.decode(byte.subarray(ptr, p));
- }
- }
-
- /**
- * WebAssembly loader
- * @param {SpeedyMatrixWASMMemory} memory
- */
- (function loadWASM(memory) {
- const base64decode = data => Uint8Array.from(atob(data), v => v.charCodeAt(0));
-
- // Endianness check
- if(!_utils_globals__WEBPACK_IMPORTED_MODULE_3__.LITTLE_ENDIAN)
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__/* .NotSupportedError */ .B8(`Can't run WebAssembly code: not in a little-endian machine!`);
-
- // Load the WASM binary
- _speedy_promise__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyPromise.resolve */ .s.resolve(WASM_BINARY)
- .then(data => base64decode(data))
- .then(bytes => WebAssembly.instantiate(bytes, {
- env: {
- memory: memory.as.object,
- ...SpeedyMatrixWASM.imports(memory),
- }
- }))
- .then(wasm => {
- _instance = wasm.instance;
- _module = wasm.module;
-
- wasm.instance.exports.srand((Date.now() * 0.001) & 0xffffffff); // srand(time(NULL))
-
- _utils_utils__WEBPACK_IMPORTED_MODULE_2__/* .Utils.log */ .c.log(`The WebAssembly routines have been loaded!`);
- })
- .catch(err => {
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__/* .WebAssemblyError */ .IT(`Can't load the WebAssembly routines: ${err}`, err);
- });
- })(_memory);
-
-
- /***/ }),
-
- /***/ 8007:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_35355__) => {
-
- "use strict";
- __nested_webpack_require_35355__.r(__webpack_exports__);
- /* harmony export */ __nested_webpack_require_35355__.d(__webpack_exports__, {
- /* harmony export */ "SpeedyMatrix": () => (/* binding */ SpeedyMatrix)
- /* harmony export */ });
- /* harmony import */ var _speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_35355__(5137);
- /* harmony import */ var _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_35355__(4368);
- /* harmony import */ var _speedy_promise__WEBPACK_IMPORTED_MODULE_2__ = __nested_webpack_require_35355__(4500);
- /* harmony import */ var _utils_utils__WEBPACK_IMPORTED_MODULE_3__ = __nested_webpack_require_35355__(5484);
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-matrix.js
- * Matrix class
- */
-
-
-
-
-
-
- /** @typedef {"float32"} SpeedyMatrixDtype Matrix data type */
- /** @typedef {Float32Array} SpeedyMatrixBufferType Buffer type */
- /** @typedef {Float32ArrayConstructor} SpeedyMatrixBufferTypeConstructor Buffer class */
- /** @typedef {import('./speedy-matrix-wasm').SpeedyMatrixWASMMemory} SpeedyMatrixWASMMemory */
- /** @typedef {import('./speedy-matrix-wasm').SpeedyMatrixWASMHandle} SpeedyMatrixWASMHandle */
-
- /**
- * Matrix class
- */
- class SpeedyMatrix extends _speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr */ .N
- {
- /**
- * @private
- *
- * Low-level constructor
- * @param {number} rows number of rows
- * @param {number} columns number of columns
- * @param {number} step0 step size between two consecutive elements (e.g., 1)
- * @param {number} step1 step size between two consecutive columns (e.g., rows)
- * @param {SpeedyMatrixBufferType} data entries in column-major format
- */
- constructor(rows, columns, step0, step1, data)
- {
- super(rows, columns, _speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.DEFAULT_DTYPE */ .N.DEFAULT_DTYPE);
-
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(data.constructor === _speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE[this.dtype]);
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(step0 > 0 && step1 >= step0);
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(
- data.length + rows * columns === 0 || // empty matrix and empty buffer, or
- data.length === 1 + step0 * (rows - 1) + step1 * (columns - 1) // correctly sized buffer
- );
-
- /** @type {number} step size between two consecutive elements */
- this._step0 = step0 | 0;
-
- /** @type {number} step size between two consecutive columns */
- this._step1 = step1 | 0;
-
- /** @type {SpeedyMatrixBufferType} buffer containing the entries of the matrix in column-major order */
- this._data = data;
- }
-
- /**
- * Create a new matrix with the specified size and entries
- * @param {number} rows number of rows
- * @param {number} columns number of columns
- * @param {number[]} entries in column-major format
- * @param {SpeedyMatrixDtype} [dtype] data type
- * @returns {SpeedyMatrix}
- */
- static Create(rows, columns, entries, dtype = _speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.DEFAULT_DTYPE */ .N.DEFAULT_DTYPE)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(rows * columns > 0, `Can't create a matrix without a shape`);
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(rows * columns === entries.length, `Can't create matrix: expected ${rows * columns} entries, but found ${entries.length}`);
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(Object.prototype.hasOwnProperty.call(_speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE, dtype), `Invalid dtype: "${dtype}"`);
-
- return new SpeedyMatrix(rows, columns, 1, rows, Reflect.construct(_speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE[dtype], [entries]));
- }
-
- /**
- * Create a new matrix filled with zeros with the specified size
- * @param {number} rows number of rows
- * @param {number} [columns] number of columns
- * @param {SpeedyMatrixDtype} [dtype] data type
- * @returns {SpeedyMatrix}
- */
- static Zeros(rows, columns = rows, dtype = _speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.DEFAULT_DTYPE */ .N.DEFAULT_DTYPE)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(rows * columns > 0, `Can't create a matrix without a shape`);
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(Object.prototype.hasOwnProperty.call(_speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE, dtype), `Invalid dtype: "${dtype}"`);
-
- return new SpeedyMatrix(rows, columns, 1, rows, Reflect.construct(_speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE[dtype], [rows * columns]));
- }
-
- /**
- * Create a new matrix filled with ones with the specified size
- * @param {number} rows number of rows
- * @param {number} [columns] number of columns
- * @param {SpeedyMatrixDtype} [dtype] data type
- * @returns {SpeedyMatrix}
- */
- static Ones(rows, columns = rows, dtype = _speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.DEFAULT_DTYPE */ .N.DEFAULT_DTYPE)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(rows * columns > 0, `Can't create a matrix without a shape`);
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(Object.prototype.hasOwnProperty.call(_speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE, dtype), `Invalid dtype: "${dtype}"`);
-
- return new SpeedyMatrix(rows, columns, 1, rows, Reflect.construct(_speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE[dtype], [rows * columns]).fill(1));
- }
-
- /**
- * Create a new identity matrix with the specified size
- * @param {number} rows number of rows
- * @param {number} [columns] number of columns
- * @param {SpeedyMatrixDtype} [dtype] data type
- * @returns {SpeedyMatrix}
- */
- static Eye(rows, columns = rows, dtype = _speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.DEFAULT_DTYPE */ .N.DEFAULT_DTYPE)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(rows * columns > 0, `Can't create a matrix without a shape`);
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(Object.prototype.hasOwnProperty.call(_speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE, dtype), `Invalid dtype: "${dtype}"`);
-
- const data = Reflect.construct(_speedy_matrix_expr__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyMatrixExpr.BUFFER_TYPE */ .N.BUFFER_TYPE[dtype], [rows * columns]);
- for(let j = Math.min(rows, columns) - 1; j >= 0; j--)
- data[j * rows + j] = 1;
-
- return new SpeedyMatrix(rows, columns, 1, rows, data);
- }
-
- /**
- * Evaluate an expression synchronously and store the result in a new matrix
- * @param {SpeedyMatrixExpr} expr matrix expression
- * @returns {SpeedyMatrix}
- */
- static From(expr)
- {
- return SpeedyMatrix.Zeros(expr.rows, expr.columns, expr.dtype).setToSync(expr);
- }
-
- /**
- * Returns a promise that resolves immediately if the WebAssembly routines
- * are ready to be used, or as soon as they do become ready
- * @returns {SpeedyPromise<void>}
- */
- static ready()
- {
- return _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_1__/* .SpeedyMatrixWASM.ready */ .r.ready().then(_ => void(0));
- }
-
- /**
- * Get the underlying buffer
- * @returns {SpeedyMatrixBufferType}
- */
- get data()
- {
- return this._data;
- }
-
- /**
- * Row-step
- * @returns {number} defaults to 1
- */
- get step0()
- {
- return this._step0;
- }
-
- /**
- * Column-step
- * @returns {number} defaults to this.rows
- */
- get step1()
- {
- return this._step1;
- }
-
- /**
- * Extract a block from this matrix. Use a shared underlying buffer
- * @param {number} firstRow
- * @param {number} lastRow
- * @param {number} firstColumn
- * @param {number} lastColumn
- * @returns {SpeedyMatrix}
- */
- block(firstRow, lastRow, firstColumn, lastColumn)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(
- firstRow <= lastRow && firstColumn <= lastColumn,
- `Invalid indices: [${firstRow}:${lastRow},${firstColumn}:${lastColumn}]`
- );
-
- // ensure that the indices are within bounds
- firstRow = Math.max(firstRow, 0);
- lastRow = Math.min(lastRow, this._rows - 1);
- firstColumn = Math.max(firstColumn, 0);
- lastColumn = Math.min(lastColumn, this._columns - 1);
-
- // compute the dimensions of the new submatrix
- const rows = lastRow - firstRow + 1;
- const columns = lastColumn - firstColumn + 1;
-
- // obtain the relevant portion of the data
- const step0 = this._step0, step1 = this._step1;
- const begin = firstRow * step0 + firstColumn * step1; // inclusive
- const end = 1 + lastRow * step0 + lastColumn * step1; // exclusive
-
- // create new matrix
- return new SpeedyMatrix(rows, columns, step0, step1, this._data.subarray(begin, end));
- }
-
- /**
- * Extract a row from this matrix
- * @param {number} index 0-based
- * @returns {SpeedyMatrix}
- */
- row(index)
- {
- return this.block(index, index, 0, this._columns - 1);
- }
-
- /**
- * Extract a column from this matrix
- * @param {number} index 0-based
- * @returns {SpeedyMatrix}
- */
- column(index)
- {
- return this.block(0, this._rows - 1, index, index);
- }
-
- /**
- * Extract the main diagonal from this matrix
- * @returns {SpeedyMatrix} as a column-vector
- */
- diagonal()
- {
- const diagsize = Math.min(this._rows, this._columns);
-
- // compute the dimensions of the new submatrix
- const rows = diagsize; // make it a column vector
- const columns = 1;
-
- // obtain the relevant portion of the data
- const diagstep = this._step0 + this._step1; // jump a row and a column
- const begin = 0; // inclusive
- const end = 1 + (diagsize - 1) * diagstep; // exclusive
-
- // create new matrix
- return new SpeedyMatrix(rows, columns, diagstep, diagstep, this._data.subarray(begin, end));
- }
-
- /**
- * Read a single entry of this matrix
- * @param {number} row 0-based index
- * @param {number} column 0-based index
- * @returns {number}
- */
- at(row, column)
- {
- if(row >= 0 && row < this._rows && column >= 0 && column < this._columns)
- return this._data[this._step0 * row + this._step1 * column];
- else
- return Number.NaN;
- }
-
- /**
- * Read the entries of the matrix in column-major format
- * @returns {number[]}
- */
- read()
- {
- const entries = new Array(this._rows * this._columns);
- const step0 = this._step0, step1 = this._step1;
- let i = 0;
-
- for(let column = 0; column < this._columns; column++) {
- for(let row = 0; row < this._rows; row++)
- entries[i++] = this._data[row * step0 + column * step1];
- }
-
- return entries;
- }
-
- /**
- * Returns a human-readable string representation of the matrix
- * @returns {string}
- */
- toString()
- {
- const DECIMALS = 5;
- const rows = this.rows, columns = this.columns;
- const entries = this.read();
- const mat = /** @type {number[][]} */ ( new Array(rows) );
-
- for(let i = 0; i < rows; i++) {
- mat[i] = new Array(columns);
- for(let j = 0; j < columns; j++)
- mat[i][j] = entries[j * rows + i];
- }
-
- const fix = x => x.toFixed(DECIMALS);
- const fmt = mat.map(row => ' ' + row.map(fix).join(', ')).join(',\n');
- const str = `SpeedyMatrix(rows=${rows}, columns=${columns}, data=[\n${fmt}\n])`;
-
- return str;
- }
-
- /**
- * Set the contents of this matrix to the result of an expression
- * @param {SpeedyMatrixExpr} expr matrix expression
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to this
- */
- setTo(expr)
- {
- return _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_1__/* .SpeedyMatrixWASM.ready */ .r.ready().then(_ => {
-
- // TODO: add support for WebWorkers
- return this.setToSync(expr);
-
- });
- }
-
- /**
- * Synchronously set the contents of this matrix to the result of an expression
- * @param {SpeedyMatrixExpr} expr matrix expression
- * @returns {SpeedyMatrix} this
- */
- setToSync(expr)
- {
- const { wasm, memory } = _speedy_matrix_wasm__WEBPACK_IMPORTED_MODULE_1__/* .SpeedyMatrixWASM.handle */ .r.handle;
-
- // evaluate the expression
- const result = expr._evaluate(wasm, memory);
-
- /*
- // shallow copy the results to this matrix
- // limitation: can't handle blocks properly
- // (a tree-like structure could be useful)
- this._rows = result.rows;
- this._columns = result.columns;
- //this._dtype = result.dtype;
- this._data = result.data;
- this._step0 = result.step0;
- this._step1 = result.step1;
- */
-
- // validate shape
- _utils_utils__WEBPACK_IMPORTED_MODULE_3__/* .Utils.assert */ .c.assert(
- this._rows === result._rows && this._columns === result._columns && this.dtype === result.dtype,
- `Can't set the values of a ${this.rows} x ${this.columns} ${this.dtype} matrix to those of a ${result.rows} x ${result.columns} ${result.dtype} matrix`
- );
-
- // deep copy
- const step0 = this._step0, step1 = this._step1, rstep0 = result._step0, rstep1 = result._step1;
- if(step0 === rstep0 && step1 === rstep1 && this._data.length === result._data.length) {
- // fast copy
- this._data.set(result._data);
- }
- else {
- // copy each element
- for(let column = this._columns - 1; column >= 0; column--) {
- for(let row = this._rows - 1; row >= 0; row--)
- this._data[row * step0 + column * step1] = result._data[row * rstep0 + column * rstep1];
- }
- }
-
- // done!
- return this;
- }
-
- /**
- * Fill this matrix with a scalar value
- * @param {number} value
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to this
- */
- fill(value)
- {
- this.fillSync(value);
- return _speedy_promise__WEBPACK_IMPORTED_MODULE_2__/* .SpeedyPromise.resolve */ .s.resolve(this);
- }
-
- /**
- * Synchronously fill this matrix with a scalar value
- * @param {number} value
- * @returns {SpeedyMatrix} this
- */
- fillSync(value)
- {
- value = +value;
-
- if(this._rows * this._columns === this._data.length) {
- this._data.fill(value);
- return this;
- }
-
- for(let column = 0; column < this._columns; column++) {
- for(let row = 0; row < this._rows; row++) {
- this._data[row * this._step0 + column * this._step1] = value;
- }
- }
-
- return this;
- }
-
- /**
- * Evaluate this expression
- * @param {WebAssembly.Instance} wasm
- * @param {SpeedyMatrixWASMMemory} memory
- * @returns {SpeedyMatrix}
- */
- _evaluate(wasm, memory)
- {
- return this;
- }
- }
-
-
- /***/ }),
-
- /***/ 2411:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_52046__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_52046__.d(__webpack_exports__, {
- /* harmony export */ "R": () => (/* binding */ SpeedyNamespace)
- /* harmony export */ });
- /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_52046__(3841);
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-namespace.js
- * Symbolizes a namespace
- */
-
-
-
- /**
- * An abstract namespace
- * @abstract
- */
- class SpeedyNamespace
- {
- /**
- * Namespaces can't be instantiated.
- * Only static methods are allowed.
- * @abstract
- * @throws SpeedyError
- */
- constructor()
- {
- // only static methods are allowed
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__/* .AbstractMethodError */ .Mi(`Namespaces can't be instantiated`);
- }
- }
-
- /***/ }),
-
- /***/ 4500:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_53588__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_53588__.d(__webpack_exports__, {
- /* harmony export */ "s": () => (/* binding */ SpeedyPromise)
- /* harmony export */ });
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-promise.js
- * Speedy Promises: a fast implementation of Promises
- */
-
- const PENDING = 0;
- const FULFILLED = 1;
- const REJECTED = 2;
-
- const SUSPEND_ASYNC = 1;
- const asap = (typeof queueMicrotask !== 'undefined' && queueMicrotask) || // browsers
- (typeof process !== 'undefined' && process.nextTick) || // node.js
- (f => Promise.resolve().then(() => f())); // most compatible
-
- /**
- * SpeedyPromise: Super Fast Promises. SpeedyPromises can
- * interoperate with ES6 Promises. This implementation is
- * based on the Promises/A+ specification.
- * @template T
- */
- class SpeedyPromise
- {
- /**
- * Constructor
- * @param {function(function(T=): void, function(Error): void): void} callback
- */
- constructor(callback)
- {
- this._state = PENDING;
- this._value = undefined;
-
- this._onFulfillment = null;
- this._onRejection = null;
- this._children = 0;
- this[0] = this;
- this._parent = undefined;
- this._flags = 0;
-
- this._fulfill = this._fulfill.bind(this);
- this._reject = this._reject.bind(this);
- this._resolve = this._resolve.bind(this);
- this._broadcastIfAsync = this._broadcastIfAsync.bind(this);
-
- callback(this._fulfill, this._reject);
- }
-
- /**
- * Setup handlers
- * @template U, V=never
- * @param {null|undefined|(function(T): U|PromiseLike<U>|SpeedyPromise<U>)} onFulfillment called when the SpeedyPromise is fulfilled
- * @param {null|undefined|(function(Error): V|PromiseLike<V>|SpeedyPromise<V>)} [onRejection] called when the SpeedyPromise is rejected
- * @returns {SpeedyPromise<U>}
- */
- then(onFulfillment, onRejection = null)
- {
- const child = new SpeedyPromise(this._nop);
- child._onFulfillment = typeof onFulfillment === 'function' && onFulfillment;
- child._onRejection = typeof onRejection === 'function' && onRejection;
- child._parent = this;
-
- this[this._children++] = child; // attach child
- this._flags &= ~SUSPEND_ASYNC; // restore the async behavior
- this._notify();
-
- return child;
- }
-
- /**
- * Setup rejection handler
- * @template U, V=never
- * @param {null|undefined|(function(Error): V|PromiseLike<V>|SpeedyPromise<V>)} [onRejection] called when the SpeedyPromise is rejected
- * @returns {SpeedyPromise<V>}
- */
- catch(onRejection)
- {
- return this.then(null, onRejection);
- }
-
- /**
- * Execute a callback when the promise is settled
- * (i.e., fulfilled or rejected)
- * @param {function(): void} onFinally
- * @returns {SpeedyPromise<T>}
- */
- finally(onFinally)
- {
- const fn = val => { onFinally(); return val; };
- return this.then(fn, fn);
- }
-
- /**
- * Start the computation immediately, synchronously.
- * Can't afford to spend any time at all waiting for micro-tasks, etc.
- * @returns {SpeedyPromise<T>} this
- */
- turbocharge()
- {
- let my = this;
-
- // suspend the async behavior
- this._flags |= SUSPEND_ASYNC;
- while(my._parent !== undefined) {
- my = my._parent;
- my._flags |= SUSPEND_ASYNC;
- }
-
- // notify the children of the root
- my._notify(); // will be synchronous
-
- // return this SpeedyPromise
- return this;
- }
-
- /**
- * Convert to string
- * @returns {string}
- */
- toString()
- {
- switch(this._state) {
- case PENDING:
- return `SpeedyPromise { <pending> }`;
- case FULFILLED:
- return `SpeedyPromise { <fulfilled> ${this._value} }`;
- case REJECTED:
- return `SpeedyPromise { <rejected> ${this._value} }`;
- default:
- return '';
- }
- }
-
- /**
- * Symbol.toStringTag
- * @returns {string}
- */
- get [Symbol.toStringTag]()
- {
- return 'SpeedyPromise';
- }
-
- /**
- * Creates a resolved SpeedyPromise
- * @template U
- * @param {U} [value]
- * @returns {SpeedyPromise<U>}
- */
- static resolve(value)
- {
- const promise = new SpeedyPromise(this._snop);
-
- if((typeof value === 'object' && value !== null && 'then' in value) || (typeof value === 'function' && 'then' in value)) {
- // resolve asynchronously
- promise._resolve(value);
- }
- else {
- // fulfill synchronously
- promise._value = value;
- promise._state = FULFILLED;
- }
-
- return promise;
- }
-
- /**
- * Creates a rejected SpeedyPromise
- * @template U
- * @param {Error} reason
- * @returns {SpeedyPromise<U>}
- */
- static reject(reason)
- {
- const promise = new SpeedyPromise(this._snop);
- promise._value = reason;
- promise._state = REJECTED;
- return promise;
- }
-
- /**
- * Returns a SpeedyPromise that resolves to an array
- * containing the results of the input promises/values,
- * in their given order. The returned SpeedyPromise will
- * resolve if all input promises resolve, or reject if
- * any input promise rejects.
- * @template U
- * @param {Iterable<U>|Iterable<SpeedyPromise<U>>|Iterable<Promise<U>>} iterable e.g., a SpeedyPromise[], a thenable[]
- * @returns {SpeedyPromise<U[]>}
- *
- * FIXME iterables need not be all <U>
- */
- static all(iterable)
- {
- return new SpeedyPromise((resolve, reject) => {
- const input = [];
-
- // get elements
- for(const element of iterable)
- input.push(element);
-
- // resolve synchronously if there are no elements
- const length = input.length;
- if(length == 0) {
- resolve([]);
- return;
- }
-
- // resolve asynchronously
- let counter = length;
- const output = new Array(length);
- const partialResolve = i => (val => { output[i] = val; if(0 == --counter) resolve(output); });
- for(let i = 0; i < length; i++) {
- const element = input[i];
- if(element.__proto__ === SpeedyPromise.prototype || element.__proto__ === Promise.prototype)
- element.then(partialResolve(i), reject);
- else
- SpeedyPromise.resolve(element).then(partialResolve(i), reject);
- }
- });
- }
-
- /**
- * Returns a promise that gets fulfilled or rejected as soon
- * as the first promise in the iterable gets fulfilled or
- * rejected (with its value/reason).
- * @template U
- * @param {Iterable<U>|Iterable<SpeedyPromise<U>>|Iterable<Promise<U>>} iterable e.g., a SpeedyPromise[], a thenable[]
- * @returns {SpeedyPromise<U>}
- */
- static race(iterable)
- {
- return new SpeedyPromise((resolve, reject) => {
- const input = [];
-
- // get elements
- for(const element of iterable)
- input.push(element);
-
- // if the iterable is empty, the promise
- // will be pending forever...
-
- // resolve asynchronously
- const length = input.length;
- for(let i = 0; i < length; i++) {
- const element = input[i];
- if(element.__proto__ === SpeedyPromise.prototype || element.__proto__ === Promise.prototype)
- element.then(resolve, reject);
- else
- SpeedyPromise.resolve(element).then(resolve, reject);
- }
- });
- }
-
- /**
- * Fulfill this promise with a value
- * @param {T} value
- */
- _fulfill(value)
- {
- this._setState(FULFILLED, value);
- }
-
- /**
- * Reject this promise with a reason
- * @param {Error} reason
- */
- _reject(reason)
- {
- this._setState(REJECTED, reason);
- }
-
- /**
- * Set the state and the value of this promise
- * @param {number} state
- * @param {T|Error} value
- */
- _setState(state, value)
- {
- // the promise is already fulfilled or rejected
- if(this._state != PENDING)
- return;
-
- // set the new state
- this._state = state;
- this._value = value;
- this._notify();
- }
-
- /**
- * Notify my children that this promise is no
- * longer pending. This is an async operation:
- * my childen will be notified "as soon
- * as possible" (it will be scheduled).
- * We may force this to be synchronous, though
- */
- _notify()
- {
- // nothing to do
- if(this._state == PENDING)
- return;
-
- // have we turbocharged this promise?
- if(this._flags & SUSPEND_ASYNC) {
- this._broadcast(); // execute synchronously
- return;
- }
-
- // install a timer (default behavior)
- asap(this._broadcastIfAsync);
- }
-
- /**
- * Helper method
- */
- _broadcastIfAsync()
- {
- // we may have installed a timer at some
- // point, but turbocharged the promise later
- if(!(this._flags & SUSPEND_ASYNC))
- this._broadcast();
- }
-
- /**
- * Tell my children that this promise
- * is either fulfilled or rejected.
- * This is a synchronous operation
- */
- _broadcast()
- {
- const children = this._children;
- const state = this._state;
-
- if(state === FULFILLED) {
- for(let i = 0; i < children; i++) {
- const child = this[i];
- const callback = child._onFulfillment;
-
- try {
- if(callback) {
- if(callback !== child._nop) {
- child._resolve(callback(this._value)); // promise resolution procedure
- child._onFulfillment = child._nop; // will not be called again
- }
- }
- else
- child._fulfill(this._value);
- }
- catch(e) {
- child._reject(e);
- }
- }
- }
- else if(state === REJECTED) {
- for(let i = 0; i < children; i++) {
- const child = this[i];
- const callback = child._onRejection;
-
- try {
- if(callback) {
- if(callback !== child._nop) {
- child._resolve(callback(this._value)); // promise resolution procedure
- child._onRejection = child._nop; // will not be called again
- }
- }
- else
- child._reject(this._value);
- }
- catch(e) {
- child._reject(e);
- }
- }
- }
- }
-
- /**
- * Promise Resolution Procedure
- * based on the Promises/A+ spec
- * @param {T} x
- */
- _resolve(x)
- {
- if((typeof x !== 'object' && typeof x !== 'function') || (x === null)) { // if(x !== Object(x))
- this._fulfill(x);
- return;
- }
-
- if(x === this)
- throw new TypeError(); // Circular reference
-
- if(x.__proto__ === SpeedyPromise.prototype || x.__proto__ === Promise.prototype) {
- x.then(this._resolve, this._reject);
- return;
- }
-
- try {
- const then = x.then;
- if(typeof then === 'function') {
- let resolve = this._resolve, reject = this._reject;
- try {
- then.call(x,
- y => { resolve(y); resolve = reject = this._nop; },
- r => { reject(r); resolve = reject = this._nop; }
- );
- }
- catch(e) {
- if(resolve !== this._nop && reject !== this._nop)
- this._reject(e);
- }
- }
- else {
- this._fulfill(x);
- }
- }
- catch(e) {
- this._reject(e);
- }
- }
-
- /**
- * No-operation
- */
- _nop()
- {
- }
-
- /**
- * Static no-operation
- */
- static _snop()
- {
- }
- }
-
- //module.exports = { SpeedyPromise };
-
- /*
- // Uncomment to test performance with regular Promises
- module.exports = { SpeedyPromise: Promise };
- Promise.prototype.turbocharge = function() { return this };
- */
-
-
- /***/ }),
-
- /***/ 9759:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_67055__) => {
-
- "use strict";
-
- // EXPORTS
- __nested_webpack_require_67055__.d(__webpack_exports__, {
- "ef": () => (/* binding */ createShader),
- "Nt": () => (/* binding */ importShader)
- });
-
- // UNUSED EXPORTS: ShaderDeclaration
-
- // EXTERNAL MODULE: ./src/utils/utils.js
- var utils = __nested_webpack_require_67055__(5484);
- // EXTERNAL MODULE: ./src/utils/types.js
- var types = __nested_webpack_require_67055__(6731);
- // EXTERNAL MODULE: ./src/utils/errors.js
- var errors = __nested_webpack_require_67055__(3841);
- ;// CONCATENATED MODULE: ./src/gpu/shader-preprocessor.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * shader-preprocessor.js
- * Custom preprocessor for shaders
- */
-
-
-
-
-
- // Import numeric globals
- const globals = __nested_webpack_require_67055__(3020);
- const numericGlobals = Object.keys(globals).filter(key => typeof globals[key] == 'number').reduce(
- (obj, key) => ((obj[key] = globals[key]), obj), {}
- );
-
- // Constants accessible by all shaders
- const constants = Object.freeze({
- // numeric globals
- ...numericGlobals,
-
- // fragment shader
- 'FS_USE_CUSTOM_PRECISION': 0, // use default precision settings
- 'FS_OUTPUT_TYPE': 0, // normalized RGBA
-
- // colors
- 'PIXELCOMPONENT_RED': types/* PixelComponent.RED */.hE.RED,
- 'PIXELCOMPONENT_GREEN': types/* PixelComponent.GREEN */.hE.GREEN,
- 'PIXELCOMPONENT_BLUE': types/* PixelComponent.BLUE */.hE.BLUE,
- 'PIXELCOMPONENT_ALPHA': types/* PixelComponent.ALPHA */.hE.ALPHA,
- });
-
- // Regular Expressions
- const commentsRegex = [ /\/\*(.|\s)*?\*\//g , /\/\/.*$/gm ];
- const includeRegex = /^\s*@\s*include\s+"(.*?)"/gm;
- const constantRegex = /@(\w+)@/g;
- const unrollRegex = [
- /@\s*unroll\s+?for\s*\(\s*(int|)\s*(?<counter>\w+)\s*=\s*(-?\d+|\w+)\s*;\s*\k<counter>\s*(<=?)\s*(-?\d+|\w+)\s*;\s*\k<counter>\s*\+\+()\s*\)\s*\{\s*([\s\S]+?)\s*\}/g,
- /@\s*unroll\s+?for\s*\(\s*(int|)\s*(?<counter>\w+)\s*=\s*(-?\d+|\w+)\s*;\s*\k<counter>\s*(<=?)\s*(-?\d+|\w+)\s*;\s*\k<counter>\s*\+=\s*(-?\d+)\s*\)\s*\{\s*([\s\S]+?)\s*\}/g,
- ];
-
- /** @typedef {Map<string,number>} ShaderDefines */
-
- /**
- * Custom preprocessor for the shaders
- */
- class ShaderPreprocessor
- {
- /**
- * Runs the preprocessor
- * @param {string} code
- * @param {ShaderDefines} [defines]
- * @returns {string} preprocessed code
- */
- static run(code, defines = new Map())
- {
- const errors = []; // compile-time errors
-
- //
- // The preprocessor will remove comments from GLSL code,
- // include requested GLSL files and import global constants
- // defined for all shaders (see above)
- //
- return unrollLoops(
- String(code)
- .replace(commentsRegex[0], '')
- .replace(commentsRegex[1], '')
- .replace(includeRegex, (_, filename) =>
- // FIXME: no cycle detection for @include
- ShaderPreprocessor.run(readfileSync(filename), defines)
- )
- .replace(constantRegex, (_, name) => String(
- // Find a defined constant. If not possible, find a global constant
- defines.has(name) ? Number(defines.get(name)) : (
- constants[name] !== undefined ? Number(constants[name]) : (
- errors.push(`Undefined constant: ${name}`), 0
- )
- )
- )),
- defines
- ) + (errors.length > 0 ? errors.map(msg => `\n#error ${msg}\n`).join('') : '');
- }
- }
-
- /**
- * Reads a shader from the shaders/include/ folder
- * @param {string} filename
- * @returns {string}
- */
- function readfileSync(filename)
- {
- if(String(filename).match(/^[a-zA-Z0-9_-]+\.glsl$/))
- return __nested_webpack_require_67055__(524)("./" + filename);
-
- throw new errors/* FileNotFoundError */.Xg(`Shader preprocessor: can't read file "${filename}"`);
- }
-
- /**
- * Unroll for loops in our own preprocessor
- * @param {string} code
- * @param {ShaderDefines} defines
- * @returns {string}
- */
- function unrollLoops(code, defines)
- {
- //
- // Currently, only integer for loops with positive step values
- // can be unrolled. (TODO: negative step values?)
- //
- // The current implementation does not support curly braces
- // inside unrolled loops. You may define macros to get around
- // this, but do you actually need to unroll such loops?
- //
- // Loops that don't fit the supported pattern will crash
- // the preprocessor if you try to unroll them.
- //
- const fn = unroll.bind(defines); // CRAZY!
- const n = unrollRegex.length;
-
- for(let i = 0; i < n; i++)
- code = code.replace(unrollRegex[i], fn);
-
- return code;
- }
-
- /**
- * Unroll a loop pattern (regexp)
- * @param {string} match the matched for loop
- * @param {string} type
- * @param {string} counter
- * @param {string} start
- * @param {string} cmp
- * @param {string} end
- * @param {string} step
- * @param {string} loopcode
- * @returns {string} unrolled loop
- */
- function unroll(match, type, counter, start, cmp, end, step, loopcode)
- {
- const defines = /** @type {ShaderDefines} */ ( this );
-
- // check if the loop limits are numeric constants or #defined numbers from the outside
- const hasStart = Number.isFinite(+start) || defines.has(start);
- const hasEnd = Number.isFinite(+end) || defines.has(end);
- if(!hasStart || !hasEnd) {
- if(defines.size > 0)
- throw new errors/* ParseError */.D3(`Can't unroll loop: unknown limits (start=${start}, end=${end}). Code:\n\n${match}`);
- else
- return match; // don't unroll now, because defines is empty - maybe we'll succeed in the next pass
- }
-
- // parse and validate limits & step
- let istart = defines.has(start) ? defines.get(start) : parseInt(start);
- let iend = defines.has(end) ? defines.get(end) : parseInt(end);
- let istep = (step.length == 0) ? 1 : parseInt(step);
- utils/* Utils.assert */.c.assert(istart <= iend && istep > 0);
-
- /*
- // debug
- console.log(`Encontrei "${match}"`);
- console.log(`type="${type}"`);
- console.log(`counter="${counter}"`);
- console.log(`start="${start}"`);
- console.log(`cmp="${cmp}"`);
- console.log(`end="${end}"`);
- console.log(`step="${step}"`);
- console.log(`loopcode="${loopcode}"`)
- console.log('Defines:', defines);
- */
-
- // continue statements are not supported inside unrolled loops
- // and will generate a compiler error. Using break is ok.
- const hasBreak = (loopcode.match(/\bbreak\s*;/) !== null);
-
- // create a new scope
- let unrolledCode = hasBreak ? 'switch(1) { default:\n' : '{\n';
-
- // declare counter
- unrolledCode += `${type} ${counter};\n`;
-
- // unroll loop
- iend += (cmp == '<=') ? 1 : 0;
- for(let i = istart; i < iend; i += istep)
- unrolledCode += `{\n${counter} = ${i};\n${loopcode}\n}\n`;
-
- // close scope
- unrolledCode += '}\n';
- //console.log('Unrolled code:\n\n' + unrolledCode);
-
- // done!
- return unrolledCode;
- }
- ;// CONCATENATED MODULE: ./src/gpu/shader-declaration.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * shader-declaration.js
- * Encapsulates a shader declaration
- */
-
-
-
-
- const DEFAULT_ATTRIBUTES = Object.freeze({
- position: 'a_position',
- texCoord: 'a_texCoord'
- });
-
- const DEFAULT_ATTRIBUTES_LOCATION = Object.freeze({
- position: 0, // use location 0; see https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices
- texCoord: 1,
- });
-
- const DEFAULT_VERTEX_SHADER_PREFIX = `#version 300 es
- precision highp float;
- precision highp int;
-
- layout (location=${DEFAULT_ATTRIBUTES_LOCATION.position}) in vec2 ${DEFAULT_ATTRIBUTES.position};
- layout (location=${DEFAULT_ATTRIBUTES_LOCATION.texCoord}) in vec2 ${DEFAULT_ATTRIBUTES.texCoord};
- out highp vec2 texCoord;
- uniform highp vec2 texSize;
-
- #define vsinit() \
- gl_Position = vec4(${DEFAULT_ATTRIBUTES.position}, 0.0f, 1.0f); \
- texCoord = ${DEFAULT_ATTRIBUTES.texCoord};
- \n\n`;
-
- const DEFAULT_VERTEX_SHADER = `#define vsmain() ;`;
-
- const DEFAULT_VERTEX_SHADER_SUFFIX = `\n\nvoid main() { vsinit(); vsmain(); }\n`;
-
- const DEFAULT_FRAGMENT_SHADER_PREFIX = `#version 300 es
-
- #if @FS_USE_CUSTOM_PRECISION@ == 0
- precision mediump float; // ~float16
- precision mediump sampler2D;
- precision highp int; // int32
- #endif
-
- #if @FS_OUTPUT_TYPE@ == 0
- #define OUT_TYPE mediump vec4
- #elif @FS_OUTPUT_TYPE@ == 1
- #define OUT_TYPE mediump ivec4
- #elif @FS_OUTPUT_TYPE@ == 2
- #define OUT_TYPE mediump uvec4
- #else
- #error Unknown FS_OUTPUT_TYPE
- #endif
-
- out OUT_TYPE color;
- in highp vec2 texCoord;
- uniform highp vec2 texSize;
-
- @include "global.glsl"\n\n`;
-
- const PRIVATE_TOKEN = Symbol();
-
- /**
- * @typedef {object} ShaderDeclarationFilepathOptions
- * @property {"filepath"} type
- * @property {string} filepath
- * @property {string} [vsfilepath]
- *
- * @typedef {object} ShaderDeclarationSourceOptions
- * @property {"source"} type
- * @property {string} source
- * @property {string} [vssource]
- *
- * @typedef {ShaderDeclarationFilepathOptions | ShaderDeclarationSourceOptions} ShaderDeclarationOptions
- */
-
- /** @typedef {import('./shader-preprocessor').ShaderDefines} ShaderDefines */
-
- /**
- * Shader Declaration
- */
- class ShaderDeclaration
- {
- /**
- * @private Constructor
- * @param {ShaderDeclarationOptions} options
- * @param {Symbol} privateToken
- */
- constructor(options, privateToken)
- {
- if(privateToken !== PRIVATE_TOKEN)
- throw new errors/* IllegalOperationError */.js(); // private constructor!
-
- /** @type {string} original source code provided by the user (fragment shader) */
- this._source = (() => {
- switch(options.type) {
- case 'filepath': return __nested_webpack_require_67055__(2863)("./" + options.filepath);
- case 'source': return options.source;
- default: return /** @type {never} */ ( '' );
- }
- })();
-
- /** @type {string} vertex shader source code (without preprocessing) */
- this._vssource = (() => {
- switch(options.type) {
- case 'filepath': return options.vsfilepath ? __nested_webpack_require_67055__(2863)("./" + options.vsfilepath) : DEFAULT_VERTEX_SHADER;
- case 'source': return options.vssource ? options.vssource : DEFAULT_VERTEX_SHADER;
- default: return /** @type {never} */ ( '' );
- }
- })();
-
- /** @type {string} preprocessed source code of the fragment shader */
- this._fragmentSource = ShaderPreprocessor.run(DEFAULT_FRAGMENT_SHADER_PREFIX + this._source);
-
- /** @type {string} preprocessed source code of the vertex shader */
- this._vertexSource = ShaderPreprocessor.run(DEFAULT_VERTEX_SHADER_PREFIX + this._vssource + DEFAULT_VERTEX_SHADER_SUFFIX);
-
- /** @type {string} filepath of the fragment shader */
- this._filepath = options.type === 'filepath' ? options.filepath : '<in-memory>';
-
- /** @type {string} filepath of the vertex shader */
- this._vsfilepath = options.type === 'filepath' && options.vsfilepath ? options.vsfilepath : '<in-memory>';
-
- /** @type {string[]} an ordered list of uniform names */
- this._arguments = [];
-
- /** @type {Map<string,string>} it maps uniform names to their types */
- this._uniforms = this._autodetectUniforms(this._fragmentSource + '\n' + this._vertexSource);
-
- /** @type {ShaderDefines} it maps externally #defined constants to their values */
- this._defines = new Map();
- }
-
- /**
- * Creates a new Shader directly from a GLSL source
- * @param {string} source fragment shader
- * @param {string|null} [vssource] vertex shader
- * @returns {ShaderDeclaration}
- */
- static create(source, vssource = null)
- {
- return new ShaderDeclaration({ type: 'source', source, vssource }, PRIVATE_TOKEN);
- }
-
- /**
- * Import a Shader from a file containing a GLSL source
- * @param {string} filepath path to .glsl file relative to the shaders/ folder
- * @param {string} [vsfilepath] path to a .vs.glsl file relative to the shaders/ folder
- * @returns {ShaderDeclaration}
- */
- static import(filepath, vsfilepath = null)
- {
- if(!String(filepath).match(/^[a-zA-Z0-9_\-/]+\.glsl$/))
- throw new errors/* FileNotFoundError */.Xg(`Can't import fragment shader at "${filepath}"`);
- else if(vsfilepath != null && !String(vsfilepath).match(/^[a-zA-Z0-9_\-/]+\.vs\.glsl$/))
- throw new errors/* FileNotFoundError */.Xg(`Can't import vertex shader at "${vsfilepath}"`);
-
- return new ShaderDeclaration({ type: 'filepath', filepath, vsfilepath }, PRIVATE_TOKEN);
- }
-
- /**
- * Specify the list & order of arguments to be
- * passed to the shader
- * @param {...string} args argument names
- * @returns {this}
- */
- withArguments(...args)
- {
- // the list of arguments may be declared only once
- if(this._arguments.length > 0)
- throw new errors/* IllegalOperationError */.js(`Redefinition of shader arguments`);
-
- // get arguments
- this._arguments = args.map(arg => String(arg));
-
- // validate
- for(const argname of this._arguments) {
- if(!this._uniforms.has(argname)) {
- if(!this._uniforms.has(argname + '[0]'))
- throw new errors/* IllegalArgumentError */.mG(`Argument "${argname}" has not been declared in the shader`);
- }
- }
-
- // done!
- return this;
- }
-
- /**
- * Specify a set of #defines to be prepended to the fragment shader
- * @param {Object<string,number>} defines key-value pairs (define-name: define-value)
- * @returns {this}
- */
- withDefines(defines)
- {
- // the list of #defines may be defined only once
- if(this._defines.size > 0)
- throw new errors/* IllegalOperationError */.js(`Redefinition of externally defined constants of a shader`);
-
- // store and write the #defines
- const defs = [], keys = Object.keys(defines);
- for(const key of keys) {
- const value = Number(defines[key]); // force numeric values (just in case)
- this._defines.set(key, value);
- defs.push(`#define ${key} ${value}\n`);
- }
-
- // update the shaders & the uniforms
- const source = DEFAULT_FRAGMENT_SHADER_PREFIX + defs.join('') + this._source;
- const vssource = DEFAULT_VERTEX_SHADER_PREFIX + defs.join('') + this._vssource + DEFAULT_VERTEX_SHADER_SUFFIX;
- this._fragmentSource = ShaderPreprocessor.run(source, this._defines);
- this._vertexSource = ShaderPreprocessor.run(vssource, this._defines);
- this._uniforms = this._autodetectUniforms(this._fragmentSource + '\n' + this._vertexSource);
-
- // done!
- return this;
- }
-
- /**
- * Return the GLSL source of the fragment shader
- * @returns {string}
- */
- get fragmentSource()
- {
- return this._fragmentSource;
- }
-
- /**
- * Return the GLSL source of the vertex shader
- * @returns {string}
- */
- get vertexSource()
- {
- return this._vertexSource;
- }
-
- /**
- * Get the names of the vertex shader attributes
- * @returns {typeof DEFAULT_ATTRIBUTES}
- */
- get attributes()
- {
- return DEFAULT_ATTRIBUTES;
- }
-
- /**
- * Get the pre-defined locations of the vertex shader attributes
- * @returns {typeof DEFAULT_ATTRIBUTES_LOCATION}
- */
- get locationOfAttributes()
- {
- return DEFAULT_ATTRIBUTES_LOCATION;
- }
-
- /**
- * Names of the arguments that will be passed to the Shader,
- * corresponding to GLSL uniforms, in the order they will be passed
- * @returns {string[]}
- */
- get arguments()
- {
- return this._arguments;
- }
-
- /**
- * Names of the uniforms declared in the shader
- * @returns {string[]}
- */
- get uniforms()
- {
- return Array.from(this._uniforms.keys());
- }
-
- /**
- * The GLSL type of a uniform variable declared in the shader
- * @param {string} name
- * @returns {string}
- */
- uniformType(name)
- {
- if(!this._uniforms.has(name))
- throw new errors/* IllegalArgumentError */.mG(`Unrecognized uniform variable: "${name}"`);
-
- return this._uniforms.get(name);
- }
-
- /**
- * The value of an externally defined constant, i.e., via withDefines()
- * @param {string} name
- * @returns {number}
- */
- definedConstant(name)
- {
- if(!this._defines.has(name))
- throw new errors/* IllegalArgumentError */.mG(`Unrecognized externally defined constant: "${name}"`);
-
- return this._defines.get(name);
- }
-
- /**
- * Parses a GLSL source and detects the uniform variables,
- * as well as their types
- * @param {string} preprocessedSource
- * @returns {Map<string,string>} specifies the types of all uniforms
- */
- _autodetectUniforms(preprocessedSource)
- {
- const sourceWithoutComments = preprocessedSource; // assume we've preprocessed the source already
- const regex = /^\s*uniform\s+(highp\s+|mediump\s+|lowp\s+)?(\w+)\s+([^;]+)/gm;
- const uniforms = new Map();
-
- let match;
- while((match = regex.exec(sourceWithoutComments)) !== null) {
- const type = match[2];
- const names = match[3].split(',').map(name => name.trim()).filter(name => name); // trim & remove empty names
-
- for(const name of names) {
- if(name.endsWith(']')) {
- // is it an array?
- if(!(match = name.match(/(\w+)\s*\[\s*(\d+)\s*\]$/)))
- throw new errors/* ParseError */.D3(`Unspecified array length for uniform "${name}" in the shader`);
-
- // read array name & size
- const [ array, size ] = [ match[1], Number(match[2]) ];
-
- // register uniforms
- for(let i = 0; i < size; i++)
- uniforms.set(`${array}[${i}]`, type);
- }
- else {
- // register a regular uniform
- if(!uniforms.has(name) || uniforms.get(name) === type)
- uniforms.set(name, type);
- else
- throw new errors/* IllegalOperationError */.js(`Redefinition of uniform "${name}" in the shader`);
- }
- }
- }
-
- return uniforms;
- }
- }
-
- /**
- * Import a ShaderDeclaration from a GLSL file
- * @param {string} filepath relative to the shaders/ folder (a .glsl file)
- * @param {string|null} [vsfilepath] optional vertex shader (a .vs.glsl file)
- * @returns {ShaderDeclaration}
- */
- function importShader(filepath, vsfilepath = null)
- {
- return ShaderDeclaration.import(filepath, vsfilepath);
- }
-
- /**
- * Create a ShaderDeclaration from a GLSL source code
- * @param {string} source fragment shader
- * @param {string|null} [vssource] optional vertex shader
- * @returns {ShaderDeclaration}
- */
- function createShader(source, vssource = null)
- {
- return ShaderDeclaration.create(source, vssource);
- }
-
- /***/ }),
-
- /***/ 6776:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_87570__) => {
-
- "use strict";
- __nested_webpack_require_87570__.r(__webpack_exports__);
- /* harmony export */ __nested_webpack_require_87570__.d(__webpack_exports__, {
- /* harmony export */ "conv2D": () => (/* binding */ conv2D),
- /* harmony export */ "convX": () => (/* binding */ convX),
- /* harmony export */ "convY": () => (/* binding */ convY)
- /* harmony export */ });
- /* harmony import */ var _shader_declaration__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_87570__(9759);
- /* harmony import */ var _utils_utils__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_87570__(5484);
- /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_2__ = __nested_webpack_require_87570__(3841);
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * convolution.js
- * Convolution shader generators
- */
-
-
-
-
-
-
-
- /**
- * Generate a 2D convolution with a square kernel
- * @param {number[]} kernel convolution kernel
- * @param {number} [normalizationConstant] will be multiplied by all kernel entries
- */
- function conv2D(kernel, normalizationConstant = 1.0)
- {
- const kernel32 = new Float32Array(kernel.map(x => (+x) * (+normalizationConstant)));
- const kSize = Math.sqrt(kernel32.length) | 0;
- const N = kSize >> 1; // idiv 2
-
- // validate input
- if(kSize < 1 || kSize % 2 == 0)
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .IllegalArgumentError */ .mG(`Can't perform a 2D convolution with an invalid kSize of ${kSize}`);
- else if(kSize * kSize != kernel32.length)
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .IllegalArgumentError */ .mG(`Invalid 2D convolution kernel of ${kernel32.length} elements (expected: square)`);
-
- // select the appropriate pixel function
- const pixelAtOffset = (N <= 7) ? 'pixelAtShortOffset' : 'pixelAtLongOffset';
-
- // code generator
- const foreachKernelElement = fn => _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.cartesian */ .c.cartesian(_utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.symmetricRange */ .c.symmetricRange(N), _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.symmetricRange */ .c.symmetricRange(N)).map(
- cur => fn(
- kernel32[(cur[0] + N) * kSize + (cur[1] + N)],
- cur[0], cur[1]
- )
- ).join('\n');
-
- const generateCode = (k, dy, dx) => `
- result += ${pixelAtOffset}(image, ivec2(${(-dx) | 0}, ${(-dy) | 0})) * float(${+k});
- `;
-
- // shader
- const source = `
- uniform sampler2D image;
-
- void main()
- {
- float alpha = threadPixel(image).a;
- vec4 result = vec4(0.0f);
-
- ${foreachKernelElement(generateCode)}
-
- color = vec4(result.rgb, alpha);
- }
- `;
-
- // done!
- return (0,_shader_declaration__WEBPACK_IMPORTED_MODULE_0__/* .createShader */ .ef)(source).withArguments('image');
- }
-
-
-
-
- /**
- * Generate a 1D convolution function on the x-axis
- * @param {number[]} kernel convolution kernel
- * @param {number} [normalizationConstant] will be multiplied by all kernel entries
- */
- function convX(kernel, normalizationConstant = 1.0)
- {
- return conv1D('x', kernel, normalizationConstant);
- }
-
-
-
-
- /**
- * Generate a 1D convolution function on the y-axis
- * @param {number[]} kernel convolution kernel
- * @param {number} [normalizationConstant] will be multiplied by all kernel entries
- */
- function convY(kernel, normalizationConstant = 1.0)
- {
- return conv1D('y', kernel, normalizationConstant);
- }
-
-
-
-
- /**
- * 1D convolution function generator
- * @param {string} axis either "x" or "y"
- * @param {number[]} kernel convolution kernel
- * @param {number} [normalizationConstant] will be multiplied by all kernel entries
- */
- function conv1D(axis, kernel, normalizationConstant = 1.0)
- {
- const kernel32 = new Float32Array(kernel.map(x => (+x) * (+normalizationConstant)));
- const kSize = kernel32.length;
- const N = kSize >> 1; // idiv 2
-
- // validate input
- if(kSize < 1 || kSize % 2 == 0)
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .IllegalArgumentError */ .mG(`Can't perform a 1D convolution with an invalid kSize of ${kSize}`);
- else if(axis != 'x' && axis != 'y')
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .IllegalArgumentError */ .mG(`Can't perform 1D convolution: invalid axis "${axis}"`); // this should never happen
-
- // select the appropriate pixel function
- const pixelAtOffset = (N <= 7) ? 'pixelAtShortOffset' : 'pixelAtLongOffset';
-
- // code generator
- const foreachKernelElement = fn => _utils_utils__WEBPACK_IMPORTED_MODULE_1__/* .Utils.symmetricRange */ .c.symmetricRange(N).reduce(
- (acc, cur) => acc + fn(kernel32[cur + N], cur),
- '');
- const generateCode = (k, i) => ((axis == 'x') ? `
- pixel += ${pixelAtOffset}(image, ivec2(${(-i) | 0}, 0)) * float(${+k});
- ` : `
- pixel += ${pixelAtOffset}(image, ivec2(0, ${(-i) | 0})) * float(${+k});
- `);
-
- // shader
- const source = `
- uniform sampler2D image;
-
- void main()
- {
- float alpha = threadPixel(image).a;
- vec4 pixel = vec4(0.0f);
-
- ${foreachKernelElement(generateCode)}
-
- color = vec4(pixel.rgb, alpha);
- }
- `;
-
- // done!
- return (0,_shader_declaration__WEBPACK_IMPORTED_MODULE_0__/* .createShader */ .ef)(source).withArguments('image');
- }
-
- /***/ }),
-
- /***/ 524:
- /***/ ((module, __unused_webpack_exports, __nested_webpack_require_93522__) => {
-
- var map = {
- "./colors.glsl": 2545,
- "./filters.glsl": 7373,
- "./fixed-point.glsl": 2229,
- "./float16.glsl": 919,
- "./global.glsl": 3815,
- "./int32.glsl": 1830,
- "./keypoint-descriptors.glsl": 1364,
- "./keypoint-matches.glsl": 4004,
- "./keypoints.glsl": 8714,
- "./math.glsl": 9010,
- "./pyramids.glsl": 6433,
- "./subpixel.glsl": 4697
- };
-
-
- function webpackContext(req) {
- var id = webpackContextResolve(req);
- return __nested_webpack_require_93522__(id);
- }
- function webpackContextResolve(req) {
- if(!__nested_webpack_require_93522__.o(map, req)) {
- var e = new Error("Cannot find module '" + req + "'");
- e.code = 'MODULE_NOT_FOUND';
- throw e;
- }
- return map[req];
- }
- webpackContext.keys = function webpackContextKeys() {
- return Object.keys(map);
- };
- webpackContext.resolve = webpackContextResolve;
- module.exports = webpackContext;
- webpackContext.id = 524;
-
- /***/ }),
-
- /***/ 2863:
- /***/ ((module, __unused_webpack_exports, __nested_webpack_require_94446__) => {
-
- var map = {
- "./filters/convolution": 6776,
- "./filters/convolution.js": 6776,
- "./filters/convolution1d.glsl": 4645,
- "./filters/convolution2d.glsl": 6942,
- "./filters/fast-median.glsl": 7054,
- "./filters/nightvision.glsl": 8961,
- "./filters/normalize-image.glsl": 9571,
- "./filters/rgb2grey.glsl": 8466,
- "./include/colors.glsl": 2545,
- "./include/filters.glsl": 7373,
- "./include/fixed-point.glsl": 2229,
- "./include/float16.glsl": 919,
- "./include/global.glsl": 3815,
- "./include/int32.glsl": 1830,
- "./include/keypoint-descriptors.glsl": 1364,
- "./include/keypoint-matches.glsl": 4004,
- "./include/keypoints.glsl": 8714,
- "./include/math.glsl": 9010,
- "./include/pyramids.glsl": 6433,
- "./include/subpixel.glsl": 4697,
- "./keypoints/allocate-descriptors.glsl": 2289,
- "./keypoints/allocate-extra.glsl": 5725,
- "./keypoints/apply-homography.glsl": 3801,
- "./keypoints/bf-knn.glsl": 2346,
- "./keypoints/clip-border.glsl": 4180,
- "./keypoints/clip.glsl": 7771,
- "./keypoints/distance-filter.glsl": 8938,
- "./keypoints/encode-keypoint-long-offsets.glsl": 4802,
- "./keypoints/encode-keypoint-offsets.glsl": 6253,
- "./keypoints/encode-keypoint-positions.glsl": 384,
- "./keypoints/encode-keypoint-properties.glsl": 500,
- "./keypoints/encode-keypoints.glsl": 3673,
- "./keypoints/encode-null-keypoints.glsl": 1703,
- "./keypoints/fast.glsl": 2633,
- "./keypoints/fast.vs.glsl": 535,
- "./keypoints/hamming-distance-filter.glsl": 3232,
- "./keypoints/harris-cutoff.glsl": 8356,
- "./keypoints/harris.glsl": 7339,
- "./keypoints/knn-init.glsl": 3177,
- "./keypoints/knn-transfer.glsl": 2769,
- "./keypoints/laplacian.glsl": 2006,
- "./keypoints/lk.glsl": 3329,
- "./keypoints/lookup-of-locations.glsl": 4251,
- "./keypoints/lookup-of-locations.vs.glsl": 4747,
- "./keypoints/lsh-knn.glsl": 7421,
- "./keypoints/mix-keypoints.glsl": 4523,
- "./keypoints/nonmax-scale.glsl": 2277,
- "./keypoints/nonmax-space.glsl": 8430,
- "./keypoints/nonmax-suppression.glsl": 9743,
- "./keypoints/orb-descriptor.glsl": 3464,
- "./keypoints/orb-orientation.glsl": 7184,
- "./keypoints/refine-scale.glsl": 7220,
- "./keypoints/score-findmax.glsl": 805,
- "./keypoints/shuffle.glsl": 8736,
- "./keypoints/sort-keypoints.glsl": 9311,
- "./keypoints/subpixel-refinement.glsl": 9423,
- "./keypoints/transfer-flow.glsl": 2060,
- "./keypoints/transfer-orientation.glsl": 5463,
- "./keypoints/transfer-to-extra.glsl": 6986,
- "./keypoints/upload-keypoints.glsl": 3179,
- "./pyramids/downsample2.glsl": 8680,
- "./pyramids/upsample2.glsl": 3384,
- "./transforms/additive-mix.glsl": 1976,
- "./transforms/resize.glsl": 4543,
- "./transforms/warp-perspective.glsl": 6296,
- "./utils/copy-components.glsl": 747,
- "./utils/copy-raster.glsl": 9176,
- "./utils/copy.glsl": 8960,
- "./utils/fill-components.glsl": 3294,
- "./utils/fill.glsl": 1959,
- "./utils/flip-y.vs.glsl": 7290,
- "./utils/scan-minmax2d.glsl": 7270,
- "./utils/sobel-derivatives.glsl": 48,
- "./utils/sobel-derivatives.vs.glsl": 3713
- };
-
-
- function webpackContext(req) {
- var id = webpackContextResolve(req);
- return __nested_webpack_require_94446__(id);
- }
- function webpackContextResolve(req) {
- if(!__nested_webpack_require_94446__.o(map, req)) {
- var e = new Error("Cannot find module '" + req + "'");
- e.code = 'MODULE_NOT_FOUND';
- throw e;
- }
- return map[req];
- }
- webpackContext.keys = function webpackContextKeys() {
- return Object.keys(map);
- };
- webpackContext.resolve = webpackContextResolve;
- module.exports = webpackContext;
- webpackContext.id = 2863;
-
- /***/ }),
-
- /***/ 7905:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_97977__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_97977__.d(__webpack_exports__, {
- /* harmony export */ "$": () => (/* binding */ SpeedyGL)
- /* harmony export */ });
- /* harmony import */ var _utils_utils__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_97977__(5484);
- /* harmony import */ var _utils_observable__WEBPACK_IMPORTED_MODULE_3__ = __nested_webpack_require_97977__(9845);
- /* harmony import */ var _core_speedy_promise__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_97977__(4500);
- /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_2__ = __nested_webpack_require_97977__(3841);
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-gl.js
- * A wrapper around the WebGL Rendering Context
- */
-
-
-
-
-
-
- /** @typedef {'default' | 'low-power' | 'high-performance'} PowerPreference */
-
- // Constants
- const SINGLETON_KEY = Symbol();
- const DEFAULT_POWER_PREFERENCE = 'default';
-
- //
- // We use a small canvas to improve the performance
- // of createImageBitmap() on Firefox.
- //
- // A large canvas (2048x2048) causes a FPS drop, even
- // if we only extract a small region of it (this is
- // unlike Chrome, which is fast).
- //
- // Note: we automatically increase the size of the
- // canvas (as needed) when rendering to it.
- //
- const CANVAS_WIDTH = 16, CANVAS_HEIGHT = 16;
-
- /** @type {SpeedyGL} Singleton */
- let instance = null;
-
- /** @type {PowerPreference} power preference */
- let powerPreference = DEFAULT_POWER_PREFERENCE;
-
-
-
- /**
- * A wrapper around the WebGL Rendering Context
- */
- class SpeedyGL extends _utils_observable__WEBPACK_IMPORTED_MODULE_3__/* .Observable */ .y
- {
- /**
- * Constructor
- * @param {Symbol} key
- * @private
- */
- constructor(key)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_0__/* .Utils.assert */ .c.assert(key === SINGLETON_KEY);
- super();
-
-
-
- /** @type {boolean} internal flag */
- this._reinitializeOnContextLoss = true;
-
- /** @type {HTMLCanvasElement} canvas */
- this._canvas = this._createCanvas(this._reinitialize.bind(this));
-
- /** @type {WebGL2RenderingContext} WebGL rendering context */
- this._gl = null;
-
-
-
- // create WebGL2 rendering context
- this._gl = this._createContext(this._canvas);
- }
-
- /**
- * Get Singleton
- * @returns {SpeedyGL}
- */
- static get instance()
- {
- return instance || (instance = new SpeedyGL(SINGLETON_KEY));
- }
-
- /**
- * The WebGL Rendering Context
- * Be careful not to cache this, as the WebGL Rendering Context may be lost!
- * @returns {WebGL2RenderingContext}
- */
- get gl()
- {
- return this._gl;
- }
-
- /**
- * The canvas
- * @returns {HTMLCanvasElement}
- */
- get canvas()
- {
- return this._canvas;
- }
-
- /**
- * Create a WebGL-capable canvas
- * @param {Function} reinitialize to be called if we get a WebGL context loss event
- * @returns {HTMLCanvasElement}
- */
- _createCanvas(reinitialize)
- {
- const canvas = _utils_utils__WEBPACK_IMPORTED_MODULE_0__/* .Utils.createCanvas */ .c.createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
-
- canvas.addEventListener('webglcontextlost', ev => {
- _utils_utils__WEBPACK_IMPORTED_MODULE_0__/* .Utils.warning */ .c.warning(`Lost WebGL2 context`);
- setTimeout(reinitialize, 0);
- ev.preventDefault();
- }, false);
-
- /*canvas.addEventListener('webglcontextrestored', ev => {
- Utils.warning(`Restored WebGL2 context`);
- ev.preventDefault();
- }, false);*/
-
- return canvas;
- }
-
- /**
- * Create a WebGL2 Rendering Context
- * @param {HTMLCanvasElement} canvas
- * @returns {WebGL2RenderingContext}
- */
- _createContext(canvas)
- {
- _utils_utils__WEBPACK_IMPORTED_MODULE_0__/* .Utils.log */ .c.log(`Creating a ${powerPreference} WebGL2 rendering context...`);
-
- // does the browser support WebGL2?
- if(typeof WebGL2RenderingContext === 'undefined')
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .NotSupportedError */ .B8(`This application requires WebGL2. Please use a different browser.`);
-
- const gl = canvas.getContext('webgl2', {
- premultipliedAlpha: false,
- preserveDrawingBuffer: false,
- powerPreference: powerPreference,
- alpha: true, // see https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#avoid_alphafalse_which_can_be_expensive
- antialias: false,
- depth: false,
- stencil: false,
- desynchronized: true,
- });
-
- if(!gl)
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .NotSupportedError */ .B8(`Can't create a WebGL2 Rendering Context. Try a different browser!`);
-
- return gl;
- }
-
- /**
- * Reinitialize WebGL
- */
- _reinitialize()
- {
- // disable reinitialization?
- if(!this._reinitializeOnContextLoss)
- return;
-
- // warning
- _utils_utils__WEBPACK_IMPORTED_MODULE_0__/* .Utils.warning */ .c.warning(`Reinitializing WebGL2...`);
-
- // create new canvas
- this._canvas.remove();
- this._canvas = this._createCanvas(this._reinitialize.bind(this));
-
- // create new context
- this._gl = this._createContext(this._canvas);
-
- // notify observers: we have a new context!
- // we need to recreate all textures...
- this._notify();
- }
-
- /**
- * Lose the WebGL context. This is used to manually
- * free resources, and also for purposes of testing
- * @returns {WEBGL_lose_context}
- */
- loseContext()
- {
- const gl = this._gl;
-
- // nothing to do?
- if(gl.isContextLost())
- return;
-
- // find the appropriate extension
- const ext = gl.getExtension('WEBGL_lose_context');
- if(!ext)
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .NotSupportedError */ .B8('WEBGL_lose_context extension is unavailable');
-
- // disable reinitialization
- this._reinitializeOnContextLoss = false;
-
- // lose context
- ext.loseContext();
-
- // done!
- return ext;
- }
-
- /**
- * Lose & restore the WebGL context
- * @param {number} [secondsToRestore]
- * @return {SpeedyPromise<WEBGL_lose_context>} resolves as soon as the context is restored
- */
- loseAndRestoreContext(secondsToRestore = 1)
- {
- const ms = Math.max(secondsToRestore, 0) * 1000;
- const ext = this.loseContext();
-
- return new _core_speedy_promise__WEBPACK_IMPORTED_MODULE_1__/* .SpeedyPromise */ .s(resolve => {
- setTimeout(() => {
- //ext.restoreContext();
- this._reinitializeOnContextLoss = true;
- this._reinitialize();
- setTimeout(() => resolve(ext), 0); // next frame
- }, ms);
- });
- }
-
- /**
- * Power preference for the WebGL context
- * @returns {PowerPreference}
- */
- static get powerPreference()
- {
- return powerPreference;
- }
-
- /**
- * Power preference for the WebGL context
- * @param {PowerPreference} value
- */
- static set powerPreference(value)
- {
- // validate
- if(!(value === 'default' || value === 'low-power' || value === 'high-performance'))
- throw new _utils_errors__WEBPACK_IMPORTED_MODULE_2__/* .IllegalArgumentError */ .mG(`Invalid powerPreference: "${value}"`);
-
- // the power preference should be set before we create the WebGL context
- if(instance == null || powerPreference !== value) {
- powerPreference = value;
-
- // recreate the context if it already exists. Experimental.
- if(instance != null)
- instance.loseAndRestoreContext();
- }
- }
- }
-
- /***/ }),
-
- /***/ 3841:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_106533__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_106533__.d(__webpack_exports__, {
- /* harmony export */ "nU": () => (/* binding */ SpeedyError),
- /* harmony export */ "B8": () => (/* binding */ NotSupportedError),
- /* harmony export */ "Ql": () => (/* binding */ GLError),
- /* harmony export */ "Mi": () => (/* binding */ AbstractMethodError),
- /* harmony export */ "mG": () => (/* binding */ IllegalArgumentError),
- /* harmony export */ "js": () => (/* binding */ IllegalOperationError),
- /* harmony export */ "Cx": () => (/* binding */ OutOfMemoryError),
- /* harmony export */ "Xg": () => (/* binding */ FileNotFoundError),
- /* harmony export */ "tg": () => (/* binding */ ResourceNotLoadedError),
- /* harmony export */ "W5": () => (/* binding */ TimeoutError),
- /* harmony export */ "D3": () => (/* binding */ ParseError),
- /* harmony export */ "ps": () => (/* binding */ AssertionError),
- /* harmony export */ "$y": () => (/* binding */ AccessDeniedError),
- /* harmony export */ "IT": () => (/* binding */ WebAssemblyError)
- /* harmony export */ });
- /* unused harmony export NotImplementedError */
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * errors.js
- * Error classes
- */
-
- /** @typedef {SpeedyError|Error|null} SpeedyErrorCause */
-
- /**
- * Generic error class for Speedy
- */
- class SpeedyError extends Error
- {
- /**
- * Class constructor
- * @param {string} message message text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message, cause = null)
- {
- super([
- message,
- cause ? cause.toString() : '[speedy-vision.js]'
- ].join('\n-> '));
-
- /** @type {SpeedyErrorCause} cause of the error */
- this._cause = cause;
- }
-
- /**
- * Error name
- * @returns {string}
- */
- get name()
- {
- return this.constructor.name;
- }
-
- /**
- * Set error name (ignored)
- * @param {string} _ ignored
- */
- set name(_)
- {
- void(0);
- }
-
- /**
- * Get the cause of the error. Available if
- * it has been specified in the constructor
- * @returns {SpeedyErrorCause}
- */
- get cause()
- {
- return this._cause;
- }
- }
-
- /**
- * Unsupported operation error
- * The requested operation is not supported
- */
- class NotSupportedError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Unsupported operation. ${message}`, cause);
- }
- }
-
- /**
- * Not implemented error
- * The called method is not implemented
- */
- class NotImplementedError extends (/* unused pure expression or super */ null && (0))
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Method not implemented. ${message}`, cause);
- }
- }
-
- /**
- * WebGL error
- */
- class GLError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`WebGL error. ${message}`, cause);
- }
-
- /**
- * Get an error object describing the latest WebGL error
- * @param {WebGL2RenderingContext} gl
- * @returns {GLError}
- */
- static from(gl)
- {
- const recognizedErrors = [
- 'NO_ERROR',
- 'INVALID_ENUM',
- 'INVALID_VALUE',
- 'INVALID_OPERATION',
- 'INVALID_FRAMEBUFFER_OPERATION',
- 'OUT_OF_MEMORY',
- 'CONTEXT_LOST_WEBGL',
- ];
-
- const glError = gl.getError();
- const message = recognizedErrors.find(error => gl[error] == glError) || 'Unknown';
- return new GLError(message);
- }
- }
-
- /**
- * AbstractMethodError
- * Thrown when one tries to call an abstract method
- */
- class AbstractMethodError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Can't call abstract method. ${message}`, cause);
- }
- }
-
- /**
- * Illegal argument error
- * A method has received one or more illegal arguments
- */
- class IllegalArgumentError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Illegal argument. ${message}`, cause);
- }
- }
-
- /**
- * Illegal operation error
- * The method arguments are valid, but the method can't
- * be called due to the current the state of the object
- */
- class IllegalOperationError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Illegal operation. ${message}`, cause);
- }
- }
-
- /**
- * Out of memory
- */
- class OutOfMemoryError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Out of memory. ${message}`, cause);
- }
- }
-
- /**
- * File not found error
- */
- class FileNotFoundError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`File not found. ${message}`, cause);
- }
- }
-
- /**
- * Resource not loaded error
- */
- class ResourceNotLoadedError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Resource not loaded. ${message}`, cause);
- }
- }
-
- /**
- * Timeout error
- */
- class TimeoutError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Timeout error. ${message}`, cause);
- }
- }
-
- /**
- * Parse error
- */
- class ParseError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Parse error. ${message}`, cause);
- }
- }
-
- /**
- * Assertion error
- */
- class AssertionError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Assertion failed. ${message}`, cause);
- }
- }
-
- /**
- * Access denied
- */
- class AccessDeniedError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`Access denied. ${message}`, cause);
- }
- }
-
- /**
- * WebAssembly error
- */
- class WebAssemblyError extends SpeedyError
- {
- /**
- * Class constructor
- * @param {string} [message] additional text
- * @param {SpeedyErrorCause} [cause] cause of the error
- */
- constructor(message = '', cause = null)
- {
- super(`WebAssembly error. ${message}`, cause);
- }
- }
-
- /***/ }),
-
- /***/ 3020:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_115191__) => {
-
- "use strict";
- __nested_webpack_require_115191__.r(__webpack_exports__);
- /* harmony export */ __nested_webpack_require_115191__.d(__webpack_exports__, {
- /* harmony export */ "PYRAMID_MAX_LEVELS": () => (/* binding */ PYRAMID_MAX_LEVELS),
- /* harmony export */ "LOG2_PYRAMID_MAX_SCALE": () => (/* binding */ LOG2_PYRAMID_MAX_SCALE),
- /* harmony export */ "PYRAMID_MAX_SCALE": () => (/* binding */ PYRAMID_MAX_SCALE),
- /* harmony export */ "FIX_BITS": () => (/* binding */ FIX_BITS),
- /* harmony export */ "FIX_RESOLUTION": () => (/* binding */ FIX_RESOLUTION),
- /* harmony export */ "MAX_TEXTURE_LENGTH": () => (/* binding */ MAX_TEXTURE_LENGTH),
- /* harmony export */ "MIN_KEYPOINT_SIZE": () => (/* binding */ MIN_KEYPOINT_SIZE),
- /* harmony export */ "MIN_ENCODER_LENGTH": () => (/* binding */ MIN_ENCODER_LENGTH),
- /* harmony export */ "MAX_ENCODER_CAPACITY": () => (/* binding */ MAX_ENCODER_CAPACITY),
- /* harmony export */ "DEFAULT_ENCODER_CAPACITY": () => (/* binding */ DEFAULT_ENCODER_CAPACITY),
- /* harmony export */ "LOG2_MAX_DESCRIPTOR_SIZE": () => (/* binding */ LOG2_MAX_DESCRIPTOR_SIZE),
- /* harmony export */ "MAX_DESCRIPTOR_SIZE": () => (/* binding */ MAX_DESCRIPTOR_SIZE),
- /* harmony export */ "MATCH_INDEX_BITS": () => (/* binding */ MATCH_INDEX_BITS),
- /* harmony export */ "MATCH_INDEX_MASK": () => (/* binding */ MATCH_INDEX_MASK),
- /* harmony export */ "MATCH_MAX_INDEX": () => (/* binding */ MATCH_MAX_INDEX),
- /* harmony export */ "MATCH_MAX_DISTANCE": () => (/* binding */ MATCH_MAX_DISTANCE),
- /* harmony export */ "LITTLE_ENDIAN": () => (/* binding */ LITTLE_ENDIAN)
- /* harmony export */ });
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * globals.js
- * Global constants
- */
-
- // -----------------------------------------------------------------
- // IMAGE PYRAMIDS & SCALE-SPACE
- // -----------------------------------------------------------------
-
- /** @type {number} The maximum number of levels in a pyramid, considering a scale factor of 2x between levels */
- const PYRAMID_MAX_LEVELS = 8;
-
- /** @type {number} The base-2 logarithm of PYRAMID_MAX_SCALE */
- const LOG2_PYRAMID_MAX_SCALE = 0;
-
- /** @type {number} The maximum supported scale for a pyramid level */
- const PYRAMID_MAX_SCALE = 1 << LOG2_PYRAMID_MAX_SCALE;
-
-
-
- // -----------------------------------------------------------------
- // FIXED-POINT MATH
- // -----------------------------------------------------------------
-
- /** @type {number} How many bits do we use to store fractional data? */
- const FIX_BITS = 3; // step size: 0.125 = 1/2^FIX_BITS
-
- /** @type {number} Fixed-point resolution */
- const FIX_RESOLUTION = 1 << FIX_BITS; // float(2^(FIX_BITS))
-
-
-
- // -----------------------------------------------------------------
- // TEXTURE LIMITS
- // -----------------------------------------------------------------
-
- /** @type {number} Maximum texture length (width, height) */
- const MAX_TEXTURE_LENGTH = (1 << (16 - FIX_BITS)) - 1; // must be 2^n - 1 due to keypoint encoding
-
-
-
- // -----------------------------------------------------------------
- // KEYPOINTS
- // -----------------------------------------------------------------
-
- /** @type {number} Size of a keypoint header, in bytes (must be divisible by 4) */
- const MIN_KEYPOINT_SIZE = 8;
-
- /** @type {number} Minimum length of a keypoint encoder, in pixels (encodes at least 1 keypoint) */
- const MIN_ENCODER_LENGTH = 2; // capacity computations are based on this // Math.ceil(Math.sqrt(MIN_KEYPOINT_SIZE / 4));
-
- /** @type {number} Maximum number of keypoints we can encode (the actual length of the encoder may vary) */
- const MAX_ENCODER_CAPACITY = 8192;
-
- /** @type {number} Default capacity of a keypoint encoder (64x64 texture with 2 pixels per keypoint) */
- const DEFAULT_ENCODER_CAPACITY = 2048;
-
- /** @type {number} log2 of MAX_DESCRIPTOR_SIZE */
- const LOG2_MAX_DESCRIPTOR_SIZE = 6;
-
- /** @type {number} maximum size of a keypoint descriptor, in bytes */
- const MAX_DESCRIPTOR_SIZE = 1 << LOG2_MAX_DESCRIPTOR_SIZE;
-
- /** @type {number} How many bits will we use when encoding the index of a keypoint match? */
- const MATCH_INDEX_BITS = 32 - (LOG2_MAX_DESCRIPTOR_SIZE + 3); // 32 - log2(MAX_DESCRIPTOR_SIZE * 8)
-
- /** @type {number} Bitwise mask to extract a keypoint index from an encoded match */
- const MATCH_INDEX_MASK = (1 << MATCH_INDEX_BITS) - 1;
-
- /** @type {number} Maximum size of the database of keypoints for matching */
- const MATCH_MAX_INDEX = (1 << MATCH_INDEX_BITS) - 1;
-
- /** @type {number} The maximum distance that can be stored in a match */
- const MATCH_MAX_DISTANCE = (1 << (32 - MATCH_INDEX_BITS)) - 1;
-
-
-
- // -----------------------------------------------------------------
- // MISC
- // -----------------------------------------------------------------
-
- /** @type {boolean} Are we in a little-endian machine? */
- const LITTLE_ENDIAN = (function() {
- return 0xCAFE === (new Uint16Array(new Uint8Array([0xFE, 0xCA]).buffer))[0];
- })();
-
- /***/ }),
-
- /***/ 9845:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_120825__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_120825__.d(__webpack_exports__, {
- /* harmony export */ "y": () => (/* binding */ Observable)
- /* harmony export */ });
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * observable.js
- * Observer design pattern
- */
-
- /**
- * Implementation of the Observer design pattern
- * @abstract
- */
- class Observable
- {
- /**
- * Constructor
- */
- constructor()
- {
- /** @type {Function[]} subscribers / callbacks */
- this._subscribers = [];
-
- /** @type {object[]} "this" pointers */
- this._thisptr = [];
-
- /** @type {Array<any[]>} function arguments */
- this._args = [];
- }
-
- /**
- * Add subscriber
- * @param {Function} fn callback
- * @param {object} [thisptr] "this" pointer to be used when invoking the callback
- * @param {...any} args arguments to be passed to the callback
- */
- subscribe(fn, thisptr, ...args)
- {
- this._subscribers.push(fn);
- this._thisptr.push(thisptr);
- this._args.push(args);
- }
-
- /**
- * Remove subscriber
- * @param {Function} fn previously added callback
- * @param {object} [thisptr] "this" pointer
- */
- unsubscribe(fn, thisptr)
- {
- for(let j = this._subscribers.length - 1; j >= 0; j--) {
- if(this._subscribers[j] === fn && this._thisptr[j] === thisptr) {
- this._subscribers.splice(j, 1);
- this._thisptr.splice(j, 1);
- this._args.splice(j, 1);
- break;
- }
- }
- }
-
- /**
- * Notify all subscribers about a state change
- * @protected
- */
- _notify()
- {
- for(let i = 0; i < this._subscribers.length; i++)
- this._subscribers[i].call(this._thisptr[i], ...(this._args[i]));
- }
- }
-
- /***/ }),
-
- /***/ 6731:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_123384__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_123384__.d(__webpack_exports__, {
- /* harmony export */ "DD": () => (/* binding */ MediaType),
- /* harmony export */ "D3": () => (/* binding */ ImageFormat),
- /* harmony export */ "hE": () => (/* binding */ PixelComponent),
- /* harmony export */ "rY": () => (/* binding */ ColorComponentId)
- /* harmony export */ });
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * types.js
- * Types & formats
- */
-
- /**
- * Media types
- * @enum {Symbol}
- */
- const MediaType = Object.freeze({
- Image: Symbol('Image'),
- Video: Symbol('Video'),
- Canvas: Symbol('Canvas'),
- Bitmap: Symbol('Bitmap'),
- });
-
- /**
- * Image formats
- * @enum {Symbol}
- */
- const ImageFormat = Object.freeze({
- RGBA: Symbol('RGBA'),
- GREY: Symbol('GREY'),
- });
-
- /**
- * Pixel component (bitwise flags)
- * @typedef {number} PixelComponent
- */
- const PixelComponent = Object.freeze({
- RED: 1,
- GREEN: 2,
- BLUE: 4,
- ALPHA: 8,
- ALL: 15 // = RED | GREEN | BLUE | ALPHA
- });
-
- /**
- * Component ID utility
- */
- const ColorComponentId = Object.freeze({
- [PixelComponent.RED]: 0,
- [PixelComponent.GREEN]: 1,
- [PixelComponent.BLUE]: 2,
- [PixelComponent.ALPHA]: 3
- });
-
- /***/ }),
-
- /***/ 5484:
- /***/ ((__unused_webpack_module, __webpack_exports__, __nested_webpack_require_125347__) => {
-
- "use strict";
- /* harmony export */ __nested_webpack_require_125347__.d(__webpack_exports__, {
- /* harmony export */ "c": () => (/* binding */ Utils)
- /* harmony export */ });
- /* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_125347__(3841);
- /* harmony import */ var _core_speedy_promise__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_125347__(4500);
- /* harmony import */ var _core_settings__WEBPACK_IMPORTED_MODULE_2__ = __nested_webpack_require_125347__(3135);
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * utils.js
- * Generic utilities
- */
-
-
-
-
-
- /**
- * Generic utilities
- */
- class Utils
- {
- /**
- * Generates a warning
- * @param {string} text message text
- * @param {...string} args optional text
- */
- static warning(text, ...args)
- {
- //if(Settings.logging === 'default' || Settings.logging === 'diagnostic') // TODO: warnings & errors only?
- if(_core_settings__WEBPACK_IMPORTED_MODULE_2__/* .Settings.logging */ .Z.logging !== 'none')
- console.warn('[speedy-vision] ' + text, ...args);
- }
-
- /**
- * Logs a message
- * @param {string} text message text
- * @param {...string} args optional text
- */
- static log(text, ...args)
- {
- if(_core_settings__WEBPACK_IMPORTED_MODULE_2__/* .Settings.logging */ .Z.logging !== 'none')
- console.log('[speedy-vision] ' + text, ...args);
- }
-
- /**
- * Assertion
- * @param {boolean} expr expression
- * @param {string} [text] error message
- * @throws {AssertionError}
- */
- static assert(expr, text = '')
- {
- if(!expr)
- throw new _errors__WEBPACK_IMPORTED_MODULE_0__/* .AssertionError */ .ps(text);
- }
-
- /**
- * Gets the names of the arguments of the specified function
- * @param {Function} fun
- * @returns {string[]}
- */
- static functionArguments(fun)
- {
- const code = fun.toString();
- const regex = code.startsWith('function') ? 'function\\s.*\\(([^)]*)\\)' :
- (code.startsWith('(') ? '\\(([^)]*)\\).*=>' : '([^=]+).*=>');
- const match = new RegExp(regex).exec(code);
-
- if(match !== null) {
- const args = match[1].replace(/\/\*.*?\*\//g, ''); // remove comments
- return args.split(',').map(argname =>
- argname.replace(/=.*$/, '').trim() // remove default params & trim
- ).filter(argname =>
- argname // handle trailing commas
- );
- }
- else
- throw new _errors__WEBPACK_IMPORTED_MODULE_0__/* .ParseError */ .D3(`Can't detect function arguments of ${code}`);
- }
-
- /**
- * Get all property descriptors from an object,
- * traversing its entire prototype chain
- * @param {object} obj
- * @returns {object}
- */
- static getAllPropertyDescriptors(obj)
- {
- if(obj) {
- const proto = Object.getPrototypeOf(obj);
-
- return {
- ...(Utils.getAllPropertyDescriptors(proto)),
- ...Object.getOwnPropertyDescriptors(obj)
- };
- }
- else
- return Object.create(null);
- }
-
- /**
- * Creates a HTMLCanvasElement with the given dimensions
- * @param {number} width in pixels
- * @param {number} height in pixels
- * @returns {HTMLCanvasElement}
- */
- static createCanvas(width, height)
- {
- const canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- return canvas;
- }
-
- /**
- * Generates a random number with
- * Gaussian distribution (mu, sigma)
- * @param {number} mu mean
- * @param {number} sigma standard deviation
- * @returns {number} random number
- */
- static gaussianNoise(mu = 0, sigma = 1)
- {
- // Box-Muller transformation
- const TWO_PI = 2.0 * Math.PI;
-
- let a, b = Math.random();
- do { a = Math.random(); } while(a <= Number.EPSILON);
- let z = Math.sqrt(-2 * Math.log(a)) * Math.sin(TWO_PI * b);
-
- return z * sigma + mu;
- }
-
- /**
- * Generate a 1D gaussian kernel with custom sigma
- * Tip: use kernelSize >= (5 * sigma), kernelSize odd
- * @param {number} sigma gaussian sigma
- * @param {number} [kernelSize] kernel size, odd number
- * @param {boolean} [normalized] normalize entries so that their sum is 1
- * @returns {number[]}
- */
- static gaussianKernel(sigma, kernelSize = 0, normalized = true)
- {
- /*
- * Let G(x) be a Gaussian function centered at 0 with fixed sigma:
- *
- * G(x) = (1 / (sigma * sqrt(2 * pi))) * exp(-(x / (sqrt(2) * sigma))^2)
- *
- * In addition, let f(p) be a kernel value at pixel p, -k/2 <= p <= k/2:
- *
- * f(p) = \int_{p - 0.5}^{p + 0.5} G(x) dx (integrate around p)
- * = \int_{0}^{p + 0.5} G(x) dx - \int_{0}^{p - 0.5} G(x) dx
- *
- * Setting a constant c := sqrt(2) * sigma, it follows that:
- *
- * f(p) = (1 / 2c) * (erf((p + 0.5) / c) - erf((p - 0.5) / c))
- */
-
- // default kernel size
- if(kernelSize == 0) {
- kernelSize = Math.ceil(5.0 * sigma) | 0;
- kernelSize += 1 - (kernelSize % 2);
- }
-
- // validate input
- kernelSize |= 0;
- if(kernelSize < 1 || kernelSize % 2 == 0)
- throw new _errors__WEBPACK_IMPORTED_MODULE_0__/* .IllegalArgumentError */ .mG(`Invalid kernel size given to gaussianKernel: ${kernelSize} x 1`);
- else if(sigma <= 0.0)
- throw new _errors__WEBPACK_IMPORTED_MODULE_0__/* .IllegalArgumentError */ .mG(`Invalid sigma given to gaussianKernel: ${sigma}`);
-
- // function erf(x) = -erf(-x) can be approximated numerically. See:
- // https://en.wikipedia.org/wiki/Error_function#Numerical_approximations
- const kernel = new Array(kernelSize);
-
- // set constants
- const N = kernelSize >> 1; // integer (floor, div 2)
- const c = (+sigma) * 1.4142135623730951; // sigma * sqrt(2)
- const m = 0.3275911;
- const a1 = 0.254829592;
- const a2 = -0.284496736;
- const a3 = 1.421413741;
- const a4 = -1.453152027;
- const a5 = 1.061405429;
-
- // compute the kernel
- let sum = 0.0;
- for(let j = 0; j < kernelSize; j++) {
- let xa = (j - N + 0.5) / c;
- let xb = (j - N - 0.5) / c;
- let sa = 1.0, sb = 1.0;
-
- if(xa < 0.0) { sa = -1.0; xa = -xa; }
- if(xb < 0.0) { sb = -1.0; xb = -xb; }
-
- const ta = 1.0 / (1.0 + m * xa);
- const tb = 1.0 / (1.0 + m * xb);
- const pa = ((((a5 * ta + a4) * ta + a3) * ta + a2) * ta + a1) * ta;
- const pb = ((((a5 * tb + a4) * tb + a3) * tb + a2) * tb + a1) * tb;
- const ya = 1.0 - pa * Math.exp(-xa * xa);
- const yb = 1.0 - pb * Math.exp(-xb * xb);
-
- const erfa = sa * ya;
- const erfb = sb * yb;
- const fp = (erfa - erfb) / (2.0 * c);
-
- kernel[j] = fp;
- sum += fp;
- }
-
- // normalize the kernel
- if(normalized) {
- for(let j = 0; j < kernelSize; j++)
- kernel[j] /= sum;
- }
-
- // done!
- return kernel;
- }
-
- /**
- * Generate a 2D kernel in column-major format using two separable 1D kernels
- * @param {number[]} ka 1D kernel
- * @param {number[]} [kb]
- * @returns {number[]}
- */
- static kernel2d(ka, kb = ka)
- {
- const ksize = ka.length;
- Utils.assert(ka.length == ka.length);
- Utils.assert(ksize >= 1 && ksize % 2 == 1);
-
- // compute the outer product ka x kb
- let kernel2d = new Array(ksize * ksize), k = 0;
- for(let col = 0; col < ksize; col++) {
- for(let row = 0; row < ksize; row++)
- kernel2d[k++] = ka[row] * kb[col];
- }
-
- return kernel2d;
- }
-
- /**
- * Cartesian product a x b: [ [ai, bj] for all i, j ]
- * @param {number[]} a
- * @param {number[]} b
- * @returns {Array<[number,number]>}
- */
- static cartesian(a, b)
- {
- return [].concat(...a.map(a => b.map(b => [a, b])));
- }
-
- /**
- * Symmetric range
- * @param {number} n non-negative integer
- * @returns {number[]} [ -n, ..., n ]
- */
- static symmetricRange(n)
- {
- if((n |= 0) < 0)
- throw new _errors__WEBPACK_IMPORTED_MODULE_0__/* .IllegalArgumentError */ .mG(`Expected a non-negative integer as input`);
-
- return [...(Array(2*n + 1).keys())].map(x => x - n);
- }
-
- /**
- * Compute the [0, n) range of integers
- * @param {number} n positive integer
- * @returns {number[]} [ 0, 1, ..., n-1 ]
- */
- static range(n)
- {
- if((n |= 0) <= 0)
- throw new _errors__WEBPACK_IMPORTED_MODULE_0__/* .IllegalArgumentError */ .mG(`Expected a positive integer as input`);
-
- return [...(Array(n).keys())];
- }
-
- /**
- * Shuffle in-place
- * @template T
- * @param {T[]} arr
- * @returns {T[]} arr
- */
- static shuffle(arr)
- {
- const len = arr.length;
- const m = len - 1;
-
- // Fisher-Yattes
- for(let i = 0; i < m; i++) {
- const j = i + ((Math.random() * (len - i)) | 0); // i <= j < arr.length
-
- if(i !== j) {
- const t = arr[i];
- arr[i] = arr[j];
- arr[j] = t;
- }
- }
-
- return arr;
- }
-
- /**
- * Flatten an array (1 level only)
- * @template U
- * @param {U[]} array
- * @returns {U[]}
- */
- static flatten(array)
- {
- //return array.flat();
- //return array.reduce((arr, val) => arr.concat(val), []);
-
- const flat = [];
-
- for(let i = 0, n = array.length; i < n; i++) {
- const entry = array[i];
-
- if(Array.isArray(entry)) {
- for(let j = 0, m = entry.length; j < m; j++)
- flat.push(entry[j]);
- }
- else
- flat.push(entry);
- }
-
- return flat;
- }
-
- /**
- * Decode a 16-bit float from a
- * unsigned 16-bit integer
- * @param {number} uint16
- * @returns {number}
- */
- static decodeFloat16(uint16)
- {
- // decode according to sec 2.1.2
- // 16-Bit Floating Point Numbers
- // of the OpenGL ES 3 spec
- const s = (uint16 & 0xFFFF) >> 15; // sign bit
- const e = (uint16 & 0x7FFF) >> 10; // exponent
- const m = (uint16 & 0x3FF); // mantissa
- const sign = 1 - 2 * s; // (-1)^s
-
- if(e == 0)
- return m == 0 ? sign * 0.0 : sign * m * 5.960464477539063e-8; // zero / subnormal
- else if(e == 31)
- return m == 0 ? sign * Number.POSITIVE_INFINITY : Number.NaN;
-
- const f = e >= 15 ? (1 << (e-15)) : 1.0 / (1 << (15-e)); // 2^(e-15)
- return sign * f * (1.0 + m * 0.0009765625); // normal
- }
-
- /**
- * Wrapper around getUserMedia()
- * @param {MediaStreamConstraints} [constraints] will be passed to getUserMedia()
- * @returns {SpeedyPromise<HTMLVideoElement>}
- */
- static requestCameraStream(constraints = { audio: false, video: true })
- {
- Utils.log('Accessing the webcam...');
-
- if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia)
- throw new _errors__WEBPACK_IMPORTED_MODULE_0__/* .NotSupportedError */ .B8('Unsupported browser: no mediaDevices.getUserMedia()');
-
- return new _core_speedy_promise__WEBPACK_IMPORTED_MODULE_1__/* .SpeedyPromise */ .s((resolve, reject) => {
- navigator.mediaDevices.getUserMedia(constraints).then(stream => {
- const video = document.createElement('video');
-
- video.onloadedmetadata = () => {
- video.play();
- Utils.log(`The camera is on! Resolution: ${video.videoWidth} x ${video.videoHeight}`);
- resolve(video);
- };
-
- video.setAttribute('playsinline', '');
- video.setAttribute('autoplay', '');
- if(constraints.audio === false || constraints.audio === undefined)
- video.setAttribute('muted', '');
-
- video.srcObject = stream;
- })
- .catch(err => {
- if(err.name === 'NotAllowedError') {
- reject(new _errors__WEBPACK_IMPORTED_MODULE_0__/* .AccessDeniedError */ .$y(
- `Please give access to the camera and reload the page.`,
- err
- ));
- }
- else if(err.name === 'OverconstrainedError' || err.name === 'NotFoundError') {
- reject(new _errors__WEBPACK_IMPORTED_MODULE_0__/* .NotSupportedError */ .B8(
- `Can't access the webcam with the requested constraints: ${JSON.stringify(constraints)}.`,
- err
- ));
- }
- else {
- reject(new _errors__WEBPACK_IMPORTED_MODULE_0__/* .SpeedyError */ .nU(
- `Can't access the webcam.`,
- err
- ));
- }
- });
- });
- }
-
- /**
- * Format binary data as a string with hex values
- * @param {ArrayBuffer} bytes
- * @returns {string}
- */
- static formatBinaryData(bytes)
- {
- const uint8 = new Uint8Array(bytes);
- const array = Array.from(uint8, b => b.toString(16).padStart(2, '0'));
-
- return array.join(' ');
- }
- }
-
- /***/ }),
-
- /***/ 4645:
- /***/ ((module) => {
-
- module.exports = "#if !defined(KERNEL_SIZE) || !defined(AXIS) || (AXIS != 0 && AXIS != 1)\n#error Undefined KERNEL_SIZE / AXIS\n#endif\nuniform sampler2D image;\nuniform float kernel[@KERNEL_SIZE@];\nconst ivec2 axis = ivec2(1-AXIS, AXIS);\n#define S(x,k) result += pixelAtShortOffset(image, ivec2((x),(x)) * axis) * kernel[k]\nvoid main()\n{\nvec4 result = vec4(0.0f);\n#if KERNEL_SIZE == 3\nS(-1, 2);\nS( 0, 1);\nS( 1, 0);\n#elif KERNEL_SIZE == 5\nS(-2, 4);\nS(-1, 3);\nS( 0, 2);\nS( 1, 1);\nS( 2, 0);\n#elif KERNEL_SIZE == 7\nS(-3, 6);\nS(-2, 5);\nS(-1, 4);\nS( 0, 3);\nS( 1, 2);\nS( 2, 1);\nS( 3, 0);\n#elif KERNEL_SIZE == 9\nS(-4, 8);\nS(-3, 7);\nS(-2, 6);\nS(-1, 5);\nS( 0, 4);\nS( 1, 3);\nS( 2, 2);\nS( 3, 1);\nS( 4, 0);\n#elif KERNEL_SIZE == 11\nS(-5, 10);\nS(-4, 9);\nS(-3, 8);\nS(-2, 7);\nS(-1, 6);\nS( 0, 5);\nS( 1, 4);\nS( 2, 3);\nS( 3, 2);\nS( 4, 1);\nS( 5, 0);\n#elif KERNEL_SIZE == 13\nS(-6, 12);\nS(-5, 11);\nS(-4, 10);\nS(-3, 9);\nS(-2, 8);\nS(-1, 7);\nS( 0, 6);\nS( 1, 5);\nS( 2, 4);\nS( 3, 3);\nS( 4, 2);\nS( 5, 1);\nS( 6, 0);\n#elif KERNEL_SIZE == 15\nS(-7, 14);\nS(-6, 13);\nS(-5, 12);\nS(-4, 11);\nS(-3, 10);\nS(-2, 9);\nS(-1, 8);\nS( 0, 7);\nS( 1, 6);\nS( 2, 5);\nS( 3, 4);\nS( 4, 3);\nS( 5, 2);\nS( 6, 1);\nS( 7, 0);\n#else\n#error Invalid parameters\n#endif\ncolor = vec4(result.rgb, 1.0f);\n}"
-
- /***/ }),
-
- /***/ 6942:
- /***/ ((module) => {
-
- module.exports = "#ifndef KERNEL_SIZE_SQUARED\n#define Must define KERNEL_SIZE_SQUARED\n#endif\nuniform sampler2D image;\nuniform float kernel[@KERNEL_SIZE_SQUARED@];\n#define S(x,y,k) result += pixelAtShortOffset(image, ivec2((x),(y))) * kernel[k]\nvoid main()\n{\nvec4 result = vec4(0.0f);\n#if KERNEL_SIZE_SQUARED == 9\nS(-1,-1, 8);\nS(-1, 0, 7);\nS(-1, 1, 6);\nS( 0,-1, 5);\nS( 0, 0, 4);\nS( 0, 1, 3);\nS( 1,-1, 2);\nS( 1, 0, 1);\nS( 1, 1, 0);\n#elif KERNEL_SIZE_SQUARED == 25\nS(-2,-2, 24);\nS(-2,-1, 23);\nS(-2, 0, 22);\nS(-2, 1, 21);\nS(-2, 2, 20);\nS(-1,-2, 19);\nS(-1,-1, 18);\nS(-1, 0, 17);\nS(-1, 1, 16);\nS(-1, 2, 15);\nS( 0,-2, 14);\nS( 0,-1, 13);\nS( 0, 0, 12);\nS( 0, 1, 11);\nS( 0, 2, 10);\nS( 1,-2, 9);\nS( 1,-1, 8);\nS( 1, 0, 7);\nS( 1, 1, 6);\nS( 1, 2, 5);\nS( 2,-2, 4);\nS( 2,-1, 3);\nS( 2, 0, 2);\nS( 2, 1, 1);\nS( 2, 2, 0);\n#elif KERNEL_SIZE_SQUARED == 49\nS(-3,-3, 48);\nS(-3,-2, 47);\nS(-3,-1, 46);\nS(-3, 0, 45);\nS(-3, 1, 44);\nS(-3, 2, 43);\nS(-3, 3, 42);\nS(-2,-3, 41);\nS(-2,-2, 40);\nS(-2,-1, 39);\nS(-2, 0, 38);\nS(-2, 1, 37);\nS(-2, 2, 36);\nS(-2, 3, 35);\nS(-1,-3, 34);\nS(-1,-2, 33);\nS(-1,-1, 32);\nS(-1, 0, 31);\nS(-1, 1, 30);\nS(-1, 2, 29);\nS(-1, 3, 28);\nS( 0,-3, 27);\nS( 0,-2, 26);\nS( 0,-1, 25);\nS( 0, 0, 24);\nS( 0, 1, 23);\nS( 0, 2, 22);\nS( 0, 3, 21);\nS( 1,-3, 20);\nS( 1,-2, 19);\nS( 1,-1, 18);\nS( 1, 0, 17);\nS( 1, 1, 16);\nS( 1, 2, 15);\nS( 1, 3, 14);\nS( 2,-3, 13);\nS( 2,-2, 12);\nS( 2,-1, 11);\nS( 2, 0, 10);\nS( 2, 1, 9);\nS( 2, 2, 8);\nS( 2, 3, 7);\nS( 3,-3, 6);\nS( 3,-2, 5);\nS( 3,-1, 4);\nS( 3, 0, 3);\nS( 3, 1, 2);\nS( 3, 2, 1);\nS( 3, 3, 0);\n#else\n#error Invalid KERNEL_SIZE_SQUARED\n#endif\ncolor = vec4(result.rgb, 1.0f);\n}"
-
- /***/ }),
-
- /***/ 7054:
- /***/ ((module) => {
-
- module.exports = "uniform sampler2D image;\n#define X(i,j) t = vec2(min(p[i], p[j]), max(p[i], p[j])); p[i] = t.x; p[j] = t.y;\n#define S(i,x,y) p[i] = pixelAtShortOffset(image, ivec2((x),(y))).g\nvoid main()\n{\nfloat median;\nvec2 t;\n#if !defined(KERNEL_SIZE)\n#error Must define KERNEL_SIZE\n#elif KERNEL_SIZE == 3\nfloat p[9];\nS(0,-1,-1);\nS(1, 0,-1);\nS(2, 1,-1);\nS(3,-1, 0);\nS(4, 0, 0);\nS(5, 1, 0);\nS(6,-1, 1);\nS(7, 0, 1);\nS(8, 1, 1);\nX(1,2);X(4,5);X(7,8);X(0,1);X(3,4);X(6,7);X(1,2);X(4,5);X(7,8);X(0,3);X(5,8);X(4,7);X(3,6);X(1,4);X(2,5);X(4,7);X(4,2);X(6,4);X(4,2);\nmedian = p[4];\n#elif KERNEL_SIZE == 5\nfloat p[25];\nS( 0,-2,-2);\nS( 1,-1,-2);\nS( 2, 0,-2);\nS( 3, 1,-2);\nS( 4, 2,-2);\nS( 5,-2,-1);\nS( 6,-1,-1);\nS( 7, 0,-1);\nS( 8, 1,-1);\nS( 9, 2,-1);\nS(10,-2, 0);\nS(11,-1, 0);\nS(12, 0, 0);\nS(13, 1, 0);\nS(14, 2, 0);\nS(15,-2, 1);\nS(16,-1, 1);\nS(17, 0, 1);\nS(18, 1, 1);\nS(19, 2, 1);\nS(20,-2, 2);\nS(21,-1, 2);\nS(22, 0, 2);\nS(23, 1, 2);\nS(24, 2, 2);\nX(0,1);X(3,4);X(2,4);X(2,3);X(6,7);X(5,7);X(5,6);X(9,10);X(8,10);X(8,9);X(12,13);X(11,13);X(11,12);X(15,16);X(14,16);X(14,15);X(18,19);X(17,19);X(17,18);X(21,22);X(20,22);X(20,21);X(23,24);X(2,5);X(3,6);X(0,6);X(0,3);X(4,7);X(1,7);X(1,4);X(11,14);X(8,14);X(8,11);X(12,15);X(9,15);X(9,12);X(13,16);X(10,16);X(10,13);X(20,23);X(17,23);X(17,20);X(21,24);X(18,24);X(18,21);X(19,22);X(8,17);X(9,18);X(0,18);X(0,9);X(10,19);X(1,19);X(1,10);X(11,20);X(2,20);X(2,11);X(12,21);X(3,21);X(3,12);X(13,22);X(4,22);X(4,13);X(14,23);X(5,23);X(5,14);X(15,24);X(6,24);X(6,15);X(7,16);X(7,19);X(13,21);X(15,23);X(7,13);X(7,15);X(1,9);X(3,11);X(5,17);X(11,17);X(9,17);X(4,10);X(6,12);X(7,14);X(4,6);X(4,7);X(12,14);X(10,14);X(6,7);X(10,12);X(6,10);X(6,17);X(12,17);X(7,17);X(7,10);X(12,18);X(7,12);X(10,18);X(12,20);X(10,20);X(10,12);\nmedian = p[12];\n#elif KERNEL_SIZE == 7\nfloat p[49];\nS( 0,-3,-3);\nS( 1,-2,-3);\nS( 2,-1,-3);\nS( 3, 0,-3);\nS( 4, 1,-3);\nS( 5, 2,-3);\nS( 6, 3,-3);\nS( 7,-3,-2);\nS( 8,-2,-2);\nS( 9,-1,-2);\nS(10, 0,-2);\nS(11, 1,-2);\nS(12, 2,-2);\nS(13, 3,-2);\nS(14,-3,-1);\nS(15,-2,-1);\nS(16,-1,-1);\nS(17, 0,-1);\nS(18, 1,-1);\nS(19, 2,-1);\nS(20, 3,-1);\nS(21,-3, 0);\nS(22,-2, 0);\nS(23,-1, 0);\nS(24, 0, 0);\nS(25, 1, 0);\nS(26, 2, 0);\nS(27, 3, 0);\nS(28,-3, 1);\nS(29,-2, 1);\nS(30,-1, 1);\nS(31, 0, 1);\nS(32, 1, 1);\nS(33, 2, 1);\nS(34, 3, 1);\nS(35,-3, 2);\nS(36,-2, 2);\nS(37,-1, 2);\nS(38, 0, 2);\nS(39, 1, 2);\nS(40, 2, 2);\nS(41, 3, 2);\nS(42,-3, 3);\nS(43,-2, 3);\nS(44,-1, 3);\nS(45, 0, 3);\nS(46, 1, 3);\nS(47, 2, 3);\nS(48, 3, 3);\nX(0,1);X(2,3);X(0,2);X(1,3);X(1,2);X(4,5);X(6,7);X(4,6);X(5,7);X(5,6);X(0,4);X(2,6);X(2,4);X(1,5);X(3,7);X(3,5);X(1,2);X(3,4);X(5,6);X(8,9);X(10,11);X(8,10);X(9,11);X(9,10);X(12,13);X(14,15);X(12,14);X(13,15);X(13,14);X(8,12);X(10,14);X(10,12);X(9,13);X(11,15);X(11,13);X(9,10);X(11,12);X(13,14);X(0,8);X(4,12);X(4,8);X(2,10);X(6,14);X(6,10);X(2,4);X(6,8);X(10,12);X(1,9);X(5,13);X(5,9);X(3,11);X(7,15);X(7,11);X(3,5);X(7,9);X(11,13);X(1,2);X(3,4);X(5,6);X(7,8);X(9,10);X(11,12);X(13,14);X(16,17);X(18,19);X(16,18);X(17,19);X(17,18);X(20,21);X(22,23);X(20,22);X(21,23);X(21,22);X(16,20);X(18,22);X(18,20);X(17,21);X(19,23);X(19,21);X(17,18);X(19,20);X(21,22);X(24,25);X(26,27);X(24,26);X(25,27);X(25,26);X(28,29);X(30,31);X(28,30);X(29,31);X(29,30);X(24,28);X(26,30);X(26,28);X(25,29);X(27,31);X(27,29);X(25,26);X(27,28);X(29,30);X(16,24);X(20,28);X(20,24);X(18,26);X(22,30);X(22,26);X(18,20);X(22,24);X(26,28);X(17,25);X(21,29);X(21,25);X(19,27);X(23,31);X(23,27);X(19,21);X(23,25);X(27,29);X(17,18);X(19,20);X(21,22);X(23,24);X(25,26);X(27,28);X(29,30);X(0,16);X(8,24);X(8,16);X(4,20);X(12,28);X(12,20);X(4,8);X(12,16);X(20,24);X(2,18);X(10,26);X(10,18);X(6,22);X(14,30);X(14,22);X(6,10);X(14,18);X(22,26);X(2,4);X(6,8);X(10,12);X(14,16);X(18,20);X(22,24);X(26,28);X(1,17);X(9,25);X(9,17);X(5,21);X(13,29);X(13,21);X(5,9);X(13,17);X(21,25);X(3,19);X(11,27);X(11,19);X(7,23);X(15,31);X(15,23);X(7,11);X(15,19);X(23,27);X(3,5);X(7,9);X(11,13);X(15,17);X(19,21);X(23,25);X(27,29);X(1,2);X(3,4);X(5,6);X(7,8);X(9,10);X(11,12);X(13,14);X(15,16);X(17,18);X(19,20);X(21,22);X(23,24);X(25,26);X(27,28);X(29,30);X(32,33);X(34,35);X(32,34);X(33,35);X(33,34);X(36,37);X(38,39);X(36,38);X(37,39);X(37,38);X(32,36);X(34,38);X(34,36);X(33,37);X(35,39);X(35,37);X(33,34);X(35,36);X(37,38);X(40,41);X(42,43);X(40,42);X(41,43);X(41,42);X(44,45);X(46,47);X(44,46);X(45,47);X(45,46);X(40,44);X(42,46);X(42,44);X(41,45);X(43,47);X(43,45);X(41,42);X(43,44);X(45,46);X(32,40);X(36,44);X(36,40);X(34,42);X(38,46);X(38,42);X(34,36);X(38,40);X(42,44);X(33,41);X(37,45);X(37,41);X(35,43);X(39,47);X(39,43);X(35,37);X(39,41);X(43,45);X(33,34);X(35,36);X(37,38);X(39,40);X(41,42);X(43,44);X(45,46);X(32,48);X(40,48);X(36,40);X(44,48);X(38,42);X(34,36);X(38,40);X(42,44);X(46,48);X(37,41);X(39,43);X(35,37);X(39,41);X(43,45);X(33,34);X(35,36);X(37,38);X(39,40);X(41,42);X(43,44);X(45,46);X(47,48);X(0,32);X(16,48);X(16,32);X(8,40);X(24,40);X(8,16);X(24,32);X(40,48);X(4,36);X(20,36);X(12,44);X(28,44);X(12,20);X(28,36);X(4,8);X(12,16);X(20,24);X(28,32);X(36,40);X(44,48);X(2,34);X(18,34);X(10,42);X(26,42);X(10,18);X(26,34);X(6,38);X(22,38);X(14,46);X(30,46);X(14,22);X(30,38);X(6,10);X(14,18);X(22,26);X(30,34);X(38,42);X(2,4);X(6,8);X(10,12);X(14,16);X(18,20);X(22,24);X(26,28);X(30,32);X(34,36);X(38,40);X(42,44);X(46,48);X(1,33);X(17,33);X(9,41);X(25,41);X(9,17);X(25,33);X(5,37);X(21,37);X(13,45);X(29,45);X(13,21);X(29,37);X(5,9);X(13,17);X(21,25);X(29,33);X(37,41);X(3,35);X(19,35);X(11,43);X(27,43);X(11,19);X(27,35);X(7,39);X(23,39);X(15,47);X(31,47);X(15,23);X(31,39);X(7,11);X(15,19);X(23,27);X(31,35);X(39,43);X(3,5);X(7,9);X(11,13);X(15,17);X(19,21);X(23,25);X(27,29);X(31,33);X(35,37);X(39,41);X(43,45);X(1,2);X(3,4);X(5,6);X(7,8);X(9,10);X(11,12);X(13,14);X(15,16);X(17,18);X(19,20);X(21,22);X(23,24);\nmedian = p[24];\n#else\n#error Unsupported kernel size\n#endif\ncolor = vec4(median, median, median, 1.0f);\n}"
-
- /***/ }),
-
- /***/ 8961:
- /***/ ((module) => {
-
- module.exports = "uniform sampler2D image;\nuniform sampler2D illuminationMap;\nuniform float gain;\nuniform float offset;\nuniform float decay;\n#ifndef GREYSCALE\n#error Must define GREYSCALE\n#endif\n#if GREYSCALE == 0\nconst mat3 rgb2yuv = mat3(\n0.299f, -0.14713f, 0.615f,\n0.587f, -0.28886f, -0.51499f,\n0.114f, 0.436f, -0.10001f\n);\nconst mat3 yuv2rgb = mat3(\n1.0f, 1.0f, 1.0f,\n0.0f, -0.39465f, 2.03211f,\n1.13983f, -0.58060f, 0.0f\n);\n#endif\nconst float eps = 0.0001f;\nconst float sqrt2 = 1.4142135623730951f;\nconst float magic = 20.0f;\nconst vec2 center = vec2(0.5f);\nvoid main()\n{\nvec4 pixel = threadPixel(image);\nvec4 imapPixel = threadPixel(illuminationMap);\nfloat lambda = -sqrt2 * log(max(1.0f - decay, eps));\nfloat dist = length(texCoord - center);\nfloat vgain = gain * exp(-lambda * dist);\nfloat normalizedGain = 2.0f * vgain;\nfloat normalizedOffset = 2.0f * offset - 1.0f;\n#if GREYSCALE != 0\nfloat luma = 1.0 / (1.0 + exp(-normalizedGain * magic * (pixel.g - imapPixel.g)));\nluma = clamp(luma + normalizedOffset, 0.0f, 1.0f);\ncolor = vec4(luma, luma, luma, 1.0f);\n#else\nvec3 yuvPixel = rgb2yuv * pixel.rgb;\nvec3 yuvImapPixel = rgb2yuv * imapPixel.rgb;\nfloat luma = 1.0 / (1.0 + exp(-normalizedGain * magic * (yuvPixel.r - yuvImapPixel.r)));\nluma += normalizedOffset;\nvec3 rgbCorrectedPixel = yuv2rgb * vec3(luma, yuvPixel.gb);\nrgbCorrectedPixel = clamp(rgbCorrectedPixel, 0.0f, 1.0f);\ncolor = vec4(rgbCorrectedPixel, 1.0f);\n#endif\n}"
-
- /***/ }),
-
- /***/ 9571:
- /***/ ((module) => {
-
- module.exports = "#ifndef GREYSCALE\n#error Must define GREYSCALE\n#endif\n#if GREYSCALE != 0\nuniform sampler2D minmax2d;\n#else\nuniform sampler2D minmax2dRGB[3];\n#endif\nuniform float minValue;\nuniform float maxValue;\nconst float eps = 1.0f / 255.0f;\nvoid main()\n{\nvec2 minmax = clamp(vec2(minValue, maxValue), 0.0f, 255.0f) / 255.0f;\nvec4 newMin = vec4(minmax.x);\nvec4 newRange = vec4(minmax.y - minmax.x);\nvec4 alpha = vec4(1.0f, newMin.x, newRange.x, 1.0f);\n#if GREYSCALE != 0\nvec4 pixel = threadPixel(minmax2d);\nmat4 channel = mat4(pixel, pixel, pixel, alpha);\n#else\nmat4 channel = mat4(\nthreadPixel(minmax2dRGB[0]),\nthreadPixel(minmax2dRGB[1]),\nthreadPixel(minmax2dRGB[2]),\nalpha\n);\n#endif\nvec4 oldMin = vec4(channel[0].g, channel[1].g, channel[2].g, channel[3].g);\nvec4 oldRange = max(vec4(channel[0].b, channel[1].b, channel[2].b, channel[3].b), eps);\nvec4 oldIntensity = vec4(channel[0].a, channel[1].a, channel[2].a, channel[3].a);\nvec4 newIntensity = (oldIntensity - oldMin) * newRange / oldRange + newMin;\ncolor = newIntensity;\n}"
-
- /***/ }),
-
- /***/ 8466:
- /***/ ((module) => {
-
- module.exports = "const vec4 grey = vec4(0.299f, 0.587f, 0.114f, 0.0f);\nuniform sampler2D image;\nvoid main()\n{\nvec4 pixel = threadPixel(image);\nfloat g = dot(pixel, grey);\ncolor = vec4(g, g, g, 1.0f);\n}"
-
- /***/ }),
-
- /***/ 2545:
- /***/ ((module) => {
-
- module.exports = "#ifndef _COLORS_GLSL\n#define _COLORS_GLSL\n#define PIXELCOMPONENT_RED @PIXELCOMPONENT_RED@\n#define PIXELCOMPONENT_GREEN @PIXELCOMPONENT_GREEN@\n#define PIXELCOMPONENT_BLUE @PIXELCOMPONENT_BLUE@\n#define PIXELCOMPONENT_ALPHA @PIXELCOMPONENT_ALPHA@\n#endif"
-
- /***/ }),
-
- /***/ 7373:
- /***/ ((module) => {
-
- module.exports = "#ifndef _FILTERS_GLSL\n#define _FILTERS_GLSL\nfloat laplacian(sampler2D pyramid, vec2 position, float lod)\n{\nfloat pot = exp2(lod);\nivec2 pyrBaseSize = textureSize(pyramid, 0);\nconst vec3 ones = vec3(1.0f);\nconst mat3 kernel = mat3(\n0,-1, 0,\n-1, 4,-1,\n0,-1, 0\n);\n#define LPC(x,y) pyrSubpixelAtExOffset(pyramid, position, lod, pot, ivec2((x),(y)), pyrBaseSize).g\nmat3 neighborhood = mat3(\n0.0f, LPC(0,-1), 0.0f,\nLPC(-1,0), LPC(0,0), LPC(1,0),\n0.0f, LPC(0,1), 0.0f\n);\nmat3 m = matrixCompMult(neighborhood, kernel);\nreturn dot(ones, vec3(\ndot(m[0], ones),\ndot(m[1], ones),\ndot(m[2], ones)\n)) * (1.0f + lod);\n}\n#endif"
-
- /***/ }),
-
- /***/ 2229:
- /***/ ((module) => {
-
- module.exports = "#ifndef _FIXEDPOINT_GLSL\n#define _FIXEDPOINT_GLSL\n#define fixed_t int\n#define fixed2_t ivec2\nconst int FIX_BITS = int(@FIX_BITS@);\nconst float FIX_RESOLUTION = float(@FIX_RESOLUTION@);\n#define itofix(x) fixed_t((x) << FIX_BITS)\n#define fixtoi(f) int((x) >> FIX_BITS)\n#define ftofix(x) fixed_t((x) * FIX_RESOLUTION + 0.5f)\n#define fixtof(f) (float(f) / FIX_RESOLUTION)\n#define ivec2tofix(x) fixed2_t((x) << FIX_BITS)\n#define fixtoivec2(f) ivec2((f) >> FIX_BITS)\n#define vec2tofix(v) fixed2_t((v) * FIX_RESOLUTION + vec2(0.5f))\n#define fixtovec2(f) (vec2(f) / FIX_RESOLUTION)\n#endif"
-
- /***/ }),
-
- /***/ 919:
- /***/ ((module) => {
-
- module.exports = "#ifndef _FLOAT16_GLSL\n#define _FLOAT16_GLSL\n#define encodeFloat16(f) (vec2(packf16(f)) / 255.0f)\n#define decodeFloat16(v) unpackf16(uvec2((v) * 255.0f))\n#define encodePairOfFloat16(f) vec4(encodeFloat16((f).x), encodeFloat16((f).y))\n#define decodePairOfFloat16(v) vec2(decodeFloat16((v).rg), decodeFloat16((v).ba))\n#define encodeNullPairOfFloat16() vec4(1.0f)\n#define isNullPairOfFloat16(v) all(equal((v), encodeNullPairOfFloat16()))\n#define encodeDiscardedPairOfFloat16() vec4(0.0f, 1.0f, 0.0f, 1.0f)\n#define isDiscardedPairOfFloat16(v) all(equal((v), encodeDiscardedPairOfFloat16()))\n#define encodeFloat16NaN() vec2(0.5f, 1.0f)\n#define isEncodedFloat16NaN(v) all(equal((v), encodeFloat16NaN()))\nuvec2 packf16( float f)\n{\nuint y = packHalf2x16(vec2(f, 0.0f));\nreturn uvec2(y, y >> 8u) & 0xFFu;\n}\nfloat unpackf16(uvec2 v)\n{\nv &= 0xFFu;\nreturn unpackHalf2x16(v.x | (v.y << 8u)).x;\n}\nbool isEncodedFloat16Zero(vec2 v)\n{\nuvec2 w = uvec2(v * 255.0f);\nreturn 0u == w.x + w.y * (0x80u - w.y);\n}\n#endif"
-
- /***/ }),
-
- /***/ 3815:
- /***/ ((module) => {
-
- module.exports = "#ifndef _GLOBAL_GLSL\n#define _GLOBAL_GLSL\n#define threadLocation() ivec2(texCoord * texSize)\n#define outputSize() ivec2(texSize)\n#define threadPixel(img) textureLod((img), texCoord, 0.0f)\n#define pixelAt(img, pos) texelFetch((img), (pos), 0)\n#define pixelAtShortOffset(img, offset) textureLodOffset((img), texCoord, 0.0f, (offset))\n#define pixelAtLongOffset(img, offset) textureLod((img), texCoord + vec2(offset) / texSize, 0.0f)\n#endif"
-
- /***/ }),
-
- /***/ 1830:
- /***/ ((module) => {
-
- module.exports = "#ifndef _INT32_GLSL\n#define _INT32_GLSL\nuint decodeUint32(vec4 rgba)\n{\nuvec4 v = uvec4(rgba * 255.0f) & 255u;\nreturn v.x | (v.y << 8u) | (v.z << 16u) | (v.w << 24u);\n}\n#if 1\nvec4 encodeUint32(uint value)\n{\nuvec4 v = uvec4(value, value / 256u, value / 65536u, value / 16777216u) % 256u;\nreturn vec4(v) / 255.0f;\n}\n#else\nvec4 encodeUint32(uint value)\n{\nuvec4 v = uvec4(value, value >> 8u, value >> 16u, value >> 24u) & 255u;\nreturn vec4(v) / 255.0f;\n}\n#endif\n#endif"
-
- /***/ }),
-
- /***/ 1364:
- /***/ ((module) => {
-
- module.exports = "#ifndef _KEYPOINT_DESCRIPTORS_GLSL\n#define _KEYPOINT_DESCRIPTORS_GLSL\n#if !defined(DESCRIPTOR_SIZE)\n#error Must define DESCRIPTOR_SIZE\n#elif !defined(_KEYPOINTS_GLSL)\n#error Must include keypoints.glsl\n#endif\nuint[DESCRIPTOR_SIZE] readKeypointDescriptor(sampler2D encodedKeypoints, int descriptorSize, int extraSize, int encoderLength, KeypointAddress address)\n{\nint descriptorOffset = sizeofEncodedKeypoint(0, extraSize) / 4;\nKeypointAddress descriptorAddress = KeypointAddress(address.base, descriptorOffset);\nuint[DESCRIPTOR_SIZE] descriptor;\nvec4 pixel; uvec4 bytes;\n@unroll\nfor(int i = 0; i < DESCRIPTOR_SIZE; i += 4) {\npixel = readKeypointData(encodedKeypoints, encoderLength, descriptorAddress);\nbytes = uvec4(pixel * 255.0f);\ndescriptor[i] = bytes.r;\ndescriptor[i+1] = bytes.g;\ndescriptor[i+2] = bytes.b;\ndescriptor[i+3] = bytes.a;\ndescriptorAddress.offset++;\n}\nreturn descriptor;\n}\nuint[DESCRIPTOR_SIZE] readKeypointDescriptorFromDB(sampler2D descriptorDB, int descriptorDBStride, int index)\n{\nuint[DESCRIPTOR_SIZE] descriptor;\nint rasterIndex = index * (DESCRIPTOR_SIZE / 4) * int(index >= 0);\nvec4 pixel; uvec4 bytes; ivec2 pos;\n@unroll\nfor(int i = 0; i < DESCRIPTOR_SIZE; i += 4) {\npos = ivec2(rasterIndex % descriptorDBStride, rasterIndex / descriptorDBStride);\npixel = (index >= 0) ? texelFetch(descriptorDB, pos, 0) : vec4(0.0f);\nbytes = uvec4(pixel * 255.0f);\ndescriptor[i] = bytes.r;\ndescriptor[i+1] = bytes.g;\ndescriptor[i+2] = bytes.b;\ndescriptor[i+3] = bytes.a;\nrasterIndex++;\n}\nreturn descriptor;\n}\nint distanceBetweenKeypointDescriptors(uint[DESCRIPTOR_SIZE] a, uint[DESCRIPTOR_SIZE] b)\n{\nconst int[256] POPCNT = int[256](0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8);\nuvec4 xor, u, v;\nint dist = 0;\nivec4 bits;\n@unroll\nfor(int i = 0; i < DESCRIPTOR_SIZE; i += 4) {\nu = uvec4(a[i], a[i+1], a[i+2], a[i+3]);\nv = uvec4(b[i], b[i+1], b[i+2], b[i+3]);\nxor = (u ^ v) & 255u;\nbits = ivec4(POPCNT[xor.x], POPCNT[xor.y], POPCNT[xor.z], POPCNT[xor.w]);\ndist += bits.x + bits.y + bits.z + bits.w;\n}\nreturn dist;\n}\n#endif"
-
- /***/ }),
-
- /***/ 4004:
- /***/ ((module) => {
-
- module.exports = "#ifndef _KEYPOINT_MATCHES_GLSL\n#define _KEYPOINT_MATCHES_GLSL\n@include \"int32.glsl\"\nconst int MATCH_INDEX_BITS = int(@MATCH_INDEX_BITS@);\nconst int MATCH_INDEX_MASK = int(@MATCH_INDEX_MASK@);\nconst int MATCH_MAX_INDEX = int(@MATCH_MAX_INDEX@);\nconst int MATCH_MAX_DISTANCE = int(@MATCH_MAX_DISTANCE@);\nstruct KeypointMatch\n{\nint index;\nint dist;\n};\nvec4 encodeKeypointMatch(KeypointMatch candidate)\n{\nuint index = uint(candidate.index) & uint(MATCH_INDEX_MASK);\nuint dist = uint(clamp(candidate.dist, 0, MATCH_MAX_DISTANCE));\nuint u32 = index | (dist << MATCH_INDEX_BITS);\nreturn encodeUint32(u32);\n}\nKeypointMatch decodeKeypointMatch(vec4 rgba)\n{\nuint u32 = decodeUint32(rgba);\nint dist = int(u32 >> MATCH_INDEX_BITS);\nint index = int(u32 & uint(MATCH_INDEX_MASK));\nreturn KeypointMatch(index, dist);\n}\nconst KeypointMatch MATCH_NOT_FOUND = KeypointMatch(MATCH_MAX_INDEX, MATCH_MAX_DISTANCE);\n#endif"
-
- /***/ }),
-
- /***/ 8714:
- /***/ ((module) => {
-
- module.exports = "#ifndef _KEYPOINTS_GLSL\n#define _KEYPOINTS_GLSL\n@include \"math.glsl\"\n@include \"fixed-point.glsl\"\n@include \"float16.glsl\"\n@include \"pyramids.glsl\"\nstruct Keypoint\n{\nvec2 position;\nfloat lod;\nfloat orientation;\nfloat score;\nuint flags;\n};\nstruct KeypointAddress\n{\nint base;\nint offset;\n};\nconst int MIN_KEYPOINT_SIZE = int(@MIN_KEYPOINT_SIZE@);\nconst int MAX_DESCRIPTOR_SIZE = int(@MAX_DESCRIPTOR_SIZE@);\nconst uint KPF_NONE = 0u;\nconst uint KPF_NULL = 1u;\nconst uint KPF_DISCARDED = 2u;\n#define encodeKeypointScore(score) encodeFloat16(score)\n#define decodeKeypointScore(encodedScore) decodeFloat16(encodedScore)\n#define encodeKeypointOrientation(angle) ((angle) * INV_PI_OVER_2 + 0.5f)\n#define decodeKeypointOrientation(value) ((value) * TWO_PI - PI)\n#define encodeNullKeypoint() (vec4(1.0f))\n#define encodeDiscardedKeypoint() (vec4(0.0f))\n#define isNullKeypoint(keypoint) ((((keypoint).flags) & KPF_NULL) != 0u)\n#define isDiscardedKeypoint(keypoint) ((((keypoint).flags) & KPF_DISCARDED) != 0u)\n#define isBadKeypoint(keypoint) ((keypoint).score < 0.0f)\n#define sizeofEncodedKeypoint(descriptorSize, extraSize) (MIN_KEYPOINT_SIZE + (descriptorSize) + (extraSize))\n#define sizeofEncodedKeypointHeader() sizeofEncodedKeypoint(0,0)\n#define findKeypointIndex(address, descriptorSize, extraSize) ((address).base / ((sizeofEncodedKeypoint((descriptorSize), (extraSize))) / 4))\nvec4 readKeypointData(sampler2D encodedKeypoints, int encoderLength, KeypointAddress address)\n{\nint rasterIndex = address.base + address.offset;\nvec4 data = pixelAt(encodedKeypoints, ivec2(rasterIndex % encoderLength, rasterIndex / encoderLength));\nreturn rasterIndex < encoderLength * encoderLength ? data : encodeNullKeypoint();\n}\nKeypointAddress findKeypointAddress(ivec2 thread, int encoderLength, int descriptorSize, int extraSize)\n{\nint threadRaster = thread.y * encoderLength + thread.x;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nint keypointIndex = int(threadRaster / pixelsPerKeypoint);\nKeypointAddress address = KeypointAddress(\nkeypointIndex * pixelsPerKeypoint,\nthreadRaster % pixelsPerKeypoint\n);\nreturn address;\n}\nKeypoint decodeKeypoint(sampler2D encodedKeypoints, int encoderLength, KeypointAddress address)\n{\nKeypoint keypoint;\nKeypointAddress positionAddress = KeypointAddress(address.base, 0);\nKeypointAddress propertiesAddress = KeypointAddress(address.base, 1);\nvec4 rawEncodedPosition = readKeypointData(encodedKeypoints, encoderLength, positionAddress);\nivec4 encodedPosition = ivec4(rawEncodedPosition * 255.0f);\nkeypoint.position = fixtovec2(fixed2_t(\nencodedPosition.r | (encodedPosition.g << 8),\nencodedPosition.b | (encodedPosition.a << 8)\n));\nvec4 rawEncodedProperties = readKeypointData(encodedKeypoints, encoderLength, propertiesAddress);\nkeypoint.lod = decodeLod(rawEncodedProperties.r);\nkeypoint.orientation = decodeKeypointOrientation(rawEncodedProperties.g);\nkeypoint.score = decodeKeypointScore(rawEncodedProperties.ba);\nbool isNull = all(equal(rawEncodedPosition, vec4(1)));\nbool isDiscarded = all(equal(rawEncodedPosition + rawEncodedProperties, vec4(0)));\nkeypoint.score = (isNull || isDiscarded) ? -1.0f : keypoint.score;\nkeypoint.flags = KPF_NONE;\nkeypoint.flags |= KPF_NULL * uint(isNull);\nkeypoint.flags |= KPF_DISCARDED * uint(isDiscarded);\nreturn keypoint;\n}\nvec4 encodeKeypointPosition(vec2 position)\n{\nconst vec2 zeros = vec2(0.0f);\nfixed2_t pos = vec2tofix(max(position, zeros));\nfixed2_t lo = pos & 255;\nfixed2_t hi = (pos >> 8) & 255;\nreturn vec4(lo.x, hi.x, lo.y, hi.y) / 255.0f;\n}\n#endif"
-
- /***/ }),
-
- /***/ 9010:
- /***/ ((module) => {
-
- module.exports = "#ifndef _MATH_GLSL\n#define _MATH_GLSL\n#define TWO_PI 6.28318530718f\n#define PI 3.14159265359f\n#define PI_OVER_2 1.57079632679f\n#define PI_OVER_4 0.78539816339f\n#define INV_PI 0.3183098861837907f\n#define INV_PI_OVER_2 0.15915494309189535f\nconst highp float INFINITY = 1.0f / 0.0f;\nfloat fastAtan(float x)\n{\nfloat w = 1.0f - abs(x);\nreturn (w >= 0.0f) ? ((PI_OVER_4 + 0.273f * w) * x) :\n(sign(x) * PI_OVER_2 - (PI_OVER_4 + 0.273f * (1.0f - abs(1.0f / x))) / x);\n}\nfloat fastAtan2(float y, float x)\n{\nreturn (x == 0.0f) ? PI_OVER_2 * sign(y) : fastAtan(y / x) + float(x < 0.0f) * PI * sign(y);\n}\n#endif"
-
- /***/ }),
-
- /***/ 6433:
- /***/ ((module) => {
-
- module.exports = "#ifndef _PYRAMIDS_GLSL\n#define _PYRAMIDS_GLSL\n#define pyrPixel(pyr, lod) textureLod((pyr), texCoord, (lod))\n#define pyrPixelAtOffset(pyr, lod, pot, offset) textureLod((pyr), texCoord + ((pot) * vec2(offset)) / texSize, (lod))\n#define pyrPixelAt(pyr, pos, lod) textureLod((pyr), (vec2(pos) + vec2(0.5f)) / texSize, (lod))\n#define pyrPixelAtEx(pyr, pos, lod, pyrBaseSize) textureLod((pyr), (vec2(pos) + vec2(0.5f)) / vec2(pyrBaseSize), (lod))\n#define pyrSubpixelAtEx(pyr, pos, lod, pyrBaseSize) textureLod((pyr), ((pos) + vec2(0.5f)) / vec2(pyrBaseSize), (lod))\n#define pyrSubpixelAtExOffset(pyr, pos, lod, pot, offset, pyrBaseSize) textureLod((pyr), (((pos) + vec2(0.5f)) + ((pot) * vec2(offset))) / vec2(pyrBaseSize), (lod))\nconst int PYRAMID_MAX_LEVELS = int(@PYRAMID_MAX_LEVELS@);\nconst float F_PYRAMID_MAX_LEVELS = float(@PYRAMID_MAX_LEVELS@);\nconst float LOG2_PYRAMID_MAX_SCALE = float(@LOG2_PYRAMID_MAX_SCALE@);\n#define encodeLod(lod) ((LOG2_PYRAMID_MAX_SCALE + (lod)) / (LOG2_PYRAMID_MAX_SCALE + F_PYRAMID_MAX_LEVELS))\nfloat decodeLod(float encodedLod)\n{\nfloat lod = encodedLod * (LOG2_PYRAMID_MAX_SCALE + F_PYRAMID_MAX_LEVELS) - LOG2_PYRAMID_MAX_SCALE;\nreturn lod - lod * step(1.0f, encodedLod);\n}\n#define LOD_EPS 0.0625f\nconst float ENCODED_LOD_EPS = (LOD_EPS / (LOG2_PYRAMID_MAX_SCALE + F_PYRAMID_MAX_LEVELS));\n#define isSameLod(lod1, lod2) (abs((lod1) - (lod2)) < LOD_EPS)\n#define isSameEncodedLod(alpha1, alpha2) (abs((alpha1) - (alpha2)) < ENCODED_LOD_EPS)\n#endif"
-
- /***/ }),
-
- /***/ 4697:
- /***/ ((module) => {
-
- module.exports = "#ifndef _SUBPIXEL_GLSL\n#define _SUBPIXEL_GLSL\n#define subpixelAt(image, pos) textureLod((image), ((pos) + vec2(0.5f)) / texSize, 0.0f)\nvec4 subpixelAtBI(sampler2D image, vec2 pos)\n{\nvec2 frc = fract(pos);\nvec2 ifrc = vec2(1.0f) - frc;\nvec2 p = (floor(pos) + vec2(0.5f)) / vec2(textureSize(image, 0));\nvec4 pix00 = textureLod(image, p, 0.0f);\nvec4 pix10 = textureLodOffset(image, p, 0.0f, ivec2(1,0));\nvec4 pix01 = textureLodOffset(image, p, 0.0f, ivec2(0,1));\nvec4 pix11 = textureLodOffset(image, p, 0.0f, ivec2(1,1));\nmat4 pix = mat4(pix00, pix10, pix01, pix11);\nvec4 mul = vec4(ifrc.x * ifrc.y, frc.x * ifrc.y, ifrc.x * frc.y, frc.x * frc.y);\nreturn pix * mul;\n}\n#endif"
-
- /***/ }),
-
- /***/ 2289:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D inputEncodedKeypoints;\nuniform int inputDescriptorSize;\nuniform int inputExtraSize;\nuniform int inputEncoderLength;\nuniform int outputDescriptorSize;\nuniform int outputExtraSize;\nuniform int outputEncoderLength;\nconst vec4 EMPTY_DESCRIPTOR = vec4(0.0f);\nvoid main()\n{\nivec2 thread = threadLocation();\nKeypointAddress myAddress = findKeypointAddress(thread, outputEncoderLength, outputDescriptorSize, outputExtraSize);\nint myIndex = findKeypointIndex(myAddress, outputDescriptorSize, outputExtraSize);\nint headerSize = sizeofEncodedKeypointHeader();\nbool isDescriptor = (myAddress.offset >= (headerSize + outputExtraSize) / 4);\nint addressOffset = myAddress.offset;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(inputDescriptorSize, inputExtraSize) / 4;\nKeypointAddress otherAddress = KeypointAddress(myIndex * pixelsPerKeypoint, addressOffset);\ncolor = isDescriptor ? EMPTY_DESCRIPTOR : readKeypointData(inputEncodedKeypoints, inputEncoderLength, otherAddress);\n}"
-
- /***/ }),
-
- /***/ 5725:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D inputEncodedKeypoints;\nuniform int inputDescriptorSize;\nuniform int inputExtraSize;\nuniform int inputEncoderLength;\nuniform int outputDescriptorSize;\nuniform int outputExtraSize;\nuniform int outputEncoderLength;\nvoid main()\n{\nivec2 thread = threadLocation();\nKeypointAddress myAddress = findKeypointAddress(thread, outputEncoderLength, outputDescriptorSize, outputExtraSize);\nint myIndex = findKeypointIndex(myAddress, outputDescriptorSize, outputExtraSize);\nint headerSize = sizeofEncodedKeypointHeader();\nbool isHead = (myAddress.offset < headerSize / 4);\nbool isDescriptor = (myAddress.offset >= (headerSize + outputExtraSize) / 4);\nbool isExtra = (!isHead && !isDescriptor);\nint numberOfExtraPixels = outputExtraSize / 4;\nint addressOffset = myAddress.offset - int(isDescriptor) * numberOfExtraPixels;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(inputDescriptorSize, inputExtraSize) / 4;\nKeypointAddress otherAddress = KeypointAddress(myIndex * pixelsPerKeypoint, addressOffset);\ncolor = isExtra ? vec4(0.0f) : readKeypointData(inputEncodedKeypoints, inputEncoderLength, otherAddress);\n}"
-
- /***/ }),
-
- /***/ 3801:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform mat3 homography;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nvoid main()\n{\nvec4 pixel = threadPixel(encodedKeypoints);\nivec2 thread = threadLocation();\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\ncolor = pixel;\nif(address.offset != 0)\nreturn;\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, address);\nif(isBadKeypoint(keypoint))\nreturn;\nvec3 pos3 = homography * vec3(keypoint.position, 1.0f);\ncolor = encodeKeypointPosition(pos3.xy / pos3.z);\n}"
-
- /***/ }),
-
- /***/ 2346:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n@include \"keypoint-descriptors.glsl\"\n@include \"keypoint-matches.glsl\"\nuniform sampler2D encodedMatches;\nuniform sampler2D encodedFilters;\nuniform int matcherLength;\nuniform sampler2D dbEncodedKeypoints;\nuniform int dbDescriptorSize;\nuniform int dbExtraSize;\nuniform int dbEncoderLength;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nuniform int passId;\n#ifndef NUMBER_OF_KEYPOINTS_PER_PASS\n#error Undefined NUMBER_OF_KEYPOINTS_PER_PASS\n#endif\nconst int INFINITE_DISTANCE = MATCH_MAX_DISTANCE + 1;\nvoid main()\n{\nivec2 thread = threadLocation();\nint keypointIndex = thread.x + thread.y * matcherLength;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = KeypointAddress(keypointIndex * pixelsPerKeypoint, 0);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, address);\ncolor = encodeKeypointMatch(MATCH_NOT_FOUND);\nif(isBadKeypoint(keypoint))\nreturn;\nKeypointMatch bestMatch = decodeKeypointMatch(threadPixel(encodedMatches));\nKeypointMatch filterMatch = decodeKeypointMatch(threadPixel(encodedFilters));\nuint[DESCRIPTOR_SIZE] descriptor = readKeypointDescriptor(encodedKeypoints, descriptorSize, extraSize, encoderLength, address);\nuint[DESCRIPTOR_SIZE] dbDescriptor;\nint dbPixelsPerKeypoint = sizeofEncodedKeypoint(dbDescriptorSize, dbExtraSize) / 4;\nfor(int i = 0; i < NUMBER_OF_KEYPOINTS_PER_PASS; i++) {\nint dbKeypointIndex = passId * NUMBER_OF_KEYPOINTS_PER_PASS + i;\nKeypointAddress dbAddress = KeypointAddress(dbKeypointIndex * dbPixelsPerKeypoint, 0);\nKeypoint dbKeypoint = decodeKeypoint(dbEncodedKeypoints, dbEncoderLength, dbAddress);\ndbDescriptor = readKeypointDescriptor(dbEncodedKeypoints, dbDescriptorSize, dbExtraSize, dbEncoderLength, dbAddress);\nint dist = !isBadKeypoint(dbKeypoint) ? distanceBetweenKeypointDescriptors(descriptor, dbDescriptor) : INFINITE_DISTANCE;\nbestMatch.index = all(bvec2(\ndist < bestMatch.dist || (dist == bestMatch.dist && dbKeypointIndex > bestMatch.index),\ndist > filterMatch.dist || (dist == filterMatch.dist && dbKeypointIndex < filterMatch.index)\n)) ? dbKeypointIndex : bestMatch.index;\nbestMatch.dist = dbKeypointIndex == bestMatch.index ? dist : bestMatch.dist;\n}\ncolor = encodeKeypointMatch(bestMatch);\n}"
-
- /***/ }),
-
- /***/ 4180:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform int imageWidth;\nuniform int imageHeight;\nuniform int borderTop;\nuniform int borderRight;\nuniform int borderBottom;\nuniform int borderLeft;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nvoid main()\n{\nivec2 thread = threadLocation();\nKeypointAddress addr = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, addr);\nvec2 p = keypoint.position;\nbool withinBorder = any(lessThan(\nvec4(p.x, p.y, -p.x, -p.y),\nvec4(borderLeft, borderTop, borderRight - (imageWidth - 1), borderBottom - (imageHeight - 1))\n));\nvec4 pixel = threadPixel(encodedKeypoints);\nvec4 nullPixel = encodeNullKeypoint();\ncolor = withinBorder ? nullPixel : pixel;\n}"
-
- /***/ }),
-
- /***/ 7771:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nuniform int maxKeypoints;\nvoid main()\n{\nivec2 thread = threadLocation();\nint newEncoderLength = outputSize().x;\nKeypointAddress address = findKeypointAddress(thread, newEncoderLength, descriptorSize, extraSize);\nint index = findKeypointIndex(address, descriptorSize, extraSize);\nvec4 pixel = readKeypointData(encodedKeypoints, encoderLength, address);\ncolor = index < maxKeypoints ? pixel : encodeNullKeypoint();\n}"
-
- /***/ }),
-
- /***/ 8938:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D encodedKeypointsA;\nuniform int encoderLengthA;\nuniform sampler2D encodedKeypointsB;\nuniform int encoderLengthB;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nuniform float threshold;\nvoid main()\n{\nivec2 thread = threadLocation();\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint index = findKeypointIndex(address, descriptorSize, extraSize);\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nvec4 data = readKeypointData(encodedKeypointsA, encoderLengthA, address);\ncolor = data;\nif(address.offset >= sizeofEncodedKeypointHeader() / 4)\nreturn;\nKeypoint keypointA = decodeKeypoint(encodedKeypointsA, encoderLengthA, address);\nKeypoint keypointB = decodeKeypoint(encodedKeypointsB, encoderLengthB, address);\ncolor = encodeNullKeypoint();\nif(isNullKeypoint(keypointA) && isNullKeypoint(keypointB))\nreturn;\ncolor = encodeDiscardedKeypoint();\nif(isDiscardedKeypoint(keypointA) || isDiscardedKeypoint(keypointB))\nreturn;\ncolor = encodeDiscardedKeypoint();\nif(isNullKeypoint(keypointA) || isNullKeypoint(keypointB))\nreturn;\nvec2 delta = keypointA.position - keypointB.position;\nbool shouldKeep = (dot(delta, delta) <= threshold * threshold);\ncolor = shouldKeep ? data : encodeDiscardedKeypoint();\n}"
-
- /***/ }),
-
- /***/ 4802:
- /***/ ((module) => {
-
- module.exports = "@include \"float16.glsl\"\nuniform sampler2D offsetsImage;\nuniform ivec2 imageSize;\n#ifndef MAX_ITERATIONS\n#error Undefined MAX_ITERATIONS\n#endif\n#define decodeSkipOffset(pixel) (int((pixel).g * 255.0f) | (int((pixel).a * 255.0f) << 8))\n#define encodeSkipOffset(offset) (vec2((offset) & 255, (offset) >> 8) / 255.0f)\nvoid main()\n{\nvec4 pixel = threadPixel(offsetsImage);\nivec2 thread = threadLocation();\nint rasterIndex = thread.y * imageSize.x + thread.x;\nint offset = decodeSkipOffset(pixel);\nint totalOffset = offset;\nvec2 encodedScore = pixel.rb;\nivec2 pos = thread; int allow = 1;\n@unroll\nfor(int i = 0; i < MAX_ITERATIONS; i++) {\nallow *= int(pos.y < imageSize.y) * int(isEncodedFloat16Zero(pixel.rb));\nrasterIndex += allow * offset;\npos = ivec2(rasterIndex % imageSize.x, rasterIndex / imageSize.x);\npixel = pixelAt(offsetsImage, pos);\noffset = decodeSkipOffset(pixel);\ntotalOffset += allow * offset;\n}\ntotalOffset = min(totalOffset, 65535);\ncolor.rb = encodedScore;\ncolor.ga = encodeSkipOffset(totalOffset);\n}"
-
- /***/ }),
-
- /***/ 6253:
- /***/ ((module) => {
-
- module.exports = "@include \"float16.glsl\"\nuniform sampler2D corners;\nuniform ivec2 imageSize;\nvoid main()\n{\nvec4 pixel = threadPixel(corners);\nivec2 pos = threadLocation();\nvec2 encodedScore = pixel.rb;\nint offset = 0, allow = 1, jumped = 0;\n#define READ(j) ; \\\nallow *= int(pos.y < imageSize.y) * int(isEncodedFloat16Zero(pixel.rb)); \\\noffset += allow; \\\npos.x = (pos.x + 1) % imageSize.x; \\\npos.y += int(pos.x == 0); \\\npixel = (0 != (jumped |= int(pos.x == 0))) ? pixelAtShortOffset(corners, ivec2((j),1)) : pixelAtShortOffset(corners, ivec2((j),0))\nREAD(1); READ(2); READ(3); READ(4); READ(5); READ(6); READ(7);\ncolor.rb = encodedScore;\ncolor.ga = vec2(offset, 0) / 255.0f;\n}"
-
- /***/ }),
-
- /***/ 384:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D offsetsImage;\nuniform ivec2 imageSize;\nuniform int passId;\nuniform int numPasses;\nuniform int keypointLimit;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#define decodeSkipOffset(pixel) (int((pixel).g * 255.0f) | (int((pixel).a * 255.0f) << 8))\nbool findQthKeypoint(int q, int p, inout ivec2 position, out vec4 pixel)\n{\nint notFirstPass = int(passId > 0);\nposition *= notFirstPass;\np |= -(1 - notFirstPass);\np -= notFirstPass;\nint rasterIndex = position.y * imageSize.x + position.x;\nwhile(position.y < imageSize.y && p != q) {\nposition = ivec2(rasterIndex % imageSize.x, rasterIndex / imageSize.x);\npixel = texelFetch(offsetsImage, position, 0);\np += int(!isEncodedFloat16Zero(pixel.rb));\nrasterIndex += max(1, decodeSkipOffset(pixel));\n}\nreturn (p == q);\n}\nvoid main()\n{\nivec2 thread = threadLocation();\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint q = findKeypointIndex(address, descriptorSize, extraSize);\ncolor = vec4(0.0f);\nif(address.offset != 0)\nreturn;\ncolor = threadPixel(encodedKeypoints);\nint numPixels = encoderLength * encoderLength;\nint maxKeypoints = numPixels / pixelsPerKeypoint;\nint maxKeypointsPerPass = maxKeypoints / numPasses + int(maxKeypoints % numPasses != 0);\nint targetPassId = q / maxKeypointsPerPass;\nif(passId != targetPassId)\nreturn;\nint lastIndexFromPrevPass = passId * maxKeypointsPerPass - 1;\nKeypointAddress lastAddressFromPrevPass = KeypointAddress(max(0, lastIndexFromPrevPass) * pixelsPerKeypoint, 0);\nKeypoint lastKeypointFromPrevPass = decodeKeypoint(encodedKeypoints, encoderLength, lastAddressFromPrevPass);\nivec2 position = passId > 0 ? ivec2(lastKeypointFromPrevPass.position) : ivec2(0);\nvec4 pixel;\ncolor = encodeNullKeypoint();\nif(q >= min(maxKeypoints, keypointLimit) || !findQthKeypoint(q, lastIndexFromPrevPass, position, pixel))\nreturn;\ncolor = encodeKeypointPosition(vec2(position));\n}"
-
- /***/ }),
-
- /***/ 500:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D corners;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nvoid main()\n{\nivec2 thread = threadLocation();\nvec4 pixel = threadPixel(encodedKeypoints);\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint q = findKeypointIndex(address, descriptorSize, extraSize);\ncolor = pixel;\nif(address.offset != 1)\nreturn;\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, address);\nvec4 kpix = pixelAt(corners, ivec2(keypoint.position));\nkeypoint.score = decodeFloat16(kpix.rb);\ncolor.r = kpix.a;\ncolor.g = encodeKeypointOrientation(0.0f);\ncolor.ba = encodeKeypointScore(keypoint.score);\n}"
-
- /***/ }),
-
- /***/ 3673:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D corners;\nuniform mediump usampler2D lookupTable;\nuniform int stride;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nuniform int encoderCapacity;\nconst uvec2 NULL_ELEMENT = uvec2(0xFFFFu);\nvoid main()\n{\nivec2 thread = threadLocation();\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint index = findKeypointIndex(address, descriptorSize, extraSize);\nivec2 pos = ivec2(index % stride, index / stride);\nuvec4 entry = texelFetch(lookupTable, pos, 0);\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nint rasterIndex = address.base + address.offset;\nint numberOfPixels = encoderLength * encoderLength;\nint numberOfValidPixels = numberOfPixels - (numberOfPixels % pixelsPerKeypoint);\nint maxEncoderCapacity = numberOfValidPixels / pixelsPerKeypoint;\ncolor = encodeNullKeypoint();\nif(all(equal(entry.xy, NULL_ELEMENT)) || index >= min(encoderCapacity, maxEncoderCapacity))\nreturn;\ncolor = encodeKeypointPosition(vec2(entry.xy));\nif(address.offset == 0)\nreturn;\ncolor = vec4(0.0f);\nif(address.offset >= sizeofEncodedKeypointHeader() / 4)\nreturn;\nvec4 pixel = texelFetch(corners, ivec2(entry.xy), 0);\nvec2 encodedScore = encodeKeypointScore(decodeFloat16(pixel.rb));\nfloat encodedOrientation = encodeKeypointOrientation(0.0f);\nfloat encodedLod = pixel.a;\ncolor = vec4(encodedLod, encodedOrientation, encodedScore);\n}"
-
- /***/ }),
-
- /***/ 1703:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nvoid main()\n{\ncolor = encodeNullKeypoint();\n}"
-
- /***/ }),
-
- /***/ 2633:
- /***/ ((module) => {
-
- module.exports = "@include \"pyramids.glsl\"\n@include \"float16.glsl\"\nuniform sampler2D corners;\nuniform sampler2D pyramid;\nuniform float lod;\nuniform int threshold;\n#define USE_VARYINGS 1\n#if !defined(FAST_TYPE)\n#error Undefined FAST_TYPE\n#elif FAST_TYPE == 916\nin vec2 v_pix0, v_pix1, v_pix2, v_pix3, v_pix4, v_pix5, v_pix6, v_pix7,\nv_pix8, v_pix9, v_pix10,v_pix11,v_pix12,v_pix13,v_pix14,v_pix15;\n#else\n#error Invalid FAST_TYPE\n#endif\n#define PIX(x,y) pyrPixelAtOffset(pyramid, lod, pot, ivec2((x),(y))).g\n#define XIP(v) textureLod(pyramid, (v), lod).g\nvoid main()\n{\nfloat pixel = threadPixel(pyramid).g;\nvec4 prev = threadPixel(corners);\nivec2 thread = threadLocation();\nivec2 size = outputSize();\nfloat pot = exp2(lod);\nfloat t = float(clamp(threshold, 0, 255)) / 255.0f;\nfloat ct = pixel + t, c_t = pixel - t;\ncolor = vec4(prev.r, pixel, prev.ba);\n#if FAST_TYPE == 916\nconst ivec4 margin = ivec4(3, 3, 4, 4);\nif(any(lessThan(ivec4(thread, size - thread), margin)))\nreturn;\n#if USE_VARYINGS\nfloat p0 = XIP(v_pix0), p4 = XIP(v_pix4), p8 = XIP(v_pix8), p12 = XIP(v_pix12);\n#else\nfloat p0 = PIX(0,3), p4 = PIX(3,0), p8 = PIX(0,-3), p12 = PIX(-3,0);\n#endif\nbvec4 brighter = bvec4(p0 > ct, p4 > ct, p8 > ct, p12 > ct);\nbvec4 darker = bvec4(p0 < c_t, p4 < c_t, p8 < c_t, p12 < c_t);\nbvec4 bpairs = bvec4(all(brighter.xy), all(brighter.yz), all(brighter.zw), all(brighter.wx));\nbvec4 dpairs = bvec4(all(darker.xy), all(darker.yz), all(darker.zw), all(darker.wx));\nif(!(any(bpairs) || any(dpairs)))\nreturn;\n#if USE_VARYINGS\nfloat p1 = XIP(v_pix1), p2 = XIP(v_pix2), p3 = XIP(v_pix3),\np5 = XIP(v_pix5), p6 = XIP(v_pix6), p7 = XIP(v_pix7),\np9 = XIP(v_pix9), p10 = XIP(v_pix10), p11 = XIP(v_pix11),\np13 = XIP(v_pix13), p14 = XIP(v_pix14), p15 = XIP(v_pix15);\n#else\nfloat p1 = PIX(1,3), p2 = PIX(2,2), p3 = PIX(3,1),\np5 = PIX(3,-1), p6 = PIX(2,-2), p7 = PIX(1,-3),\np9 = PIX(-1,-3), p10 = PIX(-2,-2), p11 = PIX(-3,-1),\np13 = PIX(-3,1), p14 = PIX(-2,2), p15 = PIX(-1,3);\n#endif\nbool A=(p0>ct),B=(p1>ct),C=(p2>ct),D=(p3>ct),E=(p4>ct),F=(p5>ct),G=(p6>ct),H=(p7>ct),I=(p8>ct),J=(p9>ct),K=(p10>ct),L=(p11>ct),M=(p12>ct),N=(p13>ct),O=(p14>ct),P=(p15>ct),a=(p0<c_t),b=(p1<c_t),c=(p2<c_t),d=(p3<c_t),e=(p4<c_t),f=(p5<c_t),g=(p6<c_t),h=(p7<c_t),i=(p8<c_t),j=(p9<c_t),k=(p10<c_t),l=(p11<c_t),m=(p12<c_t),n=(p13<c_t),o=(p14<c_t),p=(p15<c_t);\nbool isCorner=A&&(B&&(K&&L&&J&&(M&&N&&O&&P||G&&H&&I&&(M&&N&&O||F&&(M&&N||E&&(M||D))))||C&&(K&&L&&M&&(N&&O&&P||G&&H&&I&&J&&(N&&O||F&&(N||E)))||D&&(N&&(L&&M&&(K&&G&&H&&I&&J&&(O||F)||O&&P)||k&&l&&m&&e&&f&&g&&h&&i&&j)||E&&(O&&(M&&N&&(K&&L&&G&&H&&I&&J||P)||k&&l&&m&&n&&f&&g&&h&&i&&j)||F&&(P&&(N&&O||k&&l&&m&&n&&o&&g&&h&&i&&j)||G&&(O&&P||H&&(P||I)||k&&l&&m&&n&&o&&p&&h&&i&&j)||k&&l&&m&&n&&o&&h&&i&&j&&(p||g))||k&&l&&m&&n&&h&&i&&j&&(o&&(p||g)||f&&(o&&p||g)))||k&&l&&m&&h&&i&&j&&(n&&(o&&p||g&&(o||f))||e&&(n&&o&&p||g&&(n&&o||f))))||k&&l&&h&&i&&j&&(m&&(n&&o&&p||g&&(n&&o||f&&(n||e)))||d&&(m&&n&&o&&p||g&&(m&&n&&o||f&&(m&&n||e)))))||k&&h&&i&&j&&(l&&(m&&n&&o&&p||g&&(m&&n&&o||f&&(m&&n||e&&(m||d))))||c&&(l&&m&&n&&o&&p||g&&(l&&m&&n&&o||f&&(l&&m&&n||e&&(l&&m||d))))))||K&&I&&J&&(L&&M&&N&&O&&P||G&&H&&(L&&M&&N&&O||F&&(L&&M&&N||E&&(L&&M||D&&(L||C)))))||h&&i&&j&&(b&&(k&&l&&m&&n&&o&&p||g&&(k&&l&&m&&n&&o||f&&(k&&l&&m&&n||e&&(k&&l&&m||d&&(k&&l||c)))))||k&&(l&&m&&n&&o&&p||g&&(l&&m&&n&&o||f&&(l&&m&&n||e&&(l&&m||d&&(l||c)))))))||B&&(H&&I&&J&&(K&&L&&M&&N&&O&&P&&a||G&&(K&&L&&M&&N&&O&&a||F&&(K&&L&&M&&N&&a||E&&(K&&L&&M&&a||D&&(K&&L&&a||C)))))||a&&k&&i&&j&&(l&&m&&n&&o&&p||g&&h&&(l&&m&&n&&o||f&&(l&&m&&n||e&&(l&&m||d&&(l||c))))))||C&&(K&&H&&I&&J&&(L&&M&&N&&O&&P&&a&&b||G&&(L&&M&&N&&O&&a&&b||F&&(L&&M&&N&&a&&b||E&&(L&&M&&a&&b||D))))||a&&b&&k&&l&&j&&(m&&n&&o&&p||g&&h&&i&&(m&&n&&o||f&&(m&&n||e&&(m||d)))))||D&&(K&&L&&H&&I&&J&&(M&&N&&O&&P&&a&&b&&c||G&&(M&&N&&O&&a&&b&&c||F&&(M&&N&&a&&b&&c||E)))||a&&b&&k&&l&&m&&c&&(n&&o&&p||g&&h&&i&&j&&(n&&o||f&&(n||e))))||E&&(K&&L&&M&&H&&I&&J&&(N&&O&&P&&a&&b&&c&&d||G&&(N&&O&&a&&b&&c&&d||F))||a&&b&&l&&m&&n&&c&&d&&(k&&g&&h&&i&&j&&(o||f)||o&&p))||F&&(K&&L&&M&&N&&H&&I&&J&&(O&&P&&a&&b&&c&&d&&e||G)||a&&b&&m&&n&&o&&c&&d&&e&&(k&&l&&g&&h&&i&&j||p))||G&&(K&&L&&M&&N&&O&&H&&I&&J||a&&b&&n&&o&&p&&c&&d&&e&&f)||H&&(K&&L&&M&&N&&O&&P&&I&&J||a&&b&&o&&p&&c&&d&&e&&f&&g)||a&&(b&&(k&&l&&j&&(m&&n&&o&&p||g&&h&&i&&(m&&n&&o||f&&(m&&n||e&&(m||d))))||c&&(k&&l&&m&&(n&&o&&p||g&&h&&i&&j&&(n&&o||f&&(n||e)))||d&&(l&&m&&n&&(k&&g&&h&&i&&j&&(o||f)||o&&p)||e&&(m&&n&&o&&(k&&l&&g&&h&&i&&j||p)||f&&(n&&o&&p||g&&(o&&p||h&&(p||i)))))))||k&&i&&j&&(l&&m&&n&&o&&p||g&&h&&(l&&m&&n&&o||f&&(l&&m&&n||e&&(l&&m||d&&(l||c))))))||h&&i&&j&&(k&&l&&m&&n&&o&&p||g&&(k&&l&&m&&n&&o||f&&(k&&l&&m&&n||e&&(k&&l&&m||d&&(k&&l||c&&(b||k))))));\nif(!isCorner)\nreturn;\nmat4 mp = mat4(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15);\nmat4 mct = mp - mat4(ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct);\nmat4 mc_t = mat4(c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t) - mp;\nconst vec4 zeros = vec4(0.0f), ones = vec4(1.0f);\nvec4 bs = max(mct[0], zeros), ds = max(mc_t[0], zeros);\nbs += max(mct[1], zeros); ds += max(mc_t[1], zeros);\nbs += max(mct[2], zeros); ds += max(mc_t[2], zeros);\nbs += max(mct[3], zeros); ds += max(mc_t[3], zeros);\nfloat thisScore = max(dot(bs, ones), dot(ds, ones)) / 16.0f;\nfloat prevScore = decodeFloat16(prev.rb);\nvec3 thisResult = vec3(encodeFloat16(thisScore), encodeLod(lod));\ncolor.rba = thisScore > prevScore ? thisResult : color.rba;\n#endif\n}"
-
- /***/ }),
-
- /***/ 535:
- /***/ ((module) => {
-
- module.exports = "uniform mediump float lod;\n#if !defined(FAST_TYPE)\n#error Undefined FAST_TYPE\n#elif FAST_TYPE == 916\nout vec2 v_pix0, v_pix1, v_pix2, v_pix3, v_pix4, v_pix5, v_pix6, v_pix7,\nv_pix8, v_pix9, v_pix10,v_pix11,v_pix12,v_pix13,v_pix14,v_pix15;\n#else\n#error Invalid FAST_TYPE\n#endif\n#define PIX(x,y) (texCoord + ((pot) * vec2((x),(y))) / texSize)\nvoid vsmain()\n{\nfloat pot = exp2(lod);\n#if FAST_TYPE == 916\nv_pix0 = PIX(0,3); v_pix1 = PIX(1,3), v_pix2 = PIX(2,2), v_pix3 = PIX(3,1);\nv_pix4 = PIX(3,0); v_pix5 = PIX(3,-1), v_pix6 = PIX(2,-2), v_pix7 = PIX(1,-3);\nv_pix8 = PIX(0,-3); v_pix9 = PIX(-1,-3), v_pix10 = PIX(-2,-2), v_pix11 = PIX(-3,-1);\nv_pix12 = PIX(-3,0); v_pix13 = PIX(-3,1), v_pix14 = PIX(-2,2), v_pix15 = PIX(-1,3);\n#endif\n}"
-
- /***/ }),
-
- /***/ 3232:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n@include \"keypoint-descriptors.glsl\"\nuniform sampler2D encodedKeypointsA;\nuniform int encoderLengthA;\nuniform sampler2D encodedKeypointsB;\nuniform int encoderLengthB;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nuniform int threshold;\nvoid main()\n{\nivec2 thread = threadLocation();\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint index = findKeypointIndex(address, descriptorSize, extraSize);\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nvec4 data = readKeypointData(encodedKeypointsA, encoderLengthA, address);\ncolor = data;\nif(address.offset >= sizeofEncodedKeypointHeader() / 4)\nreturn;\nKeypoint keypointA = decodeKeypoint(encodedKeypointsA, encoderLengthA, address);\nKeypoint keypointB = decodeKeypoint(encodedKeypointsB, encoderLengthB, address);\ncolor = encodeNullKeypoint();\nif(isNullKeypoint(keypointA) && isNullKeypoint(keypointB))\nreturn;\ncolor = encodeDiscardedKeypoint();\nif(isDiscardedKeypoint(keypointA) || isDiscardedKeypoint(keypointB))\nreturn;\ncolor = encodeDiscardedKeypoint();\nif(isNullKeypoint(keypointA) || isNullKeypoint(keypointB))\nreturn;\nuint[DESCRIPTOR_SIZE] descriptorA, descriptorB;\ndescriptorA = readKeypointDescriptor(encodedKeypointsA, descriptorSize, extraSize, encoderLengthA, address);\ndescriptorB = readKeypointDescriptor(encodedKeypointsB, descriptorSize, extraSize, encoderLengthB, address);\nint dist = distanceBetweenKeypointDescriptors(descriptorA, descriptorB);\nbool shouldKeep = (dist <= threshold);\ncolor = shouldKeep ? data : encodeDiscardedKeypoint();\n}"
-
- /***/ }),
-
- /***/ 8356:
- /***/ ((module) => {
-
- module.exports = "@include \"float16.glsl\"\nuniform sampler2D corners;\nuniform sampler2D maxScore;\nuniform float quality;\nvoid main()\n{\nvec4 pixel = threadPixel(corners);\nfloat score = decodeFloat16(pixel.rb);\nfloat maxval = decodeFloat16(threadPixel(maxScore).rb);\nfloat threshold = maxval * clamp(quality, 0.0f, 1.0f);\ncolor = pixel;\ncolor.rb = score >= threshold ? color.rb : encodeFloat16(0.0f);\n}"
-
- /***/ }),
-
- /***/ 7339:
- /***/ ((module) => {
-
- module.exports = "@include \"pyramids.glsl\"\n@include \"float16.glsl\"\n@include \"filters.glsl\"\n#if !defined(WINDOW_SIZE)\n#error Undefined WINDOW_SIZE\n#endif\n#define WINDOW_RADIUS ((WINDOW_SIZE - 1) / 2)\nuniform sampler2D corners;\nuniform sampler2D pyramid;\nuniform sampler2D derivatives;\nuniform float lod;\nuniform float lodStep;\nuniform float gaussian[@WINDOW_SIZE@];\n#define G(x) gaussian[(x) + WINDOW_RADIUS]\n#define W(x,y) (G(x) * G(y))\n#define H(ox,oy) dpix = pixelAtShortOffset(derivatives, ivec2((ox),(oy))); \\\ndf = (1.0f + lod) * decodePairOfFloat16(dpix); \\\nh += vec3(df.x * df.x, df.x * df.y, df.y * df.y) * W((ox),(oy))\nvoid main()\n{\nfloat intensity = 0.0f;\nivec2 thread = threadLocation();\nvec4 pixel = threadPixel(corners);\nvec4 dpix = vec4(0.0f);\nvec2 df = vec2(0.0f);\nvec3 h = vec3(0.0f);\ncolor = pixel;\n#if WINDOW_SIZE == 1\nH(0,0);\n#elif WINDOW_SIZE == 3\nH(-1,-1); H(0,-1); H(1,-1);\nH(-1,0); H(0,0); H(1,0);\nH(-1,1); H(0,1); H(1,1);\n#elif WINDOW_SIZE == 5\nH(-2,-2); H(-1,-2); H(0,-2); H(1,-2); H(2,-2);\nH(-2,-1); H(-1,-1); H(0,-1); H(1,-1); H(2,-1);\nH(-2,0); H(-1,0); H(0,0); H(1,0); H(2,0);\nH(-2,1); H(-1,1); H(0,1); H(1,1); H(2,1);\nH(-2,2); H(-1,2); H(0,2); H(1,2); H(2,2);\n#elif WINDOW_SIZE == 7\nH(-3,-3); H(-2,-3); H(-1,-3); H(0,-3); H(1,-3); H(2,-3); H(3,-3);\nH(-3,-2); H(-2,-2); H(-1,-2); H(0,-2); H(1,-2); H(2,-2); H(3,-2);\nH(-3,-1); H(-2,-1); H(-1,-1); H(0,-1); H(1,-1); H(2,-1); H(3,-1);\nH(-3,0); H(-2,0); H(-1,0); H(0,0); H(1,0); H(2,0); H(3,0);\nH(-3,1); H(-2,1); H(-1,1); H(0,1); H(1,1); H(2,1); H(3,1);\nH(-3,2); H(-2,2); H(-1,2); H(0,2); H(1,2); H(2,2); H(3,2);\nH(-3,3); H(-2,3); H(-1,3); H(0,3); H(1,3); H(2,3); H(3,3);\n#else\n#error Invalid WINDOW_SIZE\n#endif\nfloat response = 0.5f * (h.x + h.z - sqrt((h.x - h.z) * (h.x - h.z) + 4.0f * h.y * h.y));\nresponse /= float(WINDOW_SIZE * WINDOW_SIZE);\nfloat lodPlus = min(float(PYRAMID_MAX_LEVELS - 1), lod + lodStep);\nfloat currentScaleStrength = abs(laplacian(pyramid, vec2(thread), lod));\nfloat previousScaleStrength = abs(laplacian(pyramid, vec2(thread), lodPlus));\nfloat previousResponse = decodeFloat16(pixel.rb);\nvec4 result = vec4(encodeFloat16(response), encodeLod(lod), intensity);\ncolor.rbag = (currentScaleStrength >= previousScaleStrength || previousResponse == 0.0f) ? result : pixel.rbag;\n}"
-
- /***/ }),
-
- /***/ 3177:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoint-matches.glsl\"\nvoid main()\n{\n#if ENCODE_FILTERS != 0\nKeypointMatch initial = KeypointMatch(MATCH_MAX_INDEX, 0);\n#else\nKeypointMatch initial = KeypointMatch(MATCH_MAX_INDEX, MATCH_MAX_DISTANCE);\n#endif\ncolor = encodeKeypointMatch(initial);\n}"
-
- /***/ }),
-
- /***/ 2769:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoint-matches.glsl\"\nuniform sampler2D encodedMatches;\nuniform sampler2D encodedKthMatches;\nuniform int numberOfMatchesPerKeypoint;\nuniform int kthMatch;\nvoid main()\n{\nivec2 thread = threadLocation();\nivec2 matcherSize = textureSize(encodedMatches, 0);\nivec2 kthMatcherSize = textureSize(encodedKthMatches, 0);\nint rasterIndex = thread.y * matcherSize.x + thread.x;\nint matchIndex = rasterIndex / numberOfMatchesPerKeypoint;\nint matchCell = rasterIndex % numberOfMatchesPerKeypoint;\ncolor = threadPixel(encodedMatches);\nif(matchCell != kthMatch)\nreturn;\ncolor = encodeKeypointMatch(MATCH_NOT_FOUND);\nif(matchIndex >= kthMatcherSize.x * kthMatcherSize.y)\nreturn;\nivec2 pos = ivec2(matchIndex % kthMatcherSize.x, matchIndex / kthMatcherSize.x);\ncolor = texelFetch(encodedKthMatches, pos, 0);\n}"
-
- /***/ }),
-
- /***/ 2006:
- /***/ ((module) => {
-
- module.exports = "@include \"pyramids.glsl\"\n@include \"float16.glsl\"\n@include \"filters.glsl\"\nuniform sampler2D corners;\nuniform sampler2D pyramid;\nuniform float lodStep;\nuniform float lodOffset;\nvoid main()\n{\nivec2 thread = threadLocation();\nvec4 pixel = threadPixel(corners);\nfloat lod = decodeLod(pixel.a);\nfloat lodMinus = max(0.0f, lod - lodStep + lodOffset);\nfloat lodPlus = min(float(PYRAMID_MAX_LEVELS - 1), lod + lodStep + lodOffset);\nfloat lapMinus = laplacian(pyramid, vec2(thread), lodMinus);\nfloat lapPlus = abs(lodPlus - lodMinus) < 1e-5 ? lapMinus : laplacian(pyramid, vec2(thread), lodPlus);\ncolor = encodePairOfFloat16(vec2(lapMinus, lapPlus));\n}"
-
- /***/ }),
-
- /***/ 3329:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n@include \"float16.glsl\"\nuniform sampler2D nextPyramid;\nuniform sampler2D prevPyramid;\nuniform sampler2D encodedFlow;\nuniform sampler2D prevKeypoints;\nuniform int level;\nuniform int depth;\nuniform int numberOfIterations;\nuniform float discardThreshold;\nuniform float epsilon;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#ifndef WINDOW_SIZE\n#error Undefined WINDOW_SIZE\n#endif\n#define NEXT_IMAGE 1\n#define PREV_IMAGE 0\nconst int WINDOW_RADIUS = (WINDOW_SIZE - 1) / 2;\nconst int WINDOW_SIZE_SQUARED = (WINDOW_SIZE) * (WINDOW_SIZE);\nconst int WINDOW_SIZE_PLUS = (WINDOW_SIZE) + 2;\nconst int WINDOW_SIZE_PLUS_SQUARED = WINDOW_SIZE_PLUS * WINDOW_SIZE_PLUS;\nconst int DBL_WINDOW_SIZE_PLUS_SQUARED = 2 * WINDOW_SIZE_PLUS_SQUARED;\nconst int WINDOW_RADIUS_PLUS = (WINDOW_SIZE_PLUS - 1) / 2;\nconst highp float FLT_SCALE = 9.5367431640625e-7;\nconst highp float FLT_EPSILON = 0.00000011920929f;\nint pixelBuffer[DBL_WINDOW_SIZE_PLUS_SQUARED];\n#define prevPixel(index) pixelBuffer[(index)]\n#define nextPixel(index) pixelBuffer[WINDOW_SIZE_PLUS_SQUARED + (index)]\n#define pixelIndex(i, j) (((j) + WINDOW_RADIUS_PLUS) * WINDOW_SIZE_PLUS + ((i) + WINDOW_RADIUS_PLUS))\nivec2 derivBuffer[WINDOW_SIZE_SQUARED];\n#define derivativesAt(x, y) derivBuffer[((y) + WINDOW_RADIUS) * WINDOW_SIZE + ((x) + WINDOW_RADIUS)]\nvoid readWindow(vec2 center, float lod)\n{\nconst int r = WINDOW_RADIUS;\nivec2 pyrBaseSize = textureSize(prevPyramid, 0);\nfloat pot = exp2(lod);\nivec2 offset; int idx;\n#define readPixelsAt(ox, oy) offset = ivec2((ox), (oy)); \\\nidx = pixelIndex(offset.x, offset.y); \\\nnextPixel(idx) = int(255.0f * pyrSubpixelAtExOffset(nextPyramid, center, lod, pot, offset, pyrBaseSize).g); \\\nprevPixel(idx) = int(255.0f * pyrSubpixelAtExOffset(prevPyramid, center, lod, pot, offset, pyrBaseSize).g)\nfor(int j = 0; j < WINDOW_SIZE; j++) {\nfor(int i = 0; i < WINDOW_SIZE; i++) {\nreadPixelsAt(i-r, j-r);\n}\n}\nint r1 = r+1;\nfor(int k = 0; k < WINDOW_SIZE; k++) {\nreadPixelsAt(-r1, k-r);\nreadPixelsAt( r1, k-r);\nreadPixelsAt(k-r,-r1);\nreadPixelsAt(k-r, r1);\n}\nreadPixelsAt(-r1,-r1);\nreadPixelsAt( r1,-r1);\nreadPixelsAt(-r1, r1);\nreadPixelsAt( r1, r1);\n}\nivec2 computeDerivatives(int imageCode, ivec2 offset)\n{\nconst mat3 dx = mat3(\n3, 0, -3,\n10, 0, -10,\n3, 0, -3\n);\nconst mat3 dy = mat3(\n3, 10, 3,\n0, 0, 0,\n-3, -10, -3\n);\nint indexOffset = imageCode * WINDOW_SIZE_PLUS_SQUARED;\nmat3 window = mat3(\npixelBuffer[indexOffset + pixelIndex(offset.x-1, offset.y-1)],\npixelBuffer[indexOffset + pixelIndex(offset.x+0, offset.y-1)],\npixelBuffer[indexOffset + pixelIndex(offset.x+1, offset.y-1)],\npixelBuffer[indexOffset + pixelIndex(offset.x-1, offset.y+0)],\n0.0f,\npixelBuffer[indexOffset + pixelIndex(offset.x+1, offset.y+0)],\npixelBuffer[indexOffset + pixelIndex(offset.x-1, offset.y+1)],\npixelBuffer[indexOffset + pixelIndex(offset.x+0, offset.y+1)],\npixelBuffer[indexOffset + pixelIndex(offset.x+1, offset.y+1)]\n);\nmat3 fx = matrixCompMult(dx, window);\nmat3 fy = matrixCompMult(dy, window);\nconst vec3 ones = vec3(1.0f);\nreturn ivec2(\ndot(fx[0], ones) + dot(fx[1], ones) + dot(fx[2], ones),\ndot(fy[0], ones) + dot(fy[1], ones) + dot(fy[2], ones)\n);\n}\nint readBufferedPixel(int imageCode, ivec2 offset)\n{\nconst int r = WINDOW_RADIUS;\noffset = clamp(offset, -r, r);\nint indexOffset = imageCode * WINDOW_SIZE_PLUS_SQUARED;\nreturn pixelBuffer[indexOffset + pixelIndex(offset.x, offset.y)];\n}\nint readBufferedSubpixel(int imageCode, vec2 offset)\n{\nivec2 p = ivec2(floor(offset));\nvec2 frc = fract(offset);\nvec2 ifrc = vec2(1.0f) - frc;\nvec4 pix = vec4(\nreadBufferedPixel(imageCode, p),\nreadBufferedPixel(imageCode, p + ivec2(1,0)),\nreadBufferedPixel(imageCode, p + ivec2(0,1)),\nreadBufferedPixel(imageCode, p + ivec2(1,1))\n);\nvec4 sub = vec4(\nifrc.x * ifrc.y,\nfrc.x * ifrc.y,\nifrc.x * frc.y,\nfrc.x * frc.y\n);\nreturn int(0.5f + dot(sub*pix, vec4(1.0f)));\n}\nvec2 computeMismatch(vec2 pyrGuess, vec2 localGuess)\n{\nconst int r = WINDOW_RADIUS;\nint timeDerivative;\nivec2 mismatch = ivec2(0);\nint x, y, _x, _y;\nvec2 d = pyrGuess + localGuess;\n#define innerLoop() \\\nfor(_x = 0; _x < WINDOW_SIZE; _x++) { \\\nx = _x - r; y = _y - r; \\\ntimeDerivative = ( \\\nreadBufferedSubpixel(NEXT_IMAGE, vec2(x, y) + d) - \\\nreadBufferedPixel(PREV_IMAGE, ivec2(x, y)) \\\n); \\\nmismatch += derivativesAt(x, y) * timeDerivative; \\\n}\n@unroll\nfor(_y = 0; _y < WINDOW_SIZE; _y++) {\ninnerLoop();\n}\nreturn vec2(mismatch) * FLT_SCALE;\n}\nbool isInsideImage(vec2 position)\n{\nvec2 imageSize = vec2(textureSize(nextPyramid, 0));\nvec2 border = vec2(WINDOW_SIZE);\nreturn all(bvec4(\ngreaterThanEqual(position, border),\nlessThan(position, imageSize - border)\n));\n}\nvoid main()\n{\nvec4 pixel = threadPixel(encodedFlow);\nivec2 thread = threadLocation();\nfloat windowArea = float(WINDOW_SIZE * WINDOW_SIZE);\nconst int r = WINDOW_RADIUS;\nint keypointIndex = thread.x + thread.y * outputSize().x;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = KeypointAddress(keypointIndex * pixelsPerKeypoint, 0);\nKeypoint keypoint = decodeKeypoint(prevKeypoints, encoderLength, address);\ncolor = encodeNullPairOfFloat16();\nif(isNullKeypoint(keypoint))\nreturn;\ncolor = encodeDiscardedPairOfFloat16();\nif(isBadKeypoint(keypoint))\nreturn;\nvec2 pyrGuess = (level < depth - 1) ? decodePairOfFloat16(pixel) : vec2(0.0f);\npyrGuess *= 2.0f;\nreadWindow(keypoint.position, float(level));\nivec2 derivatives;\nivec3 harris3i = ivec3(0);\nfor(int j = 0; j < WINDOW_SIZE; j++) {\nfor(int i = 0; i < WINDOW_SIZE; i++) {\nderivatives = computeDerivatives(PREV_IMAGE, ivec2(i-r, j-r));\nharris3i += ivec3(\nderivatives.x * derivatives.x,\nderivatives.x * derivatives.y,\nderivatives.y * derivatives.y\n);\nderivativesAt(i-r, j-r) = derivatives;\n}\n}\nhighp vec3 harris = vec3(harris3i) * FLT_SCALE;\nhighp mat2 invHarris = mat2(harris.z, -harris.y, -harris.y, harris.x);\nhighp float det = harris.x * harris.z - harris.y * harris.y;\nhighp float invDet = abs(det) >= FLT_EPSILON ? 1.0f / det : 0.0f;\nhighp float minEigenvalue = 0.5f * ((harris.x + harris.z) - sqrt(\n(harris.x - harris.z) * (harris.x - harris.z) + 4.0f * (harris.y * harris.y)\n));\nint niceNumbers = int(abs(det) >= FLT_EPSILON && minEigenvalue >= discardThreshold * windowArea);\nbool goodKeypoint = (level > 0) || (niceNumbers != 0);\nhighp float eps2 = epsilon * epsilon;\nhighp vec2 mismatch, delta, localGuess = vec2(0.0f);\nfor(int k = 0; k < numberOfIterations; k++) {\nmismatch = niceNumbers != 0 ? computeMismatch(pyrGuess, localGuess) : vec2(0.0f);\ndelta = mismatch * invHarris * invDet;\nniceNumbers *= int(eps2 <= dot(delta, delta));\nlocalGuess += float(niceNumbers) * delta;\n}\nvec2 opticalFlow = pyrGuess + localGuess;\nbool mustDiscard = (level == 0) && any(bvec2(\n!goodKeypoint,\n!isInsideImage(keypoint.position + opticalFlow)\n));\ncolor = !mustDiscard ? encodePairOfFloat16(opticalFlow) : encodeDiscardedPairOfFloat16();\n}"
-
- /***/ }),
-
- /***/ 4251:
- /***/ ((module) => {
-
- module.exports = "#if @FS_USE_CUSTOM_PRECISION@\nprecision mediump int;\nprecision mediump float;\n#endif\n#if !defined(STAGE)\n#error Undefined STAGE\n#elif STAGE == 1\n@include \"float16.glsl\"\nuniform sampler2D corners;\n#elif STAGE < 1\nuniform mediump usampler2D lookupTable;\n#else\n#define SKIP_TEXTURE_READS 1\n#define DENSITY_FACTOR 0.10\nuniform mediump usampler2D lookupTable;\nuniform int blockSize;\nuniform int width;\nuniform int height;\nin vec2 v_topLeft, v_top, v_topRight,\nv_left, v_center, v_right,\nv_bottomLeft, v_bottom, v_bottomRight;\n#endif\nconst uvec2 NULL_ELEMENT = uvec2(0xFFFFu);\nvoid main()\n{\n#if STAGE == 1\nuvec2 outSize = uvec2(outputSize());\nuvec2 thread = uvec2(threadLocation());\nuvec2 size = uvec2(textureSize(corners, 0));\nuint location = thread.y * outSize.x + thread.x;\nivec2 pos = ivec2(location % size.x, location / size.x);\nvec4 pixel = location < size.x * size.y ? texelFetch(corners, pos, 0) : vec4(0.0f);\nbool isCorner = !isEncodedFloat16Zero(pixel.rb);\ncolor = isCorner ? uvec4(uvec2(pos), 1u, 0u) : uvec4(NULL_ELEMENT, 0u, 0u);\n#elif STAGE > 1\nint dblBlockSize = 2 * blockSize;\nivec2 thread = threadLocation();\nivec2 offset = thread % dblBlockSize;\nivec2 delta = thread - offset;\n#if SKIP_TEXTURE_READS\nif(blockSize >= 8) {\nuint sb = texture(lookupTable, texCoord).z;\nfloat p = max((float(sb) / float(blockSize)) / float(blockSize), DENSITY_FACTOR);\nfloat rowthr = float(dblBlockSize) * p + 3.0f * sqrt(p * (1.0f - p));\ncolor = uvec4(NULL_ELEMENT, 4u * sb, 0u);\nif(offset.y >= max(1, int(ceil(rowthr))))\nreturn;\n}\n#endif\n#define deltaCenter ivec2(0,0)\n#define deltaTop ivec2(0,-blockSize)\n#define deltaTopRight ivec2(blockSize,-blockSize)\n#define deltaRight ivec2(blockSize,0)\n#define deltaBottomRight ivec2(blockSize,blockSize)\n#define deltaBottom ivec2(0,blockSize)\n#define deltaBottomLeft ivec2(-blockSize,blockSize)\n#define deltaLeft ivec2(-blockSize,0)\n#define deltaTopLeft ivec2(-blockSize,-blockSize)\nivec2 boundary = ivec2(width - 1, height - 1) / blockSize;\nivec2 bottomRightPos = thread + deltaBottomRight;\nuvec2 valid = uvec2(\nbottomRightPos.x < width || bottomRightPos.x / blockSize == boundary.x,\nbottomRightPos.y < height || bottomRightPos.y / blockSize == boundary.y\n);\nuvec4 mask[4];\nmask[0] = uvec4(1u, valid.x, valid.y, valid.x * valid.y);\nmask[1] = uvec4(1u, 1u, valid.y, valid.y);\nmask[2] = uvec4(1u, valid.x, 1u, valid.x);\nmask[3] = uvec4(1u);\n#if SKIP_TEXTURE_READS\n#define calcSb(delta) texelFetch(lookupTable, blockSize * ((thread + (delta)) / blockSize), 0).z\nuint center = calcSb(deltaCenter);\nuint top = calcSb(deltaTop);\nuint topRight = calcSb(deltaTopRight);\nuint right = calcSb(deltaRight);\nuint bottomRight = calcSb(deltaBottomRight);\nuint bottom = calcSb(deltaBottom);\nuint bottomLeft = calcSb(deltaBottomLeft);\nuint left = calcSb(deltaLeft);\nuint topLeft = calcSb(deltaTopLeft);\n#else\n#define calcSb(pos) texture(lookupTable, (pos)).z\nuint center = calcSb(v_center);\nuint top = calcSb(v_top);\nuint topRight = calcSb(v_topRight);\nuint right = calcSb(v_right);\nuint bottomRight = calcSb(v_bottomRight);\nuint bottom = calcSb(v_bottom);\nuint bottomLeft = calcSb(v_bottomLeft);\nuint left = calcSb(v_left);\nuint topLeft = calcSb(v_topLeft);\n#endif\nuvec4 sums[4];\nsums[0] = uvec4(center, right, bottom, bottomRight);\nsums[1] = uvec4(left, center, bottomLeft, bottom);\nsums[2] = uvec4(top, topRight, center, right);\nsums[3] = uvec4(topLeft, top, left, center);\nivec2 cmp = ivec2(greaterThanEqual(offset, ivec2(blockSize)));\nint option = 2 * cmp.y + cmp.x;\nuvec4 cdef = sums[option] * mask[option];\nuint c2b = cdef.x, d2b = cdef.y, e2b = cdef.z, f2b = cdef.w;\nuint sb = center;\nuint s2b = c2b + d2b + e2b + f2b;\ns2b = s2b < sb ? 0xFFFFu : min(0xFFFFu, s2b);\nuint w2b = uint(min(dblBlockSize, width - delta.x));\nuvec2 uoffset = uvec2(offset);\nuint ceiling = s2b >= uoffset.x ? (s2b - uoffset.x) / w2b + uint((s2b - uoffset.x) % w2b > 0u) : 0u;\ncolor = uvec4(NULL_ELEMENT, s2b, 0u);\nif(uoffset.y >= ceiling)\nreturn;\nuint i2b = uoffset.y * w2b + uoffset.x;\nuint j2b = i2b >= c2b ? i2b - c2b : 0u;\nuint k2b = j2b >= d2b ? j2b - d2b : 0u;\nuint l2b = k2b >= e2b ? k2b - e2b : 0u;\nuint wl = uint(min(blockSize, width - delta.x));\nuint wr = uint(min(blockSize, width - delta.x - blockSize));\nivec2 magicOffset = (\n(i2b < c2b) ? ivec2(i2b % wl, i2b / wl) : (\n(j2b < d2b) ? ivec2(j2b % wr, j2b / wr) + ivec2(blockSize, 0) : (\n(k2b < e2b) ? ivec2(k2b % wl, k2b / wl) + ivec2(0, blockSize) : (\n(l2b < f2b) ? ivec2(l2b % wr, l2b / wr) + ivec2(blockSize) : ivec2(0)\n))));\nuvec2 a2b = texelFetch(lookupTable, delta + magicOffset, 0).xy;\ncolor = uvec4(a2b, s2b, 0u);\n#else\nuvec4 pix = texture(lookupTable, texCoord);\ncolor = all(equal(pix.xy, NULL_ELEMENT)) ? vec4(0,1,1,1) : vec4(1,0,0,1);\n#endif\n}"
-
- /***/ }),
-
- /***/ 4747:
- /***/ ((module) => {
-
- module.exports = "#if !defined(STAGE) || STAGE < 1\n#error Invalid STAGE\n#else\nuniform mediump int blockSize;\nout vec2 v_topLeft, v_top, v_topRight,\nv_left, v_center, v_right,\nv_bottomLeft, v_bottom, v_bottomRight;\nvoid vsmain()\n{\nfloat b = float(blockSize);\n#define V(x,y) (texCoord + (vec2((x),(y)) * b) / texSize)\nv_topLeft = V(-1,-1); v_top = V(0,-1); v_topRight = V(1,-1);\nv_left = V(-1,0); v_center = V(0,0); v_right = V(1,0);\nv_bottomLeft = V(-1,1); v_bottom = V(0,1); v_bottomRight = V(1,1);\n}\n#endif"
-
- /***/ }),
-
- /***/ 7421:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n@include \"keypoint-matches.glsl\"\n@include \"keypoint-descriptors.glsl\"\nuniform sampler2D candidates;\nuniform sampler2D filters;\nuniform int matcherLength;\nuniform sampler2D tables;\nuniform sampler2D descriptorDB;\nuniform int tableIndex;\nuniform int bucketCapacity;\nuniform int bucketsPerTable;\nuniform int tablesStride;\nuniform int descriptorDBStride;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#if HASH_SIZE > SEQUENCE_MAXLEN\n#error LSH: invalid HASH_SIZE\n#elif SEQUENCE_COUNT * SEQUENCE_MAXLEN * 4 > 16384\n#error LSH: sequences are too large!\n#elif (SEQUENCE_COUNT * SEQUENCE_MAXLEN) % 4 > 0\n#error LSH: sequences of invalid size!\n#endif\nlayout(std140) uniform LSHSequences\n{\nuvec4 sequences[(SEQUENCE_COUNT * SEQUENCE_MAXLEN) / 4];\n};\n#if HASH_SIZE == 10\nconst int SWAP_COUNT[3] = int[3](1, 11, 56);\nconst int[56] SWAP = int[56](0,1,2,4,8,16,32,64,128,256,512,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768);\n#elif HASH_SIZE == 11\nconst int SWAP_COUNT[3] = int[3](1, 12, 67);\nconst int[67] SWAP = int[67](0,1,2,4,8,16,32,64,128,256,512,1024,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536);\n#elif HASH_SIZE == 12\nconst int SWAP_COUNT[3] = int[3](1, 13, 79);\nconst int[79] SWAP = int[79](0,1,2,4,8,16,32,64,128,256,512,1024,2048,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072);\n#elif HASH_SIZE == 13\nconst int SWAP_COUNT[3] = int[3](1, 14, 92);\nconst int[92] SWAP = int[92](0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072,4097,4098,4100,4104,4112,4128,4160,4224,4352,4608,5120,6144);\n#elif HASH_SIZE == 14\nconst int SWAP_COUNT[3] = int[3](1, 15, 106);\nconst int[106] SWAP = int[106](0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072,4097,4098,4100,4104,4112,4128,4160,4224,4352,4608,5120,6144,8193,8194,8196,8200,8208,8224,8256,8320,8448,8704,9216,10240,12288);\n#elif HASH_SIZE == 15\nconst int SWAP_COUNT[3] = int[3](1, 16, 121);\nconst int[121] SWAP = int[121](0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072,4097,4098,4100,4104,4112,4128,4160,4224,4352,4608,5120,6144,8193,8194,8196,8200,8208,8224,8256,8320,8448,8704,9216,10240,12288,16385,16386,16388,16392,16400,16416,16448,16512,16640,16896,17408,18432,20480,24576);\n#elif HASH_SIZE == 16\nconst int SWAP_COUNT[3] = int[3](1, 17, 137);\nconst int[137] SWAP = int[137](0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072,4097,4098,4100,4104,4112,4128,4160,4224,4352,4608,5120,6144,8193,8194,8196,8200,8208,8224,8256,8320,8448,8704,9216,10240,12288,16385,16386,16388,16392,16400,16416,16448,16512,16640,16896,17408,18432,20480,24576,32769,32770,32772,32776,32784,32800,32832,32896,33024,33280,33792,34816,36864,40960,49152);\n#elif HASH_SIZE == 17\nconst int SWAP_COUNT[3] = int[3](1, 18, 154);\nconst int[154] SWAP = int[154](0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072,4097,4098,4100,4104,4112,4128,4160,4224,4352,4608,5120,6144,8193,8194,8196,8200,8208,8224,8256,8320,8448,8704,9216,10240,12288,16385,16386,16388,16392,16400,16416,16448,16512,16640,16896,17408,18432,20480,24576,32769,32770,32772,32776,32784,32800,32832,32896,33024,33280,33792,34816,36864,40960,49152,65537,65538,65540,65544,65552,65568,65600,65664,65792,66048,66560,67584,69632,73728,81920,98304);\n#elif HASH_SIZE == 18\nconst int SWAP_COUNT[3] = int[3](1, 19, 172);\nconst int[172] SWAP = int[172](0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072,4097,4098,4100,4104,4112,4128,4160,4224,4352,4608,5120,6144,8193,8194,8196,8200,8208,8224,8256,8320,8448,8704,9216,10240,12288,16385,16386,16388,16392,16400,16416,16448,16512,16640,16896,17408,18432,20480,24576,32769,32770,32772,32776,32784,32800,32832,32896,33024,33280,33792,34816,36864,40960,49152,65537,65538,65540,65544,65552,65568,65600,65664,65792,66048,66560,67584,69632,73728,81920,98304,131073,131074,131076,131080,131088,131104,131136,131200,131328,131584,132096,133120,135168,139264,147456,163840,196608);\n#elif HASH_SIZE == 19\nconst int SWAP_COUNT[3] = int[3](1, 20, 191);\nconst int[191] SWAP = int[191](0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072,4097,4098,4100,4104,4112,4128,4160,4224,4352,4608,5120,6144,8193,8194,8196,8200,8208,8224,8256,8320,8448,8704,9216,10240,12288,16385,16386,16388,16392,16400,16416,16448,16512,16640,16896,17408,18432,20480,24576,32769,32770,32772,32776,32784,32800,32832,32896,33024,33280,33792,34816,36864,40960,49152,65537,65538,65540,65544,65552,65568,65600,65664,65792,66048,66560,67584,69632,73728,81920,98304,131073,131074,131076,131080,131088,131104,131136,131200,131328,131584,132096,133120,135168,139264,147456,163840,196608,262145,262146,262148,262152,262160,262176,262208,262272,262400,262656,263168,264192,266240,270336,278528,294912,327680,393216);\n#elif HASH_SIZE == 20\nconst int SWAP_COUNT[3] = int[3](1, 21, 211);\nconst int[211] SWAP = int[211](0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,3,5,6,9,10,12,17,18,20,24,33,34,36,40,48,65,66,68,72,80,96,129,130,132,136,144,160,192,257,258,260,264,272,288,320,384,513,514,516,520,528,544,576,640,768,1025,1026,1028,1032,1040,1056,1088,1152,1280,1536,2049,2050,2052,2056,2064,2080,2112,2176,2304,2560,3072,4097,4098,4100,4104,4112,4128,4160,4224,4352,4608,5120,6144,8193,8194,8196,8200,8208,8224,8256,8320,8448,8704,9216,10240,12288,16385,16386,16388,16392,16400,16416,16448,16512,16640,16896,17408,18432,20480,24576,32769,32770,32772,32776,32784,32800,32832,32896,33024,33280,33792,34816,36864,40960,49152,65537,65538,65540,65544,65552,65568,65600,65664,65792,66048,66560,67584,69632,73728,81920,98304,131073,131074,131076,131080,131088,131104,131136,131200,131328,131584,132096,133120,135168,139264,147456,163840,196608,262145,262146,262148,262152,262160,262176,262208,262272,262400,262656,263168,264192,266240,270336,278528,294912,327680,393216,524289,524290,524292,524296,524304,524320,524352,524416,524544,524800,525312,526336,528384,532480,540672,557056,589824,655360,786432);\n#else\n#error Invalid HASH_SIZE\n#endif\n#if LEVEL < 0 || LEVEL > 2\n#error Invalid LEVEL\n#endif\nconst uint END_OF_LIST = 0xFFFFFFFFu;\nconst int NUMBER_OF_HASHES = SWAP_COUNT[LEVEL];\nuint sequenceElement(int sequenceIndex, int elementIndex)\n{\nint offset = (SEQUENCE_MAXLEN) * sequenceIndex + elementIndex;\nuvec4 tuple = sequences[offset / 4];\nreturn tuple[offset & 3];\n}\nint descriptorHash(uint[DESCRIPTOR_SIZE] descriptor, int sequenceIndex)\n{\nuint bit, b, m;\nint hash = 0;\n@unroll\nfor(int i = 0; i < HASH_SIZE; i++) {\nbit = sequenceElement(sequenceIndex, i);\nb = bit >> 3u;\nm = 1u << (bit & 7u);\nhash = (hash << 1) | int((descriptor[b] & m) != 0u);\n}\nreturn hash;\n}\n#define readTableData(tables, tablesStride, rasterIndex) decodeUint32(texelFetch((tables), ivec2((rasterIndex) % (tablesStride), (rasterIndex) / (tablesStride)), 0))\nvoid main()\n{\nivec2 thread = threadLocation();\nint keypointIndex = thread.x + thread.y * matcherLength;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = KeypointAddress(keypointIndex * pixelsPerKeypoint, 0);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, address);\ncolor = encodeKeypointMatch(MATCH_NOT_FOUND);\nif(isBadKeypoint(keypoint))\nreturn;\nKeypointMatch candidate = decodeKeypointMatch(threadPixel(candidates));\nKeypointMatch mfilter = decodeKeypointMatch(threadPixel(filters));\nuint[DESCRIPTOR_SIZE] candidateDescriptor;\nuint[DESCRIPTOR_SIZE] descriptor = readKeypointDescriptor(encodedKeypoints, descriptorSize, extraSize, encoderLength, address);\nint hash0 = descriptorHash(descriptor, tableIndex);\nfor(int h = 0; h < NUMBER_OF_HASHES; h++) {\nint hash = hash0 ^ SWAP[h];\nint tableAddress = tableIndex * bucketsPerTable * bucketCapacity;\nint bucketAddress = tableAddress + hash * bucketCapacity;\nbool validEntry = true;\nfor(int b = 0; b < bucketCapacity; b++) {\nint entryAddress = bucketAddress + b;\nuint entry = validEntry ? readTableData(tables, tablesStride, entryAddress) : END_OF_LIST;\nvalidEntry = (validEntry && entry != END_OF_LIST);\nint candidateIndex = int(entry);\ncandidateDescriptor = readKeypointDescriptorFromDB(descriptorDB, descriptorDBStride, validEntry ? candidateIndex : -1);\nint descriptorDistance = distanceBetweenKeypointDescriptors(descriptor, candidateDescriptor);\nKeypointMatch match = KeypointMatch(candidateIndex, descriptorDistance);\nbool betterThanCandidate = (match.dist < candidate.dist) || (match.dist == candidate.dist && match.index > candidate.index);\nbool worseThanFilter = (match.dist > mfilter.dist) || (match.dist == mfilter.dist && match.index < mfilter.index);\nbool nicerMatch = (validEntry && betterThanCandidate && worseThanFilter);\nivec2 v = nicerMatch ? ivec2(match.index, match.dist) : ivec2(candidate.index, candidate.dist);\ncandidate = KeypointMatch(v.x, v.y);\n}\n}\ncolor = encodeKeypointMatch(candidate);\n}"
-
- /***/ }),
-
- /***/ 4523:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n@include \"int32.glsl\"\n#if !defined(STAGE)\n#error Undefined STAGE\n#elif STAGE == 1\nuniform sampler2D encodedKeypointsA;\nuniform sampler2D encodedKeypointsB;\nuniform int encoderLengthA;\nuniform int encoderLengthB;\nuniform int encoderCapacityA;\nuniform int encoderCapacityB;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#elif STAGE == 2\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nuniform int maxKeypoints;\n#elif STAGE == 3\nuniform sampler2D array;\nuniform int blockSize;\n#elif STAGE == 4\nuniform sampler2D array;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#elif STAGE == 5\nuniform sampler2D array;\n#else\n#error Invalid STAGE\n#endif\n#define NULL_KEYPOINT_INDEX 0xFFFF\nconst highp uint UNIT = 0x10000u;\nvoid main()\n{\n#if STAGE == 1\nivec2 thread = threadLocation();\nKeypointAddress addr = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint keypointIndex = findKeypointIndex(addr, descriptorSize, extraSize);\nint newKeypointIndex = keypointIndex < encoderCapacityA ? keypointIndex : keypointIndex - encoderCapacityA;\ncolor = encodeNullKeypoint();\nif(newKeypointIndex >= max(encoderCapacityA, encoderCapacityB))\nreturn;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\naddr = KeypointAddress(newKeypointIndex * pixelsPerKeypoint, addr.offset);\nvec4 dataA = readKeypointData(encodedKeypointsA, encoderLengthA, addr);\nvec4 dataB = readKeypointData(encodedKeypointsB, encoderLengthB, addr);\ncolor = keypointIndex < encoderCapacityA ? dataA : dataB;\n#elif STAGE == 2\nivec2 thread = threadLocation();\nint keypointIndex = thread.y * outputSize().x + thread.x;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress addr = KeypointAddress(keypointIndex * pixelsPerKeypoint, 0);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, addr);\nbool isValid = !isNullKeypoint(keypoint) && keypointIndex < maxKeypoints;\nkeypointIndex = isValid ? keypointIndex : NULL_KEYPOINT_INDEX;\ncolor = encodeUint32(uint(keypointIndex & 0xFFFF) | (isValid ? UNIT : 0u));\n#elif STAGE == 3\nivec2 thread = threadLocation();\nivec2 size = outputSize();\nint arrayLength = size.x * size.y;\nint arrayIndex = thread.y * size.x + thread.x;\nint arrayIndexLeft = arrayIndex - blockSize;\nint arrayIndexRight = arrayIndex + blockSize;\nint mask = int(arrayIndexRight < arrayLength || arrayIndexRight / blockSize == (arrayLength - 1) / blockSize);\narrayIndexLeft = max(0, arrayIndexLeft);\narrayIndexRight = min(arrayLength - 1, arrayIndexRight);\n#define raster2pos(k) ivec2((k) % size.x, (k) / size.x)\nuvec3 entries32 = uvec3(\ndecodeUint32(threadPixel(array)),\ndecodeUint32(texelFetch(array, raster2pos(arrayIndexLeft), 0)),\ndecodeUint32(texelFetch(array, raster2pos(arrayIndexRight), 0))\n);\nivec3 sb = ivec3((entries32 >> 16u) & 0xFFFFu);\nsb.z *= mask;\nint dblBlockSize = 2 * blockSize;\nint offset = arrayIndex % dblBlockSize;\nint s2b = sb.x + (offset < blockSize ? sb.z : sb.y);\nint l2b = offset < blockSize ? sb.x : sb.y;\nuint keypointIndex = entries32.x & 0xFFFFu;\nuint shiftedS2b = uint(s2b) << 16u;\ncolor = encodeUint32(uint(NULL_KEYPOINT_INDEX) | shiftedS2b);\nif(offset >= s2b)\nreturn;\ncolor = encodeUint32(keypointIndex | shiftedS2b);\nif(offset < l2b)\nreturn;\nvec4 entry = texelFetch(array, raster2pos(arrayIndex + blockSize - l2b), 0);\nkeypointIndex = decodeUint32(entry) & 0xFFFFu;\ncolor = encodeUint32(keypointIndex | shiftedS2b);\n#elif STAGE == 4\nivec2 thread = threadLocation();\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress addr = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint keypointIndex = findKeypointIndex(addr, descriptorSize, extraSize);\n#define raster2pos(k) ivec2((k) % size.x, (k) / size.x)\nivec2 size = textureSize(array, 0);\nuint sortedPair = decodeUint32(texelFetch(array, raster2pos(keypointIndex), 0));\nint newKeypointIndex = int(sortedPair & 0xFFFFu);\ncolor = encodeNullKeypoint();\nif(newKeypointIndex == NULL_KEYPOINT_INDEX || keypointIndex >= size.x * size.y)\nreturn;\nKeypointAddress newAddr = KeypointAddress(newKeypointIndex * pixelsPerKeypoint, addr.offset);\ncolor = readKeypointData(encodedKeypoints, encoderLength, newAddr);\n#elif STAGE == 5\nuint val = decodeUint32(threadPixel(array));\ncolor = (val & 0xFFFFu) == uint(NULL_KEYPOINT_INDEX) ? vec4(0,1,1,1) : vec4(1,0,0,1);\n#endif\n}"
-
- /***/ }),
-
- /***/ 2277:
- /***/ ((module) => {
-
- module.exports = "@include \"pyramids.glsl\"\n@include \"float16.glsl\"\n@include \"filters.glsl\"\n#if !defined(USE_LAPLACIAN)\n#error Undefined USE_LAPLACIAN\n#endif\nuniform sampler2D corners;\nuniform sampler2D pyramid;\nuniform float lodStep;\n#if USE_LAPLACIAN\nuniform sampler2D pyrLaplacian;\n#endif\nvoid main()\n{\nivec2 thread = threadLocation();\nvec4 pixel = threadPixel(corners);\nfloat score = decodeFloat16(pixel.rb);\nfloat myEncodedLod = pixel.a;\nfloat lod = decodeLod(myEncodedLod);\nfloat lodPlus = lod + lodStep;\nfloat lodMinus = lod - lodStep;\nfloat pot = exp2(lod);\nfloat potPlus = exp2(lodPlus);\nfloat potMinus = exp2(lodMinus);\ncolor = pixel;\nif(score == 0.0f)\nreturn;\n#define P(p,u,v) textureLod(corners, texCoord + (p) * vec2((u),(v)) / texSize, 0.0f)\nvec4 pix[18];\n#define D(u,v) P(potMinus,(u),(v))\npix[0] = D(-1,-1); pix[1] = D(0,-1); pix[2] = D(1,-1);\npix[3] = D(-1,0); pix[4] = D(0,0); pix[5] = D(1,0);\npix[6] = D(-1,1); pix[7] = D(0,1); pix[8] = D(1,1);\n#define U(u,v) P(potPlus,(u),(v))\npix[9] = U(-1,-1); pix[10] = U(0,-1); pix[11] = U(1,-1);\npix[12] = U(-1,0); pix[13] = U(0,0); pix[14] = U(1,0);\npix[15] = U(-1,1); pix[16] = U(0,1); pix[17] = U(1,1);\nfloat scores[18];\n#define C(j) decodeFloat16(pix[j].rb)\nscores[0] = C(0); scores[1] = C(1); scores[2] = C(2);\nscores[3] = C(3); scores[4] = C(4); scores[5] = C(5);\nscores[6] = C(6); scores[7] = C(7); scores[8] = C(8);\nscores[9] = C(9); scores[10] = C(10); scores[11] = C(11);\nscores[12] = C(12); scores[13] = C(13); scores[14] = C(14);\nscores[15] = C(15); scores[16] = C(16); scores[17] = C(17);\nfloat lods[18];\n#define E(j) decodeLod(pix[j].a)\nlods[0] = E(0); lods[1] = E(1); lods[2] = E(2);\nlods[3] = E(3); lods[4] = E(4); lods[5] = E(5);\nlods[6] = E(6); lods[7] = E(7); lods[8] = E(8);\nlods[9] = E(9); lods[10] = E(10); lods[11] = E(11);\nlods[12] = E(12); lods[13] = E(13); lods[14] = E(14);\nlods[15] = E(15); lods[16] = E(16); lods[17] = E(17);\n#if USE_LAPLACIAN\n#define L(p,u,v) textureLod(pyrLaplacian, texCoord + (p) * vec2((u),(v)) / texSize, 0.0f)\nmat3 strengths[2];\nstrengths[0] = mat3(\n#define Lm(u,v) abs(decodeFloat16(L(potMinus,(u),(v)).xy))\nLm(-1,-1), Lm(0,-1), Lm(1,-1),\nLm(-1,0), Lm(0,0), Lm(1,0),\nLm(-1,1), Lm(0,1), Lm(1,1)\n);\nstrengths[1] = mat3(\n#define Lp(u,v) abs(decodeFloat16(L(potPlus,(u),(v)).zw))\nLp(-1,-1), Lp(0,-1), Lp(1,-1),\nLp(-1,0), Lp(0,0), Lp(1,0),\nLp(-1,1), Lp(0,1), Lp(1,1)\n);\nfloat myStrength = abs(laplacian(pyramid, vec2(thread), lod));\n#else\n#define L(u,v) (((v)+1)*3 + ((u)+1))\nmat3 strengths[2];\nstrengths[0] = mat3(\n#define Lm(u,v) scores[L((u),(v))]\nLm(-1,-1), Lm(0,-1), Lm(1,-1),\nLm(-1,0), Lm(0,0), Lm(1,0),\nLm(-1,1), Lm(0,1), Lm(1,1)\n);\nstrengths[1] = mat3(\n#define Lp(u,v) scores[9 + L((u),(v))]\nLp(-1,-1), Lp(0,-1), Lp(1,-1),\nLp(-1,0), Lp(0,0), Lp(1,0),\nLp(-1,1), Lp(0,1), Lp(1,1)\n);\nfloat myStrength = score;\n#endif\n#define B(j,lod) float(isSameLod(lods[j], (lod))) * float(scores[j] > 0.0f)\nmat3 nearLod[2];\nnearLod[0] = mat3(\n#define Bm(j) B((j), lodMinus)\nBm(0), Bm(1), Bm(2),\nBm(3), Bm(4), Bm(5),\nBm(6), Bm(7), Bm(8)\n);\nnearLod[1] = mat3(\n#define Bp(j) B((j), lodPlus)\nBp(9), Bp(10), Bp(11),\nBp(12), Bp(13), Bp(14),\nBp(15), Bp(16), Bp(17)\n);\nmat3 upStrengths = matrixCompMult(strengths[1], nearLod[1]);\nmat3 downStrengths = matrixCompMult(strengths[0], nearLod[0]);\nvec3 maxUpStrength3 = max(upStrengths[0], max(upStrengths[1], upStrengths[2]));\nvec3 maxDownStrength3 = max(downStrengths[0], max(downStrengths[1], downStrengths[2]));\nvec3 maxStrength3 = max(maxUpStrength3, maxDownStrength3);\nfloat maxStrength = max(maxStrength3.x, max(maxStrength3.y, maxStrength3.z));\ncolor.rb = encodeFloat16(score * step(maxStrength, myStrength));\n}"
-
- /***/ }),
-
- /***/ 8430:
- /***/ ((module) => {
-
- module.exports = "@include \"pyramids.glsl\"\n@include \"float16.glsl\"\nuniform sampler2D corners;\nvoid main()\n{\nivec2 thread = threadLocation();\nvec4 pixel = threadPixel(corners);\nfloat encodedLod = pixel.a;\nfloat score = decodeFloat16(pixel.rb);\nfloat lod = decodeLod(encodedLod);\nfloat pot = exp2(lod);\ncolor = pixel;\nif(score == 0.0f)\nreturn;\n#if 1\nvec2 gridSize = vec2(pot);\nvec2 gridLocation = floor(mod(texCoord * texSize, gridSize));\nvec2 gridDelta = gridLocation / gridSize - vec2(0.5f);\nfloat gridStep = 1.0f / pot;\nconst float adjustment = 1.25f;\ncolor.rb = encodeFloat16(0.0f);\nif(max(abs(gridDelta.x), abs(gridDelta.y)) > adjustment * gridStep)\nreturn;\n#endif\n#define P(x,y) textureLod(corners, texCoord + pot * vec2((x), (y)) / texSize, 0.0f)\nvec4 pix[9];\npix[0] = P(-1,-1); pix[1] = P(0,-1); pix[2] = P(1,-1);\npix[3] = P(-1, 0); pix[4] = pixel; pix[5] = P(1, 0);\npix[6] = P(-1, 1); pix[7] = P(0, 1); pix[8] = P(1, 1);\n#define S(j) decodeFloat16(pix[j].rb)\nmat3 scores = mat3(\nS(0), S(1), S(2),\nS(3), S(4), S(5),\nS(6), S(7), S(8)\n);\n#define B(j) float(isSameLod(decodeLod(pix[j].a), lod))\nmat3 sameLod = mat3(\nB(0), B(1), B(2),\nB(3), B(4), B(5),\nB(6), B(7), B(8)\n);\nmat3 sameLodScores = matrixCompMult(scores, sameLod);\nvec3 maxScore3 = max(sameLodScores[0], max(sameLodScores[1], sameLodScores[2]));\nfloat maxScore = max(maxScore3.x, max(maxScore3.y, maxScore3.z));\ncolor.rb = encodeFloat16(score * step(maxScore, score));\n}"
-
- /***/ }),
-
- /***/ 9743:
- /***/ ((module) => {
-
- module.exports = "@include \"pyramids.glsl\"\n@include \"float16.glsl\"\nuniform sampler2D image;\nuniform float lodStep;\n#if !defined(MULTISCALE)\n#error Must define MULTISCALE\n#elif MULTISCALE != 0\n#define LOD_STEP (lodStep)\n#define USE_MIDDLE_RING\n#else\n#define LOD_STEP (0.0f)\n#endif\n#define PIX(x,y) pixelAtShortOffset(image, ivec2((x),(y)))\n#define L2(v,i) bvec2(isSameEncodedLod(v[i].a, alphaMinus), isSameEncodedLod(v[i].a, alphaPlus))\n#define L3(v,i) bvec3(isSameEncodedLod(v[i].a, alpha), isSameEncodedLod(v[i].a, alphaMinus), isSameEncodedLod(v[i].a, alphaPlus))\n#define S3(v,i) decodeFloat16(v[i].rb) * float(any(L3(v,i)))\n#define S2(v,i) decodeFloat16(v[i].rb) * float(any(L2(v,i)))\n#define P(i) S3(p,i)\n#define Q(i) S2(q,i)\n#define R(i) S2(r,i)\nconst vec4 O = vec4(0.0f);\nvoid main()\n{\nvec4 pixel = threadPixel(image);\nfloat lod = decodeLod(pixel.a);\nfloat score = decodeFloat16(pixel.rb);\ncolor = pixel;\nif(score == 0.0f)\nreturn;\nvec4 p[8];\np[0] = PIX(0,1); p[1] = PIX(1,1); p[2] = PIX(1,0); p[3] = PIX(1,-1);\np[4] = PIX(0,-1); p[5] = PIX(-1,-1); p[6] = PIX(-1,0); p[7] = PIX(-1,1);\n#ifdef USE_MIDDLE_RING\nvec4 q[16];\nq[0] = PIX(0,2); q[1] = PIX(1,2); q[2] = PIX(2,2); q[3] = PIX(2,1);\nq[4] = PIX(2,0); q[5] = PIX(2,-1); q[6] = PIX(2,-2); q[7] = PIX(1,-2);\nq[8] = PIX(0,-2); q[9] = PIX(-1,-2); q[10] = PIX(-2,-2); q[11] = PIX(-2,-1);\nq[12] = PIX(-2,0); q[13] = PIX(-2,1); q[14] = PIX(-2,2); q[15] = PIX(-1,2);\n#else\nvec4 q[16];\nq[0] = O; q[1] = O; q[2] = O; q[3] = O;\nq[4] = O; q[5] = O; q[6] = O; q[7] = O;\nq[8] = O; q[9] = O; q[10] = O; q[11] = O;\nq[12] = O; q[13] = O; q[14] = O; q[15] = O;\n#endif\n#ifdef USE_OUTER_RING\nvec4 r[16];\nr[0] = PIX(0,3); r[1] = PIX(1,3); r[2] = PIX(3,1); r[3] = PIX(3,0);\nr[4] = PIX(3,-1); r[5] = PIX(1,-3); r[6] = PIX(0,-3); r[7] = PIX(-1,-3);\nr[8] = PIX(-3,-1); r[9] = PIX(-3,0); r[10] = PIX(-3,1); r[11] = PIX(-1,3);\nr[12] = PIX(0,4); r[13] = PIX(4,0); r[14] = PIX(0,-4); r[15] = PIX(-4,0);\n#else\nvec4 r[16];\nr[0] = O; r[1] = O; r[2] = O; r[3] = O;\nr[4] = O; r[5] = O; r[6] = O; r[7] = O;\nr[8] = O; r[9] = O; r[10] = O; r[11] = O;\nr[12] = O; r[13] = O; r[14] = O; r[15] = O;\n#endif\nfloat alphaPlus = encodeLod(lod + LOD_STEP);\nfloat alphaMinus = encodeLod(lod - LOD_STEP);\nfloat alpha = encodeLod(lod);\nmat3 innerScore = mat3(\nP(0), P(1), P(2), P(3),\nP(4), P(5), P(6), P(7),\n0.0f);\nmat4 middleScore = mat4(\nQ(0), Q(1), Q(2), Q(3),\nQ(4), Q(5), Q(6), Q(7),\nQ(8), Q(9), Q(10), Q(11),\nQ(12), Q(13), Q(14), Q(15)\n);\nmat4 outerScore = mat4(\nR(0), R(1), R(2), R(3),\nR(4), R(5), R(6), R(7),\nR(8), R(9), R(10), R(11),\nR(12), R(13), R(14), R(15)\n);\nvec3 maxInnerScore3 = max(innerScore[0], max(innerScore[1], innerScore[2]));\nvec4 maxMiddleScore4 = max(max(middleScore[0], middleScore[1]), max(middleScore[2], middleScore[3]));\nvec4 maxOuterScore4 = max(max(outerScore[0], outerScore[1]), max(outerScore[2], outerScore[3]));\nfloat maxInnerScore = max(maxInnerScore3.x, max(maxInnerScore3.y, maxInnerScore3.z));\nfloat maxMiddleScore = max(max(maxMiddleScore4.x, maxMiddleScore4.y), max(maxMiddleScore4.z, maxMiddleScore4.w));\nfloat maxOuterScore = max(max(maxOuterScore4.x, maxOuterScore4.y), max(maxOuterScore4.z, maxOuterScore4.w));\nfloat maxScore = max(maxInnerScore, max(maxMiddleScore, maxOuterScore));\nfloat finalScore = step(maxScore, score) * score;\ncolor.rb = encodeFloat16(finalScore);\n}"
-
- /***/ }),
-
- /***/ 3464:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D encodedCorners;\nuniform int encoderLength;\nuniform sampler2D image;\nuniform int extraSize;\nconst int descriptorSize = 32;\n#define P(a,b,c,d) ivec4((a),(b),(c),(d))\nconst ivec4 pat31[256] = ivec4[256](\nP(8,-3,9,5),\nP(4,2,7,-12),\nP(-11,9,-8,2),\nP(7,-12,12,-13),\nP(2,-13,2,12),\nP(1,-7,1,6),\nP(-2,-10,-2,-4),\nP(-13,-13,-11,-8),\nP(-13,-3,-12,-9),\nP(10,4,11,9),\nP(-13,-8,-8,-9),\nP(-11,7,-9,12),\nP(7,7,12,6),\nP(-4,-5,-3,0),\nP(-13,2,-12,-3),\nP(-9,0,-7,5),\nP(12,-6,12,-1),\nP(-3,6,-2,12),\nP(-6,-13,-4,-8),\nP(11,-13,12,-8),\nP(4,7,5,1),\nP(5,-3,10,-3),\nP(3,-7,6,12),\nP(-8,-7,-6,-2),\nP(-2,11,-1,-10),\nP(-13,12,-8,10),\nP(-7,3,-5,-3),\nP(-4,2,-3,7),\nP(-10,-12,-6,11),\nP(5,-12,6,-7),\nP(5,-6,7,-1),\nP(1,0,4,-5),\nP(9,11,11,-13),\nP(4,7,4,12),\nP(2,-1,4,4),\nP(-4,-12,-2,7),\nP(-8,-5,-7,-10),\nP(4,11,9,12),\nP(0,-8,1,-13),\nP(-13,-2,-8,2),\nP(-3,-2,-2,3),\nP(-6,9,-4,-9),\nP(8,12,10,7),\nP(0,9,1,3),\nP(7,-5,11,-10),\nP(-13,-6,-11,0),\nP(10,7,12,1),\nP(-6,-3,-6,12),\nP(10,-9,12,-4),\nP(-13,8,-8,-12),\nP(-13,0,-8,-4),\nP(3,3,7,8),\nP(5,7,10,-7),\nP(-1,7,1,-12),\nP(3,-10,5,6),\nP(2,-4,3,-10),\nP(-13,0,-13,5),\nP(-13,-7,-12,12),\nP(-13,3,-11,8),\nP(-7,12,-4,7),\nP(6,-10,12,8),\nP(-9,-1,-7,-6),\nP(-2,-5,0,12),\nP(-12,5,-7,5),\nP(3,-10,8,-13),\nP(-7,-7,-4,5),\nP(-3,-2,-1,-7),\nP(2,9,5,-11),\nP(-11,-13,-5,-13),\nP(-1,6,0,-1),\nP(5,-3,5,2),\nP(-4,-13,-4,12),\nP(-9,-6,-9,6),\nP(-12,-10,-8,-4),\nP(10,2,12,-3),\nP(7,12,12,12),\nP(-7,-13,-6,5),\nP(-4,9,-3,4),\nP(7,-1,12,2),\nP(-7,6,-5,1),\nP(-13,11,-12,5),\nP(-3,7,-2,-6),\nP(7,-8,12,-7),\nP(-13,-7,-11,-12),\nP(1,-3,12,12),\nP(2,-6,3,0),\nP(-4,3,-2,-13),\nP(-1,-13,1,9),\nP(7,1,8,-6),\nP(1,-1,3,12),\nP(9,1,12,6),\nP(-1,-9,-1,3),\nP(-13,-13,-10,5),\nP(7,7,10,12),\nP(12,-5,12,9),\nP(6,3,7,11),\nP(5,-13,6,10),\nP(2,-12,2,3),\nP(3,8,4,-6),\nP(2,6,12,-13),\nP(9,-12,10,3),\nP(-8,4,-7,9),\nP(-11,12,-4,-6),\nP(1,12,2,-8),\nP(6,-9,7,-4),\nP(2,3,3,-2),\nP(6,3,11,0),\nP(3,-3,8,-8),\nP(7,8,9,3),\nP(-11,-5,-6,-4),\nP(-10,11,-5,10),\nP(-5,-8,-3,12),\nP(-10,5,-9,0),\nP(8,-1,12,-6),\nP(4,-6,6,-11),\nP(-10,12,-8,7),\nP(4,-2,6,7),\nP(-2,0,-2,12),\nP(-5,-8,-5,2),\nP(7,-6,10,12),\nP(-9,-13,-8,-8),\nP(-5,-13,-5,-2),\nP(8,-8,9,-13),\nP(-9,-11,-9,0),\nP(1,-8,1,-2),\nP(7,-4,9,1),\nP(-2,1,-1,-4),\nP(11,-6,12,-11),\nP(-12,-9,-6,4),\nP(3,7,7,12),\nP(5,5,10,8),\nP(0,-4,2,8),\nP(-9,12,-5,-13),\nP(0,7,2,12),\nP(-1,2,1,7),\nP(5,11,7,-9),\nP(3,5,6,-8),\nP(-13,-4,-8,9),\nP(-5,9,-3,-3),\nP(-4,-7,-3,-12),\nP(6,5,8,0),\nP(-7,6,-6,12),\nP(-13,6,-5,-2),\nP(1,-10,3,10),\nP(4,1,8,-4),\nP(-2,-2,2,-13),\nP(2,-12,12,12),\nP(-2,-13,0,-6),\nP(4,1,9,3),\nP(-6,-10,-3,-5),\nP(-3,-13,-1,1),\nP(7,5,12,-11),\nP(4,-2,5,-7),\nP(-13,9,-9,-5),\nP(7,1,8,6),\nP(7,-8,7,6),\nP(-7,-4,-7,1),\nP(-8,11,-7,-8),\nP(-13,6,-12,-8),\nP(2,4,3,9),\nP(10,-5,12,3),\nP(-6,-5,-6,7),\nP(8,-3,9,-8),\nP(2,-12,2,8),\nP(-11,-2,-10,3),\nP(-12,-13,-7,-9),\nP(-11,0,-10,-5),\nP(5,-3,11,8),\nP(-2,-13,-1,12),\nP(-1,-8,0,9),\nP(-13,-11,-12,-5),\nP(-10,-2,-10,11),\nP(-3,9,-2,-13),\nP(2,-3,3,2),\nP(-9,-13,-4,0),\nP(-4,6,-3,-10),\nP(-4,12,-2,-7),\nP(-6,-11,-4,9),\nP(6,-3,6,11),\nP(-13,11,-5,5),\nP(11,11,12,6),\nP(7,-5,12,-2),\nP(-1,12,0,7),\nP(-4,-8,-3,-2),\nP(-7,1,-6,7),\nP(-13,-12,-8,-13),\nP(-7,-2,-6,-8),\nP(-8,5,-6,-9),\nP(-5,-1,-4,5),\nP(-13,7,-8,10),\nP(1,5,5,-13),\nP(1,0,10,-13),\nP(9,12,10,-1),\nP(5,-8,10,-9),\nP(-1,11,1,-13),\nP(-9,-3,-6,2),\nP(-1,-10,1,12),\nP(-13,1,-8,-10),\nP(8,-11,10,-6),\nP(2,-13,3,-6),\nP(7,-13,12,-9),\nP(-10,-10,-5,-7),\nP(-10,-8,-8,-13),\nP(4,-6,8,5),\nP(3,12,8,-13),\nP(-4,2,-3,-3),\nP(5,-13,10,-12),\nP(4,-13,5,-1),\nP(-9,9,-4,3),\nP(0,3,3,-9),\nP(-12,1,-6,1),\nP(3,2,4,-8),\nP(-10,-10,-10,9),\nP(8,-13,12,12),\nP(-8,-12,-6,-5),\nP(2,2,3,7),\nP(10,6,11,-8),\nP(6,8,8,-12),\nP(-7,10,-6,5),\nP(-3,-9,-3,9),\nP(-1,-13,-1,5),\nP(-3,-7,-3,4),\nP(-8,-2,-8,3),\nP(4,2,12,12),\nP(2,-5,3,11),\nP(6,-9,11,-13),\nP(3,-1,7,12),\nP(11,-1,12,4),\nP(-3,0,-3,6),\nP(4,-11,4,12),\nP(2,-4,2,1),\nP(-10,-6,-8,1),\nP(-13,7,-11,1),\nP(-13,12,-11,-13),\nP(6,0,11,-13),\nP(0,-1,1,4),\nP(-13,3,-9,-2),\nP(-9,8,-6,-3),\nP(-13,-6,-8,-2),\nP(5,-9,8,10),\nP(2,7,3,-9),\nP(-1,-6,-1,-1),\nP(9,5,11,-2),\nP(11,-3,12,-8),\nP(3,0,3,5),\nP(-1,4,0,10),\nP(3,-6,4,5),\nP(-13,0,-10,5),\nP(5,8,12,11),\nP(8,9,9,-6),\nP(7,-4,8,-12),\nP(-10,4,-10,9),\nP(7,3,12,4),\nP(9,-7,10,-2),\nP(7,0,12,-2),\nP(-1,-6,0,-11)\n);\nvoid getPair(int index, mat2 rot, out vec2 p, out vec2 q)\n{\nivec4 data = pat31[index];\nvec2 op = vec2(data.xy);\nvec2 oq = vec2(data.zw);\np = rot * op;\nq = rot * oq;\n}\nvoid main()\n{\nvec4 pixel = threadPixel(encodedCorners);\nivec2 thread = threadLocation();\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint descriptorCell = address.offset - sizeofEncodedKeypoint(0, extraSize) / 4;\ncolor = pixel;\nif(descriptorCell < 0)\nreturn;\nKeypoint keypoint = decodeKeypoint(encodedCorners, encoderLength, address);\nif(isBadKeypoint(keypoint))\nreturn;\nfloat degreesOrientation = round(360.0f + degrees(keypoint.orientation));\nfloat orientation = radians(degreesOrientation - mod(degreesOrientation, 12.0f));\nfloat kcos = cos(orientation);\nfloat ksin = sin(orientation);\nmat2 rot = mat2(kcos, ksin, -ksin, kcos);\nfloat pot = exp2(keypoint.lod);\nint patternStart = 32 * descriptorCell;\nuint test[4] = uint[4](0u, 0u, 0u, 0u);\nfor(int t = 0; t < 4; t++) {\nuint bits = 0u;\nvec2 p, q;\nvec4 a, b;\nint i = t * 8;\n@unroll\nfor(int j = 0; j < 8; j++) {\ngetPair(patternStart + i + j, rot, p, q);\na = texelFetch(image, ivec2(round(keypoint.position + pot * p)), 0);\nb = texelFetch(image, ivec2(round(keypoint.position + pot * q)), 0);\nbits |= uint(a.g < b.g) << j;\n}\ntest[t] = bits;\n}\ncolor = vec4(test[0], test[1], test[2], test[3]) / 255.0f;\n}"
-
- /***/ }),
-
- /***/ 7184:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D image;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#define P(x,y) ivec2((x),(y))\nconst int diskPointCount[16] = int[16](0, 4, 12, 28, 48, 80, 112, 148, 196, 252, 316, 376, 440, 528, 612, 708);\nconst ivec2 diskPoint[708] = ivec2[708](\nP(0,-1),P(-1,0),P(1,0),P(0,1),\nP(-1,-1),P(1,-1),P(-1,1),P(1,1),P(0,-2),P(-2,0),P(2,0),P(0,2),\nP(-1,-2),P(1,-2),P(-2,-1),P(2,-1),P(-2,1),P(2,1),P(-1,2),P(1,2),P(-2,-2),P(2,-2),P(-2,2),P(2,2),P(0,-3),P(-3,0),P(3,0),P(0,3),\nP(-1,-3),P(1,-3),P(-3,-1),P(3,-1),P(-3,1),P(3,1),P(-1,3),P(1,3),P(-2,-3),P(2,-3),P(-3,-2),P(3,-2),P(-3,2),P(3,2),P(-2,3),P(2,3),P(0,-4),P(-4,0),P(4,0),P(0,4),\nP(-1,-4),P(1,-4),P(-4,-1),P(4,-1),P(-4,1),P(4,1),P(-1,4),P(1,4),P(-3,-3),P(3,-3),P(-3,3),P(3,3),P(-2,-4),P(2,-4),P(-4,-2),P(4,-2),P(-4,2),P(4,2),P(-2,4),P(2,4),P(0,-5),P(-3,-4),P(3,-4),P(-4,-3),P(4,-3),P(-5,0),P(5,0),P(-4,3),P(4,3),P(-3,4),P(3,4),P(0,5),\nP(-1,-5),P(1,-5),P(-5,-1),P(5,-1),P(-5,1),P(5,1),P(-1,5),P(1,5),P(-2,-5),P(2,-5),P(-5,-2),P(5,-2),P(-5,2),P(5,2),P(-2,5),P(2,5),P(-4,-4),P(4,-4),P(-4,4),P(4,4),P(-3,-5),P(3,-5),P(-5,-3),P(5,-3),P(-5,3),P(5,3),P(-3,5),P(3,5),P(0,-6),P(-6,0),P(6,0),P(0,6),\nP(-1,-6),P(1,-6),P(-6,-1),P(6,-1),P(-6,1),P(6,1),P(-1,6),P(1,6),P(-2,-6),P(2,-6),P(-6,-2),P(6,-2),P(-6,2),P(6,2),P(-2,6),P(2,6),P(-4,-5),P(4,-5),P(-5,-4),P(5,-4),P(-5,4),P(5,4),P(-4,5),P(4,5),P(-3,-6),P(3,-6),P(-6,-3),P(6,-3),P(-6,3),P(6,3),P(-3,6),P(3,6),P(0,-7),P(-7,0),P(7,0),P(0,7),\nP(-1,-7),P(1,-7),P(-5,-5),P(5,-5),P(-7,-1),P(7,-1),P(-7,1),P(7,1),P(-5,5),P(5,5),P(-1,7),P(1,7),P(-4,-6),P(4,-6),P(-6,-4),P(6,-4),P(-6,4),P(6,4),P(-4,6),P(4,6),P(-2,-7),P(2,-7),P(-7,-2),P(7,-2),P(-7,2),P(7,2),P(-2,7),P(2,7),P(-3,-7),P(3,-7),P(-7,-3),P(7,-3),P(-7,3),P(7,3),P(-3,7),P(3,7),P(-5,-6),P(5,-6),P(-6,-5),P(6,-5),P(-6,5),P(6,5),P(-5,6),P(5,6),P(0,-8),P(-8,0),P(8,0),P(0,8),\nP(-1,-8),P(1,-8),P(-4,-7),P(4,-7),P(-7,-4),P(7,-4),P(-8,-1),P(8,-1),P(-8,1),P(8,1),P(-7,4),P(7,4),P(-4,7),P(4,7),P(-1,8),P(1,8),P(-2,-8),P(2,-8),P(-8,-2),P(8,-2),P(-8,2),P(8,2),P(-2,8),P(2,8),P(-6,-6),P(6,-6),P(-6,6),P(6,6),P(-3,-8),P(3,-8),P(-8,-3),P(8,-3),P(-8,3),P(8,3),P(-3,8),P(3,8),P(-5,-7),P(5,-7),P(-7,-5),P(7,-5),P(-7,5),P(7,5),P(-5,7),P(5,7),P(-4,-8),P(4,-8),P(-8,-4),P(8,-4),P(-8,4),P(8,4),P(-4,8),P(4,8),P(0,-9),P(-9,0),P(9,0),P(0,9),\nP(-1,-9),P(1,-9),P(-9,-1),P(9,-1),P(-9,1),P(9,1),P(-1,9),P(1,9),P(-2,-9),P(2,-9),P(-6,-7),P(6,-7),P(-7,-6),P(7,-6),P(-9,-2),P(9,-2),P(-9,2),P(9,2),P(-7,6),P(7,6),P(-6,7),P(6,7),P(-2,9),P(2,9),P(-5,-8),P(5,-8),P(-8,-5),P(8,-5),P(-8,5),P(8,5),P(-5,8),P(5,8),P(-3,-9),P(3,-9),P(-9,-3),P(9,-3),P(-9,3),P(9,3),P(-3,9),P(3,9),P(-4,-9),P(4,-9),P(-9,-4),P(9,-4),P(-9,4),P(9,4),P(-4,9),P(4,9),P(-7,-7),P(7,-7),P(-7,7),P(7,7),P(0,-10),P(-6,-8),P(6,-8),P(-8,-6),P(8,-6),P(-10,0),P(10,0),P(-8,6),P(8,6),P(-6,8),P(6,8),P(0,10),\nP(-1,-10),P(1,-10),P(-10,-1),P(10,-1),P(-10,1),P(10,1),P(-1,10),P(1,10),P(-2,-10),P(2,-10),P(-10,-2),P(10,-2),P(-10,2),P(10,2),P(-2,10),P(2,10),P(-5,-9),P(5,-9),P(-9,-5),P(9,-5),P(-9,5),P(9,5),P(-5,9),P(5,9),P(-3,-10),P(3,-10),P(-10,-3),P(10,-3),P(-10,3),P(10,3),P(-3,10),P(3,10),P(-7,-8),P(7,-8),P(-8,-7),P(8,-7),P(-8,7),P(8,7),P(-7,8),P(7,8),P(-4,-10),P(4,-10),P(-10,-4),P(10,-4),P(-10,4),P(10,4),P(-4,10),P(4,10),P(-6,-9),P(6,-9),P(-9,-6),P(9,-6),P(-9,6),P(9,6),P(-6,9),P(6,9),P(0,-11),P(-11,0),P(11,0),P(0,11),\nP(-1,-11),P(1,-11),P(-11,-1),P(11,-1),P(-11,1),P(11,1),P(-1,11),P(1,11),P(-2,-11),P(2,-11),P(-5,-10),P(5,-10),P(-10,-5),P(10,-5),P(-11,-2),P(11,-2),P(-11,2),P(11,2),P(-10,5),P(10,5),P(-5,10),P(5,10),P(-2,11),P(2,11),P(-8,-8),P(8,-8),P(-8,8),P(8,8),P(-3,-11),P(3,-11),P(-7,-9),P(7,-9),P(-9,-7),P(9,-7),P(-11,-3),P(11,-3),P(-11,3),P(11,3),P(-9,7),P(9,7),P(-7,9),P(7,9),P(-3,11),P(3,11),P(-6,-10),P(6,-10),P(-10,-6),P(10,-6),P(-10,6),P(10,6),P(-6,10),P(6,10),P(-4,-11),P(4,-11),P(-11,-4),P(11,-4),P(-11,4),P(11,4),P(-4,11),P(4,11),P(0,-12),P(-12,0),P(12,0),P(0,12),\nP(-1,-12),P(1,-12),P(-8,-9),P(8,-9),P(-9,-8),P(9,-8),P(-12,-1),P(12,-1),P(-12,1),P(12,1),P(-9,8),P(9,8),P(-8,9),P(8,9),P(-1,12),P(1,12),P(-5,-11),P(5,-11),P(-11,-5),P(11,-5),P(-11,5),P(11,5),P(-5,11),P(5,11),P(-2,-12),P(2,-12),P(-12,-2),P(12,-2),P(-12,2),P(12,2),P(-2,12),P(2,12),P(-7,-10),P(7,-10),P(-10,-7),P(10,-7),P(-10,7),P(10,7),P(-7,10),P(7,10),P(-3,-12),P(3,-12),P(-12,-3),P(12,-3),P(-12,3),P(12,3),P(-3,12),P(3,12),P(-6,-11),P(6,-11),P(-11,-6),P(11,-6),P(-11,6),P(11,6),P(-6,11),P(6,11),P(-4,-12),P(4,-12),P(-12,-4),P(12,-4),P(-12,4),P(12,4),P(-4,12),P(4,12),P(-9,-9),P(9,-9),P(-9,9),P(9,9),P(-8,-10),P(8,-10),P(-10,-8),P(10,-8),P(-10,8),P(10,8),P(-8,10),P(8,10),P(0,-13),P(-5,-12),P(5,-12),P(-12,-5),P(12,-5),P(-13,0),P(13,0),P(-12,5),P(12,5),P(-5,12),P(5,12),P(0,13),\nP(-1,-13),P(1,-13),P(-7,-11),P(7,-11),P(-11,-7),P(11,-7),P(-13,-1),P(13,-1),P(-13,1),P(13,1),P(-11,7),P(11,7),P(-7,11),P(7,11),P(-1,13),P(1,13),P(-2,-13),P(2,-13),P(-13,-2),P(13,-2),P(-13,2),P(13,2),P(-2,13),P(2,13),P(-3,-13),P(3,-13),P(-13,-3),P(13,-3),P(-13,3),P(13,3),P(-3,13),P(3,13),P(-6,-12),P(6,-12),P(-12,-6),P(12,-6),P(-12,6),P(12,6),P(-6,12),P(6,12),P(-9,-10),P(9,-10),P(-10,-9),P(10,-9),P(-10,9),P(10,9),P(-9,10),P(9,10),P(-4,-13),P(4,-13),P(-8,-11),P(8,-11),P(-11,-8),P(11,-8),P(-13,-4),P(13,-4),P(-13,4),P(13,4),P(-11,8),P(11,8),P(-8,11),P(8,11),P(-4,13),P(4,13),P(-7,-12),P(7,-12),P(-12,-7),P(12,-7),P(-12,7),P(12,7),P(-7,12),P(7,12),P(-5,-13),P(5,-13),P(-13,-5),P(13,-5),P(-13,5),P(13,5),P(-5,13),P(5,13),P(0,-14),P(-14,0),P(14,0),P(0,14),\nP(-1,-14),P(1,-14),P(-14,-1),P(14,-1),P(-14,1),P(14,1),P(-1,14),P(1,14),P(-2,-14),P(2,-14),P(-10,-10),P(10,-10),P(-14,-2),P(14,-2),P(-14,2),P(14,2),P(-10,10),P(10,10),P(-2,14),P(2,14),P(-9,-11),P(9,-11),P(-11,-9),P(11,-9),P(-11,9),P(11,9),P(-9,11),P(9,11),P(-3,-14),P(3,-14),P(-6,-13),P(6,-13),P(-13,-6),P(13,-6),P(-14,-3),P(14,-3),P(-14,3),P(14,3),P(-13,6),P(13,6),P(-6,13),P(6,13),P(-3,14),P(3,14),P(-8,-12),P(8,-12),P(-12,-8),P(12,-8),P(-12,8),P(12,8),P(-8,12),P(8,12),P(-4,-14),P(4,-14),P(-14,-4),P(14,-4),P(-14,4),P(14,4),P(-4,14),P(4,14),P(-7,-13),P(7,-13),P(-13,-7),P(13,-7),P(-13,7),P(13,7),P(-7,13),P(7,13),P(-5,-14),P(5,-14),P(-10,-11),P(10,-11),P(-11,-10),P(11,-10),P(-14,-5),P(14,-5),P(-14,5),P(14,5),P(-11,10),P(11,10),P(-10,11),P(10,11),P(-5,14),P(5,14),P(0,-15),P(-9,-12),P(9,-12),P(-12,-9),P(12,-9),P(-15,0),P(15,0),P(-12,9),P(12,9),P(-9,12),P(9,12),P(0,15)\n);\nconst int DEFAULT_PATCH_RADIUS = 15;\nconst int MIN_PATCH_RADIUS = 2;\nvoid main()\n{\nvec4 pixel = threadPixel(encodedKeypoints);\nivec2 thread = threadLocation();\nint keypointIndex = thread.x + thread.y * outputSize().x;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = KeypointAddress(keypointIndex * pixelsPerKeypoint, 0);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, address);\nvec2 m = vec2(0.0f);\nfloat pot = exp2(keypoint.lod);\nvec2 imageSize = vec2(textureSize(image, 0));\nint scaledRadius = int(ceil(float(DEFAULT_PATCH_RADIUS) / pot));\nint radius = max(scaledRadius, MIN_PATCH_RADIUS);\nint count = diskPointCount[radius];\nfor(int j = 0; j < count; j++) {\nvec2 offset = vec2(diskPoint[j]);\nvec2 position = keypoint.position + round(pot * offset);\nvec4 patchPixel = texture(image, (position + vec2(0.5f)) / imageSize);\nm += offset * patchPixel.g;\n}\nfloat angle = fastAtan2(m.y, m.x);\nfloat encodedOrientation = encodeKeypointOrientation(angle);\ncolor = vec4(0.0f, encodedOrientation, 0.0f, 0.0f);\n}"
-
- /***/ }),
-
- /***/ 7220:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n@include \"filters.glsl\"\n#if !defined(METHOD)\n#error Undefined METHOD\n#endif\nuniform sampler2D pyramid;\nuniform float lodStep;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#if METHOD == 1\nuniform int threshold;\n#endif\nconst float eps = 1e-6;\nfloat cornerStrength(vec2 position, float lod)\n{\n#if METHOD == 0\nreturn laplacian(pyramid, position, lod);\n#elif METHOD == 1\nfloat pot = exp2(lod);\nfloat t = float(clamp(threshold, 0, 255)) / 255.0f;\n#define P(x,y) pyrPixelAtOffset(pyramid, lod, pot, ivec2((x),(y))).g\nmat4 mp = mat4(\nP(0,3),P(3,0),P(0,-3),P(-3,0),\nP(1,3),P(2,2),P(3,1),P(3,-1),\nP(2,-2),P(1,-3),P(-1,-3),P(-2,-2),\nP(-3,-1),P(-3,1),P(-2,2),P(-1,3)\n);\nfloat c = P(0,0);\nfloat ct = c + t, c_t = c - t;\nmat4 mct = mp - mat4(ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct,ct);\nmat4 mc_t = mat4(c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t,c_t) - mp;\nconst vec4 zeros = vec4(0.0f), ones = vec4(1.0f);\nvec4 bs = max(mct[0], zeros), ds = max(mc_t[0], zeros);\nbs += max(mct[1], zeros); ds += max(mc_t[1], zeros);\nbs += max(mct[2], zeros); ds += max(mc_t[2], zeros);\nbs += max(mct[3], zeros); ds += max(mc_t[3], zeros);\nreturn max(dot(bs, ones), dot(ds, ones)) / 16.0f;\n#else\n#error Invalid method\n#endif\n}\nvoid main()\n{\nvec4 pixel = threadPixel(encodedKeypoints);\nivec2 thread = threadLocation();\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\ncolor = pixel;\nif(address.offset != 1)\nreturn;\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, address);\nif(isBadKeypoint(keypoint))\nreturn;\nvec3 strength = vec3(\ncornerStrength(keypoint.position, max(0.0f, keypoint.lod - lodStep)),\ncornerStrength(keypoint.position, keypoint.lod),\ncornerStrength(keypoint.position, keypoint.lod + lodStep)\n);\nvec3 p = mat3(\n2, -3, 1,\n-4, 4, 0,\n2, -1, 0\n) * strength;\nfloat maxStrength = max(strength.x, max(strength.y, strength.z));\nvec3 diffStrength = abs(strength - vec3(maxStrength));\nvec3 strengthIndicators = vec3(lessThan(diffStrength, vec3(eps)));\nfloat maxPoint = min(1.0f, dot(vec3(0.0f, 0.5f, 1.0f), strengthIndicators));\nbool hasMax = p.x < -eps;\nfloat pmax = hasMax ? -0.5f * p.y / p.x : maxPoint;\nfloat alpha = abs(pmax - 0.5f) <= 0.5f ? pmax : maxPoint;\nfloat lodOffset = mix(-lodStep, lodStep, alpha);\nfloat lod = keypoint.lod + lodOffset;\ncolor.r = encodeLod(lod);\n}"
-
- /***/ }),
-
- /***/ 805:
- /***/ ((module) => {
-
- module.exports = "@include \"float16.glsl\"\nuniform sampler2D corners;\nuniform int iterationNumber;\nvoid main()\n{\nivec2 thread = threadLocation();\nivec2 bounds = outputSize();\nint jump = (1 << iterationNumber);\nint clusterLength = jump << 1;\nint clusterMask = clusterLength - 1;\nivec2 clusterPos = ivec2(thread >> (1 + iterationNumber)) << (1 + iterationNumber);\nivec2 next1 = clusterPos + ((thread - clusterPos + ivec2(jump, 0)) & clusterMask);\nivec2 next2 = clusterPos + ((thread - clusterPos + ivec2(0, jump)) & clusterMask);\nivec2 next3 = clusterPos + ((thread - clusterPos + ivec2(jump, jump)) & clusterMask);\nvec4 p0 = threadPixel(corners);\nvec4 p1 = texelFetch(corners, next1 % bounds, 0);\nvec4 p2 = texelFetch(corners, next2 % bounds, 0);\nvec4 p3 = texelFetch(corners, next3 % bounds, 0);\nfloat s0 = decodeFloat16(p0.rb);\nfloat s1 = decodeFloat16(p1.rb);\nfloat s2 = decodeFloat16(p2.rb);\nfloat s3 = decodeFloat16(p3.rb);\nbool b0 = s0 >= s1 && s0 >= s2 && s0 >= s3;\nbool b1 = s1 >= s0 && s1 >= s2 && s1 >= s3;\nbool b2 = s2 >= s0 && s2 >= s1 && s2 >= s3;\ncolor = vec4(0.0f);\ncolor.rb = b0 ? p0.rb : (\nb1 ? p1.rb : (\nb2 ? p2.rb : p3.rb\n)\n);\n}"
-
- /***/ }),
-
- /***/ 8736:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#if PERMUTATION_MAXLEN % 4 > 0 || PERMUTATION_MAXLEN * 4 > 16384\n#error Invalid PERMUTATION_MAXLEN\n#endif\nlayout(std140) uniform Permutation\n{\nivec4 permutation[PERMUTATION_MAXLEN / 4];\n};\nint permutationElement(int index)\n{\nint base = index - (index % PERMUTATION_MAXLEN);\nint offset = index - base;\nivec4 tuple = permutation[offset / 4];\nint newOffset = tuple[offset & 3];\nreturn base + newOffset;\n}\nvoid main()\n{\nivec2 thread = threadLocation();\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress myAddress = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint myIndex = findKeypointIndex(myAddress, descriptorSize, extraSize);\nint otherIndex = permutationElement(myIndex);\nKeypointAddress otherAddress = KeypointAddress(otherIndex * pixelsPerKeypoint, myAddress.offset);\nKeypoint myKeypoint = decodeKeypoint(encodedKeypoints, encoderLength, myAddress);\nKeypoint otherKeypoint = decodeKeypoint(encodedKeypoints, encoderLength, otherAddress);\ncolor = readKeypointData(encodedKeypoints, encoderLength, otherAddress);\n}"
-
- /***/ }),
-
- /***/ 9311:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n#if !defined(STAGE)\n#error Undefined STAGE\n#elif STAGE == 1\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#elif STAGE == 2\nuniform sampler2D permutation;\nuniform int blockSize;\nuniform int dblLog2BlockSize;\n#elif STAGE == 3\nuniform sampler2D permutation;\nuniform int maxKeypoints;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\n#else\n#error Invalid STAGE\n#endif\nstruct PermutationElement\n{\nint keypointIndex;\nfloat score;\nbool valid;\n};\nvec4 encodePermutationElement(PermutationElement element)\n{\nconst vec2 ONES = vec2(1.0f);\nvec2 encodedScore = element.valid ? encodeFloat16(element.score) : ONES;\nvec2 encodedIndex = vec2(element.keypointIndex & 255, (element.keypointIndex >> 8) & 255) / 255.0f;\nreturn vec4(encodedIndex, encodedScore);\n}\nPermutationElement decodePermutationElement(vec4 pixel)\n{\nconst vec2 ONES = vec2(1.0f);\nPermutationElement element;\nelement.keypointIndex = int(pixel.r * 255.0f) | (int(pixel.g * 255.0f) << 8);\nelement.valid = !all(equal(pixel.ba, ONES));\nelement.score = element.valid ? decodeFloat16(pixel.ba) : -1.0f;\nreturn element;\n}\nPermutationElement readPermutationElement(sampler2D permutation, int elementIndex, int stride, int height)\n{\nconst vec4 INVALID_PIXEL = vec4(1.0f);\nivec2 pos = ivec2(elementIndex % stride, elementIndex / stride);\nvec4 pixel = pos.y < height ? pixelAt(permutation, pos) : INVALID_PIXEL;\nreturn decodePermutationElement(pixel);\n}\n#if STAGE == 2\nPermutationElement selectKth(sampler2D permutation, int k, int la, int ra, int lb, int rb)\n{\nfloat scoreA, scoreB;\nint ha, hb, ma, mb;\nbool discard1stHalf, altb;\nbool locked = false;\nint tmp, result = 0;\nint stride = outputSize().x;\nint height = outputSize().y;\nfor(int i = 0; i < dblLog2BlockSize; i++) {\ntmp = (lb > rb && !locked) ? (la+k) : result;\nresult = (la > ra && !locked) ? (lb+k) : tmp;\nlocked = locked || (la > ra) || (lb > rb);\nha = (ra - la + 1) / 2;\nhb = (rb - lb + 1) / 2;\nma = la + ha;\nmb = lb + hb;\nscoreA = readPermutationElement(permutation, ma, stride, height).score;\nscoreB = readPermutationElement(permutation, mb, stride, height).score;\ndiscard1stHalf = (k > ha + hb);\naltb = (-scoreA < -scoreB);\nk -= int(discard1stHalf && altb) * (ha + 1);\nk -= int(discard1stHalf && !altb) * (hb + 1);\nla += int(discard1stHalf && altb) * (ma + 1 - la);\nlb += int(discard1stHalf && !altb) * (mb + 1 - lb);\nra += int(!discard1stHalf && !altb) * (ma - 1 - ra);\nrb += int(!discard1stHalf && altb) * (mb - 1 - rb);\n}\nreturn readPermutationElement(permutation, result, stride, height);\n}\n#endif\nvoid main()\n{\n#if STAGE == 1\nivec2 thread = threadLocation();\nint stride = outputSize().x;\nint keypointIndex = thread.y * stride + thread.x;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = KeypointAddress(keypointIndex * pixelsPerKeypoint, 0);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, address);\nPermutationElement element;\nelement.keypointIndex = keypointIndex;\nelement.score = keypoint.score;\nelement.valid = !isBadKeypoint(keypoint);\ncolor = encodePermutationElement(element);\n#elif STAGE == 2\nivec2 thread = threadLocation();\nint stride = outputSize().x;\nint elementIndex = thread.y * stride + thread.x;\nint blockIndex = elementIndex / blockSize;\nint blockOffset = elementIndex % blockSize;\nint la = blockIndex * blockSize;\nint lb = la + blockSize / 2;\nint ra = lb - 1;\nint rb = (blockIndex + 1) * blockSize - 1;\nint k = blockOffset;\nPermutationElement element = selectKth(permutation, k, la, ra, lb, rb);\ncolor = encodePermutationElement(element);\n#elif STAGE == 3\nivec2 thread = threadLocation();\nint newEncoderLength = outputSize().x;\nKeypointAddress myAddress = findKeypointAddress(thread, newEncoderLength, descriptorSize, extraSize);\nint myKeypointIndex = findKeypointIndex(myAddress, descriptorSize, extraSize);\nivec2 psize = textureSize(permutation, 0);\nPermutationElement element = readPermutationElement(permutation, myKeypointIndex, psize.x, psize.y);\nint oldEncoderLength = textureSize(encodedKeypoints, 0).x;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = KeypointAddress(element.keypointIndex * pixelsPerKeypoint, myAddress.offset);\nvec4 keypointData = readKeypointData(encodedKeypoints, oldEncoderLength, address);\ncolor = myKeypointIndex < maxKeypoints && element.valid ? keypointData : encodeNullKeypoint();\n#endif\n}"
-
- /***/ }),
-
- /***/ 9423:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n@include \"float16.glsl\"\n#if !defined(METHOD)\n#error Must define METHOD\n#endif\nuniform sampler2D pyramid;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nuniform int maxIterations;\nuniform float epsilon;\nconst int PATCH_RADIUS = 1;\nconst int PATCH_SIZE = 2 * PATCH_RADIUS + 1;\nconst int PATCH_SIZE_SQUARED = PATCH_SIZE * PATCH_SIZE;\nconst int LARGE_PATCH_RADIUS = PATCH_RADIUS + 1;\nconst int LARGE_PATCH_SIZE = 2 * LARGE_PATCH_RADIUS + 1;\nconst int LARGE_PATCH_SIZE_SQUARED = LARGE_PATCH_SIZE * LARGE_PATCH_SIZE;\nconst int LARGER_PATCH_RADIUS = LARGE_PATCH_RADIUS + 1;\nconst int LARGER_PATCH_SIZE = 2 * LARGER_PATCH_RADIUS + 1;\nconst int LARGER_PATCH_SIZE_SQUARED = LARGER_PATCH_SIZE * LARGER_PATCH_SIZE;\nconst float EPS = 1e-5;\nfloat smoothPixelBuffer[LARGER_PATCH_SIZE_SQUARED];\nvec2 derivativesBuffer[LARGE_PATCH_SIZE_SQUARED];\nfloat responseBuffer[PATCH_SIZE_SQUARED];\n#define patchPixelAt(u,v) smoothPixelBuffer[((v) + LARGER_PATCH_RADIUS) * LARGER_PATCH_SIZE + ((u) + LARGER_PATCH_RADIUS)]\n#define derivativesAt(u,v) derivativesBuffer[((v) + LARGE_PATCH_RADIUS) * LARGE_PATCH_SIZE + ((u) + LARGE_PATCH_RADIUS)]\n#define responseAt(u,v) responseBuffer[((v) + PATCH_RADIUS) * PATCH_SIZE + ((u) + PATCH_RADIUS)]\nvoid readPixels(vec2 center, float lod)\n{\nivec2 pyrBaseSize = textureSize(pyramid, 0);\nfloat pot = exp2(lod);\nint u, v;\nfor(int j = 0; j < LARGER_PATCH_SIZE; j++) {\nfor(int i = 0; i < LARGER_PATCH_SIZE; i++) {\nu = i - LARGER_PATCH_RADIUS;\nv = j - LARGER_PATCH_RADIUS;\npatchPixelAt(u,v) = pyrSubpixelAtExOffset(pyramid, center, lod, pot, ivec2(u,v), pyrBaseSize).g;\n}\n}\n}\nvoid computeDerivatives()\n{\nconst mat3 dx = mat3(\n-1, 0, 1,\n-2, 0, 2,\n-1, 0, 1\n);\nconst mat3 dy = mat3(\n1, 2, 1,\n0, 0, 0,\n-1,-2,-1\n);\nint u, v;\nmat3 pix, convX, convY;\nconst vec3 ones = vec3(1.0f);\nfor(int j = 0; j < LARGE_PATCH_SIZE; j++) {\nfor(int i = 0; i < LARGE_PATCH_SIZE; i++) {\nu = i - LARGE_PATCH_RADIUS;\nv = j - LARGE_PATCH_RADIUS;\npix = mat3(\npatchPixelAt(u+1,v+1), patchPixelAt(u+0,v+1), patchPixelAt(u-1,v+1),\npatchPixelAt(u+1,v+0), patchPixelAt(u+0,v+0), patchPixelAt(u-1,v+0),\npatchPixelAt(u+1,v-1), patchPixelAt(u+0,v-1), patchPixelAt(u-1,v-1)\n);\nconvX = matrixCompMult(dx, pix);\nconvY = matrixCompMult(dy, pix);\nderivativesAt(u,v) = vec2(\ndot(ones, vec3(\ndot(convX[0], ones),\ndot(convX[1], ones),\ndot(convX[2], ones)\n)),\ndot(ones, vec3(\ndot(convY[0], ones),\ndot(convY[1], ones),\ndot(convY[2], ones)\n))\n);\n}\n}\n}\nvec2 computeResponseMap()\n{\nfloat patchArea = float(PATCH_SIZE * PATCH_SIZE);\nvec3 h; vec2 d, c = vec2(0.0f);\nconst vec3 ones = vec3(1.0f);\nfloat response, sum = 0.0f;\nint u, v;\n#define H(r,s) d = derivativesAt((r),(s)); h += vec3(d.x * d.x, d.x * d.y, d.y * d.y)\nfor(int j = 0; j < PATCH_SIZE; j++) {\nfor(int i = 0; i < PATCH_SIZE; i++) {\nu = i - PATCH_RADIUS;\nv = j - PATCH_RADIUS;\nh = vec3(0.0f);\nH(u-1,v-1); H(u+0,v-1); H(u+1,v-1);\nH(u-1,v+0); H(u+0,v+0); H(u+1,v+0);\nH(u-1,v+1); H(u+0,v+1); H(u+1,v+1);\nresponse = 0.5f * (h.x + h.z - sqrt((h.x - h.z) * (h.x - h.z) + 4.0f * h.y * h.y));\nresponse /= patchArea;\nresponseAt(u,v) = response;\nc += vec2(u,v) * response;\nsum += response;\n}\n}\nreturn abs(sum) > EPS ? c / sum : vec2(0.0f);\n}\n#if METHOD == 0\nvec2 quadratic1d()\n{\nfloat a = 0.5f * (responseAt(-1,0) - 2.0f * responseAt(0,0) + responseAt(1,0));\nfloat b = 0.5f * (responseAt(1,0) - responseAt(-1,0));\nfloat c = responseAt(0,0);\nfloat d = 0.5f * (responseAt(0,-1) - 2.0f * responseAt(0,0) + responseAt(0,1));\nfloat e = 0.5f * (responseAt(0,1) - responseAt(0,-1));\nfloat f = responseAt(0,0);\nbool hasMax = a < -EPS && d < -EPS;\nreturn hasMax ? -0.5f * vec2(b / a, e / d) : vec2(0.0f);\n}\n#endif\n#if METHOD == 1\nvec2 taylor2d()\n{\nfloat dx = (-responseAt(-1,0) + responseAt(1,0)) * 0.5f;\nfloat dy = (-responseAt(0,-1) + responseAt(0,1)) * 0.5f;\nfloat dxx = responseAt(-1,0) - 2.0f * responseAt(0,0) + responseAt(1,0);\nfloat dyy = responseAt(0,-1) - 2.0f * responseAt(0,0) + responseAt(0,1);\nfloat dxy = (responseAt(-1,-1) + responseAt(1,1) - responseAt(1,-1) - responseAt(-1,1)) * 0.25f;\nfloat det = dxx * dyy - dxy * dxy;\nmat2 inv = mat2(dyy, -dxy, -dxy, dxx);\nbool hasMax = det > EPS && dxx < 0.0f;\nreturn hasMax ? inv * vec2(dx, dy) / (-det) : vec2(0.0f);\n}\n#endif\n#if METHOD == 2\nvoid bilinearUpsample(ivec2 patchOffset, vec4 pixelsOfPatch)\n{\nint u, v, i, j;\nvec2 frc, ifrc; vec4 sub;\nconst vec4 ones = vec4(1.0f);\nfloat s = 1.0f / float(PATCH_SIZE - 1);\nint xoff = 2 * patchOffset.x;\nint yoff = 2 * patchOffset.y;\nfor(j = 0; j < PATCH_SIZE; j++) {\nfor(i = 0; i < PATCH_SIZE; i++) {\nu = i - PATCH_RADIUS;\nv = j - PATCH_RADIUS;\nfrc = vec2(i, j) * s;\nifrc = vec2(1.0f) - frc;\nsub = vec4(\nifrc.x * ifrc.y,\nfrc.x * ifrc.y,\nifrc.x * frc.y,\nfrc.x * frc.y\n);\npatchPixelAt(u+xoff,v+yoff) = dot(sub*pixelsOfPatch, ones);\n}\n}\n}\n#endif\n#if METHOD == 3\nvoid bicubicUpsample(ivec2 patchOffset, vec4 pixelsOfPatch, vec4 dx, vec4 dy, vec4 dxy)\n{\nfloat x, y, s = 1.0f / float(PATCH_SIZE - 1);\nint u, v, i, j;\nfloat f00 = pixelsOfPatch.x;\nfloat f10 = pixelsOfPatch.y;\nfloat f01 = pixelsOfPatch.z;\nfloat f11 = pixelsOfPatch.w;\nfloat fx00 = dx.x;\nfloat fx10 = dx.y;\nfloat fx01 = dx.z;\nfloat fx11 = dx.w;\nfloat fy00 = dy.x;\nfloat fy10 = dy.y;\nfloat fy01 = dy.z;\nfloat fy11 = dy.w;\nfloat fxy00 = dxy.x;\nfloat fxy10 = dxy.y;\nfloat fxy01 = dxy.z;\nfloat fxy11 = dxy.w;\nmat4 bicubic = mat4(\n1, 0, -3, 2,\n0, 0, 3, -2,\n0, 1, -2, 1,\n0, 0, -1, 1\n) * mat4(\nf00, f10, fx00, fx10,\nf01, f11, fx01, fx11,\nfy00, fy10, fxy00, fxy10,\nfy01, fy11, fxy01, fxy11\n) * mat4(\n1, 0, 0, 0,\n0, 0, 1, 0,\n-3, 3, -2, -1,\n2, -2, 1, 1\n);\nint xoff = 2 * patchOffset.x;\nint yoff = 2 * patchOffset.y;\nfor(j = 0; j < PATCH_SIZE; j++) {\nfor(i = 0; i < PATCH_SIZE; i++) {\nu = i - PATCH_RADIUS;\nv = j - PATCH_RADIUS;\nx = float(i) * s;\ny = float(j) * s;\npatchPixelAt(u+xoff,v+yoff) = dot(\nvec4(1, x, x*x, x*x*x),\nbicubic * vec4(1, y, y*y, y*y*y)\n);\n}\n}\n}\n#endif\n#if METHOD == 2 || METHOD == 3\nvoid upsamplePatch(int left, int top, int right, int bottom)\n{\nint x, y, k;\nvec4 ptch[9];\nvec2 d00, d10, d01, d11;\nfor(k = 0; k < 9; k++) {\nx = -1 + (k % 3);\ny = -1 + (k / 3);\nptch[k] = vec4(\npatchPixelAt(left+x, top+y),\npatchPixelAt(right+x, top+y),\npatchPixelAt(left+x, bottom+y),\npatchPixelAt(right+x, bottom+y)\n);\n}\nfor(k = 0; k < 9; k++) {\nx = -1 + (k % 3);\ny = -1 + (k / 3);\n#if METHOD == 2\nbilinearUpsample(ivec2(x, y), ptch[k]);\n#elif METHOD == 3\nd00 = derivativesAt(left+x, top+y);\nd10 = derivativesAt(right+x, top+y);\nd01 = derivativesAt(left+x, bottom+y);\nd11 = derivativesAt(right+x, bottom+y);\nbicubicUpsample(ivec2(x, y), ptch[k],\nvec4(d00.x, d10.x, d01.x, d11.x),\nvec4(d00.y, d10.y, d01.y, d11.y),\n0.25f * vec4(\n(patchPixelAt(left+x + 1,top+y + 1) + patchPixelAt(left+x - 1, top+y - 1)) - (patchPixelAt(left+x + 1, top+y - 1) + patchPixelAt(left+x - 1, top+y + 1)),\n(patchPixelAt(right+x + 1,top+y + 1) + patchPixelAt(right+x - 1, top+y - 1)) - (patchPixelAt(right+x + 1, top+y - 1) + patchPixelAt(right+x - 1, top+y + 1)),\n(patchPixelAt(left+x + 1,bottom+y + 1) + patchPixelAt(left+x - 1, bottom+y - 1)) - (patchPixelAt(left+x + 1, bottom+y - 1) + patchPixelAt(left+x - 1, bottom+y + 1)),\n(patchPixelAt(right+x + 1,bottom+y + 1) + patchPixelAt(right+x - 1, bottom+y - 1)) - (patchPixelAt(right+x + 1, bottom+y - 1) + patchPixelAt(right+x - 1, bottom+y + 1))\n)\n);\n#endif\n}\n}\nvec2 upsampleResponseMap(int left, int top, int right, int bottom)\n{\nupsamplePatch(left, top, right, bottom);\ncomputeDerivatives();\nreturn computeResponseMap();\n}\nvec2 iterativeUpsample(vec2 initialGuess)\n{\nint refine = 1;\nfloat scale = 0.5f;\nfloat eps2 = epsilon * epsilon;\nvec2 guess = initialGuess, localGuess = initialGuess;\nfor(int k = 0; k < maxIterations; k++) {\nivec4 quad = ivec4(floor(localGuess.x), floor(localGuess.y), ceil(localGuess.x), ceil(localGuess.y));\nvec2 response = (refine != 0) ? upsampleResponseMap(quad.x, quad.y, quad.z, quad.w) : vec2(0.0f);\nlocalGuess = response * scale;\nguess += localGuess;\nscale *= 0.5f;\nrefine *= int(dot(localGuess, localGuess) >= eps2);\n}\nreturn guess;\n}\n#endif\nvoid main()\n{\nivec2 thread = threadLocation();\nint keypointIndex = thread.x + thread.y * outputSize().x;\nint pixelsPerKeypoint = sizeofEncodedKeypoint(descriptorSize, extraSize) / 4;\nKeypointAddress address = KeypointAddress(keypointIndex * pixelsPerKeypoint, 0);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, address);\ncolor = encodeNullPairOfFloat16();\nif(isNullKeypoint(keypoint))\nreturn;\ncolor = encodeDiscardedPairOfFloat16();\nif(isBadKeypoint(keypoint))\nreturn;\nreadPixels(keypoint.position, keypoint.lod);\ncomputeDerivatives();\nvec2 offset = computeResponseMap();\n#if METHOD == 0\noffset = quadratic1d();\n#elif METHOD == 1\noffset = taylor2d();\n#elif METHOD == 2 || METHOD == 3\noffset = iterativeUpsample(offset);\n#else\n#error Unknown METHOD\n#endif\nfloat pot = exp2(keypoint.lod);\ncolor = encodePairOfFloat16(offset * pot);\n}"
-
- /***/ }),
-
- /***/ 2060:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\n@include \"float16.glsl\"\nuniform sampler2D encodedFlow;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nvoid main()\n{\nvec4 pixel = threadPixel(encodedKeypoints);\nivec2 thread = threadLocation();\nint len = textureSize(encodedFlow, 0).x;\nKeypointAddress myAddress = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, myAddress);\nint myIndex = findKeypointIndex(myAddress, descriptorSize, extraSize);\ncolor = pixel;\nif(isBadKeypoint(keypoint))\nreturn;\nivec2 location = ivec2(myIndex % len, myIndex / len);\nvec4 encodedFlow = myIndex < len * len ? pixelAt(encodedFlow, location) : encodeDiscardedKeypoint();\nbool discardFlow = isDiscardedPairOfFloat16(encodedFlow);\nvec2 flow = !discardFlow ? decodePairOfFloat16(encodedFlow) : vec2(0.0f);\nvec4 newPosition = encodeKeypointPosition(keypoint.position + flow);\nvec4 newPixel = myAddress.offset == 0 ? newPosition : pixel;\ncolor = !discardFlow ? newPixel : encodeDiscardedKeypoint();\n}"
-
- /***/ }),
-
- /***/ 5463:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D encodedOrientations;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nvoid main()\n{\nvec4 pixel = threadPixel(encodedKeypoints);\nivec2 thread = threadLocation();\nKeypointAddress myAddress = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint myIndex = findKeypointIndex(myAddress, descriptorSize, extraSize);\nint orientationEncoderLength = textureSize(encodedOrientations, 0).x;\nivec2 location = ivec2(myIndex % orientationEncoderLength, myIndex / orientationEncoderLength);\nvec4 targetPixel = pixelAt(encodedOrientations, location);\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, myAddress);\nbool isValid = !isBadKeypoint(keypoint);\nfloat encodedOrientation = targetPixel.g;\ncolor = isValid && myAddress.offset == 1 ? vec4(pixel.r, encodedOrientation, pixel.ba) : pixel;\n}"
-
- /***/ }),
-
- /***/ 6986:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D encodedData;\nuniform int strideOfEncodedData;\nuniform sampler2D encodedKeypoints;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\nvec4 readEncodedData(sampler2D encodedData, int strideOfEncodedData, int elementId, int pixelsPerElement, int pixelOffset)\n{\nint rasterIndex = elementId * pixelsPerElement + pixelOffset;\nivec2 pos = ivec2(rasterIndex % strideOfEncodedData, rasterIndex / strideOfEncodedData);\nreturn texelFetch(encodedData, pos, 0);\n}\nvoid main()\n{\nivec2 thread = threadLocation();\nKeypointAddress myAddress = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint myIndex = findKeypointIndex(myAddress, descriptorSize, extraSize);\nint headerSize = sizeofEncodedKeypointHeader();\nint extraCell = myAddress.offset - headerSize / 4;\nint numberOfExtraCells = extraSize / 4;\ncolor = threadPixel(encodedKeypoints);\nif(extraCell < 0 || extraCell >= numberOfExtraCells)\nreturn;\nKeypoint keypoint = decodeKeypoint(encodedKeypoints, encoderLength, myAddress);\nif(isBadKeypoint(keypoint))\nreturn;\ncolor = readEncodedData(encodedData, strideOfEncodedData, myIndex, numberOfExtraCells, extraCell);\n}"
-
- /***/ }),
-
- /***/ 3179:
- /***/ ((module) => {
-
- module.exports = "@include \"keypoints.glsl\"\nuniform sampler2D encodedKeypoints;\nuniform int startIndex;\nuniform int endIndex;\nuniform int descriptorSize;\nuniform int extraSize;\nuniform int encoderLength;\n#ifndef BUFFER_SIZE\n#error Undefined BUFFER_SIZE\n#endif\nlayout(std140) uniform KeypointBuffer\n{\nvec4 keypointBuffer[BUFFER_SIZE];\n};\nvoid main()\n{\nvec4 pixel = threadPixel(encodedKeypoints);\nivec2 thread = threadLocation();\nKeypointAddress address = findKeypointAddress(thread, encoderLength, descriptorSize, extraSize);\nint index = findKeypointIndex(address, descriptorSize, extraSize);\ncolor = pixel;\nif(index < startIndex)\nreturn;\ncolor = encodeNullKeypoint();\nif(index >= endIndex)\nreturn;\nvec4 data = keypointBuffer[index - startIndex];\nswitch(address.offset) {\ncase 0: {\ncolor = encodeKeypointPosition(data.xy);\nbreak;\n}\ncase 1: {\nvec2 score = encodeKeypointScore(max(data.w, 0.0f));\nfloat scale = encodeLod(data.z);\nfloat rotation = encodeKeypointOrientation(0.0f);\ncolor = vec4(scale, rotation, score);\nbreak;\n}\ndefault: {\ncolor = vec4(0.0f);\nbreak;\n}\n}\n}"
-
- /***/ }),
-
- /***/ 8680:
- /***/ ((module) => {
-
- module.exports = "uniform sampler2D image;\nvoid main()\n{\n#if 1\ncolor = texture(image, texCoord);\n#else\nivec2 thread = threadLocation();\nivec2 pos = min(thread * 2, textureSize(image, 0) - ivec2(1));\ncolor = pixelAt(image, pos);\n#endif\n}"
-
- /***/ }),
-
- /***/ 3384:
- /***/ ((module) => {
-
- module.exports = "uniform sampler2D image;\nvoid main()\n{\nivec2 thread = threadLocation();\nvec4 pixel = pixelAt(image, thread / 2);\ncolor = (((thread.x + thread.y) & 1) == 0) ? pixel : vec4(0.0f, 0.0f, 0.0f, pixel.a);\n}"
-
- /***/ }),
-
- /***/ 1976:
- /***/ ((module) => {
-
- module.exports = "@include \"subpixel.glsl\"\nuniform sampler2D image0;\nuniform sampler2D image1;\nuniform float alpha;\nuniform float beta;\nuniform float gamma;\nconst vec4 BACKGROUND = vec4(0.0f);\nvoid main()\n{\nivec2 location = threadLocation();\nivec2 size0 = textureSize(image0, 0);\nivec2 size1 = textureSize(image1, 0);\nvec4 pix0 = all(lessThan(location, size0)) ? pixelAt(image0, location) : BACKGROUND;\nvec4 pix1 = all(lessThan(location, size1)) ? pixelAt(image1, location) : BACKGROUND;\nvec4 pix = clamp(alpha * pix0 + beta * pix1 + vec4(gamma), 0.0f, 1.0f);\ncolor = vec4(pix.rgb, 1.0f);\n}"
-
- /***/ }),
-
- /***/ 4543:
- /***/ ((module) => {
-
- module.exports = "@include \"subpixel.glsl\"\nuniform sampler2D image;\nvoid main()\n{\nvec2 imageSize = vec2(textureSize(image, 0));\n#if !defined(INTERPOLATION_METHOD)\n#error Must define INTERPOLATION_METHOD\n#elif INTERPOLATION_METHOD == 0\nvec2 pos = texCoord * imageSize;\ncolor = textureLod(image, (round(pos) + vec2(0.5f)) / imageSize, 0.0f);\n#elif INTERPOLATION_METHOD == 1\ncolor = subpixelAtBI(image, texCoord * imageSize);\n#else\n#error Invalid INTERPOLATION_METHOD\n#endif\n}"
-
- /***/ }),
-
- /***/ 6296:
- /***/ ((module) => {
-
- module.exports = "@include \"subpixel.glsl\"\nuniform sampler2D image;\nuniform mat3 inverseHomography;\nconst vec4 emptyColor = vec4(0.0f, 0.0f, 0.0f, 1.0f);\nvec2 perspectiveWarp(mat3 homography, vec2 p)\n{\nvec3 q = homography * vec3(p, 1.0f);\nreturn q.xy / q.z;\n}\nvoid main()\n{\nivec2 location = threadLocation();\nivec2 size = outputSize();\nconst vec2 zero = vec2(0.0f);\nvec2 target = perspectiveWarp(inverseHomography, vec2(location));\nbool withinBounds = all(bvec4(greaterThanEqual(target, zero), lessThan(target, vec2(size))));\ncolor = withinBounds ? subpixelAtBI(image, target) : emptyColor;\n}"
-
- /***/ }),
-
- /***/ 747:
- /***/ ((module) => {
-
- module.exports = "@include \"colors.glsl\"\nuniform sampler2D dest, src;\nuniform int destComponents;\nuniform int srcComponentId;\nvoid main()\n{\nvec4 destPixel = threadPixel(dest);\nvec4 srcPixel = threadPixel(src);\nbvec4 flags = bvec4(\n(destComponents & PIXELCOMPONENT_RED) != 0,\n(destComponents & PIXELCOMPONENT_GREEN) != 0,\n(destComponents & PIXELCOMPONENT_BLUE) != 0,\n(destComponents & PIXELCOMPONENT_ALPHA) != 0\n);\ncolor = mix(destPixel, vec4(srcPixel[srcComponentId]), flags);\n}"
-
- /***/ }),
-
- /***/ 9176:
- /***/ ((module) => {
-
- module.exports = "#if !defined(TYPE)\n#error Undefined TYPE\n#elif TYPE == 1\n@include \"keypoints.glsl\"\n#define nullPixel() encodeNullKeypoint()\n#elif TYPE == 2\n@include \"float16.glsl\"\n#define nullPixel() encodeNullPairOfFloat16()\n#else\n#error Invalid TYPE\n#endif\nuniform sampler2D image;\nvoid main()\n{\nivec2 thread = threadLocation();\nivec2 imageSize = textureSize(image, 0);\nint rasterIndex = thread.y * outputSize().x + thread.x;\nbool isValidPixel = rasterIndex < imageSize.x * imageSize.y;\nivec2 pos = ivec2(rasterIndex % imageSize.x, rasterIndex / imageSize.x);\nvec4 nullpix = nullPixel();\ncolor = isValidPixel ? texelFetch(image, pos, 0) : nullpix;\n}"
-
- /***/ }),
-
- /***/ 8960:
- /***/ ((module) => {
-
- module.exports = "uniform sampler2D image;\nvoid main()\n{\ncolor = threadPixel(image);\n}"
-
- /***/ }),
-
- /***/ 3294:
- /***/ ((module) => {
-
- module.exports = "@include \"colors.glsl\"\nuniform sampler2D image;\nuniform int pixelComponents;\nuniform float value;\nvoid main()\n{\nvec4 pixel = threadPixel(image);\nbvec4 flags = bvec4(\n(pixelComponents & PIXELCOMPONENT_RED) != 0,\n(pixelComponents & PIXELCOMPONENT_GREEN) != 0,\n(pixelComponents & PIXELCOMPONENT_BLUE) != 0,\n(pixelComponents & PIXELCOMPONENT_ALPHA) != 0\n);\ncolor = mix(pixel, vec4(value), flags);\n}"
-
- /***/ }),
-
- /***/ 1959:
- /***/ ((module) => {
-
- module.exports = "uniform float value;\nvoid main()\n{\ncolor = vec4(value);\n}"
-
- /***/ }),
-
- /***/ 7290:
- /***/ ((module) => {
-
- module.exports = "void vsmain()\n{\ngl_Position *= vec4(1,-1,1,1);\n}"
-
- /***/ }),
-
- /***/ 7270:
- /***/ ((module) => {
-
- module.exports = "uniform sampler2D image;\nuniform int iterationNumber;\nvoid main()\n{\nivec2 thread = threadLocation();\nivec2 last = outputSize() - ivec2(1);\nint jump = (1 << iterationNumber);\nint clusterLength = jump << 1;\nint clusterMask = clusterLength - 1;\nivec2 clusterPos = ivec2(thread >> (1 + iterationNumber)) << (1 + iterationNumber);\nivec2 next1 = clusterPos + ((thread - clusterPos + ivec2(jump, 0)) & clusterMask);\nivec2 next2 = clusterPos + ((thread - clusterPos + ivec2(0, jump)) & clusterMask);\nivec2 next3 = clusterPos + ((thread - clusterPos + ivec2(jump, jump)) & clusterMask);\nvec4 p0 = texelFetch(image, thread, 0);\nvec4 p1 = texelFetch(image, min(next1, last), 0);\nvec4 p2 = texelFetch(image, min(next2, last), 0);\nvec4 p3 = texelFetch(image, min(next3, last), 0);\nvec4 pmax = max(max(p0, p1), max(p2, p3));\nvec4 pmin = min(min(p0, p1), min(p2, p3));\ncolor = vec4(pmax.r, pmin.g, pmax.r - pmin.g, p0.a);\n}"
-
- /***/ }),
-
- /***/ 48:
- /***/ ((module) => {
-
- module.exports = "@include \"pyramids.glsl\"\n@include \"float16.glsl\"\nuniform sampler2D pyramid;\nuniform float lod;\n#define USE_VARYINGS 1\nin vec2 v_pix0, v_pix1, v_pix2,\nv_pix3, v_pix4, v_pix5,\nv_pix6, v_pix7, v_pix8;\nconst mat3 hkern = mat3(\n1.0f, 0.0f,-1.0f,\n2.0f, 0.0f,-2.0f,\n1.0f, 0.0f,-1.0f\n), vkern = mat3(\n1.0f, 2.0f, 1.0f,\n0.0f, 0.0f, 0.0f,\n-1.0f,-2.0f,-1.0f\n);\n#define PIX(x,y) pyrPixelAtOffset(pyramid, lod, pot, ivec2((x),(y))).g\n#define XIP(v) textureLod(pyramid, (v), lod).g\nvoid main()\n{\nconst vec3 ones = vec3(1.0f);\nfloat pot = exp2(lod);\nmat3 win = mat3(\n#if USE_VARYINGS\nXIP(v_pix0), XIP(v_pix1), XIP(v_pix2),\nXIP(v_pix3), XIP(v_pix4), XIP(v_pix5),\nXIP(v_pix6), XIP(v_pix7), XIP(v_pix8)\n#else\nPIX(-1,-1), PIX(0,-1), PIX(1,-1),\nPIX(-1,0), PIX(0,0), PIX(1,0),\nPIX(-1,1), PIX(0,1), PIX(1,1)\n#endif\n);\nmat3 dx = matrixCompMult(hkern, win);\nmat3 dy = matrixCompMult(vkern, win);\nvec2 df = vec2(\ndot(dx[0] + dx[1] + dx[2], ones),\ndot(dy[0] + dy[1] + dy[2], ones)\n);\ncolor = encodePairOfFloat16(df);\n}"
-
- /***/ }),
-
- /***/ 3713:
- /***/ ((module) => {
-
- module.exports = "uniform mediump float lod;\nout vec2 v_pix0, v_pix1, v_pix2,\nv_pix3, v_pix4, v_pix5,\nv_pix6, v_pix7, v_pix8;\n#define PIX(x,y) (texCoord + ((pot) * vec2((x),(y))) / texSize)\nvoid vsmain()\n{\nfloat pot = exp2(lod);\nv_pix0 = PIX(-1,-1); v_pix1 = PIX(0,-1); v_pix2 = PIX(1,-1);\nv_pix3 = PIX(-1,0); v_pix4 = PIX(0,0); v_pix5 = PIX(1,0);\nv_pix6 = PIX(-1,1); v_pix7 = PIX(0,1); v_pix8 = PIX(1,1);\n}"
-
- /***/ }),
-
- /***/ 4209:
- /***/ ((module) => {
-
- module.exports = `AGFzbQEAAAABiwETYAABfmADf39/AX9gAX8AYAN/f38AYAF9AX9gAX8Bf2ACf38Bf2AFf39/f38B
- f2AFf39/f38AYAZ/f39/f38Bf2AAAX9gAn99AX9gA39/fQF/YAJ/fwF9YAF/AX1gBH9/f38AYAR/
- f39/AX9gEX98fHx8fHx8fHx8fHx8fHx8AGAHf39/f39/fQF/AjsEA2VudgZtZW1vcnkCAAIDZW52
- BWZhdGFsAAIDZW52CGJ5dGVmaWxsAAMDZW52CmNvcHlXaXRoaW4AAwNAPwQFBgIGAQECBwgGAwAJ
- AgYCBgYKBQUFCQsFBgEBDAEBBgYGAQEMAQ0OAwgPAxAIAwYBEQEBAQEBARIBEgEBDwQFAXABBQUG
- CAF/AUHwmgQLB/QDHAZtYWxsb2MABARmcmVlAAYFc3JhbmQACgxNYXQzMl9jcmVhdGUAEA1NYXQz
- Ml9kZXN0cm95ABcKTWF0MzJfZGF0YQAYDk1hdDMyX2RhdGFTaXplABkPTWF0MzJfdHJhbnNwb3Nl
- AB0JTWF0MzJfYWRkAB4OTWF0MzJfc3VidHJhY3QAHwtNYXQzMl9zY2FsZQAgDk1hdDMyX2NvbXBt
- dWx0ACEOTWF0MzJfbXVsdGlwbHkAIg5NYXQzMl9pbnZlcnNlMQAjDk1hdDMyX2ludmVyc2UyACQO
- TWF0MzJfaW52ZXJzZTMAJQ1NYXQzMl9xcl9mdWxsACwQTWF0MzJfcXJfcmVkdWNlZAAvDE1hdDMy
- X3FyX29scwAwEE1hdDMyX3FyX2ludmVyc2UAMxZNYXQzMl9ob21vZ3JhcGh5X25kbHQ0ADcVTWF0
- MzJfaG9tb2dyYXBoeV9uZGx0ADgUTWF0MzJfYWZmaW5lX2RpcmVjdDMAOhNNYXQzMl9hZmZpbmVf
- ZGlyZWN0ADsYTWF0MzJfcHJhbnNhY19ob21vZ3JhcGh5ADwUTWF0MzJfcHJhbnNhY19hZmZpbmUA
- PhtNYXQzMl90cmFuc2Zvcm1fcGVyc3BlY3RpdmUAPxZNYXQzMl90cmFuc2Zvcm1fYWZmaW5lAEAJ
- CgEAQQELBA8REz0Kh7oBPyMBAX8gALwiAUGAgID8B3FBgICA/AdGIAFB////A3FBAEdxC2kBAX9B
- AEEAKALAmoCAAEEBajYCwJqAgABBAEEAKAK0moCAACIBQQdxIAFqIgEgAGo2ArSagIAAAkBB8JqE
- gABBB3EgAWpB8JqEgABqIgA/AEEQdEkNAEGEiICAABCAgICAAEEADwsgAAt1AQJ/QQAhAkEAQQAo
- AsCagIAAQQFqNgLAmoCAAEEAQQAoArSagIAAIgNBB3EgA2oiAyAAajYCtJqAgAACQAJAQfCahIAA
- QQdxIANqQfCahIAAaiIAPwBBEHRJDQAgAUUNASABEICAgIAAQQAPCyAAIQILIAILRgECf0EAQQAo
- AsCagIAAIgFBf2oiAjYCwJqAgAACQCACDQBBAEEINgK0moCAAA8LAkAgAUEASg0AQZOIgIAAEICA
- gIAACwtGAQJ/QQBBACgCwJqAgAAiAkF/aiIDNgLAmoCAAAJAIAMNAEEAQQg2ArSagIAAQQAPCwJA
- IAJBAEoNACABEICAgIAAC0EACxcAIAFB/wFxIAAgACACahCBgICAACAACxMAIAAgASABIAJqEIKA
- gIAAIAALoQECAX8CfkEAKAK4moCAACIBIACtQiCGIABBf3OthCICQqrw0/Sv7ry3PHwiA0IeiCAD
- hUK5y5Pn0e2RrL9/fiIDQhuIIAOFQuujxJmxt5LolH9+IgNCH4ggA4U3AwggASACQpX4qfqXt96b
- nn98IgJCHoggAoVCucuT59Htkay/f34iAkIbiCAChULro8SZsbeS6JR/fiICQh+IIAKFNwMAC0QB
- AX9B3oG33QAhBQJAIAJFDQAgAEUNACADRQ0AQQAhBSABQQJJDQAgACAAIAFBf2ogAmxqIAIgAyAE
- EIyAgIAACyAFC60GAwR/AXwFfwJAAkAgASAASw0AIAEhBSAAIQYMAQtBACACayEHIAJBBEshCANA
- IAEiBSAAIgZrIAJuIgFBCEkNAQJAAkBBACgCvJqAgAARgICAgAAAQgyIQoCAgICAgID4P4S/RAAA
- AAAAAPC/oCABQQFquKIiCUQAAAAAAADwQWMgCUQAAAAAAAAAAGZxRQ0AIAmrIQEMAQtBACEBCyAG
- IAEgAmxqIQogBSEBIAYhCwNAAkAgCyAKIAQgAxGBgICAAABBf0oNAANAIAsgAmoiCyAKIAQgAxGB
- gICAAABBAEgNAAsLAkAgASAKIAQgAxGBgICAAABBAUgNAANAIAEgB2oiASAKIAQgAxGBgICAAABB
- AEoNAAsLAkAgCyABTw0AIAEhACALIQwgAiENAkACQCAIDQACQAJAIAIOBQMBAQEAAwsgCygCACEA
- IAsgASgCADYCACABIAA2AgAMAgsgASEAIAshDCACIQ0LA0AgDC0AACEOIAwgAC0AADoAACAAIA46
- AAAgAEEBaiEAIAxBAWohDCANQX9qIg0NAAsLIAEgCyAKIAogAUYbIAogC0YbIQogASAHaiEBIAsg
- AmohCwwBCwsgCyACaiALIAsgAUYiABshDAJAAkAgASAHaiABIAAbIgEgBk0NACAMIAVPDQACQCAB
- IAZrIAUgDGtNDQAgDCAFIAIgAyAEEIyAgIAAIAYhAAwCCyAGIAEgAiADIAQQjICAgAAgBSEBIAwh
- AAwBCyAGIAwgASAGSyIKGyEAIAEgBSAKGyEBIAoNACAMIAVPDQILIAEhBSAAIQYgASAASw0ACwsC
- QCAGIAVPDQAgAkEESyEHA0AgBiINIAJqIgYhASANIQACQCAGIAVLDQADQCABIAAgASAAIAQgAxGB
- gICAAABBAEgbIQAgASACaiIBIAVNDQALIAAgDUYNAAJAIAcNAAJAIAIOBQIBAQEAAgsgACgCACEB
- IAAgDSgCADYCACANIAE2AgAMAQtBACEBA0AgACABaiIMLQAAIQogDCANIAFqIgstAAA6AAAgCyAK
- OgAAIAIgAUEBaiIBRw0ACwsgBiAFSQ0ACwsLNQECfwJAIAFBAUgNAEEAIQIgACEDA0AgAyACNgIA
- IANBBGohAyABIAJBAWoiAkcNAAsLIAALvgIFAn8BfAF/AXwEfwJAIAFBf2oiA0UNACACQQRLIQRE
- AAAAAAAAAAAhBUEAIQYDQAJAAkBBACgCvJqAgAARgICAgAAAQgyIQoCAgICAgID4P4S/RAAAAAAA
- APC/oCABIAZruKIgBaAiB0QAAAAAAADwQWMgB0QAAAAAAAAAAGZxRQ0AIAerIQgMAQtBACEICwJA
- IAYgCEYNAAJAIAQNAAJAIAIOBQIBAQEAAgsgACAGQQJ0aiIJKAIAIQogCSAAIAhBAnRqIggoAgA2
- AgAgCCAKNgIADAELIAAgBiACbGohCSAAIAggAmxqIQggAiEKA0AgCS0AACELIAkgCC0AADoAACAI
- IAs6AAAgCEEBaiEIIAlBAWohCSAKQX9qIgoNAAsLIAVEAAAAAAAA8D+gIQUgBkEBaiIGIANHDQAL
- CwtFAQN+QQBBACkD2JqAgAAiAEEAKQPQmoCAACIBhSICQiWJNwPYmoCAAEEAIAFCGIkgAoUgAkIQ
- hoU3A9CagIAAIAAgAXwLlAEBAX8CQAJAIAMgAkgNACAAQQFIDQAgAUEBSA0AIAJBAUgNACAAQX9q
- IAJsIAFBf2ogA2xqQQFqIARHDQAgBQ0BC0GfiICAABCAgICAAAtBHEG+iICAABCFgICAACIGIAM2
- AhQgBiACNgIQIAYgATYCDCAGIAA2AgggBiAENgIEIAZBgoCAgAA2AhggBiAFNgIAIAYLAgALkwEB
- BH8CQAJAIABBAUgNACABQQBKDQELQdqIgIAAEICAgIAAC0EcQfmIgIAAEIWAgIAAIQIgASAAbCID
- QQJ0IgRBlYmAgAAQhYCAgAAhBSACIAA2AhQgAkEBNgIQIAIgATYCDCACIAA2AgggAiADNgIEIAVB
- ACAEEIiAgIAAIQAgAkGDgICAADYCGCACIAA2AgAgAgsRACAAQeeKgIAAEIeAgIAAGgv0AQEEfwJA
- AkAgAEEBSA0AIAFBAEoNAQtB2oiAgAAQgICAgAALQRxB+YiAgAAQhYCAgAAhAiABIABsIgNBAnQi
- BEGViYCAABCFgICAACEFIAIgADYCFCACQQE2AhAgAiABNgIMIAIgADYCCCACIAM2AgQgBUEAIAQQ
- iICAgAAhAyACQYOAgIAANgIYIAIgAzYCAAJAIAAgASAAIAFIGyIBQQFIDQAgAyACKAIUIAIoAhBq
- IgQgAUF/amxBAnRqIQAgAUEBaiEBQQAgBEECdGshAwNAIABBgICA/AM2AgAgACADaiEAIAFBf2oi
- AUEBSg0ACwsgAguYAgEKfwJAAkAgACgCCCABKAIIRw0AIAAoAgwgASgCDEYNAQtBx4qAgAAQgICA
- gAALAkACQCAAKAIEIgIgASgCBEYNACAAKAIMIgNBAUgNAUEAIQQgACgCCCIFQQFIIQZBACEHA0AC
- QCAGDQAgACgCEEECdCEIIAEoAhBBAnQhCSAAKAIAIAAoAhQgBGxqIQIgASgCACABKAIUIARsaiEK
- QQAhCwNAIAIgCigCADYCACACIAhqIQIgCiAJaiEKIAtBAWoiCyAFSA0ACwsgBEEEaiEEIAdBAWoi
- ByADSA0ADAILCwJAIAEoAgAiCiAAKAIAIgsgAkECdCICak8NACAKIAJqIAtLDQELIAsgCiACEImA
- gIAAGgsgAAtVAQF/QRxBsYmAgAAQhYCAgAAiAEEYakEAKALoiYCAADYCACAAQRBqQQApAuCJgIAA
- NwIAIABBCGpBACkC2ImAgAA3AgAgAEEAKQLQiYCAADcCACAACyEAIAAoAgAgACgCGBGCgICAAAAg
- AEHsiYCAABCHgICAAAsHACAAKAIACwoAIAAoAgRBAnQL0AEBAn8CQCAAKAIYQYKAgIAARg0AQYeK
- gIAAEICAgIAACwJAAkAgAyACSA0AIAJBAEgNACAFIARIDQAgBEEASA0AIAEoAgggA0wNACABKAIM
- IAVKDQELQaeKgIAAEICAgIAACyABKAIQIQYgAEEUaiABQRRqKAIAIgc2AgAgACAGNgIQIAAgBSAE
- a0EBajYCDCAAIAMgAmtBAWo2AgggACAGIANsIAcgBWxqIAcgBGwgBiACbGoiAmtBAWo2AgQgACAB
- KAIAIAJBAnRqNgIAIAALgQEBCH8CQCAAKAIMIgJBAUgNAEEAIQMgACgCCCIEQQFIIQVBACEGA0AC
- QCAFDQAgACgCEEECdCEHIAAoAgAgACgCFCADbGohCEEAIQkDQCAIIAE4AgAgCCAHaiEIIAlBAWoi
- CSAESA0ACwsgA0EEaiEDIAZBAWoiBiACSA0ACwsgAAumAQEIfwJAIAAoAgwiASAAKAIIIgJsIgMg
- ACgCBEcNACAAKAIAQQAgA0ECdBCIgICAABogAA8LAkAgAUEBSA0AIAJBAUghBEEAIQVBACEGA0AC
- QCAEDQAgACgCEEECdCEHIAAoAgAgACgCFCAFbGohAyACIQgDQCADQQA2AgAgAyAHaiEDIAhBf2oi
- CA0ACwsgBUEEaiEFIAZBAWoiBiABRw0ACwsgAAvcAQEKfwJAAkAgACgCCCABKAIMRw0AIAAoAgwi
- AiABKAIIRg0BC0GBi4CAABCAgICAACAAKAIMIQILAkAgAkEBSA0AIAAoAgwhA0EAIQQgACgCCCIF
- QQFIIQZBACEHA0ACQCAGDQAgACgCEEECdCEIIAEoAhRBAnQhCSAAKAIAIAAoAhQgBGxqIQIgASgC
- ACABKAIQIARsaiEKQQAhCwNAIAIgCigCADYCACACIAhqIQIgCiAJaiEKIAtBAWoiCyAFSA0ACwsg
- BEEEaiEEIAdBAWoiByADSA0ACwsgAAuZAgEMfwJAAkAgASgCCCIDIAIoAghHDQAgASgCDCIEIAIo
- AgxHDQAgACgCCCADRw0AIAAoAgwgBEYNAQtBp4uAgAAQgICAgAAgACgCDCEECwJAIARBAUgNACAA
- KAIMIQVBACEGIAAoAggiB0EBSCEIQQAhCQNAAkAgCA0AIAAoAhBBAnQhCiACKAIQQQJ0IQsgASgC
- EEECdCEMIAAoAgAgACgCFCAGbGohBCACKAIAIAIoAhQgBmxqIQMgASgCACABKAIUIAZsaiENQQAh
- DgNAIAQgDSoCACADKgIAkjgCACAEIApqIQQgAyALaiEDIA0gDGohDSAOQQFqIg4gB0gNAAsLIAZB
- BGohBiAJQQFqIgkgBUgNAAsLIAALmQIBDH8CQAJAIAEoAggiAyACKAIIRw0AIAEoAgwiBCACKAIM
- Rw0AIAAoAgggA0cNACAAKAIMIARGDQELQc2LgIAAEICAgIAAIAAoAgwhBAsCQCAEQQFIDQAgACgC
- DCEFQQAhBiAAKAIIIgdBAUghCEEAIQkDQAJAIAgNACAAKAIQQQJ0IQogAigCEEECdCELIAEoAhBB
- AnQhDCAAKAIAIAAoAhQgBmxqIQQgAigCACACKAIUIAZsaiEDIAEoAgAgASgCFCAGbGohDUEAIQ4D
- QCAEIA0qAgAgAyoCAJM4AgAgBCAKaiEEIAMgC2ohAyANIAxqIQ0gDkEBaiIOIAdIDQALCyAGQQRq
- IQYgCUEBaiIJIAVIDQALCyAAC98BAQp/AkACQCAAKAIIIAEoAghHDQAgACgCDCIDIAEoAgxGDQEL
- QfOLgIAAEICAgIAAIAAoAgwhAwsCQCADQQFIDQAgACgCDCEEQQAhBSAAKAIIIgZBAUghB0EAIQgD
- QAJAIAcNACAAKAIQQQJ0IQkgASgCEEECdCEKIAAoAgAgACgCFCAFbGohAyABKAIAIAEoAhQgBWxq
- IQtBACEMA0AgAyALKgIAIAKUOAIAIAMgCWohAyALIApqIQsgDEEBaiIMIAZIDQALCyAFQQRqIQUg
- CEEBaiIIIARIDQALCyAAC5kCAQx/AkACQCABKAIIIgMgAigCCEcNACABKAIMIgQgAigCDEcNACAA
- KAIIIANHDQAgACgCDCAERg0BC0GZjICAABCAgICAACAAKAIMIQQLAkAgBEEBSA0AIAAoAgwhBUEA
- IQYgACgCCCIHQQFIIQhBACEJA0ACQCAIDQAgACgCEEECdCEKIAIoAhBBAnQhCyABKAIQQQJ0IQwg
- ACgCACAAKAIUIAZsaiEEIAIoAgAgAigCFCAGbGohAyABKAIAIAEoAhQgBmxqIQ1BACEOA0AgBCAN
- KgIAIAMqAgCUOAIAIAQgCmohBCADIAtqIQMgDSAMaiENIA5BAWoiDiAHSA0ACwsgBkEEaiEGIAlB
- AWoiCSAFSA0ACwsgAAvOAgMLfwF9BX8CQAJAIAEoAgwgAigCCEcNACAAKAIIIAEoAghHDQAgACgC
- DCACKAIMRg0BC0HAjICAABCAgICAAAsgABCcgICAABoCQCAAKAIMIgNBAUgNAEEAIQQgAigCCCIF
- QQFIIQZBACEHA0ACQCAGDQAgAigCFCAHbCEIIAAoAgghCSACKAIQIQogAigCACELQQAhDEEAIQ0D
- QAJAIAlBAUgNACALIAggCiANbGpBAnRqKgIAIQ4gACgCEEECdCEPIAEoAhBBAnQhECAAKAIAIAQg
- ACgCFGxqIREgASgCACABKAIUIAxsaiESQQAhEwNAIBEgDiASKgIAlCARKgIAkjgCACARIA9qIREg
- EiAQaiESIBNBAWoiEyAJSA0ACwsgDEEEaiEMIA1BAWoiDSAFSA0ACwsgBEEEaiEEIAdBAWoiByAD
- SA0ACwsgAAuIAQICfwF9AkACQCAAKAIIIgIgASgCCEcNACACQQFHDQAgAiAAKAIMIgNHDQAgAyAB
- KAIMRg0BC0HnjICAABCAgICAAAsCQAJAIAEoAgAqAgAiBIu7RI3ttaD3xrA+Y0EBcw0AQQAqAoCI
- gIAAIQQMAQtDAACAPyAElSEECyAAKAIAIAQ4AgAgAAuNAgICfwV9AkACQCAAKAIIIgIgASgCCEcN
- ACACQQJHDQAgAiAAKAIMIgNHDQAgAyABKAIMRg0BC0GOjYCAABCAgICAAAsCQAJAIAEoAgAiAioC
- ACIEIAIgAUEUaigCACIDIAEoAhAiAWpBAnRqKgIAIgWUIAIgAUECdGoqAgAiBiACIANBAnRqKgIA
- IgeUkyIIi7tEje21oPfGsD5jQQFzDQBBACoCgIiAgAAhCAwBC0MAAIA/IAiVIQgLIAAoAgAiASAF
- IAiUOAIAIAEgACgCECICQQJ0aiAIIAaMlDgCACABIABBFGooAgAiA0ECdGogCCAHjJQ4AgAgASAD
- IAJqQQJ0aiAEIAiUOAIAIAALnAQGAn8CfQF/BX0BfwZ9AkACQCAAKAIIIgIgASgCCEcNACACQQNH
- DQAgAiAAKAIMIgNHDQAgAyABKAIMRg0BC0G1jYCAABCAgICAAAsCQAJAIAEoAgAiAiABKAIQIgNB
- A3RqKgIAIgQgAiABQRRqKAIAIgFBAnRqKgIAIgUgAiABQQF0IgYgA2pBAnRqKgIAIgeUIAIgASAD
- akECdGoqAgAiCCACIAFBA3RqKgIAIgmUkyIKlCACKgIAIgsgCCACIAYgA0EBdCIMakECdGoqAgAi
- DZQgAiAMIAFqQQJ0aioCACIOIAeUkyIPlCACIANBAnRqKgIAIhAgBSANlCAOIAmUkyIRlJOSIhKL
- u0SN7bWg98awPmNBAXMNAEEAKgKAiICAACESDAELQwAAgD8gEpUhEgsgACgCACICIA8gEpQ4AgAg
- AiAAKAIQIgFBAnRqIBIgECANlCAEIAeUk4yUOAIAIAIgAUEDdGogECAOlCAEIAiUkyASlDgCACAC
- IABBFGooAgAiA0ECdGogEiARjJQ4AgAgAiADIAFqIgZBAnRqIAsgDZQgBCAJlJMgEpQ4AgAgAiAD
- IAFBAXRqQQJ0aiASIAsgDpQgBCAFlJOMlDgCACACIANBA3RqIAogEpQ4AgAgAiABIANBAXRqQQJ0
- aiASIAsgB5QgECAJlJOMlDgCACACIAZBA3RqIAsgCJQgECAFlJMgEpQ4AgAgAAvZAgIRfwF9AkAC
- QCABKAIIIAIoAghHDQAgACgCCCABKAIMRw0AIAAoAgwiAyACKAIMRg0BC0HcjYCAABCAgICAACAA
- KAIMIQMLAkAgA0EBSA0AIAAoAgwhBCAAKAIIIgVBAUghBkEAIQdBACEIA0ACQCAGDQAgACgCFCAI
- bCEJIAIoAgghCiAAKAIQIQsgACgCACEMQQAhDUEAIQ4DQCAMIAkgCyAObGpBAnRqIg9BADYCAAJA
- IApBAUgNACACKAIQQQJ0IRAgASgCEEECdCERIAIoAgAgByACKAIUbGohAyABKAIAIAEoAhQgDWxq
- IRJBACETQwAAAAAhFANAIA8gFCASKgIAIAMqAgCUkiIUOAIAIAMgEGohAyASIBFqIRIgE0EBaiIT
- IApIDQALCyANQQRqIQ0gDkEBaiIOIAVIDQALCyAHQQRqIQcgCEEBaiIIIARIDQALCyAAC5sFBAR/
- An0DfxB9AkACQCAAKAIIIgMgACgCDEcNACABKAIIIgQgASgCDEcNACACKAIIIgVBA0cNACAEQQNH
- DQAgA0EDRw0AIAUgAigCDEYNAQtBg46AgAAQgICAgAALIAIoAgAiAyACQRRqKAIAIgRBAXQiBiAC
- KAIQIgVBAXQiAmpBAnRqKgIAIQcgAyACIARqQQJ0aioCACEIIAEoAgAiAiABKAIQIglBAXQiCiAB
- QRRqKAIAIgtqQQJ0aioCACEMIAIgC0EBdCIBIApqQQJ0aioCACENIAMgBEEDdGoqAgAhDiADIAYg
- BWpBAnRqKgIAIQ8gAyAEQQJ0aioCACEQIAMgBCAFakECdGoqAgAhESACIAlBA3RqKgIAIRIgAiAJ
- QQJ0aioCACETIAIgCyAJakECdGoqAgAhFCACIAEgCWpBAnRqKgIAIRUgACgCACIBIAIqAgAiFiAD
- KgIAIheUIAIgC0ECdGoqAgAiGCADIAVBAnRqKgIAIhmUkiACIAtBA3RqKgIAIhogAyAFQQN0aioC
- ACIblJI4AgAgASAAKAIQIgNBAnRqIBMgF5QgFCAZlJIgFSAblJI4AgAgASADQQN0aiASIBeUIAwg
- GZSSIA0gG5SSOAIAIAEgAEEUaigCACICQQJ0aiAWIBCUIBggEZSSIBogCJSSOAIAIAEgAiADaiIE
- QQJ0aiATIBCUIBQgEZSSIBUgCJSSOAIAIAEgAiADQQF0akECdGogEiAQlCAMIBGUkiANIAiUkjgC
- ACABIAJBA3RqIBYgDpQgGCAPlJIgGiAHlJI4AgAgASADIAJBAXRqQQJ0aiATIA6UIBQgD5SSIBUg
- B5SSOAIAIAEgBEEDdGogEiAOlCAMIA+UkiANIAeUkjgCACAAC+UBAQp/AkACQCAAKAIIIAEoAghH
- DQAgACgCDCIDIAEoAgxGDQELQaqOgIAAEICAgIAAIAAoAgwhAwsCQCADQQFIDQAgACgCDCEEQQAh
- BSAAKAIIIgZBAUghB0EAIQgDQAJAIAcNACAAKAIQQQJ0IQkgASgCEEECdCEKIAAoAgAgACgCFCAF
- bGohAyABKAIAIAEoAhQgBWxqIQtBACEMA0AgAyALKgIAIAKUIAMqAgCSOAIAIAMgCWohAyALIApq
- IQsgDEEBaiIMIAZIDQALCyAFQQRqIQUgCEEBaiIIIARIDQALCyAAC48CAwh/AX0DfwJAAkAgASgC
- DEEBRw0AIAIoAghBAUcNACAAKAIIIAEoAghHDQAgACgCDCIDIAIoAgxGDQELQdGOgIAAEICAgIAA
- IAAoAgwhAwsCQCADQQFIDQAgAkEUaigCACEEIAAoAgwhBSACKAIAIQZBACEHIAAoAggiCEEBSCEJ
- QQAhCgNAAkAgCQ0AIAYgBCAKbEECdGoqAgAhCyAAKAIQQQJ0IQwgASgCEEECdCENIAAoAgAgACgC
- FCAHbGohAiABKAIAIQNBACEOA0AgAiALIAMqAgCUOAIAIAIgDGohAiADIA1qIQMgDkEBaiIOIAhI
- DQALCyAHQQRqIQcgCkEBaiIKIAVIDQALCyAAC70BAwF/AX0DfwJAAkAgACgCDEEBRw0AIAEoAgxB
- AUcNACAAKAIIIgIgASgCCEYNAQtB+I6AgAAQgICAgAAgASgCCCECCwJAAkAgAkEBTg0AQwAAAAAh
- AwwBCyABKAIQQQJ0IQQgACgCEEECdCEFIAEoAgghBiABKAIAIQEgACgCACEAQwAAAAAhA0EAIQID
- QCADIAAqAgAgASoCAJSSIQMgASAEaiEBIAAgBWohACACQQFqIgIgBkgNAAsLIAMLggEEAX8BfQJ/
- AX0CQCAAKAIMQQFGDQBBn4+AgAAQgICAgAALAkACQCAAKAIIIgFBAU4NAEMAAAAAIQIMAQsgACgC
- EEECdCEDIAAoAgAhAEEAIQRDAAAAACECA0AgAiAAKgIAIgUgBZSSIQIgACADaiEAIARBAWoiBCAB
- SA0ACwsgApELsQIBBX8CQCACKAIIIgMgAigCDCIETg0AQcaPgIAAEICAgIAACwJAAkAgACgCCCAD
- Rw0AIAAoAgwgA0cNACABKAIIIANHDQAgASgCDCAERg0BC0Hlj4CAABCAgICAAAsgBEECdEGfkYCA
- ABCFgICAACEFAkACQCAEQQFIDQBBACEGIAUhBwNAIAcgAyAGakEBEJKAgIAANgIAIAdBBGohByAE
- IAZBf2oiBmoNAAsgAyAEIAUgASACEK2AgIAAIAMgBCAFIAAQroCAgAAgBEEBaiEHIARBAnQgBWpB
- fGohBgNAIAYoAgAQl4CAgAAaIAZBfGohBiAHQX9qIgdBAUoNAAwCCwsgAyAEIAUgASACEK2AgIAA
- IAMgBCAFIAAQroCAgAALIAVBlZKAgAAQh4CAgAAaC5AEAgl/An0CQCAAIAFODQBBupGAgAAQgICA
- gAALAkACQCAEKAIIIABHDQAgBCgCDCABRw0AIAMoAgggAEcNACADKAIMIAFGDQELQdiRgIAAEICA
- gIAACxCWgICAACEFEJaAgIAAIQYQloCAgAAhBxCWgICAACEIIABBAWoiCSABQQFqIgoQkoCAgAAh
- CyAJIAoQkoCAgAAhDCADIAQQlYCAgAAaAkAgAUEBSA0AIAFBf2ohDSAAQX9qIQpBACEAA0AgBSAD
- IAAgCiAAIAAQmoCAgAAiBCgCACoCACEOIAIoAgAgBBCVgICAABogBBCrgICAACEPIAIoAgAiBCgC
- ACIJIA8gDkMAAAAAYCAOQwAAAABda7KUIAkqAgCSOAIAAkAgBBCrgICAACIOi7tEje21oPfGsD5j
- DQAgAigCACIEIARDAACAPyAOlRCggICAABogBiADIAAgCiAAIA0QmoCAgAAhBCAHIAtBASACKAIA
- KAIMQQEgBCgCDBCagICAACACKAIAIAQQpoCAgAAhCSAEIAggDEEBIAIoAgAoAghBASAEKAIMEJqA
- gIAAIAIoAgAgCRCpgICAAEMAAADAEKiAgIAAGgsgAkEEaiECIAEgAEEBaiIARw0ACwsgDBCXgICA
- ABogCxCXgICAABogCBCXgICAABogBxCXgICAABogBhCXgICAABogBRCXgICAABoL8gICCH8BfQJA
- AkAgAygCCCAARw0AIAMoAgwiBCAARg0BIAQgAUYNAQtB9pGAgAAQgICAgAALEJaAgIAAIQUQloCA
- gAAhBiADEJyAgIAAGgJAIAMoAgwiB0EBSA0AIAMoAgAgA0EUaigCACADKAIQaiIIIAdBf2psQQJ0
- aiEEIAdBAWohCUEAIAhBAnRrIQgDQCAEQYCAgPwDNgIAIAQgCGohBCAJQX9qIglBAUoNAAsgB0EB
- SA0AIAFBAWohCiAAQX9qIQAgAUECdCACakF8aiELQQAhAgNAIAUgA0EAIAAgAiACEJqAgIAAIQcg
- CyEEIAohCQJAIAFBAUgNAANAIAYgByAJQX5qIABBAEEAEJqAgIAAIQggBCgCACAIEKqAgIAAIQwg
- CCAEKAIAIAxDAAAAwJQQqICAgAAaIARBfGohBCAJQX9qIglBAUoNAAsLIAJBAWoiAiADKAIMSA0A
- CwsgBhCXgICAABogBRCXgICAABoLlwMBB38CQCACKAIIIgMgAigCDCIETg0AQYSQgIAAEICAgIAA
- CwJAAkAgACgCCCADRw0AIAAoAgwgBEcNACABKAIIIARHDQAgASgCDCAERg0BC0GjkICAABCAgICA
- AAsQloCAgAAhBSADIAQQkoCAgAAhBiAEQQJ0QZ+RgIAAEIWAgIAAIQcCQAJAIARBAUgNAEEAIQgg
- ByEJA0AgCSADIAhqQQEQkoCAgAA2AgAgCUEEaiEJIAQgCEF/aiIIag0ACyADIAQgByAGIAIQrYCA
- gAAgAyAEIAcgABCugICAACABIAUgBkEAIARBf2oiCEEAIAgQmoCAgAAQlYCAgAAaIARBAWohCSAE
- QQJ0IAdqQXxqIQgDQCAIKAIAEJeAgIAAGiAIQXxqIQggCUF/aiIJQQFKDQAMAgsLIAMgBCAHIAYg
- AhCtgICAACADIAQgByAAEK6AgIAAIAEgBSAGQQAgBEF/aiIIQQAgCBCagICAABCVgICAABoLIAdB
- lZKAgAAQh4CAgAAaIAYQl4CAgAAaIAUQl4CAgAAaC+QDAQp/AkAgASgCCCIEIAEoAgwiBU4NAEHC
- kICAABCAgICAAAsCQAJAIAIoAgggBEcNACACKAIMQQFHDQAgACgCCCAFRw0AIAAoAgxBAUYNAQtB
- 4ZCAgAAQgICAgAALIAQgBRCSgICAACEGIARBARCSgICAACEHIARBARCSgICAACEIIAVBARCSgICA
- ACEJIAVBAnRBn5GAgAAQhYCAgAAhCgJAIAVBAUgNACAEIQsgCiEMIAUhDQNAIAwgC0EBEJKAgIAA
- NgIAIAtBf2ohCyAMQQRqIQwgDUF/aiINDQALCyAEIAUgCiAGIAEQrYCAgAAgBCAFIAogByACELGA
- gIAAIAAgBiAHELKAgIAAAkAgA0EBSA0AIANBAWohCwNAIAggAiAHIAEgABCigICAABCfgICAABog
- BCAFIAogByAIELGAgIAAIAkgBiAHELKAgIAAIAAgCUMAAIA/EKiAgIAAGiALQX9qIgtBAUoNAAsL
- AkAgBUEBSA0AIAVBAWohDCAFQQJ0IApqQXxqIQsDQCALKAIAEJeAgIAAGiALQXxqIQsgDEF/aiIM
- QQFKDQALCyAKQZWSgIAAEIeAgIAAGiAJEJeAgIAAGiAIEJeAgIAAGiAHEJeAgIAAGiAGEJeAgIAA
- GiAAC+MCAwh/AX0BfwJAAkAgAygCCCAARw0AIAMoAgxBAUcNACAEKAIIIABHDQAgBCgCDEEBRg0B
- C0GukoCAABCAgICAAAsgAyAEEJWAgIAAGgJAIAFBAUgNAEEAIQUgACEGQQAhBwNAAkAgByAATiII
- DQAgAygCECIEQQJ0IQkgAygCACAEIAVsaiEEIAIgB0ECdGoiCigCACILKAIQQQJ0IQwgCygCACEL
- QwAAAAAhDSAGIQ4DQCANIAsqAgAgBCoCAJSSIQ0gBCAJaiEEIAsgDGohCyAOQX9qIg4NAAsgCA0A
- IA0gDZIhDSADKAIQIgRBAnQhCSADKAIAIAQgBWxqIQQgCigCACILKAIQQQJ0IQwgCygCACELIAYh
- DgNAIAQgBCoCACANIAsqAgCUkzgCACAEIAlqIQQgCyAMaiELIA5Bf2oiDg0ACwsgBUEEaiEFIAZB
- f2ohBiAHQQFqIgcgAUcNAAsLC7IDAwx/An0DfwJAIAEoAggiAyABKAIMIgRODQBBzZKAgAAQgICA
- gAALAkACQCAAKAIIIARHDQAgACgCDEEBRw0AIAIoAgggA0cNACACKAIMQQFGDQELQeySgIAAEICA
- gIAACwJAIARBAUgNAEEAIQVBACABQRRqKAIAIgNBAnQiBiABKAIQIgdBAnRqayEIIAEoAgAiCSAD
- IARsIAcgBEF/amxqQQJ0aiEKIARBAnQhCyADIAdqIQwgBCENA0ACQCAJIAwgDUF/aiIObEECdGoq
- AgAiD4u7RI3ttaD3xrA+Y0EBcw0AIABBACoCgIiAgAAQm4CAgAAaDwsgAigCACACKAIQIA5sQQJ0
- aioCACEQAkACQCANIARIDQAgACgCECERIAAoAgAhEgwBCyAAKAIQIhFBAnQhEyAAKAIAIhIgESAL
- bGohASAKIQMgBSEHA0AgECADKgIAIAEqAgCUkyEQIAEgE2ohASADIAZqIQMgB0F/aiIHDQALCyAS
- IBEgDmxBAnRqIBAgD5U4AgAgC0F8aiELIAogCGohCiAFQQFqIQUgDUEBSiEBIA4hDSABDQALCwvC
- AwEKfwJAAkAgACgCCCICIAAoAgxHDQAgAiABKAIIIgNHDQAgAyABKAIMRg0BC0GAkYCAABCAgICA
- ACAAKAIMIQILIAIgAhCUgICAACEEIAIgAhCSgICAACEFIAJBARCSgICAACEGEJaAgIAAIQcQloCA
- gAAhCCACQQJ0QZ+RgIAAEIWAgIAAIQkCQAJAIAJBAUgNACAJIQMgAiEKA0AgAyAKQQEQkoCAgAA2
- AgAgA0EEaiEDIApBf2oiCg0ACyACIAIgCSAFIAEQrYCAgAAgAkEBSA0BIAJBf2ohCkEAIQMDQCAH
- IARBACAKIAMgAxCagICAACEBIAggAEEAIAogAyADEJqAgIAAIQsgAiACIAkgBiABELGAgIAAIAsg
- BSAGELKAgIAAIAIgA0EBaiIDRw0ACyACQQFIDQEgAkEBaiEKIAJBAnQgCWpBfGohAwNAIAMoAgAQ
- l4CAgAAaIANBfGohAyAKQX9qIgpBAUoNAAwCCwsgAiACIAkgBSABEK2AgIAACyAJQZWSgIAAEIeA
- gIAAGiAIEJeAgIAAGiAHEJeAgIAAGiAGEJeAgIAAGiAFEJeAgIAAGiAEEJeAgIAAGiAAC9YCAQJ/
- AkACQCAAKAIIQQNHDQAgACgCDEEDRw0AIAEoAghBAkcNACABKAIMQQRHDQAgAigCCEECRw0AIAIo
- AgxBBEYNAQtBi5OAgAAQgICAgAALIAAgASgCACIDKgIAuyADIAEoAhAiBEECdGoqAgC7IAMgAUEU
- aigCACIBQQJ0aioCALsgAyABIARqQQJ0aioCALsgAyABQQN0aioCALsgAyABQQF0IARqQQJ0aioC
- ALsgAyABQQNsIgFBAnRqKgIAuyADIAEgBGpBAnRqKgIAuyACKAIAIgMqAgC7IAMgAigCECIEQQJ0
- aioCALsgAyACQRRqKAIAIgFBAnRqKgIAuyADIAEgBGpBAnRqKgIAuyADIAFBA3RqKgIAuyADIAFB
- AXQgBGpBAnRqKgIAuyADIAFBA2wiAUECdGoqAgC7IAMgASAEakECdGoqAgC7ELWAgIAAIAAL9QoC
- FnwDf0EAKgKAiICAALshEQJAAkAgAiAEoSISIAWiIAQgBqEiEyABoiAGIAKhIhQgA6KgoCAKIAyh
- IhUgDaIgDCAOoSIWIAmiIA4gCqEgC6KgoKJEAAAAAAAAAABjDQAgEyAHoiAGIAihIhcgA6IgCCAE
- oSIYIAWioKAgFiAPoiAOIBChIhkgC6IgECAMoSANoqCgokQAAAAAAAAAAGMNACASIAeiIAQgCKEg
- AaIgCCACoSITIAOioKAgFSAPoiAMIBChIAmiIBAgCqEiEiALoqCgokQAAAAAAAAAAGMNACACIAah
- IAeiIBcgAaIgEyAFoqCgIAogDqEgD6IgGSAJoiASIA2ioKCiRAAAAAAAAAAAYw0AIAQgAqEiGiAH
- IAGhIheiIAMgAaEiGyAToqEiHJkiHUSN7bWg98awPmMNACAUIBeiIAUgAaEiHiAToqEiH5kiIESN
- 7bWg98awPmMNACAbIBSiIBogHqKhIhSZIiFEje21oPfGsD5jDQAgBiAEoSAHIAOhoiAFIAOhIBii
- oZlEje21oPfGsD5jDQAgHCAFoiIYIB8gA6KhIiIgFCAIoiAcIAaiIh6gIiOiIB4gHyAEoqEiHiAU
- IAeiIBigIhiioSIkmUSN7bWg98awPmMNACAcmiIlIBShIiYgIqIgHyAcoSIiIBiioUQAAAAAAADw
- PyAkoyIkoiEYICIgI6IgJiAeoqEgJKIhHgJAAkAgHSAgZEEBcw0AIBMgGCAEoiAeIAOiRAAAAAAA
- APA/oKAiBKIgJaMhHSAcIR8MAQsgEyAYIAaiIB4gBaJEAAAAAAAA8D+goCIEoiAfmqMhHQsgFyAE
- oiAfoyETAkACQCAhICWZZEEBcw0AIBogGCAGoiAeIAWiRAAAAAAAAPA/oKAiBKIgFJqjIQcMAQsg
- GiAYIAiiIB4gB6JEAAAAAAAA8D+goCIEoiAcoyEHICUhFAsgGCAdmiABoiATIAKioSIXIAeioiAd
- IBsgBKIgFKMiFKIgHiATIAeaIAGiIBQgAqKhIhyioqCgIBMgB6KhIBggHSAcoqKhIB4gFyAUoqKh
- mUSN7bWg98awPmMNACALIA2hIhsgECAOoSIaoiAWIA8gDaEiH6KhIiCZRI3ttaD3xrA+Yw0AIBEh
- BCARIQIgESEGIBEhDiARIQEgESEDIBEhBSARIQggGyAVIBmgIhWiIBYgCSALoSANIA+hoCIZoqFE
- AAAAAAAA8D8gIKMiFqIiDSAMIAqhIBogGaIgHyAVoqEgFqIiFiAMoqAiDCAJoqIgCyAJoSAWIAui
- oCILIBIgDSAQoqAiEKIgFiAPIAmhIA0gD6KgIg8gCqKioKAgDyAMoqEgDSALIAqioqEgFiAQIAmi
- oqGZRI3ttaD3xrA+Yw0BIBYgF6IgDSAcoqBEAAAAAAAA8D+gIQUgGCAWIBOiIA0gFKKgoCEDIB4g
- FiAdoiANIAeioKAhASAMIBeiIBAgHKKgIAqgIQ4gGCAKoiAMIBOiIBAgFKKgoCEGIB4gCqIgDCAd
- oiAQIAeioKAhAiALIBeiIA8gHKKgIAmgIQQgGCAJoiALIBOiIA8gFKKgoCERIB4gCaIgCyAdoiAP
- IAeioKAhCAwBCyARIQQgESECIBEhBiARIQ4gESEBIBEhAyARIQUgESEICyAAKAIAIicgCLY4AgAg
- JyAAQRRqKAIAIihBAnRqIBG2OAIAICcgKEEDdGogBLY4AgAgJyAAKAIQIgBBAnRqIAK2OAIAICcg
- ACAoaiIpQQJ0aiAGtjgCACAnIAAgKEEBdGpBAnRqIA62OAIAICcgAEEDdGogAbY4AgAgJyAoIABB
- AXRqQQJ0aiADtjgCACAnIClBA3RqIAW2OAIAC7oHAhZ/Cn0CQAJAIAAoAghBA0cNACAAKAIMQQNH
- DQAgASgCCEECRw0AIAEoAgwiA0EESA0AIAIoAghBAkcNACACKAIMIANGDQELQbKTgIAAEICAgIAA
- IAEoAgwhAwsgA0EBdCIEQQgQkoCAgAAhBSAEQQEQkoCAgAAhBkEIQQEQkoCAgAAhBwJAIANBAUgN
- ACAFQRRqKAIAIgRBDGwgBSgCECIIQQJ0IglqIQogBEEEdCAJaiELIARBFGwgCWohDCAEQRhsIg0g
- CWohDiAEQRxsIg8gCWohECACKAIQQQJ0IREgASgCEEECdCESIAhBA3QhCCAGKAIQIglBA3QhEyAJ
- QQJ0IRQgAkEUaigCAEECdCEVIAFBFGooAgBBAnQhFiAEQQN0IRcgBEECdCEYIAYoAgAhCSAFKAIA
- IQQgAigCACECIAEoAgAhAQNAIAIgEWoqAgAhGSABIBJqKgIAIRogAioCACEbIAQgASoCACIcOAIA
- IAQgGGogGjgCACAEIBdqQYCAgPwDNgIAIAQgCmogHDgCACAEIAtqIBo4AgAgBCAMakGAgID8AzYC
- ACAEIA1qIBsgHIwiHJQ4AgAgBCAOaiAZIByUOAIAIAQgD2ogGyAajCIalDgCACAEIBBqIBkgGpQ4
- AgAgCSAbOAIAIAkgFGogGTgCACACIBVqIQIgASAWaiEBIAQgCGohBCAJIBNqIQkgA0F/aiIDDQAL
- CyAHIAUgBkEDELCAgIAAGgJAAkAgBygCACIEKgIAIhkgBCAHKAIQIglBBHRqKgIAIhqUIAQgCUEC
- dGoqAgAiGyAEIAlBFGxqKgIAIhyUIAQgCUEYbGoqAgAiHZSSIAQgCUEDdGoqAgAiHiAEIAlBDGxq
- KgIAIh+UIAQgCUEcbGoqAgAiIJSSIBsgH5STIBkgHJQgIJSTIB4gGpQgHZSTIiEQg4CAgAANAEMA
- AIA/ISIgIYu7RI3ttaD3xrA+Y0EBcw0BC0EAKgKAiICAACIZIRsgGSEeIBkhHyAZIRogGSEcIBkh
- HSAZISAgGSEiCyAAKAIAIgQgGTgCACAEIABBFGooAgAiCUECdGogGzgCACAEIAlBA3RqIB44AgAg
- BCAAKAIQIgJBAnRqIB84AgAgBCACIAlqIgFBAnRqIBo4AgAgBCACIAlBAXRqQQJ0aiAcOAIAIAQg
- AkEDdGogHTgCACAEIAkgAkEBdGpBAnRqICA4AgAgBCABQQN0aiAiOAIAIAcQl4CAgAAaIAYQl4CA
- gAAaIAUQl4CAgAAaIAALnwgKAX8BfQF/An0Bfwp9AX8BfQN/AX0CQAJAIAAoAghBA0cNACAAKAIM
- QQNHDQAgASgCCEECRw0AIAEoAgxBBEcNACACKAIIQQJHDQAgAigCDEEERg0BC0HZk4CAABCAgICA
- AAsgACABKAIAIgMqAgAiBCAEIAMgAUEUaigCACIFQQJ0aioCACIGkiADIAVBA3RqKgIAIgeSIAMg
- BUEDbCIIQQJ0aioCACIJkkMAAIA+lCIKkyIEQwAAAEEgAyAIIAEoAhAiAWpBAnRqKgIAIgsgCyAD
- IAFBAnRqKgIAIgwgAyAFIAFqQQJ0aioCACINkiADIAVBAXQgAWpBAnRqKgIAIg6SkkMAAIA+lCIP
- kyILIAuUIAkgCpMiCSAJlCAOIA+TIg4gDpQgByAKkyIHIAeUIA0gD5MiDSANlCAGIAqTIgYgBpQg
- BCAElCAMIA+TIgwgDJSSkpKSkpKSlZEiBJS7IAwgBJS7IAYgBJS7IA0gBJS7IAcgBJS7IA4gBJS7
- IAkgBJS7IAsgBJS7IAIoAgAiAyoCACILIAsgAyACQRRqKAIAIgVBAnRqKgIAIhCSIAMgBUEDdGoq
- AgAiDJIgAyAFQQNsIghBAnRqKgIAIg2SQwAAgD6UIgmTIgtDAAAAQSADIAggAigCECIBakECdGoq
- AgAiDiAOIAMgAUECdGoqAgAiESADIAUgAWpBAnRqKgIAIhKSIAMgBUEBdCABakECdGoqAgAiBpKS
- QwAAgD6UIg6TIgcgB5QgDSAJkyINIA2UIAYgDpMiBiAGlCAMIAmTIgwgDJQgEiAOkyISIBKUIBAg
- CZMiECAQlCALIAuUIBEgDpMiESARlJKSkpKSkpKVkSILlLsgESALlLsgECALlLsgEiALlLsgDCAL
- lLsgBiALlLsgDSALlLsgByALlLsQtYCAgAAgACgCACIDIABBFGooAgAiBUEBdCICIAAoAhAiAUEB
- dCIIakECdGoqAgAhECADIAggBWpBAnRqIggqAgAhByADIAIgAWpBAnRqIgIqAgAhESADIAVBA3Rq
- IhMqAgAhFCADIAUgAWoiFUECdGoiFioCACEGIAMgBUECdGoiBSoCACEMIAMgAUECdGoiFyoCACES
- IAMgBCAJIAMgAUEDdGoiASoCACINlCADKgIAIhhDAACAPyALlSILlJKUOAIAIBcgBCAOIA2UIBIg
- C5SSlDgCACABIAQgDZQ4AgAgBSAEIAkgB5QgDCALlJKUOAIAIBYgBCAOIAeUIAYgC5SSlDgCACAI
- IAQgB5Q4AgAgEyAUIAQgCiAYlCAPIAyUkpSTIAuUIAkgECAEIAogDZQgDyAHlJKUkyIHlJI4AgAg
- AiARIAQgCiASlCAPIAaUkpSTIAuUIA4gB5SSOAIAIAMgFUEDdGogBzgCACAAC5sCAQZ/AkACQCAA
- KAIIQQNHDQAgACgCDEEDRw0AIAEoAghBAkcNACABKAIMIgNBBEgNACACKAIIQQJHDQAgAigCDCAD
- Rg0BC0GAlICAABCAgICAACABKAIMIQMLQQIgAxCSgICAACEEQQIgAxCSgICAACEFQQNBAxCSgICA
- ACEGQQNBAxCSgICAACEHQQNBAxCSgICAACEIIAQgASAGQQNBAxCSgICAACIDEMGAgIAAIAUgAiAD
- IAcQwYCAgAAgAyAIIAQgBRC2gICAACIBIAYQp4CAgAAaIAAgByADEKeAgIAAGiADEJeAgIAAGiAB
- EJeAgIAAGiAHEJeAgIAAGiAGEJeAgIAAGiAFEJeAgIAAGiAEEJeAgIAAGiAAC/kFAhZ/Bn0CQAJA
- IAAoAghBAkcNACAAKAIMQQNHDQAgASgCCEECRw0AIAEoAgwiA0EDSA0AIAIoAghBAkcNACACKAIM
- IANGDQELQaeUgIAAEICAgIAAIAEoAgwhAwsgA0EBdCIEQQYQkoCAgAAhBSAEQQEQkoCAgAAhBkEG
- QQEQkoCAgAAhBwJAIANBAUgNACAFQRRqKAIAIgRBDGwgBSgCECIIQQJ0IglqIQogBEEEdCAJaiEL
- IARBFGwgCWohDCACKAIQQQJ0IQ0gASgCEEECdCEOIAhBA3QhDyAGKAIQIglBA3QhECAJQQJ0IREg
- AkEUaigCAEECdCESIAFBFGooAgBBAnQhEyAEQQN0IRQgBEECdCEVIAYoAgAhCSAFKAIAIQQgAigC
- ACECIAEoAgAhAQNAIAIgDWooAgAhFiABIA5qKAIAIQggAigCACEXIAQgASgCACIYNgIAIAQgFWog
- CDYCACAEIBRqQYCAgPwDNgIAIAQgCmogGDYCACAEIAtqIAg2AgAgBCAMakGAgID8AzYCACAJIBc2
- AgAgCSARaiAWNgIAIAIgEmohAiABIBNqIQEgBCAPaiEEIAkgEGohCSADQX9qIgMNAAsLIAcgBSAG
- QQMQsICAgAAaAkACQCAHKAIAIgQqAgAiGSAEIAcoAhAiCUECdGoqAgAiGpIgBCAJQQN0aioCACIb
- kiAEIAlBDGxqKgIAIhySIAQgCUEEdGoqAgAiHZIgBCAJQRRsaioCACIekhCDgICAAA0AIBkgHZQg
- GiAclJOLu0SN7bWg98awPmNBAXMNAQtBACoCgIiAgAAiGSEaIBkhGyAZIRwgGSEdIBkhHgsgACgC
- ACIEIBk4AgAgBCAAQRRqKAIAIglBAnRqIBo4AgAgBCAJQQN0aiAbOAIAIAQgACgCECICQQJ0aiAc
- OAIAIAQgAiAJakECdGogHTgCACAEIAIgCUEBdGpBAnRqIB44AgAgBxCXgICAABogBhCXgICAABog
- BRCXgICAABogAAvNBQMBfAJ/FXwCQAJAIAAoAghBAkcNACAAKAIMQQNHDQAgASgCCEECRw0AIAEo
- AgxBA0cNACACKAIIQQJHDQAgAigCDEEDRg0BC0HKlICAABCAgICAAAtBACoCgIiAgAC7IQMCQAJA
- IAEoAgAiBCABKAIQIgVBAnRqKgIAuyIGIAQgAUEUaigCACIBIAVqQQJ0aioCALsiB6EiCCAEIAFB
- A3RqKgIAuyIJoiAHIAQgAUEBdCAFakECdGoqAgC7IgqhIgsgBCoCALsiDKIgCiAGoSINIAQgAUEC
- dGoqAgC7Ig6ioKAiD5lEje21oPfGsD5jDQAgAigCACIEIAIoAhAiBUECdGoqAgC7IhAgBCACQRRq
- KAIAIgEgBWpBAnRqKgIAuyIRoSAEIAFBA3RqKgIAuyISoiARIAQgAUEBdCAFakECdGoqAgC7IhOh
- IAQqAgC7IhSiIBMgEKEgBCABQQJ0aioCALsiFaKgoJlEje21oPfGsD5jDQBEAAAAAAAA8D8gD6Mi
- FiALIBSiIA0gFaKgIAggEqKgoiIPIBYgCSAOoSIXIBCiIAwgCaEiGCARoqAgDiAMoSIZIBOioKIi
- GqIgFiAXIBSiIBggFaKgIBkgEqKgoiIXIBYgCyAQoiANIBGioCAIIBOioKIiCKKhmUSN7bWg98aw
- PmNBAXNFDQAgFiAOIAqiIAcgCaKhIgMgEKIgBiAJoiAMIAqioSIKIBGioCAMIAeiIAYgDqKhIgcg
- E6KgoiEGIBYgAyAUoiAKIBWioCAHIBKioKIhAwwBCyADIQ8gAyEXIAMhCCADIRogAyEGCyAAKAIA
- IgQgD7Y4AgAgBCAAQRRqKAIAIgFBAnRqIBe2OAIAIAQgAUEDdGogA7Y4AgAgBCAAKAIQIgVBAnRq
- IAi2OAIAIAQgBSABakECdGogGrY4AgAgBCAFIAFBAXRqQQJ0aiAGtjgCACAAC4EDAQl/AkACQCAA
- KAIIQQJHDQAgACgCDEEDRw0AIAEoAghBAkcNACABKAIMIgNBA0gNACACKAIIQQJHDQAgAigCDCAD
- Rg0BC0HtlICAABCAgICAACABKAIMIQMLQQIgAxCSgICAACEEQQIgAxCSgICAACEFQQNBAxCSgICA
- ACEGQQNBAxCSgICAACEHQQNBAxCUgICAACEIEJaAgIAAIAhBAEEBQQBBAhCagICAACEJQQNBAxCS
- gICAACEDQQNBAxCSgICAACEKEJaAgIAAIApBAEEBQQBBAhCagICAACELIAQgASAGIAMQwYCAgAAg
- BSACIAMgBxDBgICAACAJIAQgBRC5gICAACEBIAMgCCAGEKeAgIAAGiAKIAcgAxCngICAABogACAL
- EJWAgIAAGiALEJeAgIAAGiAKEJeAgIAAGiADEJeAgIAAGiABEJeAgIAAGiAIEJeAgIAAGiAHEJeA
- gIAAGiAGEJeAgIAAGiAFEJeAgIAAGiAEEJeAgIAAGiAAC5kUAhx/DX0jgICAgABBEGsiBySAgICA
- AAJAAkAgACgCCEEDRw0AIAAoAgxBA0cNACACKAIIQQJHDQAgAigCDCIIQQRIDQAgAygCCEECRw0A
- IAMoAgwgCEcNAAJAIAFFDQAgASgCCEEBRw0BIAEoAgwgCEcNAQsgBEEBSA0AIAVBAUgNACAGQwAA
- AABgDQELQZCVgIAAEICAgIAAIAIoAgwhCAsCQCABRQ0AIAFDAAAAABCbgICAABoLIAhBAnQiCUGy
- lYCAABCFgICAACEKIAlB0ZWAgAAQhYCAgAAgCBCNgICAACILIAhBBBCOgICAACAIIARBAnQiDCAI
- b2sgDGoiDUECdEHwlYCAABCFgICAACEOAkAgDUEBSA0AQQAhDyAIQQFIIRAgDiERA0ACQCAQDQBB
- ACEMIBEhEgNAIBIgDDYCACASQQRqIRIgCCAMQQFqIgxHDQALCyAOIA9BAnRqIAhBBBCOgICAACAR
- IAlqIREgDyAIaiIPIA1IDQALC0ECQQQQkoCAgAAhE0ECQQQQkoCAgAAhFCAEQQN0QY+WgIAAEIWA
- gIAAIRUgBCEWAkAgBEEBSA0AIBUhFyAOIQkgBCEYIAQhFgNAIAcgCSgCACIZNgIAIAcgCUEEaigC
- ACIaNgIEIAcgCUEIaigCACIbNgIIIAcgCUEMaigCADYCDCAUKAIUIQ0gEygCFCEQIAMoAhAhHCAU
- KAIQIR0gFCgCACEMIAMoAgAhEiADKAIUIR4gAigCECEfIBMoAhAhICATKAIAIg8gAigCACIRIBkg
- AigCFCIhbCIiQQJ0aigCADYCACAPICBBAnRqIBEgHyAiakECdGooAgA2AgAgDCASIB4gGWwiGUEC
- dGooAgA2AgAgDCAdQQJ0aiASIBwgGWpBAnRqKAIANgIAIA8gEEECdGogESAhIBpsIhlBAnRqKAIA
- NgIAIA8gICAQakECdGogESAfIBlqQQJ0aigCADYCACAMIA1BAnRqIBIgHiAabCIZQQJ0aigCADYC
- ACAMIB0gDWpBAnRqIBIgHCAZakECdGooAgA2AgAgDyAQQQN0aiARICEgG2wiGUECdGooAgA2AgAg
- DyAgIBBBAXRqQQJ0aiARIB8gGWpBAnRqKAIANgIAIAwgDUEDdGogEiAeIBtsIhlBAnRqKAIANgIA
- IAwgHSANQQF0akECdGogEiAcIBlqQQJ0aigCADYCACAPIBBBA2wiEEECdGogESAhIAcoAgwiGWwi
- IUECdGooAgA2AgAgDyAgIBBqQQJ0aiARIB8gIWpBAnRqKAIANgIAIAwgDUEDbCIPQQJ0aiASIB4g
- GWwiEUECdGooAgA2AgAgDCAdIA9qQQJ0aiASIBwgEWpBAnRqKAIANgIAQQNBAxCSgICAACEMIBdB
- BGoiEkEANgIAIBcgDDYCACAMIBMgFBC0gICAABoCQCAXKAIAKAIAKgIAEIOAgIAARQ0AIBJBfzYC
- ACAWQX9qIRYLIBdBCGohFyAJQRBqIQkgGEF/aiIYDQALCwJAAkAgFg0AIABBACoCgIiAgAAQm4CA
- gAAaDAELIAYgBpQhI0EAIRcgFSAEQQhBhICAgABBABCLgICAABoCQAJAIAhBAUgNAEEAIRwDQCAc
- IhJBAWoiHCAFbyEMAkAgFkECSA0AIAwNACAVIBZBCEGEgICAAEEAEIuAgIAAGiAWQQF2IRYLAkAg
- FkEBRw0AQQAhFwwDCwJAIBZBAUgNACADKAIAIgwgAygCFCALIBJBAnRqKAIAIhJsIg9BAnRqKgIA
- ISQgAigCACIRIAIoAhQgEmwiEkECdGoqAgAhBiAMIA8gAygCEGpBAnRqKgIAISUgESASIAIoAhBq
- QQJ0aioCACEmIBUhESAWIQkDQCARQQRqIgwgDCgCACARKAIAIg8oAgAiDCAPQRRqKAIAIhJBAXQi
- DSAPKAIQIg9qQQJ0aioCACAGIAwgD0ECdGoqAgCUICYgDCASIA9qQQJ0aioCAJSSkiAMIA0gD0EB
- dCIQakECdGoqAgAgBiAMIA9BA3RqKgIAlCAmIAwgECASakECdGoqAgCUkpIiJ5UgJZMiKCAolCAM
- IBJBA3RqKgIAIAYgDCoCAJQgJiAMIBJBAnRqKgIAlJKSICeVICSTIicgJ5SSICNfajYCACARQQhq
- IREgCUF/aiIJDQALCyAcIAhHDQALCyAWQQJIDQAgFUEMaiEMQQAhF0EBIRIDQCASIBcgDCgCACAV
- IBdBA3RqKAIEShshFyAMQQhqIQwgFiASQQFqIhJHDQALCwJAIAhBAUgNACAVIBdBA3RqKAIAIg8o
- AgAiDCAPKAIQIhJBA3RqKgIAISQgDCASQQJ0aioCACElIAwgD0EUaigCACIPQQN0aioCACEpIAwg
- D0ECdGoqAgAhKiAMIBJBAXQiESAPakECdGoqAgAhKyAMIA8gEmpBAnRqKgIAISwgDCAPQQF0Ig8g
- EWpBAnRqKgIAIS0gDCAPIBJqQQJ0aioCACEuIAwqAgAhLyADKAIAIQ8gAigCACERQQAhEkEAIQwD
- QAJAICkgLyARIAIoAhQgDGwiCUECdGoqAgAiBpQgKiARIAkgAigCEGpBAnRqKgIAIiaUkpIgLSAk
- IAaUICsgJpSSkiInlSAPIAMoAhQgDGwiCUECdGoqAgCTIiggKJQgLiAlIAaUICwgJpSSkiAnlSAP
- IAkgAygCEGpBAnRqKgIAkyIGIAaUkiAjX0EBcw0AIAogEkECdGogDDYCACASQQFqIRIgAUUNACAB
- KAIAIAEoAhQgDGxBAnRqQYCAgPwDNgIACyAIIAxBAWoiDEcNAAsgEkEDTA0AQQIgEhCSgICAACEW
- QQIgEhCSgICAACIZKAIQQQJ0IRcgFkEUaigCAEECdCEcIBYoAhBBAnQhHSAZQRRqKAIAQQJ0IR4g
- GSgCACEMIANBFGooAgAhHyAWKAIAIQ8gAkEUaigCACEgIAMoAhAhISADKAIAIQggAigCECEDIAIo
- AgAhCSAKIREDQCAPIAkgICARKAIAIg1sIhBBAnRqKAIANgIAIA8gHWogCSADIBBqQQJ0aigCADYC
- ACAMIAggHyANbCINQQJ0aigCADYCACAMIBdqIAggISANakECdGooAgA2AgAgDCAeaiEMIA8gHGoh
- DyARQQRqIREgEkF/aiISDQALIAAgFiAZELiAgIAAGiAZEJeAgIAAGiAWEJeAgIAAGgwBCyAAQQAq
- AoCIgIAAEJuAgIAAGgsCQCAEQQFIDQAgBEEBaiESIARBA3QgFWpBeGohDANAIAwoAgAQl4CAgAAa
- IAxBeGohDCASQX9qIhJBAUoNAAsLIBVBr5aAgAAQh4CAgAAaIBQQl4CAgAAaIBMQl4CAgAAaIA5B
- zZaAgAAQh4CAgAAaIAtB65aAgAAQh4CAgAAaIApBiZeAgAAQh4CAgAAaIAdBEGokgICAgAAgAAsN
- ACABKAIEIAAoAgRrC8gRAhh/CX0CQAJAIAAoAghBAkcNACAAKAIMQQNHDQAgAigCCEECRw0AIAIo
- AgwiB0EDSA0AIAMoAghBAkcNACADKAIMIAdHDQACQCABRQ0AIAEoAghBAUcNASABKAIMIAdHDQEL
- IARBAUgNACAFQQFIDQAgBkMAAAAAYA0BC0Gnl4CAABCAgICAACACKAIMIQcLAkAgAUUNACABQwAA
- AAAQm4CAgAAaCyAHQQJ0IghBypeAgAAQhYCAgAAhCSAIQeqXgIAAEIWAgIAAIAcQjYCAgAAiCiAH
- QQQQjoCAgAAgByAEQQNsIgsgB29rIAtqIgxBAnRBipiAgAAQhYCAgAAhDQJAIAxBAUgNAEEAIQ4g
- B0EBSCEPIA0hEANAAkAgDw0AQQAhCyAQIREDQCARIAs2AgAgEUEEaiERIAcgC0EBaiILRw0ACwsg
- DSAOQQJ0aiAHQQQQjoCAgAAgECAIaiEQIA4gB2oiDiAMSA0ACwtBAkEDEJKAgIAAIQ9BAkEDEJKA
- gIAAIRIgBEEDdEGqmICAABCFgICAACETIAQhFAJAIARBAUgNACATIQggDSEMIAQhFSAEIRQDQCAP
- KAIAIgsgAigCACIRIAIoAhQiFiAMKAIAIhdsIg5BAnRqKAIANgIAIAsgDygCECIYQQJ0aiARIAIo
- AhAiGSAOakECdGooAgA2AgAgEigCACIOIAMoAgAiECAXIAMoAhQiGmwiF0ECdGooAgA2AgAgDiAS
- KAIQIhtBAnRqIBAgAygCECIcIBdqQQJ0aigCADYCACALIA8oAhQiF0ECdGogESAWIAxBBGooAgAi
- HWwiHkECdGooAgA2AgAgCyAYIBdqQQJ0aiARIBkgHmpBAnRqKAIANgIAIA4gEigCFCIeQQJ0aiAQ
- IBogHWwiHUECdGooAgA2AgAgDiAbIB5qQQJ0aiAQIBwgHWpBAnRqKAIANgIAIAsgF0EDdGogESAW
- IAxBCGooAgAiHWwiFkECdGooAgA2AgAgCyAYIBdBAXRqQQJ0aiARIBkgFmpBAnRqKAIANgIAIA4g
- HkEDdGogECAaIB1sIgtBAnRqKAIANgIAIA4gGyAeQQF0akECdGogECAcIAtqQQJ0aigCADYCAEEC
- QQMQkoCAgAAhCyAIQQRqIhFBADYCACAIIAs2AgAgCyAPIBIQuoCAgAAaAkAgCCgCACgCACoCABCD
- gICAAEUNACARQX82AgAgFEF/aiEUCyAIQQhqIQggDEEMaiEMIBVBf2oiFQ0ACwsCQAJAIBQNACAA
- QQAqAoCIgIAAEJuAgIAAGgwBCyAGIAaUIR9BACEMIBMgBEEIQYSAgIAAQQAQi4CAgAAaAkACQCAH
- QQFIDQBBACEXA0AgFyIRQQFqIhcgBW8hCwJAIBRBAkgNACALDQAgEyAUQQhBhICAgABBABCLgICA
- ABogFEEBdiEUCwJAIBRBAUcNAEEAIQwMAwsCQCAUQQFIDQAgAygCACILIAMoAhQgCiARQQJ0aigC
- ACIRbCIOQQJ0aioCACEgIAIoAgAiECACKAIUIBFsIhFBAnRqKgIAIQYgCyAOIAMoAhBqQQJ0aioC
- ACEhIBAgESACKAIQakECdGoqAgAhIiATIREgFCEIA0AgEUEEaiILIAsoAgAgESgCACIQKAIAIgsg
- EEEUaigCACIOQQN0aioCACAGIAsqAgCUICIgCyAOQQJ0aioCAJSSkiAgkyIjICOUIAsgDkEBdCAQ
- KAIQIhBqQQJ0aioCACAGIAsgEEECdGoqAgCUICIgCyAOIBBqQQJ0aioCAJSSkiAhkyIjICOUkiAf
- X2o2AgAgEUEIaiERIAhBf2oiCA0ACwsgFyAHRw0ACwsgFEECSA0AIBNBDGohC0EAIQxBASERA0Ag
- ESAMIAsoAgAgEyAMQQN0aigCBEobIQwgC0EIaiELIBQgEUEBaiIRRw0ACwsCQCAHQQFIDQAgEyAM
- QQN0aigCACIRKAIAIgsgESgCECIOQQJ0aioCACEgIAsgEUEUaigCACIRQQN0aioCACEhIAsgEUEC
- dGoqAgAhJCALIBEgDmpBAnRqKgIAISUgCyARQQF0IA5qQQJ0aioCACEmIAsqAgAhJyADKAIAIQ4g
- AigCACEQQQAhEUEAIQsDQAJAICEgJyAQIAIoAhQgC2wiCEECdGoqAgAiBpQgJCAQIAggAigCEGpB
- AnRqKgIAIiKUkpIgDiADKAIUIAtsIghBAnRqKgIAkyIjICOUICYgICAGlCAlICKUkpIgDiAIIAMo
- AhBqQQJ0aioCAJMiBiAGlJIgH19BAXMNACAJIBFBAnRqIAs2AgAgEUEBaiERIAFFDQAgASgCACAB
- KAIUIAtsQQJ0akGAgID8AzYCAAsgByALQQFqIgtHDQALIBFBAkwNAEECIBEQkoCAgAAhG0ECIBEQ
- koCAgAAiHCgCEEECdCEXIBtBFGooAgBBAnQhHiAbKAIQQQJ0IRQgHEEUaigCAEECdCEWIBwoAgAh
- CyADQRRqKAIAIRggGygCACEOIAJBFGooAgAhGSADKAIQIRogAygCACEQIAIoAhAhAyACKAIAIQgg
- CSEHA0AgDiAIIBkgBygCACIMbCICQQJ0aigCADYCACAOIBRqIAggAyACakECdGooAgA2AgAgCyAQ
- IBggDGwiDEECdGooAgA2AgAgCyAXaiAQIBogDGpBAnRqKAIANgIAIAsgFmohCyAOIB5qIQ4gB0EE
- aiEHIBFBf2oiEQ0ACyAAIBsgHBC7gICAABogHBCXgICAABogGxCXgICAABoMAQsgAEEAKgKAiICA
- ABCbgICAABoLAkAgBEEBSA0AIARBAWohESAEQQN0IBNqQXhqIQsDQCALKAIAEJeAgIAAGiALQXhq
- IQsgEUF/aiIRQQFKDQALCyATQcqYgIAAEIeAgIAAGiASEJeAgIAAGiAPEJeAgIAAGiANQeiYgIAA
- EIeAgIAAGiAKQYaZgIAAEIeAgIAAGiAJQaSZgIAAEIeAgIAAGiAAC+IDCAN/An0BfwN9AX8EfQF/
- A30CQAJAIAAoAghBAkcNACABKAIIQQJHDQAgACgCDCIDIAEoAgxHDQAgAigCCEEDRw0AIAIoAgxB
- A0YNAQtBwpmAgAAQgICAgAAgASgCDCEDCwJAIAIoAgAiBCACKAIQIgVBA3RqKgIAIgYgBCACQRRq
- KAIAIgJBAnRqKgIAIgcgBCACQQF0IgggBWpBAnRqKgIAIgmUIAQgAkEDdGoqAgAiCiAEIAIgBWpB
- AnRqKgIAIguUk5QgBCAFQQF0IgwgAmpBAnRqKgIAIg0gCiAEIAVBAnRqKgIAIg6UIAQqAgAiDyAJ
- lJOUkiAPIAuUIAcgDpSTIAQgCCAMakECdGoqAgAiEJSSi7tEje21oPfGsD5jDQACQCADQQFIDQAg
- ACgCEEECdCECIAEoAhBBAnQhCCAAQRRqKAIAQQJ0IQwgAUEUaigCAEECdCERIAAoAgAhBCABKAIA
- IQUDQCAEIAogDyAFKgIAIhKUIAcgBSAIaioCACITlJKSIBAgBiASlCANIBOUkpIiFJU4AgAgBCAC
- aiAJIA4gEpQgCyATlJKSIBSVOAIAIAQgDGohBCAFIBFqIQUgA0F/aiIDDQALCyAADwsgAEEAKgKA
- iICAABCbgICAAAvVAgQDfwZ9An8CfQJAAkAgACgCCEECRw0AIAEoAghBAkcNACAAKAIMIgMgASgC
- DEcNACACKAIIQQJHDQAgAigCDEEDRg0BC0HnmYCAABCAgICAACABKAIMIQMLAkAgA0EBSA0AIAIo
- AgAiBCACKAIQIgVBAnRqKgIAIQYgBCACQRRqKAIAIgJBA3RqKgIAIQcgBCACQQJ0aioCACEIIAQg
- AiAFakECdGoqAgAhCSAEIAJBAXQgBWpBAnRqKgIAIQogBCoCACELIAAoAhBBAnQhAiABKAIQQQJ0
- IQUgAEEUaigCAEECdCEMIAFBFGooAgBBAnQhDSAAKAIAIQQgASgCACEBA0AgBCAHIAsgASoCACIO
- lCAIIAEgBWoqAgAiD5SSkjgCACAEIAJqIAogBiAOlCAJIA+UkpI4AgAgBCAMaiEEIAEgDWohASAD
- QX9qIgMNAAsLIAAL+AcHAX8BfQF/A30DfwF9An8CQAJAAkAgASgCCEECRw0AIAEoAgwiBEEBSA0A
- IAAoAghBAkcNACAAKAIMIARHDQAgAigCCEEDRw0AIAIoAgxBA0cNACADKAIIQQNHDQAgAygCDEED
- Rw0AIASyIQUMAQtBjJqAgAAQgICAgABBACEGIAEoAgwiBLIhBSAEQQBKDQBDAAAAACEHQwAAAAAg
- BZUiCCEJDAELIAEoAhBBAnQhCiABQRRqKAIAQQJ0IQsgASgCACEGQwAAAAAhByAEIQxDAAAAACEN
- A0AgByAGKgIAkiEHIA0gBiAKaioCAJIhDSAGIAtqIQYgDEF/aiIMDQALIA0gBZUhCCAHIAWVIQkg
- ASgCEEECdCEKIAFBFGooAgBBAnQhCyABKAIAIQZDAAAAACEHIAQhDANAIAcgBioCACAJkyINIA2U
- IAYgCmoqAgAgCJMiDSANlJKSIQcgBiALaiEGIAxBf2oiDA0AC0EBIQYLAkAgByAFlZEiB4u7RI3t
- taD3xrA+Y0UNACACEJyAgIAAGiADEJyAgIAAGiADKAIAIgZBgICA/AM2AgAgAigCACIMQYCAgPwD
- NgIAIAYgA0EUaigCACADKAIQaiIKQQJ0akGAgID8AzYCACAMIAJBFGooAgAgAigCEGoiC0ECdGpB
- gICA/AM2AgAgBiAKQQN0akGAgID8AzYCACAMIAtBA3RqQYCAgPwDNgIAIAAgARCVgICAABoPCyAH
- Q/MEtT+VIQ1D8wS1PyAHlSEHAkAgBkUNACAAKAIQQQJ0IQogASgCEEECdCELIABBFGooAgBBAnQh
- DiABQRRqKAIAQQJ0IQ8gACgCACEGIAEoAgAhDANAIAYgByAMKgIAIAmTlDgCACAGIApqIAcgDCAL
- aioCACAIk5Q4AgAgBiAOaiEGIAwgD2ohDCAEQX9qIgQNAAsLIAIoAgAiBiAHOAIAIAYgAkEUaigC
- ACIMQQJ0akEANgIAIAYgDEEDdGogCSAHjCIFlDgCACAGIAIoAhAiCkECdGpBADYCACAGIAogDGoi
- C0ECdGogBzgCACAGIAogDEEBdGpBAnRqIAggBZQ4AgAgBiAKQQN0akEANgIAIAYgDCAKQQF0akEC
- dGpBADYCACAGIAtBA3RqQYCAgPwDNgIAIAMoAgAiBiANOAIAIAYgA0EUaigCACIMQQJ0akEANgIA
- IAYgDEEDdGogCTgCACAGIAMoAhAiCkECdGpBADYCACAGIAogDGoiC0ECdGogDTgCACAGIAogDEEB
- dGpBAnRqIAg4AgAgBiAKQQN0akEANgIAIAYgDCAKQQF0akECdGpBADYCACAGIAtBA3RqQYCAgPwD
- NgIACwv2EgMAQYAIC7ISAAD4f091dCBvZiBtZW1vcnkhAERvdWJsZSBmcmVlAEFzc2VydGlvbiBm
- YWlsZWQgYXQgbWF0MzIuYzo2MQBPdXQgb2YgbWVtb3J5IGF0IG1hdDMyLmM6NjMAQXNzZXJ0aW9u
- IGZhaWxlZCBhdCBtYXQzMi5jOjg0AE91dCBvZiBtZW1vcnkgYXQgbWF0MzIuYzo4NgBPdXQgb2Yg
- bWVtb3J5IGF0IG1hdDMyLmM6ODkAT3V0IG9mIG1lbW9yeSBhdCBtYXQzMi5jOjEzNgAAAGANAAAB
- AAAAAAAAAAAAAAABAAAAAQAAAAIAAABEb3VibGUgZnJlZSBhdCBtYXQzMi5jOjE0OQBBc3NlcnRp
- b24gZmFpbGVkIGF0IG1hdDMyLmM6MTg0AEFzc2VydGlvbiBmYWlsZWQgYXQgbWF0MzIuYzoxODgA
- QXNzZXJ0aW9uIGZhaWxlZCBhdCBtYXQzMi5jOjI3NQBEb3VibGUgZnJlZSBhdCBtYXQzMi5jOjI5
- AEFzc2VydGlvbiBmYWlsZWQgYXQgYXJpdGhtZXRpYzMyLmM6MzYAQXNzZXJ0aW9uIGZhaWxlZCBh
- dCBhcml0aG1ldGljMzIuYzo1OABBc3NlcnRpb24gZmFpbGVkIGF0IGFyaXRobWV0aWMzMi5jOjgw
- AEFzc2VydGlvbiBmYWlsZWQgYXQgYXJpdGhtZXRpYzMyLmM6OTkAQXNzZXJ0aW9uIGZhaWxlZCBh
- dCBhcml0aG1ldGljMzIuYzoxMjEAQXNzZXJ0aW9uIGZhaWxlZCBhdCBhcml0aG1ldGljMzIuYzox
- NDMAQXNzZXJ0aW9uIGZhaWxlZCBhdCBhcml0aG1ldGljMzIuYzoxNjgAQXNzZXJ0aW9uIGZhaWxl
- ZCBhdCBhcml0aG1ldGljMzIuYzoxODkAQXNzZXJ0aW9uIGZhaWxlZCBhdCBhcml0aG1ldGljMzIu
- YzoyMTgAQXNzZXJ0aW9uIGZhaWxlZCBhdCBhcml0aG1ldGljMzIuYzoyNzEAQXNzZXJ0aW9uIGZh
- aWxlZCBhdCBhcml0aG1ldGljMzIuYzozMjIAQXNzZXJ0aW9uIGZhaWxlZCBhdCBhcml0aG1ldGlj
- MzIuYzozNTYAQXNzZXJ0aW9uIGZhaWxlZCBhdCBhcml0aG1ldGljMzIuYzozNzgAQXNzZXJ0aW9u
- IGZhaWxlZCBhdCBhcml0aG1ldGljMzIuYzo0MjAAQXNzZXJ0aW9uIGZhaWxlZCBhdCBhcml0aG1l
- dGljMzIuYzo0MzYAQXNzZXJ0aW9uIGZhaWxlZCBhdCBxcjMyLmM6MjYxAEFzc2VydGlvbiBmYWls
- ZWQgYXQgcXIzMi5jOjI2NQBBc3NlcnRpb24gZmFpbGVkIGF0IHFyMzIuYzoyODYAQXNzZXJ0aW9u
- IGZhaWxlZCBhdCBxcjMyLmM6MjkwAEFzc2VydGlvbiBmYWlsZWQgYXQgcXIzMi5jOjMyMQBBc3Nl
- cnRpb24gZmFpbGVkIGF0IHFyMzIuYzozMjUAQXNzZXJ0aW9uIGZhaWxlZCBhdCBxcjMyLmM6Mzc5
- AE91dCBvZiBtZW1vcnkgYXQgcXIzMi5jOjM2AEFzc2VydGlvbiBmYWlsZWQgYXQgcXIzMi5jOjY5
- AEFzc2VydGlvbiBmYWlsZWQgYXQgcXIzMi5jOjczAEFzc2VydGlvbiBmYWlsZWQgYXQgcXIzMi5j
- OjE4NABEb3VibGUgZnJlZSBhdCBxcjMyLmM6NTUAQXNzZXJ0aW9uIGZhaWxlZCBhdCBxcjMyLmM6
- MTQ4AEFzc2VydGlvbiBmYWlsZWQgYXQgcXIzMi5jOjIyNABBc3NlcnRpb24gZmFpbGVkIGF0IHFy
- MzIuYzoyMjgAQXNzZXJ0aW9uIGZhaWxlZCBhdCBob21vZ3JhcGh5MzIuYzoyNDQAQXNzZXJ0aW9u
- IGZhaWxlZCBhdCBob21vZ3JhcGh5MzIuYzoyODAAQXNzZXJ0aW9uIGZhaWxlZCBhdCBob21vZ3Jh
- cGh5MzIuYzozNTkAQXNzZXJ0aW9uIGZhaWxlZCBhdCBob21vZ3JhcGh5MzIuYzo0NDQAQXNzZXJ0
- aW9uIGZhaWxlZCBhdCBhZmZpbmUzMi5jOjExOQBBc3NlcnRpb24gZmFpbGVkIGF0IGFmZmluZTMy
- LmM6MTk2AEFzc2VydGlvbiBmYWlsZWQgYXQgYWZmaW5lMzIuYzoyMjkAQXNzZXJ0aW9uIGZhaWxl
- ZCBhdCByYW5zYWMzMi5jOjcxAE91dCBvZiBtZW1vcnkgYXQgcmFuc2FjMzIuYzo4NABPdXQgb2Yg
- bWVtb3J5IGF0IHJhbnNhYzMyLmM6ODgAT3V0IG9mIG1lbW9yeSBhdCByYW5zYWMzMi5jOjkzAE91
- dCBvZiBtZW1vcnkgYXQgcmFuc2FjMzIuYzoxMDcARG91YmxlIGZyZWUgYXQgcmFuc2FjMzIuYzoy
- MzYARG91YmxlIGZyZWUgYXQgcmFuc2FjMzIuYzoyNDMARG91YmxlIGZyZWUgYXQgcmFuc2FjMzIu
- YzoyNDYARG91YmxlIGZyZWUgYXQgcmFuc2FjMzIuYzoyNDkAQXNzZXJ0aW9uIGZhaWxlZCBhdCBy
- YW5zYWMzMi5jOjI3NQBPdXQgb2YgbWVtb3J5IGF0IHJhbnNhYzMyLmM6Mjg4AE91dCBvZiBtZW1v
- cnkgYXQgcmFuc2FjMzIuYzoyOTIAT3V0IG9mIG1lbW9yeSBhdCByYW5zYWMzMi5jOjI5NwBPdXQg
- b2YgbWVtb3J5IGF0IHJhbnNhYzMyLmM6MzExAERvdWJsZSBmcmVlIGF0IHJhbnNhYzMyLmM6NDM2
- AERvdWJsZSBmcmVlIGF0IHJhbnNhYzMyLmM6NDQzAERvdWJsZSBmcmVlIGF0IHJhbnNhYzMyLmM6
- NDQ2AERvdWJsZSBmcmVlIGF0IHJhbnNhYzMyLmM6NDQ5AEFzc2VydGlvbiBmYWlsZWQgYXQgdHJh
- bnNmb3JtMzIuYzozOQBBc3NlcnRpb24gZmFpbGVkIGF0IHRyYW5zZm9ybTMyLmM6NzcAQXNzZXJ0
- aW9uIGZhaWxlZCBhdCB0cmFuc2Zvcm0zMi5jOjExNAAAQbQaCwwIAAAAUA0AAAEAAAAAQcAaCyQA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
- `
-
- /***/ })
-
- /******/ });
- /************************************************************************/
- /******/ // The module cache
- /******/ var __webpack_module_cache__ = {};
- /******/
- /******/ // The require function
- /******/ function __nested_webpack_require_312600__(moduleId) {
- /******/ // Check if module is in cache
- /******/ var cachedModule = __webpack_module_cache__[moduleId];
- /******/ if (cachedModule !== undefined) {
- /******/ return cachedModule.exports;
- /******/ }
- /******/ // Create a new module (and put it into the cache)
- /******/ var module = __webpack_module_cache__[moduleId] = {
- /******/ // no module.id needed
- /******/ // no module.loaded needed
- /******/ exports: {}
- /******/ };
- /******/
- /******/ // Execute the module function
- /******/ __webpack_modules__[moduleId](module, module.exports, __nested_webpack_require_312600__);
- /******/
- /******/ // Return the exports of the module
- /******/ return module.exports;
- /******/ }
- /******/
- /************************************************************************/
- /******/ /* webpack/runtime/define property getters */
- /******/ (() => {
- /******/ // define getter functions for harmony exports
- /******/ __nested_webpack_require_312600__.d = (exports, definition) => {
- /******/ for(var key in definition) {
- /******/ if(__nested_webpack_require_312600__.o(definition, key) && !__nested_webpack_require_312600__.o(exports, key)) {
- /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
- /******/ }
- /******/ }
- /******/ };
- /******/ })();
- /******/
- /******/ /* webpack/runtime/hasOwnProperty shorthand */
- /******/ (() => {
- /******/ __nested_webpack_require_312600__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
- /******/ })();
- /******/
- /******/ /* webpack/runtime/make namespace object */
- /******/ (() => {
- /******/ // define __esModule on exports
- /******/ __nested_webpack_require_312600__.r = (exports) => {
- /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
- /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
- /******/ }
- /******/ Object.defineProperty(exports, '__esModule', { value: true });
- /******/ };
- /******/ })();
- /******/
- /************************************************************************/
- var __webpack_exports__ = {};
- // This entry need to be wrapped in an IIFE because it need to be in strict mode.
- (() => {
- "use strict";
-
- // EXPORTS
- __nested_webpack_require_312600__.d(__webpack_exports__, {
- "default": () => (/* binding */ Speedy)
- });
-
- // EXTERNAL MODULE: ./src/gpu/speedy-gl.js
- var speedy_gl = __nested_webpack_require_312600__(7905);
- // EXTERNAL MODULE: ./src/utils/utils.js
- var utils = __nested_webpack_require_312600__(5484);
- // EXTERNAL MODULE: ./src/core/settings.js
- var settings = __nested_webpack_require_312600__(3135);
- // EXTERNAL MODULE: ./src/core/speedy-promise.js
- var speedy_promise = __nested_webpack_require_312600__(4500);
- ;// CONCATENATED MODULE: ./src/utils/asap.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * asap.js
- * Schedule a function to run "as soon as possible"
- */
-
- /** callbacks */
- const callbacks = /** @type {Function[]} */ ( [] );
-
- /** arguments to be passed to the callbacks */
- const args = /** @type {any[][]} */ ( [] );
-
- /** asap key */
- const ASAP_KEY = 'asap' + Math.random().toString(36).substr(1);
-
- // Register an event listener
- window.addEventListener('message', event => {
- if(event.source !== window || event.data !== ASAP_KEY)
- return;
-
- event.stopPropagation();
- if(callbacks.length == 0)
- return;
-
- const fn = callbacks.pop();
- const argArray = args.pop();
- fn.apply(undefined, argArray);
- }, true);
-
- /**
- * Schedule a function to run "as soon as possible"
- * @param {Function} fn callback
- * @param {any[]} params optional parameters
- */
- function asap(fn, ...params)
- {
- callbacks.unshift(fn);
- args.unshift(params);
- window.postMessage(ASAP_KEY, '*');
- }
- // EXTERNAL MODULE: ./src/utils/errors.js
- var utils_errors = __nested_webpack_require_312600__(3841);
- ;// CONCATENATED MODULE: ./src/gpu/speedy-texture-reader.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-texture-reader.js
- * Reads data from textures
- */
-
-
-
-
-
-
-
-
-
- /** @type {number} number of PBOs; used to get a performance boost in gl.readPixels() */
- const DEFAULT_NUMBER_OF_BUFFERS = 2;
-
- /** @type {(fn: Function, ...args: any[]) => number} Run function fn on the "next frame" */
- const runOnNextFrame = navigator.userAgent.includes('Firefox') ?
- ((fn, ...args) => setTimeout(fn, 10, ...args)) : // RAF produces a warning on Firefox
- ((fn, ...args) => requestAnimationFrame(() => fn.apply(undefined, args))); // reduce battery usage
-
- /**
- * Reads data from textures
- */
- class SpeedyTextureReader
- {
- /**
- * Constructor
- * @param {number} [numberOfBuffers]
- */
- constructor(numberOfBuffers = DEFAULT_NUMBER_OF_BUFFERS)
- {
- utils/* Utils.assert */.c.assert(numberOfBuffers > 0);
-
- /** @type {boolean} is this object initialized? */
- this._initialized = false;
-
- /** @type {Uint8Array[]} pixel buffers for data transfers (each stores RGBA data) */
- this._pixelBuffer = (new Array(numberOfBuffers)).fill(null).map(() => new Uint8Array(0));
-
- /** @type {WebGLBuffer[]} Pixel Buffer Objects (PBOs) */
- this._pbo = (new Array(numberOfBuffers)).fill(null);
-
- /** @type {number} the index of the buffer that will be consumed in this frame */
- this._consumerIndex = 0;
-
- /** @type {number} the index of the buffer that will be produced next */
- this._producerIndex = numberOfBuffers - 1;
-
- /** @type {SpeedyPromise<void>[]} producer-consumer promises */
- this._promise = Array.from({ length: numberOfBuffers }, () => speedy_promise/* SpeedyPromise.resolve */.s.resolve());
-
- /** @type {boolean[]} are the contents of the ith buffer being produced? */
- this._busy = (new Array(numberOfBuffers)).fill(false);
-
- /** @type {boolean[]} can the ith buffer be consumed? */
- this._ready = (new Array(numberOfBuffers)).fill(true);
- }
-
- /**
- * Initialize this object
- * @param {SpeedyGPU} gpu
- */
- init(gpu)
- {
- this._allocatePBOs(gpu);
- gpu.subscribe(this._allocatePBOs, this, gpu);
-
- this._initialized = true;
- }
-
- /**
- * Release resources
- * @param {SpeedyGPU} gpu
- * @returns {null}
- */
- release(gpu)
- {
- gpu.unsubscribe(this._allocatePBOs, this);
- this._deallocatePBOs(gpu);
-
- this._initialized = false;
- return null;
- }
-
- /**
- * Read pixels from a texture, synchronously.
- * You may optionally specify a (x,y,width,height) sub-rectangle.
- * @param {SpeedyDrawableTexture} texture a texture with a FBO
- * @param {number} [x]
- * @param {number} [y]
- * @param {number} [width]
- * @param {number} [height]
- * @returns {Uint8Array} pixels in the RGBA format
- */
- readPixelsSync(texture, x = 0, y = 0, width = texture.width, height = texture.height)
- {
- utils/* Utils.assert */.c.assert(this._initialized);
-
- const gl = texture.gl;
- const fbo = texture.glFbo;
-
- // clamp values
- width = Math.max(0, Math.min(width, texture.width));
- height = Math.max(0, Math.min(height, texture.height));
- x = Math.max(0, Math.min(x, texture.width - width));
- y = Math.max(0, Math.min(y, texture.height - height));
-
- // buffer allocation
- const sizeofBuffer = width * height * 4; // 4 bytes per pixel (RGBA)
- this._reallocate(sizeofBuffer);
-
- // lost context?
- if(gl.isContextLost())
- return this._pixelBuffer[0].subarray(0, sizeofBuffer);
-
- // read pixels
- gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
- gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this._pixelBuffer[0]);
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
-
- // done!
- return this._pixelBuffer[0].subarray(0, sizeofBuffer);
- }
-
- /**
- * Read pixels from a texture, asynchronously, with PBOs.
- * You may optionally specify a (x,y,width,height) sub-rectangle.
- * @param {SpeedyDrawableTexture} texture a texture with a FBO
- * @param {number} [x]
- * @param {number} [y]
- * @param {number} [width]
- * @param {number} [height]
- * @param {boolean} [useBufferedDownloads] accelerate downloads by returning pixels from the texture of the previous call (useful for streaming)
- * @returns {SpeedyPromise<Uint8Array>} resolves to an array of pixels in the RGBA format
- */
- readPixelsAsync(texture, x = 0, y = 0, width = texture.width, height = texture.height, useBufferedDownloads = false)
- {
- utils/* Utils.assert */.c.assert(this._initialized);
-
- const gl = texture.gl;
- const fbo = texture.glFbo;
-
- // clamp values
- width = Math.max(0, Math.min(width, texture.width));
- height = Math.max(0, Math.min(height, texture.height));
- x = Math.max(0, Math.min(x, texture.width - width));
- y = Math.max(0, Math.min(y, texture.height - height));
-
- // buffer allocation
- const sizeofBuffer = width * height * 4; // 4 bytes per pixel (RGBA)
- this._reallocate(sizeofBuffer);
-
- // lost context?
- if(gl.isContextLost())
- return speedy_promise/* SpeedyPromise.resolve */.s.resolve(this._pixelBuffer[0].subarray(0, sizeofBuffer));
-
- // do not optimize?
- if(!useBufferedDownloads) {
- const pixelBuffer = this._pixelBuffer[0].subarray(0, sizeofBuffer);
- return SpeedyTextureReader._readPixelsViaPBO(gl, this._pbo[0], pixelBuffer, fbo, x, y, width, height).then(() =>
- pixelBuffer
- );
- }
-
- // Hide latency with a Producer-Consumer mechanism
- const numberOfBuffers = this._pixelBuffer.length;
-
- // GPU needs to produce data
- const producerIndex = this._producerIndex;
-
- if(!this._busy[producerIndex]) {
- const pbo = this._pbo[producerIndex];
- const pixelBuffer = this._pixelBuffer[producerIndex].subarray(0, sizeofBuffer);
-
- this._producerIndex = (producerIndex + 1) % numberOfBuffers;
-
- this._ready[producerIndex] = false;
- this._busy[producerIndex] = true;
- //console.time("produce "+producerIndex);
- this._promise[producerIndex] = SpeedyTextureReader._readPixelsViaPBO(gl, pbo, pixelBuffer, fbo, x, y, width, height).then(() => {
- //console.timeEnd("produce "+producerIndex);
- this._busy[producerIndex] = false;
- this._ready[producerIndex] = true;
- });
- }
- //else console.log("skip",producerIndex);
- else /* skip frame */ ;
-
- // CPU needs to consume data
- const consumerIndex = this._consumerIndex;
- this._consumerIndex = (consumerIndex + 1) % numberOfBuffers;
-
- if(!this._ready[consumerIndex]) {
- //console.time("consume "+consumerIndex);
- return this._promise[consumerIndex].then(() => {
- //console.timeEnd("consume "+consumerIndex);
- this._ready[consumerIndex] = false;
- return this._pixelBuffer[consumerIndex];
- });
- }
-
- //console.log("NO WAIT "+consumerIndex);
- this._ready[consumerIndex] = false;
- return speedy_promise/* SpeedyPromise.resolve */.s.resolve(this._pixelBuffer[consumerIndex]);
- }
-
- /**
- * Reallocate the pixel buffers, so that they can hold the required number of bytes
- * If the pixel buffers already have the required capacity, then nothing is done
- * @param {number} size in bytes
- */
- _reallocate(size)
- {
- // no need to reallocate
- if(size <= this._pixelBuffer[0].byteLength)
- return;
-
- // reallocate
- for(let i = 0; i < this._pixelBuffer.length; i++) {
- const newBuffer = new Uint8Array(size);
- //newBuffer.set(this._pixelBuffer[i]); // make this optional?
- this._pixelBuffer[i] = newBuffer;
- }
- }
-
- /**
- * Allocate PBOs
- * @param {SpeedyGPU} gpu
- */
- _allocatePBOs(gpu)
- {
- const gl = gpu.gl;
-
- for(let i = 0; i < this._pbo.length; i++)
- this._pbo[i] = gl.createBuffer();
- }
-
- /**
- * Deallocate PBOs
- * @param {SpeedyGPU} gpu
- */
- _deallocatePBOs(gpu)
- {
- const gl = gpu.gl;
-
- for(let i = this._pbo.length - 1; i >= 0; i--) {
- gl.deleteBuffer(this._pbo[i]);
- this._pbo[i] = null;
- }
- }
-
- /**
- * Read pixels to a Uint8Array, asynchronously, using a Pixel Buffer Object (PBO)
- * It's assumed that the target texture is in the RGBA8 format
- * @param {WebGL2RenderingContext} gl
- * @param {WebGLBuffer} pbo
- * @param {Uint8Array} outputBuffer with size >= width * height * 4
- * @param {WebGLFramebuffer} fbo
- * @param {GLint} x
- * @param {GLint} y
- * @param {GLsizei} width
- * @param {GLsizei} height
- * @returns {SpeedyPromise<void>}
- */
- static _readPixelsViaPBO(gl, pbo, outputBuffer, fbo, x, y, width, height)
- {
- /*
-
- When testing Speedy on Chrome (mobile) using about:tracing with the
- --enable-gpu-service-tracing flag, I found that A LOT of time is spent
- in TraceGLAPI::glMapBufferRange, which takes place just after
- GLES2DecoderImpl::HandleReadPixels and GLES2DecoderImpl::glReadPixels.
-
- Using multiple PBOs doesn't seem to impact Chrome too much. Performance
- is much better on Firefox. This suggests there is room for improvement.
- I do not yet understand clearly the cause for this lag on Chrome. It
- may be a CPU-GPU synchronization issue.
-
- EDIT: I have found that using gl.flush() aggressively greatly improves
- things. WebGL commands will be pushed frequently!
-
- See also:
- https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3 (Buffer objects)
- https://github.com/chromium/chromium/blob/master/docs/gpu/debugging_gpu_related_code.md
-
- */
- const size = width * height * 4;
-
- // validate outputBuffer
- utils/* Utils.assert */.c.assert(outputBuffer.byteLength >= size, `Invalid buffer size`);
-
- // read pixels into the PBO
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
- gl.bufferData(gl.PIXEL_PACK_BUFFER, size, gl.DYNAMIC_READ);
- gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
- gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, 0);
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
-
- // create a fence
- const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
- gl.flush(); // make sure the sync command is read
-
- // wait for the commands to be processed by the GPU
- return new speedy_promise/* SpeedyPromise */.s((resolve, reject) => {
-
- // according to the WebGL2 spec sec 3.7.14 Sync objects,
- // "sync objects may only transition to the signaled state
- // when the user agent's event loop is not executing a task"
- // in other words, it won't be signaled in the same frame
- if(settings/* Settings.gpuPollingMode */.Z.gpuPollingMode != 'asap')
- runOnNextFrame(SpeedyTextureReader._clientWaitAsync, gl, sync, 0, resolve, reject);
- else
- asap(SpeedyTextureReader._clientWaitAsync, gl, sync, 0, resolve, reject);
-
- }).then(() => {
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
- gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, outputBuffer);
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
- }).catch(err => {
- throw new utils_errors/* IllegalOperationError */.js(`Can't getBufferSubDataAsync(): error in clientWaitAsync()`, err);
- }).finally(() => {
- gl.deleteSync(sync);
- });
- }
-
- /**
- * Waits for a sync object to become signaled
- * @param {WebGL2RenderingContext} gl
- * @param {WebGLSync} sync
- * @param {GLbitfield} flags may be gl.SYNC_FLUSH_COMMANDS_BIT or 0
- * @param {Function} resolve
- * @param {Function} reject
- * @param {number} [pollInterval] in milliseconds
- * @param {number} [remainingAttempts] for timeout
- */
- static _clientWaitAsync(gl, sync, flags, resolve, reject, pollInterval = 10, remainingAttempts = 1000)
- {
- (function poll() {
- const status = gl.clientWaitSync(sync, flags, 0);
-
- if(remainingAttempts-- <= 0) {
- reject(new utils_errors/* TimeoutError */.W5(`GPU polling timeout`, utils_errors/* GLError.from */.Ql.from(gl)));
- }
- else if(status === gl.CONDITION_SATISFIED || status === gl.ALREADY_SIGNALED) {
- resolve();
- }
- else {
- //setTimeout(poll, pollInterval);
- if(settings/* Settings.gpuPollingMode */.Z.gpuPollingMode != 'asap')
- requestAnimationFrame(poll); // RAF is a rather unusual way to do polling at ~60 fps. Does it reduce CPU usage?
- else
- asap(poll);
- }
- })();
- }
- }
- // EXTERNAL MODULE: ./src/utils/globals.js
- var globals = __nested_webpack_require_312600__(3020);
- ;// CONCATENATED MODULE: ./src/gpu/speedy-texture.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-texture.js
- * A wrapper around WebGLTexture
- */
-
-
-
-
-
-
-
- /**
- * Get a buffer filled with zeros
- * @param {number} size number of bytes
- * @returns {Uint8Array}
- */
- /*
- const zeros = (function() {
- let buffer = new Uint8Array(4);
-
- return function(size) {
- if(size > buffer.length)
- buffer = new Uint8Array(size);
-
- return buffer.subarray(0, size);
- }
- })();
- */
-
- /**
- * A wrapper around WebGLTexture
- */
- class SpeedyTexture
- {
- /**
- * Constructor
- * @param {WebGL2RenderingContext} gl
- * @param {number} width texture width in pixels
- * @param {number} height texture height in pixels
- * @param {number} [format]
- * @param {number} [internalFormat]
- * @param {number} [dataType]
- * @param {number} [filter]
- * @param {number} [wrap]
- */
- constructor(gl, width, height, format = gl.RGBA, internalFormat = gl.RGBA8, dataType = gl.UNSIGNED_BYTE, filter = gl.NEAREST, wrap = gl.MIRRORED_REPEAT)
- {
- /** @type {WebGL2RenderingContext} rendering context */
- this._gl = gl;
-
- /** @type {number} width of the texture */
- this._width = Math.max(1, width | 0);
-
- /** @type {number} height of the texture */
- this._height = Math.max(1, height | 0);
-
- /** @type {boolean} have we generated mipmaps for this texture? */
- this._hasMipmaps = false;
-
- /** @type {number} texture format */
- this._format = format;
-
- /** @type {number} internal format (usually a sized format) */
- this._internalFormat = internalFormat;
-
- /** @type {number} data type */
- this._dataType = dataType;
-
- /** @type {number} texture filtering (min & mag) */
- this._filter = filter;
-
- /** @type {number} texture wrapping */
- this._wrap = wrap;
-
- /** @type {WebGLTexture} internal texture object */
- this._glTexture = SpeedyTexture._createTexture(this._gl, this._width, this._height, this._format, this._internalFormat, this._dataType, this._filter, this._wrap);
- }
-
- /**
- * Releases the texture
- * @returns {null}
- */
- release()
- {
- const gl = this._gl;
-
- // already released?
- if(this._glTexture == null)
- throw new utils_errors/* IllegalOperationError */.js(`The SpeedyTexture has already been released`);
-
- // release resources
- this.discardMipmaps();
- gl.deleteTexture(this._glTexture);
- this._glTexture = null;
- this._width = this._height = 0;
-
- // done!
- return null;
- }
-
- /**
- * Upload pixel data to the texture. The texture will be resized if needed.
- * @param {TexImageSource} pixels
- * @param {number} [width] in pixels
- * @param {number} [height] in pixels
- * @return {SpeedyTexture} this
- */
- upload(pixels, width = this._width, height = this._height)
- {
- const gl = this._gl;
- utils/* Utils.assert */.c.assert(width > 0 && height > 0);
-
- this.discardMipmaps();
- this._width = width;
- this._height = height;
- this._internalFormat = gl.RGBA8;
- this._format = gl.RGBA;
- this._dataType = gl.UNSIGNED_BYTE;
-
- SpeedyTexture._upload(gl, this._glTexture, this._width, this._height, pixels, 0, this._format, this._internalFormat, this._dataType);
- return this;
- }
-
- /**
- * Clear the texture
- * @returns {this}
- */
- clear()
- {
- const gl = this._gl;
-
- // context loss?
- if(gl.isContextLost())
- return this;
-
- // clear texture data
- gl.bindTexture(gl.TEXTURE_2D, this._glTexture);
- gl.texImage2D(gl.TEXTURE_2D, 0, this._internalFormat, this._width, this._height, 0, this._format, this._dataType, null);
- gl.bindTexture(gl.TEXTURE_2D, null);
-
- // no mipmaps
- this.discardMipmaps();
-
- // done!
- return this;
- }
-
- /**
- * Resize this texture. Its content will be lost!
- * @param {number} width new width, in pixels
- * @param {number} height new height, in pixels
- * @returns {this}
- */
- resize(width, height)
- {
- const gl = this._gl;
-
- // no need to resize?
- if(this._width === width && this._height === height)
- return this;
-
- // validate size
- width |= 0; height |= 0;
- if(width > globals.MAX_TEXTURE_LENGTH || height > globals.MAX_TEXTURE_LENGTH)
- throw new utils_errors/* NotSupportedError */.B8(`Maximum texture size exceeded. Using ${width} x ${height}, expected up to ${globals.MAX_TEXTURE_LENGTH} x ${globals.MAX_TEXTURE_LENGTH}.`);
- else if(width < 1 || height < 1)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid texture size: ${width} x ${height}`);
-
- // context loss?
- if(gl.isContextLost())
- return this;
-
- // update dimensions
- this._width = width;
- this._height = height;
-
- // resize
- // Note: this is fast on Chrome, but seems slow on Firefox
- gl.bindTexture(gl.TEXTURE_2D, this._glTexture);
- gl.texImage2D(gl.TEXTURE_2D, 0, this._internalFormat, this._width, this._height, 0, this._format, this._dataType, null);
- gl.bindTexture(gl.TEXTURE_2D, null);
-
- // no mipmaps
- this.discardMipmaps();
-
- // done!
- return this;
- }
-
- /**
- * Generate mipmap
- * @param {SpeedyDrawableTexture[]} [mipmap] custom texture for each mip level
- * @returns {SpeedyTexture} this
- */
- generateMipmaps(mipmap = [])
- {
- const gl = this._gl;
-
- // nothing to do
- if(this._hasMipmaps)
- return this;
-
- // let the hardware compute the all levels of the pyramid, up to 1x1
- // we also specify the TEXTURE_MIN_FILTER to be used from now on
- gl.bindTexture(gl.TEXTURE_2D, this._glTexture);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);
- gl.generateMipmap(gl.TEXTURE_2D);
- gl.bindTexture(gl.TEXTURE_2D, null);
-
- // accept custom textures
- if(mipmap.length > 0) {
- // expected number of mipmap levels according to the OpenGL ES 3.0 spec (sec 3.8.10.4)
- const width = this.width, height = this.height;
- const numMipmaps = 1 + Math.floor(Math.log2(Math.max(width, height)));
- utils/* Utils.assert */.c.assert(mipmap.length <= numMipmaps);
-
- // verify the dimensions of each level
- for(let level = 1; level < mipmap.length; level++) {
- // use max(1, floor(size / 2^lod)), in accordance to
- // the OpenGL ES 3.0 spec sec 3.8.10.4 (Mipmapping)
- const w = Math.max(1, width >>> level);
- const h = Math.max(1, height >>> level);
-
- // verify the dimensions of this level
- utils/* Utils.assert */.c.assert(mipmap[level].width === w && mipmap[level].height === h);
-
- // copy to mipmap
- mipmap[level].copyTo(this, level);
- }
- }
-
- // done!
- this._hasMipmaps = true;
- return this;
- }
-
- /**
- * Invalidates previously generated mipmap, if any
- */
- discardMipmaps()
- {
- const gl = this._gl;
-
- // nothing to do
- if(!this._hasMipmaps)
- return;
-
- // reset the min filter
- gl.bindTexture(gl.TEXTURE_2D, this._glTexture);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this._filter);
- gl.bindTexture(gl.TEXTURE_2D, null);
-
- // done!
- this._hasMipmaps = false;
- }
-
- /**
- * Does this texture have a mipmap?
- * @returns {boolean}
- */
- hasMipmaps()
- {
- return this._hasMipmaps;
- }
-
- /**
- * Has this texture been released?
- * @returns {boolean}
- */
- isReleased()
- {
- return this._glTexture == null;
- }
-
- /**
- * The internal WebGLTexture
- * @returns {WebGLTexture}
- */
- get glTexture()
- {
- return this._glTexture;
- }
-
- /**
- * The width of the texture, in pixels
- * @returns {number}
- */
- get width()
- {
- return this._width;
- }
-
- /**
- * The height of the texture, in pixels
- * @returns {number}
- */
- get height()
- {
- return this._height;
- }
-
- /**
- * The WebGL Context
- * @returns {WebGL2RenderingContext}
- */
- get gl()
- {
- return this._gl;
- }
-
- /**
- * Create a WebGL texture
- * @param {WebGL2RenderingContext} gl
- * @param {number} width in pixels
- * @param {number} height in pixels
- * @param {number} format usually gl.RGBA
- * @param {number} internalFormat usually gl.RGBA8
- * @param {number} dataType usually gl.UNSIGNED_BYTE
- * @param {number} filter usually gl.NEAREST or gl.LINEAR
- * @param {number} wrap gl.REPEAT, gl.MIRRORED_REPEAT or gl.CLAMP_TO_EDGE
- * @returns {WebGLTexture}
- */
- static _createTexture(gl, width, height, format, internalFormat, dataType, filter, wrap)
- {
- utils/* Utils.assert */.c.assert(width > 0 && height > 0);
-
- // create & bind texture
- const texture = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_2D, texture);
-
- // setup
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);
- //gl.texStorage2D(gl.TEXTURE_2D, 1, internalFormat, width, height);
- gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, dataType, null);
-
- // unbind & return
- gl.bindTexture(gl.TEXTURE_2D, null);
- return texture;
- }
- /**
- * Upload pixel data to a WebGL texture
- * @param {WebGL2RenderingContext} gl
- * @param {WebGLTexture} texture
- * @param {GLsizei} width texture width
- * @param {GLsizei} height texture height
- * @param {TexImageSource} pixels
- * @param {GLint} lod mipmap level-of-detail
- * @param {number} format
- * @param {number} internalFormat
- * @param {number} dataType
- * @returns {WebGLTexture} texture
- */
- static _upload(gl, texture, width, height, pixels, lod, format, internalFormat, dataType)
- {
- // Prefer calling _upload() before gl.useProgram() to avoid the
- // needless switching of GL programs internally. See also:
- // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices
- gl.bindTexture(gl.TEXTURE_2D, texture);
-
- /*
- // slower than texImage2D, unlike the spec?
- gl.texSubImage2D(gl.TEXTURE_2D, // target
- lod, // mip level
- 0, // x-offset
- 0, // y-offset
- width, // texture width
- height, // texture height
- gl.RGBA, // source format
- gl.UNSIGNED_BYTE, // source type
- pixels); // source data
- */
-
- gl.texImage2D(gl.TEXTURE_2D, // target
- lod, // mip level
- internalFormat, // internal format
- width, // texture width
- height, // texture height
- 0, // border
- format, // source format
- dataType, // source type
- pixels); // source data
-
- gl.bindTexture(gl.TEXTURE_2D, null);
- return texture;
- }
- }
-
- /**
- * A SpeedyTexture with a framebuffer
- */
- class SpeedyDrawableTexture extends SpeedyTexture
- {
- /**
- * Constructor
- * @param {WebGL2RenderingContext} gl
- * @param {number} width texture width in pixels
- * @param {number} height texture height in pixels
- * @param {number} [format]
- * @param {number} [internalFormat]
- * @param {number} [dataType]
- * @param {number} [filter]
- * @param {number} [wrap]
- */
- constructor(gl, width, height, format = undefined, internalFormat = undefined, dataType = undefined, filter = undefined, wrap = undefined)
- {
- super(gl, width, height, format, internalFormat, dataType, filter, wrap);
-
- /** @type {WebGLFramebuffer} framebuffer */
- this._glFbo = SpeedyDrawableTexture._createFramebuffer(gl, this._glTexture);
- }
-
- /**
- * Releases the texture
- * @returns {null}
- */
- release()
- {
- const gl = this._gl;
-
- // already released?
- if(this._glFbo == null)
- throw new utils_errors/* IllegalOperationError */.js(`The SpeedyDrawableTexture has already been released`);
-
- // release the framebuffer
- gl.deleteFramebuffer(this._glFbo);
- this._glFbo = null;
-
- // release the SpeedyTexture
- return super.release();
- }
-
- /**
- * The internal WebGLFramebuffer
- * @returns {WebGLFramebuffer}
- */
- get glFbo()
- {
- return this._glFbo;
- }
-
- /**
- * Copy this texture into another
- * (you may have to discard the mipmaps after calling this function)
- * @param {SpeedyTexture} texture target texture
- * @param {number} [lod] level-of-detail of the target texture
- */
- copyTo(texture, lod = 0)
- {
- const gl = this._gl;
-
- // context loss?
- if(gl.isContextLost())
- return;
-
- // compute texture size as max(1, floor(size / 2^lod)),
- // in accordance to the OpenGL ES 3.0 spec sec 3.8.10.4
- // (Mipmapping)
- const pot = 1 << (lod |= 0);
- const expectedWidth = Math.max(1, Math.floor(texture.width / pot));
- const expectedHeight = Math.max(1, Math.floor(texture.height / pot));
-
- // validate
- utils/* Utils.assert */.c.assert(this._width === expectedWidth && this._height === expectedHeight);
-
- // copy to texture
- SpeedyDrawableTexture._copyToTexture(gl, this._glFbo, texture.glTexture, 0, 0, this._width, this._height, lod);
- }
-
- /*
- * Resize this texture
- * @param {number} width new width, in pixels
- * @param {number} height new height, in pixels
- * @param {boolean} [preserveContent] should we preserve the content of the texture? EXPENSIVE!
- * @returns {this}
- */
- /*resize(width, height, preserveContent = false)
- {
- const gl = this._gl;
-
- // no need to preserve the content?
- if(!preserveContent)
- return super.resize(width, height);
-
- // no need to resize?
- if(this._width === width && this._height === height)
- return this;
-
- // validate size
- width |= 0; height |= 0;
- Utils.assert(width > 0 && height > 0);
-
- // context loss?
- if(gl.isContextLost())
- return this;
-
- // allocate new texture
- const newTexture = SpeedyTexture._createTexture(gl, width, height);
-
- // initialize the new texture with zeros to avoid a
- // warning when calling copyTexSubImage2D() on Firefox
- // this may not be very efficient?
- SpeedyTexture._upload(gl, newTexture, width, height, zeros(width * height * 4)); // RGBA: 4 bytes per pixel
-
- // copy the old texture to the new one
- const oldWidth = this._width, oldHeight = this._height;
- SpeedyDrawableTexture._copyToTexture(gl, this._glFbo, newTexture, 0, 0, Math.min(width, oldWidth), Math.min(height, oldHeight), 0);
-
- // bind FBO
- gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFbo);
-
- // invalidate old data (is this needed?)
- gl.invalidateFramebuffer(gl.FRAMEBUFFER, [gl.COLOR_ATTACHMENT0]);
-
- // attach the new texture to the existing framebuffer
- gl.framebufferTexture2D(gl.FRAMEBUFFER, // target
- gl.COLOR_ATTACHMENT0, // color buffer
- gl.TEXTURE_2D, // tex target
- newTexture, // texture
- 0); // mipmap level
-
- // unbind FBO
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
-
- // release the old texture and replace it
- gl.deleteTexture(this._glTexture);
- this._glTexture = newTexture;
-
- // update dimensions & discard mipmaps
- this.discardMipmaps();
- this._width = width;
- this._height = height;
-
- // done!
- return this;
- }
- */
-
- /**
- * Clear the texture
- * @returns {this}
- */
- clear()
- {
- //
- // When we pass null to texImage2D(), it seems that Firefox
- // doesn't clear the texture. Instead, it displays this warning:
- //
- // "WebGL warning: drawArraysInstanced:
- // Tex image TEXTURE_2D level 0 is incurring lazy initialization."
- //
- // Here is a workaround:
- //
- return this.clearToColor(0, 0, 0, 0);
- }
-
- /**
- * Clear the texture to a color
- * @param {number} r red component, a value in [0,1]
- * @param {number} g green component, a value in [0,1]
- * @param {number} b blue component, a value in [0,1]
- * @param {number} a alpha component, a value in [0,1]
- * @returns {this}
- */
- clearToColor(r, g, b, a)
- {
- const gl = this._gl;
-
- // context loss?
- if(gl.isContextLost())
- return this;
-
- // clamp parameters
- r = Math.max(0.0, Math.min(+r, 1.0));
- g = Math.max(0.0, Math.min(+g, 1.0));
- b = Math.max(0.0, Math.min(+b, 1.0));
- a = Math.max(0.0, Math.min(+a, 1.0));
-
- // discard mipmaps, if any
- this.discardMipmaps();
-
- // clear the texture
- gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFbo);
- gl.viewport(0, 0, this._width, this._height);
- gl.clearColor(r, g, b, a);
- gl.clear(gl.COLOR_BUFFER_BIT);
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
-
- // done!
- return this;
- }
-
- /**
- * Inspect the pixels of the texture for debugging purposes
- * @param {SpeedyGPU} gpu
- * @param {SpeedyTextureReader} [textureReader] optional texture reader
- * @returns {Uint8Array}
- */
- inspect(gpu, textureReader)
- {
- if(textureReader === undefined) {
- textureReader = new SpeedyTextureReader();
- textureReader.init(gpu);
- const pixels = textureReader.readPixelsSync(this);
- textureReader.release(gpu);
-
- return new Uint8Array(pixels); // copy the array
- }
- else {
- const pixels = textureReader.readPixelsSync(this);
- return new Uint8Array(pixels);
- }
- }
-
- /**
- * Inspect the pixels of the texture as unsigned 32-bit integers
- * @param {SpeedyGPU} gpu
- * @param {SpeedyTextureReader} [textureReader] optional texture reader
- * @returns {Uint32Array}
- */
- inspect32(gpu, textureReader)
- {
- utils/* Utils.assert */.c.assert(globals.LITTLE_ENDIAN); // make sure we use little-endian
- return new Uint32Array(this.inspect(gpu, textureReader).buffer);
- }
-
- /**
- * Create a FBO associated with an existing texture
- * @param {WebGL2RenderingContext} gl
- * @param {WebGLTexture} texture
- * @returns {WebGLFramebuffer}
- */
- static _createFramebuffer(gl, texture)
- {
- const fbo = gl.createFramebuffer();
-
- // setup framebuffer
- gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
- gl.framebufferTexture2D(gl.FRAMEBUFFER, // target
- gl.COLOR_ATTACHMENT0, // color buffer
- gl.TEXTURE_2D, // tex target
- texture, // texture
- 0); // mipmap level
-
- // check for errors
- const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
- if(status != gl.FRAMEBUFFER_COMPLETE) {
- const error = (() => (([
- 'FRAMEBUFFER_UNSUPPORTED',
- 'FRAMEBUFFER_INCOMPLETE_ATTACHMENT',
- 'FRAMEBUFFER_INCOMPLETE_DIMENSIONS',
- 'FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT',
- 'FRAMEBUFFER_INCOMPLETE_MULTISAMPLE'
- ].filter(err => gl[err] === status))[0] || 'unknown error'))();
- throw new utils_errors/* GLError */.Ql(`Can't create framebuffer: ${error} (${status})`);
- }
-
- // unbind & return
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- return fbo;
- }
-
- /**
- * Copy data from a framebuffer to a texture
- * @param {WebGL2RenderingContext} gl
- * @param {WebGLFramebuffer} fbo we'll read the data from this
- * @param {WebGLTexture} texture destination texture
- * @param {GLint} x xpos (where to start copying)
- * @param {GLint} y ypos (where to start copying)
- * @param {GLsizei} width width of the texture
- * @param {GLsizei} height height of the texture
- * @param {GLint} [lod] mipmap level-of-detail
- * @returns {WebGLTexture} texture
- */
- static _copyToTexture(gl, fbo, texture, x, y, width, height, lod = 0)
- {
- //gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
-
- gl.copyTexSubImage2D(
- gl.TEXTURE_2D, // target
- lod, // mipmap level
- 0, // xoffset
- 0, // yoffset
- x, // xpos (where to start copying)
- y, // ypos (where to start copying)
- width, // width of the texture
- height // height of the texture
- );
-
- /*
- gl.copyTexImage2D(
- gl.TEXTURE_2D, // target
- lod, // mipmap level
- gl.RGBA, // internal format
- x, // xpos (where to start copying)
- y, // ypos (where to start copying)
- width, // width of the texture
- height, // height of the texture
- 0 // border
- );
- */
-
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- gl.bindTexture(gl.TEXTURE_2D, null);
-
- return texture;
- }
- }
- // EXTERNAL MODULE: ./src/gpu/shader-declaration.js + 1 modules
- var shader_declaration = __nested_webpack_require_312600__(9759);
- ;// CONCATENATED MODULE: ./src/gpu/speedy-program.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-program.js
- * SpeedyProgram class
- */
-
-
-
-
-
-
-
- /** @const {Object<string,string>} Map uniform type to a gl function */
- const UNIFORM_SETTERS = Object.freeze({
- 'sampler2D': 'uniform1i',
- 'isampler2D':'uniform1i',
- 'usampler2D':'uniform1i',
- 'float': 'uniform1f',
- 'int': 'uniform1i',
- 'uint': 'uniform1ui',
- 'bool': 'uniform1i',
- 'vec2': 'uniform2f',
- 'vec3': 'uniform3f',
- 'vec4': 'uniform4f',
- 'ivec2': 'uniform2i',
- 'ivec3': 'uniform3i',
- 'ivec4': 'uniform4i',
- 'uvec2': 'uniform2ui',
- 'uvec3': 'uniform3ui',
- 'uvec4': 'uniform4ui',
- 'bvec2': 'uniform2i',
- 'bvec3': 'uniform3i',
- 'bvec4': 'uniform4i',
- 'mat2': 'uniformMatrix2fv',
- 'mat3': 'uniformMatrix3fv',
- 'mat4': 'uniformMatrix4fv',
- });
-
- /**
- * @typedef {object} SpeedyProgramOptions
- * @property {boolean} [renderToTexture] render results to a texture?
- * @property {boolean} [pingpong] alternate output texture between calls
- */
-
- /** @typedef {number|number[]|boolean|boolean[]|SpeedyTexture} SpeedyProgramUniformValue */
-
- /**
- * A SpeedyProgram is a Function that
- * runs GPU-accelerated GLSL code
- */
- class SpeedyProgram extends Function
- {
- /**
- * Creates a new SpeedyProgram
- * @param {WebGL2RenderingContext} gl WebGL context
- * @param {ShaderDeclaration} shaderdecl Shader declaration
- * @param {SpeedyProgramOptions} [options] user options
- */
- constructor(gl, shaderdecl, options = { })
- {
- super('...args', 'return this._self._call(...args)');
-
- /** @type {SpeedyProgram} this function bound to this function! */
- this._self = this.bind(this);
-
- this._self._init(gl, shaderdecl, options);
- return this._self;
- }
-
- /**
- * Initialize the SpeedyProgram
- * @param {WebGL2RenderingContext} gl WebGL context
- * @param {ShaderDeclaration} shaderdecl Shader declaration
- * @param {SpeedyProgramOptions} options user options
- */
- _init(gl, shaderdecl, options)
- {
- // not a valid context?
- if(gl.isContextLost())
- throw new utils_errors/* IllegalOperationError */.js(`Can't initialize SpeedyProgram: lost context`);
-
- // options object
- options = Object.assign({
- // default options
- renderToTexture: true,
- pingpong: false,
- }, options);
-
-
-
- /** @type {WebGL2RenderingContext} */
- this._gl = gl;
-
- /** @type {WebGLProgram} vertex shader + fragment shader */
- this._program = SpeedyProgram._compile(gl, shaderdecl.vertexSource, shaderdecl.fragmentSource);
-
- /** @type {ProgramGeometry} this is a quad */
- this._geometry = new ProgramGeometry(gl, {
- position: shaderdecl.locationOfAttributes.position,
- texCoord: shaderdecl.locationOfAttributes.texCoord
- });
-
- /** @type {string[]} names of the arguments of the SpeedyProgram */
- this._argnames = shaderdecl.arguments;
-
- /** @type {boolean[]} tells whether the i-th argument of the SpeedyProgram is an array or not */
- this._argIsArray = (new Array(this._argnames.length)).fill(false);
-
- /** @type {UBOHelper} UBO helper (lazy instantiation) */
- this._ubo = null;
-
- /** @type {boolean} should we render to a texture? If false, we render to the canvas */
- this._renderToTexture = Boolean(options.renderToTexture);
-
- /** @type {number} width of the output */
- this._width = 1;
-
- /** @type {number} height of the output */
- this._height = 1;
-
- /** @type {SpeedyDrawableTexture[]} output texture(s) */
- this._texture = (new Array(options.pingpong ? 2 : 1)).fill(null);
-
- /** @type {number} used for pingpong rendering */
- this._textureIndex = 0;
-
- /** @type {Map<string,UniformVariable>} uniform variables */
- this._uniform = new Map();
-
- /** @type {ShaderDeclaration} shader declaration */
- this._shaderdecl = shaderdecl;
-
-
- // autodetect uniforms
- gl.useProgram(this._program);
- for(const name of shaderdecl.uniforms) {
- const type = shaderdecl.uniformType(name);
- const location = gl.getUniformLocation(this._program, name);
- this._uniform.set(name, new UniformVariable(type, location));
- }
-
- // match arguments & uniforms
- for(let j = 0; j < this._argnames.length; j++) {
- const argname = this._argnames[j];
- if(!this._uniform.has(argname)) {
- this._argIsArray[j] = this._uniform.has(argname + '[0]');
- if(!this._argIsArray[j])
- throw new utils_errors/* IllegalOperationError */.js(`Expected uniform "${argname}", as declared in the argument list`);
- }
- }
- }
-
- /**
- * Run the SpeedyProgram
- * @param {...SpeedyProgramUniformValue} args
- * @returns {SpeedyDrawableTexture}
- */
- _call(...args)
- {
- const gl = this._gl;
- const argnames = this._argnames;
-
- // matching arguments?
- if(args.length != argnames.length)
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't run shader: incorrect number of arguments (expected ${argnames.length}, got ${args.length})`);
-
- // can't use the output texture as an input
- const flatArgs = utils/* Utils.flatten */.c.flatten(args);
- for(let j = flatArgs.length - 1; j >= 0; j--) {
- if(flatArgs[j] === this._texture[this._textureIndex])
- throw new utils_errors/* NotSupportedError */.B8(`Can't run shader: don't use its output texture as an input to itself. Consider using pingpong rendering!`);
- }
-
- // context loss?
- if(gl.isContextLost())
- return this._texture[this._textureIndex];
-
- // use program
- gl.useProgram(this._program);
-
- // bind the VAO
- gl.bindVertexArray(this._geometry.vao);
-
- // select the render target
- const texture = this._texture[this._textureIndex];
- const fbo = this._renderToTexture ? texture.glFbo : null;
-
- // update texSize uniform (available in all fragment shaders)
- const width = this._width, height = this._height;
- const texSize = this._uniform.get('texSize');
- texSize.setValue(gl, [ width, height ]);
- //gl.uniform2f(texSize.location, width, height);
-
- // set uniforms[i] to args[i]
- for(let i = 0, texNo = 0; i < args.length; i++) {
- const argname = argnames[i];
-
- if(!this._argIsArray[i]) {
- // uniform variable matches argument name
- const uniform = this._uniform.get(argname);
- texNo = uniform.setValue(gl, args[i], texNo);
- }
- else {
- // uniform array matches argument name
- const array = args[i];
- if(Array.isArray(array)) {
- if(this._uniform.has(`${argname}[${array.length}]`))
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't run shader: too few elements in the "${argname}" array`);
- for(let j = 0, uniform = undefined; (uniform = this._uniform.get(`${argname}[${j}]`)) !== undefined; j++)
- texNo = uniform.setValue(gl, array[j], texNo);
- }
- else
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't run shader: expected an array for "${argname}"`);
- }
- }
-
- // set Uniform Buffer Objects (if any)
- if(this._ubo !== null)
- this._ubo.update();
-
- // bind the FBO
- gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
-
- // draw call
- gl.viewport(0, 0, width, height);
- gl.drawArrays(gl.TRIANGLES, 0, 6); // mode, offset, count
-
- // unbind the FBO
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
-
- // unbind the VAO
- gl.bindVertexArray(null);
-
- // we've just changed the texture! discard the pyramid, if any
- if(texture != null)
- texture.discardMipmaps();
-
- // ping-pong rendering
- this._pingpong();
-
- // done!
- return texture;
- }
-
- /**
- * Set the output texture(s) and its (their) shape(s)
- * @param {number} width new width, in pixels
- * @param {number} height new height, in pixels
- * @param {...SpeedyDrawableTexture|null} texture output texture(s)
- * @returns {SpeedyProgram} this
- */
- outputs(width, height, ...texture)
- {
- this._setOutputTexture(...texture);
- this._setOutputSize(width, height);
- return this;
- }
-
- /**
- * Set the size of the output
- * @param {number} width new width, in pixels
- * @param {number} height new height, in pixels
- * @returns {SpeedyProgram} this
- */
- _setOutputSize(width, height)
- {
- utils/* Utils.assert */.c.assert(width > 0 && height > 0);
-
- // update output size
- this._width = width | 0;
- this._height = height | 0;
-
- // resize the output texture(s)
- for(let i = 0; i < this._texture.length; i++) {
- if(this._texture[i] != null)
- this._texture[i].resize(this._width, this._height);
- }
-
- // done!
- return this;
- }
-
- /**
- * Use the provided texture(s) as output
- * @param {...SpeedyDrawableTexture} texture set to null to use the internal texture(s)
- * @returns {SpeedyProgram} this
- */
- _setOutputTexture(...texture)
- {
- utils/* Utils.assert */.c.assert(texture.length === this._texture.length, `Incorrect number of textures (expected ${this._texture.length})`);
-
- // update output texture(s)
- for(let i = 0; i < this._texture.length; i++)
- this._texture[i] = texture[i];
- this._textureIndex = 0;
-
- // done!
- return this;
- }
-
- /**
- * Clear the internal textures
- * @returns {SpeedyDrawableTexture}
- */
- clear()
- {
- const texture = this._texture[this._textureIndex];
-
- // clear internal textures
- for(let i = 0; i < this._texture.length; i++)
- this._texture[i].clear();
-
- // ping-pong rendering
- this._pingpong();
-
- // done!
- return texture;
- }
-
- /**
- * Set data using a Uniform Buffer Object
- * @param {string} blockName uniform block name
- * @param {ArrayBufferView} data
- * @returns {SpeedyProgram} this
- */
- setUBO(blockName, data)
- {
- if(this._ubo === null)
- this._ubo = new UBOHelper(this._gl, this._program);
-
- this._ubo.set(blockName, data);
- return this;
- }
-
- /**
- * Release the resources associated with this SpeedyProgram
- * @returns {null}
- */
- release()
- {
- const gl = this._gl;
-
- // Release UBOs (if any)
- if(this._ubo != null)
- this._ubo = this._ubo.release();
-
- // Unlink textures
- this._texture.fill(null);
-
- // Release geometry
- this._geometry = this._geometry.release();
-
- // Release program
- gl.deleteProgram(this._program);
- this._program = null;
-
- // Need to delete the shaders as well? In sec 5.14.9 Programs and shaders
- // of the WebGL 1.0 spec, it is mentioned that the underlying GL object
- // will automatically be marked for deletion when the JS object is
- // destroyed (i.e., garbage collected)
-
- // done!
- return null;
- }
-
- /**
- * A constant #defined in the shader declaration
- * @param {string} name
- * @returns {number}
- */
- definedConstant(name)
- {
- return this._shaderdecl.definedConstant(name);
- }
-
- /**
- * Helper method for pingpong rendering: alternates
- * the texture index from 0 to 1 and vice-versa
- */
- _pingpong()
- {
- if(this._texture.length > 1)
- this._textureIndex = 1 - this._textureIndex;
- }
-
- /**
- * Compile and link GLSL shaders
- * @param {WebGL2RenderingContext} gl
- * @param {string} vertexShaderSource GLSL code of the vertex shader
- * @param {string} fragmentShaderSource GLSL code of the fragment shader
- * @returns {WebGLProgram}
- */
- static _compile(gl, vertexShaderSource, fragmentShaderSource)
- {
- const program = gl.createProgram();
- const vertexShader = gl.createShader(gl.VERTEX_SHADER);
- const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
-
- // compile vertex shader
- gl.shaderSource(vertexShader, vertexShaderSource);
- gl.compileShader(vertexShader);
- gl.attachShader(program, vertexShader);
-
- // compile fragment shader
- gl.shaderSource(fragmentShader, fragmentShaderSource);
- gl.compileShader(fragmentShader);
- gl.attachShader(program, fragmentShader);
-
- // link program
- gl.linkProgram(program);
- gl.validateProgram(program);
-
- // return on success
- if(gl.getProgramParameter(program, gl.LINK_STATUS))
- return program;
-
- // display an error
- const errors = [
- gl.getShaderInfoLog(fragmentShader),
- gl.getShaderInfoLog(vertexShader),
- gl.getProgramInfoLog(program),
- ];
-
- gl.deleteProgram(program);
- gl.deleteShader(fragmentShader);
- gl.deleteShader(vertexShader);
-
- // display error
- const spaces = i => Math.max(0, 2 - Math.floor(Math.log10(i)));
- const col = k => new Array(spaces(k)).fill(' ').join('') + k + '. ';
- const source = errors[0] ? fragmentShaderSource : vertexShaderSource;
- const formattedSource = source.split('\n')
- .map((line, no) => col(1+no) + line)
- .join('\n');
-
- throw new utils_errors/* GLError */.Ql(
- `\n\n---------- ERROR ----------\n\n` +
- errors.filter(err => err).join('\n') +
- `\n\n---------- SOURCE CODE ----------\n\n` +
- formattedSource + '\n'
- );
- }
- }
-
-
-
-
-
- // ============================================================================
- // HELPERS
- // ============================================================================
-
-
-
-
-
-
- /**
- * Configure and store the VAO and the VBOs
- * @param {WebGL2RenderingContext} gl
- * @param {LocationOfAttributes} location
- * @returns {ProgramGeometry}
- *
- * @typedef {Object} LocationOfAttributes
- * @property {number} position
- * @property {number} texCoord
- *
- * @typedef {Object} BufferOfAttributes
- * @property {WebGLBuffer} position
- * @property {WebGLBuffer} texCoord
- */
- function ProgramGeometry(gl, location)
- {
- /** @type {WebGLVertexArrayObject} Vertex Array Object */
- this.vao = gl.createVertexArray();
-
- /** @type {BufferOfAttributes} Vertex Buffer Objects */
- this.vbo = Object.freeze({
- position: gl.createBuffer(),
- texCoord: gl.createBuffer()
- });
-
- /** @type {WebGL2RenderingContext} */
- this._gl = gl;
-
-
-
- // bind the VAO
- gl.bindVertexArray(this.vao);
-
- // set the position attribute
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo.position);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
- // clip coordinates (CCW)
- -1, -1,
- 1, -1,
- -1, 1,
-
- -1, 1,
- 1, -1,
- 1, 1,
- ]), gl.STATIC_DRAW);
- gl.enableVertexAttribArray(location.position);
- gl.vertexAttribPointer(location.position, // attribute location
- 2, // 2 components per vertex (x,y)
- gl.FLOAT, // type
- false, // don't normalize
- 0, // default stride (tightly packed)
- 0); // offset
-
- // set the texCoord attribute
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo.texCoord);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
- // texture coordinates (CCW)
- 0, 0,
- 1, 0,
- 0, 1,
-
- 0, 1,
- 1, 0,
- 1, 1,
- ]), gl.STATIC_DRAW);
- gl.enableVertexAttribArray(location.texCoord);
- gl.vertexAttribPointer(location.texCoord, // attribute location
- 2, // 2 components per vertex (x,y)
- gl.FLOAT, // type
- false, // don't normalize
- 0, // default stride (tightly packed)
- 0); // offset
-
- // unbind
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
- gl.bindVertexArray(null);
-
- // done!
- return Object.freeze(this);
- }
-
- /**
- * Releases the internal resources
- * @returns {null}
- */
- ProgramGeometry.prototype.release = function()
- {
- const gl = this._gl;
-
- gl.deleteVertexArray(this.vao);
- gl.deleteBuffer(this.vbo.position);
- gl.deleteBuffer(this.vbo.texCoord);
-
- return null;
- }
-
-
-
-
-
- /**
- * Helper class for storing data in GLSL uniform variables
- * @param {string} type
- * @param {WebGLUniformLocation} location
- */
- function UniformVariable(type, location)
- {
- /** @type {string} GLSL data type */
- this.type = String(type);
- if(!Object.prototype.hasOwnProperty.call(UNIFORM_SETTERS, this.type))
- throw new utils_errors/* NotSupportedError */.B8(`Unsupported uniform type: ${this.type}`);
-
- /** @type {WebGLUniformLocation} uniform location in a WebGL program */
- this.location = location;
-
- /** @type {string} setter function */
- this.setter = UNIFORM_SETTERS[this.type];
- const n = Number((this.setter.match(/^uniform(Matrix)?(\d)/))[2]) | 0;
-
- /** @type {number} is the uniform a scalar (0), a vector (1) or a matrix (2)? */
- this.dim = this.type.startsWith('mat') ? 2 : ((this.type.indexOf('vec') >= 0) ? 1 : 0);
-
- /** @type {number} required number of scalars */
- this.length = (this.dim == 2) ? n * n : n;
-
- /** @type {SpeedyProgramUniformValue|null} cached value */
- this._value = null;
- }
-
- /**
- * Set the value of a uniform variable
- * @param {WebGL2RenderingContext} gl
- * @param {SpeedyProgramUniformValue} value use column-major format for matrices
- * @param {number} [texNo] current texture index
- * @returns {number} new texture index
- */
- UniformVariable.prototype.setValue = function(gl, value, texNo = -1)
- {
- const setValue = /** @type {Function} */ ( gl[this.setter] );
-
- // check uniform type
- if(typeof value === 'object' && this.type.endsWith('sampler2D')) {
- // set texture
- if(texNo >= gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS)
- throw new utils_errors/* NotSupportedError */.B8(`Can't activate texture unit ${texNo}: max is ${gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS}`);
- else if(Array.isArray(value))
- throw new utils_errors/* NotSupportedError */.B8(`Can't pass arrays of textures to shaders`);
- else if(value == null)
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't run shader: cannot use ${value} as an input texture`);
- else if(texNo < 0)
- throw new utils_errors/* IllegalArgumentError */.mG(`Missing texNo`);
-
- const tex = value;
- gl.activeTexture(gl.TEXTURE0 + texNo);
- gl.bindTexture(gl.TEXTURE_2D, tex.glTexture);
- gl.uniform1i(this.location, texNo);
-
- texNo++;
- }
- else if(value === this._value) {
- // do not update the uniform if it hasn't changed
- void(0);
- }
- else if(typeof value === 'number' || typeof value === 'boolean') {
- // set scalar value
- setValue.call(gl, this.location, value);
- }
- else if(Array.isArray(value)) {
- // set vector or matrix
- if(value.length === this.length) {
- if(this.dim == 2)
- setValue.call(gl, this.location, false, value); // matrix
- else
- setValue.call(gl, this.location, ...value); // vector
- }
- else
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't run shader: incorrect number of values for ${this.type}: "${value}"`);
- }
- else
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't run shader: unrecognized argument "${value}"`);
-
- // cache the value
- this._value = value;
-
- // done
- return texNo;
- }
-
-
-
-
- /**
- * @typedef {object} UBOStuff
- * @property {WebGLBuffer} buffer
- * @property {number} blockBindingIndex "global" binding index
- * @property {number} blockIndex UBO "location" in the program
- * @property {ArrayBufferView|null} data user-data
- */
-
- /**
- * A helper class for handling Uniform Buffer Objects (UBOs)
- * @param {WebGL2RenderingContext} gl
- * @param {WebGLProgram} program
- */
- function UBOHelper(gl, program)
- {
- /** @type {WebGL2RenderingContext} */
- this._gl = gl;
-
- /** @type {WebGLProgram} */
- this._program = program;
-
- /** @type {number} auto-increment counter */
- this._nextIndex = 0;
-
- /** @type {Object<string,UBOStuff>} UBO dictionary indexed by uniform block names */
- this._ubo = Object.create(null);
- }
-
- /**
- * Set Uniform Buffer Object data
- * (the buffer will be uploaded when the program is executed)
- * @param {string} name uniform block name
- * @param {ArrayBufferView} data
- */
- UBOHelper.prototype.set = function(name, data)
- {
- const gl = this._gl;
-
- // create UBO entry
- if(this._ubo[name] === undefined) {
- this._ubo[name] = {
- buffer: gl.createBuffer(),
- blockBindingIndex: this._nextIndex++,
- blockIndex: -1,
- data: null
- };
- }
-
- // get UBO entry for the given block name
- const ubo = this._ubo[name];
-
- // read block index & assign binding point
- if(ubo.blockIndex < 0) {
- const blockIndex = gl.getUniformBlockIndex(this._program, name); // GLuint
- gl.uniformBlockBinding(this._program, blockIndex, ubo.blockBindingIndex);
- ubo.blockIndex = blockIndex;
- }
-
- // store the data - we'll upload it later
- ubo.data = data;
- }
-
- /**
- * Update UBO data
- * Called when we're using the appropriate WebGLProgram
- */
- UBOHelper.prototype.update = function()
- {
- const gl = this._gl;
-
- for(const name in this._ubo) {
- const ubo = this._ubo[name];
-
- gl.bindBuffer(gl.UNIFORM_BUFFER, ubo.buffer);
- gl.bufferData(gl.UNIFORM_BUFFER, ubo.data, gl.DYNAMIC_DRAW);
- gl.bindBufferBase(gl.UNIFORM_BUFFER, ubo.blockBindingIndex, ubo.buffer);
- gl.bindBuffer(gl.UNIFORM_BUFFER, null);
- }
- }
-
- /**
- * Release allocated buffers
- * @returns {null}
- */
- UBOHelper.prototype.release = function()
- {
- const gl = this._gl;
-
- for(const name in this._ubo) {
- const ubo = this._ubo[name];
-
- gl.deleteBuffer(ubo.buffer);
- ubo.data = null;
- }
-
- return null;
- }
-
- ;// CONCATENATED MODULE: ./src/gpu/speedy-program-group.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-program-group.js
- * An abstract group of programs that run on the GPU
- */
-
-
-
-
-
- /** @typedef {import('./speedy-program').SpeedyProgramOptions} SpeedyProgramOptions */
-
- /**
- * @typedef {object} SpeedyProgramHelpers
- * @property {function(): SpeedyProgramOptions} usesPingpongRendering
- * @property {function(): SpeedyProgramOptions} rendersToCanvas
- */
-
- /** @const {SpeedyProgramHelpers} Program settings generator */
- const PROGRAM_HELPERS = Object.freeze({
-
- /**
- * Pingpong Rendering: the output texture of a
- * program cannot be used as an input to itself.
- * This is a convenient helper in these situations
- * @returns {SpeedyProgramOptions}
- */
- usesPingpongRendering() {
- return {
- pingpong: true
- };
- },
-
- /**
- * Render to canvas
- * Use it when we're supposed to see the texture
- * @returns {SpeedyProgramOptions}
- */
- rendersToCanvas() {
- return {
- renderToTexture: false
- };
- },
-
- });
-
-
- /**
- * SpeedyProgramGroup
- * A semantically correlated group
- * of programs that run on the GPU
- * @abstract
- */
- class SpeedyProgramGroup
- {
- /**
- * Class constructor
- * @protected
- * @param {SpeedyGPU} gpu
- */
- constructor(gpu)
- {
- /** @type {SpeedyGPU} GPU-accelerated routines */
- this._gpu = gpu;
-
- /** @type {SpeedyProgram[]} the list of all programs that belong to this group */
- this._programs = [];
- }
-
- /**
- * Declare a program
- * @protected
- * @param {string} name Program name
- * @param {ShaderDeclaration} shaderdecl Shader declaration
- * @param {SpeedyProgramOptions} [options] Program settings
- * @returns {this}
- */
- declare(name, shaderdecl, options = {})
- {
- // lazy instantiation of kernels
- Object.defineProperty(this, name, {
- get: (() => {
- // Why cast a symbol to symbol?
- // Suppress error TS9005: Declaration emit for this file requires using private name 'key'.
- const key = /** @type {symbol} */ ( Symbol(name) );
- return () => this[key] || (this[key] = this._createProgram(shaderdecl, options));
- })()
- });
-
- return this;
- }
-
- /**
- * Neat helpers to be used when declaring programs
- * @returns {SpeedyProgramHelpers}
- */
- get program()
- {
- return PROGRAM_HELPERS;
- }
-
- /**
- * Releases all programs from this group
- * @returns {null}
- */
- release()
- {
- for(let i = 0; i < this._programs.length; i++)
- this._programs[i].release();
-
- return null;
- }
-
- /**
- * Spawn a SpeedyProgram
- * @param {ShaderDeclaration} shaderdecl Shader declaration
- * @param {SpeedyProgramOptions} [options] Program settings
- * @returns {SpeedyProgram}
- */
- _createProgram(shaderdecl, options = {})
- {
- const program = new SpeedyProgram(this._gpu.gl, shaderdecl, options);
- this._programs.push(program);
- return program;
- }
- }
- ;// CONCATENATED MODULE: ./src/gpu/programs/utils.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * utils.js
- * GPU utilities
- */
-
-
-
-
-
-
-
-
-
- //
- // Shaders
- //
-
- // Copy image
- const copy = (0,shader_declaration/* importShader */.Nt)('utils/copy.glsl').withArguments('image');
-
- // Copy keypoints
- const copyKeypoints = (0,shader_declaration/* importShader */.Nt)('utils/copy-raster.glsl').withDefines({ 'TYPE': 1 }).withArguments('image');
-
- // Copy 2D vectors
- const copy2DVectors = (0,shader_declaration/* importShader */.Nt)('utils/copy-raster.glsl').withDefines({ 'TYPE': 2 }).withArguments('image');
-
- // Flip y-axis for output
- const flipY = (0,shader_declaration/* importShader */.Nt)('utils/copy.glsl', 'utils/flip-y.vs.glsl').withArguments('image');
-
- // Fill image with a constant
- const fill = (0,shader_declaration/* importShader */.Nt)('utils/fill.glsl').withArguments('value');
-
- // Fill zero or more color components of the input image with a constant value
- const fillComponents = (0,shader_declaration/* importShader */.Nt)('utils/fill-components.glsl').withArguments('image', 'pixelComponents', 'value');
-
- // Copy the src component of src to zero or more color components of a copy of dest
- const copyComponents = (0,shader_declaration/* importShader */.Nt)('utils/copy-components.glsl').withArguments('dest', 'src', 'destComponents', 'srcComponentId');
-
- // Scan the entire image and find the minimum & maximum pixel intensity
- const scanMinMax2D = (0,shader_declaration/* importShader */.Nt)('utils/scan-minmax2d.glsl').withArguments('image', 'iterationNumber');
-
- // Compute the partial derivatives of an image
- const sobelDerivatives = (0,shader_declaration/* importShader */.Nt)('utils/sobel-derivatives.glsl', 'utils/sobel-derivatives.vs.glsl').withArguments('pyramid', 'lod');
-
-
-
-
- /**
- * SpeedyProgramGroupUtils
- * Utility operations
- */
- class SpeedyProgramGroupUtils extends SpeedyProgramGroup
- {
- /**
- * Class constructor
- * @param {SpeedyGPU} gpu
- */
- constructor(gpu)
- {
- super(gpu);
- this
- // render to the canvas
- .declare('renderToCanvas', flipY, {
- ...this.program.rendersToCanvas()
- })
-
- // copy image
- .declare('copy', copy)
-
- // copy keypoints
- .declare('copyKeypoints', copyKeypoints)
-
- // copy 2D vectors
- .declare('copy2DVectors', copy2DVectors)
-
- // Fill image with a constant
- .declare('fill', fill)
-
- // Fill zero or more color components of the input image with a constant value
- .declare('fillComponents', fillComponents)
-
- // Copy the src component of src to zero or more color components of a copy of dest
- .declare('copyComponents', copyComponents)
-
- // find minimum & maximum pixel intensity
- .declare('scanMinMax2D', scanMinMax2D, {
- ...this.program.usesPingpongRendering()
- })
-
- // Compute the partial derivatives of an image
- .declare('sobelDerivatives', sobelDerivatives)
- ;
- }
- }
- // EXTERNAL MODULE: ./src/gpu/shaders/filters/convolution.js
- var convolution = __nested_webpack_require_312600__(6776);
- ;// CONCATENATED MODULE: ./src/gpu/programs/filters.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * filters.js
- * Image filtering on the GPU
- */
-
-
-
-
-
-
-
-
-
- //
- // Shaders
- //
-
- // Convert to greyscale
- const rgb2grey = (0,shader_declaration/* importShader */.Nt)('filters/rgb2grey.glsl')
- .withArguments('image');
-
- // Convolution
- const filters_convolution = [3, 5, 7].reduce((obj, ksize) => ((obj[ksize] =
- (0,shader_declaration/* importShader */.Nt)('filters/convolution2d.glsl')
- .withDefines({ 'KERNEL_SIZE_SQUARED': ksize * ksize })
- .withArguments('image', 'kernel')
- ), obj), {});
-
- // Separable convolution
- const convolutionX = [3, 5, 7, 9, 11, 13, 15].reduce((obj, ksize) => ((obj[ksize] =
- (0,shader_declaration/* importShader */.Nt)('filters/convolution1d.glsl')
- .withDefines({ 'KERNEL_SIZE': ksize, 'AXIS': 0 })
- .withArguments('image', 'kernel')
- ), obj), {});
-
- const convolutionY = [3, 5, 7, 9, 11, 13, 15].reduce((obj, ksize) => ((obj[ksize] =
- (0,shader_declaration/* importShader */.Nt)('filters/convolution1d.glsl')
- .withDefines({ 'KERNEL_SIZE': ksize, 'AXIS': 1 })
- .withArguments('image', 'kernel')
- ), obj), {});
- // Median filter
- const median = [3, 5, 7].reduce((obj, ksize) => ((obj[ksize] =
- (0,shader_declaration/* importShader */.Nt)('filters/fast-median.glsl')
- .withDefines({ 'KERNEL_SIZE': ksize })
- .withArguments('image')
- ), obj), {});
-
- // Normalize image
- const normalizeGreyscale = (0,shader_declaration/* importShader */.Nt)('filters/normalize-image.glsl')
- .withDefines({ 'GREYSCALE': 1 })
- .withArguments('minmax2d', 'minValue', 'maxValue');
-
- const normalizeColored = (0,shader_declaration/* importShader */.Nt)('filters/normalize-image.glsl')
- .withDefines({ 'GREYSCALE': 0 })
- .withArguments('minmax2dRGB', 'minValue', 'maxValue');
-
- // Nightvision
- const nightvision = (0,shader_declaration/* importShader */.Nt)('filters/nightvision.glsl')
- .withDefines({ 'GREYSCALE': 0 })
- .withArguments('image', 'illuminationMap', 'gain', 'offset', 'decay');
-
- const nightvisionGreyscale = (0,shader_declaration/* importShader */.Nt)('filters/nightvision.glsl')
- .withDefines({ 'GREYSCALE': 1 })
- .withArguments('image', 'illuminationMap', 'gain', 'offset', 'decay');
-
-
-
- //
- // Utilities
- //
-
- // Handy conversion for Gaussian filters
- // (symmetric kernel, approx. zero after 3*sigma)
- const ksize2sigma = ksize => Math.max(1.0, ksize / 6.0);
-
- // Generate a 1D Gaussian kernel
- const gaussian = ksize => utils/* Utils.gaussianKernel */.c.gaussianKernel(ksize2sigma(ksize), ksize);
-
- // Generate a 1D Box filter
- const box = ksize => (new Array(ksize)).fill(1.0 / ksize);
-
-
-
- /**
- * SpeedyProgramGroupFilters
- * Image filtering
- */
- class SpeedyProgramGroupFilters extends SpeedyProgramGroup
- {
- /**
- * Class constructor
- * @param {SpeedyGPU} gpu
- */
- constructor(gpu)
- {
- super(gpu);
- this
- // convert to greyscale
- .declare('rgb2grey', rgb2grey)
-
- // median filters
- .declare('median3', median[3]) // 3x3 window
- .declare('median5', median[5]) // 5x5 window
- .declare('median7', median[7]) // 7x7 window
-
- // 2D convolution
- .declare('convolution3', filters_convolution[3]) // 3x3 kernel
- .declare('convolution5', filters_convolution[5]) // 5x5 kernel
- .declare('convolution7', filters_convolution[7]) // 7x7 kernel
-
- // 1D separable convolution
- .declare('convolution3x', convolutionX[3]) // 1x3 kernel
- .declare('convolution3y', convolutionY[3]) // 3x1 kernel
- .declare('convolution5x', convolutionX[5]) // 1x5 kernel
- .declare('convolution5y', convolutionY[5]) // 5x1 kernel
- .declare('convolution7x', convolutionX[7])
- .declare('convolution7y', convolutionY[7])
- .declare('convolution9x', convolutionX[9])
- .declare('convolution9y', convolutionY[9])
- .declare('convolution11x', convolutionX[11])
- .declare('convolution11y', convolutionY[11])
- .declare('convolution13x', convolutionX[13])
- .declare('convolution13y', convolutionY[13])
- .declare('convolution15x', convolutionX[15])
- .declare('convolution15y', convolutionY[15])
-
- // normalize image
- .declare('normalizeGreyscale', normalizeGreyscale)
- .declare('normalizeColored', normalizeColored)
-
- // nightvision
- .declare('nightvision', nightvision)
- .declare('nightvisionGreyscale', nightvisionGreyscale)
- .declare('illuminationMapLoX', (0,convolution.convX)(utils/* Utils.gaussianKernel */.c.gaussianKernel(80, 31)))
- .declare('illuminationMapLoY', (0,convolution.convY)(utils/* Utils.gaussianKernel */.c.gaussianKernel(80, 31)))
- .declare('illuminationMapX', (0,convolution.convX)(utils/* Utils.gaussianKernel */.c.gaussianKernel(80, 63)))
- .declare('illuminationMapY', (0,convolution.convY)(utils/* Utils.gaussianKernel */.c.gaussianKernel(80, 63)))
- .declare('illuminationMapHiX', (0,convolution.convX)(utils/* Utils.gaussianKernel */.c.gaussianKernel(80, 255)))
- .declare('illuminationMapHiY', (0,convolution.convY)(utils/* Utils.gaussianKernel */.c.gaussianKernel(80, 255)))
-
- // gaussian: separable kernels
- // see also: http://dev.theomader.com/gaussian-kernel-calculator/
- .declare('gaussian3x', (0,convolution.convX)([ 0.25, 0.5, 0.25 ])) // sigma ~ 1.0
- .declare('gaussian3y', (0,convolution.convY)([ 0.25, 0.5, 0.25 ]))
- .declare('gaussian5x', (0,convolution.convX)([ 0.05, 0.25, 0.4, 0.25, 0.05 ])) // sigma ~ 1.0
- .declare('gaussian5y', (0,convolution.convY)([ 0.05, 0.25, 0.4, 0.25, 0.05 ]))
- .declare('gaussian7x', (0,convolution.convX)(gaussian(7)))
- .declare('gaussian7y', (0,convolution.convY)(gaussian(7)))
- .declare('gaussian9x', (0,convolution.convX)(gaussian(9)))
- .declare('gaussian9y', (0,convolution.convY)(gaussian(9)))
- .declare('gaussian11x', (0,convolution.convX)(gaussian(11)))
- .declare('gaussian11y', (0,convolution.convY)(gaussian(11)))
-
- // box filter: separable kernels
- .declare('box3x', (0,convolution.convX)(box(3)))
- .declare('box3y', (0,convolution.convY)(box(3)))
- .declare('box5x', (0,convolution.convX)(box(5)))
- .declare('box5y', (0,convolution.convY)(box(5)))
- .declare('box7x', (0,convolution.convX)(box(7)))
- .declare('box7y', (0,convolution.convY)(box(7)))
- .declare('box9x', (0,convolution.convX)(box(9)))
- .declare('box9y', (0,convolution.convY)(box(9)))
- .declare('box11x', (0,convolution.convX)(box(11)))
- .declare('box11y', (0,convolution.convY)(box(11)))
- ;
- }
- }
-
- // EXTERNAL MODULE: ./src/core/speedy-namespace.js
- var speedy_namespace = __nested_webpack_require_312600__(2411);
- ;// CONCATENATED MODULE: ./src/gpu/speedy-descriptordb.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-descriptordb.js
- * A database of binary descriptors in video memory
- */
-
-
-
-
-
-
- //
- // A database of binary descriptors is a texture that stores
- // a set of (descriptor: uint8_t[]) entries.
- //
-
- /** @type {number} we use RGBA8 textures to store the descriptors */
- const DESCRIPTORDB_BYTESPERPIXEL = 4;
-
- /** @type {number} texture size goes up to 16 MB */
- const DESCRIPTORDB_MAXLOG2STRIDE = 11; // 2048x2048 RGBA8 textures are guaranteed to be available in WebGL2 (where is the source of this?)
-
- /**
- * Utility for generating a database of binary descriptors in video memory
- */
- class SpeedyDescriptorDB extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * Create a database of binary descriptors
- * @param {SpeedyTexture} texture output texture
- * @param {Uint8Array[]} descriptors binary descriptors
- * @param {number} descriptorSize in bytes, a multiple of 4
- * @returns {SpeedyTexture} texture
- */
- static create(texture, descriptors, descriptorSize)
- {
- utils/* Utils.assert */.c.assert(descriptorSize % DESCRIPTORDB_BYTESPERPIXEL == 0, `Invalid descriptorSize: ${descriptorSize}`);
-
- const numberOfDescriptors = descriptors.length;
- const pixelsPerDescriptor = descriptorSize / DESCRIPTORDB_BYTESPERPIXEL;
-
- // find an appropriate texture size
- const n = Math.log2(pixelsPerDescriptor * Math.max(numberOfDescriptors, 1)) / 2;
- const log2stride = Math.min(DESCRIPTORDB_MAXLOG2STRIDE, Math.ceil(n));
-
- // setup texture parameters
- const stride = 1 << log2stride;
- const width = stride, height = stride; // we use powers-of-two
-
- // are we within storage capacity?
- const capacity = (width * height) / pixelsPerDescriptor;
- if(numberOfDescriptors > capacity)
- throw new utils_errors/* NotSupportedError */.B8(`The capacity of the descriptorDB (${capacity} for ${descriptorSize * 8}-bit descriptors) has been exceeded`);
-
- // create texture data
- const data = new Uint8Array(width * height * DESCRIPTORDB_BYTESPERPIXEL);
- for(let i = 0; i < numberOfDescriptors; i++) {
- const byteOffset = i * descriptorSize;
- const descriptor = descriptors[i];
-
- // validate input
- utils/* Utils.assert */.c.assert(descriptor.byteLength === descriptorSize);
- utils/* Utils.assert */.c.assert(byteOffset + descriptorSize <= data.byteLength);
-
- // write data
- data.set(descriptor, byteOffset);
- }
-
- // log data for further study
- const MEGABYTE = 1048576;
- const totalSize = numberOfDescriptors * descriptorSize;
- utils/* Utils.log */.c.log(
- `Creating a ${width}x${height} database of ${numberOfDescriptors} ` +
- `${descriptorSize * 8}-bit descriptors ` +
- `(total size: ${(totalSize / MEGABYTE).toFixed(2)} MB)`
- );
-
- // upload to the texture
- texture.resize(width, height);
- texture.upload(data);
- return texture;
- }
- }
- ;// CONCATENATED MODULE: ./src/gpu/speedy-lsh.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-lsh.js
- * GPU-based LSH tables for fast matching of binary descriptors
- */
-
-
-
-
-
-
- /*
- * ALE'S GPU-BASED LSH FOR APPROXIMATE KNN MATCHING
- * ------------------------------------------------
- *
- * Here is my variant of Locality Sensitive Hashing for GPU-based KNN matching!
- * Indices of keypoint descriptors are stored in several tables, each with many
- * buckets of fixed capacity. In a nutshell, I create a data structure of fixed
- * size to match the keypoints.
- *
- * Buckets in video memory may get full. Wouldn't it be cool if we could use a
- * probabilistic approach to let us work within their storage capacity?
- *
- * Let there be n buckets in a table, each with storage capacity c (holding
- * up to c elements). Buckets are numbered from 0 to n-1.
- *
- * We pick uniformly a random bucket to store a new element in the table. Let
- * X be the chosen bucket. The probability that we'll store the new element in
- * any particular bucket k is:
- *
- * P(X = k) = 1/n (k = 0, 1, 2, ... n-1)
- *
- * On average, each new element stored in the table inserts 1/n of an element
- * in each bucket. If we add m new elements to the table, each bucket receives
- * m/n elements, on average(*).
- *
- * (*) for all k, define the Ik random variable as 1 if X = k and 0 otherwise.
- * It follows that the expected value of Ik, E(Ik), is 1/n for all k. In
- * addition, the expected value of (m Ik) is m * E(ik) = m/n.
- *
- * Now let Yi be the number of elements inserted in bucket i in m additions to
- * the table. We model Yi as Poisson(m/n), since on average, m additions to
- * the table result in m/n new elements being inserted in bucket i. Buckets
- * are picked independently. Hence, for all i, the probability that we insert
- * q elements in bucket i in m additions to the table is:
- *
- * P(Yi = q) = (m/n)^q * exp(-m/n) / q! (q = 0, 1, 2...)
- *
- * Given that each bucket has storage capacity c, we require Yi <= c with a
- * high probability p (say, p = 0.99). This means that, in m additions, we
- * don't want to exceed the capacity c with high probability. So, let us find
- * a (large) value of m such that:
- *
- * P(Yi <= c) >= p
- *
- * Sounds good! We can find the largest matching m using binary search.
- *
- * I don't think we need to enforce a high probability that ALL buckets stay
- * within their capacity - n is large, we need to use the available space, and
- * we have multiple tables anyway.
- *
- * In practice, the assumption that buckets are picked uniformly doesn't hold:
- * keypoints that are nearby tend to have similar descriptors and buckets are
- * picked according to those descriptors. Still, this model works well enough
- * in practice and it is simple! That's what I like about it!
- *
- * ... now, how I actually do the matching is the theme of the next episode!
- */
-
- /** @type {number} Default number of tables in a LSH data structure */
- const LSH_DEFAULT_NUMBER_OF_TABLES = 8;
-
- /** @type {number} Default number of bits of a hash */
- const LSH_DEFAULT_HASH_SIZE = 15;
-
- /** @type {number[]} Acceptable number of tables for a LSH data structure */
- const LSH_ACCEPTABLE_NUMBER_OF_TABLES = [4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32];
-
- /** @type {number[]} Acceptable values for hashSize, in bits */
- const LSH_ACCEPTABLE_HASH_SIZES = [10,11,12,13,14,15,16,17,18,19,20];
-
- /** @type {number[]} Acceptable sizes for keypoint descriptors, in bytes */
- const LSH_ACCEPTABLE_DESCRIPTOR_SIZES = [32,64];
-
- /**
- * @typedef {Object} LSHProfile LSH profile
- * @property {string} name name of the profile
- * @property {number} capacity maximum number of keypoints that can be stored in such a table
- * @property {number} hashSize number of bits in a keypoint descriptor hash (at most 16)
- * @property {number} tableCount number of tables, preferably a power of 2 (at most 16)
- * @property {number} bucketCapacity maximum number of entries of a bucket of a table
- */
-
- /** @type {function(number,number,number):LSHProfile[]|null} generate LSH profiles sorted by increasing capacity */
- const generateLSHProfiles = (t,h,p) => !LSH_ACCEPTABLE_HASH_SIZES.includes(h) || !LSH_ACCEPTABLE_NUMBER_OF_TABLES.includes(t) ? null : [
- {
- name: 'x-small',
- bucketCapacity: 1,
- tableCount: t,
- hashSize: h,
- capacity: findTableCapacity(h, 1, p),
- },
- {
- name: 'small',
- bucketCapacity: 2,
- tableCount: t,
- hashSize: h,
- capacity: findTableCapacity(h, 2, p),
- },
- {
- name: 'small-plus',
- bucketCapacity: 3,
- tableCount: t,
- hashSize: h,
- capacity: findTableCapacity(h, 3, p),
- },
- {
- name: 'medium',
- bucketCapacity: 4,
- tableCount: t,
- hashSize: h,
- capacity: findTableCapacity(h, 4, p),
- },
- {
- name: 'medium-plus',
- bucketCapacity: 5,
- tableCount: t,
- hashSize: h,
- capacity: findTableCapacity(h, 5, p),
- },
- {
- name: 'large',
- bucketCapacity: 6,
- tableCount: t,
- hashSize: h,
- capacity: findTableCapacity(h, 6, p),
- },
- {
- name: 'x-large',
- bucketCapacity: 8,
- tableCount: t,
- hashSize: h,
- capacity: findTableCapacity(h, 8, p),
- },
- ];
-
- //
- // LSH hash sequences: random bits in increasing order
- // We generate a few sequences (one for each table) supporting up to 16 hash bits
- // We pad each sequence with invalid values at the end - we want to pick any bit with equal probability
- //
-
- /** @typedef {Uint32Array} BitSequences flattened array of LSH_SEQUENCE_COUNT sequences of LSH_SEQUENCE_MAXLEN elements each - each entry represents a bit index */
- /** @typedef {Object<number,BitSequences>} BitSequencesIndexedByDescriptorSize */
- /** @typedef {Object<number,BitSequencesIndexedByDescriptorSize>} LSHSequences */
-
- /** @type {number} maximum number of elements of a sequence */
- const LSH_SEQUENCE_MAXLEN = Math.max(...LSH_ACCEPTABLE_HASH_SIZES);
-
- /** @type {number} number of sequences in a BitSequences object */
- const LSH_SEQUENCE_COUNT = Math.max(...LSH_ACCEPTABLE_NUMBER_OF_TABLES);
-
- /** @type {function(BitSequences): BitSequences} Sort subsequences of random bits in ascending order */
- const partitionedSort = seq => (utils/* Utils.range */.c.range(LSH_SEQUENCE_COUNT)
- .forEach(i => seq.subarray(i * LSH_SEQUENCE_MAXLEN, (i+1) * LSH_SEQUENCE_MAXLEN).sort()),
- seq);
-
- /** @type {function(number, BitSequences): BitSequences} Set the last p entries of the input subsequences to an invalid value */
- const padSequences = (p, seq) => (utils/* Utils.range */.c.range(LSH_SEQUENCE_COUNT)
- .forEach(i => seq.subarray((i+1) * LSH_SEQUENCE_MAXLEN - p, (i+1) * LSH_SEQUENCE_MAXLEN).fill(0xBADCAFE)),
- seq);
-
- /** @type {LSHSequences} the bits we pick to form the hashes, laid out in ascending order and indexed by descriptorSize and hashSize */
- const LSH_SEQUENCES = (f => LSH_ACCEPTABLE_HASH_SIZES.reduce((p,o) => ((p[o]=f(o)), p), {}))(h => ({
- // for 256-bit descriptors
- 32: partitionedSort(padSequences(LSH_SEQUENCE_MAXLEN - h, new Uint32Array([
- ...(utils/* Utils.shuffle */.c.shuffle(utils/* Utils.range */.c.range(256))),
- ...(utils/* Utils.shuffle */.c.shuffle(utils/* Utils.range */.c.range(256))),
- ...(utils/* Utils.shuffle */.c.shuffle(utils/* Utils.range */.c.range(256))),
- ].slice(0, LSH_SEQUENCE_COUNT * LSH_SEQUENCE_MAXLEN)))),
-
- // for 512-bit descriptors
- 64: partitionedSort(padSequences(LSH_SEQUENCE_MAXLEN - h, new Uint32Array([
- ...(utils/* Utils.shuffle */.c.shuffle(utils/* Utils.range */.c.range(512))),
- ...(utils/* Utils.shuffle */.c.shuffle(utils/* Utils.range */.c.range(512))),
- ].slice(0, LSH_SEQUENCE_COUNT * LSH_SEQUENCE_MAXLEN)))),
- }));
-
- //
- // Misc
- //
-
- /** @type {number} we use RGBA8 textures (32 bits per pixel) as storage */
- const LSH_BYTESPERPIXEL = 4;
-
- /** @type {function(number): number} next power of 2 */
- const nextPot = x => x > 1 ? 1 << Math.ceil(Math.log2(x)) : 1;
-
-
-
- /**
- * GPU-based LSH tables for fast matching of binary descriptors
- */
- class SpeedyLSH
- {
- /**
- * Constructor
- * @param {SpeedyTexture} lshTables texture to be used as the set of LSH tables
- * @param {SpeedyTexture} descriptorDB texture to be used as the descriptor database
- * @param {Uint8Array[]} descriptors the binary descriptors you'll store (make sure you don't repeat them, otherwise they will just waste space)
- * @param {number} [tableCount] number of LSH tables, preferably a power of two
- * @param {number} [hashSize] number of bits of a hash of a descriptor
- * @param {number} [probability] probability of no discard events happening in the theoretical model
- */
- constructor(lshTables, descriptorDB, descriptors, tableCount = LSH_DEFAULT_NUMBER_OF_TABLES, hashSize = LSH_DEFAULT_HASH_SIZE, probability = 0.95)
- {
- const descriptorCount = descriptors.length;
- const descriptorSize = descriptorCount > 0 ? descriptors[0].byteLength : 0;
- const lshProfiles = generateLSHProfiles(tableCount, hashSize, probability);
-
- // validate input
- utils/* Utils.assert */.c.assert(descriptorCount > 0, `Can't build LSH tables without descriptors!`);
- utils/* Utils.assert */.c.assert(LSH_ACCEPTABLE_DESCRIPTOR_SIZES.includes(descriptorSize), `Can't build LSH tables: unacceptable descriptor size of ${descriptorSize} bytes`);
- utils/* Utils.assert */.c.assert(descriptors.findIndex(d => d.byteLength !== descriptorSize) < 0, `Can't build LSH tables: incorrectly sized descriptors. Expected ${descriptorSize} bytes for each`);
- utils/* Utils.assert */.c.assert(descriptorCount < globals.MATCH_MAX_INDEX, `Can't build LSH tables: too many descriptors (${descriptors.length})`);
- utils/* Utils.assert */.c.assert(lshProfiles != null, `Can't build LSH tables: unacceptable number of tables (${tableCount}) x hash size (${hashSize})`);
-
- /** @type {LSHProfile} LSH profile */
- this._profile = lshProfiles.find(profile => descriptorCount <= profile.capacity) || lshProfiles[lshProfiles.length - 1];
-
- /** @type {number} descriptor size, in bytes */
- this._descriptorSize = descriptorSize;
-
- /** @type {number} number of descriptors */
- this._descriptorCount = descriptorCount;
-
- /** @type {BitSequences} bit sequences */
- this._sequences = this._pickSequences(this._descriptorSize);
-
- /** @type {SpeedyTexture} LSH tables storing indices of descriptors */
- this._tables = this._createStaticTables(lshTables, this._sequences, descriptors, descriptorSize);
-
- /** @type {SpeedyTexture} a storage of descriptors */
- this._descriptorDB = SpeedyDescriptorDB.create(descriptorDB, descriptors, descriptorSize);
- }
-
- /**
- * Descriptor size, in bytes
- * @returns {number}
- */
- get descriptorSize()
- {
- return this._descriptorSize;
- }
-
- /**
- * Number of descriptors stored in this LSH data structure
- * @returns {number}
- */
- get descriptorCount()
- {
- return this._descriptorCount;
- }
-
- /**
- * LSH bit sequences
- * @returns {BitSequences}
- */
- get sequences()
- {
- return this._sequences;
- }
-
- /**
- * Number of bits that make a hash
- * @returns {number}
- */
- get hashSize()
- {
- return this._profile.hashSize;
- }
-
- /**
- * Maximum number of descriptors that can be stored in a bucket of a table
- * @returns {number}
- */
- get bucketCapacity()
- {
- return this._profile.bucketCapacity;
- }
-
- /**
- * How many buckets per table do we have?
- * @returns {number}
- */
- get bucketsPerTable()
- {
- return 1 << this._profile.hashSize;
- }
-
- /**
- * Number of LSH tables
- * @returns {number}
- */
- get tableCount()
- {
- return this._profile.tableCount;
- }
-
- /**
- * Size of one LSH table, in bytes
- * @returns {number}
- */
- get tableSize()
- {
- return this.bucketsPerTable * this.bucketCapacity * LSH_BYTESPERPIXEL;
- }
-
- /**
- * Size of all LSH tables combined, in bytes
- * @returns {number}
- */
- get totalSize()
- {
- // actually, the total memory in VRAM may be a bit larger than
- // this value, depending on the actual size of the texture
- return this.tableCount * this.tableSize;
- }
-
- /**
- * LSH tables texture
- * @returns {SpeedyDrawableTexture}
- */
- get tables()
- {
- return this._tables;
- }
-
- /**
- * A collection of descriptors
- * @returns {SpeedyDrawableTexture}
- */
- get descriptorDB()
- {
- return this._descriptorDB;
- }
-
- /**
- * Pick the appropriate LSH sequences for a particular descriptor size
- * @param {number} descriptorSize in bytes
- * @returns {BitSequences}
- */
- _pickSequences(descriptorSize)
- {
- utils/* Utils.assert */.c.assert(Object.prototype.hasOwnProperty.call(LSH_SEQUENCES, this.hashSize));
- utils/* Utils.assert */.c.assert(Object.prototype.hasOwnProperty.call(LSH_SEQUENCES[this.hashSize], descriptorSize));
-
- return LSH_SEQUENCES[this.hashSize][descriptorSize];
- }
-
- /**
- * Create LSH tables
- * @param {SpeedyTexture} texture output texture
- * @param {BitSequences} sequences bit sequences
- * @param {Uint8Array[]} descriptors non-empty array of binary descriptors, ALL HAVING THE SAME SIZE
- * @param {number} descriptorSize in bytes
- * @returns {SpeedyTexture} texture
- */
- _createStaticTables(texture, sequences, descriptors, descriptorSize)
- {
- const END_OF_LIST = 0xFFFFFFFF;
- const profileName = this._profile.name;
- const tableCapacity = this._profile.capacity;
- const tableCount = this.tableCount;
- const bucketsPerTable = this.bucketsPerTable;
- const bucketSize = this.bucketCapacity * LSH_BYTESPERPIXEL;
- const hashSize = this.hashSize;
- const numberOfPixels = this.tableCount * this.bucketsPerTable * this.bucketCapacity; // watch for overflow?
- const textureWidth = Math.min(nextPot(Math.sqrt(numberOfPixels)), 4096); // 4096 is compatible with most devices according to MDN
- const textureHeight = Math.ceil(numberOfPixels / textureWidth);
- const numberOfDescriptors = descriptors.length;
-
- // validate input
- utils/* Utils.assert */.c.assert(hashSize <= LSH_SEQUENCE_MAXLEN);
- utils/* Utils.assert */.c.assert(tableCount <= LSH_SEQUENCE_COUNT);
- utils/* Utils.assert */.c.assert(numberOfPixels <= textureWidth * textureHeight);
-
- // log
- const MEGABYTE = 1048576;
- utils/* Utils.log */.c.log(
- `Building ${tableCount} ${profileName} LSH tables with ${numberOfDescriptors} ` +
- `${descriptorSize * 8}-bit descriptors each and hashSize = ${hashSize} bits ` +
- `(${textureWidth}x${textureHeight}, with ${(this.tableSize / MEGABYTE).toFixed(2)} ` +
- `MB per table and total size = ${(this.totalSize / MEGABYTE).toFixed(2)} MB), `
- );
-
- // warn the user if there are too many descriptors
- if(numberOfDescriptors > tableCapacity) {
- const exceedingPercentage = 100 * numberOfDescriptors / tableCapacity;
- utils/* Utils.warning */.c.warning(`There are too many descriptors (${numberOfDescriptors}) for a ${profileName} LSH table. That's ${exceedingPercentage.toFixed(2)}% of its theoretical capacity. Consider increasing the hashSize (currently set to ${hashSize}) or reducing the number of descriptors to avoid degradation.`);
- }
-
- // create empty LSH tables
- const buffer = new ArrayBuffer(textureWidth * textureHeight * LSH_BYTESPERPIXEL);
- const bytes = (new Uint8Array(buffer)).fill(0xFF);
- const data = new DataView(buffer);
-
- // shuffle the descriptors...
- // it seems like a good idea to handle collisions of similar descriptors,
- // which may be located next to each other in the array
- const permutation = utils/* Utils.shuffle */.c.shuffle(utils/* Utils.range */.c.range(numberOfDescriptors));
-
- // for each descriptor
- // do everything in little-endian format!
- const numberOfDiscardedDescriptorsPerTable = (new Array(tableCount)).fill(0);
- for(let i = 0; i < numberOfDescriptors; i++) {
- const descriptorIndex = permutation[i]; //i;
- const hashes = this._hashCodes(descriptors[descriptorIndex], sequences);
-
- // for each table
- for(let table = 0; table < tableCount; table++) {
- // compute hash & memory addresses
- const hash = hashes[table];
- const tableByteOffset = table * bucketsPerTable * bucketSize;
- const bucketByteOffset = tableByteOffset + hash * bucketSize;
-
- // find the end of the list
- let index = END_OF_LIST;
- for(let entryByteOffset = 0; entryByteOffset < bucketSize; entryByteOffset += LSH_BYTESPERPIXEL) {
- const byteOffset = bucketByteOffset + entryByteOffset;
- index = data.getUint32(byteOffset, true);
-
- // add the keypoint
- if(index == END_OF_LIST) {
- data.setUint32(byteOffset, descriptorIndex, true);
- break;
- }
- }
-
- // note: if the bucket is full, we just discard the entry :\
- // we give this event a probabilistic treatment (see above),
- // so it happens with low probability
- if(index != END_OF_LIST)
- numberOfDiscardedDescriptorsPerTable[table]++;
- }
- }
-
- // log data for further study
- const numberOfDiscardedDescriptors = numberOfDiscardedDescriptorsPerTable.reduce((sum, val) => sum + val, 0);
- const profile = numberOfDiscardedDescriptorsPerTable.map(d => 100 * d / numberOfDescriptors);
- utils/* Utils.log */.c.log(
- `When building ${tableCount} ${profileName} LSH tables with ${numberOfDescriptors} ` +
- `${descriptorSize * 8}-bit descriptors each and hashSize = ${hashSize} bits, ` +
- `I got the following discard profile: ` + profile.map(x => x.toFixed(2) + '%').join(', ') + `. ` +
- `Average: ${(100 * numberOfDiscardedDescriptors / (tableCount * numberOfDescriptors)).toFixed(2)}%. ` +
- `Minimum: ${Math.min(...profile).toFixed(2)}%. ` +
- `Table capacity: ${tableCapacity}.`
- );
-
- // upload the LSH tables to the GPU
- texture.resize(textureWidth, textureHeight);
- texture.upload(bytes);
- return texture;
- }
-
- /**
- * Pick bits from a binary descriptor
- * @param {Uint8Array} descriptor a single descriptor
- * @param {BitSequences} sequences flattened array of tableCount sequences of LSH_SEQUENCE_MAXLEN elements each
- * @returns {number[]} hash code for each table
- */
- _hashCodes(descriptor, sequences)
- {
- const tableCount = this.tableCount;
- const hashSize = this.hashSize;
- const bucketsPerTable = this.bucketsPerTable;
- const hashes = new Array(tableCount);
- //const descriptorSize = descriptor.length;
-
- // just to be sure...
- utils/* Utils.assert */.c.assert(
- hashSize <= LSH_SEQUENCE_MAXLEN &&
- sequences.length >= LSH_SEQUENCE_MAXLEN * tableCount
- );
-
- // for each table
- for(let table = 0; table < tableCount; table++) {
- const offset = LSH_SEQUENCE_MAXLEN * table;
-
- // pick bits [ sequences[offset] .. sequences[offset + hashSize-1] ]
- let hash = 0;
- for(let i = 0; i < hashSize; i++) {
- let bit = sequences[offset + i];
- let b = bit >>> 3;
- let m = 1 << (bit & 7);
-
- //Utils.assert(b < descriptorSize);
- hash = (hash << 1) | ((descriptor[b] & m) != 0);
- }
-
- // validate & store
- utils/* Utils.assert */.c.assert(hash >= 0 && hash < bucketsPerTable);
- hashes[table] = hash;
- }
-
- // done!
- return hashes;
- }
- }
-
- /**
- * Compute P(X <= k), where X ~ Poisson(lambda)
- * @param {number} lambda positive number
- * @param {number} k non-negative integer
- * @returns {number}
- */
- function cumulativePoisson(lambda, k)
- {
- const exp = Math.exp(-lambda);
- let sum = 1, fat = 1, pow = 1;
-
- // k should be small!!!
- for(let i = 1; i <= k; i++)
- sum += (pow *= lambda) / (fat *= i);
-
- return sum * exp;
- }
-
- /**
- * Find the maximum number of keypoint descriptors that a table can hold
- * @param {number} hashSize positive integer
- * @param {number} bucketCapacity positive integer
- * @param {number} [probability] probability of no discard events happening in the theoretical model
- * @return {number} optimal table capacity
- */
- function findTableCapacity(hashSize, bucketCapacity, probability = 0.99)
- {
- const n = 1 << hashSize // number of buckets
- const c = bucketCapacity;
- const p = probability;
-
- let l = 1, r = n * c; // watch for overflow!
- let m = 0, pm = 0;
-
- // binary search
- while(l < r) {
- m = Math.floor((l + r) / 2);
- pm = cumulativePoisson(m / n, c);
-
- if(pm > p) //if(1-pm < 1-p)
- l = m + 1;
- else
- r = m;
- }
-
- return m;
- }
-
- ;// CONCATENATED MODULE: ./src/gpu/programs/keypoints.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * keypoints.js
- * Facade for various keypoint detection algorithms
- */
-
-
-
-
-
-
-
-
- // FAST corner detector
- const fast9_16 = (0,shader_declaration/* importShader */.Nt)('keypoints/fast.glsl', 'keypoints/fast.vs.glsl')
- .withDefines({ 'FAST_TYPE': 916 })
- .withArguments('corners', 'pyramid', 'lod', 'threshold');
-
- // Harris corner detector
- const harris = [1, 3, 5, 7].reduce((obj, win) => ((obj[win] =
- (0,shader_declaration/* importShader */.Nt)('keypoints/harris.glsl')
- .withDefines({ 'WINDOW_SIZE': win })
- .withArguments('corners', 'pyramid', 'derivatives', 'lod', 'lodStep', 'gaussian')
- ), obj), {});
-
- const harrisScoreFindMax = (0,shader_declaration/* importShader */.Nt)('keypoints/score-findmax.glsl')
- .withArguments('corners', 'iterationNumber');
-
- const harrisScoreCutoff = (0,shader_declaration/* importShader */.Nt)('keypoints/harris-cutoff.glsl')
- .withArguments('corners', 'maxScore', 'quality');
-
- // Subpixel refinement
- const subpixelQuadratic1d = (0,shader_declaration/* importShader */.Nt)('keypoints/subpixel-refinement.glsl')
- .withDefines({ 'METHOD': 0 })
- .withArguments('pyramid', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'maxIterations', 'epsilon');
-
- const subpixelTaylor2d = (0,shader_declaration/* importShader */.Nt)('keypoints/subpixel-refinement.glsl')
- .withDefines({ 'METHOD': 1 })
- .withArguments('pyramid', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'maxIterations', 'epsilon');
-
- const subpixelBilinear = (0,shader_declaration/* importShader */.Nt)('keypoints/subpixel-refinement.glsl')
- .withDefines({ 'METHOD': 2 })
- .withArguments('pyramid', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'maxIterations', 'epsilon');
-
- const subpixelBicubic = (0,shader_declaration/* importShader */.Nt)('keypoints/subpixel-refinement.glsl')
- .withDefines({ 'METHOD': 3 })
- .withArguments('pyramid', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'maxIterations', 'epsilon');
-
- // Scale refinement
- const refineScaleLoG = (0,shader_declaration/* importShader */.Nt)('keypoints/refine-scale.glsl')
- .withDefines({ 'METHOD': 0 })
- .withArguments('pyramid', 'lodStep', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- const refineScaleFAST916 = (0,shader_declaration/* importShader */.Nt)('keypoints/refine-scale.glsl')
- .withDefines({ 'METHOD': 1 })
- .withArguments('pyramid', 'lodStep', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'threshold');
-
- // Pixel allocation
- const allocateDescriptors = (0,shader_declaration/* importShader */.Nt)('keypoints/allocate-descriptors.glsl')
- .withArguments('inputEncodedKeypoints', 'inputDescriptorSize', 'inputExtraSize', 'inputEncoderLength', 'outputDescriptorSize', 'outputExtraSize', 'outputEncoderLength');
-
- const allocateExtra = (0,shader_declaration/* importShader */.Nt)('keypoints/allocate-extra.glsl')
- .withArguments('inputEncodedKeypoints', 'inputDescriptorSize', 'inputExtraSize', 'inputEncoderLength', 'outputDescriptorSize', 'outputExtraSize', 'outputEncoderLength');
-
- const transferToExtra = (0,shader_declaration/* importShader */.Nt)('keypoints/transfer-to-extra.glsl')
- .withArguments('encodedData', 'strideOfEncodedData', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- // ORB descriptors
- const orbDescriptor = (0,shader_declaration/* importShader */.Nt)('keypoints/orb-descriptor.glsl')
- .withArguments('image', 'encodedCorners', 'extraSize', 'encoderLength');
-
- const orbOrientation = (0,shader_declaration/* importShader */.Nt)('keypoints/orb-orientation.glsl')
- .withArguments('image', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- // Non-maximum suppression
- const nonMaxSuppression = (0,shader_declaration/* importShader */.Nt)('keypoints/nonmax-suppression.glsl')
- .withDefines({ 'MULTISCALE': 0 })
- .withArguments('image', 'lodStep');
-
- const multiscaleNonMaxSuppression = (0,shader_declaration/* importShader */.Nt)('keypoints/nonmax-suppression.glsl')
- .withDefines({ 'MULTISCALE': 1 })
- .withArguments('image', 'lodStep');
-
- const nonmaxSpace = (0,shader_declaration/* importShader */.Nt)('keypoints/nonmax-space.glsl')
- .withArguments('corners');
-
- const nonmaxScale = (0,shader_declaration/* importShader */.Nt)('keypoints/nonmax-scale.glsl')
- .withDefines({ 'USE_LAPLACIAN': 1 })
- .withArguments('corners', 'pyramid', 'pyrLaplacian', 'lodStep');
-
- const nonmaxScaleSimple = (0,shader_declaration/* importShader */.Nt)('keypoints/nonmax-scale.glsl')
- .withDefines({ 'USE_LAPLACIAN': 0 })
- .withArguments('corners', 'pyramid', 'lodStep');
-
- const laplacian = (0,shader_declaration/* importShader */.Nt)('keypoints/laplacian.glsl')
- .withArguments('corners', 'pyramid', 'lodStep', 'lodOffset');
-
- // Keypoint tracking & optical-flow
- const lk = [3, 5, 7, 9, 11, 13, 15, 17, 19, 21].reduce((obj, win) => ((obj[win] =
- (0,shader_declaration/* importShader */.Nt)('keypoints/lk.glsl')
- .withDefines({ 'WINDOW_SIZE': win })
- .withArguments('encodedFlow', 'prevKeypoints', 'nextPyramid', 'prevPyramid', 'level', 'depth', 'numberOfIterations', 'discardThreshold', 'epsilon', 'descriptorSize', 'extraSize', 'encoderLength')
- ), obj), {});
-
- const transferFlow = (0,shader_declaration/* importShader */.Nt)('keypoints/transfer-flow.glsl')
- .withArguments('encodedFlow', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- // Brute-force matching
- const bfMatcherInitCandidates = (0,shader_declaration/* importShader */.Nt)('keypoints/knn-init.glsl')
- .withDefines({ 'ENCODE_FILTERS': 0 });
-
- const bfMatcherInitFilters = (0,shader_declaration/* importShader */.Nt)('keypoints/knn-init.glsl')
- .withDefines({ 'ENCODE_FILTERS': 1 });
-
- const bfMatcherTransfer = (0,shader_declaration/* importShader */.Nt)('keypoints/knn-transfer.glsl')
- .withArguments('encodedMatches', 'encodedKthMatches', 'numberOfMatchesPerKeypoint', 'kthMatch');
-
- const bfMatcher32 = (0,shader_declaration/* importShader */.Nt)('keypoints/bf-knn.glsl')
- .withDefines({
- 'DESCRIPTOR_SIZE': 32,
- 'NUMBER_OF_KEYPOINTS_PER_PASS': 16,
- })
- .withArguments('encodedMatches', 'encodedFilters', 'matcherLength', 'dbEncodedKeypoints', 'dbDescriptorSize', 'dbExtraSize', 'dbEncoderLength', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'passId');
-
- const bfMatcher64 = (0,shader_declaration/* importShader */.Nt)('keypoints/bf-knn.glsl')
- .withDefines({
- 'DESCRIPTOR_SIZE': 64,
- 'NUMBER_OF_KEYPOINTS_PER_PASS': 8,
- })
- .withArguments('encodedMatches', 'encodedFilters', 'matcherLength', 'dbEncodedKeypoints', 'dbDescriptorSize', 'dbExtraSize', 'dbEncoderLength', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'passId');
-
- // LSH-based KNN matching
- const lshKnnInitCandidates = (0,shader_declaration/* importShader */.Nt)('keypoints/knn-init.glsl')
- .withDefines({ 'ENCODE_FILTERS': 0 });
-
- const lshKnnInitFilters = (0,shader_declaration/* importShader */.Nt)('keypoints/knn-init.glsl')
- .withDefines({ 'ENCODE_FILTERS': 1 });
-
- const lshKnn = LSH_ACCEPTABLE_DESCRIPTOR_SIZES.reduce((obj, descriptorSize) => ((obj[descriptorSize] = LSH_ACCEPTABLE_HASH_SIZES.reduce((obj, hashSize) => ((obj[hashSize] = [0, 1, 2].reduce((obj, level) => ((obj[level] =
- (0,shader_declaration/* importShader */.Nt)('keypoints/lsh-knn.glsl')
- .withDefines({
- 'DESCRIPTOR_SIZE': descriptorSize,
- 'HASH_SIZE': hashSize,
- 'LEVEL': level,
- 'SEQUENCE_MAXLEN': LSH_SEQUENCE_MAXLEN,
- 'SEQUENCE_COUNT': LSH_SEQUENCE_COUNT,
- })
- .withArguments('candidates', 'filters', 'matcherLength', 'tables', 'descriptorDB', 'tableIndex', 'bucketCapacity', 'bucketsPerTable', 'tablesStride', 'descriptorDBStride', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength')
- ), obj), {})), obj), {})), obj), {});
-
- const lshKnnTransfer = (0,shader_declaration/* importShader */.Nt)('keypoints/knn-transfer.glsl')
- .withArguments('encodedMatches', 'encodedKthMatches', 'numberOfMatchesPerKeypoint', 'kthMatch');
-
- // Keypoint sorting
- const sortCreatePermutation = (0,shader_declaration/* importShader */.Nt)('keypoints/sort-keypoints.glsl')
- .withDefines({ 'STAGE': 1 })
- .withArguments('encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- const sortMergePermutation = (0,shader_declaration/* importShader */.Nt)('keypoints/sort-keypoints.glsl')
- .withDefines({ 'STAGE': 2 })
- .withArguments('permutation', 'blockSize', 'dblLog2BlockSize');
-
- const sortApplyPermutation = (0,shader_declaration/* importShader */.Nt)('keypoints/sort-keypoints.glsl')
- .withDefines({ 'STAGE': 3 })
- .withArguments('permutation', 'maxKeypoints', 'encodedKeypoints', 'descriptorSize', 'extraSize');
-
- // Keypoint mixing
- const mixKeypointsPreInit = (0,shader_declaration/* importShader */.Nt)('keypoints/mix-keypoints.glsl')
- .withDefines({ 'STAGE': 1 })
- .withArguments('encodedKeypointsA', 'encodedKeypointsB', 'encoderLengthA', 'encoderLengthB', 'encoderCapacityA', 'encoderCapacityB', 'descriptorSize', 'extraSize', 'encoderLength');
-
- const mixKeypointsInit = (0,shader_declaration/* importShader */.Nt)('keypoints/mix-keypoints.glsl')
- .withDefines({ 'STAGE': 2 })
- .withArguments('encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'maxKeypoints');
-
- const mixKeypointsSort = (0,shader_declaration/* importShader */.Nt)('keypoints/mix-keypoints.glsl')
- .withDefines({ 'STAGE': 3 })
- .withArguments('array', 'blockSize');
-
- const mixKeypointsView = (0,shader_declaration/* importShader */.Nt)('keypoints/mix-keypoints.glsl')
- .withDefines({ 'STAGE': 5 })
- .withArguments('array');
-
- const mixKeypointsApply = (0,shader_declaration/* importShader */.Nt)('keypoints/mix-keypoints.glsl')
- .withDefines({ 'STAGE': 4 })
- .withArguments('array', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- // Keypoint encoding
- const initLookupTable = (0,shader_declaration/* importShader */.Nt)('keypoints/lookup-of-locations.glsl')
- .withDefines({ 'FS_OUTPUT_TYPE': 2, 'STAGE': 1 })
- .withArguments('corners');
-
- const sortLookupTable = (0,shader_declaration/* importShader */.Nt)('keypoints/lookup-of-locations.glsl', 'keypoints/lookup-of-locations.vs.glsl')
- .withDefines({ 'FS_OUTPUT_TYPE': 2, 'FS_USE_CUSTOM_PRECISION': 1, 'STAGE': 2 })
- .withArguments('lookupTable', 'blockSize', 'width', 'height');
-
- const viewLookupTable = (0,shader_declaration/* importShader */.Nt)('keypoints/lookup-of-locations.glsl')
- .withDefines({ 'STAGE': -1 })
- .withArguments('lookupTable');
-
- const encodeKeypoints = (0,shader_declaration/* importShader */.Nt)('keypoints/encode-keypoints.glsl')
- .withArguments('corners', 'lookupTable', 'stride', 'descriptorSize', 'extraSize', 'encoderLength', 'encoderCapacity');
-
- const encodeKeypointSkipOffsets = (0,shader_declaration/* importShader */.Nt)('keypoints/encode-keypoint-offsets.glsl')
- .withArguments('corners', 'imageSize');
-
- const encodeKeypointLongSkipOffsets = (0,shader_declaration/* importShader */.Nt)('keypoints/encode-keypoint-long-offsets.glsl')
- .withDefines({ 'MAX_ITERATIONS': 6 }) // dependent texture reads :(
- .withArguments('offsetsImage', 'imageSize');
-
- const encodeKeypointPositions = (0,shader_declaration/* importShader */.Nt)('keypoints/encode-keypoint-positions.glsl')
- .withArguments('offsetsImage', 'imageSize', 'passId', 'numPasses', 'keypointLimit', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- const encodeKeypointProperties = (0,shader_declaration/* importShader */.Nt)('keypoints/encode-keypoint-properties.glsl')
- .withArguments('corners', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- const encodeNullKeypoints = (0,shader_declaration/* importShader */.Nt)('keypoints/encode-null-keypoints.glsl')
- .withArguments();
-
- const transferOrientation = (0,shader_declaration/* importShader */.Nt)('keypoints/transfer-orientation.glsl')
- .withArguments('encodedOrientations', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- const uploadKeypoints = (0,shader_declaration/* importShader */.Nt)('keypoints/upload-keypoints.glsl')
- .withDefines({
- // UBOs can hold at least 16KB of data;
- // gl.MAX_UNIFORM_BLOCK_SIZE >= 16384
- // according to the GL ES 3 reference.
- // Each keypoint uses 16 bytes (vec4)
- 'BUFFER_SIZE': 1024 //16384 / 16
- })
- .withArguments('encodedKeypoints', 'startIndex', 'endIndex', 'descriptorSize', 'extraSize', 'encoderLength');
-
- // Geometric transformations
- const applyHomography = (0,shader_declaration/* importShader */.Nt)('keypoints/apply-homography.glsl')
- .withArguments('homography', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- // Keypoint filters
- const clipBorder = (0,shader_declaration/* importShader */.Nt)('keypoints/clip-border.glsl')
- .withArguments('imageWidth', 'imageHeight', 'borderTop', 'borderRight', 'borderBottom', 'borderLeft', 'encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- const distanceFilter = (0,shader_declaration/* importShader */.Nt)('keypoints/distance-filter.glsl')
- .withArguments('encodedKeypointsA', 'encoderLengthA', 'encodedKeypointsB', 'encoderLengthB', 'descriptorSize', 'extraSize', 'encoderLength', 'threshold');
-
- const hammingDistanceFilter32 = (0,shader_declaration/* importShader */.Nt)('keypoints/hamming-distance-filter.glsl')
- .withDefines({ 'DESCRIPTOR_SIZE': 32 })
- .withArguments('encodedKeypointsA', 'encoderLengthA', 'encodedKeypointsB', 'encoderLengthB', 'descriptorSize', 'extraSize', 'encoderLength', 'threshold');
-
- const hammingDistanceFilter64 = (0,shader_declaration/* importShader */.Nt)('keypoints/hamming-distance-filter.glsl')
- .withDefines({ 'DESCRIPTOR_SIZE': 64 })
- .withArguments('encodedKeypointsA', 'encoderLengthA', 'encodedKeypointsB', 'encoderLengthB', 'descriptorSize', 'extraSize', 'encoderLength', 'threshold');
-
- // Other utilities
- const shuffle = (0,shader_declaration/* importShader */.Nt)('keypoints/shuffle.glsl')
- .withDefines({ 'PERMUTATION_MAXLEN': 2048 })
- .withArguments('encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength');
-
- const clip = (0,shader_declaration/* importShader */.Nt)('keypoints/clip.glsl')
- .withArguments('encodedKeypoints', 'descriptorSize', 'extraSize', 'encoderLength', 'maxKeypoints');
-
- /**
- * SpeedyProgramGroupKeypoints
- * Keypoint detection
- */
- class SpeedyProgramGroupKeypoints extends SpeedyProgramGroup
- {
- /**
- * Class constructor
- * @param {SpeedyGPU} gpu
- */
- constructor(gpu)
- {
- super(gpu);
- this
- //
- // FAST corner detector
- //
- .declare('fast9_16', fast9_16, {
- ...this.program.usesPingpongRendering()
- })
-
- //
- // Harris corner detector
- //
- .declare('harris1', harris[1], {
- ...this.program.usesPingpongRendering()
- })
- .declare('harris3', harris[3], {
- ...this.program.usesPingpongRendering()
- })
- .declare('harris5', harris[5], {
- ...this.program.usesPingpongRendering()
- })
- .declare('harris7', harris[7], {
- ...this.program.usesPingpongRendering()
- })
- .declare('harrisScoreFindMax', harrisScoreFindMax, {
- ...this.program.usesPingpongRendering()
- })
- .declare('harrisScoreCutoff', harrisScoreCutoff)
-
- //
- // Subpixel refinement
- //
- .declare('subpixelQuadratic1d', subpixelQuadratic1d)
- .declare('subpixelTaylor2d', subpixelTaylor2d)
- .declare('subpixelBicubic', subpixelBicubic)
- .declare('subpixelBilinear', subpixelBilinear)
-
- //
- // Scale refinement
- //
- .declare('refineScaleLoG', refineScaleLoG)
- .declare('refineScaleFAST916', refineScaleFAST916)
-
- //
- // Pixel allocation
- //
- .declare('allocateDescriptors', allocateDescriptors)
- .declare('allocateExtra', allocateExtra)
- .declare('transferToExtra', transferToExtra)
-
- //
- // ORB descriptors
- //
- .declare('orbDescriptor', orbDescriptor)
- .declare('orbOrientation', orbOrientation)
-
- //
- // Non-maximum suppression
- //
- .declare('nonmax', nonMaxSuppression)
- .declare('pyrnonmax', multiscaleNonMaxSuppression)
- .declare('nonmaxSpace', nonmaxSpace)
- .declare('nonmaxScale', nonmaxScale)
- .declare('nonmaxScaleSimple', nonmaxScaleSimple)
- .declare('laplacian', laplacian)
-
- //
- // LK optical-flow
- //
- .declare('lk21', lk[21], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk19', lk[19], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk17', lk[17], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk15', lk[15], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk13', lk[13], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk11', lk[11], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk9', lk[9], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk7', lk[7], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk5', lk[5], {
- ...this.program.usesPingpongRendering()
- })
- .declare('lk3', lk[3], {
- ...this.program.usesPingpongRendering()
- })
- .declare('transferFlow', transferFlow)
-
- //
- // Brute-force KNN matching
- //
- .declare('bfMatcherInitCandidates', bfMatcherInitCandidates)
- .declare('bfMatcherInitFilters', bfMatcherInitFilters)
- .declare('bfMatcherTransfer', bfMatcherTransfer, {
- ...this.program.usesPingpongRendering()
- })
- .declare('bfMatcher32', bfMatcher32, {
- ...this.program.usesPingpongRendering()
- })
- .declare('bfMatcher64', bfMatcher64, {
- ...this.program.usesPingpongRendering()
- })
-
- //
- // LSH-based KNN matching
- //
- .declare('lshKnnInitCandidates', lshKnnInitCandidates)
- .declare('lshKnnInitFilters', lshKnnInitFilters)
- .declare('lshKnnTransfer', lshKnnTransfer, {
- ...this.program.usesPingpongRendering()
- })
-
- //
- // Keypoint sorting
- //
- .declare('sortCreatePermutation', sortCreatePermutation)
- .declare('sortMergePermutation', sortMergePermutation, {
- ...this.program.usesPingpongRendering()
- })
- .declare('sortApplyPermutation', sortApplyPermutation)
-
- //
- // Keypoint mixing
- //
- .declare('mixKeypointsPreInit', mixKeypointsPreInit)
- .declare('mixKeypointsInit', mixKeypointsInit)
- .declare('mixKeypointsSort', mixKeypointsSort, {
- ...this.program.usesPingpongRendering()
- })
- .declare('mixKeypointsView', mixKeypointsView)
- .declare('mixKeypointsApply', mixKeypointsApply)
-
- //
- // Keypoint encoders
- //
- .declare('encodeNullKeypoints', encodeNullKeypoints)
- .declare('encodeKeypoints', encodeKeypoints)
- .declare('initLookupTable', initLookupTable)
- .declare('sortLookupTable', sortLookupTable, {
- ...this.program.usesPingpongRendering()
- })
- .declare('viewLookupTable', viewLookupTable)
-
-
- .declare('encodeKeypointSkipOffsets', encodeKeypointSkipOffsets)
- .declare('encodeKeypointLongSkipOffsets', encodeKeypointLongSkipOffsets, {
- ...this.program.usesPingpongRendering()
- })
- .declare('encodeKeypointPositions', encodeKeypointPositions, {
- ...this.program.usesPingpongRendering()
- })
- .declare('encodeKeypointProperties', encodeKeypointProperties)
-
-
-
- .declare('transferOrientation', transferOrientation)
- .declare('uploadKeypoints', uploadKeypoints, {
- ...this.program.usesPingpongRendering()
- })
-
- //
- // Geometric transformations
- //
- .declare('applyHomography', applyHomography)
-
- //
- // Keypoint filters
- //
- .declare('clipBorder', clipBorder)
- .declare('distanceFilter', distanceFilter)
- .declare('hammingDistanceFilter32', hammingDistanceFilter32)
- .declare('hammingDistanceFilter64', hammingDistanceFilter64)
-
- //
- // Other utilities
- //
- .declare('shuffle', shuffle)
- .declare('clip', clip)
- ;
-
- //
- // LSH-based KNN matching
- //
- for(const descriptorSize of Object.keys(lshKnn)) {
- for(const hashSize of Object.keys(lshKnn[descriptorSize])) {
- for(const level of Object.keys(lshKnn[descriptorSize][hashSize])) {
- const name = `lshKnn${descriptorSize}h${hashSize}lv${level}`;
- this.declare(name, lshKnn[descriptorSize][hashSize][level], {
- ...this.program.usesPingpongRendering()
- });
- }
- }
- }
- }
- }
- ;// CONCATENATED MODULE: ./src/gpu/programs/pyramids.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * pyramids.js
- * Image pyramids
- */
-
-
-
-
-
-
-
-
-
- //
- // Shaders
- //
-
- const upsample2 = (0,shader_declaration/* importShader */.Nt)('pyramids/upsample2.glsl').withArguments('image');
- const downsample2 = (0,shader_declaration/* importShader */.Nt)('pyramids/downsample2.glsl').withArguments('image');
-
-
- /**
- * SpeedyProgramGroupPyramids
- * Image pyramids
- */
- class SpeedyProgramGroupPyramids extends SpeedyProgramGroup
- {
- /**
- * Class constructor
- * @param {SpeedyGPU} gpu
- */
- constructor(gpu)
- {
- super(gpu);
- this
- // upsampling & downsampling
- .declare('upsample2', upsample2)
- .declare('downsample2', downsample2)
-
- // separable kernels for gaussian smoothing
- // use [c, b, a, b, c] where a+2c = 2b and a+2b+2c = 1
- // pick a = 0.4 for gaussian approximation (sigma = 1)
- .declare('smoothX', (0,convolution.convX)([
- 0.05, 0.25, 0.4, 0.25, 0.05
- ]))
- .declare('smoothY', (0,convolution.convY)([
- 0.05, 0.25, 0.4, 0.25, 0.05
- ]))
- /*
- .declare('reduce', conv2D([
- 0.00250, 0.01250, 0.02000, 0.01250, 0.00250,
- 0.01250, 0.06250, 0.10000, 0.06250, 0.01250,
- 0.02000, 0.10000, 0.16000, 0.10000, 0.02000,
- 0.01250, 0.06250, 0.10000, 0.06250, 0.01250,
- 0.00250, 0.01250, 0.02000, 0.01250, 0.00250
- ]))
- */
-
- // smoothing for 2x image
- // same rules as above with sum(k) = 2
- .declare('smoothX2', (0,convolution.convX)([
- 0.1, 0.5, 0.8, 0.5, 0.1
- // NOTE: this would saturate the image, but we apply it
- // on a 2x upsampled version with lots of zero pixels
- ]))
- .declare('smoothY2', (0,convolution.convY)([
- 0.1, 0.5, 0.8, 0.5, 0.1
- ], 1.0 / 2.0))
- ;
- }
- }
- ;// CONCATENATED MODULE: ./src/gpu/programs/transforms.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * transforms.js
- * Geometric transformations
- */
-
-
-
-
-
-
-
-
- //
- // Shaders
- //
-
- // Perspective warp
- const warpPerspective = (0,shader_declaration/* importShader */.Nt)('transforms/warp-perspective.glsl')
- .withArguments('image', 'inverseHomography');
-
- // Resize image
- const resizeNearest = (0,shader_declaration/* importShader */.Nt)('transforms/resize.glsl')
- .withDefines({
- 'INTERPOLATION_METHOD': 0 // Nearest neighbors
- })
- .withArguments('image');
-
- const resizeBilinear = (0,shader_declaration/* importShader */.Nt)('transforms/resize.glsl')
- .withDefines({
- 'INTERPOLATION_METHOD': 1 // Bilinear interpolation
- })
- .withArguments('image');
-
- // Additive mix (TODO create a new program group?)
- const additiveMix = (0,shader_declaration/* importShader */.Nt)('transforms/additive-mix.glsl')
- .withArguments('image0', 'image1', 'alpha', 'beta', 'gamma');
-
- /**
- * SpeedyProgramGroupTransforms
- * Geometric transformations
- */
- class SpeedyProgramGroupTransforms extends SpeedyProgramGroup
- {
- /**
- * Class constructor
- * @param {SpeedyGPU} gpu
- */
- constructor(gpu)
- {
- super(gpu);
- this
- .declare('warpPerspective', warpPerspective)
- .declare('resizeNearest', resizeNearest)
- .declare('resizeBilinear', resizeBilinear)
- .declare('additiveMix', additiveMix)
- ;
- }
- }
- ;// CONCATENATED MODULE: ./src/gpu/speedy-program-center.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-program-center.js
- * An access point to all programs that run on the GPU
- */
-
-
-
-
-
-
-
-
-
- /**
- * An access point to all programs that run on the CPU
- * All program groups can be accessed via this class
- */
- class SpeedyProgramCenter
- {
- /**
- * Class constructor
- * @param {SpeedyGPU} gpu reference to SpeedyGPU
- */
- constructor(gpu)
- {
- // Note: we instantiate the program groups lazily
-
- /** @type {SpeedyGPU} reference to SpeedyGPU */
- this._gpu = gpu;
-
- /** @type {SpeedyProgramGroupFilters} image filters */
- this._filters = null;
-
- /** @type {SpeedyProgramGroupTransforms} geometric transformations */
- this._transforms = null;
-
- /** @type {SpeedyProgramGroupPyramids} pyramids & scale-space */
- this._pyramids = null;
-
- /** @type {SpeedyProgramGroupKeypoints} keypoint routines */
- this._keypoints = null;
-
- /** @type {SpeedyProgramGroupUtils} utility programs */
- this._utils = null;
- }
-
- /**
- * Image filters & convolutions
- * @returns {SpeedyProgramGroupFilters}
- */
- get filters()
- {
- return this._filters || (this._filters = new SpeedyProgramGroupFilters(this._gpu));
- }
-
- /**
- * Geometric transformations
- * @returns {SpeedyProgramGroupTransforms}
- */
- get transforms()
- {
- return this._transforms || (this._transforms = new SpeedyProgramGroupTransforms(this._gpu));
- }
-
- /**
- * Image pyramids & scale-space
- * @returns {SpeedyProgramGroupPyramids}
- */
- get pyramids()
- {
- return this._pyramids || (this._pyramids = new SpeedyProgramGroupPyramids(this._gpu));
- }
-
- /**
- * Keypoint detection & description
- * @returns {SpeedyProgramGroupKeypoints}
- */
- get keypoints()
- {
- return this._keypoints || (this._keypoints = new SpeedyProgramGroupKeypoints(this._gpu));
- }
-
- /**
- * Utility programs
- * @returns {SpeedyProgramGroupUtils}
- */
- get utils()
- {
- return this._utils || (this._utils = new SpeedyProgramGroupUtils(this._gpu));
- }
-
- /**
- * Release all programs from all groups. You'll
- * no longer be able to use any of them.
- * @returns {null}
- */
- release()
- {
- for(const key in this) {
- if(Object.prototype.hasOwnProperty.call(this, key) && this[key] != null) {
- const group = this[key];
- if(group instanceof SpeedyProgramGroup)
- group.release();
- }
- }
-
- return null;
- }
- }
- ;// CONCATENATED MODULE: ./src/gpu/speedy-texture-pool.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-texture-pool.js
- * Texture pool
- */
-
-
-
-
-
-
- // Constants
- const DEFAULT_CAPACITY = 1024;
- const BUCKET = Symbol('Bucket');
-
-
- /*
-
- === Heuristics to figure out the capacity of a texture pool ===
-
- 1. Decide the maximum amount of VRAM you'd like to use in a pool (say, 64 MB).
-
- 2. Figure out the average texture size in your application (say, 640x360 pixels).
-
- 3. Figure out the average texture size in bytes (say, 921600 bytes). Each pixel
- uses 4 bytes (RGBA format).
-
- 4. Divide the maximum amount of VRAM by the average texture size in bytes
- (say, 72). That's the capacity of the pool.
-
- Note that textures are allocated lazily, so VRAM usage is kept to a minimum.
-
- Adapted from: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices
-
- */
-
-
-
- /**
- * @typedef {number} TextureBucketIndex index of a bucket in a pool
- */
-
-
-
- /**
- * A bucket
- */
- class TextureBucket
- {
- /**
- * Constructor
- * @param {SpeedyDrawableTexture} texture managed texture
- * @param {TextureBucketIndex} index index of this bucket
- * @param {TextureBucketIndex} next index of the next bucket
- */
- constructor(texture, index, next)
- {
- /** @type {SpeedyDrawableTexture} managed texture */
- this.texture = texture;
-
- /** @type {TextureBucketIndex} index of this bucket */
- this.index = index;
-
- /** @type {TextureBucketIndex} index of the next bucket */
- this.next = next;
-
- /** @type {boolean} whether the texture is available or not */
- this.free = true;
- }
- }
-
-
-
- /**
- * Texture pool
- */
- class SpeedyTexturePool
- {
- /**
- * Constructor
- * @param {SpeedyGPU} gpu
- * @param {number} [capacity] number of textures in the pool
- */
- constructor(gpu, capacity = DEFAULT_CAPACITY)
- {
- utils/* Utils.assert */.c.assert(capacity > 0);
-
- /** @type {TextureBucket[]} buckets */
- this._bucket = Array.from({ length: capacity }, (_, i) => new TextureBucket(null, i, i - 1));
-
- /** @type {TextureBucketIndex} index of an available bucket */
- this._head = capacity - 1;
-
- /** @type {SpeedyGPU} GPU instance */
- this._gpu = gpu;
- }
-
- /**
- * Get a texture from the pool
- * @returns {SpeedyDrawableTexture}
- */
- allocate()
- {
- if(this._head < 0)
- throw new utils_errors/* OutOfMemoryError */.Cx(`Exhausted pool (capacity: ${this._bucket.length})`);
-
- const bucket = this._bucket[this._head];
- bucket.free = false;
- this._head = bucket.next;
-
- if(bucket.texture == null) // lazy instantiation
- bucket.texture = SpeedyTexturePool._createManagedTexture(this._gpu.gl, bucket);
-
- return bucket.texture;
- }
-
- /**
- * Put a texture back in the pool
- * @param {SpeedyDrawableTexture} texture
- * @returns {null}
- */
- free(texture)
- {
- const bucket = texture[BUCKET];
- utils/* Utils.assert */.c.assert(bucket !== undefined && !bucket.free, `Unmanaged texture or double free`);
-
- bucket.next = this._head;
- bucket.free = true;
- this._head = bucket.index;
-
- return null;
- }
-
- /**
- * Release the texture pool
- * @returns {null}
- */
- release()
- {
- for(let i = 0; i < this._bucket.length; i++) {
- if(this._bucket[i].texture != null)
- this._bucket[i].texture = this._bucket[i].texture.release();
- }
-
- return null;
- }
-
- /**
- * Create a texture with a reference to a bucket
- * @param {WebGL2RenderingContext} gl
- * @param {TextureBucket} bucket
- * @returns {SpeedyDrawableTexture}
- */
- static _createManagedTexture(gl, bucket)
- {
- const texture = new SpeedyDrawableTexture(gl, 1, 1);
- return Object.defineProperty(texture, BUCKET, {
- configurable: false,
- enumerable: false,
- writable: false,
- value: bucket
- });
- }
- }
- // EXTERNAL MODULE: ./src/utils/types.js
- var types = __nested_webpack_require_312600__(6731);
- ;// CONCATENATED MODULE: ./src/core/speedy-media-source.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-media-source.js
- * Wrappers around <img>, <video>, <canvas>, etc.
- */
-
-
-
-
-
-
- /** @typedef {HTMLImageElement|HTMLVideoElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} SpeedyMediaSourceNativeElement */
-
- /** Internal token for protected constructors */
- const PRIVATE_TOKEN = Symbol();
-
- /**
- * An abstract media source: a wrapper around native
- * elements such as: HTMLImageElement, HTMLVideoElement,
- * and so on
- * @abstract
- */
- class SpeedyMediaSource
- {
- /**
- * @protected Constructor
- * @param {symbol} token
- */
- constructor(token)
- {
- // the constructor is not public
- if(token !== PRIVATE_TOKEN)
- throw new utils_errors/* IllegalOperationError */.js();
-
- /** @type {SpeedyMediaSourceNativeElement} underlying media object */
- this._data = null;
- }
-
- /**
- * Load a media source
- * @param {SpeedyMediaSourceNativeElement} wrappedObject
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- static load(wrappedObject)
- {
- if(wrappedObject instanceof HTMLImageElement)
- return SpeedyImageMediaSource.load(wrappedObject);
- else if(wrappedObject instanceof HTMLVideoElement)
- return SpeedyVideoMediaSource.load(wrappedObject);
- else if(wrappedObject instanceof HTMLCanvasElement)
- return SpeedyCanvasMediaSource.load(wrappedObject);
- else if(typeof OffscreenCanvas !== 'undefined' && wrappedObject instanceof OffscreenCanvas)
- return SpeedyOffscreenCanvasMediaSource.load(wrappedObject);
- else if(wrappedObject instanceof ImageBitmap)
- return SpeedyBitmapMediaSource.load(wrappedObject);
- else
- throw new utils_errors/* IllegalArgumentError */.mG(`Unsupported media type: ${wrappedObject}`);
- }
-
- /**
- * The underlying wrapped object
- * @returns {SpeedyMediaSourceNativeElement}
- */
- get data()
- {
- return this._data;
- }
-
- /**
- * Is the underlying media loaded?
- * @returns {boolean}
- */
- isLoaded()
- {
- return this._data !== null;
- }
-
- /**
- * The type of the underlying media source
- * @abstract
- * @returns {MediaType}
- */
- get type()
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Media width, in pixels
- * @abstract
- * @returns {number}
- */
- get width()
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Media height, in pixels
- * @abstract
- * @returns {number}
- */
- get height()
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Clone this media source
- * @abstract
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- clone()
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Release resources associated with this object
- * @returns {null}
- */
- release()
- {
- return (this._data = null);
- }
-
- /**
- * Load the underlying media
- * @abstract
- * @param {SpeedyMediaSourceNativeElement} element
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- _load(element)
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Wait for an event to be triggered in an element
- * @param {Element} element
- * @param {string} eventName
- * @param {number} [timeout] in ms
- * @returns {SpeedyPromise<Element>}
- */
- static _waitUntil(element, eventName, timeout = 30000)
- {
- return new speedy_promise/* SpeedyPromise */.s((resolve, reject) => {
- utils/* Utils.log */.c.log(`Waiting for ${eventName} to be triggered in ${element}...`);
-
- const timer = setTimeout(() => {
- clear();
- reject(new utils_errors/* TimeoutError */.W5(`${eventName} has not been triggered in ${element}: timeout (${timeout}ms)`));
- }, timeout);
-
- function clear()
- {
- clearTimeout(timer);
- element.removeEventListener('error', handleError, false);
- element.removeEventListener(eventName, handleSuccess, false);
- }
-
- function handleError()
- {
- const hasError = (element.error !== null && typeof element.error === 'object');
- const error = hasError ? element.error : ({ code: -1, message: '' });
- const info = `${error.message} (error code ${error.code})`;
-
- clear();
- reject(new utils_errors/* ResourceNotLoadedError */.tg(`Can't load ${element}. ${info}`));
- }
-
- function handleSuccess()
- {
- clear();
- resolve(element);
- }
-
- element.addEventListener('error', handleError, false);
- element.addEventListener(eventName, handleSuccess, false);
- });
- }
- }
-
- /**
- * Image media source:
- * a wrapper around HTMLImageElement
- */
- class SpeedyImageMediaSource extends SpeedyMediaSource
- {
- /**
- * @private Constructor
- * @param {symbol} token
- */
- constructor(token)
- {
- super(token);
-
- /** @type {HTMLImageElement} image element */
- this._data = null;
- }
-
- /**
- * The underlying wrapped object
- * @returns {HTMLImageElement}
- */
- get data()
- {
- return this._data;
- }
-
- /**
- * The type of the underlying media source
- * @returns {MediaType}
- */
- get type()
- {
- return types/* MediaType.Image */.DD.Image;
- }
-
- /**
- * Media width, in pixels
- * @returns {number}
- */
- get width()
- {
- return this._data ? this._data.naturalWidth : 0;
- }
-
- /**
- * Media height, in pixels
- * @returns {number}
- */
- get height()
- {
- return this._data ? this._data.naturalHeight : 0;
- }
-
- /**
- * Clone this media source
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- clone()
- {
- if(this._data == null)
- throw new utils_errors/* IllegalOperationError */.js(`Media not loaded`);
-
- const newNode = /** @type {HTMLImageElement} */ ( this._data.cloneNode(true) );
- return SpeedyImageMediaSource.load(newNode);
- }
-
- /**
- * Load the underlying media
- * @param {HTMLImageElement} image
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- _load(image)
- {
- if(this.isLoaded())
- this.release();
-
- if(image.complete && image.naturalWidth !== 0) { // already loaded?
- return new speedy_promise/* SpeedyPromise */.s(resolve => {
- this._data = image;
- resolve(this);
- });
- }
- else {
- return SpeedyMediaSource._waitUntil(image, 'load').then(() => {
- this._data = image;
- return this;
- });
- }
- }
-
- /**
- * Load the underlying media
- * @param {HTMLImageElement} image
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- static load(image)
- {
- return new SpeedyImageMediaSource(PRIVATE_TOKEN)._load(image);
- }
- }
-
- /**
- * Video media source:
- * a wrapper around HTMLVideoElement
- */
- class SpeedyVideoMediaSource extends SpeedyMediaSource
- {
- /**
- * @private Constructor
- * @param {symbol} token
- */
- constructor(token)
- {
- super(token);
-
- /** @type {HTMLVideoElement} video element */
- this._data = null;
- }
-
- /**
- * The underlying wrapped object
- * @returns {HTMLVideoElement}
- */
- get data()
- {
- return this._data;
- }
-
- /**
- * The type of the underlying media source
- * @returns {MediaType}
- */
- get type()
- {
- return types/* MediaType.Video */.DD.Video;
- }
-
- /**
- * Media width, in pixels
- * @returns {number}
- */
- get width()
- {
- // Warning: videoWidth & videoHeight may change at any time !!!
- // so you can't cache these dimensions
- return this._data ? this._data.videoWidth : 0;
- }
-
- /**
- * Media height, in pixels
- * @returns {number}
- */
- get height()
- {
- return this._data ? this._data.videoHeight : 0;
- }
-
- /**
- * Clone this media source
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- clone()
- {
- if(this._data == null)
- throw new utils_errors/* IllegalOperationError */.js(`Media not loaded`);
-
- const newNode = /** @type {HTMLVideoElement} */ ( this._data.cloneNode(true) );
- return SpeedyVideoMediaSource.load(newNode);
- }
-
- /**
- * Load the underlying media
- * @param {HTMLVideoElement} video
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- _load(video)
- {
- if(this.isLoaded())
- this.release();
-
- if(video.readyState >= 4) { // already loaded?
- return new speedy_promise/* SpeedyPromise */.s(resolve => {
- this._data = video;
- resolve(this);
- });
- }
- else {
- // waitUntil('canplay'); // use readyState >= 3
- setTimeout(() => video.load());
- return SpeedyMediaSource._waitUntil(video, 'canplaythrough').then(() => {
- this._data = video;
- return this;
- });
- }
- }
-
- /**
- * Load the underlying media
- * @param {HTMLVideoElement} video
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- static load(video)
- {
- return new SpeedyVideoMediaSource(PRIVATE_TOKEN)._load(video);
- }
- }
-
- /**
- * Canvas media source:
- * a wrapper around HTMLCanvasElement
- */
- class SpeedyCanvasMediaSource extends SpeedyMediaSource
- {
- /**
- * @private Constructor
- * @param {symbol} token
- */
- constructor(token)
- {
- super(token);
-
- /** @type {HTMLCanvasElement} canvas element */
- this._data = null;
- }
-
- /**
- * The underlying wrapped object
- * @returns {HTMLCanvasElement}
- */
- get data()
- {
- return this._data;
- }
-
- /**
- * The type of the underlying media source
- * @returns {MediaType}
- */
- get type()
- {
- return types/* MediaType.Canvas */.DD.Canvas;
- }
-
- /**
- * Media width, in pixels
- * @returns {number}
- */
- get width()
- {
- return this._data ? this._data.width : 0;
- }
-
- /**
- * Media height, in pixels
- * @returns {number}
- */
- get height()
- {
- return this._data ? this._data.height : 0;
- }
-
- /**
- * Clone this media source
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- clone()
- {
- if(this._data == null)
- throw new utils_errors/* IllegalOperationError */.js(`Media not loaded`);
-
- const newCanvas = utils/* Utils.createCanvas */.c.createCanvas(this.width, this.height);
- const newContext = newCanvas.getContext('2d');
- newContext.drawImage(this._data, 0, 0);
-
- return SpeedyCanvasMediaSource.load(newCanvas);
- }
-
- /**
- * Load the underlying media
- * @param {HTMLCanvasElement} canvas
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- _load(canvas)
- {
- if(this.isLoaded())
- this.release();
-
- return new speedy_promise/* SpeedyPromise */.s(resolve => {
- this._data = canvas;
- resolve(this);
- });
- }
-
- /**
- * Load the underlying media
- * @param {HTMLCanvasElement} canvas
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- static load(canvas)
- {
- return new SpeedyCanvasMediaSource(PRIVATE_TOKEN)._load(canvas);
- }
- }
-
- /**
- * OffscreenCanvas media source:
- * a wrapper around OffscreenCanvas
- */
- class SpeedyOffscreenCanvasMediaSource extends SpeedyMediaSource
- {
- /**
- * @private Constructor
- * @param {symbol} token
- */
- constructor(token)
- {
- super(token);
-
- /** @type {OffscreenCanvas} offscreen canvas element */
- this._data = null;
- }
-
- /**
- * The underlying wrapped object
- * @returns {OffscreenCanvas}
- */
- get data()
- {
- return this._data;
- }
-
- /**
- * The type of the underlying media source
- * @returns {MediaType}
- */
- get type()
- {
- return types/* MediaType.Canvas */.DD.Canvas; // or a new MediaType for OffscreenCanvas if necessary
- }
-
- /**
- * Media width, in pixels
- * @returns {number}
- */
- get width()
- {
- return this._data ? this._data.width : 0;
- }
-
- /**
- * Media height, in pixels
- * @returns {number}
- */
- get height()
- {
- return this._data ? this._data.height : 0;
- }
-
- /**
- * Clone this media source
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- clone()
- {
- if(this._data == null)
- throw new utils_errors/* IllegalOperationError */.js(`Media not loaded`);
-
- const newCanvas = new OffscreenCanvas(this.width, this.height);
- const newContext = newCanvas.getContext('2d');
- newContext.drawImage(this._data, 0, 0);
-
- return SpeedyOffscreenCanvasMediaSource.load(newCanvas);
- }
-
- /**
- * Load the underlying media
- * @param {OffscreenCanvas} offscreenCanvas
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- _load(offscreenCanvas)
- {
- if(this.isLoaded())
- this.release();
-
- return new speedy_promise/* SpeedyPromise */.s(resolve => {
- this._data = offscreenCanvas;
- resolve(this);
- });
- }
-
- /**
- * Load the underlying media
- * @param {OffscreenCanvas} offscreenCanvas
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- static load(offscreenCanvas)
- {
- return new SpeedyOffscreenCanvasMediaSource(PRIVATE_TOKEN)._load(offscreenCanvas);
- }
- }
-
- /**
- * Bitmap media source:
- * a wrapper around ImageBitmap
- */
- class SpeedyBitmapMediaSource extends SpeedyMediaSource
- {
- /**
- * @private Constructor
- * @param {symbol} token
- */
- constructor(token)
- {
- super(token);
-
- /** @type {ImageBitmap} image bitmap */
- this._data = null;
- }
-
- /**
- * The underlying wrapped object
- * @returns {ImageBitmap}
- */
- get data()
- {
- return this._data;
- }
-
- /**
- * The type of the underlying media source
- * @returns {MediaType}
- */
- get type()
- {
- return types/* MediaType.Bitmap */.DD.Bitmap;
- }
-
- /**
- * Media width, in pixels
- * @returns {number}
- */
- get width()
- {
- return this._data ? this._data.width : 0;
- }
-
- /**
- * Media height, in pixels
- * @returns {number}
- */
- get height()
- {
- return this._data ? this._data.height : 0;
- }
-
- /**
- * Clone this media source
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- clone()
- {
- if(this._data == null)
- throw new utils_errors/* IllegalOperationError */.js(`Media not loaded`);
-
- return new speedy_promise/* SpeedyPromise */.s((resolve, reject) => {
- createImageBitmap(this._data).then(
- newBitmap => {
- const newSource = new SpeedyBitmapMediaSource(PRIVATE_TOKEN);
- newSource._load(newBitmap).then(resolve, reject);
- },
- reject
- );
- });
- }
-
- /**
- * Release resources associated with this object
- * @returns {null}
- */
- release()
- {
- if(this._data != null)
- this._data.close();
-
- return super.release();
- }
-
- /**
- * Load the underlying media
- * @param {ImageBitmap} bitmap
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- _load(bitmap)
- {
- if(this.isLoaded())
- this.release();
-
- return new speedy_promise/* SpeedyPromise */.s(resolve => {
- this._data = bitmap;
- resolve(this);
- });
- }
-
- /**
- * Load the underlying media
- * @param {ImageBitmap} bitmap
- * @returns {SpeedyPromise<SpeedyMediaSource>}
- */
- static load(bitmap)
- {
- return new SpeedyBitmapMediaSource(PRIVATE_TOKEN)._load(bitmap);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/gpu/speedy-texture-uploader.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-texture-uploader.js
- * A utility that helps uploading data to textures
- */
-
-
-
-
-
- /**
- * A utility that helps uploading data to textures
- */
- class SpeedyTextureUploader
- {
- /**
- * Constructor
- * @param {SpeedyGPU} gpu
- */
- constructor(gpu)
- {
- /** @type {SpeedyGPU} GPU instance */
- this._gpu = gpu;
- }
-
- /**
- * Upload an image to the GPU
- * @param {SpeedyMediaSource} source
- * @param {SpeedyTexture} outputTexture
- * @returns {SpeedyTexture} output texture
- */
- upload(source, outputTexture)
- {
- const data = source.data;
-
- // bugfix: if the media is a video, we can't really
- // upload it to the GPU unless it's ready
- //if(data.constructor.name == 'HTMLVideoElement') {
- if(data instanceof HTMLVideoElement) {
- if(data.readyState < 2) {
- // this may happen when the video loops (Firefox)
- // return the previously uploaded texture
- //Utils.warning(`Trying to process a video that isn't ready yet`);
- return outputTexture;
- }
- }
-
- // upload to the output texture
- return outputTexture.upload(data, source.width, source.height);
- }
-
- /**
- * Release the texture uploader
- * @returns {null}
- */
- release()
- {
- return null;
- }
- }
- // EXTERNAL MODULE: ./src/utils/observable.js
- var observable = __nested_webpack_require_312600__(9845);
- ;// CONCATENATED MODULE: ./src/gpu/speedy-gpu.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-gpu.js
- * GPU-accelerated routines for Computer Vision
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * GPU-accelerated routines for Computer Vision
- */
- class SpeedyGPU extends observable/* Observable */.y
- {
- /**
- * Constructor
- */
- constructor()
- {
- super();
-
- /** @type {SpeedyGL} cached reference */
- this._speedyGL = speedy_gl/* SpeedyGL.instance */.$.instance;
-
- /** @type {SpeedyProgramCenter} GPU-based programs */
- this._programs = new SpeedyProgramCenter(this);
-
- /** @type {SpeedyTexturePool} texture pool */
- this._texturePool = new SpeedyTexturePool(this);
-
- /** @type {SpeedyTextureUploader} texture uploader */
- this._textureUploader = new SpeedyTextureUploader(this);
-
-
-
- // recreate the state if necessary
- this._speedyGL.subscribe(this._reset, this);
- }
-
- /**
- * Access point to all GPU programs
- * @returns {SpeedyProgramCenter}
- */
- get programs()
- {
- return this._programs;
- }
-
- /**
- * The WebGL Rendering Context
- * Be careful not to cache this, as the WebGL Rendering Context may be lost!
- * @returns {WebGL2RenderingContext}
- */
- get gl()
- {
- return this._speedyGL.gl;
- }
-
- /**
- * Internal canvas
- * @returns {HTMLCanvasElement}
- */
- get canvas()
- {
- return this._speedyGL.canvas;
- }
-
- /**
- * Texture pool
- * @returns {SpeedyTexturePool}
- */
- get texturePool()
- {
- return this._texturePool;
- }
-
- /**
- * Renders a texture to the canvas
- * @param {SpeedyTexture} texture
- * @returns {HTMLCanvasElement} returned for convenience
- */
- renderToCanvas(texture)
- {
- const width = texture.width;
- const height = texture.height;
- const canvas = this.canvas;
-
- // do we need to resize the canvas?
- if(width > canvas.width || height > canvas.height) {
- utils/* Utils.warning */.c.warning(`Resizing the canvas to ${width} x ${height}`);
- canvas.width = width;
- canvas.height = height;
- }
-
- // render
- this.programs.utils.renderToCanvas.outputs(width, height, null);
- this.programs.utils.renderToCanvas(texture);
-
- // done!
- return canvas;
- }
-
- /**
- * Upload an image to the GPU
- * @param {SpeedyMediaSource} source
- * @param {SpeedyTexture} outputTexture
- * @returns {SpeedyTexture} outputTexture
- */
- upload(source, outputTexture)
- {
- return this._textureUploader.upload(source, outputTexture);
- }
-
- /**
- * Releases resources
- * @returns {null}
- */
- release()
- {
- utils/* Utils.assert */.c.assert(!this.isReleased());
-
- // release internal components
- this._programs = this._programs.release();
- this._texturePool = this._texturePool.release();
- this._textureUploader = this._textureUploader.release();
-
- // unsubscribe
- this._speedyGL.unsubscribe(this._reset);
- return null;
- }
-
- /**
- * Has this SpeedyGPU been released?
- * @returns {boolean}
- */
- isReleased()
- {
- return this._programs == null;
- }
-
- /**
- * Lose & restore the WebGL context (useful for testing purposes)
- * @return {SpeedyPromise<void>} resolves as soon as the context is restored
- */
- loseAndRestoreWebGLContext()
- {
- return this._speedyGL.loseAndRestoreContext().then(() => void(0));
- }
-
- /**
- * Reset the internal state
- * (called on context reset)
- */
- _reset()
- {
- if(this.isReleased())
- return;
-
- this._programs = new SpeedyProgramCenter(this);
- this._texturePool = new SpeedyTexturePool(this);
- this._textureUploader = new SpeedyTextureUploader(this);
-
- this._notify();
- }
- }
- ;// CONCATENATED MODULE: ./src/core/speedy-size.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-size.js
- * Size of a rectangle
- */
-
- /**
- * Size of a rectangle
- */
- class SpeedySize
- {
- /**
- * Constructor
- * @param {number} width non-negative number
- * @param {number} height non-negative number
- */
- constructor(width, height)
- {
- /** @type {number} width */
- this._width = Math.max(0, +width);
-
- /** @type {number} height */
- this._height = Math.max(0, +height);
- }
-
-
-
- //
- // ===== METHODS =====
- //
-
- /**
- * Width
- * @returns {number}
- */
- get width()
- {
- return this._width;
- }
-
- /**
- * Width
- * @param {number} value
- */
- set width(value)
- {
- this._width = Math.max(0, +value);
- }
-
- /**
- * Height
- * @returns {number}
- */
- get height()
- {
- return this._height;
- }
-
- /**
- * Height
- * @param {number} value
- */
- set height(value)
- {
- this._height = Math.max(0, +value);
- }
-
- /**
- * Convert to string
- * @returns {string}
- */
- toString()
- {
- return `SpeedySize(${this.width}, ${this.height})`;
- }
-
- /**
- * Is this size equal to anotherSize?
- * @param {SpeedySize} anotherSize
- * @returns {boolean}
- */
- equals(anotherSize)
- {
- return this.width === anotherSize.width && this.height === anotherSize.height;
- }
-
- /**
- * The area of the rectangle
- * @returns {number}
- */
- area()
- {
- return this.width * this.height;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/speedy-media.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-media.js
- * SpeedyMedia implementation
- */
-
-
-
-
-
-
-
-
-
-
- /** @typedef {import('./speedy-media-source').SpeedyMediaSourceNativeElement} SpeedyMediaSourceNativeElement */
-
- /**
- * @typedef {object} SpeedyMediaOptions
- * @property {ImageFormat} [format] default is RGBA
- */
-
- /** A helper used to keep the constructor of SpeedyMedia private */
- const speedy_media_PRIVATE_TOKEN = Symbol();
-
- /**
- * SpeedyMedia encapsulates a media element
- * (e.g., image, video, canvas)
- */
- class SpeedyMedia
- {
- /**
- * @private Constructor. It receives a VALID media source that is ALREADY LOADED.
- * @param {symbol} token
- * @param {SpeedyMediaSource} source
- * @param {SpeedyMediaOptions} [options] options object
- */
- constructor(token, source, options = {})
- {
- // private constructor
- if(token !== speedy_media_PRIVATE_TOKEN)
- throw new utils_errors/* IllegalOperationError */.js();
-
-
-
- /** @type {SpeedyMediaSource} media source */
- this._source = source;
-
- /** @type {ImageFormat} format */
- this._format = options.format !== undefined ? options.format : types/* ImageFormat.RGBA */.D3.RGBA;
-
- /** @type {SpeedyMediaOptions} options */
- this._options = Object.freeze({ ...options, format: this._format });
-
-
-
- // validate
- if(!source.isLoaded())
- throw new utils_errors/* IllegalOperationError */.js(`Source not loaded: ${source}`);
- else if(this._format !== types/* ImageFormat.RGBA */.D3.RGBA && this._format !== types/* ImageFormat.GREY */.D3.GREY)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid format: ${this._format}`);
- }
-
- /**
- * Load a media source
- * Will wait until the HTML media source is loaded
- * @param {SpeedyMediaSourceNativeElement} mediaSource An image, video or canvas
- * @param {SpeedyMediaOptions} [options] options object
- * @param {boolean} [log] show log message?
- * @returns {SpeedyPromise<SpeedyMedia>}
- */
- static load(mediaSource, options = {}, log = true)
- {
- return SpeedyMediaSource.load(mediaSource).then(source => {
- utils/* Utils.assert */.c.assert(source.width !== 0 && source.height !== 0);
-
- // FIXME user could pass an invalid format in options if ImageFormat is made public
- const media = new SpeedyMedia(speedy_media_PRIVATE_TOKEN, source, options);
-
- // show log message
- if(log)
- utils/* Utils.log */.c.log(`Loaded SpeedyMedia with a ${mediaSource}.`);
-
- // done!
- return media;
- });
- }
-
- /**
- * The media element (image, video, canvas) encapsulated by this SpeedyMedia object
- * @returns {SpeedyMediaSourceNativeElement} the media element
- */
- get source()
- {
- return this._source ? this._source.data : null;
- }
-
- /**
- * The type of the media attached to this SpeedyMedia object
- * @returns {"image" | "video" | "canvas" | "bitmap" | "unknown"}
- */
- get type()
- {
- if(this.isReleased())
- return 'unknown';
-
- switch(this._source.type) {
- case types/* MediaType.Image */.DD.Image:
- return 'image';
-
- case types/* MediaType.Video */.DD.Video:
- return 'video';
-
- case types/* MediaType.Canvas */.DD.Canvas:
- return 'canvas';
-
- case types/* MediaType.Bitmap */.DD.Bitmap:
- return 'bitmap';
-
- default: // this shouldn't happen
- return 'unknown';
- }
- }
-
- /**
- * Gets the width of the media
- * @returns {number} media width
- */
- get width()
- {
- return this._source ? this._source.width : 0;
- }
-
- /**
- * Gets the height of the media
- * @returns {number} media height
- */
- get height()
- {
- return this._source ? this._source.height : 0;
- }
-
- /**
- * The size of this media, in pixels
- * @returns {SpeedySize}
- */
- get size()
- {
- return this._source ? new SpeedySize(this._source.width, this._source.height) : new SpeedySize(0, 0);
- }
-
- /**
- * Returns a read-only object featuring advanced options
- * related to this SpeedyMedia object
- * @returns {SpeedyMediaOptions}
- */
- get options()
- {
- return this._options;
- }
-
- /**
- * Releases resources associated with this media
- * @returns {null}
- */
- release()
- {
- if(!this.isReleased()) {
- utils/* Utils.log */.c.log('Releasing SpeedyMedia object...');
- this._source = this._source.release();
- }
-
- return null;
- }
-
- /**
- * Has this media been released?
- * @returns {boolean}
- */
- isReleased()
- {
- return this._source == null;
- }
-
- /**
- * Clones the SpeedyMedia object
- * @returns {SpeedyPromise<SpeedyMedia>} a clone object
- */
- clone()
- {
- // has the media been released?
- if(this.isReleased())
- throw new utils_errors/* IllegalOperationError */.js(`Can't clone a SpeedyMedia that has been released`);
-
- // clone the object
- const clone = new SpeedyMedia(speedy_media_PRIVATE_TOKEN, this._source, this._options);
-
- // done!
- return speedy_promise/* SpeedyPromise.resolve */.s.resolve(clone);
- }
-
- /**
- * Converts the media to an ImageBitmap
- * @returns {SpeedyPromise<ImageBitmap>}
- */
- toBitmap()
- {
- if(this.isReleased())
- throw new utils_errors/* IllegalOperationError */.js('Can\'t convert SpeedyMedia to ImageBitmap: the media has been released');
- else if(!this._source.isLoaded())
- throw new utils_errors/* IllegalOperationError */.js('Can\'t convert SpeedyMedia to bitmap: the media hasn\'t been loaded');
- else if(this._source.type == types/* MediaType.Bitmap */.DD.Bitmap)
- return speedy_promise/* SpeedyPromise.resolve */.s.resolve(this._source.data);
- else
- return new speedy_promise/* SpeedyPromise */.s((resolve, reject) => createImageBitmap(this._source.data).then(resolve, reject));
- }
- }
-
- ;// CONCATENATED MODULE: ./src/utils/fps-counter.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * fps-counter.js
- * A FPS counter
- */
-
-
-
- /** @const {number} update interval in milliseconds */
- const UPDATE_INTERVAL = 500;
-
- /** @type {FPSCounter|null} Singleton */
- let instance = null;
-
- /**
- * FPS counter
- */
- class FPSCounter
- {
- /**
- * Creates a new FPSCounter
- * @private
- */
- constructor()
- {
- /** @type {number} current FPS rate */
- this._fps = 60;
-
- /** @type {number} frame counter */
- this._frames = 0;
-
- /** @type {number} update interval in milliseconds */
- this._updateInterval = UPDATE_INTERVAL;
-
- /** @type {number} time of the last update */
- this._lastUpdate = performance.now();
-
- /** @type {function(): void} bound update function */
- this._boundUpdate = this._update.bind(this);
-
-
-
- // this should never happen...
- if(instance !== null)
- throw new utils_errors/* IllegalOperationError */.js(`Can't have multiple instances of FPSCounter`);
-
- // start FPS counter
- this._boundUpdate();
- }
-
- /**
- * Gets an instance of the FPS counter.
- * We use lazy loading, i.e., we will not
- * create a FPS counter unless we need to!
- * @returns {FPSCounter}
- */
- static get instance()
- {
- if(instance === null)
- instance = new FPSCounter();
-
- return instance;
- }
-
- /**
- * Get the FPS rate
- * @returns {number} frames per second
- */
- get fps()
- {
- return this._fps;
- }
-
- /**
- * Updates the FPS counter
- */
- _update()
- {
- const now = performance.now();
- const deltaTime = now - this._lastUpdate;
-
- if(deltaTime >= this._updateInterval) {
- this._fps = Math.round(this._frames / (deltaTime * 0.001));
- this._frames = 0;
- this._lastUpdate = now;
- }
-
- this._frames++;
- requestAnimationFrame(this._boundUpdate);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/speedy-vector.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-vector.js
- * Vectors
- */
-
- /**
- * 2D vector of floating-point numbers
- */
- class SpeedyVector2
- {
- /**
- * Create a 2D vector
- * @param {number} x
- * @param {number} y
- */
- constructor(x, y)
- {
- /** @type {number} x coordinate */
- this._x = +x;
-
- /** @type {number} y coordinate */
- this._y = +y;
- }
-
-
-
- //
- // ===== METHODS =====
- //
-
- /**
- * x-coordinate
- * @returns {number}
- */
- get x()
- {
- return this._x;
- }
-
- /**
- * x-coordinate
- * @param {number} value
- */
- set x(value)
- {
- this._x = +value;
- }
-
- /**
- * y-coordinate
- * @returns {number}
- */
- get y()
- {
- return this._y;
- }
-
- /**
- * y-coordinate
- * @param {number} value
- */
- set y(value)
- {
- this._y = +value;
- }
-
- /**
- * Convert to string
- * @returns {string}
- */
- toString()
- {
- return `SpeedyVector2(${this.x.toFixed(5)}, ${this.y.toFixed(5)})`;
- }
-
- /**
- * Is this vector equal to v?
- * @param {SpeedyVector2} v
- * @returns {boolean}
- */
- equals(v)
- {
- return this.x === v.x && this.y === v.y;
- }
-
- /**
- * Dot product between this vector and another vector
- * @param {SpeedyVector2} v another vector
- * @returns {number}
- */
- dot(v)
- {
- return this.x * v.x + this.y * v.y;
- }
-
- /**
- * The distance between this vector and another vector
- * @param {SpeedyVector2} v another vector
- * @returns {number}
- */
- distanceTo(v)
- {
- const dx = this.x - v.x;
- const dy = this.y - v.y;
-
- return Math.sqrt(dx * dx + dy * dy);
- }
-
- /**
- * Euclidean norm
- * @returns {number}
- */
- length()
- {
- return Math.sqrt(this.x * this.x + this.y * this.y);
- }
-
- /**
- * Returns a normalized version of this vector
- * @returns {SpeedyVector2}
- */
- normalized()
- {
- const len = this.length();
-
- if(len > 0.0)
- return new SpeedyVector2(this.x / len, this.y / len);
- else
- return new SpeedyVector2(0.0, 0.0);
- }
-
- /**
- * Returns a copy of this vector translated by offset
- * @param {SpeedyVector2} offset
- * @returns {SpeedyVector2}
- */
- plus(offset)
- {
- return new SpeedyVector2(this.x + offset.x, this.y + offset.y);
- }
-
- /**
- * Returns a copy of this vector translated by -offset
- * @param {SpeedyVector2} offset
- * @returns {SpeedyVector2}
- */
- minus(offset)
- {
- return new SpeedyVector2(this.x - offset.x, this.y - offset.y);
- }
-
- /**
- * Returns a copy of this vector scaled by a scalar
- * @param {number} scalar
- * @returns {SpeedyVector2}
- */
- times(scalar)
- {
- return new SpeedyVector2(this.x * scalar, this.y * scalar);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/speedy-point.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-point.js
- * Points in space
- */
-
-
-
- /**
- * 2D point
- */
- class SpeedyPoint2
- {
- /**
- * Create a 2D point
- * @param {number} x
- * @param {number} y
- */
- constructor(x, y)
- {
- /** @type {number} x coordinate */
- this._x = +x;
-
- /** @type {number} y coordinate */
- this._y = +y;
- }
-
-
-
- //
- // ===== METHODS =====
- //
-
- /**
- * x-coordinate
- * @returns {number}
- */
- get x()
- {
- return this._x;
- }
-
- /**
- * x-coordinate
- * @param {number} value
- */
- set x(value)
- {
- this._x = +value;
- }
-
- /**
- * y-coordinate
- * @returns {number}
- */
- get y()
- {
- return this._y;
- }
-
- /**
- * y-coordinate
- * @param {number} value
- */
- set y(value)
- {
- this._y = +value;
- }
-
- /**
- * Convert to string
- * @returns {string}
- */
- toString()
- {
- return `SpeedyPoint2(${this.x.toFixed(5)}, ${this.y.toFixed(5)})`;
- }
-
- /**
- * Add a vector to this point
- * @param {SpeedyVector2} v
- * @returns {SpeedyPoint2}
- */
- plus(v)
- {
- return new SpeedyPoint2(this.x + v.x, this.y + v.y);
- }
-
- /**
- * Subtracts a point p from this point
- * @param {SpeedyPoint2} p
- * @returns {SpeedyVector2}
- */
- minus(p)
- {
- return new SpeedyVector2(this.x - p.x, this.y - p.y);
- }
-
- /**
- * Is this point equal to p?
- * @param {SpeedyPoint2} p
- * @returns {boolean}
- */
- equals(p)
- {
- return this.x === p.x && this.y === p.y;
- }
- }
- // EXTERNAL MODULE: ./src/core/speedy-matrix-expr.js
- var speedy_matrix_expr = __nested_webpack_require_312600__(5137);
- // EXTERNAL MODULE: ./src/core/speedy-matrix-wasm.js
- var speedy_matrix_wasm = __nested_webpack_require_312600__(4368);
- // EXTERNAL MODULE: ./src/core/speedy-matrix.js
- var speedy_matrix = __nested_webpack_require_312600__(8007);
- ;// CONCATENATED MODULE: ./src/core/speedy-matrix-factory.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-matrix-factory.js
- * A factory of matrices
- */
-
-
-
-
-
-
-
-
- /**
- * Matrix routines
- */
- class SpeedyMatrixFactory extends Function
- {
- /**
- * Constructor
- */
- constructor()
- {
- // This factory can be invoked as a function
- super('...args', 'return args.length > 1 ? this._create(...args) : this._from(args[0])');
- return this.bind(this);
- }
-
- /**
- * @private
- *
- * Create a new matrix filled with the specified size and entries
- * @param {number} rows
- * @param {number} [columns]
- * @param {number[]} [entries] in column-major format
- * @returns {SpeedyMatrix}
- */
- _create(rows, columns = rows, entries = [])
- {
- return speedy_matrix.SpeedyMatrix.Create(rows, columns, entries);
- }
-
- /**
- * @private
- *
- * Evaluate an expression synchronously and store the result in a new matrix
- * @param {SpeedyMatrixExpr} expr matrix expression
- * @returns {SpeedyMatrix}
- */
- _from(expr)
- {
- return speedy_matrix.SpeedyMatrix.From(expr);
- }
-
- /**
- * Create a new matrix filled with zeros with the specified size
- * @param {number} rows
- * @param {number} [columns]
- * @returns {SpeedyMatrix}
- */
- Zeros(rows, columns = rows)
- {
- return speedy_matrix.SpeedyMatrix.Zeros(rows, columns);
- }
-
- /**
- * Create a new matrix filled with ones with the specified size
- * @param {number} rows
- * @param {number} [columns]
- * @returns {SpeedyMatrix}
- */
- Ones(rows, columns = rows)
- {
- return speedy_matrix.SpeedyMatrix.Ones(rows, columns);
- }
-
- /**
- * Create an identity matrix with the specified size
- * @param {number} rows
- * @param {number} [columns]
- * @returns {SpeedyMatrix}
- */
- Eye(rows, columns = rows)
- {
- return speedy_matrix.SpeedyMatrix.Eye(rows, columns);
- }
-
- /**
- * Returns a promise that resolves immediately if the WebAssembly routines
- * are ready to be used, or as soon as they do become ready
- * @returns {SpeedyPromise<void>}
- */
- ready()
- {
- return speedy_matrix.SpeedyMatrix.ready();
- }
-
- /**
- * QR decomposition
- * @param {SpeedyMatrix} Q is m x n (reduced) or m x m (full), output
- * @param {SpeedyMatrix} R is n x n (reduced) or m x n (full), output
- * @param {SpeedyMatrix} mat is m x n, input
- * @param {object} [options]
- * @param {'reduced'|'full'} [options.mode]
- * @returns {SpeedyPromise<[SpeedyMatrix,SpeedyMatrix]>} resolves to [Q,R]
- */
- qr(Q, R, mat, { mode = 'reduced' } = {})
- {
- const A = mat, m = mat.rows, n = mat.columns;
-
- // validate shapes & mode
- if(mode == 'reduced') {
- if(Q.rows != m || Q.columns != n || R.rows != n || R.columns != n)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid shape for reduced QR`);
- }
- else if(mode == 'full') {
- if(Q.rows != m || Q.columns != m || R.rows != m || R.columns != n)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid shape for full QR`);
- }
- else
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid mode for QR: "${mode}"`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // allocate matrices
- const Qptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, Q);
- const Rptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, R);
- const Aptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, A);
-
- // copy input matrices to WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, Aptr, A);
-
- // run the WASM routine
- if(mode == 'reduced')
- wasm.exports.Mat32_qr_reduced(Qptr, Rptr, Aptr);
- else
- wasm.exports.Mat32_qr_full(Qptr, Rptr, Aptr);
-
- // copy output matrices from WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, Qptr, Q);
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, Rptr, R);
-
- // deallocate matrices
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, Aptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, Rptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, Qptr);
-
- // done!
- return [Q, R];
- });
- }
-
- /**
- * Solve a possibly overdetermined system of linear
- * equations Ax = b for x using ordinary least squares
- * @param {SpeedyMatrix} solution n x 1, output
- * @param {SpeedyMatrix} A m x n, m >= n, input
- * @param {SpeedyMatrix} b m x 1, output
- * @param {object} [options]
- * @param {'qr'} [options.method] method of resolution
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to solution
- */
- ols(solution, A, b, { method = 'qr' } = {})
- {
- const m = A.rows, n = A.columns;
- const x = solution;
-
- // validate shapes
- if(m < n || n == 0)
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't solve an underdetermined system of equations`);
- else if(b.rows != m || b.columns != 1 || x.rows != n || x.columns != 1)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid shapes`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // allocate matrices
- const Aptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, A);
- const bptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, b);
- const xptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, x);
-
- // copy input matrices to WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, Aptr, A);
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, bptr, b);
-
- // run the WASM routine
- switch(method) {
- case 'qr':
- wasm.exports.Mat32_qr_ols(xptr, Aptr, bptr, 2);
- break;
-
- default:
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid method: "${method}"`);
- }
-
- // copy output matrix from WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, xptr, x);
-
- // deallocate matrices
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, xptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, bptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, Aptr);
-
- // done!
- return solution;
- });
- }
-
- /**
- * Solve a system of linear equations Ax = b for x
- * @param {SpeedyMatrix} solution m x 1, output
- * @param {SpeedyMatrix} A m x m, input
- * @param {SpeedyMatrix} b m x 1, output
- * @param {object} [options]
- * @param {'qr'} [options.method] method of resolution
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to solution
- */
- solve(solution, A, b, { method = 'qr' } = {})
- {
- const m = A.rows, n = A.columns;
- const x = solution;
-
- // validate shapes
- if(m != n)
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't solve an over or underdetermined system of equations`);
- else if(b.rows != m || b.columns != 1 || x.rows != m || x.columns != 1)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid shapes`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // select method
- switch(method) {
- case 'qr':
- return this.ols(x, A, b, { method });
-
- /*case 'lu':
- break;*/
-
- default:
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid method: "${method}"`);
- }
- });
- }
-
- /**
- * Compute a perspective transformation using 4 correspondences of points
- * @param {SpeedyMatrix} homography 3x3 output - homography matrix
- * @param {SpeedyMatrix} src 2x4 input points - source coordinates
- * @param {SpeedyMatrix} dest 2x4 input points - destination coordinates
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to homography
- */
- perspective(homography, src, dest)
- {
- // validate shapes
- if(src.rows != 2 || src.columns != 4 || dest.rows != 2 || dest.columns != 4)
- throw new utils_errors/* IllegalArgumentError */.mG(`You need two 2x4 input matrices to compute a perspective transformation`);
- else if(homography.rows != 3 || homography.columns != 3)
- throw new utils_errors/* IllegalArgumentError */.mG(`The output of perspective() is a 3x3 homography`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // allocate matrices
- const homptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, homography);
- const srcptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, src);
- const destptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, dest);
-
- // copy input matrices to WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, srcptr, src);
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, destptr, dest);
-
- // run the WASM routine
- wasm.exports.Mat32_homography_ndlt4(homptr, srcptr, destptr);
-
- // copy output matrix from WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, homptr, homography);
-
- // deallocate matrices
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, destptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, srcptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, homptr);
-
- // done!
- return homography;
- });
- }
-
- /**
- * Compute a perspective transformation using n >= 4 correspondences of points
- * @param {SpeedyMatrix} homography 3x3 output - homography matrix
- * @param {SpeedyMatrix} src 2 x n input points - source coordinates
- * @param {SpeedyMatrix} dest 2 x n input points - destination coordinates
- * @param {object} [options]
- * @param {'default'|'pransac'} [options.method] method of computation
- * @param {SpeedyMatrix|null} [options.mask] (pransac) 1 x n output: i-th entry will be 1 if the i-th input point is an inlier, or 0 otherwise
- * @param {number} [options.reprojectionError] (pransac) given in pixels, used to separate inliers from outliers of a particular model (e.g., 1 pixel)
- * @param {number} [options.numberOfHypotheses] (pransac) number of hypotheses to be generated up-front (e.g., 512)
- * @param {number} [options.bundleSize] (pransac) how many points should we check before reducing the number of viable hypotheses (e.g., 128)
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to homography
- */
- findHomography(homography, src, dest, {
- method = 'default',
- mask = null,
- reprojectionError = 3,
- numberOfHypotheses = 512,
- bundleSize = 128,
- } = {})
- {
- // validate shapes
- if(src.rows != 2 || src.columns < 4 || dest.rows != 2 || dest.columns != src.columns)
- throw new utils_errors/* IllegalArgumentError */.mG(`You need two 2 x n (n >= 4) input matrices to compute a homography`);
- else if(homography.rows != 3 || homography.columns != 3)
- throw new utils_errors/* IllegalArgumentError */.mG(`The output of findHomography() is a 3x3 homography`);
- else if(mask != null && (mask.rows != 1 || mask.columns != src.columns))
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid shape of the inliers mask`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // allocate matrices
- const homptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, homography);
- const srcptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, src);
- const destptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, dest);
- const maskptr = mask != null ? speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, mask) : 0;
-
- // copy input matrices to WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, srcptr, src);
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, destptr, dest);
-
- // run the WASM routine
- switch(method) {
- case 'pransac':
- utils/* Utils.assert */.c.assert(reprojectionError >= 0 && numberOfHypotheses > 0 && bundleSize > 0);
- wasm.exports.Mat32_pransac_homography(homptr, maskptr, srcptr, destptr, numberOfHypotheses, bundleSize, reprojectionError);
- break;
-
- case 'default':
- case 'dlt': // obsolete
- wasm.exports.Mat32_homography_ndlt(homptr, srcptr, destptr);
- break;
-
- default:
- throw new utils_errors/* IllegalArgumentError */.mG(`Illegal method for findHomography(): "${method}"`);
- }
-
- // copy output matrices from WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, homptr, homography);
- if(mask != null)
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, maskptr, mask);
-
- // deallocate matrices
- if(mask != null)
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, maskptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, destptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, srcptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, homptr);
-
- // done!
- return homography;
- });
- }
-
- /**
- * Apply a perspective transformation to a set of 2D points
- * @param {SpeedyMatrix} dest 2 x n output matrix
- * @param {SpeedyMatrix} src 2 x n input matrix (a set of points)
- * @param {SpeedyMatrix} transform 3x3 homography matrix
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to dest
- */
- applyPerspectiveTransform(dest, src, transform)
- {
- // validate shapes
- if(src.rows != 2 || dest.rows != 2 || src.columns != dest.columns)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid shapes`);
- else if(transform.rows != 3 || transform.columns != 3)
- throw new utils_errors/* IllegalArgumentError */.mG(`The perspective transformation must be a 3x3 matrix`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // allocate matrices
- const matptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, transform);
- const srcptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, src);
- const destptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, dest);
-
- // copy input matrices to WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, srcptr, src);
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, matptr, transform);
-
- // run the WASM routine
- wasm.exports.Mat32_transform_perspective(destptr, srcptr, matptr);
-
- // copy output matrix from WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, destptr, dest);
-
- // deallocate matrices
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, destptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, srcptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, matptr);
-
- // done!
- return dest;
- });
- }
-
- /**
- * Compute an affine transform using 3 correspondences of points
- * @param {SpeedyMatrix} transform 2x3 output - affine transform
- * @param {SpeedyMatrix} src 2x3 input points - source coordinates
- * @param {SpeedyMatrix} dest 2x3 input points - destination coordinates
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to homography
- */
- affine(transform, src, dest)
- {
- // validate shapes
- if(src.rows != 2 || src.columns != 3 || dest.rows != 2 || dest.columns != 3)
- throw new utils_errors/* IllegalArgumentError */.mG(`You need two 2x3 input matrices to compute an affine transform`);
- else if(transform.rows != 2 || transform.columns != 3)
- throw new utils_errors/* IllegalArgumentError */.mG(`The output of affine() is a 2x3 matrix`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // allocate matrices
- const matptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, transform);
- const srcptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, src);
- const destptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, dest);
-
- // copy input matrices to WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, srcptr, src);
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, destptr, dest);
-
- // run the WASM routine
- wasm.exports.Mat32_affine_direct3(matptr, srcptr, destptr);
-
- // copy output matrix from WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, matptr, transform);
-
- // deallocate matrices
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, destptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, srcptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, matptr);
-
- // done!
- return transform;
- });
- }
-
- /**
- * Compute an affine transformation using n >= 3 correspondences of points
- * @param {SpeedyMatrix} transform 2x3 output - affine transform
- * @param {SpeedyMatrix} src 2 x n input points - source coordinates
- * @param {SpeedyMatrix} dest 2 x n input points - destination coordinates
- * @param {object} [options]
- * @param {'default'|'pransac'} [options.method] method of computation
- * @param {SpeedyMatrix|null} [options.mask] (pransac) 1 x n output: i-th entry will be 1 if the i-th input point is an inlier, or 0 otherwise
- * @param {number} [options.reprojectionError] (pransac) given in pixels, used to separate inliers from outliers of a particular model (e.g., 1 pixel)
- * @param {number} [options.numberOfHypotheses] (pransac) number of hypotheses to be generated up-front (e.g., 512)
- * @param {number} [options.bundleSize] (pransac) how many points should we check before reducing the number of viable hypotheses (e.g., 128)
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to an affine transform
- */
- findAffineTransform(transform, src, dest, {
- method = 'default',
- mask = null,
- reprojectionError = 3,
- numberOfHypotheses = 512,
- bundleSize = 128,
- } = {})
- {
- // validate shapes
- if(src.rows != 2 || src.columns < 3 || dest.rows != 2 || dest.columns != src.columns)
- throw new utils_errors/* IllegalArgumentError */.mG(`You need two 2 x n (n >= 3) input matrices to compute an affine transform`);
- else if(transform.rows != 2 || transform.columns != 3)
- throw new utils_errors/* IllegalArgumentError */.mG(`The output of findAffineTransform() is a 2x3 matrix`);
- else if(mask != null && (mask.rows != 1 || mask.columns != src.columns))
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid shape of the inliers mask`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // allocate matrices
- const matptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, transform);
- const srcptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, src);
- const destptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, dest);
- const maskptr = mask != null ? speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, mask) : 0;
-
- // copy input matrices to WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, srcptr, src);
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, destptr, dest);
-
- // run the WASM routine
- switch(method) {
- case 'pransac':
- utils/* Utils.assert */.c.assert(reprojectionError >= 0 && numberOfHypotheses > 0 && bundleSize > 0);
- wasm.exports.Mat32_pransac_affine(matptr, maskptr, srcptr, destptr, numberOfHypotheses, bundleSize, reprojectionError);
- break;
-
- case 'default':
- wasm.exports.Mat32_affine_direct(matptr, srcptr, destptr);
- break;
-
- default:
- throw new utils_errors/* IllegalArgumentError */.mG(`Illegal method for findAffineTransform(): "${method}"`);
- }
-
- // copy output matrices from WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, matptr, transform);
- if(mask != null)
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, maskptr, mask);
-
- // deallocate matrices
- if(mask != null)
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, maskptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, destptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, srcptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, matptr);
-
- // done!
- return transform;
- });
- }
-
- /**
- * Apply an affine transformation to a set of 2D points
- * @param {SpeedyMatrix} dest 2 x n output matrix
- * @param {SpeedyMatrix} src 2 x n input matrix (a set of points)
- * @param {SpeedyMatrix} transform 2x3 affine transform
- * @returns {SpeedyPromise<SpeedyMatrix>} resolves to dest
- */
- applyAffineTransform(dest, src, transform)
- {
- // validate shapes
- if(src.rows != 2 || dest.rows != 2 || src.columns != dest.columns)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid shapes`);
- else if(transform.rows != 2 || transform.columns != 3)
- throw new utils_errors/* IllegalArgumentError */.mG(`The affine transformation must be a 2x3 matrix`);
-
- return speedy_matrix_wasm/* SpeedyMatrixWASM.ready */.r.ready().then(({wasm, memory}) => {
- // allocate matrices
- const matptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, transform);
- const srcptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, src);
- const destptr = speedy_matrix_wasm/* SpeedyMatrixWASM.allocateMat32 */.r.allocateMat32(wasm, memory, dest);
-
- // copy input matrices to WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, srcptr, src);
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyToMat32 */.r.copyToMat32(wasm, memory, matptr, transform);
-
- // run the WASM routine
- wasm.exports.Mat32_transform_affine(destptr, srcptr, matptr);
-
- // copy output matrix from WASM memory
- speedy_matrix_wasm/* SpeedyMatrixWASM.copyFromMat32 */.r.copyFromMat32(wasm, memory, destptr, dest);
-
- // deallocate matrices
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, destptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, srcptr);
- speedy_matrix_wasm/* SpeedyMatrixWASM.deallocateMat32 */.r.deallocateMat32(wasm, memory, matptr);
-
- // done!
- return dest;
- });
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/pipeline-message.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * pipeline-message.js
- * A message that is shared between nodes of a pipeline
- */
-
-
-
-
-
-
-
-
- /**
- * Types of messages
- * @enum {Symbol}
- */
- const SpeedyPipelineMessageType = Object.freeze({
- Nothing: Symbol('Nothing'),
- Image: Symbol('Image'),
- Keypoints: Symbol('Keypoints'),
- Vector2: Symbol('Vector2'),
- LSHTables: Symbol('LSHTables'),
- KeypointMatches: Symbol('KeypointMatches'),
- });
-
- /**
- * Diagnostic data
- * @typedef {Object.<string, string|number>} SpeedyPipelineMessageDiagnosticData
- */
-
- /**
- * A message that is shared between nodes of a pipeline
- * @abstract
- */
- class SpeedyPipelineMessage
- {
- /**
- * Constructor
- * @param {SpeedyPipelineMessageType} type message type
- */
- constructor(type)
- {
- /** @type {SpeedyPipelineMessageType} message type */
- this._type = type;
- }
-
- /**
- * Message type
- * @returns {SpeedyPipelineMessageType}
- */
- get type()
- {
- return this._type;
- }
-
- /**
- * Checks if the type of this message is equal to parameter type
- * @param {SpeedyPipelineMessageType} type
- * @returns {boolean}
- */
- hasType(type)
- {
- return this._type === type;
- }
-
- /**
- * Is this an empty message?
- * @returns {boolean}
- */
- isEmpty()
- {
- return this.hasType(SpeedyPipelineMessageType.Nothing);
- }
-
- /**
- * Convert to string
- * @returns {string}
- */
- toString()
- {
- const type = Object.keys(SpeedyPipelineMessageType).find(
- type => SpeedyPipelineMessageType[type] === this.type
- );
-
- return `message of type ${type}`;
- }
-
- /**
- * Inspect this message for debugging purposes
- * @param {SpeedyGPU} gpu
- * @returns {SpeedyPipelineMessageDiagnosticData}
- */
- inspect(gpu)
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Set parameters
- * @abstract
- * @param {...any} args
- * @returns {SpeedyPipelineMessage} this message
- */
- set(...args)
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Create a message of the specified type
- * @param {SpeedyPipelineMessageType} type
- * @returns {SpeedyPipelineMessage}
- */
- static create(type)
- {
- return createMessage(type);
- }
- }
-
- /**
- * An empty message carrying nothing
- */
- class SpeedyPipelineMessageWithNothing extends SpeedyPipelineMessage
- {
- /**
- * Constructor
- */
- constructor()
- {
- super(SpeedyPipelineMessageType.Nothing);
- }
-
- /**
- * Set parameters
- * @returns {SpeedyPipelineMessage} this message
- */
- set()
- {
- return this;
- }
-
- /**
- * Inspect this message for debugging purposes
- * @param {SpeedyGPU} gpu
- * @returns {SpeedyPipelineMessageDiagnosticData}
- */
- inspect(gpu)
- {
- return {
- type: this.constructor.name
- };
- }
- }
-
- /**
- * A message transporting an image
- */
- class SpeedyPipelineMessageWithImage extends SpeedyPipelineMessage
- {
- /**
- * Constructor
- */
- constructor()
- {
- super(SpeedyPipelineMessageType.Image);
-
- /** @type {SpeedyDrawableTexture} the image we carry */
- this._image = null;
-
- /** @type {ImageFormat} image format */
- this._format = types/* ImageFormat.RGBA */.D3.RGBA;
- }
-
- /**
- * Set parameters
- * @param {SpeedyDrawableTexture} image the image we carry
- * @param {ImageFormat} [format] image format
- * @returns {SpeedyPipelineMessage} this message
- */
- set(image, format = types/* ImageFormat.RGBA */.D3.RGBA)
- {
- // set parameters
- this._image = image;
- this._format = format;
-
- // done!
- return this;
- }
-
- /**
- * Inspect this message for debugging purposes
- * @param {SpeedyGPU} gpu
- * @returns {SpeedyPipelineMessageDiagnosticData}
- */
- inspect(gpu)
- {
- const formatName = Object.keys(types/* ImageFormat */.D3).find(
- format => types/* ImageFormat */.D3[format] === this.format
- );
-
- return {
- type: this.constructor.name,
- format: String(formatName),
- imageSize: this.image ? `${this.image.width}x${this.image.height}` : '0x0',
- image: this.image ? '<image data>' /* possibly MBs of data */ : '',
- hasMipmaps: this.image && this.image.hasMipmaps() ? 'yes' : 'no'
- };
- }
-
- /**
- * The image we carry
- * @returns {SpeedyDrawableTexture}
- */
- get image()
- {
- return this._image;
- }
-
- /**
- * Image format
- * @returns {ImageFormat}
- */
- get format()
- {
- return this._format;
- }
- }
-
- /**
- * A message transporting keypoints
- */
- class SpeedyPipelineMessageWithKeypoints extends SpeedyPipelineMessage
- {
- /**
- * Constructor
- */
- constructor()
- {
- super(SpeedyPipelineMessageType.Keypoints);
-
- /** @type {SpeedyDrawableTexture} encoded keypoints */
- this._encodedKeypoints = null;
-
- /** @type {number} descriptor size in bytes */
- this._descriptorSize = 0;
-
- /** @type {number} extra size in bytes */
- this._extraSize = 0;
-
- /** @type {number} encoder length */
- this._encoderLength = 1;
- }
-
- /**
- * Set parameters
- * @param {SpeedyDrawableTexture} encodedKeypoints encoded keypoints
- * @param {number} descriptorSize in bytes
- * @param {number} extraSize in bytes
- * @param {number} encoderLength positive integer
- * @returns {SpeedyPipelineMessage} this message
- */
- set(encodedKeypoints, descriptorSize, extraSize, encoderLength)
- {
- // set parameters
- this._encodedKeypoints = encodedKeypoints;
- this._descriptorSize = descriptorSize | 0;
- this._extraSize = extraSize | 0;
- this._encoderLength = encoderLength | 0;
-
- // validate
- utils/* Utils.assert */.c.assert(this._descriptorSize >= 0 && this._extraSize >= 0);
- utils/* Utils.assert */.c.assert(this._encoderLength === this._encodedKeypoints.width, 'Invalid encoderLength');
- utils/* Utils.assert */.c.assert(this._encodedKeypoints.width === this._encodedKeypoints.height, 'Invalid encodedKeypoints texture');
-
- // done!
- return this;
- }
-
- /**
- * Inspect this message for debugging purposes
- * @param {SpeedyGPU} gpu
- * @returns {SpeedyPipelineMessageDiagnosticData}
- */
- inspect(gpu)
- {
- return {
- type: this.constructor.name,
- descriptorSize: this.descriptorSize,
- extraSize: this.extraSize,
- encoderLength: this.encoderLength,
- encodedKeypointsSize: this.encodedKeypoints ? `${this.encodedKeypoints.width}x${this.encodedKeypoints.height}` : '0x0',
- encodedKeypoints: this.encodedKeypoints ? utils/* Utils.formatBinaryData */.c.formatBinaryData(this.encodedKeypoints.inspect(gpu).buffer) : '',
- };
- }
-
- /**
- * Encoded keypoints
- * @returns {SpeedyDrawableTexture}
- */
- get encodedKeypoints()
- {
- return this._encodedKeypoints;
- }
-
- /**
- * Descriptor size, in bytes
- * @returns {number}
- */
- get descriptorSize()
- {
- return this._descriptorSize;
- }
-
- /**
- * Extra size, in bytes
- * @returns {number}
- */
- get extraSize()
- {
- return this._extraSize;
- }
-
- /**
- * Encoder length
- * @returns {number}
- */
- get encoderLength()
- {
- return this._encoderLength;
- }
- }
-
- /*
- * A message transporting a set of 2D vectors
- */
- class SpeedyPipelineMessageWith2DVectors extends SpeedyPipelineMessage
- {
- /**
- * Constructor
- */
- constructor()
- {
- super(SpeedyPipelineMessageType.Vector2);
-
- /** @type {SpeedyDrawableTexture} the set of vectors */
- this._vectors = null;
- }
-
- /**
- * Set parameters
- * @param {SpeedyDrawableTexture} vectors the set of vectors
- * @returns {SpeedyPipelineMessage} this message
- */
- set(vectors)
- {
- // set parameters
- this._vectors = vectors;
-
- // done!
- return this;
- }
-
- /**
- * Inspect this message for debugging purposes
- * @param {SpeedyGPU} gpu
- * @returns {SpeedyPipelineMessageDiagnosticData}
- */
- inspect(gpu)
- {
- return {
- type: this.constructor.name,
- vectorsSize: this.vectors ? `${this.vectors.width}x${this.vectors.height}` : '0x0',
- vectors: this.vectors ? utils/* Utils.formatBinaryData */.c.formatBinaryData(this.vectors.inspect(gpu).buffer) : ''
- };
- }
-
- /**
- * The set of vectors
- * @returns {SpeedyDrawableTexture}
- */
- get vectors()
- {
- return this._vectors;
- }
- }
-
- /**
- * A message transporting LSH tables
- */
- class SpeedyPipelineMessageWithLSHTables extends SpeedyPipelineMessage
- {
- /**
- * Constructor
- */
- constructor()
- {
- super(SpeedyPipelineMessageType.LSHTables);
-
- /** @type {SpeedyLSH} LSH data structure */
- this._lsh = null;
- }
-
- /**
- * Set parameters
- * @param {SpeedyLSH} lsh
- * @returns {SpeedyPipelineMessage} this message
- */
- set(lsh)
- {
- // set parameters
- this._lsh = lsh;
-
- // done!
- return this;
- }
-
- /**
- * Inspect this message for debugging purposes
- * @param {SpeedyGPU} gpu
- * @returns {SpeedyPipelineMessageDiagnosticData}
- */
- inspect(gpu)
- {
- return {
- type: this.constructor.name,
- lsh: '<LSH tables>'
- };
- }
-
- /**
- * LSH data structure
- * @returns {SpeedyLSH}
- */
- get lsh()
- {
- return this._lsh;
- }
- }
-
- /*
- * A message transporting a set of keypoint matches
- */
- class SpeedyPipelineMessageWithKeypointMatches extends SpeedyPipelineMessage
- {
- /**
- * Constructor
- */
- constructor()
- {
- super(SpeedyPipelineMessageType.KeypointMatches);
-
- /** @type {SpeedyDrawableTexture} keypoint matches (note: 1 pixel encodes 1 match) */
- this._encodedMatches = null;
-
- /** @type {number} number of matches per keypoint */
- this._matchesPerKeypoint = 1;
- }
-
- /**
- * Set parameters
- * @param {SpeedyDrawableTexture} encodedMatches
- * @param {number} matchesPerKeypoint
- * @returns {SpeedyPipelineMessage} this message
- */
- set(encodedMatches, matchesPerKeypoint)
- {
- // set parameters
- this._encodedMatches = encodedMatches;
- this._matchesPerKeypoint = matchesPerKeypoint | 0;
-
- // validate
- utils/* Utils.assert */.c.assert(this._matchesPerKeypoint > 0);
-
- // done!
- return this;
- }
-
- /**
- * Inspect this message for debugging purposes
- * @param {SpeedyGPU} gpu
- * @returns {SpeedyPipelineMessageDiagnosticData}
- */
- inspect(gpu)
- {
- return {
- type: this.constructor.name,
- matchesPerKeypoint: this.matchesPerKeypoint,
- encodedMatchesSize: this.encodedMatches ? `${this.encodedMatches.width}x${this.encodedMatches.height}` : '0x0',
- encodedMatches: this.encodedMatches ? utils/* Utils.formatBinaryData */.c.formatBinaryData(this.encodedMatches.inspect(gpu).buffer) : ''
- };
- }
-
- /**
- * The matches
- * @returns {SpeedyDrawableTexture}
- */
- get encodedMatches()
- {
- return this._encodedMatches;
- }
-
- /**
- * Number of matches per keypoint
- * @returns {number}
- */
- get matchesPerKeypoint()
- {
- return this._matchesPerKeypoint;
- }
- }
-
-
-
-
-
-
- //
- // Utilities
- //
-
-
-
- /** Map message type to message class */
- const MESSAGE_CLASS = Object.freeze({
- [SpeedyPipelineMessageType.Nothing]: SpeedyPipelineMessageWithNothing,
- [SpeedyPipelineMessageType.Image]: SpeedyPipelineMessageWithImage,
- [SpeedyPipelineMessageType.Keypoints]: SpeedyPipelineMessageWithKeypoints,
- [SpeedyPipelineMessageType.Vector2]: SpeedyPipelineMessageWith2DVectors,
- [SpeedyPipelineMessageType.LSHTables]: SpeedyPipelineMessageWithLSHTables,
- [SpeedyPipelineMessageType.KeypointMatches]: SpeedyPipelineMessageWithKeypointMatches,
- });
-
- /**
- * Create a message of the specified type
- * @param {SpeedyPipelineMessageType} type
- * @returns {SpeedyPipelineMessage}
- */
- function createMessage(type)
- {
- //return Reflect.construct(MESSAGE_CLASS[type], []);
- return new MESSAGE_CLASS[
- // error TS2538: Type 'Symbol' cannot be used as an index type.
- // heck, what the hack...
- /** @type {any} */ ( type )
- ];
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/pipeline-portspec.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * pipeline-portspec.js
- * Specification (requirements) of a port of a node of a pipeline
- */
-
-
-
-
- /**
- * A message constraint is a message validation predicate
- * @typedef {function(SpeedyPipelineMessage): boolean} SpeedyPipelineMessageConstraint
- */
-
- /**
- * A validation predicate that validates all messages
- * @type {SpeedyPipelineMessageConstraint}
- */
- const none = message => true;
-
- /**
- * Specification (requirements) of a port of a node of a pipeline
- */
- class SpeedyPipelinePortSpec
- {
- /**
- * Constructor
- * @param {SpeedyPipelineMessageType} expectedMessageType expected message type
- * @param {SpeedyPipelineMessageConstraint} [messageConstraint] message validation function
- */
- constructor(expectedMessageType, messageConstraint = none)
- {
- /** @type {SpeedyPipelineMessageType} expected message type */
- this._expectedMessageType = expectedMessageType;
-
- /** @type {SpeedyPipelineMessageConstraint} message validation function */
- this._isValidMessage = (typeof messageConstraint === 'function') ? messageConstraint : none;
-
-
- // expect a valid type
- utils/* Utils.assert */.c.assert(this._expectedMessageType != SpeedyPipelineMessageType.Nothing);
- }
-
- /**
- * Checks if two specs have the same expected type
- * @param {SpeedyPipelinePortSpec} spec
- * @returns {boolean}
- */
- isCompatibleWith(spec)
- {
- return this._expectedMessageType == spec._expectedMessageType;
- }
-
- /**
- * Is the given message accepted by a port that abides by this specification?
- * @param {SpeedyPipelineMessage} message
- * @returns {boolean}
- */
- accepts(message)
- {
- return message.hasType(this._expectedMessageType) && this._isValidMessage(message);
- }
-
- /**
- * Convert to string
- * @returns {string}
- */
- toString()
- {
- const type = Object.keys(SpeedyPipelineMessageType).find(
- type => SpeedyPipelineMessageType[type] === this._expectedMessageType
- );
-
- return `Port expects ${type} satisfying ${this._isValidMessage}`;
- }
-
- /**
- * Expected message type
- * @returns {SpeedyPipelineMessageType}
- */
- get expectedMessageType()
- {
- return this._expectedMessageType;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/pipeline-port.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * pipeline-port.js
- * Port of a node of a pipeline
- */
-
-
-
-
-
-
-
-
- // Constants
- const DEFAULT_INPUT_PORT_NAME = 'in';
- const DEFAULT_OUTPUT_PORT_NAME = 'out';
- const ACCEPTABLE_PORT_NAME = /^[a-z][a-zA-Z0-9]*$/;
- const EMPTY_MESSAGE = new SpeedyPipelineMessageWithNothing();
-
- /**
- * Diagnostic data
- * @typedef {import('./pipeline-message.js').SpeedyPipelineMessageDiagnosticData} SpeedyPipelinePortDiagnosticData
- */
-
- /**
- * Port of a node of a pipeline
- * @abstract
- */
- class SpeedyPipelinePort
- {
- /**
- * Constructor
- * @param {string} name the name of this port
- * @param {SpeedyPipelinePortSpec} spec port specification
- * @param {SpeedyPipelineNode} node the node to which this port belongs
- */
- constructor(name, spec, node)
- {
- /** @type {string} the name of this port */
- this._name = String(name);
-
- /** @type {SpeedyPipelinePortSpec} the specification of this port */
- this._spec = spec;
-
- /** @type {SpeedyPipelineNode} the node to which this port belongs */
- this._node = node;
-
- /** @type {SpeedyPipelineMessage} the message located in this port */
- this._message = EMPTY_MESSAGE;
-
-
- // check if we've got an acceptable port name
- utils/* Utils.assert */.c.assert(ACCEPTABLE_PORT_NAME.test(this._name), `Port name "${this._name}" is not acceptable`);
- }
-
- /**
- * The name of this port
- * @returns {string}
- */
- get name()
- {
- return this._name;
- }
-
- /**
- * The node to which this port belongs
- * @returns {SpeedyPipelineNode}
- */
- get node()
- {
- return this._node;
- }
-
- /**
- * Connect this port to another
- * @abstract
- * @param {SpeedyPipelinePort} port
- */
- connectTo(port)
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Is this an input port?
- * @abstract
- * @returns {boolean}
- */
- isInputPort()
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Is this an output port?
- * @returns {boolean}
- */
- isOutputPort()
- {
- return !this.isInputPort();
- }
-
- /**
- * Clear the message stored in this port
- */
- clearMessage()
- {
- this._message = EMPTY_MESSAGE;
- }
-
- /**
- * Is there a valid message located in this port?
- * @returns {boolean}
- */
- hasMessage()
- {
- return !this._message.isEmpty();
- }
-
- /**
- * Read the message that is in this port
- * @returns {SpeedyPipelineMessage}
- */
- read()
- {
- if(this._message.isEmpty())
- throw new utils_errors/* IllegalOperationError */.js(`Can't read from port ${this.name}: nothing to read`);
-
- return this._message;
- }
-
- /**
- * Write a message to this port
- * @param {SpeedyPipelineMessage} message
- */
- write(message)
- {
- throw new utils_errors/* NotSupportedError */.B8(`Can't write ${message} to port ${this.name}: unsupported operation`);
- }
-
- /**
- * Inspect this port for debugging purposes
- * @param {SpeedyGPU} gpu
- * @returns {SpeedyPipelinePortDiagnosticData} diagnostic data
- */
- inspect(gpu)
- {
- return this._message.inspect(gpu);
- }
-
- /**
- * Default port name
- * @abstract
- * @returns {string}
- */
- static get DEFAULT_NAME()
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
- }
-
- /**
- * Output port
- */
- class SpeedyPipelineOutputPort extends SpeedyPipelinePort
- {
- /**
- * Constructor
- * @param {string} name the name of this port
- * @param {SpeedyPipelinePortSpec} spec port specification
- * @param {SpeedyPipelineNode} node the node to which this port belongs
- */
- constructor(name, spec, node)
- {
- super(name, spec, node);
-
- /** @type {SpeedyPipelineMessage} cached message */
- this._cachedMessage = null;
- }
-
- /**
- * Connect this port to another
- * @param {SpeedyPipelineInputPort} port
- */
- connectTo(port)
- {
- if(!port.isInputPort())
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't connect output port ${this.name} to port ${port.name}: expected an input port`);
-
- port.connectTo(this);
- }
-
- /**
- * Is this an input port?
- * @returns {boolean}
- */
- isInputPort()
- {
- return false;
- }
-
- /**
- * Write a message to this port
- * @param {SpeedyPipelineMessage} message
- */
- write(message)
- {
- if(!this._spec.accepts(message))
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't write ${message} to port ${this.name}. ${this._spec}`);
-
- this._message = message;
- }
-
- /**
- * Write a message to this port using a cached message object
- * @param {...any} args to be passed to SpeedyPipelineMessage.set()
- */
- swrite(...args)
- {
- if(this._cachedMessage == null)
- this._cachedMessage = SpeedyPipelineMessage.create(this._spec.expectedMessageType);
-
- this.write(this._cachedMessage.set(...args));
- }
-
- /**
- * Default port name
- * @returns {string}
- */
- static get DEFAULT_NAME()
- {
- return DEFAULT_OUTPUT_PORT_NAME;
- }
- }
-
- /**
- * Input port
- */
- class SpeedyPipelineInputPort extends SpeedyPipelinePort
- {
- /**
- * Constructor
- * @param {string} name the name of this port
- * @param {SpeedyPipelinePortSpec} spec port specification
- * @param {SpeedyPipelineNode} node the node to which this port belongs
- */
- constructor(name, spec, node)
- {
- super(name, spec, node);
-
- /** @type {SpeedyPipelineOutputPort|null} incoming link */
- this._incomingLink = null;
- }
-
- /**
- * Incoming link
- * @returns {SpeedyPipelineOutputPort|null}
- */
- get incomingLink()
- {
- return this._incomingLink;
- }
-
- /**
- * Connect this port to another
- * @param {SpeedyPipelineOutputPort} port
- */
- connectTo(port)
- {
- if(!port.isOutputPort())
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't connect input port ${this.name} of "${this.node.fullName}" to input port ${port.name} of "${port.node.fullName}": expected an output port`);
- else if(!this._spec.isCompatibleWith(port._spec))
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't connect port ${this.name} of "${this.node.fullName}" to port ${port.name} of "${port.node.fullName}": incompatible types`);
-
- this._incomingLink = port;
- }
-
- /**
- * Unlink this port
- */
- disconnect()
- {
- this._incomingLink = null;
- }
-
- /**
- * Is this an input port?
- * @returns {boolean}
- */
- isInputPort()
- {
- return true;
- }
-
- /**
- * Receive a message using the incoming link
- * @param {string} [nodeName]
- * @returns {SpeedyPipelineMessage}
- */
- pullMessage(nodeName = '')
- {
- const name = nodeName.length > 0 ? `${this.name} of ${nodeName}` : this.name;
-
- if(this._incomingLink == null)
- throw new utils_errors/* IllegalOperationError */.js(`No incoming link for input port ${name}`);
-
- const message = this._incomingLink.read();
- if(!this._spec.accepts(message))
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't receive ${message} at port ${name}: ${this._spec}`);
-
- return (this._message = message);
- }
-
- /**
- * Default port name
- * @returns {string}
- */
- static get DEFAULT_NAME()
- {
- return DEFAULT_INPUT_PORT_NAME;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/pipeline-portbuilder.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * pipeline-portbuilder.js
- * Builder of a port of a node of a pipeline
- */
-
-
-
-
-
-
-
- /**
- * @typedef {import('./pipeline-portspec').SpeedyPipelineMessageConstraint} SpeedyPipelineMessageConstraint
- */
-
- /**
- * Builder of a port of a node of a pipeline
- */
- class SpeedyPipelinePortBuilder
- {
- /**
- * Constructor
- * @param {typeof SpeedyPipelinePort} portClass input or output?
- * @param {string} portName
- */
- constructor(portClass, portName)
- {
- /** @type {typeof SpeedyPipelinePort} input or output? */
- this._class = portClass;
-
- /** @type {string} port name */
- this._name = String(portName);
-
- /** @type {SpeedyPipelineMessageType} accepted message type */
- this._type = SpeedyPipelineMessageType.Nothing;
-
- /** @type {SpeedyPipelineMessageConstraint} message validation function */
- this._messageConstraint = undefined;
- }
-
- /**
- * Declare that the new port expects a certain type of message
- * @param {SpeedyPipelineMessageType} type expected type
- * @returns {SpeedyPipelinePortBuilder} this builder
- */
- expects(type)
- {
- utils/* Utils.assert */.c.assert(this._type == SpeedyPipelineMessageType.Nothing);
- utils/* Utils.assert */.c.assert(type != SpeedyPipelineMessageType.Nothing);
-
- this._type = type;
-
- return this;
- }
-
- /**
- * Declare that the new port expects messages satisfying a constraint
- * @param {SpeedyPipelineMessageConstraint} constraint
- * @returns {SpeedyPipelinePortBuilder} this builder
- */
- satisfying(constraint)
- {
- utils/* Utils.assert */.c.assert(this._type != SpeedyPipelineMessageType.Nothing, 'You must first declare what type of message this port expects');
- utils/* Utils.assert */.c.assert(this._messageConstraint === undefined);
- utils/* Utils.assert */.c.assert(typeof constraint === 'function');
-
- this._messageConstraint = constraint;
-
- return this;
- }
-
- /**
- * Build a port
- * @param {SpeedyPipelineNode} node the node to which the new port will belong
- * @returns {SpeedyPipelinePort}
- */
- build(node)
- {
- const spec = new SpeedyPipelinePortSpec(this._type, this._messageConstraint);
- return Reflect.construct(this._class, [this._name, spec, node]);
- }
- }
-
- /**
- * Creates a builder for an input port
- * @param {string} [portName]
- * @returns {SpeedyPipelinePortBuilder}
- */
- function InputPort(portName = SpeedyPipelineInputPort.DEFAULT_NAME)
- {
- return new SpeedyPipelinePortBuilder(SpeedyPipelineInputPort, portName);
- }
-
- /**
- * Creates a builder for an output port
- * @param {string} [portName]
- * @returns {SpeedyPipelinePortBuilder}
- */
- function OutputPort(portName = SpeedyPipelineOutputPort.DEFAULT_NAME)
- {
- return new SpeedyPipelinePortBuilder(SpeedyPipelineOutputPort, portName);
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/pipeline-node.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * pipeline-node.js
- * Node of a pipeline
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /** @typedef {Object<string,SpeedyPipelineInputPort>} InputPortDictionary */
- /** @typedef {Object<string,SpeedyPipelineOutputPort>} OutputPortDictionary */
-
- /** Generate a random name for a node */
- const generateRandomName = () => Math.random().toString(16).substr(2);
-
- /** Create an empty input port dictionary */
- const createInputPortDictionary = () => /** @type {InputPortDictionary} */ ( Object.create(null) );
-
- /** Create an empty output port dictionary */
- const createOutputPortDictionary = () => /** @type {OutputPortDictionary} */ ( Object.create(null) );
-
- /**
- * Map an array of input ports to an InputPortDictionary whose keys are their names
- * @param {SpeedyPipelineInputPort[]} ports
- * @returns {InputPortDictionary}
- */
- function InputPortDictionary(ports)
- {
- return ports.reduce((dict, port) => ((dict[port.name] = port), dict), createInputPortDictionary());
- }
-
- /**
- * Map an array of output ports to an OutputPortDictionary whose keys are their names
- * @param {SpeedyPipelineOutputPort[]} ports
- * @returns {OutputPortDictionary}
- */
- function OutputPortDictionary(ports)
- {
- return ports.reduce((dict, port) => ((dict[port.name] = port), dict), createOutputPortDictionary());
- }
-
- /** A flag used for debugging purposes */
- let _texView = false;
-
-
-
- /**
- * Node of a pipeline
- * @abstract
- */
- class SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] the name of this node
- * @param {number} [texCount] number of work textures
- * @param {SpeedyPipelinePortBuilder[]} [portBuilders] port builders
- */
- constructor(name = generateRandomName(), texCount = 0, portBuilders = [])
- {
- /** @type {string} the name of this node */
- this._name = String(name);
-
- /** @type {SpeedyDrawableTexture[]} work texture(s) */
- this._tex = (new Array(texCount)).fill(null);
-
-
-
- // build the ports
- const ports = portBuilders.map(builder => builder.build(this));
- const inputPorts = /** @type {SpeedyPipelineInputPort[]} */ ( ports.filter(port => port.isInputPort()) );
- const outputPorts = /** @type {SpeedyPipelineOutputPort[]} */ ( ports.filter(port => port.isOutputPort()) );
-
- /** @type {InputPortDictionary} input ports */
- this._inputPorts = InputPortDictionary(inputPorts);
-
- /** @type {OutputPortDictionary} output ports */
- this._outputPorts = OutputPortDictionary(outputPorts);
-
-
-
- // validate
- if(this._name.length == 0)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid name "${this._name}" for node ${this.fullName}`);
- else if(portBuilders.length == 0)
- throw new utils_errors/* IllegalArgumentError */.mG(`No ports have been found in node ${this.fullName}`);
- }
-
- /**
- * The name of this node
- * @returns {string}
- */
- get name()
- {
- return this._name;
- }
-
- /**
- * Name and type of this node
- * @returns {string}
- */
- get fullName()
- {
- return `${this.constructor.name}[${this.name}]`;
- }
-
- /**
- * Find input port by name
- * @param {string} [portName]
- * @returns {SpeedyPipelineInputPort}
- */
- input(portName = SpeedyPipelineInputPort.DEFAULT_NAME)
- {
- if(portName in this._inputPorts)
- return this._inputPorts[portName];
-
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't find input port ${portName} in node ${this.fullName}`);
- }
-
- /**
- * Find output port by name
- * @param {string} [portName]
- * @returns {SpeedyPipelineOutputPort}
- */
- output(portName = SpeedyPipelineOutputPort.DEFAULT_NAME)
- {
- if(portName in this._outputPorts)
- return this._outputPorts[portName];
-
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't find output port ${portName} in node ${this.fullName}`);
- }
-
- /**
- * Get data from the input ports and execute
- * the task that this node is supposed to!
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- execute(gpu)
- {
- let portName;
-
- // clear output ports
- for(portName in this._outputPorts)
- this._outputPorts[portName].clearMessage();
-
- // let the input ports receive what is due
- for(portName in this._inputPorts)
- this._inputPorts[portName].pullMessage(this.fullName);
-
- // run the task
- const runTask = this._run(gpu);
- if(typeof runTask === 'undefined')
- return void(this._finishExecution(gpu));
- else
- return runTask.then(() => this._finishExecution(gpu));
- }
-
- /**
- * Finish the execution of this node;
- * to be called after execute()
- * @param {SpeedyGPU} gpu
- */
- _finishExecution(gpu)
- {
- // ensure that no output ports are empty
- for(const portName in this._outputPorts) {
- utils/* Utils.assert */.c.assert(this._outputPorts[portName].hasMessage(), `Did you forget to write data to the output port ${portName} of ${this.fullName}?`);
- }
-
- // diagnosticize the node / pipeline
- if(settings/* Settings.logging */.Z.logging === 'diagnostic') {
- utils/* Utils.log */.c.log(`%c ${this.fullName} `, 'font-size:12pt;font-weight:bold;color:white;background:blue');
-
- // Inspecting the data has performance implications.
- // It is for diagnostic purposes only, not meant to be done in production!
-
- for(const portName in this._inputPorts)
- utils/* Utils.log */.c.log(`%c-> ${portName}:`, 'font-size:10pt;font-weight:bold', this._inputPorts[portName].inspect(gpu));
-
- for(const portName in this._outputPorts)
- utils/* Utils.log */.c.log(`%c<- ${portName}:`, 'font-size:10pt;font-weight:bold', this._outputPorts[portName].inspect(gpu));
- }
- }
-
- /**
- * Run the specific task of this node
- * @abstract
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Initializes this node
- * @param {SpeedyGPU} gpu
- */
- init(gpu)
- {
- gpu.subscribe(this._allocateWorkTextures, this, gpu);
- this._allocateWorkTextures(gpu);
- }
-
- /**
- * Releases this node
- * @param {SpeedyGPU} gpu
- */
- release(gpu)
- {
- this._deallocateWorkTextures(gpu);
- gpu.unsubscribe(this._allocateWorkTextures, this);
- }
-
- /**
- * Clear all ports
- */
- clearPorts()
- {
- let portName;
-
- for(portName in this._inputPorts)
- this._inputPorts[portName].clearMessage();
-
- for(portName in this._outputPorts)
- this._outputPorts[portName].clearMessage();
- }
-
- /**
- * Find all nodes that feed input to this node
- * @returns {SpeedyPipelineNode[]}
- */
- inputNodes()
- {
- const nodes = [];
-
- for(const portName in this._inputPorts) {
- const port = this._inputPorts[portName];
- if(port.incomingLink != null)
- nodes.push(port.incomingLink.node);
- }
-
- return nodes;
- }
-
- /**
- * Is this a source of the pipeline?
- * @returns {boolean}
- */
- isSource()
- {
- return false;
- }
-
- /**
- * Is this a sink of the pipeline?
- * @returns {boolean}
- */
- isSink()
- {
- return false;
-
- // note: a portal sink has no output ports, but it isn't a sink of the pipeline!
- //return Object.keys(this._outputPorts).length == 0;
- }
-
- /**
- * Allocate work texture(s)
- * @param {SpeedyGPU} gpu
- */
- _allocateWorkTextures(gpu)
- {
- for(let j = 0; j < this._tex.length; j++)
- this._tex[j] = gpu.texturePool.allocate();
- }
-
- /**
- * Deallocate work texture(s)
- * @param {SpeedyGPU} gpu
- */
- _deallocateWorkTextures(gpu)
- {
- for(let j = this._tex.length - 1; j >= 0; j--)
- this._tex[j] = gpu.texturePool.free(this._tex[j]);
- }
-
- /**
- * Visually inspect a texture for debugging purposes
- * @param {SpeedyGPU} gpu
- * @param {SpeedyDrawableTexture} texture
- */
- _visualize(gpu, texture)
- {
- const canvas = gpu.renderToCanvas(texture);
- if(!_texView) {
- document.body.appendChild(canvas);
- _texView = true;
- }
- }
- }
-
- /**
- * Source node (a node with no input ports)
- * @abstract
- */
- class SpeedyPipelineSourceNode extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] the name of this node
- * @param {number} [texCount] number of work textures
- * @param {SpeedyPipelinePortBuilder[]} [portBuilders] port builders
- */
- constructor(name = undefined, texCount = undefined, portBuilders = undefined)
- {
- super(name, texCount, portBuilders);
- utils/* Utils.assert */.c.assert(Object.keys(this._inputPorts).length == 0);
- }
-
- /**
- * Is this a source of the pipeline?
- * @returns {boolean}
- */
- isSource()
- {
- return true;
- }
- }
-
- /**
- * Sink node (a node with no output ports)
- * @abstract
- */
- class SpeedyPipelineSinkNode extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] the name of this node
- * @param {number} [texCount] number of work textures
- * @param {SpeedyPipelinePortBuilder[]} [portBuilders] port builders
- */
- constructor(name = undefined, texCount = undefined, portBuilders = undefined)
- {
- super(name, texCount, portBuilders);
- utils/* Utils.assert */.c.assert(Object.keys(this._outputPorts).length == 0);
- }
-
- /**
- * Export data from this node to the user
- * @abstract
- * @returns {SpeedyPromise<any>}
- */
- export()
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Is this a sink of the pipeline?
- * @returns {boolean}
- */
- isSink()
- {
- return true;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/speedy-keypoint-match.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-match.js
- * A match between two keypoint descriptors
- */
-
-
-
- // Constants
- const MATCH_NOT_FOUND = -1;
-
- /**
- * A match between two keypoint descriptors
- */
- class SpeedyKeypointMatch
- {
- /**
- * Constructor
- * @param {number} index index of the stored keypoint, a non-negative integer
- * @param {number} distance a measure of the quality of the match, a non-negative number
- */
- constructor(index, distance)
- {
- const isValid = distance < globals.MATCH_MAX_DISTANCE;
-
- /** @type {number} index of the stored keypoint */
- this._index = isValid ? (index | 0) : MATCH_NOT_FOUND;
-
- /** @type {number} a measure of the quality of the match */
- this._distance = isValid ? +distance : Number.POSITIVE_INFINITY;
-
- // done!
- return Object.freeze(this);
- }
-
- /**
- * The index of the stored keypoint
- * @returns {number}
- */
- get index()
- {
- return this._index;
- }
-
- /**
- * A measure of the quality of the match (lower values indicate better matches)
- * @returns {number}
- */
- get distance()
- {
- return this._distance;
- }
-
- /**
- * A string representation of the keypoint match
- * @returns {string}
- */
- toString()
- {
- return `SpeedyKeypointMatch(${this.index},${this.distance})`;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/speedy-keypoint.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-keypoint.js
- * Keypoint class
- */
-
-
-
-
-
-
- /**
- * Represents a keypoint
- */
- class SpeedyKeypoint
- {
- /**
- * Constructor
- * @param {number} x X position
- * @param {number} y Y position
- * @param {number} [lod] Level-of-detail
- * @param {number} [rotation] Rotation in radians
- * @param {number} [score] Cornerness measure
- * @param {SpeedyKeypointDescriptor|null} [descriptor] Keypoint descriptor, if any
- */
- constructor(x, y, lod = 0.0, rotation = 0.0, score = 0.0, descriptor = null)
- {
- /** @type {SpeedyPoint2} keypoint position */
- this._position = new SpeedyPoint2(+x, +y);
-
- /** @type {number} level of detail */
- this._lod = +lod;
-
- /** @type {number} rotation in radians */
- this._rotation = +rotation;
-
- /** @type {number} a cornerness measure */
- this._score = +score;
-
- /** @type {SpeedyKeypointDescriptor|null} keypoint descriptor, if any */
- this._descriptor = descriptor;
- }
-
- /**
- * Converts this keypoint to a descriptive string
- * @returns {string}
- */
- toString()
- {
- return `SpeedyKeypoint(${this.x},${this.y})`;
- }
-
- /**
- * The position of this keypoint
- * @returns {SpeedyPoint2}
- */
- get position()
- {
- return this._position;
- }
-
- /**
- * The x-position of this keypoint
- * @returns {number}
- */
- get x()
- {
- return this._position.x;
- }
-
- /**
- * The x-position of this keypoint
- * @param {number} value
- */
- set x(value)
- {
- this._position.x = +value;
- }
-
- /**
- * The y-position of this keypoint
- * @returns {number}
- */
- get y()
- {
- return this._position.y;
- }
-
- /**
- * The y-position of this keypoint
- * @param {number} value
- */
- set y(value)
- {
- this._position.y = +value;
- }
-
- /**
- * The pyramid level-of-detail from which this keypoint was extracted
- * @returns {number}
- */
- get lod()
- {
- return this._lod;
- }
-
- /**
- * Scale: 2^lod
- * @returns {number}
- */
- get scale()
- {
- return Math.pow(2, this._lod);
- }
-
- /**
- * The orientation of the keypoint, in radians
- * @returns {number} Angle in radians
- */
- get rotation()
- {
- return this._rotation;
- }
-
- /**
- * Score: a cornerness measure
- * @returns {number} Score
- */
- get score()
- {
- return this._score;
- }
-
- /**
- * Keypoint descriptor
- * @return {SpeedyKeypointDescriptor|null}
- */
- get descriptor()
- {
- return this._descriptor;
- }
- }
-
- /**
- * Represents a tracked keypoint
- */
- class SpeedyTrackedKeypoint extends SpeedyKeypoint
- {
- /**
- * Constructor
- * @param {number} x X position
- * @param {number} y Y position
- * @param {number} [lod] Level-of-detail
- * @param {number} [rotation] Rotation in radians
- * @param {number} [score] Cornerness measure
- * @param {SpeedyKeypointDescriptor|null} [descriptor] Keypoint descriptor, if any
- * @param {SpeedyVector2} [flow] flow vector
- */
- constructor(x, y, lod = 0.0, rotation = 0.0, score = 0.0, descriptor = null, flow = new SpeedyVector2(0,0))
- {
- super(x, y, lod, rotation, score, descriptor);
-
- /** @type {SpeedyVector2} flow vector */
- this._flow = flow;
- }
-
- /**
- * Flow vector
- * @returns {SpeedyVector2}
- */
- get flow()
- {
- return this._flow;
- }
- }
-
- /**
- * Represents a matched keypoint
- */
- class SpeedyMatchedKeypoint extends SpeedyKeypoint
- {
- /**
- * Constructor
- * @param {number} x X position
- * @param {number} y Y position
- * @param {number} [lod] Level-of-detail
- * @param {number} [rotation] Rotation in radians
- * @param {number} [score] Cornerness measure
- * @param {SpeedyKeypointDescriptor|null} [descriptor] Keypoint descriptor, if any
- * @param {SpeedyKeypointMatch[]} [matches] Keypoint matches, if any
- */
- constructor(x, y, lod = 0.0, rotation = 0.0, score = 0.0, descriptor = null, matches = [])
- {
- super(x, y, lod, rotation, score, descriptor);
-
- /** @type {SpeedyKeypointMatch[]} keypoint matches */
- this._matches = matches;
- }
-
- /**
- * Keypoint matches
- * @returns {SpeedyKeypointMatch[]}
- */
- get matches()
- {
- return this._matches;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/pipeline.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * pipeline.js
- * A pipeline is a network of nodes in which data flows to a sink
- */
-
-
-
-
-
-
-
-
-
-
-
- /**
- * A dictionary indexed by the names of the sink nodes
- * @typedef {Object<string,any>} SpeedyPipelineOutput
- */
-
- /** @type {SpeedyGPU} shared GPU programs & textures */
- let gpu = null;
-
- /** @type {number} gpu reference count */
- let referenceCount = 0;
-
-
-
- /**
- * A pipeline is a network of nodes in which data flows to a sink
- */
- class SpeedyPipeline
- {
- /**
- * Constructor
- */
- constructor()
- {
- /** @type {SpeedyPipelineNode[]} the collection of all nodes that belong to this pipeline */
- this._nodes = [];
-
- /** @type {SpeedyPipelineNode[]} a sequence of nodes: from the source(s) to the sink */
- this._sequence = [];
-
- /** @type {boolean} are we running the pipeline at this moment? */
- this._busy = false;
- }
-
- /**
- * Find a node by its name
- * @template T extends SpeedyPipelineNode
- * @param {string} name
- * @returns {T|null}
- */
- node(name)
- {
- for(let i = 0, n = this._nodes.length; i < n; i++) {
- if(this._nodes[i].name === name)
- return this._nodes[i];
- }
-
- return null;
- }
-
- /**
- * Initialize the pipeline
- * @param {...SpeedyPipelineNode} nodes
- * @returns {SpeedyPipeline} this pipeline
- */
- init(...nodes)
- {
- // validate
- if(this._nodes.length > 0)
- throw new utils_errors/* IllegalOperationError */.js(`The pipeline has already been initialized`);
- else if(nodes.length == 0)
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't initialize the pipeline. Please specify its nodes`);
-
- // create a GPU instance and increase the reference count
- if(0 == referenceCount++) {
- utils/* Utils.assert */.c.assert(!gpu, 'Duplicate SpeedyGPU instance');
- gpu = new SpeedyGPU();
- }
-
- // add nodes to the network
- for(let i = 0; i < nodes.length; i++) {
- const node = nodes[i];
- if(!this._nodes.includes(node))
- this._nodes.push(node);
- }
-
- // generate the sequence of nodes
- this._sequence = SpeedyPipeline._tsort(this._nodes);
- SpeedyPipeline._validateSequence(this._sequence);
-
- // initialize nodes
- for(let i = 0; i < this._sequence.length; i++)
- this._sequence[i].init(gpu);
-
- // done!
- return this;
- }
-
- /**
- * Release the resources associated with this pipeline
- * @returns {null}
- */
- release()
- {
- if(this._nodes.length == 0)
- throw new utils_errors/* IllegalOperationError */.js(`The pipeline has already been released or has never been initialized`);
-
- // release nodes
- for(let i = this._sequence.length - 1; i >= 0; i--)
- this._sequence[i].release(gpu);
- this._sequence.length = 0;
- this._nodes.length = 0;
-
- // decrease reference count and release GPU if necessary
- if(0 == --referenceCount)
- gpu = gpu.release();
-
- // done!
- return null;
- }
-
- /**
- * Run the pipeline
- * @returns {SpeedyPromise<SpeedyPipelineOutput>} results are indexed by the names of the sink nodes
- */
- run()
- {
- utils/* Utils.assert */.c.assert(this._sequence.length > 0, `The pipeline has not been initialized or has been released`);
-
- // is the pipeline busy?
- if(this._busy) {
- // if so, we need to wait 'til it finishes
- return new speedy_promise/* SpeedyPromise */.s((resolve, reject) => {
- setTimeout(() => this.run().then(resolve, reject), 0);
- });
- }
- else {
- // the pipeline is now busy and won't accept concurrent tasks
- // (we allocate textures using a single pool)
- this._busy = true;
- }
-
- // find the sinks
- const sinks = /** @type {SpeedyPipelineSinkNode[]} */ ( this._sequence.filter(node => node.isSink()) );
-
- // create output template
- const template = SpeedyPipeline._createOutputTemplate(sinks);
-
- // diagnostic log
- if(settings/* Settings.logging */.Z.logging === 'diagnostic')
- utils/* Utils.log */.c.log('%c RUNNING PIPELINE ', 'background:red;color:white;font-size:28pt;font-weight:bold');
-
- // run the pipeline
- return SpeedyPipeline._runSequence(this._sequence).then(() =>
-
- // export results
- speedy_promise/* SpeedyPromise.all */.s.all(sinks.map(sink => sink.export().turbocharge())).then(results =>
-
- // aggregate results by the names of the sinks
- results.reduce((obj, val, idx) => ((obj[sinks[idx].name] = val), obj), template)
-
- )
-
- ).finally(() => {
-
- // clear all ports
- for(let i = this._sequence.length - 1; i >= 0; i--)
- this._sequence[i].clearPorts();
-
- // the pipeline is no longer busy
- this._busy = false;
-
- // diagnostic log
- if(settings/* Settings.logging */.Z.logging === 'diagnostic') {
- utils/* Utils.log */.c.log('%c PIPELINE OUTPUT \n', 'background:green;color:white;font-size:16pt;font-weight:bold');
- Object.keys(template).forEach(entry => {
- utils/* Utils.log */.c.log('%c' + entry + ':', 'font-size:10pt;font-weight:bold', template[entry]);
- });
- }
-
- }).turbocharge();
- }
-
- /**
- * @internal
- *
- * GPU instance
- * @returns {SpeedyGPU}
- */
- get _gpu()
- {
- return gpu;
- }
-
- /**
- * Execute the tasks of a sequence of nodes
- * @param {SpeedyPipelineNode[]} sequence sequence of nodes
- * @param {number} [i] in [0,n)
- * @param {number} [n] number of nodes
- * @returns {SpeedyPromise<void>}
- */
- static _runSequence(sequence, i = 0, n = sequence.length)
- {
- for(; i < n; i++) {
- const runTask = sequence[i].execute(gpu);
-
- // this call greatly improves performance when downloading pixel data using PBOs
- gpu.gl.flush();
-
- if(typeof runTask !== 'undefined')
- return runTask.then(() => SpeedyPipeline._runSequence(sequence, i+1, n));
- }
-
- return speedy_promise/* SpeedyPromise.resolve */.s.resolve();
- }
-
- /**
- * Topological sorting
- * @param {SpeedyPipelineNode[]} nodes
- * @returns {SpeedyPipelineNode[]}
- */
- static _tsort(nodes)
- {
- /** @typedef {[SpeedyPipelineNode, boolean]} StackNode */
-
- const outlinks = SpeedyPipeline._outlinks(nodes);
- const stack = nodes.map(node => /** @type {StackNode} */ ([ node, false ]) );
- const trash = new Set();
- const sorted = new Array(nodes.length);
- let j = sorted.length;
-
- while(stack.length > 0) {
- const [ node, done ] = stack.pop();
- if(!done) {
- if(!trash.has(node)) {
- const outnodes = outlinks.get(node);
-
- trash.add(node);
- stack.push([ node, true ]);
- stack.push(...(outnodes.map(node => /** @type {StackNode} */ ([ node, false ]) )));
-
- if(outnodes.some(node => trash.has(node) && !sorted.includes(node)))
- throw new utils_errors/* IllegalOperationError */.js(`Pipeline networks cannot have cycles!`);
- }
- }
- else
- sorted[--j] = node;
- }
-
- return sorted;
- }
-
- /**
- * Figure out the outgoing links of all nodes
- * @param {SpeedyPipelineNode[]} nodes
- * @returns {Map<SpeedyPipelineNode,SpeedyPipelineNode[]>}
- */
- static _outlinks(nodes)
- {
- const outlinks = new Map();
-
- for(let k = 0; k < nodes.length; k++)
- outlinks.set(nodes[k], []);
-
- for(let i = 0; i < nodes.length; i++) {
- const to = nodes[i];
- const inputs = to.inputNodes();
-
- for(let j = 0; j < inputs.length; j++) {
- const from = inputs[j];
- const links = outlinks.get(from);
-
- if(!links)
- throw new utils_errors/* IllegalOperationError */.js(`Can't initialize the pipeline. Missing node: ${from.fullName}. Did you forget to add it to the initialization list?`);
-
- if(!links.includes(to))
- links.push(to);
- }
- }
-
- return outlinks;
- }
-
- /**
- * Generate the output template by aggregating the names of the sinks
- * @param {SpeedyPipelineNode[]} [sinks]
- * @returns {SpeedyPipelineOutput}
- */
- static _createOutputTemplate(sinks = [])
- {
- const template = Object.create(null);
-
- for(let i = sinks.length - 1; i >= 0; i--)
- template[sinks[i].name] = null;
-
- return template;
- }
-
- /**
- * Validate a sequence of nodes
- * @param {SpeedyPipelineNode[]} sequence
- */
- static _validateSequence(sequence)
- {
- if(sequence.length == 0)
- throw new utils_errors/* IllegalOperationError */.js(`Pipeline doesn't have nodes`);
- else if(!sequence[0].isSource())
- throw new utils_errors/* IllegalOperationError */.js(`Pipeline doesn't have a source`);
- else if(!sequence.find(node => node.isSink()))
- throw new utils_errors/* IllegalOperationError */.js(`Pipeline doesn't have a sink`);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/images/source.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * image-input.js
- * Gets an image into a pipeline
- */
-
-
-
-
-
-
-
-
-
-
-
-
- // Constants
- const UPLOAD_BUFFER_SIZE = 2; // how many textures we allocate for uploading data
-
- /**
- * Gets an image into a pipeline
- */
- class SpeedyPipelineNodeImageSource extends SpeedyPipelineSourceNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, UPLOAD_BUFFER_SIZE, [
- OutputPort().expects(SpeedyPipelineMessageType.Image)
- ]);
-
- /** @type {SpeedyMedia|null} source media */
- this._media = null;
-
- /** @type {number} texture index */
- this._textureIndex = 0;
- }
-
- /**
- * Source media
- * @returns {SpeedyMedia|null}
- */
- get media()
- {
- return this._media;
- }
-
- /**
- * Source media
- * @param {SpeedyMedia|null} media
- */
- set media(media)
- {
- if(media !== null && !(media instanceof SpeedyMedia))
- throw new utils_errors/* IllegalArgumentError */.mG(`Not a SpeedyMedia: ${media}`);
-
- this._media = media;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- if(this._media == null)
- throw new utils_errors/* IllegalOperationError */.js(`Did you forget to set the media of ${this.fullName}?`);
-
- // use round-robin to mitigate WebGL's implicit synchronization
- // and maybe minimize texture upload times
- this._textureIndex = (this._textureIndex + 1) % this._tex.length;
-
- // upload texture
- const outputTexture = this._tex[this._textureIndex];
- gpu.upload(this._media._source, outputTexture);
- this.output().swrite(outputTexture, this._media._format);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/images/sink.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * image-output.js
- * Gets an image out of a pipeline
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Gets an image out of a pipeline
- */
- class SpeedyPipelineNodeImageSink extends SpeedyPipelineSinkNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = 'image')
- {
- super(name, 0, [
- InputPort().expects(SpeedyPipelineMessageType.Image)
- ]);
-
- /** @type {ImageBitmap} output bitmap */
- this._bitmap = null;
-
- /** @type {ImageFormat} output format */
- this._format = types/* ImageFormat.RGBA */.D3.RGBA;
- }
-
- /**
- * Export data from this node to the user
- * @returns {SpeedyPromise<SpeedyMedia>}
- */
- export()
- {
- utils/* Utils.assert */.c.assert(this._bitmap != null);
- return SpeedyMedia.load(this._bitmap, { format: this._format }, false);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
-
- return new speedy_promise/* SpeedyPromise */.s(resolve => {
- const canvas = gpu.renderToCanvas(image);
- createImageBitmap(canvas, 0, canvas.height - image.height, image.width, image.height).then(bitmap => {
- this._bitmap = bitmap;
- this._format = format;
- resolve();
- });
- });
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/images/multiplexer.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * multiplexer.js
- * Image multiplexer
- */
-
-
-
-
-
-
-
-
-
-
-
- /** @type {string[]} the names of the input ports indexed by their number */
- const INPUT_PORT = [ 'in0', 'in1' ];
-
- /**
- * Image multiplexer
- */
- class SpeedyPipelineNodeImageMultiplexer extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 0, [
- ...(INPUT_PORT.map(portName => InputPort(portName).expects(SpeedyPipelineMessageType.Image))),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {number} which port should be linked to the output? */
- this._port = 0;
- }
-
- /**
- * The number of the port that should be linked to the output
- * @returns {number}
- */
- get port()
- {
- return this._port;
- }
-
- /**
- * The number of the port that should be linked to the output
- * @param {number} port
- */
- set port(port)
- {
- if(port < 0 || port >= INPUT_PORT.length)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid port: ${port}`);
-
- this._port = port | 0;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const message = this.input(INPUT_PORT[this._port]).read();
-
- this.output().write(message);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/images/buffer.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * buffer.js
- * Image Buffer
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Image Buffer: a node with memory.
- * At time t, it outputs the image received at time t-1
- */
- class SpeedyPipelineNodeImageBuffer extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 2, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image)
- ]);
-
- /** @type {number} current page: 0 or 1 */
- this._pageIndex = 0;
-
- /** @type {boolean} first run? */
- this._initialized = false;
-
- /** @type {ImageFormat} previous image format */
- this._previousFormat = types/* ImageFormat.RGBA */.D3.RGBA;
-
- /** @type {boolean} frozen buffer? */
- this._frozen = false;
- }
-
- /**
- * A frozen buffer discards the input, effectively increasing the buffering time
- * @returns {boolean}
- */
- get frozen()
- {
- return this._frozen;
- }
-
- /**
- * A frozen buffer discards the input, effectively increasing the buffering time
- * @param {boolean} value
- */
- set frozen(value)
- {
- this._frozen = Boolean(value);
- }
-
- /**
- * Releases this node
- * @param {SpeedyGPU} gpu
- */
- release(gpu)
- {
- this._initialized = false;
- super.release(gpu);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const previousFormat = this._previousFormat;
- const page = this._tex;
- const previousInputTexture = page[1 - this._pageIndex];
- const outputTexture = page[this._pageIndex];
-
- // can't store pyramids
- if(image.hasMipmaps())
- throw new utils_errors/* NotSupportedError */.B8(`${this.fullName} can't bufferize a pyramid`);
-
- // bufferize
- if(!this._frozen || !this._initialized) {
- // store input
- this._previousFormat = format;
- previousInputTexture.resize(image.width, image.height);
- image.copyTo(previousInputTexture);
-
- // page flipping
- this._pageIndex = 1 - this._pageIndex;
- }
-
- // first run?
- if(!this._initialized) {
- this._initialized = true;
- this.output().swrite(previousInputTexture, format);
- return;
- }
-
- // done!
- this.output().swrite(outputTexture, previousFormat);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/images/pyramid.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * pyramid.js
- * Generate pyramid
- */
-
-
-
-
-
-
-
-
-
-
-
- // Constants
- const MAX_LEVELS = globals.PYRAMID_MAX_LEVELS; //14; // supposing image size <= 8K = 2^13 (downto 1)
- const MAX_TEXTURES = 2 * MAX_LEVELS; //MAX_LEVELS;
-
- /**
- * Generate pyramid
- */
- class SpeedyPipelineNodeImagePyramid extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, MAX_TEXTURES + 1, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const outputTexture = this._tex[0];
- const pyramids = gpu.programs.pyramids;
- let width = image.width, height = image.height;
-
- // number of mipmap levels according to the OpenGL ES 3.0 spec (sec 3.8.10.4)
- const mipLevels = 1 + Math.floor(Math.log2(Math.max(width, height)));
-
- // get work textures
- const mip = new Array(MAX_TEXTURES + 1);
- for(let i = MAX_TEXTURES; i >= 1; i--)
- mip[i-1] = this._tex[i];
-
- // get a copy of the input image
- mip[0].resize(width, height);
- image.copyTo(mip[0]);
-
- // generate gaussian pyramid
- const numLevels = Math.min(mipLevels, MAX_LEVELS);
- for(let level = 1; level < numLevels; level++) {
- // use max(1, floor(size / 2^lod)), in accordance to
- // the OpenGL ES 3.0 spec sec 3.8.10.4 (Mipmapping)
- const halfWidth = Math.max(1, width >>> 1);
- const halfHeight = Math.max(1, height >>> 1);
-
- // reduce operation
- const tmp = (level - 1) + MAX_LEVELS;
- (pyramids.smoothX.outputs(width, height, mip[tmp]))(mip[level-1]);
- (pyramids.smoothY.outputs(width, height, mip[level-1]))(mip[tmp]);
- (pyramids.downsample2.outputs(halfWidth, halfHeight, mip[level]))(mip[level-1]);
- /*
- (pyramids.reduce.outputs(width, height, mip[tmp]))(mip[level-1]);
- (pyramids.downsample2.outputs(halfWidth, halfHeight, mip[level]))(mip[tmp]);
- */
-
- // flush
- gpu.gl.flush();
-
- // next level
- width = halfWidth;
- height = halfHeight;
-
- /*
- // debug: view pyramid
- const view = mip[level-1];
- const canvas = gpu.renderToCanvas(view);
- if(!window._ww) document.body.appendChild(canvas);
- window._ww = 1;
- */
- }
-
- // copy to output & set mipmap
- outputTexture.resize(image.width, image.height);
- outputTexture.clear();
- image.copyTo(outputTexture);
- outputTexture.generateMipmaps(mip.slice(0, numLevels));
-
- // done!
- this.output().swrite(outputTexture, format);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/images/mixer.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * mixer.js
- * Image Mixer
- */
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Image Mixer
- */
- class SpeedyPipelineNodeImageMixer extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort('in0').expects(SpeedyPipelineMessageType.Image),
- InputPort('in1').expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {number} alpha coefficient (applied to image0) */
- this._alpha = 0.5;
-
- /** @type {number} beta coefficient (applied to image1) */
- this._beta = 0.5;
-
- /** @type {number} gamma coefficient (brightness control) */
- this._gamma = 0.0;
- }
-
- /**
- * Alpha coefficient (applied to image0)
- * @returns {number}
- */
- get alpha()
- {
- return this._alpha;
- }
-
- /**
- * Alpha coefficient (applied to image0)
- * @param {number} value
- */
- set alpha(value)
- {
- this._alpha = +value;
- }
-
- /**
- * Beta coefficient (applied to image1)
- * @returns {number}
- */
- get beta()
- {
- return this._beta;
- }
-
- /**
- * Beta coefficient (applied to image1)
- * @param {number} value
- */
- set beta(value)
- {
- this._beta = +value;
- }
-
- /**
- * Gamma coefficient (brightness control)
- * @returns {number}
- */
- get gamma()
- {
- return this._gamma;
- }
-
- /**
- * Gamma coefficient (brightness control)
- * @param {number} value
- */
- set gamma(value)
- {
- this._gamma = +value;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const in0 = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input('in0').read() );
- const in1 = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input('in1').read() );
- const image0 = in0.image, image1 = in1.image;
- const format0 = in0.format, format1 = in1.format;
- const width = Math.max(image0.width, image1.width);
- const height = Math.max(image0.height, image1.height);
- const alpha = this._alpha, beta = this._beta, gamma = this._gamma;
- const outputTexture = this._tex[0];
-
- if(format0 != format1)
- throw new utils_errors/* NotSupportedError */.B8(`Can't mix images of different formats`);
-
- gpu.programs.transforms.additiveMix.outputs(width, height, outputTexture);
- gpu.programs.transforms.additiveMix(image0, image1, alpha, beta, gamma);
-
- this.output().swrite(outputTexture, format0);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/images/portal.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * portal.js
- * Image Portals
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * A sink of an Image Portal
- * This is not a pipeline sink - it doesn't export any data!
- */
- class SpeedyPipelineNodeImagePortalSink extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {ImageFormat} stored image format */
- this._format = types/* ImageFormat.RGBA */.D3.RGBA;
-
- /** @type {boolean} is this node initialized? */
- this._initialized = false;
- }
-
- /**
- * Stored image
- * @returns {SpeedyTexture}
- */
- get image()
- {
- if(!this._initialized)
- throw new utils_errors/* IllegalOperationError */.js(`Portal error: ${this.fullName} holds no data`);
-
- return this._tex[0];
- }
-
- /**
- * Stored image format
- * @returns {ImageFormat}
- */
- get format()
- {
- if(!this._initialized)
- throw new utils_errors/* IllegalOperationError */.js(`Portal error: ${this.fullName} holds no data`);
-
- return this._format;
- }
-
- /**
- * Initializes this node
- * @param {SpeedyGPU} gpu
- */
- init(gpu)
- {
- super.init(gpu);
-
- this._tex[0].resize(1, 1).clear(); // initial texture
- this._format = types/* ImageFormat.RGBA */.D3.RGBA;
-
- this._initialized = true;
- }
-
- /**
- * Releases this node
- * @param {SpeedyGPU} gpu
- */
- release(gpu)
- {
- this._initialized = false;
- super.release(gpu);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const tex = this._tex[0];
-
- // can't store pyramids
- if(image.hasMipmaps())
- throw new utils_errors/* NotSupportedError */.B8(`${this.fullName} can't store a pyramid`);
-
- // copy input
- this._format = format;
- tex.resize(image.width, image.height);
- image.copyTo(tex);
- }
- }
-
-
-
- /**
- * A source of an Image Portal
- */
- class SpeedyPipelineNodeImagePortalSource extends SpeedyPipelineSourceNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 0, [
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {SpeedyPipelineNodeImagePortalSink|null} portal sink */
- this._source = null;
- }
-
- /**
- * Data source
- * @returns {SpeedyPipelineNodeImagePortalSink|null}
- */
- get source()
- {
- return this._source;
- }
-
- /**
- * Data source
- * @param {SpeedyPipelineNodeImagePortalSink|null} node
- */
- set source(node)
- {
- if(node !== null && !(node instanceof SpeedyPipelineNodeImagePortalSink))
- throw new utils_errors/* IllegalArgumentError */.mG(`Incompatible source for ${this.fullName}`);
-
- this._source = node;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- if(this._source == null)
- throw new utils_errors/* IllegalOperationError */.js(`${this.fullName} has no source`);
-
- this.output().swrite(this._source.image, this._source.format);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/factories/image-factory.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * image-factory.js
- * Image-related nodes
- */
-
-
-
-
-
-
-
-
-
-
- /**
- * Portal nodes
- */
- class SpeedyPipelineImagePortalFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * Create an image portal source
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeImagePortalSource}
- */
- static Source(name = undefined)
- {
- return new SpeedyPipelineNodeImagePortalSource(name);
- }
-
- /**
- * Create an image portal sink
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeImagePortalSink}
- */
- static Sink(name = undefined)
- {
- return new SpeedyPipelineNodeImagePortalSink(name);
- }
- }
-
- /**
- * Image nodes
- */
- class SpeedyPipelineImageFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * Create an image source
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeImageSource}
- */
- static Source(name = undefined)
- {
- return new SpeedyPipelineNodeImageSource(name);
- }
-
- /**
- * Create an image sink
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeImageSink}
- */
- static Sink(name = undefined)
- {
- return new SpeedyPipelineNodeImageSink(name);
- }
-
- /**
- * Create an image multiplexer
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeImageMultiplexer}
- */
- static Multiplexer(name = undefined)
- {
- return new SpeedyPipelineNodeImageMultiplexer(name);
- }
-
- /**
- * Create an image buffer
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeImageBuffer}
- */
- static Buffer(name = undefined)
- {
- return new SpeedyPipelineNodeImageBuffer(name);
- }
-
- /**
- * Image Pyramid
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeImagePyramid}
- */
- static Pyramid(name = undefined)
- {
- return new SpeedyPipelineNodeImagePyramid(name);
- }
-
- /**
- * Image Mixer (blending)
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeImageMixer}
- */
- static Mixer(name = undefined)
- {
- return new SpeedyPipelineNodeImageMixer(name);
- }
-
- /**
- * Image Portals
- * @returns {typeof SpeedyPipelineImagePortalFactory}
- */
- static get Portal()
- {
- return SpeedyPipelineImagePortalFactory;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/filters/greyscale.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * greyscale.js
- * Convert an image to greyscale
- */
-
-
-
-
-
-
-
-
-
-
- /**
- * Convert an image to greyscale
- */
- class SpeedyPipelineNodeGreyscale extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const outputTexture = this._tex[0];
- const filters = gpu.programs.filters;
-
- filters.rgb2grey.outputs(width, height, outputTexture);
- filters.rgb2grey(image);
-
- this.output().swrite(outputTexture, types/* ImageFormat.GREY */.D3.GREY);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/filters/gaussian-blur.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * gaussian-blur.js
- * Gaussian Blur
- */
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Default kernels for different sizes: 3x3, 5x5, 7x7... (use sigma_x = sigma_y)
- * Heuristics: in order to pick a sigma, we set radius = 2 * sigma. Since
- * ksize = 1 + 2 * radius, it follows that sigma = (ksize - 1) / 4. When
- * ksize is 3, we set sigma = 1. Therefore, sigma = max(1, (ksize - 1) / 4).
- */
- const DEFAULT_KERNEL = Object.freeze({
- 3: [ 0.27901008925473514, 0.44197982149052983, 0.27901008925473514 ], // 1D convolution (sigma = 1)
- 5: [ 0.06135959781344021, 0.2447701955296099, 0.3877404133138998, 0.2447701955296099, 0.06135959781344021 ], // 1D convolution (separable kernel)
- 7: [ 0.03873542500847274, 0.11308485700794121, 0.2150068609928349, 0.26634571398150225, 0.2150068609928349, 0.11308485700794121, 0.03873542500847274 ],
- 9: [ 0.028532262603370988, 0.067234535494912, 0.12400932997922749, 0.17904386461741617, 0.20236001461014655, 0.17904386461741617, 0.12400932997922749, 0.067234535494912, 0.028532262603370988 ],
- 11:[ 0.022656882730580346, 0.04610857898527292, 0.08012661469398517, 0.11890414969751599, 0.15067709325491124, 0.16305336127546846, 0.15067709325491124, 0.11890414969751599, 0.08012661469398517, 0.04610857898527292, 0.022656882730580346 ],
- 13:[ 0.018815730430644363, 0.03447396964662016, 0.05657737457255748, 0.08317258170844948, 0.10952340502389682, 0.12918787500405662, 0.13649812722755, 0.12918787500405662, 0.10952340502389682, 0.08317258170844948, 0.05657737457255748, 0.03447396964662016, 0.018815730430644363 ],
- 15:[ 0.016100340991695383, 0.027272329212157102, 0.042598338587449644, 0.06135478775568558, 0.08148767614129326, 0.09979838342934616, 0.11270444144735056, 0.11736740487004466, 0.11270444144735056, 0.09979838342934616, 0.08148767614129326, 0.06135478775568558, 0.042598338587449644, 0.027272329212157102, 0.016100340991695383 ],
- //3: [ 0.25, 0.5, 0.25 ],
- //5: [ 0.05, 0.25, 0.4, 0.25, 0.05 ],
- });
-
- /** Zero vector. When we set sigma_x = sigma_y = 0, we use the default rule to compute the actual sigma */
- const DEFAULT_SIGMA = new SpeedyVector2(0,0);
-
- /** convolution programs (x-axis) */
- const CONVOLUTION_X = Object.freeze({
- 3: 'convolution3x',
- 5: 'convolution5x',
- 7: 'convolution7x',
- 9: 'convolution9x',
- 11: 'convolution11x',
- 13: 'convolution13x',
- 15: 'convolution15x',
- });
-
- /** convolution programs (y-axis) */
- const CONVOLUTION_Y = Object.freeze({
- 3: 'convolution3y',
- 5: 'convolution5y',
- 7: 'convolution7y',
- 9: 'convolution9y',
- 11: 'convolution11y',
- 13: 'convolution13y',
- 15: 'convolution15y',
- });
-
- /**
- * @typedef {object} SeparableConvolutionKernel
- * @property {number[]} x
- * @property {number[]} y
- */
-
- /**
- * Gaussian Blur
- */
- class SpeedyPipelineNodeGaussianBlur extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 2, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {SpeedySize} size of the kernel */
- this._kernelSize = new SpeedySize(5,5);
-
- /** @type {SpeedyVector2} sigma of the Gaussian kernel (0 means: use default settings) */
- this._sigma = DEFAULT_SIGMA;
-
- /** @type {SeparableConvolutionKernel} convolution kernel */
- this._kernel = {
- x: DEFAULT_KERNEL[this._kernelSize.width],
- y: DEFAULT_KERNEL[this._kernelSize.height]
- };
- }
-
- /**
- * Size of the kernel
- * @returns {SpeedySize}
- */
- get kernelSize()
- {
- return this._kernelSize;
- }
-
- /**
- * Size of the kernel
- * @param {SpeedySize} kernelSize
- */
- set kernelSize(kernelSize)
- {
- utils/* Utils.assert */.c.assert(kernelSize instanceof SpeedySize);
-
- const kw = kernelSize.width, kh = kernelSize.height;
- if(kw < 3 || kh < 3 || kw > 15 || kh > 15 || kw % 2 == 0 || kh % 2 == 0)
- throw new utils_errors/* NotSupportedError */.B8(`Unsupported kernel size: ${kw}x${kh}`);
-
- this._kernelSize = kernelSize;
- this._updateKernel();
- }
-
- /**
- * Sigma of the Gaussian kernel
- * @returns {SpeedyVector2}
- */
- get sigma()
- {
- return this._sigma;
- }
-
- /**
- * Sigma of the Gaussian kernel
- * @param {SpeedyVector2} sigma
- */
- set sigma(sigma)
- {
- utils/* Utils.assert */.c.assert(sigma instanceof SpeedyVector2, `Sigma must be a SpeedyVector2`);
- utils/* Utils.assert */.c.assert(sigma.x >= 0 && sigma.y >= 0);
-
- this._sigma = sigma;
- this._updateKernel();
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const kernX = this._kernel.x;
- const kernY = this._kernel.y;
- const convX = CONVOLUTION_X[this._kernelSize.width];
- const convY = CONVOLUTION_Y[this._kernelSize.height];
- const tex = this._tex[0];
- const outputTexture = this._tex[1];
-
- (gpu.programs.filters[convX]
- .outputs(width, height, tex)
- )(image, kernX);
-
- (gpu.programs.filters[convY]
- .outputs(width, height, outputTexture)
- )(tex, kernY);
-
- this.output().swrite(outputTexture, format);
- }
-
- /**
- * Update the internal kernel to match
- * sigma and kernelSize
- */
- _updateKernel()
- {
- if(this._sigma.x == DEFAULT_SIGMA.x)
- this._kernel.x = DEFAULT_KERNEL[this._kernelSize.width];
- else
- this._kernel.x = utils/* Utils.gaussianKernel */.c.gaussianKernel(this._sigma.x, this._kernelSize.width, true);
-
- if(this._sigma.y == DEFAULT_SIGMA.y)
- this._kernel.y = DEFAULT_KERNEL[this._kernelSize.height];
- else
- this._kernel.y = utils/* Utils.gaussianKernel */.c.gaussianKernel(this._sigma.y, this._kernelSize.height, true);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/filters/simple-blur.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * simple-blur.js
- * Simple Blur (Box Filter)
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /** 1D convolution filters */
- const BOX_FILTER = Object.freeze({
- 3: (new Array(3)).fill(1/3),
- 5: (new Array(5)).fill(1/5),
- 7: (new Array(7)).fill(1/7),
- 9: (new Array(9)).fill(1/9),
- 11: (new Array(11)).fill(1/11),
- 13: (new Array(13)).fill(1/13),
- 15: (new Array(15)).fill(1/15),
- });
-
- /** convolution programs (x-axis) */
- const simple_blur_CONVOLUTION_X = Object.freeze({
- 3: 'convolution3x',
- 5: 'convolution5x',
- 7: 'convolution7x',
- 9: 'convolution9x',
- 11: 'convolution11x',
- 13: 'convolution13x',
- 15: 'convolution15x',
- });
-
- /** convolution programs (y-axis) */
- const simple_blur_CONVOLUTION_Y = Object.freeze({
- 3: 'convolution3y',
- 5: 'convolution5y',
- 7: 'convolution7y',
- 9: 'convolution9y',
- 11: 'convolution11y',
- 13: 'convolution13y',
- 15: 'convolution15y',
- });
-
- /**
- * @typedef {object} SeparableConvolutionKernel
- * @property {number[]} x
- * @property {number[]} y
- */
-
- /**
- * Simple Blur (Box Filter)
- */
- class SpeedyPipelineNodeSimpleBlur extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 2, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {SpeedySize} size of the kernel */
- this._kernelSize = new SpeedySize(5,5);
-
- /** @type {SeparableConvolutionKernel} convolution kernel */
- this._kernel = {
- x: BOX_FILTER[this._kernelSize.width],
- y: BOX_FILTER[this._kernelSize.height]
- };
- }
-
- /**
- * Size of the kernel
- * @returns {SpeedySize}
- */
- get kernelSize()
- {
- return this._kernelSize;
- }
-
- /**
- * Size of the kernel
- * @param {SpeedySize} kernelSize
- */
- set kernelSize(kernelSize)
- {
- utils/* Utils.assert */.c.assert(kernelSize instanceof SpeedySize);
-
- const kw = kernelSize.width, kh = kernelSize.height;
- if(kw < 3 || kh < 3 || kw > 15 || kh > 15 || kw % 2 == 0 || kh % 2 == 0)
- throw new utils_errors/* NotSupportedError */.B8(`Unsupported kernel size: ${kw}x${kh}`);
-
- this._kernelSize = kernelSize;
- this._kernel.x = BOX_FILTER[this._kernelSize.width];
- this._kernel.y = BOX_FILTER[this._kernelSize.height];
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const kernX = this._kernel.x;
- const kernY = this._kernel.y;
- const convX = simple_blur_CONVOLUTION_X[this._kernelSize.width];
- const convY = simple_blur_CONVOLUTION_Y[this._kernelSize.height];
- const tex = this._tex[0];
- const outputTexture = this._tex[1];
-
- (gpu.programs.filters[convX]
- .outputs(width, height, tex)
- )(image, kernX);
-
- (gpu.programs.filters[convY]
- .outputs(width, height, outputTexture)
- )(tex, kernY);
-
- this.output().swrite(outputTexture, format);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/filters/median-blur.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * median-blur.js
- * Median Blur
- */
-
-
-
-
-
-
-
-
-
-
-
-
- // Median programs
- const MEDIAN = {
- 3: 'median3',
- 5: 'median5',
- 7: 'median7',
- };
-
- /**
- * Median Blur
- */
- class SpeedyPipelineNodeMedianBlur extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort().expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {SpeedySize} size of the kernel (assumed to be square) */
- this._kernelSize = new SpeedySize(5,5);
- }
-
- /**
- * Size of the kernel
- * @returns {SpeedySize}
- */
- get kernelSize()
- {
- return this._kernelSize;
- }
-
- /**
- * Size of the kernel
- * @param {SpeedySize} kernelSize
- */
- set kernelSize(kernelSize)
- {
- utils/* Utils.assert */.c.assert(kernelSize instanceof SpeedySize);
-
- const ksize = kernelSize.width;
- if(!(ksize == 3 || ksize == 5 || ksize == 7))
- throw new utils_errors/* NotSupportedError */.B8(`Supported kernel sizes: 3x3, 5x5, 7x7`);
- else if(kernelSize.width != kernelSize.height)
- throw new utils_errors/* NotSupportedError */.B8(`Use a square kernel`);
-
- this._kernelSize = kernelSize;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const ksize = this._kernelSize.width;
- const med = MEDIAN[ksize];
- const outputTexture = this._tex[0];
-
- (gpu.programs.filters[med]
- .outputs(width, height, outputTexture)
- )(image);
-
- this.output().swrite(outputTexture, format);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/filters/convolution.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * convolution.js
- * Image convolution
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
- // 2D convolution programs
- const CONVOLUTION = {
- 3: 'convolution3',
- 5: 'convolution5',
- 7: 'convolution7',
- };
-
- /**
- * Image convolution
- */
- class SpeedyPipelineNodeConvolution extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {SpeedyMatrix} convolution kernel (square matrix) */
- this._kernel = speedy_matrix.SpeedyMatrix.Create(3, 3, [0, 0, 0, 0, 1, 0, 0, 0, 0]); // identity transform
- }
-
- /**
- * Convolution kernel
- * @returns {SpeedyMatrix}
- */
- get kernel()
- {
- return this._kernel;
- }
-
- /**
- * Convolution kernel
- * @param {SpeedyMatrix} kernel
- */
- set kernel(kernel)
- {
- if(kernel.rows != kernel.columns)
- throw new utils_errors/* NotSupportedError */.B8(`Use a square kernel`);
- else if(!(kernel.rows == 3 || kernel.rows == 5 || kernel.rows == 7))
- throw new utils_errors/* NotSupportedError */.B8(`Invalid kernel size. Supported sizes: 3x3, 5x5, 7x7`);
-
- this._kernel = kernel;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const outputTexture = this._tex[0];
- const ksize = this._kernel.rows;
- const conv = CONVOLUTION[ksize];
- const kernel = this._kernel.read();
-
- (gpu.programs.filters[conv]
- .outputs(width, height, outputTexture)
- )(image, kernel);
-
- this.output().swrite(outputTexture, format);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/filters/nightvision.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * nightvision.js
- * Nightvision filter
- */
-
-
-
-
-
-
-
-
-
-
-
- /**
- * @typedef {"high"|"medium"|"low"} NightvisionQualityLevel
- */
-
- /**
- * Nightvision filter: "see in the dark"
- */
- class SpeedyPipelineNodeNightvision extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 3, [
- InputPort().expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.RGBA */.D3.RGBA ||
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {number} a value typically in [0,1]: larger number => higher contrast */
- this._gain = 0.5;
-
- /** @type {number} a value typically in [0,1]: controls brightness */
- this._offset = 0.5;
-
- /** @type {number} gain decay, a value in [0,1] */
- this._decay = 0.0;
-
- /** @type {NightvisionQualityLevel} quality level */
- this._quality = 'medium';
- }
-
- /**
- * Gain, a value typically in [0,1]: larger number => higher contrast
- * @returns {number}
- */
- get gain()
- {
- return this._gain;
- }
-
- /**
- * Gain, a value typically in [0,1]: larger number => higher contrast
- * @param {number} gain
- */
- set gain(gain)
- {
- this._gain = +gain;
- }
-
- /**
- * Offset, a value typically in [0,1] that controls the brightness
- * @returns {number}
- */
- get offset()
- {
- return this._offset;
- }
-
- /**
- * Offset, a value typically in [0,1] that controls the brightness
- * @param {number} offset
- */
- set offset(offset)
- {
- this._offset = +offset;
- }
-
- /**
- * Gain decay, a value in [0,1] that controls how the gain decays from the center of the image
- * @returns {number}
- */
- get decay()
- {
- return this._decay;
- }
-
- /**
- * Gain decay, a value in [0,1] that controls how the gain decays from the center of the image
- * @param {number} decay
- */
- set decay(decay)
- {
- this._decay = Math.max(0.0, Math.min(+decay, 1.0));
- }
-
- /**
- * Quality level of the filter
- * @returns {NightvisionQualityLevel}
- */
- get quality()
- {
- return this._quality;
- }
-
- /**
- * Quality level of the filter
- * @param {NightvisionQualityLevel} quality
- */
- set quality(quality)
- {
- if(quality === 'high' || quality === 'medium' || quality === 'low')
- this._quality = quality;
- else
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid quality level for the Nightvision filter: "${quality}"`);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const gain = this._gain;
- const offset = this._offset;
- const decay = this._decay;
- const quality = this._quality;
- const filters = gpu.programs.filters;
- const tmp = this._tex[0];
- const illuminationMap = this._tex[1];
- const outputTexture = this._tex[2];
-
- // compute illumination map
- if(quality == 'medium') {
- filters.illuminationMapX.outputs(width, height, tmp);
- filters.illuminationMapY.outputs(width, height, illuminationMap);
- filters.illuminationMapX(image);
- filters.illuminationMapY(tmp);
- }
- else if(quality == 'high') {
- filters.illuminationMapHiX.outputs(width, height, tmp);
- filters.illuminationMapHiY.outputs(width, height, illuminationMap);
- filters.illuminationMapHiX(image);
- filters.illuminationMapHiY(tmp);
- }
- else if(quality == 'low') {
- filters.illuminationMapLoX.outputs(width, height, tmp);
- filters.illuminationMapLoY.outputs(width, height, illuminationMap);
- filters.illuminationMapLoX(image);
- filters.illuminationMapLoY(tmp);
- }
-
- // run nightvision
- if(format === types/* ImageFormat.GREY */.D3.GREY) {
- filters.nightvisionGreyscale.outputs(width, height, outputTexture);
- filters.nightvisionGreyscale(image, illuminationMap, gain, offset, decay);
- }
- else if(format === types/* ImageFormat.RGBA */.D3.RGBA) {
- filters.nightvision.outputs(width, height, outputTexture);
- filters.nightvision(image, illuminationMap, gain, offset, decay);
- }
-
- // done!
- this.output().swrite(outputTexture, format);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/filters/normalize.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * normalize.js
- * Normalize image to a range
- */
-
-
-
-
-
-
-
-
-
-
- /**
- * Normalize image to a range
- */
- class SpeedyPipelineNodeNormalize extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 4, [
- InputPort().expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {number} a value in [0,255] */
- this._minValue = 0;
-
- /** @type {number} a value in [0,255] */
- this._maxValue = 255;
- }
-
- /**
- * Minimum intensity in the output image, a value in [0,255]
- * @returns {number}
- */
- get minValue()
- {
- return this._minValue;
- }
-
- /**
- * Minimum intensity in the output image, a value in [0,255]
- * @param {number} minValue
- */
- set minValue(minValue)
- {
- this._minValue = Math.max(0, Math.min(+minValue, 255));
- }
-
- /**
- * Maximum intensity in the output image, a value in [0,255]
- * @returns {number}
- */
- get maxValue()
- {
- return this._maxValue;
- }
-
- /**
- * Maximum intensity in the output image, a value in [0,255]
- * @param {number} maxValue
- */
- set maxValue(maxValue)
- {
- this._maxValue = Math.max(0, Math.min(+maxValue, 255));
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const outputTexture = this._tex[3];
- let minValue = this._minValue;
- let maxValue = this._maxValue;
-
- if(minValue > maxValue)
- minValue = maxValue = (minValue + maxValue) / 2;
-
- const minmax = this._scanMinMax(gpu, image, types/* PixelComponent.GREEN */.hE.GREEN);
- gpu.programs.filters.normalizeGreyscale.outputs(width, height, outputTexture);
- gpu.programs.filters.normalizeGreyscale(minmax, minValue, maxValue);
-
- this.output().swrite(outputTexture, format);
- }
-
- /**
- * Scan a single component in all pixels of the image and find the min & max intensities
- * @param {SpeedyGPU} gpu
- * @param {SpeedyTexture} image input image
- * @param {PixelComponent} pixelComponent a single PixelComponent flag
- * @returns {SpeedyDrawableTexture} RGBA = (max, min, max - min, original_pixel)
- */
- _scanMinMax(gpu, image, pixelComponent)
- {
- const tex = this._tex;
- const program = gpu.programs.utils;
- const width = image.width, height = image.height;
- const numIterations = Math.ceil(Math.log2(Math.max(width, height))) | 0;
-
- utils/* Utils.assert */.c.assert(types/* ColorComponentId */.rY[pixelComponent] !== undefined);
-
- program.copyComponents.outputs(width, height, tex[2]);
- program.scanMinMax2D.outputs(width, height, tex[0], tex[1]);
-
- let texture = program.copyComponents(image, image, types/* PixelComponent.ALL */.hE.ALL, types/* ColorComponentId */.rY[pixelComponent]);
- for(let i = 0; i < numIterations; i++)
- texture = program.scanMinMax2D(texture, i);
-
- return texture;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/factories/filter-factory.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * filter-factory.js
- * Image filters
- */
-
-
-
-
-
-
-
-
-
-
- /**
- * Image filters
- */
- class SpeedyPipelineFilterFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * Convert image to greyscale
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeGreyscale}
- */
- static Greyscale(name = undefined)
- {
- return new SpeedyPipelineNodeGreyscale(name);
- }
-
- /**
- * Gaussian Blur
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeGaussianBlur}
- */
- static GaussianBlur(name = undefined)
- {
- return new SpeedyPipelineNodeGaussianBlur(name);
- }
-
- /**
- * Simple Blur (Box Filter)
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeSimpleBlur}
- */
- static SimpleBlur(name = undefined)
- {
- return new SpeedyPipelineNodeSimpleBlur(name);
- }
-
- /**
- * Median Blur
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeMedianBlur}
- */
- static MedianBlur(name = undefined)
- {
- return new SpeedyPipelineNodeMedianBlur(name);
- }
-
- /**
- * Image Convolution
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeConvolution}
- */
- static Convolution(name = undefined)
- {
- return new SpeedyPipelineNodeConvolution(name);
- }
-
- /**
- * Nightvision
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeNightvision}
- */
- static Nightvision(name = undefined)
- {
- return new SpeedyPipelineNodeNightvision(name);
- }
-
- /**
- * Normalize image
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeNormalize}
- */
- static Normalize(name = undefined)
- {
- return new SpeedyPipelineNodeNormalize(name);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/transforms/perspective-warp.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * perspective-warp.js
- * Warp an image using a perspective transformation
- */
-
-
-
-
-
-
-
-
-
-
-
-
- // Used when an invalid matrix is provided
- const SINGULAR_MATRIX = [0,0,0,0,0,0,0,0,1];
-
- /**
- * Warp an image using a perspective transformation
- */
- class SpeedyPipelineNodePerspectiveWarp extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {SpeedyMatrix} perspective transformation */
- this._transform = speedy_matrix.SpeedyMatrix.Create(3, 3, [1, 0, 0, 0, 1, 0, 0, 0, 1]); // identity matrix
- }
-
- /**
- * Perspective transform, a 3x3 homography matrix
- * @returns {SpeedyMatrix}
- */
- get transform()
- {
- return this._transform;
- }
-
- /**
- * Perspective transform, a 3x3 homography matrix
- * @param {SpeedyMatrix} transform
- */
- set transform(transform)
- {
- if(!(transform.rows == 3 && transform.columns == 3))
- throw new utils_errors/* IllegalArgumentError */.mG(`Not a 3x3 transformation matrix: ${transform}`);
-
- this._transform = transform;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const outputTexture = this._tex[0];
- const homography = this._transform.read();
- const inverseHomography = this._inverse3(homography);
- const isValidHomography = !Number.isNaN(inverseHomography[0]);
-
- gpu.programs.transforms.warpPerspective.outputs(width, height, outputTexture);
- gpu.programs.transforms.warpPerspective(image, isValidHomography ? inverseHomography : SINGULAR_MATRIX);
-
- this.output().swrite(outputTexture, format);
- }
-
- /**
- * Compute the inverse of a 3x3 matrix IN-PLACE (do it fast!)
- * @param {number[]} mat 3x3 matrix in column-major format
- * @param {number} [eps] epsilon
- * @returns {number[]} 3x3 inverse matrix in column-major format
- */
- _inverse3(mat, eps = 1e-6)
- {
- // read the entries of the matrix
- const a11 = mat[0];
- const a21 = mat[1];
- const a31 = mat[2];
- const a12 = mat[3];
- const a22 = mat[4];
- const a32 = mat[5];
- const a13 = mat[6];
- const a23 = mat[7];
- const a33 = mat[8];
-
- // compute cofactors
- const b1 = a33 * a22 - a32 * a23; // b11
- const b2 = a33 * a12 - a32 * a13; // b21
- const b3 = a23 * a12 - a22 * a13; // b31
-
- // compute the determinant
- const det = a11 * b1 - a21 * b2 + a31 * b3;
-
- // set up the inverse
- if(!(Math.abs(det) < eps)) {
- const d = 1.0 / det;
- mat[0] = b1 * d;
- mat[1] = -(a33 * a21 - a31 * a23) * d;
- mat[2] = (a32 * a21 - a31 * a22) * d;
- mat[3] = -b2 * d;
- mat[4] = (a33 * a11 - a31 * a13) * d;
- mat[5] = -(a32 * a11 - a31 * a12) * d;
- mat[6] = b3 * d;
- mat[7] = -(a23 * a11 - a21 * a13) * d;
- mat[8] = (a22 * a11 - a21 * a12) * d;
- }
- else
- mat.fill(Number.NaN, 0, 9);
-
- // done!
- return mat;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/transforms/resize.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * resize.js
- * Resize image
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
- /** @typedef {"bilinear"|"nearest"} SpeedyPipelineNodeResizeMethod */
-
- /**
- * Resize image
- */
- class SpeedyPipelineNodeResize extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort().expects(SpeedyPipelineMessageType.Image),
- OutputPort().expects(SpeedyPipelineMessageType.Image),
- ]);
-
- /** @type {SpeedySize} size of the output image, in pixels */
- this._size = new SpeedySize(0, 0);
-
- /** @type {SpeedyVector2} size of the output relative to the size of the input */
- this._scale = new SpeedyVector2(1, 1);
-
- /** @type {SpeedyPipelineNodeResizeMethod} interpolation method */
- this._method = 'bilinear';
- }
-
- /**
- * Size of the output image, in pixels (use 0 to use scale)
- * @returns {SpeedySize}
- */
- get size()
- {
- return this._size;
- }
-
- /**
- * Size of the output image, in pixels (use 0 to use scale)
- * @param {SpeedySize} size
- */
- set size(size)
- {
- this._size = size;
- }
-
- /**
- * Size of the output image relative to the size of the input image
- * @returns {SpeedyVector2}
- */
- get scale()
- {
- return this._scale;
- }
-
- /**
- * Size of the output image relative to the size of the input image
- * @param {SpeedyVector2} scale
- */
- set scale(scale)
- {
- this._scale = scale;
- }
-
- /**
- * Interpolation method
- * @returns {SpeedyPipelineNodeResizeMethod}
- */
- get method()
- {
- return this._method;
- }
-
- /**
- * Interpolation method
- * @param {SpeedyPipelineNodeResizeMethod} method
- */
- set method(method)
- {
- if(method !== 'nearest' && method !== 'bilinear')
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid method method: "${method}"`);
-
- this._method = method;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const outputTexture = this._tex[0];
- const method = this._method;
- const newWidth = this._size.width || Math.max(1, this._scale.x * width);
- const newHeight = this._size.height || Math.max(1, this._scale.y * height);
-
- if(method == 'bilinear') {
- (gpu.programs.transforms.resizeBilinear
- .outputs(newWidth, newHeight, outputTexture)
- )(image);
- }
- else if(method == 'nearest') {
- (gpu.programs.transforms.resizeNearest
- .outputs(newWidth, newHeight, outputTexture)
- )(image);
- }
-
- this.output().swrite(outputTexture, format);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/factories/transform-factory.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * transform-factory.js
- * Image transforms
- */
-
-
-
-
-
- /**
- * Image transforms
- */
- class SpeedyPipelineTransformFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * Resize image
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeResize}
- */
- static Resize(name = undefined)
- {
- return new SpeedyPipelineNodeResize(name);
- }
-
- /**
- * Warp an image using a perspective transformation
- * @param {string} [name]
- * @returns {SpeedyPipelineNodePerspectiveWarp}
- */
- static PerspectiveWarp(name = undefined)
- {
- return new SpeedyPipelineNodePerspectiveWarp(name);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/detectors/detector.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * detector.js
- * Abstract keypoint detectors
- */
-
-
-
-
-
-
-
-
-
-
- // Constants
- const MAX_CAPACITY = globals.MAX_ENCODER_CAPACITY; // maximum capacity of the encoder (up to this many keypoints can be stored)
- const detector_DEFAULT_CAPACITY = globals.DEFAULT_ENCODER_CAPACITY; // default capacity of the encoder
- const DEFAULT_SCALE_FACTOR = 1.4142135623730951; // sqrt(2)
- const NUMBER_OF_RGBA16_TEXTURES = 2;
-
- // legacy constants
- const NUMBER_OF_INTERNAL_TEXTURES = 0; //5; // number of internal textures used to encode the keypoints
- const ENCODER_PASSES = 4; // number of passes of the keypoint encoder: directly impacts performance
- const LONG_SKIP_OFFSET_PASSES = 2; // number of passes of the long skip offsets shader
-
- /**
- * Abstract keypoint detector
- * @abstract
- */
- class SpeedyPipelineNodeKeypointDetector extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- * @param {number} [texCount] number of work textures
- * @param {SpeedyPipelinePortBuilder[]} [portBuilders] port builders
- */
- constructor(name = undefined, texCount = 0, portBuilders = undefined)
- {
- super(name, texCount + NUMBER_OF_INTERNAL_TEXTURES, portBuilders);
-
- /** @type {number} encoder capacity */
- this._capacity = detector_DEFAULT_CAPACITY; // must not be greater than MAX_ENCODER_CAPACITY
-
- /** @type {GLint} auxiliary storage */
- this._oldWrapS = 0;
-
- /** @type {SpeedyDrawableTexture[]} textures with 8-bytes per pixel */
- this._tex16 = new Array(NUMBER_OF_RGBA16_TEXTURES).fill(null);
- }
-
- /**
- * Initialize this node
- * @param {SpeedyGPU} gpu
- */
- init(gpu)
- {
- // initialize
- super.init(gpu);
-
- // encodeKeypointSkipOffsets() relies on this
- this._oldWrapS = this._setupSpecialTexture(gpu.gl.TEXTURE_WRAP_S, gpu.gl.REPEAT);
-
- // allocate RGBA16 textures
- this._allocateTex16(gpu);
- gpu.subscribe(this._allocateTex16, this, gpu);
- }
-
- /**
- * Release this node
- * @param {SpeedyGPU} gpu
- */
- release(gpu)
- {
- // deallocate RGBA16 textures
- gpu.unsubscribe(this._allocateTex16, this);
- this._deallocateTex16(gpu);
-
- // we need to restore the texture parameter because textures come from a pool!
- this._setupSpecialTexture(gpu.gl.TEXTURE_WRAP_S, this._oldWrapS);
-
- // release
- super.release(gpu);
- }
-
- /**
- * Set a parameter of the special texture
- * @param {GLenum} pname
- * @param {GLint} param new value
- * @returns {GLint} old value of param
- */
- _setupSpecialTexture(pname, param)
- {
- if(NUMBER_OF_INTERNAL_TEXTURES == 0)
- return;
-
- // legacy code
- const texture = this._tex[this._tex.length - 1];
- const gl = texture.gl;
-
- gl.bindTexture(gl.TEXTURE_2D, texture.glTexture);
- const oldval = gl.getTexParameter(gl.TEXTURE_2D, pname);
- gl.texParameteri(gl.TEXTURE_2D, pname, param);
- gl.bindTexture(gl.TEXTURE_2D, null);
-
- return oldval;
- }
-
- /**
- * We can encode up to this many keypoints. If you find a
- * tight bound for this, download times will be faster.
- * @returns {number}
- */
- get capacity()
- {
- return this._capacity;
- }
-
- /**
- * We can encode up to this many keypoints. If you find a
- * tight bound for this, download times will be faster.
- * @param {number} capacity
- */
- set capacity(capacity)
- {
- this._capacity = Math.min(Math.max(0, capacity | 0), MAX_CAPACITY);
- }
-
- /**
- * Create a tiny texture with encoded keypoints out of
- * an encoded corners texture
- * @param {SpeedyGPU} gpu
- * @param {SpeedyTexture} corners input
- * @param {SpeedyDrawableTexture} encodedKeypoints output
- * @param {number} [descriptorSize] in bytes
- * @param {number} [extraSize] in bytes
- * @returns {SpeedyDrawableTexture} encodedKeypoints
- */
- _encodeKeypoints(gpu, corners, encodedKeypoints, descriptorSize = 0, extraSize = 0)
- {
- const encoderCapacity = this._capacity;
- const encoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(encoderCapacity, descriptorSize, extraSize);
- const width = 1 << (Math.ceil(Math.log2(corners.width * corners.height)) >>> 1); // power of two
- const height = Math.ceil(corners.width * corners.height / width); // probabilistic approach in Parallel Ale Sort 2D
- //const width = corners.width, height = corners.height; // independent texture reads approach in Parallel Ale Sort 2D
- const maxSize = Math.max(width, height);
- const keypoints = gpu.programs.keypoints;
-
- // prepare programs
- keypoints.initLookupTable.outputs(width, height, this._tex16[1]);
- keypoints.sortLookupTable.outputs(width, height, this._tex16[0], this._tex16[1]);
- keypoints.encodeKeypoints.outputs(encoderLength, encoderLength, encodedKeypoints);
-
- // compute lookup table
- let lookupTable = keypoints.initLookupTable(corners);
- for(let b = 1; b < maxSize; b *= 2)
- lookupTable = keypoints.sortLookupTable(lookupTable, b, width, height);
-
- /*
- // debug: view texture
- const lookupView = (keypoints.viewLookupTable.outputs(
- width, height, this._tex[0]
- ))(lookupTable);
- const canvas = gpu.renderToCanvas(lookupView);
- if(!this._ww) document.body.appendChild(canvas);
- this._ww = 1;
- */
-
- // encode keypoints
- return keypoints.encodeKeypoints(corners, lookupTable, width, descriptorSize, extraSize, encoderLength, encoderCapacity);
- }
-
- _encodeKeypointsOLD(gpu, corners, encodedKeypoints, descriptorSize = 0, extraSize = 0)
- {
- const capacity = this._capacity;
- const encoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(capacity, descriptorSize, extraSize);
- const width = corners.width, height = corners.height;
- const imageSize = [ width, height ];
- const tex = this._tex.slice(this._tex.length - NUMBER_OF_INTERNAL_TEXTURES); // array of internal textures
- const keypoints = gpu.programs.keypoints;
- const specialTexture = tex.pop(); // gl.TEXTURE_WRAP_S is set to gl.REPEAT
-
- // prepare programs
- keypoints.encodeKeypointSkipOffsets.outputs(width, height, tex[0]);
- keypoints.encodeKeypointLongSkipOffsets.outputs(width, height, tex[1], tex[0]);
- keypoints.encodeKeypointPositions.outputs(encoderLength, encoderLength, tex[2], tex[3]);
- keypoints.encodeKeypointProperties.outputs(encoderLength, encoderLength, encodedKeypoints);
-
- // copy the input corners to a special texture
- // that is needed by encodeKeypointSkipOffsets()
- corners = (gpu.programs.utils.copy
- .outputs(width, height, specialTexture)
- )(corners);
-
- // encode skip offsets
- let offsets = keypoints.encodeKeypointSkipOffsets(corners, imageSize);
- for(let i = 0; i < LONG_SKIP_OFFSET_PASSES; i++) { // to boost performance
- // the maximum skip offset of pass p=1,2,3... is 7 * (1+m)^p,
- // where m = MAX_ITERATIONS of encodeKeypointLongSkipOffsets()
- offsets = keypoints.encodeKeypointLongSkipOffsets(offsets, imageSize); // **bottleneck**
- }
-
- /*
- // debug: view corners
- let cornerview = offsets;
- const canvas = gpu.renderToCanvas(cornerview);
- if(!window._ww) document.body.appendChild(canvas);
- window._ww = 1;
- */
-
- // encode keypoint positions
- let encodedKps = tex[3].clear();
- for(let j = 0; j < ENCODER_PASSES; j++)
- encodedKps = keypoints.encodeKeypointPositions(offsets, imageSize, j, ENCODER_PASSES, capacity, encodedKps, descriptorSize, extraSize, encoderLength);
-
- // encode keypoint properties
- return keypoints.encodeKeypointProperties(corners, encodedKps, descriptorSize, extraSize, encoderLength);
- }
-
- /**
- * Create a tiny texture with zero encoded keypoints
- * @param {SpeedyGPU} gpu
- * @param {SpeedyDrawableTexture} encodedKeypoints output texture
- * @param {number} [descriptorSize] in bytes
- * @param {number} [extraSize] in bytes
- * @returns {SpeedyDrawableTexture} encodedKeypoints
- */
- _encodeZeroKeypoints(gpu, encodedKeypoints, descriptorSize = 0, extraSize = 0)
- {
- const capacity = 0;
- const encoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(capacity, descriptorSize, extraSize);
- const keypoints = gpu.programs.keypoints;
-
- keypoints.encodeNullKeypoints.outputs(encoderLength, encoderLength, encodedKeypoints);
- return keypoints.encodeNullKeypoints();
- }
-
- /**
- * Allocate RGBA16 textures
- * @param {SpeedyGPU} gpu
- */
- _allocateTex16(gpu)
- {
- const gl = gpu.gl;
-
- // RGBA16UI is color renderable according to the OpenGL ES 3 spec
- for(let i = 0; i < this._tex16.length; i++)
- this._tex16[i] = new SpeedyDrawableTexture(gl, 1, 1, gl.RGBA_INTEGER, gl.RGBA16UI, gl.UNSIGNED_SHORT, gl.NEAREST, gl.CLAMP_TO_EDGE);
- }
-
- /**
- * Deallocate RGBA16 textures
- * @param {SpeedyGPU} gpu
- */
- _deallocateTex16(gpu)
- {
- for(let i = 0; i < this._tex16.length; i++)
- this._tex16[i] = this._tex16[i].release();
- }
-
- /**
- * Compute the length of the keypoint encoder, given its capacity
- * @param {number} encoderCapacity how many keypoints can we fit?
- * @param {number} descriptorSize in bytes
- * @param {number} extraSize in bytes
- */
- static encoderLength(encoderCapacity, descriptorSize, extraSize)
- {
- const pixelsPerKeypoint = Math.ceil((globals.MIN_KEYPOINT_SIZE + descriptorSize + extraSize) / 4);
- const numberOfPixels = encoderCapacity * pixelsPerKeypoint;
-
- return Math.max(globals.MIN_ENCODER_LENGTH, Math.ceil(Math.sqrt(numberOfPixels)));
- }
-
- /**
- * The maximum number of keypoints we can store using
- * a particular configuration of a keypoint encoder
- * @param {number} descriptorSize in bytes
- * @param {number} extraSize in bytes
- * @param {number} encoderLength
- */
- static encoderCapacity(descriptorSize, extraSize, encoderLength)
- {
- const pixelsPerKeypoint = Math.ceil((globals.MIN_KEYPOINT_SIZE + descriptorSize + extraSize) / 4);
- const numberOfPixels = encoderLength * encoderLength;
-
- return Math.floor(numberOfPixels / pixelsPerKeypoint);
- }
- }
-
- /**
- * Abstract scale-space keypoint detector
- * @abstract
- */
- class SpeedyPipelineNodeMultiscaleKeypointDetector extends SpeedyPipelineNodeKeypointDetector
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- * @param {number} [texCount] number of work textures
- * @param {SpeedyPipelinePortBuilder[]} [portBuilders] port builders
- */
- constructor(name = undefined, texCount = undefined, portBuilders = undefined)
- {
- super(name, texCount, portBuilders);
-
- /** @type {number} number of pyramid levels */
- this._levels = 1;
-
- /** @type {number} scale factor between two pyramid levels */
- this._scaleFactor = DEFAULT_SCALE_FACTOR;
- }
-
- /**
- * Number of pyramid levels
- * @returns {number}
- */
- get levels()
- {
- return this._levels;
- }
-
- /**
- * Number of pyramid levels
- * @param {number} levels
- */
- set levels(levels)
- {
- this._levels = Math.max(1, levels | 0);
- }
-
- /**
- * Scale factor between two pyramid levels
- * @returns {number}
- */
- get scaleFactor()
- {
- return this._scaleFactor;
- }
-
- /**
- * Scale factor between two pyramid levels
- * @param {number} scaleFactor should be greater than 1
- */
- set scaleFactor(scaleFactor)
- {
- this._scaleFactor = Math.max(1.0, Math.min(+scaleFactor, 2.0));
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/source.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * source.js
- * Gets keypoints into the pipeline
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
- // Constants
- const UBO_MAX_BYTES = 16384; // UBOs can hold at least 16KB of data: gl.MAX_UNIFORM_BLOCK_SIZE >= 16384 according to the GL ES 3 reference
- const BUFFER_SIZE = 1024; // how many keypoints we can upload in one pass of the shader (as defined in the shader program)
- const SIZEOF_VEC4 = Float32Array.BYTES_PER_ELEMENT * 4; // 16 bytes
-
- /**
- * Gets keypoints into the pipeline
- */
- class SpeedyPipelineNodeKeypointSource extends SpeedyPipelineSourceNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 2, [
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
-
- /** @type {SpeedyKeypoint[]} keypoints to be uploaded to the GPU */
- this._keypoints = [];
-
- /** @type {Float32Array} upload buffer (UBO) */
- this._buffer = SpeedyPipelineNodeKeypointSource._createUploadBuffer(BUFFER_SIZE);
-
- /** @type {number} maximum number of keypoints */
- this._capacity = globals.DEFAULT_ENCODER_CAPACITY;
- }
-
- /**
- * Keypoints to be uploaded
- * @returns {SpeedyKeypoint[]}
- */
- get keypoints()
- {
- return this._keypoints;
- }
-
- /**
- * Keypoints to be uploaded
- * @param {SpeedyKeypoint[]} keypoints
- */
- set keypoints(keypoints)
- {
- if(!Array.isArray(keypoints))
- throw new utils_errors/* IllegalArgumentError */.mG(`Not an array of keypoints`);
-
- this._keypoints = keypoints;
- }
-
- /**
- * The maximum number of keypoints we'll accept.
- * This should be a tight bound for better performance.
- * @returns {number}
- */
- get capacity()
- {
- return this._capacity;
- }
-
- /**
- * The maximum number of keypoints we'll accept.
- * This should be a tight bound for better performance.
- * @param {number} capacity
- */
- set capacity(capacity)
- {
- this._capacity = Math.min(Math.max(0, capacity | 0), globals.MAX_ENCODER_CAPACITY);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- // Orientation, descriptors and extra bytes will be lost
- const descriptorSize = 0, extraSize = 0;
- const keypoints = this._keypoints;
- const maxKeypoints = this._capacity;
- const numKeypoints = Math.min(keypoints.length, maxKeypoints);
- const numPasses = Math.max(1, Math.ceil(numKeypoints / BUFFER_SIZE));
- const buffer = this._buffer;
- const uploadKeypoints = gpu.programs.keypoints.uploadKeypoints;
- const encoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(maxKeypoints, descriptorSize, extraSize); // we're using maxKeypoints to avoid constant texture resize (slow on Firefox)
-
- uploadKeypoints.outputs(encoderLength, encoderLength, this._tex[0], this._tex[1]);
-
- let startIndex = 0, encodedKeypoints = uploadKeypoints.clear();
- for(let i = 0; i < numPasses; i++) {
- const n = Math.min(BUFFER_SIZE, numKeypoints - startIndex);
- const endIndex = startIndex + n;
-
- uploadKeypoints.setUBO('KeypointBuffer', SpeedyPipelineNodeKeypointSource._fillUploadBuffer(buffer, keypoints, startIndex, endIndex));
- encodedKeypoints = uploadKeypoints(encodedKeypoints, startIndex, endIndex, descriptorSize, extraSize, encoderLength);
-
- startIndex = endIndex;
- }
-
- this.output().swrite(encodedKeypoints, descriptorSize, extraSize, encoderLength);
- }
-
- /**
- * Create an upload buffer
- * @param {number} bufferSize number of keypoints
- * @returns {Float32Array}
- */
- static _createUploadBuffer(bufferSize)
- {
- const internalBuffer = new ArrayBuffer(SIZEOF_VEC4 * bufferSize);
-
- utils/* Utils.assert */.c.assert(internalBuffer.byteLength <= UBO_MAX_BYTES);
-
- return new Float32Array(internalBuffer);
- }
-
- /**
- * Fill upload buffer with keypoint data
- * @param {Float32Array} buffer
- * @param {SpeedyKeypoint[]} keypoints
- * @param {number} start index, inclusive
- * @param {number} end index, exclusive
- * @returns {Float32Array} buffer
- */
- static _fillUploadBuffer(buffer, keypoints, start, end)
- {
- const n = end - start;
- for(let i = 0; i < n; i++) {
- const keypoint = keypoints[start + i];
- const hasPos = keypoint.position !== undefined;
- const j = i * 4;
-
- // Format data as follows:
- // vec4(xpos, ypos, lod, score)
- buffer[j] = +(hasPos ? keypoint.position.x : keypoint.x) || 0;
- buffer[j+1] = +(hasPos ? keypoint.position.y : keypoint.y) || 0;
- buffer[j+2] = +(keypoint.lod) || 0;
- buffer[j+3] = +(keypoint.score) || 0;
- }
-
- // done!
- return buffer;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/speedy-keypoint-descriptor.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * speedy-keypoint-descriptor.js
- * Keypoint descriptor
- */
-
- /**
- * Represents a keypoint descriptor
- */
- class SpeedyKeypointDescriptor
- {
- /**
- * Constructor
- * @param {Uint8Array} data descriptor bytes
- */
- constructor(data)
- {
- this._data = data;
- return Object.freeze(this);
- }
-
- /**
- * Descriptor data
- * @returns {Uint8Array}
- */
- get data()
- {
- return this._data;
- }
-
- /**
- * The size of the descriptor, in bytes
- * @returns {number}
- */
- get size()
- {
- return this._data.byteLength;
- }
-
- /**
- * A string representation of the keypoint descriptor
- * @returns {string}
- */
- toString()
- {
- return `SpeedyKeypointDescriptor(${this._data.join(',')})`;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/sink.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * sink.js
- * Gets keypoints out of the pipeline
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- /** next power of 2 */
- const sink_nextPot = x => x > 1 ? 1 << Math.ceil(Math.log2(x)) : 1;
-
- /** empty array of bytes */
- const ZERO_BYTES = new Uint8Array([]);
-
-
- /**
- * Gets keypoints out of the pipeline
- * @template {SpeedyKeypoint} T
- * @abstract
- */
- class SpeedyPipelineNodeAbstractKeypointSink extends SpeedyPipelineSinkNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- * @param {number} [texCount]
- * @param {SpeedyPipelinePortBuilder[]} [portBuilders]
- */
- constructor(name = 'keypoints', texCount = 0, portBuilders = [])
- {
- super(name, texCount + 2, portBuilders);
-
- /** @type {Array<T|null>} keypoints (output) */
- this._keypoints = [];
-
- /** @type {SpeedyTextureReader} texture reader */
- this._textureReader = new SpeedyTextureReader();
-
- /** @type {number} page flipping index */
- this._page = 0;
-
- /** @type {boolean} accelerate GPU-CPU transfers */
- this._turbo = false;
-
- /** @type {boolean} should discarded keypoints be exported as null or dropped altogether? */
- this._includeDiscarded = false;
- }
-
- /**
- * Accelerate GPU-CPU transfers
- * @returns {boolean}
- */
- get turbo()
- {
- return this._turbo;
- }
-
- /**
- * Accelerate GPU-CPU transfers
- * @param {boolean} value
- */
- set turbo(value)
- {
- this._turbo = Boolean(value);
- }
-
- /**
- * Should discarded keypoints be exported as null or dropped altogether?
- * @returns {boolean}
- */
- get includeDiscarded()
- {
- return this._includeDiscarded;
- }
-
- /**
- * Should discarded keypoints be exported as null or dropped altogether?
- * @param {boolean} value
- */
- set includeDiscarded(value)
- {
- this._includeDiscarded = Boolean(value);
- }
-
- /**
- * Initializes this node
- * @param {SpeedyGPU} gpu
- */
- init(gpu)
- {
- super.init(gpu);
- this._textureReader.init(gpu);
- }
-
- /**
- * Releases this node
- * @param {SpeedyGPU} gpu
- */
- release(gpu)
- {
- this._textureReader.release(gpu);
- super.release(gpu);
- }
-
- /**
- * Export data from this node to the user
- * @returns {SpeedyPromise<Array<T|null>>}
- */
- export()
- {
- return speedy_promise/* SpeedyPromise.resolve */.s.resolve(this._keypoints);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- return this._download(gpu, encodedKeypoints, descriptorSize, extraSize, encoderLength);
- }
-
- /**
- * Download and decode keypoints from the GPU
- * @param {SpeedyGPU} gpu
- * @param {SpeedyDrawableTexture} encodedKeypoints
- * @param {number} descriptorSize
- * @param {number} extraSize
- * @param {number} encoderLength
- * @returns {SpeedyPromise<void>}
- */
- _download(gpu, encodedKeypoints, descriptorSize, extraSize, encoderLength)
- {
- const useBufferedDownloads = this._turbo;
-
- /*
-
- I have found experimentally that, in Firefox, readPixelsAsync()
- performs MUCH better if the width of the target texture is a power
- of two. I have no idea why this is the case, nor if it's related to
- some interaction with the GL drivers, somehow. This seems to make no
- difference on Chrome, however. In any case, let's convert the input
- texture to POT.
-
- */
- const encoderWidth = sink_nextPot(encoderLength);
- //const encoderHeight = nextPot(Math.ceil(encoderLength * encoderLength / encoderWidth));
- const encoderHeight = Math.ceil(encoderLength * encoderLength / encoderWidth);
- //const encoderWidth=encoderLength,encoderHeight=encoderLength;
-
- // copy the set of keypoints to an internal texture
- const copiedTexture = this._tex[(this._tex.length - 1) - this._page];
- (gpu.programs.utils.copyKeypoints
- .outputs(encoderWidth, encoderHeight, copiedTexture)
- )(encodedKeypoints);
-
- // flip page
- this._page = 1 - this._page;
-
- // download the internal texture
- return this._textureReader.readPixelsAsync(copiedTexture, 0, 0, copiedTexture.width, copiedTexture.height, useBufferedDownloads).then(pixels => {
-
- // decode the keypoints and store them in this._keypoints
- this._keypoints = this._decode(pixels, descriptorSize, extraSize, encoderWidth, encoderHeight);
-
- });
- }
-
- /**
- * Decode a sequence of keypoints, given a flattened image of encoded pixels
- * @param {Uint8Array} pixels pixels in the [r,g,b,a,...] format
- * @param {number} descriptorSize in bytes
- * @param {number} extraSize in bytes
- * @param {number} encoderWidth
- * @param {number} encoderHeight
- * @returns {Array<T|null>} keypoints
- */
- _decode(pixels, descriptorSize, extraSize, encoderWidth, encoderHeight)
- {
- const bytesPerKeypoint = globals.MIN_KEYPOINT_SIZE + descriptorSize + extraSize;
- const m = globals.LOG2_PYRAMID_MAX_SCALE, h = globals.PYRAMID_MAX_LEVELS;
- const piOver255 = Math.PI / 255.0;
- const keypoints = /** @type {Array<T|null>} */ ( [] );
- const includeDiscarded = this._includeDiscarded;
- let descriptorBytes = ZERO_BYTES, extraBytes = ZERO_BYTES;
- let x, y, z, w, lod, rotation, score;
- let keypoint;
-
- // validate
- if(descriptorSize % 4 != 0 || extraSize % 4 != 0)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid descriptorSize (${descriptorSize}) / extraSize (${extraSize})`);
-
- // how many bytes should we read?
- const e2 = encoderWidth * encoderHeight * 4;
- const size = pixels.byteLength;
- if(size != e2)
- utils/* Utils.warning */.c.warning(`Expected ${e2} bytes when decoding a set of keypoints, found ${size}`);
-
- // copy the data (we use shared buffers when receiving pixels[])
- if(descriptorSize + extraSize > 0)
- pixels = new Uint8Array(pixels);
-
- // for each encoded keypoint
- for(let i = 0; i < size; i += bytesPerKeypoint) {
- // extract encoded header
- x = (pixels[i+1] << 8) | pixels[i];
- y = (pixels[i+3] << 8) | pixels[i+2];
- z = (pixels[i+5] << 8) | pixels[i+4];
- w = (pixels[i+7] << 8) | pixels[i+6];
-
- // the keypoint is "null": we have reached the end of the list
- if(x == 0xFFFF && y == 0xFFFF)
- break;
-
- // the header is zero: discard the keypoint
- if(x + y + z + w == 0) {
- if(includeDiscarded)
- keypoints.push(null);
- continue;
- }
-
- // extract extra & descriptor bytes
- if(extraSize > 0) {
- extraBytes = pixels.subarray(8 + i, 8 + i + extraSize);
- if(extraBytes.byteLength < extraSize) {
- utils/* Utils.warning */.c.warning(`KeypointSink: expected ${extraSize} extra bytes when decoding the ${i/bytesPerKeypoint}-th keypoint, found ${extraBytes.byteLength} instead`);
- continue; // something is off here; discard
- }
- }
- if(descriptorSize > 0) {
- descriptorBytes = pixels.subarray(8 + i + extraSize, 8 + i + extraSize + descriptorSize);
- if(descriptorBytes.byteLength < descriptorSize) {
- utils/* Utils.warning */.c.warning(`KeypointSink: expected ${descriptorSize} descriptor bytes when decoding the ${i/bytesPerKeypoint}-th keypoint, found ${descriptorBytes.byteLength} instead`);
- continue; // something is off here; discard
- }
- }
-
- // decode position: convert from fixed-point
- x /= globals.FIX_RESOLUTION;
- y /= globals.FIX_RESOLUTION;
-
- // decode level-of-detail
- lod = (pixels[i+4] < 255) ? -m + ((m + h) * pixels[i+4]) / 255.0 : 0.0;
-
- // decode orientation
- rotation = (2 * pixels[i+5] - 255) * piOver255;
-
- // decode score
- score = utils/* Utils.decodeFloat16 */.c.decodeFloat16(w);
-
- // create keypoint
- keypoint = this._createKeypoint(x, y, lod, rotation, score, descriptorBytes, extraBytes);
-
- // register keypoint
- keypoints.push(keypoint);
- }
-
- // done!
- return keypoints;
- }
-
- /**
- * Instantiate a new keypoint
- * @param {number} x
- * @param {number} y
- * @param {number} lod
- * @param {number} rotation
- * @param {number} score
- * @param {Uint8Array} descriptorBytes
- * @param {Uint8Array} extraBytes
- * @returns {T}
- */
- _createKeypoint(x, y, lod, rotation, score, descriptorBytes, extraBytes)
- {
- throw new utils_errors/* AbstractMethodError */.Mi();
- }
-
- /**
- * Allocate extra soace
- * @param {SpeedyGPU} gpu
- * @param {SpeedyDrawableTexture} output output texture
- * @param {SpeedyTexture} inputEncodedKeypoints input with no extra space
- * @param {number} inputDescriptorSize in bytes, must be positive
- * @param {number} inputExtraSize must be 0
- * @param {number} outputDescriptorSize must be inputDescriptorSize
- * @param {number} outputExtraSize in bytes, must be positive and a multiple of 4
- * @returns {SpeedyDrawableTexture} encodedKeypoints with extra space
- */
- _allocateExtra(gpu, output, inputEncodedKeypoints, inputDescriptorSize, inputExtraSize, outputDescriptorSize, outputExtraSize)
- {
- utils/* Utils.assert */.c.assert(inputExtraSize === 0);
- utils/* Utils.assert */.c.assert(outputDescriptorSize === inputDescriptorSize && outputExtraSize > 0 && outputExtraSize % 4 === 0);
-
- const inputEncoderLength = inputEncodedKeypoints.width;
- const inputEncoderCapacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(inputDescriptorSize, inputExtraSize, inputEncoderLength);
- const outputEncoderCapacity = inputEncoderCapacity;
- const outputEncoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(outputEncoderCapacity, outputDescriptorSize, outputExtraSize);
-
- return (gpu.programs.keypoints.allocateExtra
- .outputs(outputEncoderLength, outputEncoderLength, output)
- )(inputEncodedKeypoints, inputDescriptorSize, inputExtraSize, inputEncoderLength, outputDescriptorSize, outputExtraSize, outputEncoderLength);
- }
- }
-
- /**
- * Gets standard keypoints out of the pipeline
- * @extends {SpeedyPipelineNodeAbstractKeypointSink<SpeedyKeypoint>}
- */
- class SpeedyPipelineNodeKeypointSink extends SpeedyPipelineNodeAbstractKeypointSink
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = 'keypoints')
- {
- super(name, 0, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
- }
-
- /**
- * Instantiate a new keypoint
- * @param {number} x
- * @param {number} y
- * @param {number} lod
- * @param {number} rotation
- * @param {number} score
- * @param {Uint8Array} descriptorBytes
- * @param {Uint8Array} extraBytes
- * @returns {SpeedyKeypoint}
- */
- _createKeypoint(x, y, lod, rotation, score, descriptorBytes, extraBytes)
- {
- const descriptorSize = descriptorBytes.byteLength;
-
- // read descriptor, if any
- const descriptor = descriptorSize > 0 ? new SpeedyKeypointDescriptor(descriptorBytes) : null;
-
- // create keypoint
- return new SpeedyKeypoint(x, y, lod, rotation, score, descriptor);
- }
- }
-
- /**
- * Gets tracked keypoints out of the pipeline
- * @extends {SpeedyPipelineNodeAbstractKeypointSink<SpeedyTrackedKeypoint>}
- */
- class SpeedyPipelineNodeTrackedKeypointSink extends SpeedyPipelineNodeAbstractKeypointSink
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = 'keypoints')
- {
- super(name, 2, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints).satisfying(
- ( /** @type {SpeedyPipelineMessageWithKeypoints} */ msg ) =>
- msg.extraSize == 0
- ),
- InputPort('flow').expects(SpeedyPipelineMessageType.Vector2)
- ]);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- const { vectors } = /** @type {SpeedyPipelineMessageWith2DVectors} */ ( this.input('flow').read() );
-
- // allocate extra space
- const newDescriptorSize = descriptorSize;
- const newExtraSize = 4; // 1 pixel per flow vector per keypoint
- const encodedKeypointsWithExtraSpace = this._allocateExtra(gpu, this._tex[0], encodedKeypoints, descriptorSize, extraSize, newDescriptorSize, newExtraSize);
-
- // attach flow vectors
- const newEncoderLength = encodedKeypointsWithExtraSpace.width;
- const newEncodedKeypoints = (gpu.programs.keypoints.transferToExtra
- .outputs(newEncoderLength, newEncoderLength, this._tex[1])
- )(vectors, vectors.width, encodedKeypointsWithExtraSpace, newDescriptorSize, newExtraSize, newEncoderLength);
-
- // done!
- return this._download(gpu, newEncodedKeypoints, newDescriptorSize, newExtraSize, newEncoderLength);
- }
-
- /**
- * Instantiate a new keypoint
- * @param {number} x
- * @param {number} y
- * @param {number} lod
- * @param {number} rotation
- * @param {number} score
- * @param {Uint8Array} descriptorBytes
- * @param {Uint8Array} extraBytes
- * @returns {SpeedyTrackedKeypoint}
- */
- _createKeypoint(x, y, lod, rotation, score, descriptorBytes, extraBytes)
- {
- const descriptorSize = descriptorBytes.byteLength;
- const extraSize = extraBytes.byteLength;
-
- // read descriptor, if any
- const descriptor = descriptorSize > 0 ? new SpeedyKeypointDescriptor(descriptorBytes) : null;
-
- // read flow vector
- const fx = utils/* Utils.decodeFloat16 */.c.decodeFloat16((extraBytes[1] << 8) | extraBytes[0]);
- const fy = utils/* Utils.decodeFloat16 */.c.decodeFloat16((extraBytes[3] << 8) | extraBytes[2]);
- const flow = new SpeedyVector2(fx, fy);
-
- // create keypoint
- return new SpeedyTrackedKeypoint(x, y, lod, rotation, score, descriptor, flow);
- }
- }
-
- /**
- * Gets matched keypoints out of the pipeline
- * @extends SpeedyPipelineNodeAbstractKeypointSink<SpeedyMatchedKeypoint>
- */
- class SpeedyPipelineNodeMatchedKeypointSink extends SpeedyPipelineNodeAbstractKeypointSink
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = 'keypoints')
- {
- super(name, 2, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints).satisfying(
- ( /** @type {SpeedyPipelineMessageWithKeypoints} */ msg ) =>
- msg.extraSize == 0
- ),
- InputPort('matches').expects(SpeedyPipelineMessageType.KeypointMatches)
- ]);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- const { encodedMatches, matchesPerKeypoint } = /** @type {SpeedyPipelineMessageWithKeypointMatches} */ ( this.input('matches').read() );
-
- // allocate space for the matches
- const newDescriptorSize = descriptorSize;
- const newExtraSize = matchesPerKeypoint * 4; // 4 bytes per pixel
- const encodedKeypointsWithExtraSpace = this._allocateExtra(gpu, this._tex[0], encodedKeypoints, descriptorSize, extraSize, newDescriptorSize, newExtraSize);
-
- // transfer matches to a new texture
- const newEncoderLength = encodedKeypointsWithExtraSpace.width;
- const newEncodedKeypoints = (gpu.programs.keypoints.transferToExtra
- .outputs(newEncoderLength, newEncoderLength, this._tex[1])
- )(encodedMatches, encodedMatches.width, encodedKeypointsWithExtraSpace, newDescriptorSize, newExtraSize, newEncoderLength);
-
- // done!
- return this._download(gpu, newEncodedKeypoints, newDescriptorSize, newExtraSize, newEncoderLength);
- }
-
- /**
- * Instantiate a new keypoint
- * @param {number} x
- * @param {number} y
- * @param {number} lod
- * @param {number} rotation
- * @param {number} score
- * @param {Uint8Array} descriptorBytes
- * @param {Uint8Array} extraBytes
- * @returns {SpeedyMatchedKeypoint}
- */
- _createKeypoint(x, y, lod, rotation, score, descriptorBytes, extraBytes)
- {
- const descriptorSize = descriptorBytes.byteLength;
- const extraSize = extraBytes.byteLength;
-
- // read descriptor, if any
- const descriptor = descriptorSize > 0 ? new SpeedyKeypointDescriptor(descriptorBytes) : null;
-
- // decode matches
- const matchesPerKeypoint = extraSize / 4;
- const matches = /** @type {SpeedyKeypointMatch[]} */ ( new Array(matchesPerKeypoint) );
- for(let matchIndex = 0; matchIndex < matchesPerKeypoint; matchIndex++) {
- const base = matchIndex * 4;
- const u32 = extraBytes[base] | (extraBytes[base+1] << 8) | (extraBytes[base+2] << 16) | (extraBytes[base+3] << 24);
- const match = new SpeedyKeypointMatch(u32 & globals.MATCH_INDEX_MASK, u32 >>> globals.MATCH_INDEX_BITS);
-
- matches[matchIndex] = match;
- }
-
- // done!
- return new SpeedyMatchedKeypoint(x, y, lod, rotation, score, descriptor, matches);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/clipper.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * clipper.js
- * Keypoint clipper
- */
-
-
-
-
-
-
-
-
-
-
-
-
- // Constants
- const LOG2_STRIDE = 5;
- const MAX_SIZE = globals.MAX_ENCODER_CAPACITY;
-
-
-
- /**
- * Keypoint clipper: filters the best keypoints from a stream
- */
- class SpeedyPipelineNodeKeypointClipper extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 4, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
-
- /** @type {number} the maximum number of keypoints in the output */
- this._size = MAX_SIZE;
- }
-
- /**
- * The maximum number of keypoints in the output
- * @returns {number}
- */
- get size()
- {
- return this._size;
- }
-
- /**
- * The maximum number of keypoints in the output
- * @param {number} size
- */
- set size(size)
- {
- this._size = Math.max(0, Math.min(size | 0, MAX_SIZE));
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- const keypoints = gpu.programs.keypoints;
- const clipValue = this._size;
- const tex = this._tex;
- const outputTexture = this._tex[3];
-
- // find the minimum power of 2 pot such that pot >= capacity
- const capacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength);
- //const pot = 1 << (Math.ceil(Math.log2(capacity)) | 0);
-
- // find the dimensions of the sorting shaders
- const stride = 1 << LOG2_STRIDE; // must be a power of 2
- //const height = Math.max(1, pot >>> LOG2_STRIDE); // this is also a power of 2
- const height = Math.ceil(capacity / stride); // more economical, maybe not a power of 2
- const numberOfPixels = stride * height;
-
- // find the dimensions of the output texture
- const newCapacity = Math.min(capacity, clipValue);
- const newEncoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(newCapacity, descriptorSize, extraSize);
-
- // generate permutation of keypoints
- keypoints.sortCreatePermutation.outputs(stride, height, tex[0]);
- let permutation = keypoints.sortCreatePermutation(encodedKeypoints, descriptorSize, extraSize, encoderLength);
-
- // sort permutation
- const numPasses = Math.ceil(Math.log2(numberOfPixels));
- keypoints.sortMergePermutation.outputs(stride, height, tex[1], tex[2]);
- for(let i = 1; i <= numPasses; i++) {
- const blockSize = 1 << i; // 2, 4, 8...
- const dblLog2BlockSize = i << 1; // 2 * log2(blockSize)
- permutation = keypoints.sortMergePermutation(permutation, blockSize, dblLog2BlockSize);
- }
-
- // apply permutation
- keypoints.sortApplyPermutation.outputs(newEncoderLength, newEncoderLength, outputTexture);
- keypoints.sortApplyPermutation(permutation, newCapacity, encodedKeypoints, descriptorSize, extraSize);
-
- /*
- // debug (read the contents of the permutation)
- const pixels = permutation.inspect(gpu), debug = [];
- for(let i = 0; i < pixels.length; i += 4) {
- let id = pixels[i] | (pixels[i+1] << 8);
- let score = pixels[i+2] / 255.0;
- let valid = pixels[i+3] / 255.0;
- debug.push([ id, valid, score, ].join(', '));
- }
- console.log(debug);
- */
-
- // done!
- this.output().swrite(outputTexture, descriptorSize, extraSize, newEncoderLength);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/border-clipper.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * border-clipper.js
- * Keypoint Border Clipper
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * The Border Clipper removes all keypoints within a border of the edges of an image
- */
- class SpeedyPipelineNodeKeypointBorderClipper extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 5, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
-
- /** @type {SpeedySize} image size, in pixels */
- this._imageSize = new SpeedySize(0,0);
-
- /** @type {SpeedyVector2} border size, in pixels */
- this._borderSize = new SpeedyVector2(0,0);
- }
-
- /**
- * Image size, in pixels
- * @returns {SpeedySize}
- */
- get imageSize()
- {
- return this._imageSize;
- }
-
- /**
- * Image size, in pixels
- * @param {SpeedySize} imageSize
- */
- set imageSize(imageSize)
- {
- this._imageSize = imageSize;
- }
-
- /**
- * Border size, in pixels
- * @returns {SpeedyVector2}
- */
- get borderSize()
- {
- return this._borderSize;
- }
-
- /**
- * Border size, in pixels
- * @param {SpeedyVector2} borderSize
- */
- set borderSize(borderSize)
- {
- this._borderSize = borderSize;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- const keypoints = gpu.programs.keypoints;
- const imageSize = this._imageSize;
- const borderSize = this._borderSize;
- const imageWidth = imageSize.width, imageHeight = imageSize.height;
- const borderLeft = borderSize.x, borderRight = borderSize.x;
- const borderTop = borderSize.y, borderBottom = borderSize.y;
- const tex = this._tex;
-
- // validate
- if(imageWidth == 0 || imageHeight == 0)
- throw new utils_errors/* IllegalOperationError */.js(`BorderClipper: did you forget to set the image size?`);
-
- // find the capacity of the keypoint stream
- const capacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength);
- const mixEncoderLength = Math.max(1, Math.ceil(Math.sqrt(capacity)));
-
- // prepare programs
- keypoints.clipBorder.outputs(encoderLength, encoderLength, tex[0]);
- keypoints.mixKeypointsInit.outputs(mixEncoderLength, mixEncoderLength, tex[1]);
- keypoints.mixKeypointsSort.outputs(mixEncoderLength, mixEncoderLength, tex[2], tex[3]);
- keypoints.mixKeypointsApply.outputs(encoderLength, encoderLength, tex[4]);
-
- // clip keypoints
- let clippedKeypoints = keypoints.clipBorder(
- imageWidth, imageHeight,
- borderTop, borderRight, borderBottom, borderLeft,
- encodedKeypoints, descriptorSize, extraSize, encoderLength
- );
-
- // sort keypoints
- let sortedKeypoints = keypoints.mixKeypointsInit(
- clippedKeypoints, descriptorSize, extraSize, encoderLength, capacity
- );
-
- for(let b = 1; b < capacity; b *= 2)
- sortedKeypoints = keypoints.mixKeypointsSort(sortedKeypoints, b);
-
- clippedKeypoints = keypoints.mixKeypointsApply(
- sortedKeypoints, clippedKeypoints, descriptorSize, extraSize, encoderLength
- );
-
- /*
- // debug: view keypoints
- keypoints.mixKeypointsView.outputs(mixEncoderLength, mixEncoderLength, tex[1]);
- this._visualize(gpu, keypoints.mixKeypointsView(sortedKeypoints));
- */
-
- // done!
- this.output().swrite(clippedKeypoints, descriptorSize, extraSize, encoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/buffer.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * buffer.js
- * Keypoint Buffer
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Keypoint Buffer: a node with memory.
- * At time t, it outputs the keypoints received at time t-1
- */
- class SpeedyPipelineNodeKeypointBuffer extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 2, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
-
- /** @type {number} current page: 0 or 1 */
- this._pageIndex = 0;
-
- /** @type {boolean} first run? */
- this._initialized = false;
-
- /** @type {number} previous descriptor size, in bytes */
- this._previousDescriptorSize = 0;
-
- /** @type {number} previous extra size, in bytes */
- this._previousExtraSize = 0;
-
- /** @type {number} previous encoder length */
- this._previousEncoderLength = 0;
-
- /** @type {boolean} frozen buffer? */
- this._frozen = false;
- }
-
- /**
- * A frozen buffer discards the input, effectively increasing the buffering time
- * @returns {boolean}
- */
- get frozen()
- {
- return this._frozen;
- }
-
- /**
- * A frozen buffer discards the input, effectively increasing the buffering time
- * @param {boolean} value
- */
- set frozen(value)
- {
- this._frozen = Boolean(value);
- }
-
- /**
- * Releases this node
- * @param {SpeedyGPU} gpu
- */
- release(gpu)
- {
- this._initialized = false;
- super.release(gpu);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- const previousDescriptorSize = this._previousDescriptorSize;
- const previousExtraSize = this._previousExtraSize;
- const previousEncoderLength = this._previousEncoderLength;
- const page = this._tex;
- const previousInputTexture = page[1 - this._pageIndex];
- const outputTexture = page[this._pageIndex];
-
- // bufferize
- if(!this._frozen || !this._initialized) {
- // store input
- this._previousDescriptorSize = descriptorSize;
- this._previousExtraSize = extraSize;
- this._previousEncoderLength = encoderLength;
- previousInputTexture.resize(encoderLength, encoderLength);
- encodedKeypoints.copyTo(previousInputTexture);
-
- // page flipping
- this._pageIndex = 1 - this._pageIndex;
- }
-
- // first run?
- if(!this._initialized) {
- this._initialized = true;
- this.output().swrite(previousInputTexture, descriptorSize, extraSize, encoderLength);
- return;
- }
-
- // done!
- this.output().swrite(outputTexture, previousDescriptorSize, previousExtraSize, previousEncoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/mixer.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * mixer.js
- * Keypoint Mixer
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Keypoint Mixer: merges two sets of keypoints
- */
- class SpeedyPipelineNodeKeypointMixer extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 5, [
- InputPort('in0').expects(SpeedyPipelineMessageType.Keypoints),
- InputPort('in1').expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const kps0 = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('in0').read() );
- const kps1 = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('in1').read() );
- const descriptorSize = kps0.descriptorSize;
- const extraSize = kps0.extraSize;
- const keypoints = gpu.programs.keypoints;
- const tex = this._tex;
-
- // ensure that the format of kps0 equals the format of kps1
- if(!(kps0.descriptorSize === kps1.descriptorSize && kps0.extraSize === kps0.extraSize))
- throw new utils_errors/* IllegalOperationError */.js(`Can't merge two sets of keypoints that have different formats`);
-
- // find the capacity of kps0 + kps1
- const cap0 = SpeedyPipelineNodeKeypointDetector.encoderCapacity(kps0.descriptorSize, kps0.extraSize, kps0.encoderLength);
- const cap1 = SpeedyPipelineNodeKeypointDetector.encoderCapacity(kps1.descriptorSize, kps1.extraSize, kps1.encoderLength);
- const capacity = cap0 + cap1;
-
- // find the dimensions of the output texture
- const encoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(capacity, descriptorSize, extraSize);
- const mixEncoderLength = Math.max(1, Math.ceil(Math.sqrt(capacity)));
-
- // prepare programs
- keypoints.mixKeypointsPreInit.outputs(encoderLength, encoderLength, tex[0]);
- keypoints.mixKeypointsInit.outputs(mixEncoderLength, mixEncoderLength, tex[1]);
- keypoints.mixKeypointsSort.outputs(mixEncoderLength, mixEncoderLength, tex[2], tex[3]);
- keypoints.mixKeypointsApply.outputs(encoderLength, encoderLength, tex[4]);
-
- // mix keypoints
- let mixedKeypoints = keypoints.mixKeypointsPreInit(
- kps0.encodedKeypoints, kps1.encodedKeypoints,
- kps0.encoderLength, kps1.encoderLength,
- cap0, cap1,
- descriptorSize,
- extraSize,
- encoderLength
- );
-
- let sortedKeypoints = keypoints.mixKeypointsInit(
- mixedKeypoints, descriptorSize, extraSize, encoderLength, capacity
- );
-
- for(let b = 1; b < capacity; b *= 2)
- sortedKeypoints = keypoints.mixKeypointsSort(sortedKeypoints, b);
-
- mixedKeypoints = keypoints.mixKeypointsApply(
- sortedKeypoints, mixedKeypoints, descriptorSize, extraSize, encoderLength
- );
-
- /*
- // debug: view keypoints
- keypoints.mixKeypointsView.outputs(mixEncoderLength, mixEncoderLength, tex[1]);
- this._visualize(gpu, keypoints.mixKeypointsView(sortedKeypoints));
- */
-
- this.output().swrite(mixedKeypoints, descriptorSize, extraSize, encoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/shuffler.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * shuffler.js
- * Keypoint Shuffler
- */
-
-
-
-
-
-
-
-
-
-
- /**
- * The Keypoint Shuffler shuffles a list of keypoints
- */
- class SpeedyPipelineNodeKeypointShuffler extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 6, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
-
- /** @type {number} maximum number of keypoints */
- this._maxKeypoints = Number.NaN;
- }
-
- /**
- * Maximum number of keypoints (optional)
- * @returns {number}
- */
- get maxKeypoints()
- {
- return this._maxKeypoints;
- }
-
- /**
- * Maximum number of keypoints (optional)
- * @param {number} value
- */
- set maxKeypoints(value)
- {
- if(!Number.isNaN(value))
- this._maxKeypoints = Math.max(0, value | 0);
- else
- this._maxKeypoints = Number.NaN;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- let { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- const capacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength);
- const maxKeypoints = this._maxKeypoints;
-
- // shuffle the keypoints (including nulls)
- const permutationMaxLength = gpu.programs.keypoints.shuffle.definedConstant('PERMUTATION_MAXLEN');
- const permutationLength = Math.min(permutationMaxLength, capacity);
- const permutation = this._generatePermutation(permutationLength, permutationMaxLength);
- encodedKeypoints = (gpu.programs.keypoints.shuffle
- .setUBO('Permutation', permutation)
- .outputs(encoderLength, encoderLength, this._tex[0])
- )(encodedKeypoints, descriptorSize, extraSize, encoderLength);
-
- // sort the keypoints
- gpu.programs.keypoints.mixKeypointsInit.outputs(encoderLength, encoderLength, this._tex[1]);
- gpu.programs.keypoints.mixKeypointsSort.outputs(encoderLength, encoderLength, this._tex[2], this._tex[3]);
- gpu.programs.keypoints.mixKeypointsApply.outputs(encoderLength, encoderLength, this._tex[4]);
-
- let sortedKeypoints = gpu.programs.keypoints.mixKeypointsInit(
- encodedKeypoints, descriptorSize, extraSize, encoderLength, capacity
- );
-
- for(let b = 1; b < capacity; b *= 2)
- sortedKeypoints = gpu.programs.keypoints.mixKeypointsSort(sortedKeypoints, b);
-
- encodedKeypoints = gpu.programs.keypoints.mixKeypointsApply(
- sortedKeypoints, encodedKeypoints, descriptorSize, extraSize, encoderLength
- );
-
- // clip the output?
- if(!Number.isNaN(maxKeypoints) && maxKeypoints < capacity) {
- const newEncoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(maxKeypoints, descriptorSize, extraSize);
- encodedKeypoints = (gpu.programs.keypoints.clip
- .outputs(newEncoderLength, newEncoderLength, this._tex[5])
- )(encodedKeypoints, descriptorSize, extraSize, encoderLength, maxKeypoints);
- encoderLength = newEncoderLength;
- }
-
- // done!
- this.output().swrite(encodedKeypoints, descriptorSize, extraSize, encoderLength);
- }
-
- /**
- * Generate a permutation p of { 0, 1, ..., n-1 } such that p(p(x)) = x for all x
- * @param {number} n positive integer
- * @param {number} [bufsize] size of the output array
- * @returns {Int32Array} permutation
- */
- _generatePermutation(n, bufsize = n)
- {
- const array = new Int32Array(bufsize);
- const p = array.subarray(0, n).fill(-1);
- const q = utils/* Utils.shuffle */.c.shuffle(utils/* Utils.range */.c.range(n));
-
- for(let i = 0, j = 0; i < n; i++) {
- if(p[i] < 0) {
- do { p[i] = q[j++]; } while(p[i] < i);
- p[p[i]] = i;
- }
- }
-
- return array; // padded with zeros
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/multiplexer.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * multiplexer.js
- * Keypoint multiplexer
- */
-
-
-
-
-
-
-
-
-
-
-
- /** @type {string[]} the names of the input ports indexed by their number */
- const multiplexer_INPUT_PORT = [ 'in0', 'in1' ];
-
- /**
- * Keypoint multiplexer
- */
- class SpeedyPipelineNodeKeypointMultiplexer extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 0, [
- ...(multiplexer_INPUT_PORT.map(portName => InputPort(portName).expects(SpeedyPipelineMessageType.Keypoints))),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints),
- ]);
-
- /** @type {number} which port should be linked to the output? */
- this._port = 0;
- }
-
- /**
- * The number of the port that should be linked to the output
- * @returns {number}
- */
- get port()
- {
- return this._port;
- }
-
- /**
- * The number of the port that should be linked to the output
- * @param {number} port
- */
- set port(port)
- {
- if(port < 0 || port >= multiplexer_INPUT_PORT.length)
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid port: ${port}`);
-
- this._port = port | 0;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const message = this.input(multiplexer_INPUT_PORT[this._port]).read();
-
- this.output().write(message);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/transformer.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * transformer.js
- * Apply a transformation matrix to a set of keypoints
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Apply a transformation matrix to a set of keypoints
- */
- class SpeedyPipelineNodeKeypointTransformer extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
-
- /** @type {SpeedyMatrix} transformation matrix */
- this._transform = speedy_matrix.SpeedyMatrix.Create(3, 3, [1, 0, 0, 0, 1, 0, 0, 0, 1]); // identity matrix
- }
-
- /**
- * Transformation matrix
- * @returns {SpeedyMatrix}
- */
- get transform()
- {
- return this._transform;
- }
-
- /**
- * Transformation matrix. Must be 3x3
- * @param {SpeedyMatrix} transform
- */
- set transform(transform)
- {
- if(!(transform.rows == 3 && transform.columns == 3))
- throw new utils_errors/* IllegalArgumentError */.mG(`Not a 3x3 transformation matrix: ${transform}`);
-
- this._transform = transform;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- const outputTexture = this._tex[0];
- const homography = this._transform.read();
-
- // apply homography
- (gpu.programs.keypoints.applyHomography
- .outputs(encodedKeypoints.width, encodedKeypoints.height, outputTexture)
- )(homography, encodedKeypoints, descriptorSize, extraSize, encoderLength);
-
- // done!
- this.output().swrite(outputTexture, descriptorSize, extraSize, encoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/subpixel.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * subpixel.js
- * Subpixel refinement of keypoint location
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /** @typedef {"quadratic1d"|"taylor2d"|"bicubic-upsample"|"bilinear-upsample"} SubpixelRefinementMethod */
-
- /** @const {Object<SubpixelRefinementMethod,string>} method name to program name */
- const METHOD2PROGRAM = Object.freeze({
- 'quadratic1d': 'subpixelQuadratic1d',
- 'taylor2d': 'subpixelTaylor2d',
- 'bicubic-upsample': 'subpixelBicubic',
- 'bilinear-upsample': 'subpixelBilinear',
- });
-
- /**
- * Subpixel refinement of keypoint location
- */
- class SpeedyPipelineNodeKeypointSubpixelRefiner extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 2, [
- InputPort('image').expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- InputPort('keypoints').expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort('displacements').expects(SpeedyPipelineMessageType.Vector2),
- ]);
-
- /** @type {SubpixelRefinementMethod} subpixel refinement method */
- this._method = 'quadratic1d';
-
- /** @type {number} max iterations for the upsampling methods */
- this._maxIterations = 6;
-
- /** @type {number} convergence threshold for the upsampling methods */
- this._epsilon = 0.1;
- }
-
- /**
- * Subpixel refinement method
- * @returns {SubpixelRefinementMethod}
- */
- get method()
- {
- return this._method;
- }
-
- /**
- * Subpixel refinement method
- * @param {SubpixelRefinementMethod} name
- */
- set method(name)
- {
- if(!Object.prototype.hasOwnProperty.call(METHOD2PROGRAM, name))
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid method: "${name}"`);
-
- this._method = name;
- }
-
- /**
- * Max. iterations for the upsampling methods
- * @returns {number}
- */
- get maxIterations()
- {
- return this._maxIterations;
- }
-
- /**
- * Max. iterations for the upsampling methods
- * @param {number} value
- */
- set maxIterations(value)
- {
- this._maxIterations = Math.max(0, +value);
- }
-
- /**
- * Convergence threshold for the upsampling methods
- * @returns {number}
- */
- get epsilon()
- {
- return this._epsilon;
- }
-
- /**
- * Convergence threshold for the upsampling methods
- * @param {number} value
- */
- set epsilon(value)
- {
- this._epsilon = Math.max(0, +value);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('keypoints').read() );
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input('image').read() );
- const tex = this._tex;
- const program = METHOD2PROGRAM[this._method];
- const maxIterations = this._maxIterations;
- const epsilon = this._epsilon;
-
- // note: if you detected the keypoints using a pyramid,
- // you need to pass that pyramid as input!
-
- // we'll compute the offsets for each keypoint
- const capacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength);
- const offsetEncoderLength = Math.max(1, Math.ceil(Math.sqrt(capacity))); // 1 pixel per refinement offset
- const offsets = (gpu.programs.keypoints[program]
- .outputs(offsetEncoderLength, offsetEncoderLength, tex[0])
- )(image, encodedKeypoints, descriptorSize, extraSize, encoderLength, maxIterations, epsilon);
-
- // apply the offsets to the keypoints
- const refinedKeypoints = (gpu.programs.keypoints.transferFlow
- .outputs(encoderLength, encoderLength, tex[1])
- )(offsets, encodedKeypoints, descriptorSize, extraSize, encoderLength);
-
- // done!
- this.output().swrite(refinedKeypoints, descriptorSize, extraSize, encoderLength);
- this.output('displacements').swrite(offsets);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/detectors/fast.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * fast.js
- * FAST corner detector
- */
-
-
-
-
-
-
-
-
-
-
-
-
- // Constants
- const DEFAULT_THRESHOLD = 20;
-
-
-
- /**
- * FAST corner detector
- */
- class SpeedyPipelineNodeFASTKeypointDetector extends SpeedyPipelineNodeMultiscaleKeypointDetector
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 5, [
- InputPort().expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints),
- ]);
-
- /** @type {number} FAST threshold in [0,255] */
- this._threshold = DEFAULT_THRESHOLD;
- }
-
- /**
- * FAST threshold in [0,255]
- * @returns {number}
- */
- get threshold()
- {
- return this._threshold;
- }
-
- /**
- * FAST threshold in [0,255]
- * @param {number} threshold
- */
- set threshold(threshold)
- {
- this._threshold = Math.max(0, Math.min(threshold | 0, 255));
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const tex = this._tex;
- const capacity = this._capacity;
- const threshold = this._threshold;
- const lodStep = Math.log2(this.scaleFactor);
- const levels = this.levels;
-
- // validate pyramid
- if(!(levels == 1 || image.hasMipmaps()))
- throw new utils_errors/* IllegalOperationError */.js(`Expected a pyramid in ${this.fullName}`);
-
- // skip if the capacity is zero
- if(capacity == 0) {
- const encodedKeypoints = this._encodeZeroKeypoints(gpu, tex[4]);
- const encoderLength = encodedKeypoints.width;
- this.output().swrite(encodedKeypoints, 0, 0, encoderLength);
- return;
- }
-
- // FAST
- gpu.programs.keypoints.fast9_16.outputs(width, height, tex[0], tex[1]);
- gpu.programs.keypoints.nonmaxSpace.outputs(width, height, tex[2]);
- let corners = tex[1].clear();
- let numPasses = Math.max(1, Math.min(levels, (globals.PYRAMID_MAX_LEVELS / lodStep) | 0));
- for(let lod = lodStep * (numPasses - 1); numPasses-- > 0; lod -= lodStep) {
- corners = gpu.programs.keypoints.fast9_16(corners, image, lod, threshold);
- //corners = gpu.programs.keypoints.nonmaxSpace(corners); // see below*
- }
-
- // Same-scale non-maximum suppression
- // *nicer results inside the loop; faster outside
- // Hard to notice a difference when using FAST
- corners = gpu.programs.keypoints.nonmaxSpace(corners);
-
- // Multi-scale non-maximum suppression
- // (doesn't seem to remove many keypoints)
- if(levels > 1) {
- corners = (gpu.programs.keypoints.nonmaxScaleSimple
- .outputs(width, height, tex[1])
- )(corners, image, lodStep);
- }
-
- // encode keypoints
- let encodedKeypoints = this._encodeKeypoints(gpu, corners, tex[3]);
- const encoderLength = encodedKeypoints.width;
-
- // scale refinement
- if(levels > 1) {
- encodedKeypoints = (gpu.programs.keypoints.refineScaleFAST916
- .outputs(encoderLength, encoderLength, tex[4])
- )(image, lodStep, encodedKeypoints, 0, 0, encoderLength, threshold);
- }
-
- // done!
- this.output().swrite(encodedKeypoints, 0, 0, encoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/detectors/harris.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * harris.js
- * Harris corner detector
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
- /** Window size helper */
- const HARRIS = Object.freeze({
- 1: 'harris1',
- 3: 'harris3',
- 5: 'harris5',
- 7: 'harris7',
- });
-
- /**
- * Harris corner detector
- */
- class SpeedyPipelineNodeHarrisKeypointDetector extends SpeedyPipelineNodeMultiscaleKeypointDetector
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 6, [
- InputPort().expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints),
- ]);
-
- /** @type {SpeedySize} neighborhood size */
- this._windowSize = new SpeedySize(3, 3);
-
- /** @type {number} min corner quality in [0,1] */
- this._quality = 0.1;
- }
-
- /**
- * Minimum corner quality in [0,1] - this is a fraction of
- * the largest min. eigenvalue of the autocorrelation matrix
- * over the entire image
- * @returns {number}
- */
- get quality()
- {
- return this._quality;
- }
-
- /**
- * Minimum corner quality in [0,1]
- * @param {number} quality
- */
- set quality(quality)
- {
- this._quality = Math.max(0.0, Math.min(+quality, 1.0));
- }
-
- /**
- * Neighborhood size
- * @returns {SpeedySize}
- */
- get windowSize()
- {
- return this._windowSize;
- }
-
- /**
- * Neighborhood size
- * @param {SpeedySize} windowSize
- */
- set windowSize(windowSize)
- {
- const d = windowSize.width;
- if(!((d == windowSize.height) && (d == 1 || d == 3 || d == 5 || d == 7)))
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid window: ${windowSize}. Acceptable sizes: 1x1, 3x3, 5x5, 7x7`);
-
- this._windowSize = windowSize;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input().read() );
- const width = image.width, height = image.height;
- const capacity = this._capacity;
- const quality = this._quality;
- const windowSize = this._windowSize.width;
- const levels = this.levels;
- const lodStep = Math.log2(this.scaleFactor);
- const intFactor = levels > 1 ? this.scaleFactor : 1;
- const harris = gpu.programs.keypoints[HARRIS[windowSize]];
- const tex = this._tex;
-
- // validate pyramid
- if(!(levels == 1 || image.hasMipmaps()))
- throw new utils_errors/* IllegalOperationError */.js(`Expected a pyramid in ${this.fullName}`);
-
- // skip if the capacity is zero
- if(capacity == 0) {
- const encodedKeypoints = this._encodeZeroKeypoints(gpu, tex[5]);
- const encoderLength = encodedKeypoints.width;
- this.output().swrite(encodedKeypoints, 0, 0, encoderLength);
- return;
- }
-
- // compute corner response map
- harris.outputs(width, height, tex[0], tex[1]);
- gpu.programs.utils.sobelDerivatives.outputs(width, height, tex[2]);
- gpu.programs.keypoints.nonmaxSpace.outputs(width, height, tex[3]);
- let corners = tex[1].clear();
- let numPasses = Math.max(1, Math.min(levels, (globals.PYRAMID_MAX_LEVELS / lodStep) | 0));
- for(let lod = lodStep * (numPasses - 1); numPasses-- > 0; lod -= lodStep) {
- const gaussian = utils/* Utils.gaussianKernel */.c.gaussianKernel(intFactor * (1 + lod), windowSize);
- const derivatives = gpu.programs.utils.sobelDerivatives(image, lod);
- corners = harris(corners, image, derivatives, lod, lodStep, gaussian);
- corners = gpu.programs.keypoints.nonmaxSpace(corners); // see below*
- }
-
- // Same-scale non-maximum suppression
- // *performs better inside the loop
- //corners = gpu.programs.keypoints.nonmaxSpace(corners);
-
- // Multi-scale non-maximum suppression
- // (doesn't seem to remove many keypoints)
- if(levels > 1) {
- const laplacian = (gpu.programs.keypoints.laplacian
- .outputs(width, height, tex[0])
- )(corners, image, lodStep, 0);
-
- corners = (gpu.programs.keypoints.nonmaxScale
- .outputs(width, height, tex[2])
- )(corners, image, laplacian, lodStep);
- }
-
- // find the maximum corner response over the entire image
- gpu.programs.keypoints.harrisScoreFindMax.outputs(width, height, tex[0], tex[1]);
- numPasses = Math.ceil(Math.log2(Math.max(width, height)));
- let maxScore = corners;
- for(let j = 0; j < numPasses; j++)
- maxScore = gpu.programs.keypoints.harrisScoreFindMax(maxScore, j);
-
- // discard corners below a quality level
- corners = (gpu.programs.keypoints.harrisScoreCutoff
- .outputs(width, height, maxScore == tex[0] ? tex[1] : tex[0])
- )(corners, maxScore, quality);
-
- // encode keypoints
- let encodedKeypoints = this._encodeKeypoints(gpu, corners, tex[4]);
- const encoderLength = encodedKeypoints.width;
-
- // scale refinement
- if(levels > 1) {
- encodedKeypoints = (gpu.programs.keypoints.refineScaleLoG
- .outputs(encoderLength, encoderLength, tex[5])
- )(image, lodStep, encodedKeypoints, 0, 0, encoderLength);
- }
-
- // done!
- this.output().swrite(encodedKeypoints, 0, 0, encoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/descriptors/descriptor.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * descriptor.js
- * Abstract keypoint descriptor
- */
-
-
-
-
-
-
-
-
- /**
- * Abstract keypoint descriptor
- * @abstract
- */
- class SpeedyPipelineNodeKeypointDescriptor extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- * @param {number} [texCount] number of work textures
- * @param {SpeedyPipelinePortBuilder[]} [portBuilders] port builders
- */
- constructor(name = undefined, texCount = 0, portBuilders = undefined)
- {
- super(name, texCount + 1, portBuilders);
- }
-
- /**
- *
- * Allocate space for keypoint descriptors
- * @param {SpeedyGPU} gpu
- * @param {number} inputDescriptorSize should be 0
- * @param {number} inputExtraSize must be non-negative
- * @param {number} outputDescriptorSize in bytes, must be a multiple of 4
- * @param {number} outputExtraSize must be inputExtraSize
- * @param {SpeedyTexture} inputEncodedKeypoints input with no descriptors
- * @returns {SpeedyDrawableTexture} encodedKeypoints
- */
- _allocateDescriptors(gpu, inputDescriptorSize, inputExtraSize, outputDescriptorSize, outputExtraSize, inputEncodedKeypoints)
- {
- utils/* Utils.assert */.c.assert(inputDescriptorSize >= 0 && inputExtraSize >= 0);
- utils/* Utils.assert */.c.assert(outputDescriptorSize >= 0 && outputDescriptorSize % 4 === 0 && outputExtraSize === inputExtraSize);
-
- const inputEncoderLength = inputEncodedKeypoints.width;
- const inputEncoderCapacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(inputDescriptorSize, inputExtraSize, inputEncoderLength);
- const outputEncoderCapacity = inputEncoderCapacity;
- const outputEncoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(outputEncoderCapacity, outputDescriptorSize, outputExtraSize);
-
- const tex = this._tex[this._tex.length - 1];
- return (gpu.programs.keypoints.allocateDescriptors
- .outputs(outputEncoderLength, outputEncoderLength, tex)
- )(inputEncodedKeypoints, inputDescriptorSize, inputExtraSize, inputEncoderLength, outputDescriptorSize, outputExtraSize, outputEncoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/descriptors/orb.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * orb.js
- * ORB descriptors
- */
-
-
-
-
-
-
-
-
-
-
-
-
- // Constants
- const DESCRIPTOR_SIZE = 32; // 256 bits
-
- /**
- * ORB descriptors
- */
- class SpeedyPipelineNodeORBKeypointDescriptor extends SpeedyPipelineNodeKeypointDescriptor
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 3, [
- InputPort('image').expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- InputPort('keypoints').expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints),
- ]);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('keypoints').read() );
- const image = ( /** @type {SpeedyPipelineMessageWithImage} */ ( this.input('image').read() ) ).image;
- const tex = this._tex;
- const outputTexture = this._tex[2];
-
- // compute orientation
- const capacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength);
- const orientationEncoderLength = Math.max(1, Math.ceil(Math.sqrt(capacity))); // 1 pixel per keypoint
- const encodedOrientations = (gpu.programs.keypoints.orbOrientation
- .outputs(orientationEncoderLength, orientationEncoderLength, tex[0])
- )(image, encodedKeypoints, descriptorSize, extraSize, encoderLength);
- const orientedKeypoints = (gpu.programs.keypoints.transferOrientation
- .outputs(encoderLength, encoderLength, tex[1])
- )(encodedOrientations, encodedKeypoints, descriptorSize, extraSize, encoderLength);
-
- // allocate space
- const encodedKps = this._allocateDescriptors(gpu, descriptorSize, extraSize, DESCRIPTOR_SIZE, extraSize, orientedKeypoints);
- const newEncoderLength = encodedKps.width;
-
- // compute descriptors (it's a good idea to blur the image)
- const describedKeypoints = (gpu.programs.keypoints.orbDescriptor
- .outputs(newEncoderLength, newEncoderLength, outputTexture)
- )(image, encodedKps, extraSize, newEncoderLength);
-
- // done!
- this.output().swrite(describedKeypoints, DESCRIPTOR_SIZE, extraSize, newEncoderLength);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/trackers/lk.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * lk.js
- * LK optical-flow
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- // Constants
- const DEFAULT_WINDOW_SIZE = new SpeedySize(11, 11); // nice on mobile?
- const DEFAULT_DEPTH = Math.min(3, globals.PYRAMID_MAX_LEVELS);
- const DEFAULT_NUMBER_OF_ITERATIONS = 30;
- const DEFAULT_DISCARD_THRESHOLD = 0.0001;
- const DEFAULT_EPSILON = 0.01;
- const LK_PROGRAM = {
- 3: 'lk3',
- 5: 'lk5',
- 7: 'lk7',
- 9: 'lk9',
- 11: 'lk11',
- 13: 'lk13',
- 15: 'lk15',
- 17: 'lk17',
- 19: 'lk19',
- 21: 'lk21',
- };
-
-
- /**
- * LK optical-flow
- */
- class SpeedyPipelineNodeLKKeypointTracker extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 3, [
- InputPort('previousImage').expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- InputPort('nextImage').expects(SpeedyPipelineMessageType.Image).satisfying(
- ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) =>
- msg.format === types/* ImageFormat.GREY */.D3.GREY
- ),
- InputPort('previousKeypoints').expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort('flow').expects(SpeedyPipelineMessageType.Vector2),
- ]);
-
- /** @type {SpeedySize} window size */
- this._windowSize = DEFAULT_WINDOW_SIZE;
-
- /** @type {number} number of pyramid levels to use */
- this._levels = DEFAULT_DEPTH;
-
- /** @type {number} minimum acceptable corner response */
- this._discardThreshold = DEFAULT_DISCARD_THRESHOLD;
-
- /** @type {number} number of iterations per pyramid level (termination criteria) */
- this._numberOfIterations = DEFAULT_NUMBER_OF_ITERATIONS;
-
- /** @type {number} minimum increment per iteration (termination criteria) */
- this._epsilon = DEFAULT_EPSILON;
- }
-
- /**
- * Window size (use odd numbers)
- * @returns {SpeedySize}
- */
- get windowSize()
- {
- return this._windowSize;
- }
-
- /**
- * Window size (use odd numbers)
- * @param {SpeedySize} windowSize must be a square window
- */
- set windowSize(windowSize)
- {
- if(windowSize.width != windowSize.height) {
- throw new utils_errors/* NotSupportedError */.B8(`LK: window ${this._windowSize.toString()} is not square!`);
- }
- else if(!Object.prototype.hasOwnProperty.call(LK_PROGRAM, windowSize.width)) {
- const SUPPORTED_WINDOWS = Object.keys(LK_PROGRAM).sort((a,b) => a-b).map(k => k+'x'+k).join(', ');
- throw new utils_errors/* NotSupportedError */.B8(`LK: window of size ${this._windowSize.toString()} is not supported! Supported sizes: ${SUPPORTED_WINDOWS}`);
- }
-
- this._windowSize = windowSize;
- }
-
- /**
- * Number of pyramid levels to use
- * @returns {number}
- */
- get levels()
- {
- return this._levels;
- }
-
- /**
- * Number of pyramid levels to use
- * @param {number} levels
- */
- set levels(levels)
- {
- utils/* Utils.assert */.c.assert(levels >= 1 && levels <= globals.PYRAMID_MAX_LEVELS);
- this._levels = levels | 0;
- }
-
- /**
- * Get the discard threshold, used to discard "bad" keypoints
- * @returns {number}
- */
- get discardThreshold()
- {
- return this._discardThreshold;
- }
-
- /**
- * Set the discard threshold, used to discard "bad" keypoints
- * @param {number} value typically 10^(-4) - increase to discard more
- */
- set discardThreshold(value)
- {
- utils/* Utils.assert */.c.assert(value >= 0);
- this._discardThreshold = +value;
- }
-
- /**
- * Get the maximum number of iterations of the pyramidal LK algorithm
- * @returns {number}
- */
- get numberOfIterations()
- {
- return this._numberOfIterations;
- }
-
- /**
- * Set the maximum number of iterations of the pyramidal LK algorithm
- * @param {number} value
- */
- set numberOfIterations(value)
- {
- utils/* Utils.assert */.c.assert(value >= 1);
- this._numberOfIterations = value | 0;
- }
-
- /**
- * Get the accuracy threshold, used to stop LK iterations
- * @returns {number}
- */
- get epsilon()
- {
- return this._epsilon;
- }
-
- /**
- * Get the accuracy threshold, used to stop LK iterations
- * @param {number} value typically 0.01
- */
- set epsilon(value)
- {
- utils/* Utils.assert */.c.assert(value >= 0);
- this._epsilon = +value;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('previousKeypoints').read() );
- const previousImage = ( /** @type {SpeedyPipelineMessageWithImage} */ ( this.input('previousImage').read() )).image;
- const nextImage = ( /** @type {SpeedyPipelineMessageWithImage} */ ( this.input('nextImage').read() )).image;
- const previousKeypoints = encodedKeypoints;
- const levels = this._levels;
- const windowSize = this._windowSize;
- const wsize = windowSize.width; // square window
- const numberOfIterations = this._numberOfIterations;
- const discardThreshold = this._discardThreshold;
- const epsilon = this._epsilon;
- const keypoints = gpu.programs.keypoints;
- const tex = this._tex;
-
- // do we need a pyramid?
- if(!(levels == 1 || (previousImage.hasMipmaps() && nextImage.hasMipmaps())))
- throw new utils_errors/* IllegalOperationError */.js(`LK: a pyramid is required if levels > 1`);
- else if(previousImage.width !== nextImage.width || previousImage.height !== nextImage.height)
- throw new utils_errors/* IllegalOperationError */.js(`LK: can't use input images of different size`);
-
- // select the appropriate program
- const lk = keypoints[LK_PROGRAM[wsize]];
-
- // find the dimensions of the flow texture (1 pixel per flow vector)
- const numKeypoints = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength);
- const lkEncoderLength = Math.max(1, Math.ceil(Math.sqrt(numKeypoints)));
- lk.outputs(lkEncoderLength, lkEncoderLength, tex[0], tex[1]);
-
- // compute optical-flow
- let flow = lk.clear();
- for(let lod = levels - 1; lod >= 0; lod--)
- flow = lk(flow, previousKeypoints, nextImage, previousImage, lod, levels, numberOfIterations, discardThreshold, epsilon, descriptorSize, extraSize, encoderLength);
-
- // transfer optical-flow to nextKeypoints
- keypoints.transferFlow.outputs(encoderLength, encoderLength, tex[2]);
- const nextKeypoints = keypoints.transferFlow(flow, previousKeypoints, descriptorSize, extraSize, encoderLength);
-
- // done!
- this.output().swrite(nextKeypoints, descriptorSize, extraSize, encoderLength);
- this.output('flow').swrite(flow);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/matchers/lsh-static-tables.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * lsh-static-tables.js
- * Static LSH tables
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Static LSH tables
- */
- class SpeedyPipelineNodeStaticLSHTables extends SpeedyPipelineSourceNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 2, [
- OutputPort().expects(SpeedyPipelineMessageType.LSHTables)
- ]);
-
- /** @type {SpeedyKeypoint[]} "training" keypoints */
- this._keypoints = [];
-
- /** @type {SpeedyKeypoint[]} internal copy of the "training" keypoints */
- this._keypointsCopy = [];
-
- /** @type {number} number of tables in the LSH data structure */
- this._numberOfTables = LSH_DEFAULT_NUMBER_OF_TABLES;
-
- /** @type {number} number of bits of a hash */
- this._hashSize = LSH_DEFAULT_HASH_SIZE;
-
- /** @type {SpeedyLSH|null} LSH data structure */
- this._lsh = null;
- }
-
- /**
- * "Training" keypoints
- * @returns {SpeedyKeypoint[]}
- */
- get keypoints()
- {
- return this._keypoints;
- }
-
- /**
- * "Training" keypoints
- * @param {SpeedyKeypoint[]} keypoints
- */
- set keypoints(keypoints)
- {
- if(!Array.isArray(keypoints) || keypoints.find(keypoint => !(keypoint instanceof SpeedyKeypoint)))
- throw new utils_errors/* IllegalArgumentError */.mG(`Static LSH tables: an invalid set of keypoints has been provided`);
-
- if(this._keypoints !== keypoints) {
- this._keypoints = keypoints; // update internal pointer
- this._keypointsCopy = keypoints.slice(0); // clone the array, so it won't be modified externally
- this._lsh = null; // (re)train the model
- }
- }
-
- /**
- * Number of tables in the LSH data structure
- * @returns {number}
- */
- get numberOfTables()
- {
- return this._numberOfTables;
- }
-
- /**
- * Number of tables in the LSH data structure
- * @param {number} n
- */
- set numberOfTables(n)
- {
- if(!LSH_ACCEPTABLE_NUMBER_OF_TABLES.includes(n))
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid number of tables: ${n}. Acceptable values: ${LSH_ACCEPTABLE_NUMBER_OF_TABLES.join(', ')}`);
-
- if(n !== this._numberOfTables) {
- this._numberOfTables = n | 0;
- this._lsh = null; // need to retrain the model
- }
- }
-
- /**
- * Number of bits of a hash
- * @returns {number}
- */
- get hashSize()
- {
- return this._hashSize;
- }
-
- /**
- * Number of bits of a hash
- * @param {number} h
- */
- set hashSize(h)
- {
- if(!LSH_ACCEPTABLE_HASH_SIZES.includes(h))
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid hash size: ${h}. Acceptable values: ${LSH_ACCEPTABLE_HASH_SIZES.join(', ')}`);
-
- if(h !== this._hashSize) {
- this._hashSize = h | 0;
- this._lsh = null; // need to retrain the model
- }
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- // Need to train the model?
- if(this._lsh == null) {
- // internal work textures are only available after initialization,
- // i.e., after calling this._init()
- this._lsh = this._train();
- }
-
- // Pass it forward
- this.output().swrite(this._lsh);
- }
-
- /**
- * Train the model
- * @returns {SpeedyLSH}
- */
- _train()
- {
- const keypoints = this._keypointsCopy;
- const numberOfTables = this._numberOfTables;
- const hashSize = this._hashSize;
-
- if(keypoints.find(keypoint => keypoint.descriptor == null))
- throw new utils_errors/* IllegalOperationError */.js(`Static LSH tables: can't train the model with no keypoint descriptors!`);
-
- const descriptors = keypoints.map(keypoint => keypoint.descriptor.data);
- const lshTables = this._tex[0];
- const descriptorDB = this._tex[1];
-
- return new SpeedyLSH(lshTables, descriptorDB, descriptors, numberOfTables, hashSize);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/matchers/lsh-knn.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * lsh-knn.js
- * K approximate nearest neighbors matcher
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /** @typedef {'fastest' | 'default' | 'demanding'} LSHKNNQualityLevel quality of the approximate matching */
-
- /** @type {number} how many neighbors to search for, by default */
- const DEFAULT_K = 1;
-
- /** @type {LSHKNNQualityLevel} default quality level */
- const DEFAULT_QUALITY = 'default';
-
- /** @type {{ [key in LSHKNNQualityLevel]: number }} maps quality level to bit swaps */
- const NUMBER_OF_BIT_SWAPS = {
- 'fastest': 0,
- 'default': 1,
- 'demanding': 2,
- };
-
- /** @type {object} program names indexed as LSH_KNN[descriptorSize][hashSize][level] */
- const LSH_KNN = (fd => LSH_ACCEPTABLE_DESCRIPTOR_SIZES.reduce((o,d) => ((o[d] = fd(d)), o), {}))(
- d => ((fh => LSH_ACCEPTABLE_HASH_SIZES.reduce((o,h) => ((o[h] = fh(h)), o), {}))(
- h => ((fl => [0,1,2].reduce((o,l) => ((o[l] = fl(l)), o), {}))(
- l => `lshKnn${d}h${h}lv${l}`
- ))
- ))
- );
-
-
-
- /**
- * K approximate nearest neighbors matcher
- */
- class SpeedyPipelineNodeLSHKNNKeypointMatcher extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 6, [
- InputPort('keypoints').expects(SpeedyPipelineMessageType.Keypoints).satisfying(
- ( /** @type {SpeedyPipelineMessageWithKeypoints} */ msg ) =>
- msg.descriptorSize > 0
- ),
- InputPort('lsh').expects(SpeedyPipelineMessageType.LSHTables),
- OutputPort().expects(SpeedyPipelineMessageType.KeypointMatches),
- ]);
-
- /** @type {number} how many neighbors do you want? */
- this._k = DEFAULT_K;
-
- /** @type {LSHKNNQualityLevel} quality of the matching */
- this._quality = DEFAULT_QUALITY;
- }
-
- /**
- * How many neighbors do you want?
- * @returns {number}
- */
- get k()
- {
- return this._k;
- }
-
- /**
- * How many neighbors do you want?
- * @param {number} k number of neighbors
- */
- set k(k)
- {
- this._k = Math.max(1, k | 0);
- }
-
- /**
- * Quality of the matching
- * @returns {LSHKNNQualityLevel}
- */
- get quality()
- {
- return this._quality;
- }
-
- /**
- * Quality of the matching
- * @param {LSHKNNQualityLevel} quality
- */
- set quality(quality)
- {
- if(!Object.prototype.hasOwnProperty.call(NUMBER_OF_BIT_SWAPS, quality))
- throw new utils_errors/* IllegalArgumentError */.mG(`Invalid quality level: "${quality}"`);
-
- this._quality = quality;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('keypoints').read() );
- /** @type {SpeedyLSH} */ const lsh = this.input('lsh').read().lsh;
- const keypoints = gpu.programs.keypoints;
- const tables = lsh.tables;
- const descriptorDB = lsh.descriptorDB;
- const tablesStride = tables.width;
- const descriptorDBStride = descriptorDB.width;
- const tableCount = lsh.tableCount;
- const hashSize = lsh.hashSize;
- const bucketCapacity = lsh.bucketCapacity;
- const bucketsPerTable = lsh.bucketsPerTable;
- const sequences = lsh.sequences;
- const candidatesA = this._tex[0];
- const candidatesB = this._tex[1];
- const candidatesC = this._tex[2];
- const filters = this._tex[3];
- const transferA = this._tex[4];
- const transferB = this._tex[5];
- const level = NUMBER_OF_BIT_SWAPS[this._quality];
- const matchesPerKeypoint = this._k;
-
- // validate parameters
- if(descriptorSize !== lsh.descriptorSize)
- throw new utils_errors/* IllegalArgumentError */.mG(`Can't match different types of descriptors in ${this.fullName}`);
-
- utils/* Utils.assert */.c.assert(LSH_KNN[descriptorSize] != undefined);
- utils/* Utils.assert */.c.assert(LSH_KNN[descriptorSize][hashSize] != undefined);
- utils/* Utils.assert */.c.assert(LSH_KNN[descriptorSize][hashSize][level] != undefined);
-
- // configure the output texture
- const capacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength);
- const matcherLength = Math.max(1, Math.ceil(Math.sqrt(capacity * matchesPerKeypoint)));
- let encodedMatches = transferB;
- keypoints.lshKnnTransfer.outputs(matcherLength, matcherLength, transferA, transferB);
-
- // prepare the LSH matching
- const kthMatcherLength = Math.max(1, Math.ceil(Math.sqrt(capacity)));
- keypoints.lshKnnInitCandidates.outputs(kthMatcherLength, kthMatcherLength, candidatesA);
- keypoints.lshKnnInitFilters.outputs(kthMatcherLength, kthMatcherLength, filters);
-
- const lshKnn = keypoints[LSH_KNN[descriptorSize][hashSize][level]];
- lshKnn.outputs(kthMatcherLength, kthMatcherLength, candidatesB, candidatesC);
- lshKnn.setUBO('LSHSequences', sequences);
-
- // match keypoints
- encodedMatches.clear();
- keypoints.lshKnnInitFilters();
- for(let i = 0; i < matchesPerKeypoint; i++) {
- // find the (i+1)-th best match
- let candidates = keypoints.lshKnnInitCandidates();
- for(let tableIndex = 0; tableIndex < tableCount; tableIndex++) {
- candidates = lshKnn(candidates, filters, kthMatcherLength, tables, descriptorDB, tableIndex, bucketCapacity, bucketsPerTable, tablesStride, descriptorDBStride, encodedKeypoints, descriptorSize, extraSize, encoderLength);
- gpu.gl.flush();
- }
- candidates.copyTo(filters);
-
- // transfer matches to an encoded matches texture
- encodedMatches = keypoints.lshKnnTransfer(encodedMatches, candidates, matchesPerKeypoint, i);
- }
-
- // done
- this.output().swrite(encodedMatches, matchesPerKeypoint);
-
- /*
- // debug
- let data = filters.inspect32(gpu), debug = [];
- for(let i = 0; i < data.length; i++) {
- const bits = MATCH_INDEX_BITS;
- const mask = (1 << bits) - 1;
- const u32 = data[i];
- const index = u32 & mask, distance = u32 >>> bits;
- //debug.push('|'+[ u32 ].toString());
- debug.push('|'+[ index, distance ].toString());
- }
- console.log(debug.join(','));
- */
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/matchers/bf-knn.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * bf-knn.js
- * Brute Force KNN Keypoint Matcher
- */
-
-
-
-
-
-
-
-
-
-
-
- /** @type {Object<number,string>} program name indexed by descriptor size */
- const PROGRAM_NAME = {
- 32: 'bfMatcher32',
- 64: 'bfMatcher64',
- };
-
- /**
- * Brute Force KNN Keypoint Matcher. Make sure to use a Keypoint Clipper before
- * invoking this (use a database of 50 keypoints or so - your mileage may vary)
- */
- class SpeedyPipelineNodeBruteForceKNNKeypointMatcher extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 6, [
- InputPort('keypoints').expects(SpeedyPipelineMessageType.Keypoints).satisfying(
- ( /** @type {SpeedyPipelineMessageWithKeypoints} */ msg ) =>
- msg.descriptorSize > 0
- ),
- InputPort('database').expects(SpeedyPipelineMessageType.Keypoints).satisfying(
- ( /** @type {SpeedyPipelineMessageWithKeypoints} */ msg ) =>
- msg.descriptorSize > 0
- ),
- OutputPort().expects(SpeedyPipelineMessageType.KeypointMatches),
- ]);
-
- /** @type {number} number of matches per keypoint (the "k" of knn) */
- this._matchesPerKeypoint = 1;
- }
-
- /**
- * Number of matches per keypoint
- * @returns {number}
- */
- get k()
- {
- return this._matchesPerKeypoint;
- }
-
- /**
- * Number of matches per keypoint
- * @param {number} value
- */
- set k(value)
- {
- this._matchesPerKeypoint = Math.max(1, value | 0);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('keypoints').read() );
- const database = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('database').read() );
- const candidatesA = this._tex[0];
- const candidatesB = this._tex[1];
- const candidatesC = this._tex[2];
- const encodedFiltersA = this._tex[3];
- const encodedMatchesA = this._tex[4];
- const encodedMatchesB = this._tex[5];
- const matchesPerKeypoint = this._matchesPerKeypoint;
- const keypoints = gpu.programs.keypoints;
-
- // validate parameters
- if(descriptorSize !== database.descriptorSize)
- throw new utils_errors/* IllegalArgumentError */.mG(`Incompatible descriptors in ${this.fullName}`);
- else if(!Object.prototype.hasOwnProperty.call(PROGRAM_NAME, descriptorSize))
- throw new utils_errors/* NotSupportedError */.B8(`Unsupported descriptor size (${descriptorSize}) in ${this.fullName}`);
-
- // prepare the brute force matching
- const bfMatcher = keypoints[PROGRAM_NAME[descriptorSize]];
- const capacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength);
- const dbCapacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(database.descriptorSize, database.extraSize, database.encoderLength);
- const numberOfKeypointsPerPass = bfMatcher.definedConstant('NUMBER_OF_KEYPOINTS_PER_PASS');
- const numberOfPasses = Math.ceil(dbCapacity / numberOfKeypointsPerPass);
- const partialMatcherLength = Math.max(1, Math.ceil(Math.sqrt(capacity)));
- const matcherLength = Math.max(1, Math.ceil(Math.sqrt(capacity * matchesPerKeypoint)));
- keypoints.bfMatcherTransfer.outputs(matcherLength, matcherLength, encodedMatchesA, encodedMatchesB);
- keypoints.bfMatcherInitCandidates.outputs(partialMatcherLength, partialMatcherLength, candidatesC);
- keypoints.bfMatcherInitFilters.outputs(partialMatcherLength, partialMatcherLength, encodedFiltersA);
- bfMatcher.outputs(partialMatcherLength, partialMatcherLength, candidatesA, candidatesB);
-
- // match keypoints
- let encodedMatches = encodedMatchesB.clear(); // will hold all best matches
- let encodedFilters = keypoints.bfMatcherInitFilters();
- for(let k = 0; k < matchesPerKeypoint; k++) {
- let encodedPartialMatches = keypoints.bfMatcherInitCandidates(); // hold the (k+1)-th best matches
-
- // find the (k+1)-th best match
- for(let passId = 0; passId < numberOfPasses; passId++) {
- encodedPartialMatches = bfMatcher(
- encodedPartialMatches, encodedFilters, partialMatcherLength,
- database.encodedKeypoints, database.descriptorSize, database.extraSize, database.encoderLength,
- encodedKeypoints, descriptorSize, extraSize, encoderLength,
- passId
- );
- gpu.gl.flush();
- }
- //gpu.gl.flush();
-
- // copy the (k+1)-th best match to the filter
- if(matchesPerKeypoint > 1)
- encodedPartialMatches.copyTo(encodedFilters);
-
- // aggregate matches
- encodedMatches = keypoints.bfMatcherTransfer(
- encodedMatches, encodedPartialMatches, matchesPerKeypoint, k
- );
- }
-
- // done!
- this.output().swrite(encodedMatches, matchesPerKeypoint);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/distance-filter.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * distance-filter.js
- * Given a set of pairs of keypoints, discard all pairs whose distance is
- * above a user-defined threshold. Useful for bidirectional optical-flow.
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Given a set of pairs of keypoints, discard all pairs whose distance is
- * above a user-defined threshold. Useful for bidirectional optical-flow.
- *
- * The pairs of keypoints are provided as two separate sets, "in" and
- * "reference". Keypoints that are kept will have their data extracted
- * from the "in" set.
- */
- class SpeedyPipelineNodeKeypointDistanceFilter extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort('in').expects(SpeedyPipelineMessageType.Keypoints),
- InputPort('reference').expects(SpeedyPipelineMessageType.Keypoints),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
-
- /** @type {number} maximum accepted distance */
- this._threshold = globals.MAX_TEXTURE_LENGTH + 1;
- }
-
- /**
- * Maximum accepted distance
- * @returns {number}
- */
- get threshold()
- {
- return this._threshold;
- }
-
- /**
- * Maximum accepted distance
- * @param {number} value
- */
- set threshold(value)
- {
- this._threshold = Math.max(0, +value);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const set0 = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('in').read() );
- const set1 = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('reference').read() );
- const threshold = this._threshold;
-
- // validate shapes
- if(set0.descriptorSize != set1.descriptorSize || set0.extraSize != set1.extraSize)
- throw new utils_errors/* IllegalOperationError */.js(`The distance filter requires two compatible shapes of keypoint streams`);
-
- // calculate the shape of the output
- const outputTexture = this._tex[0];
- const encoderLength = Math.max(set0.encoderLength, set1.encoderLength);
- const descriptorSize = set0.descriptorSize;
- const extraSize = set0.extraSize;
-
- // apply the distance filter
- (gpu.programs.keypoints.distanceFilter
- .outputs(encoderLength, encoderLength, outputTexture)
- )(set0.encodedKeypoints, set0.encoderLength, set1.encodedKeypoints, set1.encoderLength, descriptorSize, extraSize, encoderLength, threshold);
-
- // done!
- this.output().swrite(outputTexture, descriptorSize, extraSize, encoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/hamming-distance-filter.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * hamming-distance-filter.js
- * Given a set of pairs of keypoints, discard all pairs whose hamming
- * distance (of descriptor) is above a user-defined threshold
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /** @type {Object<number,string>} Program names */
- const hamming_distance_filter_PROGRAM_NAME = {
- 32: 'hammingDistanceFilter32',
- 64: 'hammingDistanceFilter64',
- };
-
-
- /**
- * Given a set of pairs of keypoints, discard all pairs whose hamming
- * distance (of descriptor) is above a user-defined threshold
- *
- * The pairs of keypoints are provided as two separate sets, "in" and
- * "reference". Keypoints that are kept will have their data extracted
- * from the "in" set.
- */
- class SpeedyPipelineNodeKeypointHammingDistanceFilter extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort('in').expects(SpeedyPipelineMessageType.Keypoints).satisfying(
- ( /** @type {SpeedyPipelineMessageWithKeypoints} */ msg ) =>
- msg.descriptorSize > 0
- ),
- InputPort('reference').expects(SpeedyPipelineMessageType.Keypoints).satisfying(
- ( /** @type {SpeedyPipelineMessageWithKeypoints} */ msg ) =>
- msg.descriptorSize > 0
- ),
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints)
- ]);
-
- /** @type {number} distance threshold, an integer */
- this._threshold = globals.MAX_DESCRIPTOR_SIZE * 8; // convert from bytes to bits
- }
-
- /**
- * Distance threshold, an integer
- * @returns {number}
- */
- get threshold()
- {
- return this._threshold;
- }
-
- /**
- * Distance threshold, an integer
- * @param {number} value
- */
- set threshold(value)
- {
- this._threshold = Math.max(0, value | 0);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const set0 = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('in').read() );
- const set1 = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('reference').read() );
- const threshold = this._threshold;
-
- // validate shapes
- if(set0.descriptorSize != set1.descriptorSize || set0.extraSize != set1.extraSize)
- throw new utils_errors/* IllegalOperationError */.js(`The Hamming distance filter requires two compatible shapes of keypoint streams`);
-
- // validate descriptor size
- if(!Object.prototype.hasOwnProperty.call(hamming_distance_filter_PROGRAM_NAME, set0.descriptorSize))
- throw new utils_errors/* NotSupportedError */.B8(`Hamming distance filter - invalid descriptor size: ${set0.descriptorSize}`);
-
- // calculate the shape of the output
- const outputTexture = this._tex[0];
- const encoderLength = Math.max(set0.encoderLength, set1.encoderLength);
- const descriptorSize = set0.descriptorSize;
- const extraSize = set0.extraSize;
-
- // apply the distance filter
- const program = hamming_distance_filter_PROGRAM_NAME[set0.descriptorSize];
- (gpu.programs.keypoints[program]
- .outputs(encoderLength, encoderLength, outputTexture)
- )(set0.encodedKeypoints, set0.encoderLength, set1.encodedKeypoints, set1.encoderLength, descriptorSize, extraSize, encoderLength, threshold);
-
- // done!
- this.output().swrite(outputTexture, descriptorSize, extraSize, encoderLength);
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/keypoints/portal.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * portal.js
- * Keypoint Portals
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * A sink of a Keypoint Portal
- * This is not a pipeline sink - it doesn't export any data!
- */
- class SpeedyPipelineNodeKeypointPortalSink extends SpeedyPipelineNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 1, [
- InputPort().expects(SpeedyPipelineMessageType.Keypoints),
- ]);
-
- /** @type {number} descriptor size, in bytes */
- this._descriptorSize = 0;
-
- /** @type {number} extra size, in bytes */
- this._extraSize = 0;
-
- /** @type {number} extra size */
- this._encoderLength = 0;
-
- /** @type {boolean} is this node initialized? */
- this._initialized = false;
- }
-
- /**
- * Encoded keypoints
- * @returns {SpeedyTexture}
- */
- get encodedKeypoints()
- {
- if(!this._initialized)
- throw new utils_errors/* IllegalOperationError */.js(`Portal error: ${this.fullName} holds no data`);
-
- return this._tex[0];
- }
-
- /**
- * Descriptor size, in bytes
- * @returns {number}
- */
- get descriptorSize()
- {
- if(!this._initialized)
- throw new utils_errors/* IllegalOperationError */.js(`Portal error: ${this.fullName} holds no data`);
-
- return this._descriptorSize;
- }
-
- /**
- * Extra size, in bytes
- * @returns {number}
- */
- get extraSize()
- {
- if(!this._initialized)
- throw new utils_errors/* IllegalOperationError */.js(`Portal error: ${this.fullName} holds no data`);
-
- return this._extraSize;
- }
-
- /**
- * Encoder length
- * @returns {number}
- */
- get encoderLength()
- {
- if(!this._initialized)
- throw new utils_errors/* IllegalOperationError */.js(`Portal error: ${this.fullName} holds no data`);
-
- return this._encoderLength;
- }
-
- /**
- * Initializes this node
- * @param {SpeedyGPU} gpu
- */
- init(gpu)
- {
- super.init(gpu);
-
- const encoderLength = SpeedyPipelineNodeKeypointDetector.encoderLength(0, 0, 0);
- this._tex[0].resize(encoderLength, encoderLength).clearToColor(1,1,1,1); // initial texture
- this._descriptorSize = this._extraSize = 0;
- this._encoderLength = encoderLength;
-
- this._initialized = true;
- }
-
- /**
- * Releases this node
- * @param {SpeedyGPU} gpu
- */
- release(gpu)
- {
- this._initialized = false;
- super.release(gpu);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input().read() );
- const tex = this._tex[0];
-
- // copy input
- tex.resize(encodedKeypoints.width, encodedKeypoints.height);
- encodedKeypoints.copyTo(tex);
- this._descriptorSize = descriptorSize;
- this._extraSize = extraSize;
- this._encoderLength = encoderLength;
- }
- }
-
-
-
- /**
- * A source of a Keypoint Portal
- */
- class SpeedyPipelineNodeKeypointPortalSource extends SpeedyPipelineSourceNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = undefined)
- {
- super(name, 0, [
- OutputPort().expects(SpeedyPipelineMessageType.Keypoints),
- ]);
-
- /** @type {SpeedyPipelineNodeKeypointPortalSink|null} portal sink */
- this._source = null;
- }
-
- /**
- * Data source
- * @returns {SpeedyPipelineNodeKeypointPortalSink|null}
- */
- get source()
- {
- return this._source;
- }
-
- /**
- * Data source
- * @param {SpeedyPipelineNodeKeypointPortalSink|null} node
- */
- set source(node)
- {
- if(node !== null && !(node instanceof SpeedyPipelineNodeKeypointPortalSink))
- throw new utils_errors/* IllegalArgumentError */.mG(`Incompatible source for ${this.fullName}`);
-
- this._source = node;
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- if(this._source == null)
- throw new utils_errors/* IllegalOperationError */.js(`${this.fullName} has no source`);
-
- this.output().swrite(this._source.encodedKeypoints, this._source.descriptorSize, this._source.extraSize, this._source.encoderLength);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/factories/keypoint-factory.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * keypoint-factory.js
- * Keypoint-related nodes
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- /**
- * Keypoint detectors
- */
- class SpeedyPipelineKeypointDetectorFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * FAST corner detector
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeFASTKeypointDetector}
- */
- static FAST(name = undefined)
- {
- return new SpeedyPipelineNodeFASTKeypointDetector(name);
- }
-
- /**
- * Harris corner detector
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeHarrisKeypointDetector}
- */
- static Harris(name = undefined)
- {
- return new SpeedyPipelineNodeHarrisKeypointDetector(name);
- }
- }
-
- /**
- * Keypoint descriptors
- */
- class SpeedyPipelineKeypointDescriptorFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * ORB descriptors
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeORBKeypointDescriptor}
- */
- static ORB(name = undefined)
- {
- return new SpeedyPipelineNodeORBKeypointDescriptor(name);
- }
- }
-
- /**
- * Keypoint trackers
- */
- class SpeedyPipelineKeypointTrackerFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * LK optical-flow
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeLKKeypointTracker}
- */
- static LK(name = undefined)
- {
- return new SpeedyPipelineNodeLKKeypointTracker(name);
- }
- }
-
- /**
- * Keypoint matchers
- */
- class SpeedyPipelineKeypointMatcherFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * Static LSH tables
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeStaticLSHTables}
- */
- static StaticLSHTables(name = undefined)
- {
- return new SpeedyPipelineNodeStaticLSHTables(name);
- }
-
- /**
- * LSH-based K-approximate nearest neighbors
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeLSHKNNKeypointMatcher}
- */
- static LSHKNN(name = undefined)
- {
- return new SpeedyPipelineNodeLSHKNNKeypointMatcher(name);
- }
-
- /**
- * Brute-force K-nearest neighbors keypoint matcher
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeBruteForceKNNKeypointMatcher}
- */
- static BFKNN(name = undefined)
- {
- return new SpeedyPipelineNodeBruteForceKNNKeypointMatcher(name);
- }
- }
-
- /**
- * Portal nodes
- */
- class SpeedyPipelineKeypointPortalFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * Create an image portal source
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeKeypointPortalSource}
- */
- static Source(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointPortalSource(name);
- }
-
- /**
- * Create an image portal sink
- * @param {string} [name] name of the node
- * @returns {SpeedyPipelineNodeKeypointPortalSink}
- */
- static Sink(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointPortalSink(name);
- }
- }
-
- /**
- * Keypoint-related nodes
- */
- class SpeedyPipelineKeypointFactory extends speedy_namespace/* SpeedyNamespace */.R
- {
- /**
- * Keypoint detectors
- * @returns {typeof SpeedyPipelineKeypointDetectorFactory}
- */
- static get Detector()
- {
- return SpeedyPipelineKeypointDetectorFactory;
- }
-
- /**
- * Keypoint descriptors
- * @returns {typeof SpeedyPipelineKeypointDescriptorFactory}
- */
- static get Descriptor()
- {
- return SpeedyPipelineKeypointDescriptorFactory;
- }
-
- /**
- * Keypoint trackers
- * @returns {typeof SpeedyPipelineKeypointTrackerFactory}
- */
- static get Tracker()
- {
- return SpeedyPipelineKeypointTrackerFactory;
- }
-
- /**
- * Keypoint matchers
- * @returns {typeof SpeedyPipelineKeypointMatcherFactory}
- */
- static get Matcher()
- {
- return SpeedyPipelineKeypointMatcherFactory;
- }
-
- /**
- * Keypoint Portals
- * @returns {typeof SpeedyPipelineKeypointPortalFactory}
- */
- static get Portal()
- {
- return SpeedyPipelineKeypointPortalFactory;
- }
-
- /**
- * Create a keypoint source
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointSource}
- */
- static Source(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointSource(name);
- }
-
- /**
- * Create a keypoint sink
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointSink}
- */
- static Sink(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointSink(name);
- }
-
- /**
- * Create a sink of tracked keypoints
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeTrackedKeypointSink}
- */
- static SinkOfTrackedKeypoints(name = undefined)
- {
- return new SpeedyPipelineNodeTrackedKeypointSink(name);
- }
-
- /**
- * Create a sink of matched keypoints
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeMatchedKeypointSink}
- */
- static SinkOfMatchedKeypoints(name = undefined)
- {
- return new SpeedyPipelineNodeMatchedKeypointSink(name);
- }
-
- /**
- * Keypoint clipper
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointClipper}
- */
- static Clipper(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointClipper(name);
- }
-
- /**
- * Border Clipper
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointBorderClipper}
- */
- static BorderClipper(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointBorderClipper(name);
- }
-
- /**
- * Create a keypoint buffer
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointBuffer}
- */
- static Buffer(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointBuffer(name);
- }
-
- /**
- * Create a keypoint mixer
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointMixer}
- */
- static Mixer(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointMixer(name);
- }
-
- /**
- * Create a keypoint shuffler
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointShuffler}
- */
- static Shuffler(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointShuffler(name);
- }
-
- /**
- * Create a keypoint multiplexer
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointMultiplexer}
- */
- static Multiplexer(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointMultiplexer(name);
- }
-
- /**
- * Create a keypoint transformer
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointTransformer}
- */
- static Transformer(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointTransformer(name);
- }
-
- /**
- * Create a subpixel refiner of keypoint locations
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeKeypointSubpixelRefiner}
- */
- static SubpixelRefiner(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointSubpixelRefiner(name);
- }
-
- /**
- * Distance filter
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeDistanceFilter}
- */
- static DistanceFilter(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointDistanceFilter(name);
- }
-
- /**
- * Hamming distance filter
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeHammingDistanceFilter}
- */
- static HammingDistanceFilter(name = undefined)
- {
- return new SpeedyPipelineNodeKeypointHammingDistanceFilter(name);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/pipeline/nodes/vector2/sink.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * sink.js
- * Gets keypoints out of the pipeline
- */
-
-
-
-
-
-
-
-
-
-
-
- // next power of 2
- const vector2_sink_nextPot = x => x > 1 ? 1 << Math.ceil(Math.log2(x)) : 1;
-
-
- /**
- * Gets 2D vectors out of the pipeline
- */
- class SpeedyPipelineNodeVector2Sink extends SpeedyPipelineSinkNode
- {
- /**
- * Constructor
- * @param {string} [name] name of the node
- */
- constructor(name = 'vec2')
- {
- super(name, 2, [
- InputPort().expects(SpeedyPipelineMessageType.Vector2)
- ]);
-
- /** @type {SpeedyVector2[]} 2D vectors (output) */
- this._vectors = [];
-
- /** @type {SpeedyTextureReader} texture reader */
- this._textureReader = new SpeedyTextureReader();
-
- /** @type {number} page flipping index */
- this._page = 0;
-
- /** @type {boolean} accelerate GPU-CPU transfers */
- this._turbo = false;
- }
-
- /**
- * Accelerate GPU-CPU transfers
- * @returns {boolean}
- */
- get turbo()
- {
- return this._turbo;
- }
-
- /**
- * Accelerate GPU-CPU transfers
- * @param {boolean} value
- */
- set turbo(value)
- {
- this._turbo = Boolean(value);
- }
-
- /**
- * Initializes this node
- * @param {SpeedyGPU} gpu
- */
- init(gpu)
- {
- super.init(gpu);
- this._textureReader.init(gpu);
- }
-
- /**
- * Releases this node
- * @param {SpeedyGPU} gpu
- */
- release(gpu)
- {
- this._textureReader.release(gpu);
- super.release(gpu);
- }
-
- /**
- * Export data from this node to the user
- * @returns {SpeedyPromise<SpeedyVector2[]>}
- */
- export()
- {
- return speedy_promise/* SpeedyPromise.resolve */.s.resolve(this._vectors);
- }
-
- /**
- * Run the specific task of this node
- * @param {SpeedyGPU} gpu
- * @returns {void|SpeedyPromise<void>}
- */
- _run(gpu)
- {
- const { vectors } = /** @type {SpeedyPipelineMessageWith2DVectors} */ ( this.input().read() );
- const useBufferedDownloads = this._turbo;
- const encoderLength = vectors.width;
-
- /*
-
- I have found experimentally that, in Firefox, readPixelsAsync()
- performs MUCH better if the width of the target texture is a power
- of two. I have no idea why this is the case, nor if it's related to
- some interaction with the GL drivers, somehow. This seems to make no
- difference on Chrome, however. In any case, let's convert the input
- texture to POT.
-
- */
- const encoderWidth = vector2_sink_nextPot(encoderLength);
- const encoderHeight = vector2_sink_nextPot(Math.ceil(encoderLength * encoderLength / encoderWidth));
- //const encoderHeight = (Math.ceil(encoderLength * encoderLength / encoderWidth));
-
- // copy the set of vectors to an internal texture
- const copiedTexture = this._tex[this._page];
- (gpu.programs.utils.copy2DVectors
- .outputs(encoderWidth, encoderHeight, copiedTexture)
- )(vectors);
-
- // flip page
- this._page = 1 - this._page;
-
- // download the internal texture
- return this._textureReader.readPixelsAsync(copiedTexture, 0, 0, copiedTexture.width, copiedTexture.height, useBufferedDownloads).then(pixels => {
- this._vectors = SpeedyPipelineNodeVector2Sink._decode(pixels, encoderWidth, encoderHeight);
- });
- }
-
- /**
- * Decode a sequence of vectors, given a flattened image of encoded pixels
- * @param {Uint8Array} pixels pixels in the [r,g,b,a,...] format
- * @param {number} encoderWidth
- * @param {number} encoderHeight
- * @returns {SpeedyVector2[]} vectors
- */
- static _decode(pixels, encoderWidth, encoderHeight)
- {
- const bytesPerVector = 4; // 1 pixel per vector
- const vectors = [];
- let hi = 0, lo = 0;
- let x = 0, y = 0;
-
- // how many bytes should we read?
- const e2 = encoderWidth * encoderHeight * bytesPerVector;
- const size = Math.min(pixels.length, e2);
-
- // for each encoded vector
- for(let i = 0; i < size; i += bytesPerVector) {
- // extract 16-bit words
- lo = (pixels[i+1] << 8) | pixels[i];
- hi = (pixels[i+3] << 8) | pixels[i+2];
-
- // the vector is "null": we have reached the end of the list
- if(lo == 0xFFFF && hi == 0xFFFF)
- break;
-
- // the vector must be discarded
- if(lo == 0xFF00 && hi == 0xFF00)
- continue;
-
- // decode floats
- x = utils/* Utils.decodeFloat16 */.c.decodeFloat16(lo);
- y = utils/* Utils.decodeFloat16 */.c.decodeFloat16(hi);
-
- // register vector
- vectors.push(new SpeedyVector2(x, y));
- }
-
- // done!
- return vectors;
- }
- }
- ;// CONCATENATED MODULE: ./src/core/pipeline/factories/vector2-factory.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * vector2-factory.js
- * 2D vectors
- */
-
-
-
-
- /**
- * 2D vectors
- */
- class SpeedyPipelineVector2Factory extends Function
- {
- /**
- * Constructor
- */
- constructor()
- {
- // This factory can be invoked as a function
- super('...args', 'return this._create(...args)');
- return this.bind(this);
- }
-
- /**
- * @private
- *
- * Create a 2D vector
- * @param {number} x x-coordinate
- * @param {number} y y-coordinate
- * @returns {SpeedyVector2}
- */
- _create(x, y)
- {
- return new SpeedyVector2(x, y);
- }
-
- /**
- * Create a Vector2 sink
- * @param {string} [name]
- * @returns {SpeedyPipelineNodeVector2Sink}
- */
- Sink(name = undefined)
- {
- return new SpeedyPipelineNodeVector2Sink(name);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/main.js
- /*
- * speedy-vision.js
- * GPU-accelerated Computer Vision for JavaScript
- * Copyright 2020-2023 Alexandre Martins <alemartf(at)gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * main.js
- * The entry point of the library
- */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- /* eslint-disable no-undef */
- /** @typedef {import('./core/speedy-matrix').SpeedyMatrix} SpeedyMatrix */
- /** @typedef {import('./core/speedy-matrix-expr').SpeedyMatrixExpr} SpeedyMatrixExpr */
- /** @typedef {import('./core/speedy-media').SpeedyMediaOptions} SpeedyMediaOptions */
- /** @typedef {import('./core/speedy-media-source').SpeedyMediaSourceNativeElement} SpeedyMediaSourceNativeElement */
-
-
- // Constants
-
- /** @type {SpeedyMatrixFactory} */
- const matrixFactory = new SpeedyMatrixFactory();
-
- /** @type {SpeedyPipelineVector2Factory} */
- const vector2Factory = new SpeedyPipelineVector2Factory();
-
-
-
- /**
- * GPU-accelerated Computer Vision for JavaScript
- */
- class Speedy
- {
- /**
- * Loads a SpeedyMedia object based on the provided source element
- * @param {SpeedyMediaSourceNativeElement} sourceElement The source media
- * @param {SpeedyMediaOptions} [options] Additional options for advanced configuration
- * @returns {SpeedyPromise<SpeedyMedia>}
- */
- static load(sourceElement, options = {})
- {
- return SpeedyMedia.load(sourceElement, options);
- }
-
- /**
- * Loads a camera stream
- * @param {number | MediaStreamConstraints} [widthOrConstraints] width of the stream or contraints object
- * @param {number} [height] height of the stream
- * @returns {SpeedyPromise<SpeedyMedia>}
- */
- static camera(widthOrConstraints = 640, height = 360)
- {
- const constraints = (typeof(widthOrConstraints) === 'object') ? widthOrConstraints : ({
- audio: false,
- video: {
- width: widthOrConstraints | 0,
- height: height | 0,
- },
- });
-
- return utils/* Utils.requestCameraStream */.c.requestCameraStream(constraints).then(
- video => SpeedyMedia.load(video)
- );
- }
-
- /**
- * Checks if Speedy can be executed in this machine & browser
- * @returns {boolean} true if Speedy can be executed in this machine & browser
- */
- static isSupported()
- {
- return (
- (typeof WebAssembly !== 'undefined') &&
- (typeof WebGL2RenderingContext !== 'undefined') &&
- (speedy_gl/* SpeedyGL.instance.gl */.$.instance.gl != null)
- );
- }
-
- /**
- * Create a 2D vector
- * @returns {SpeedyPipelineVector2Factory & ((x: number, y: number) => SpeedyVector2)}
- */
- static get Vector2()
- {
- return vector2Factory;
- }
-
- /**
- * Create a 2D point
- * @param {number} x
- * @param {number} y
- * @returns {SpeedyPoint2}
- */
- static Point2(x, y)
- {
- return new SpeedyPoint2(x, y);
- }
-
- /**
- * Create a new size object
- * @param {number} width
- * @param {number} height
- * @returns {SpeedySize}
- */
- static Size(width, height)
- {
- return new SpeedySize(width, height);
- }
-
- /**
- * Create a Matrix (entries are given in column-major format)
- * @returns {SpeedyMatrixFactory & ((rows: number, columns: number, entries: number[]) => SpeedyMatrix) & ((expr: SpeedyMatrixExpr) => SpeedyMatrix)}
- */
- static get Matrix()
- {
- return matrixFactory;
- }
-
- /**
- * Speedy Promises
- * @returns {typeof SpeedyPromise}
- */
- static get Promise()
- {
- return speedy_promise/* SpeedyPromise */.s;
- }
-
- /**
- * Create a new Pipeline
- * @returns {SpeedyPipeline}
- */
- static Pipeline()
- {
- return new SpeedyPipeline();
- }
-
- /**
- * Image-related nodes
- * @returns {typeof SpeedyPipelineImageFactory}
- */
- static get Image()
- {
- return SpeedyPipelineImageFactory;
- }
-
- /**
- * Image filters
- * @returns {typeof SpeedyPipelineFilterFactory}
- */
- static get Filter()
- {
- return SpeedyPipelineFilterFactory;
- }
-
- /**
- * Image transforms
- * @returns {typeof SpeedyPipelineTransformFactory}
- */
- static get Transform()
- {
- return SpeedyPipelineTransformFactory;
- }
-
- /**
- * Keypoint-related nodes
- * @returns {typeof SpeedyPipelineKeypointFactory}
- */
- static get Keypoint()
- {
- return SpeedyPipelineKeypointFactory;
- }
-
- /**
- * The version of the library
- * @returns {string} The version of the library
- */
- static get version()
- {
- if(false)
- {}
- else
- return "0.9.1-wip";
- }
-
- /**
- * The FPS rate
- * @returns {number} Frames per second (FPS)
- */
- static get fps()
- {
- return FPSCounter.instance.fps;
- }
-
- /**
- * Global settings
- * @returns {typeof Settings}
- */
- static get Settings()
- {
- return settings/* Settings */.Z;
- }
- }
-
- // Freeze the namespace
- Object.freeze(Speedy);
-
- // Display a notice
- utils/* Utils.log */.c.log(
- `Speedy Vision version ${Speedy.version}. ` +
- `GPU-accelerated Computer Vision for JavaScript by Alexandre Martins. ` +
- "https://github.com/alemart/speedy-vision"
- );
-
- // Big-endian machine? Currently untested.
- if(!globals.LITTLE_ENDIAN)
- utils/* Utils.warning */.c.warning('Running on a big-endian machine');
-
- })();
-
- __webpack_exports__ = __webpack_exports__["default"];
- /******/ return __webpack_exports__;
- /******/ })()
- ;
- });
-
- /***/ })
-
- /******/ });
- /************************************************************************/
- /******/ // The module cache
- /******/ var __webpack_module_cache__ = {};
- /******/
- /******/ // The require function
- /******/ function __webpack_require__(moduleId) {
- /******/ // Check if module is in cache
- /******/ var cachedModule = __webpack_module_cache__[moduleId];
- /******/ if (cachedModule !== undefined) {
- /******/ return cachedModule.exports;
- /******/ }
- /******/ // Create a new module (and put it into the cache)
- /******/ var module = __webpack_module_cache__[moduleId] = {
- /******/ // no module.id needed
- /******/ // no module.loaded needed
- /******/ exports: {}
- /******/ };
- /******/
- /******/ // Execute the module function
- /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
- /******/
- /******/ // Return the exports of the module
- /******/ return module.exports;
- /******/ }
- /******/
- /************************************************************************/
- /******/ /* webpack/runtime/compat get default export */
- /******/ (() => {
- /******/ // getDefaultExport function for compatibility with non-harmony modules
- /******/ __webpack_require__.n = (module) => {
- /******/ var getter = module && module.__esModule ?
- /******/ () => (module['default']) :
- /******/ () => (module);
- /******/ __webpack_require__.d(getter, { a: getter });
- /******/ return getter;
- /******/ };
- /******/ })();
- /******/
- /******/ /* webpack/runtime/define property getters */
- /******/ (() => {
- /******/ // define getter functions for harmony exports
- /******/ __webpack_require__.d = (exports, definition) => {
- /******/ for(var key in definition) {
- /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
- /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
- /******/ }
- /******/ }
- /******/ };
- /******/ })();
- /******/
- /******/ /* webpack/runtime/hasOwnProperty shorthand */
- /******/ (() => {
- /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
- /******/ })();
- /******/
- /************************************************************************/
- var __webpack_exports__ = {};
- // This entry need to be wrapped in an IIFE because it need to be in strict mode.
- (() => {
- "use strict";
-
- // EXPORTS
- __webpack_require__.d(__webpack_exports__, {
- "default": () => (/* binding */ Martins)
- });
-
- // EXTERNAL MODULE: ./node_modules/speedy-vision/dist/speedy-vision.js
- var speedy_vision = __webpack_require__(528);
- var speedy_vision_default = /*#__PURE__*/__webpack_require__.n(speedy_vision);
- ;// CONCATENATED MODULE: ./src/utils/errors.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * errors.ts
- * Error classes
- */
- /**
- * Generic error class
- */
- class MartinsError extends Error {
- /**
- * Constructor
- * @param message error message
- * @param cause optional error cause
- */
- constructor(message = '', cause = null) {
- super(`${message}\n${cause ? cause.toString() : ''}`);
- this.cause = cause;
- }
- /**
- * Error name
- */
- get name() {
- return this.constructor.name;
- }
- }
- /**
- * A method has received one or more illegal arguments
- */
- class IllegalArgumentError extends MartinsError {
- }
- /**
- * The method arguments are valid, but the method can't be called due to the
- * current state of the object
- */
- class IllegalOperationError extends MartinsError {
- }
- /**
- * The requested operation is not supported
- */
- class NotSupportedError extends MartinsError {
- }
- /**
- * Access denied
- */
- class AccessDeniedError extends MartinsError {
- }
- /**
- * Assertion error
- */
- class AssertionError extends MartinsError {
- }
- /**
- * Tracking error
- */
- class TrackingError extends MartinsError {
- }
- /**
- * Detection error
- */
- class DetectionError extends MartinsError {
- }
- /**
- * Training error
- */
- class TrainingError extends MartinsError {
- }
-
- ;// CONCATENATED MODULE: ./src/core/resolution.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * resolution.ts
- * Resolution utilities
- */
-
- /** Reference heights when in landscape mode, measured in pixels */
- const REFERENCE_HEIGHT = {
- 'xs': 120,
- 'xs+': 160,
- 'sm': 200,
- 'sm+': 240,
- 'md': 320,
- 'md+': 360,
- 'lg': 480,
- 'lg+': 600,
- };
- /**
- * Convert a resolution type to a (width, height) pair
- * @param resolution resolution type
- * @param aspectRatio desired width / height ratio
- * @returns size in pixels
- */
- function computeResolution(resolution, aspectRatio) {
- const referenceHeight = REFERENCE_HEIGHT[resolution];
- let width = 0, height = 0;
- if (aspectRatio >= 1) {
- // landscape
- height = referenceHeight;
- width = Math.round(height * aspectRatio);
- width -= width % 2;
- }
- else {
- // portrait
- width = referenceHeight;
- height = Math.round(width / aspectRatio);
- height -= height % 2;
- }
- return speedy_vision_default().Size(width, height);
- }
-
- ;// CONCATENATED MODULE: ./src/utils/utils.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * utils.ts
- * Generic utilities
- */
-
-
- /**
- * Generic utilities
- */
- class Utils {
- /**
- * Log a message
- * @param message
- * @param args optional additional messages
- */
- static log(message, ...args) {
- console.log('[martins-js]', message, ...args);
- }
- /**
- * Display a warning
- * @param message
- * @param args optional additional messages
- */
- static warning(message, ...args) {
- console.warn('[martins-js]', message, ...args);
- }
- /**
- * Display an error message
- * @param message
- * @param args optional additional messages
- */
- static error(message, ...args) {
- console.error('[martins-js]', message, ...args);
- }
- /**
- * Assertion
- * @param expr expression
- * @param errorMessage optional error message
- * @throws {AssertionError}
- */
- static assert(expr, errorMessage = '') {
- if (!expr)
- throw new AssertionError(errorMessage);
- }
- /**
- * Returns a range [0, 1, ..., n-1]
- * @param n non-negative integer
- * @returns range from 0 to n-1, inclusive
- */
- static range(n) {
- if ((n |= 0) < 0)
- throw new IllegalArgumentError();
- return Array.from({ length: n }, (_, i) => i);
- }
- /**
- * Convert a resolution type to a resolution measured in pixels
- * @param resolution resolution type
- * @param aspectRatio width / height ratio
- * @returns resolution measured in pixels
- */
- static resolution(resolution, aspectRatio) {
- return computeResolution(resolution, aspectRatio);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/utils/ar-events.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ar-events.ts
- * AR-related Events
- */
- /**
- * AR Event
- */
- class AREvent extends Event {
- /**
- * Constructor
- * @param type event type
- */
- constructor(type) {
- super(type);
- }
- /**
- * Event type
- */
- get type() {
- return super.type;
- }
- }
- /**
- * AR Event Target
- */
- class AREventTarget {
- /**
- * Constructor
- */
- constructor() {
- this._delegate = new EventTarget();
- }
- /**
- * Add event listener
- * @param type event type
- * @param callback
- */
- addEventListener(type, callback) {
- this._delegate.addEventListener(type, callback);
- }
- /**
- * Remove event listener
- * @param type event type
- * @param callback
- */
- removeEventListener(type, callback) {
- this._delegate.removeEventListener(type, callback);
- }
- /**
- * Synchronously trigger an event
- * @param event
- * @returns same value as a standard event target
- * @internal
- */
- dispatchEvent(event) {
- return this._delegate.dispatchEvent(event);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/hud.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * hud.ts
- * Heads Up Display
- */
-
-
- /**
- * Heads Up Display: an overlay displayed in front of the augmented scene
- */
- class HUD {
- /**
- * Constructor
- * @param parent parent of the hud container
- * @param hudContainer an existing hud container (optional)
- */
- constructor(parent, hudContainer) {
- this._container = hudContainer || this._createContainer(parent);
- this._ownContainer = (hudContainer == null);
- // validate
- if (this._container.parentElement !== parent)
- throw new IllegalArgumentError('The container of the HUD must be a direct child of the container of the viewport');
- // the HUD should be hidden initially
- if (!this._container.hidden)
- Utils.warning(`The container of the HUD should have the hidden attribute`);
- }
- /**
- * The container of the HUD
- */
- get container() {
- return this._container;
- }
- /**
- * Whether or not the HUD is visible
- */
- get visible() {
- return !this._container.hidden;
- }
- /**
- * Whether or not the HUD is visible
- */
- set visible(visible) {
- this._container.hidden = !visible;
- }
- /**
- * Initialize the HUD
- * @param zIndex the z-index of the container
- * @internal
- */
- _init(zIndex) {
- const container = this._container;
- container.style.position = 'absolute';
- container.style.left = container.style.top = '0px';
- container.style.right = container.style.bottom = '0px';
- container.style.padding = container.style.margin = '0px';
- container.style.zIndex = String(zIndex);
- container.style.userSelect = 'none';
- }
- /**
- * Release the HUD
- * @internal
- */
- _release() {
- if (this._ownContainer) {
- this._ownContainer = false;
- this._container.remove();
- }
- }
- /**
- * Create a HUD container as an immediate child of the input node
- * @param parent parent container
- * @returns HUD container
- */
- _createContainer(parent) {
- const node = document.createElement('div');
- node.hidden = true;
- parent.appendChild(node);
- return node;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/viewport.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * viewport.ts
- * Viewport
- */
-
-
-
-
-
- /** An event emitted by a Viewport */
- class ViewportEvent extends AREvent {
- }
- /** Default viewport constructor settings */
- const DEFAULT_VIEWPORT_SETTINGS = {
- container: null,
- hudContainer: null,
- resolution: 'lg',
- canvas: null,
- };
- /** Base z-index of the children of the viewport container */
- const BASE_ZINDEX = 0;
- /** Default viewport width, in pixels */
- const DEFAULT_VIEWPORT_WIDTH = 300;
- /** Default viewport height, in pixels */
- const DEFAULT_VIEWPORT_HEIGHT = 150;
- /**
- * Viewport
- */
- class BaseViewport extends AREventTarget {
- /**
- * Constructor
- * @param viewportSettings
- */
- constructor(viewportSettings) {
- super();
- // validate settings
- const settings = Object.assign({}, DEFAULT_VIEWPORT_SETTINGS, viewportSettings);
- if (settings.container == null)
- throw new IllegalArgumentError('Unspecified viewport container');
- // initialize attributes
- this._resolution = settings.resolution;
- this._container = settings.container;
- this._hud = new HUD(settings.container, settings.hudContainer);
- this._parentOfImportedForegroundCanvas = settings.canvas ? settings.canvas.parentNode : null;
- // create canvas elements
- const size = speedy_vision_default().Size(DEFAULT_VIEWPORT_WIDTH, DEFAULT_VIEWPORT_HEIGHT);
- this._backgroundCanvas = this._createBackgroundCanvas(this._container, size);
- this._foregroundCanvas = settings.canvas == null ?
- this._createForegroundCanvas(this._container, size) :
- this._foregroundCanvas = this._importForegroundCanvas(settings.canvas, this._container, size);
- }
- /**
- * Viewport container
- */
- get container() {
- return this._container;
- }
- /**
- * HUD
- */
- get hud() {
- return this._hud;
- }
- /**
- * Resolution of the virtual scene
- */
- get resolution() {
- return this._resolution;
- }
- /**
- * Size in pixels of the drawing buffer of the canvas
- * on which the virtual scene will be drawn
- */
- get virtualSize() {
- const aspectRatio = this._backgroundCanvas.width / this._backgroundCanvas.height;
- return Utils.resolution(this._resolution, aspectRatio);
- }
- /**
- * The canvas on which the virtual scene will be drawn
- */
- get canvas() {
- return this._foregroundCanvas;
- }
- /**
- * Background canvas
- * @internal
- */
- get _background() {
- return this._backgroundCanvas;
- }
- /**
- * Size of the drawing buffer of the background canvas, in pixels
- * @internal
- */
- get _size() {
- throw new IllegalOperationError();
- }
- /**
- * Initialize the viewport (when the session starts)
- * @internal
- */
- _init() {
- this._container.style.touchAction = 'none';
- this._hud._init(BASE_ZINDEX + 2);
- this._hud.visible = true;
- }
- /**
- * Release the viewport (when the session starts)
- * @internal
- */
- _release() {
- //this._hud.visible = false; // depends on the type of the viewport
- this._hud._release();
- this._restoreImportedForegroundCanvas();
- this._container.style.touchAction = 'auto';
- }
- /**
- * Function to be called when the viewport is resized
- * @internal
- */
- _onResize() {
- // Resize the drawing buffer of the foreground canvas, so that it
- // matches the desired resolution and the aspect ratio of the
- // background canvas
- const virtualSize = this.virtualSize;
- this._foregroundCanvas.width = virtualSize.width;
- this._foregroundCanvas.height = virtualSize.height;
- this._styleCanvas(this._foregroundCanvas, 'foreground');
- // dispatch event
- const event = new ViewportEvent('resize');
- this.dispatchEvent(event);
- }
- /**
- * Create the background canvas
- * @param parent parent container
- * @param size size of the drawing buffer
- * @returns a new canvas as a child of parent
- */
- _createBackgroundCanvas(parent, size) {
- const canvas = this._createCanvas(parent, size);
- return this._styleCanvas(canvas, 'background');
- }
- /**
- * Create the foreground canvas
- * @param parent parent container
- * @param size size of the drawing buffer
- * @returns a new canvas as a child of parent
- */
- _createForegroundCanvas(parent, size) {
- const canvas = this._createCanvas(parent, size);
- return this._styleCanvas(canvas, 'foreground');
- }
- /**
- * Create a canvas and attach it to another HTML element
- * @param parent parent container
- * @param size size of the drawing buffer
- * @returns a new canvas as a child of parent
- */
- _createCanvas(parent, size) {
- const canvas = document.createElement('canvas');
- canvas.width = size.width;
- canvas.height = size.height;
- parent.appendChild(canvas);
- return canvas;
- }
- /**
- * Add suitable CSS rules to a canvas
- * @param canvas
- * @param canvasType
- * @returns canvas
- */
- _styleCanvas(canvas, canvasType) {
- const offset = (canvasType == 'foreground') ? 1 : 0;
- const zIndex = BASE_ZINDEX + offset;
- canvas.setAttribute('style', [
- 'position: absolute',
- 'left: 0px',
- 'top: 0px',
- 'z-index: ' + String(zIndex),
- 'width: 100% !important',
- 'height: 100% !important',
- ].join('; '));
- return canvas;
- }
- /**
- * Import an existing foreground canvas to the viewport
- * @param canvas existing canvas
- * @param parent parent container
- * @param size size of the drawing buffer
- * @returns the input canvas
- */
- _importForegroundCanvas(canvas, parent, size) {
- if (!(canvas instanceof HTMLCanvasElement))
- throw new IllegalArgumentError(`Not a <canvas>: ${canvas}`);
- // borrow the canvas; add it as a child of the viewport container
- canvas.remove();
- parent.appendChild(canvas);
- canvas.width = size.width;
- canvas.height = size.height;
- canvas.dataset.cssText = canvas.style.cssText; // save CSS
- canvas.style.cssText = ''; // clear CSS
- this._styleCanvas(canvas, 'foreground');
- return canvas;
- }
- /**
- * Restore a previously imported foreground canvas to its original parent
- */
- _restoreImportedForegroundCanvas() {
- // not an imported canvas; nothing to do
- if (this._parentOfImportedForegroundCanvas == null)
- return;
- const canvas = this._foregroundCanvas;
- canvas.style.cssText = canvas.dataset.cssText || ''; // restore CSS
- canvas.remove();
- this._parentOfImportedForegroundCanvas.appendChild(canvas);
- }
- }
- /**
- * Viewport decorator
- */
- class ViewportDecorator extends AREventTarget {
- /**
- * Constructor
- * @param base to be decorated
- * @param getSize size getter
- */
- constructor(base, getSize) {
- super();
- this._base = base;
- this._getSize = getSize;
- }
- /**
- * Viewport container
- */
- get container() {
- return this._base.container;
- }
- /**
- * HUD
- */
- get hud() {
- return this._base.hud;
- }
- /**
- * Resolution of the virtual scene
- */
- get resolution() {
- return this._base.resolution;
- }
- /**
- * Size in pixels of the drawing buffer of the canvas
- * on which the virtual scene will be drawn
- */
- get virtualSize() {
- return this._base.virtualSize;
- }
- /**
- * The canvas on which the virtual scene will be drawn
- */
- get canvas() {
- return this._base.canvas;
- }
- /**
- * Background canvas
- * @internal
- */
- get _background() {
- return this._base._background;
- }
- /**
- * Size of the drawing buffer of the background canvas, in pixels
- * @internal
- */
- get _size() {
- return this._getSize();
- }
- /**
- * Initialize the viewport
- * @internal
- */
- _init() {
- this._base._init();
- }
- /**
- * Release the viewport
- * @internal
- */
- _release() {
- this._base._release();
- }
- /**
- * Function to be called when the viewport is resized
- * @internal
- */
- _onResize() {
- this._base._onResize();
- }
- /**
- * Add event listener
- * @param type event type
- * @param callback
- */
- addEventListener(type, callback) {
- this._base.addEventListener(type, callback);
- }
- /**
- * Remove event listener
- * @param type event type
- * @param callback
- */
- removeEventListener(type, callback) {
- this._base.removeEventListener(type, callback);
- }
- /**
- * Synchronously trigger an event
- * @param event
- * @returns same value as a standard event target
- * @internal
- */
- dispatchEvent(event) {
- return this._base.dispatchEvent(event);
- }
- }
- /**
- * A viewport that watches for page resizes
- */
- class ResizableViewport extends ViewportDecorator {
- /**
- * Constructor
- * @param base to be decorated
- * @param getSize size getter
- */
- constructor(base, getSize) {
- super(base, getSize);
- this._active = false;
- }
- /**
- * Initialize the viewport
- * @internal
- */
- _init() {
- super._init();
- this._active = true;
- // Configure the resize listener. We want the viewport
- // to adjust itself if the phone/screen is resized or
- // changes orientation
- let timeout = null;
- const onresize = () => {
- if (!this._active) {
- window.removeEventListener('resize', onresize);
- return;
- }
- if (timeout !== null)
- clearTimeout(timeout);
- timeout = setTimeout(() => {
- timeout = null;
- this._resize.call(this);
- this._onResize.call(this);
- }, 100);
- };
- window.addEventListener('resize', onresize);
- this._resize();
- this._onResize();
- }
- /**
- * Release the viewport
- * @internal
- */
- _release() {
- this._active = false;
- super._release();
- }
- }
- /**
- * Immersive viewport: it occupies the entire page
- */
- class ImmersiveViewport extends ResizableViewport {
- /**
- * Release the viewport
- * @internal
- */
- _release() {
- this.canvas.remove();
- this._background.remove();
- this.hud.visible = false;
- this.container.style.cssText = ''; // reset CSS
- super._release();
- }
- /**
- * Resize the immersive viewport, so that it occupies the entire page.
- * We respect the aspect ratio of the source media
- */
- _resize() {
- const { width, height } = this._size;
- const viewportSize = speedy_vision_default().Size(0, 0);
- const viewportAspectRatio = width / height;
- const windowSize = speedy_vision_default().Size(window.innerWidth, window.innerHeight);
- const windowAspectRatio = windowSize.width / windowSize.height;
- // figure out the viewport size
- if (viewportAspectRatio <= windowAspectRatio) {
- viewportSize.height = windowSize.height;
- viewportSize.width = (viewportSize.height * viewportAspectRatio) | 0;
- }
- else {
- viewportSize.width = windowSize.width;
- viewportSize.height = (viewportSize.width / viewportAspectRatio) | 0;
- }
- // position the viewport and set its size
- const container = this.container;
- container.style.position = 'fixed';
- container.style.left = `calc(50% - ${viewportSize.width >>> 1}px)`;
- container.style.top = `calc(50% - ${viewportSize.height >>> 1}px)`;
- container.style.zIndex = '1000000000'; // 1B //String(2147483647);
- container.style.width = viewportSize.width + 'px';
- container.style.height = viewportSize.height + 'px';
- container.style.backgroundColor = '#000';
- // set the size of the drawing buffer of the background canvas
- const backgroundCanvas = this._background;
- const backgroundCanvasAspectRatio = viewportAspectRatio;
- const referenceHeight = height;
- backgroundCanvas.height = referenceHeight;
- backgroundCanvas.width = (backgroundCanvas.height * backgroundCanvasAspectRatio) | 0;
- }
- }
- /**
- * Inline viewport: it follows the typical flow of a web page
- */
- class InlineViewport extends ResizableViewport {
- /**
- * Resize the inline viewport
- */
- _resize() {
- const { width, height } = this._size;
- this.container.style.position = 'relative';
- this.container.style.width = width + 'px';
- this.container.style.height = height + 'px';
- //this.container.style.display = 'inline-block';
- this._background.width = width;
- this._background.height = height;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/stats.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * stats.ts
- * Stats for performance measurements
- */
- /** update interval, given in seconds */
- const UPDATE_INTERVAL = 0.5;
- /**
- * Stats for performance measurements
- */
- class Stats {
- /**
- * Constructor
- */
- constructor() {
- this._timeOfLastUpdate = this._now();
- this._partialCycleCount = 0;
- this._cyclesPerSecond = 0;
- }
- /**
- * Update stats - call every frame
- */
- update() {
- const now = this._now();
- ++this._partialCycleCount;
- if (now >= this._timeOfLastUpdate + 1000 * UPDATE_INTERVAL) {
- this._cyclesPerSecond = this._partialCycleCount / UPDATE_INTERVAL;
- this._partialCycleCount = 0;
- this._timeOfLastUpdate = now;
- }
- }
- /**
- * Reset stats
- */
- reset() {
- this._timeOfLastUpdate = this._now();
- this._partialCycleCount = 0;
- this._cyclesPerSecond = 0;
- }
- /**
- * Number of cycles per second
- */
- get cyclesPerSecond() {
- return this._cyclesPerSecond;
- }
- /**
- * A measurement of time, in milliseconds
- * @returns time in ms
- */
- _now() {
- return performance.now();
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/stats-panel.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * stats-panel.ts
- * Stats panel used for development purposes
- */
-
-
- /** Update interval, in ms */
- const stats_panel_UPDATE_INTERVAL = 500;
- /** Icons for different power profiles */
- const POWER_ICON = Object.freeze({
- 'default': '',
- 'low-power': '<span style="color:#0f0">🔋</span>',
- 'high-performance': '<span style="color:#ff0">⚡</span>'
- });
- /**
- * Stats panel used for development purposes
- */
- class StatsPanel {
- /**
- * Constructor
- * @param parent parent element of the panel
- */
- constructor(parent) {
- this._container = this._createContainer(parent);
- this._lastUpdate = 0;
- }
- /**
- * Release the panel
- */
- release() {
- this._container.remove();
- }
- /**
- * A method to be called in the update loop
- * @param time current time in ms
- * @param trackers the trackers attached to the session
- * @param sources the sources of media linked to the session
- * @param gpu GPU cycles per second
- * @param fps frames per second
- */
- update(time, trackers, sources, gpu, fps) {
- if (time >= this._lastUpdate + stats_panel_UPDATE_INTERVAL) {
- this._lastUpdate = time;
- this._update(trackers, sources, fps, gpu);
- }
- }
- /**
- * Visibility of the panel
- */
- get visible() {
- return !this._container.hidden;
- }
- /**
- * Visibility of the panel
- */
- set visible(visible) {
- this._container.hidden = !visible;
- }
- /**
- * Update the contents of the panel
- * @param trackers the trackers attached to the session
- * @param sources the sources of media linked to the session
- * @param fps frames per second
- * @param gpu GPU cycles per second
- */
- _update(trackers, sources, fps, gpu) {
- const trackerStats = trackers.map(tracker => tracker._stats).join(', ');
- const sourceStats = sources.map(source => source._stats).join(', ');
- const param = {
- fps: this._colorize(fps),
- gpu: this._colorize(gpu),
- powerIcon: POWER_ICON[Settings.powerPreference]
- };
- this._container.textContent = (`MARTINS.js ${Martins.edition}
- Version ${Martins.version}
- FPS: [fps] | GPU: [gpu] [powerIcon]
- IN : ${sourceStats}
- OUT: ${trackerStats}`);
- const fn = (_, x) => param[x];
- this._container.innerHTML = this._container.innerHTML.replace(/\[(\w+)\]/g, fn);
- }
- /**
- * Colorize a frequency number
- * @param f frequency given in cycles per second
- * @returns colorized number (HTML)
- */
- _colorize(f) {
- const GREEN = '#0f0', YELLOW = '#ff0', RED = '#f33';
- const color3 = f >= 50 ? GREEN : (f >= 30 ? YELLOW : RED);
- const color2 = f >= 30 ? GREEN : RED;
- const color = Settings.powerPreference != 'low-power' ? color3 : color2;
- return `<span style="color:${color}">${Number(f)}</span>`;
- }
- /**
- * Create the container for the panel
- * @param parent parent element
- * @returns a container
- */
- _createContainer(parent) {
- const container = document.createElement('div');
- container.style.position = 'absolute';
- container.style.left = container.style.top = '0px';
- container.style.zIndex = '1000000';
- container.style.padding = '4px';
- container.style.whiteSpace = 'pre-line';
- container.style.backgroundColor = 'rgba(0,0,0,0.5)';
- container.style.color = '#fff';
- container.style.fontFamily = 'monospace';
- container.style.fontSize = '14px';
- parent.appendChild(container);
- return container;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/frame.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * frame.ts
- * A Frame holds information used to render a single animation frame of a Session
- */
- /**
- * Iterable frame results (helper class)
- */
- class IterableTrackerResults {
- constructor(_results) {
- this._results = _results;
- this._index = 0;
- }
- next() {
- const i = this._index++;
- return i < this._results.length ?
- { done: false, value: this._results[i] } :
- { done: true, value: undefined };
- }
- [Symbol.iterator]() {
- return this;
- }
- }
- /**
- * A Frame holds information used to render a single animation frame of a Session
- */
- class Frame {
- /**
- * Constructor
- * @param session
- * @param results
- */
- constructor(session, results) {
- this._session = session;
- this._results = new IterableTrackerResults(results);
- }
- /**
- * The session of which this frame holds data
- */
- get session() {
- return this._session;
- }
- /**
- * The results of all trackers in this frame
- */
- get results() {
- return this._results;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/time.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * time.ts
- * Time utilities
- */
- /**
- * Time Manager
- */
- class Time {
- constructor() {
- /** time scale */
- this._scale = 1;
- /** time since the start of the session, in milliseconds */
- this._time = 0;
- /** unscaled time since the start of the session, in milliseconds */
- this._unscaledTime = 0;
- /** elapsed time between the current and the previous frame, in milliseconds */
- this._delta = 0;
- /** time of the first update call, in milliseconds */
- this._firstUpdate = 0;
- /** time of the last update call, in milliseconds */
- this._lastUpdate = Number.POSITIVE_INFINITY;
- }
- /**
- * Update the Time Manager
- * @param timestamp in milliseconds
- * @internal
- */
- _update(timestamp) {
- if (timestamp < this._lastUpdate) {
- this._firstUpdate = this._lastUpdate = timestamp;
- return;
- }
- this._delta = (timestamp - this._lastUpdate) * this._scale;
- this._time += this._delta;
- this._unscaledTime = timestamp - this._firstUpdate;
- this._lastUpdate = timestamp;
- }
- /**
- * Elapsed time since the start of the session, measured at the
- * beginning of the current animation frame and given in seconds
- */
- get elapsed() {
- return this._time * 0.001;
- }
- /**
- * Elapsed time between the current and the previous animation
- * frame, given in seconds
- */
- get delta() {
- return this._delta * 0.001;
- }
- /**
- * Time scale (defaults to 1)
- */
- get scale() {
- return this._scale;
- }
- /**
- * Time scale (defaults to 1)
- */
- set scale(scale) {
- this._scale = Math.max(0, +scale);
- }
- /**
- * Time scale independent elapsed time since the start of the session,
- * measured at the beginning of the current animation frame and given
- * in seconds
- */
- get unscaled() {
- return this._unscaledTime * 0.001;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/core/gizmos.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * gizmos.ts
- * Visual cues for testing & debugging
- */
-
- /** The maximum match distance ratio we'll consider to be "good" */
- const GOOD_MATCH_THRESHOLD = 0.7;
- /**
- * Visual cues for testing & debugging
- */
- class Gizmos {
- /**
- * Constructor
- */
- constructor() {
- this._visible = false;
- }
- /**
- * Whether or not the gizmos will be rendered
- */
- get visible() {
- return this._visible;
- }
- /**
- * Whether or not the gizmos will be rendered
- */
- set visible(visible) {
- this._visible = visible;
- }
- /**
- * Render gizmos
- * @param viewport
- * @param trackers
- * @internal
- */
- _render(viewport, trackers) {
- // no need to render?
- if (!this._visible)
- return;
- // viewport
- const viewportSize = viewport._size;
- const canvas = viewport._background;
- const ctx = canvas.getContext('2d', { alpha: false });
- if (!ctx)
- throw new IllegalOperationError();
- // debug
- //ctx.fillStyle = '#000';
- //ctx.fillRect(0, 0, canvas.width, canvas.height);
- //ctx.clearRect(0, 0, canvas.width, canvas.height);
- // render keypoints
- for (let i = 0; i < trackers.length; i++) {
- if (trackers[i].type != 'image-tracker')
- continue;
- const output = trackers[i]._output;
- const keypoints = output.keypoints;
- const screenSize = output.screenSize;
- if (keypoints !== undefined && screenSize !== undefined)
- this._splitAndRenderKeypoints(ctx, keypoints, screenSize, viewportSize);
- }
- // render polylines
- for (let i = 0; i < trackers.length; i++) {
- if (trackers[i].type != 'image-tracker')
- continue;
- const output = trackers[i]._output;
- const polyline = output.polyline;
- const screenSize = output.screenSize;
- if (polyline !== undefined && screenSize !== undefined)
- this._renderPolyline(ctx, polyline, screenSize, viewportSize);
- }
- // render the axes of the 3D coordinate system
- for (let i = 0; i < trackers.length; i++) {
- if (trackers[i].type != 'image-tracker')
- continue;
- const output = trackers[i]._output;
- const cameraMatrix = output.cameraMatrix;
- const screenSize = output.screenSize;
- if (cameraMatrix !== undefined && screenSize !== undefined)
- this._renderAxes(ctx, cameraMatrix, screenSize, viewportSize);
- }
- }
- /**
- * Split keypoints in matched/unmatched categories and
- * render them for testing & development purposes
- * @param ctx canvas 2D context
- * @param keypoints keypoints to render
- * @param screenSize AR screen size
- * @param viewportSize viewport size
- * @param size base keypoint rendering size
- */
- _splitAndRenderKeypoints(ctx, keypoints, screenSize, viewportSize, size = 1) {
- if (keypoints.length == 0)
- return;
- if (!Object.prototype.hasOwnProperty.call(keypoints[0], '_matches')) { // hack...
- this._renderKeypoints(ctx, keypoints, screenSize, viewportSize, '#f00', size);
- return;
- }
- const isGoodMatch = (keypoint) => (keypoint.matches.length == 1 && keypoint.matches[0].index >= 0) ||
- (keypoint.matches.length > 1 &&
- keypoint.matches[0].index >= 0 && keypoint.matches[1].index >= 0 &&
- keypoint.matches[0].distance <= GOOD_MATCH_THRESHOLD * keypoint.matches[1].distance);
- const matchedKeypoints = keypoints;
- const goodMatches = matchedKeypoints.filter(keypoint => isGoodMatch(keypoint));
- const badMatches = matchedKeypoints.filter(keypoint => !isGoodMatch(keypoint));
- this._renderKeypoints(ctx, badMatches, screenSize, viewportSize, '#f00', size);
- this._renderKeypoints(ctx, goodMatches, screenSize, viewportSize, '#0f0', size);
- }
- /**
- * Render keypoints for testing & development purposes
- * @param ctx canvas 2D context
- * @param keypoints keypoints to render
- * @param screenSize AR screen size
- * @param viewportSize viewport size
- * @param color color of the rendered keypoints
- * @param size base keypoint rendering size
- */
- _renderKeypoints(ctx, keypoints, screenSize, viewportSize, color = 'red', size = 1) {
- const sx = viewportSize.width / screenSize.width;
- const sy = viewportSize.height / screenSize.height;
- ctx.beginPath();
- for (let i = keypoints.length - 1; i >= 0; i--) {
- const keypoint = keypoints[i];
- const x = (keypoint.x * sx + 0.5) | 0;
- const y = (keypoint.y * sy + 0.5) | 0;
- const r = (size * keypoint.scale + 0.5) | 0;
- ctx.rect(x - r, y - r, 2 * r, 2 * r);
- }
- ctx.strokeStyle = color;
- ctx.lineWidth = 1;
- ctx.stroke();
- }
- /**
- * Render polyline for testing & development purposes
- * @param ctx canvas 2D context
- * @param polyline vertices
- * @param screenSize AR screen size
- * @param viewportSize viewport size
- * @param color color of the rendered polyline
- * @param lineWidth
- */
- _renderPolyline(ctx, polyline, screenSize, viewportSize, color = '#0f0', lineWidth = 2) {
- if (polyline.length == 0)
- return;
- const n = polyline.length;
- const sx = viewportSize.width / screenSize.width;
- const sy = viewportSize.height / screenSize.height;
- // render polyline
- ctx.beginPath();
- ctx.moveTo(polyline[n - 1].x * sx, polyline[n - 1].y * sy);
- for (let j = 0; j < n; j++)
- ctx.lineTo(polyline[j].x * sx, polyline[j].y * sy);
- ctx.strokeStyle = color;
- ctx.lineWidth = lineWidth;
- ctx.stroke();
- }
- /**
- * Render the axes of a 3D coordinate system
- * @param ctx canvas 2D context
- * @param cameraMatrix 3x4 camera matrix that maps normalized coordinates [-1,1]^3 to AR screen space
- * @param screenSize AR screen size
- * @param viewportSize viewport size
- * @param lineWidth
- */
- _renderAxes(ctx, cameraMatrix, screenSize, viewportSize, lineWidth = 4) {
- const RED = '#f00', GREEN = '#0f0', BLUE = '#00f';
- const color = [RED, GREEN, BLUE]; // color of each axis: (X,Y,Z)
- const length = 1; // length of each axis-corresponding line, given in normalized space units
- const sx = viewportSize.width / screenSize.width;
- const sy = viewportSize.height / screenSize.height;
- /*
-
- Multiply the 3x4 camera matrix P by:
-
- [ 0 L 0 0 ]
- [ 0 0 L 0 ] , where L = length in normalized space of the lines
- [ 0 0 0 L ] corresponding to the 3 axes (typically 1)
- [ 1 1 1 1 ]
-
- Each column of the resulting matrix will give us the pixel coordinates
- we're looking for.
-
- Note: we're working with homogeneous coordinates
-
- */
- const p = cameraMatrix.read();
- const l = length;
- const o = [p[9], p[10], p[11]]; // origin of the coordinate system
- const x = [l * p[0] + p[9], l * p[1] + p[10], l * p[2] + p[11]]; // x-axis
- const y = [l * p[3] + p[9], l * p[4] + p[10], l * p[5] + p[11]]; // y-axis
- const z = [l * p[6] + p[9], l * p[7] + p[10], l * p[8] + p[11]]; // z-axis
- const axis = [x, y, z];
- // draw each axis
- const ox = o[0] / o[2], oy = o[1] / o[2];
- for (let i = 0; i < 3; i++) {
- const q = axis[i];
- const x = q[0] / q[2], y = q[1] / q[2];
- ctx.beginPath();
- ctx.moveTo(ox * sx, oy * sy);
- ctx.lineTo(x * sx, y * sy);
- ctx.strokeStyle = color[i];
- ctx.lineWidth = lineWidth;
- ctx.stroke();
- }
- //console.log("Origin",ox,oy);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/utils/asap.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * asap.ts
- * Schedule a function to run "as soon as possible"
- */
- /** callbacks */
- const callbacks = [];
- /** arguments to be passed to the callbacks */
- const args = [];
- /** asap key */
- const ASAP_KEY = 'asap' + Math.random().toString(36).substr(1);
- // Register an event listener
- window.addEventListener('message', event => {
- if (event.source !== window || event.data !== ASAP_KEY)
- return;
- event.stopPropagation();
- if (callbacks.length == 0)
- return;
- const fn = callbacks.pop();
- const argArray = args.pop();
- fn.apply(undefined, argArray);
- }, true);
- /**
- * Schedule a function to run "as soon as possible"
- * @param fn callback
- * @param params optional parameters
- */
- function asap(fn, ...params) {
- callbacks.unshift(fn);
- args.unshift(params);
- window.postMessage(ASAP_KEY, '*');
- }
-
- ;// CONCATENATED MODULE: ./src/core/session.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * session.ts
- * WebAR Session
- */
-
-
-
-
-
-
-
-
-
-
-
-
- /** An event emitted by a Session */
- class SessionEvent extends AREvent {
- }
- /** Default options when starting a session */
- const DEFAULT_OPTIONS = {
- mode: 'immersive',
- trackers: [],
- sources: [],
- viewport: null,
- stats: false,
- gizmos: false,
- };
- /**
- * A Session represents an intent to display AR content
- * and encapsulates the main loop (update-render cycle)
- */
- class Session extends AREventTarget {
- /**
- * Constructor
- * @param sources previously initialized sources of data
- * @param mode session mode
- * @param viewport viewport
- * @param stats render stats panel?
- * @param gizmos render gizmos?
- */
- constructor(sources, mode, viewport, stats, gizmos) {
- super();
- this._mode = mode;
- this._trackers = [];
- this._sources = sources;
- this._updateStats = new Stats();
- this._renderStats = new Stats();
- this._active = true;
- this._frameReady = true; // no trackers at the moment
- this._rafQueue = [];
- this._time = new Time();
- this._gizmos = new Gizmos();
- this._gizmos.visible = gizmos;
- // get media
- const media = this.media;
- // setup the viewport
- if (mode == 'immersive')
- this._viewport = new ImmersiveViewport(viewport, () => media.size);
- else if (mode == 'inline')
- this._viewport = new InlineViewport(viewport, () => media.size);
- else
- throw new IllegalArgumentError(`Invalid session mode "${mode}"`);
- this._viewport._init();
- // setup the main loop
- this._setupUpdateLoop();
- this._setupRenderLoop();
- // setup the stats panel
- this._statsPanel = new StatsPanel(this._viewport.hud.container);
- this._statsPanel.visible = stats;
- // done!
- Session._count++;
- Utils.log(`The ${mode} session is now active!`);
- }
- /**
- * Checks if the engine can be run in the browser the client is using
- * @returns true if the engine is compatible with the browser
- */
- static isSupported() {
- return speedy_vision_default().isSupported();
- }
- /**
- * Instantiate a session
- * @param options options
- * @returns a promise that resolves to a new session
- */
- static instantiate(options = DEFAULT_OPTIONS) {
- const { mode = DEFAULT_OPTIONS.mode, sources = DEFAULT_OPTIONS.sources, trackers = DEFAULT_OPTIONS.trackers, viewport = DEFAULT_OPTIONS.viewport, stats = DEFAULT_OPTIONS.stats, gizmos = DEFAULT_OPTIONS.gizmos, } = options;
- Utils.log(`Starting a new ${mode} session...`);
- return speedy_vision_default().Promise.resolve().then(() => {
- // is the engine supported?
- if (!Session.isSupported())
- throw new NotSupportedError('You need a browser/device compatible with WebGL2 and WebAssembly in order to experience Augmented Reality with the MARTINS.js engine');
- // block multiple immersive sessions
- if (mode !== 'inline' && Session.count > 0)
- throw new IllegalOperationError(`Can't start more than one immersive session`);
- // initialize matrix routines
- return speedy_vision_default().Matrix.ready();
- }).then(() => {
- // validate sources of data
- const videoSources = sources.filter(source => source._type == 'video');
- if (videoSources.length != 1)
- throw new IllegalArgumentError(`One video source of data must be provided`);
- for (let i = sources.length - 1; i >= 0; i--) {
- if (sources.indexOf(sources[i]) < i)
- throw new IllegalArgumentError(`Found repeated sources of data`);
- }
- // initialize sources of data
- return speedy_vision_default().Promise.all(sources.map(source => source._init()));
- }).then(() => {
- // get the viewport
- if (!viewport)
- throw new IllegalArgumentError(`Can't create a session without a viewport`);
- // instantiate session
- return new Session(sources, mode, viewport, stats, gizmos);
- }).then(session => {
- // validate trackers
- if (trackers.length == 0)
- Utils.warning(`No trackers have been attached to the session!`);
- for (let i = trackers.length - 1; i >= 0; i--) {
- if (trackers.indexOf(trackers[i]) < i)
- throw new IllegalArgumentError(`Found repeated trackers`);
- }
- // attach trackers and return the session
- return speedy_vision_default().Promise.all(trackers.map(tracker => session._attachTracker(tracker))).then(() => session);
- }).catch(err => {
- // log errors, if any
- Utils.error(`Can't start session: ${err.message}`);
- throw err;
- });
- }
- /**
- * Number of active sessions
- */
- static get count() {
- return this._count;
- }
- /**
- * End the session
- * @returns promise that resolves after the session is shut down
- */
- end() {
- // is the session inactive?
- if (!this._active)
- return speedy_vision_default().Promise.resolve();
- // deactivate the session
- Utils.log('Shutting down the session...');
- this._active = false; // set before wait()
- // wait a few ms, so that the GPU is no longer sending any data
- const wait = (ms) => new (speedy_vision_default()).Promise(resolve => {
- setTimeout(resolve, ms);
- });
- // release resources
- return wait(100).then(() => speedy_vision_default().Promise.all(
- // release trackers
- this._trackers.map(tracker => tracker._release()))).then(() => speedy_vision_default().Promise.all(
- // release input sources
- this._sources.map(source => source._release()))).then(() => {
- this._sources.length = 0;
- this._trackers.length = 0;
- // release internal components
- this._updateStats.reset();
- this._renderStats.reset();
- this._statsPanel.release();
- this._viewport._release();
- // end the session
- Session._count--;
- // dispatch event
- const event = new SessionEvent('end');
- this.dispatchEvent(event);
- // done!
- Utils.log('Session ended.');
- });
- }
- /**
- * Analogous to window.requestAnimationFrame()
- * @param callback
- * @returns a handle
- */
- requestAnimationFrame(callback) {
- const handle = Symbol('raf-handle');
- if (this._active)
- this._rafQueue.push([handle, callback]);
- else
- throw new IllegalOperationError(`Can't requestAnimationFrame(): session ended.`);
- return handle;
- }
- /**
- * Analogous to window.cancelAnimationFrame()
- * @param handle a handle returned by this.requestAnimationFrame()
- */
- cancelAnimationFrame(handle) {
- for (let i = this._rafQueue.length - 1; i >= 0; i--) {
- if (this._rafQueue[i][0] === handle) {
- this._rafQueue.splice(i, 1);
- break;
- }
- }
- }
- /**
- * The underlying media (generally a camera stream)
- * @internal
- */
- get media() {
- for (let i = this._sources.length - 1; i >= 0; i--) {
- if (this._sources[i]._type == 'video')
- return this._sources[i]._data;
- }
- // this shouldn't happen
- throw new IllegalOperationError(`Invalid input source`);
- }
- /**
- * Session mode
- */
- get mode() {
- return this._mode;
- }
- /**
- * Rendering viewport
- */
- get viewport() {
- return this._viewport;
- }
- /**
- * Time utilities
- */
- get time() {
- return this._time;
- }
- /**
- * Visual cues for testing & debugging
- */
- get gizmos() {
- return this._gizmos;
- }
- /**
- * Attach a tracker to the session
- * @param tracker
- */
- _attachTracker(tracker) {
- if (this._trackers.indexOf(tracker) >= 0)
- throw new IllegalArgumentError(`Duplicate tracker attached to the session`);
- else if (!this._active)
- throw new IllegalOperationError(`Inactive session`);
- this._trackers.push(tracker);
- return tracker._init(this);
- }
- /**
- * Render the user media to the background canvas
- */
- _renderUserMedia() {
- const canvas = this._viewport._background;
- const ctx = canvas.getContext('2d', { alpha: false });
- if (ctx) {
- ctx.imageSmoothingEnabled = false;
- // draw user media
- const image = this.media.source;
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
- // render output image(s)
- for (let i = 0; i < this._trackers.length; i++) {
- const image = this._trackers[i]._output.image;
- if (image !== undefined)
- ctx.drawImage(image.source, 0, 0, canvas.width, canvas.height);
- //ctx.drawImage(image.source, canvas.width - image.width, canvas.height - image.height, image.width, image.height);
- }
- // render gizmos
- this._gizmos._render(this._viewport, this._trackers);
- }
- }
- /**
- * Setup the update loop
- */
- _setupUpdateLoop() {
- const scheduleNextFrame = () => {
- if (this._active) {
- if (Settings.powerPreference == 'high-performance')
- asap(repeat);
- else
- window.requestAnimationFrame(repeat);
- }
- };
- const update = () => {
- this._update().then(scheduleNextFrame).turbocharge();
- };
- function repeat() {
- if (Settings.powerPreference == 'low-power') // 30 fps
- window.requestAnimationFrame(update);
- else
- update();
- }
- window.requestAnimationFrame(update);
- }
- /**
- * The core of the update loop
- */
- _update() {
- // active session?
- if (this._active) {
- return speedy_vision_default().Promise.all(
- // update trackers
- this._trackers.map(tracker => tracker._update().turbocharge())).then(() => {
- // update internals
- this._updateStats.update();
- this._frameReady = true;
- }).catch(err => {
- // handle error
- Utils.warning('Tracking error: ' + err.toString());
- });
- }
- else {
- // inactive session
- this._updateStats.reset();
- return speedy_vision_default().Promise.resolve();
- }
- }
- /**
- * Setup the render loop
- */
- _setupRenderLoop() {
- let skip = false, toggle = false;
- const render = (timestamp) => {
- const enableFrameSkipping = (Settings.powerPreference == 'low-power');
- const highPerformance = (Settings.powerPreference == 'high-performance');
- // advance time
- this._time._update(timestamp);
- // skip frames
- if (!enableFrameSkipping || !(skip = !skip))
- this._render(timestamp, false);
- //this._render(timestamp, !enableFrameSkipping && !highPerformance && (toggle = !toggle));
- // repeat
- if (this._active)
- window.requestAnimationFrame(render);
- };
- window.requestAnimationFrame(render);
- }
- /**
- * Render a frame (RAF callback)
- * @param time current time, in ms
- * @param skipUserMedia skip copying the pixels of the user media to the background canvas in order to reduce the processing load (video stream is probably at 30fps?)
- */
- _render(time, skipUserMedia) {
- // is the session active?
- if (this._active) {
- // are we ready to render a frame?
- if (this._frameReady) {
- // create a frame
- const results = this._trackers.map(tracker => tracker._output.exports || ({
- tracker: tracker,
- trackables: [],
- }));
- const frame = new Frame(this, results);
- // clone & clear the RAF queue
- const rafQueue = this._rafQueue.slice(0);
- this._rafQueue.length = 0;
- // render user media
- if (!skipUserMedia)
- this._renderUserMedia();
- // render frame
- for (let i = 0; i < rafQueue.length; i++)
- rafQueue[i][1].call(undefined, time, frame);
- // update internals
- this._renderStats.update();
- this._statsPanel.update(time, this._trackers, this._sources, this._updateStats.cyclesPerSecond, this._renderStats.cyclesPerSecond);
- this._frameReady = false;
- }
- else {
- // skip frame
- ;
- // we'll update the renderStats even if we skip the frame,
- // otherwise this becomes updateStats! (approximately)
- // This is a window.requestAnimationFrame() call, so the
- // browser is rendering content even if we're not.
- this._renderStats.update();
- }
- }
- else {
- // inactive session
- this._renderStats.reset();
- }
- }
- }
- /** Number of active sessions */
- Session._count = 0;
-
- ;// CONCATENATED MODULE: ./src/core/settings.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * settings.ts
- * Global Settings
- */
-
-
-
-
- /**
- * Global Settings
- */
- class Settings {
- /**
- * Power preference (may impact performance x battery life)
- */
- static get powerPreference() {
- return this._powerPreference;
- }
- /**
- * Power preference (may impact performance x battery life)
- * Note: this setting should be the very first thing you set
- * (before the WebGL context is created by Speedy)
- */
- static set powerPreference(value) {
- // validate
- if (Session.count > 0)
- throw new IllegalOperationError(`Can't change the powerPreference while there are active sessions going on`);
- else if (!('low-power' == value || 'default' == value || 'high-performance' == value))
- throw new IllegalArgumentError(`Invalid powerPreference: "${value}"`);
- /*
- // we won't use 'high-performance' for Speedy's GPU computations
- // see the WebGL 1.0 spec sec 5.2.1 for battery life considerations
- // also, it seems like low-power mode may break WebGL2 in some drivers?!
-
- if(value == 'high-performance')
- Speedy.Settings.powerPreference = 'default';
- else
- Speedy.Settings.powerPreference = value;
- */
- // change the GPU polling mode
- if (value == 'high-performance')
- (speedy_vision_default()).Settings.gpuPollingMode = 'asap';
- else
- (speedy_vision_default()).Settings.gpuPollingMode = 'raf';
- // update the power preference
- this._powerPreference = value;
- // log
- Utils.log(`Changed the powerPreference to "${this._powerPreference}"`);
- }
- }
- Settings._powerPreference = 'default';
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/reference-image-database.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * reference-image-database.ts
- * A collection of Reference Images
- */
-
-
- /** Default capacity of a Reference Image Database */
- const DEFAULT_CAPACITY = 100;
- /** Generate a unique name for a reference image */
- const generateUniqueName = () => 'target-' + Math.random().toString(16).substr(2);
- /**
- * A collection of Reference Images
- */
- class ReferenceImageDatabase {
- /**
- * Constructor
- */
- constructor() {
- this._capacity = DEFAULT_CAPACITY;
- this._database = [];
- this._locked = false;
- }
- /**
- * The number of reference images stored in this database
- */
- get count() {
- return this._database.length;
- }
- /**
- * Maximum number of elements
- */
- get capacity() {
- return this._capacity;
- }
- /**
- * Maximum number of elements
- */
- /*
- set capacity(value: number)
- {
- const capacity = Math.max(0, value | 0);
-
- if(this.count > capacity)
- throw new IllegalArgumentError(`Can't set the capacity of the database to ${this._capacity}: it currently stores ${this.count} entries`);
-
- this._capacity = capacity;
- }
- */
- /**
- * Iterates over the collection
- */
- *[Symbol.iterator]() {
- const ref = this._database.map(entry => entry.referenceImage);
- yield* ref;
- }
- /**
- * Add reference images to this database
- * Add only the images you actually need to track!
- * (each image take up storage space)
- * @param referenceImages one or more reference images with unique names (a unique name will
- * be generated automatically if you don't specify one)
- * @returns a promise that resolves as soon as the images are loaded and added to this database
- */
- add(referenceImages) {
- // handle no input
- if (referenceImages.length == 0)
- return speedy_vision_default().Promise.resolve();
- // handle multiple images as input
- if (referenceImages.length > 1) {
- const promises = referenceImages.map(image => this.add([image]));
- return speedy_vision_default().Promise.all(promises).then(() => void (0));
- }
- // handle a single image as input
- const referenceImage = referenceImages[0];
- // locked database?
- if (this._locked)
- throw new IllegalOperationError(`Can't add reference image to the database: it's locked`);
- // reached full capacity?
- if (this.count >= this.capacity)
- throw new IllegalOperationError(`Can't add reference image to the database: the capacity of ${this.capacity} images has been exceeded.`);
- // check for duplicate names
- if (this._database.find(entry => entry.referenceImage.name === referenceImage.name) !== undefined)
- throw new IllegalArgumentError(`Can't add reference image to the database: found duplicated name "${referenceImage.name}"`);
- // load the media and add the reference image to the database
- return speedy_vision_default().load(referenceImage.image).then(media => {
- this._database.push({
- referenceImage: Object.freeze(Object.assign(Object.assign({}, referenceImage), { name: referenceImage.name || generateUniqueName() })),
- media: media
- });
- });
- }
- /**
- * Lock the database, so that new reference images can no longer be added to it
- * @internal
- */
- _lock() {
- this._locked = true;
- }
- /**
- * Get the media object associated to a reference image
- * @param name reference image name
- * @returns media
- * @internal
- */
- _findMedia(name) {
- for (let i = 0; i < this._database.length; i++) {
- if (this._database[i].referenceImage.name === name)
- return this._database[i].media;
- }
- throw new IllegalArgumentError(`Can't find reference image "${name}"`);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/settings.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * settings.ts
- * Settings of the Image Tracker
- */
- /** Default tracking resolution */
- const DEFAULT_TRACKING_RESOLUTION = 'sm+';
- /** Maximum number of keypoints to be stored for each reference image when in the training state */
- const TRAIN_MAX_KEYPOINTS = 1024; //512;
- /** Percentage relative to the screen size adjusted to the aspect ratio of the reference image */
- const TRAIN_IMAGE_SCALE = 0.8; // ORB is not scale invariant
- /** Normalized width & height of an image target, in pixels */
- const TRAIN_TARGET_NORMALIZED_SIZE = 1024; // keypoint positions are stored as fixed point
- /** Used to identify the best maches */
- const SCAN_MATCH_RATIO = 0.7; // usually a value in [0.6, 0.8]
- /** Maximum number of keypoints to be analyzed when in the scanning state */
- const SCAN_MAX_KEYPOINTS = 512;
- /** Number of pyramid levels to be scanned by the corner detector when in the scanning & training states */
- const SCAN_PYRAMID_LEVELS = 4; //7;
- /** Scale factor between pyramid levels to be scanned by the corner detector when in the scanning & training states */
- const SCAN_PYRAMID_SCALEFACTOR = 1.19; // 2 ^ 0.25
- /** Threshold of the FAST corner detector used in the scanning/training states */
- const SCAN_FAST_THRESHOLD = 60;
- /** Minimum number of accepted matches for us to move out from the scanning state */
- const SCAN_MIN_MATCHES = 20; //30;
- /** When in the scanning state, we require the image to be matched during a few consecutive frames before accepting it */
- const SCAN_CONSECUTIVE_FRAMES = 30; //15;//45;
- /** Reprojection error, in pixels, used when estimating a motion model (scanning state) */
- const SCAN_RANSAC_REPROJECTIONERROR = 5;
- /** Number of tables used in the LSH-based keypoint matching */
- const SCAN_LSH_TABLES = 8; // up to 32
- /** Hash size, in bits, used in the LSH-based keypoint matching */
- const SCAN_LSH_HASHSIZE = 15; // up to 16
- /** Use the Nightvision filter when in the scanning/training state? */
- const SCAN_WITH_NIGHTVISION = true;
- /** Nightvision filter: gain */
- const NIGHTVISION_GAIN = 0.3; // 0.2;
- /** Nightvision filter: offset */
- const NIGHTVISION_OFFSET = 0.5;
- /** Nightvision filter: decay */
- const NIGHTVISION_DECAY = 0.0;
- /** Nightvision filter: quality level */
- const NIGHTVISION_QUALITY = 'low';
- /** Kernel size (square) of the Gaussian filter applied before computing the ORB descriptors */
- const ORB_GAUSSIAN_KSIZE = 9;
- /** Sigma of the Gaussian filter applied before computing the ORB descriptors */
- const ORB_GAUSSIAN_SIGMA = 2.0;
- /** Kernel size (square) of the Gaussian filter applied before subpixel refinement for noise reduction */
- const SUBPIXEL_GAUSSIAN_KSIZE = 5;
- /** Sigma of the Gaussian filter applied before subpixel refinement for noise reduction */
- const SUBPIXEL_GAUSSIAN_SIGMA = 1.0;
- /** Subpixel refinement method */
- const SUBPIXEL_METHOD = 'bilinear-upsample'; // 'quadratic1d';
- /** Minimum acceptable number of matched keypoints when in the tracking state */
- const TRACK_MIN_MATCHES = 4; //10; //20;
- /** Maximum number of keypoints to be analyzed in the tracking state */
- const TRACK_MAX_KEYPOINTS = 200; //400; // <-- impacts performance!
- /** Capacity of the keypoint detector used in the the tracking state */
- const TRACK_DETECTOR_CAPACITY = 2048; //4096;
- /** Quality of the Harris/Shi-Tomasi corner detector */
- const TRACK_HARRIS_QUALITY = 0.005; // get a lot of keypoints
- /** Use the Nightvision filter when in the tracking state? */
- const TRACK_WITH_NIGHTVISION = false; // produces shaking?
- /** Relative size (%) of the (top, right, bottom, left) borders of the rectified image */
- const TRACK_RECTIFIED_BORDER = 0.15; //0.20;
- /** Relative size (%) used to clip keypoints from the borders of the rectified image */
- const TRACK_CLIPPING_BORDER = TRACK_RECTIFIED_BORDER * 1.20; //1.25; //1.15;
- /** Number of iterations used to refine the target image before tracking */
- const TRACK_REFINEMENT_ITERATIONS = 3;
- /** Reprojection error, in pixels, used when estimating a motion model (tracking state) */
- const TRACK_RANSAC_REPROJECTIONERROR = 3; //2.5;
- /** We use a N x N grid to spatially distribute the keypoints in order to compute a better homography */
- const TRACK_GRID_GRANULARITY = 10; //20; // the value of N
- /** Used to identify the best maches */
- const TRACK_MATCH_RATIO = 0.75; // usually a value in [0.6, 0.8] - low values => strict tracking
- /** Number of consecutive frames in which we tolerate a "target lost" situation */
- const TRACK_LOST_TOLERANCE = 10;
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/states/state.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * state.ts
- * Abstract state of the Image Tracker
- */
-
-
-
- /**
- * Abstract state of the Image Tracker
- */
- class ImageTrackerState {
- /**
- * Constructor
- * @param name
- * @param imageTracker
- */
- constructor(name, imageTracker) {
- this._name = name;
- this._imageTracker = imageTracker;
- this._pipeline = this._createPipeline();
- }
- /**
- * State name
- */
- get name() {
- return this._name;
- }
- /**
- * AR screen size
- */
- get screenSize() {
- const screen = this._pipeline.node('screen');
- if (!screen)
- throw new IllegalOperationError();
- // this is available once this state has run at least once
- return screen.size;
- }
- /**
- * Initialize the state
- */
- init() {
- }
- /**
- * Release resources
- */
- release() {
- return this._pipeline.release();
- }
- /**
- * Update the state
- * @param media user media
- * @param screenSize AR screen size for image processing
- * @param state all states
- * @returns promise
- */
- update(media, screenSize) {
- const source = this._pipeline.node('source');
- const screen = this._pipeline.node('screen');
- // validate the pipeline
- if (!source || !screen)
- throw new IllegalOperationError();
- // prepare the pipeline
- source.media = media;
- screen.size = screenSize;
- // run the pipeline
- return this._beforeUpdate().then(() => this._gpuUpdate()).then(result => this._afterUpdate(result));
- }
- /**
- * Called as soon as this becomes the active state, just before update() runs for the first time
- * @param settings
- */
- onEnterState(settings) {
- }
- /**
- * Called when leaving the state, after update()
- */
- onLeaveState() {
- }
- /**
- * Called just before the GPU processing
- * @returns promise
- */
- _beforeUpdate() {
- return speedy_vision_default().Promise.resolve();
- }
- /**
- * GPU processing
- * @returns promise with the pipeline results
- */
- _gpuUpdate() {
- return this._pipeline.run();
- }
- //
- // Some utility methods common to various states
- //
- /**
- * Find the coordinates of a polyline surrounding the target image
- * @param homography maps the target image to the AR screen
- * @param targetSize size of the target space
- * @returns promise that resolves to 4 points in AR screen space
- */
- _findPolylineCoordinates(homography, targetSize) {
- const w = targetSize.width, h = targetSize.height;
- const referenceImageCoordinates = speedy_vision_default().Matrix(2, 4, [
- 0, 0,
- w, 0,
- w, h,
- 0, h,
- ]);
- const polylineCoordinates = speedy_vision_default().Matrix.Zeros(2, 4);
- return speedy_vision_default().Matrix.applyPerspectiveTransform(polylineCoordinates, referenceImageCoordinates, homography);
- }
- /**
- * Find a polyline surrounding the target image
- * @param homography maps the target image to the AR screen
- * @param targetSize size of the target space
- * @returns promise that resolves to 4 points in AR screen space
- */
- _findPolyline(homography, targetSize) {
- return this._findPolylineCoordinates(homography, targetSize).then(polylineCoordinates => {
- const polydata = polylineCoordinates.read();
- const polyline = Array.from({ length: 4 }, (_, i) => speedy_vision_default().Point2(polydata[2 * i], polydata[2 * i + 1]));
- return polyline;
- });
- }
- /**
- * Whether or not to rotate the warped image in order to best fit the AR screen
- * @param media media associated with the reference image
- * @param screenSize AR screen
- * @returns boolean
- */
- _mustRotateWarpedImage(media, screenSize) {
- const screenAspectRatio = screenSize.width / screenSize.height;
- const mediaAspectRatio = media.width / media.height;
- const eps = 0.1;
- return (mediaAspectRatio >= 1 + eps && screenAspectRatio < 1 - eps) || (mediaAspectRatio < 1 - eps && screenAspectRatio >= 1 + eps);
- }
- /**
- * Find a rectification matrix to be applied to an image fitting the entire AR screen
- * @param media media associated with the reference image
- * @param screenSize AR screen
- * @returns promise that resolves to a rectification matrix
- */
- _findRectificationMatrixOfFullscreenImage(media, screenSize) {
- const b = TRACK_RECTIFIED_BORDER;
- const sw = screenSize.width, sh = screenSize.height;
- const mediaAspectRatio = media.width / media.height;
- const mustRotate = this._mustRotateWarpedImage(media, screenSize);
- // compute the vertices of the target in screen space
- // we suppose portrait or landscape mode for both screen & media
- const c = mustRotate ? 1 / mediaAspectRatio : mediaAspectRatio;
- const top = sw >= sh ? b * sh : (sh - sw * (1 - 2 * b) / c) / 2;
- const left = sw >= sh ? (sw - sh * (1 - 2 * b) * c) / 2 : b * sw;
- const right = sw - left;
- const bottom = sh - top;
- const targetVertices = speedy_vision_default().Matrix(2, 4, [
- left, top,
- right, top,
- right, bottom,
- left, bottom,
- ]);
- const screenVertices = speedy_vision_default().Matrix(2, 4, [
- 0, 0,
- sw, 0,
- sw, sh,
- 0, sh
- ]);
- const preRectificationMatrix = speedy_vision_default().Matrix.Eye(3);
- const alignmentMatrix = speedy_vision_default().Matrix.Zeros(3);
- const rectificationMatrix = speedy_vision_default().Matrix.Zeros(3);
- return (mustRotate ? speedy_vision_default().Matrix.perspective(
- // pre-rectifation: rotate by 90 degrees counterclockwise and scale to screenSize
- preRectificationMatrix, screenVertices, speedy_vision_default().Matrix(2, 4, [0, sh, 0, 0, sw, 0, sw, sh])) : speedy_vision_default().Promise.resolve(preRectificationMatrix)).then(_ =>
- // alignment: align the target to the center of the screen
- speedy_vision_default().Matrix.perspective(alignmentMatrix, screenVertices, targetVertices)).then(_ =>
- // pre-rectify and then align
- rectificationMatrix.setTo(alignmentMatrix.times(preRectificationMatrix)));
- }
- /**
- * Find a rectification matrix to be applied to the target image
- * @param homography maps a reference image to the AR screen
- * @param targetSize size of the target space
- * @param media media associated with the reference image
- * @param screenSize AR screen
- * @returns promise that resolves to a rectification matrix
- */
- _findRectificationMatrixOfCameraImage(homography, targetSize, media, screenSize) {
- const sw = screenSize.width, sh = screenSize.height;
- const screen = speedy_vision_default().Matrix(2, 4, [0, 0, sw, 0, sw, sh, 0, sh]);
- const rectificationMatrix = speedy_vision_default().Matrix.Zeros(3);
- return this._findPolylineCoordinates(homography, targetSize).then(polyline =>
- // from target space to (full)screen
- speedy_vision_default().Matrix.perspective(rectificationMatrix, polyline, screen)).then(_ =>
- // from (full)screen to rectified coordinates
- this._findRectificationMatrixOfFullscreenImage(media, screenSize)).then(mat =>
- // function composition
- rectificationMatrix.setTo(mat.times(rectificationMatrix)));
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/states/initial.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * initial.ts
- * Initial state of the Image Tracker
- */
-
-
-
-
- /**
- * The purpose of the initial state of the Image Tracker
- * is to initialize the training state using the state machine
- */
- class ImageTrackerInitialState extends ImageTrackerState {
- /**
- * Constructor
- * @param imageTracker
- */
- constructor(imageTracker) {
- super('initial', imageTracker);
- }
- /**
- * Called just before the GPU processing
- * @returns promise
- */
- _beforeUpdate() {
- const source = this._pipeline.node('source');
- const media = source.media;
- const mediaSize = media.size;
- if (mediaSize.area() < this.screenSize.area())
- Utils.warning('The resolution of the tracker is larger than the resolution of the video. This is inefficient.');
- return speedy_vision_default().Promise.resolve();
- }
- /**
- * Post processing that takes place just after the GPU processing
- * @param result pipeline results
- * @returns state output
- */
- _afterUpdate(result) {
- return speedy_vision_default().Promise.resolve({
- nextState: 'training',
- trackerOutput: {},
- });
- }
- /**
- * Create & setup the pipeline
- * @returns pipeline
- */
- _createPipeline() {
- // this pipeline does nothing useful,
- // but it does preload some shaders...
- const pipeline = speedy_vision_default().Pipeline();
- const source = speedy_vision_default().Image.Source('source');
- const screen = speedy_vision_default().Transform.Resize('screen');
- const greyscale = speedy_vision_default().Filter.Greyscale();
- const imageRectifier = speedy_vision_default().Transform.PerspectiveWarp();
- const nightvision = speedy_vision_default().Filter.Nightvision();
- const nightvisionMux = speedy_vision_default().Image.Multiplexer();
- const detector = speedy_vision_default().Keypoint.Detector.Harris();
- const descriptor = speedy_vision_default().Keypoint.Descriptor.ORB();
- const blur = speedy_vision_default().Filter.GaussianBlur();
- const clipper = speedy_vision_default().Keypoint.Clipper();
- const borderClipper = speedy_vision_default().Keypoint.BorderClipper();
- const denoiser = speedy_vision_default().Filter.GaussianBlur();
- const subpixel = speedy_vision_default().Keypoint.SubpixelRefiner();
- const matcher = speedy_vision_default().Keypoint.Matcher.BFKNN();
- const keypointRectifier = speedy_vision_default().Keypoint.Transformer();
- const keypointPortalSink = speedy_vision_default().Keypoint.Portal.Sink();
- const keypointPortalSource = speedy_vision_default().Keypoint.Portal.Source();
- const muxOfReferenceKeypoints = speedy_vision_default().Keypoint.Multiplexer();
- const bufferOfReferenceKeypoints = speedy_vision_default().Keypoint.Buffer();
- const muxOfBufferOfReferenceKeypoints = speedy_vision_default().Keypoint.Multiplexer();
- const keypointSink = speedy_vision_default().Keypoint.SinkOfMatchedKeypoints();
- source.media = null;
- screen.size = speedy_vision_default().Size(0, 0);
- imageRectifier.transform = speedy_vision_default().Matrix.Eye(3);
- nightvision.quality = NIGHTVISION_QUALITY;
- subpixel.method = SUBPIXEL_METHOD;
- //borderClipper.imageSize = screen.size;
- borderClipper.imageSize = speedy_vision_default().Size(100, 100);
- borderClipper.borderSize = speedy_vision_default().Vector2(0, 0);
- matcher.k = 1; //2;
- keypointRectifier.transform = speedy_vision_default().Matrix.Eye(3);
- keypointPortalSource.source = keypointPortalSink;
- muxOfReferenceKeypoints.port = 0;
- muxOfBufferOfReferenceKeypoints.port = 0;
- bufferOfReferenceKeypoints.frozen = false;
- keypointSink.turbo = false;
- // prepare input
- source.output().connectTo(screen.input());
- screen.output().connectTo(greyscale.input());
- // preprocess images
- greyscale.output().connectTo(imageRectifier.input());
- imageRectifier.output().connectTo(nightvisionMux.input('in0'));
- imageRectifier.output().connectTo(nightvision.input());
- nightvision.output().connectTo(nightvisionMux.input('in1'));
- nightvisionMux.output().connectTo(blur.input());
- // keypoint detection & clipping
- nightvisionMux.output().connectTo(detector.input());
- detector.output().connectTo(borderClipper.input());
- borderClipper.output().connectTo(clipper.input());
- // keypoint refinement
- imageRectifier.output().connectTo(denoiser.input());
- denoiser.output().connectTo(subpixel.input('image'));
- clipper.output().connectTo(subpixel.input('keypoints'));
- // keypoint description
- blur.output().connectTo(descriptor.input('image'));
- subpixel.output().connectTo(descriptor.input('keypoints'));
- // keypoint matching
- descriptor.output().connectTo(muxOfReferenceKeypoints.input('in0'));
- muxOfBufferOfReferenceKeypoints.output().connectTo(muxOfReferenceKeypoints.input('in1'));
- muxOfReferenceKeypoints.output().connectTo(matcher.input('database'));
- descriptor.output().connectTo(matcher.input('keypoints'));
- // store reference keypoints
- keypointPortalSource.output().connectTo(muxOfBufferOfReferenceKeypoints.input('in0'));
- bufferOfReferenceKeypoints.output().connectTo(muxOfBufferOfReferenceKeypoints.input('in1'));
- keypointPortalSource.output().connectTo(bufferOfReferenceKeypoints.input());
- // portals
- descriptor.output().connectTo(keypointPortalSink.input());
- // prepare output
- descriptor.output().connectTo(keypointRectifier.input());
- keypointRectifier.output().connectTo(keypointSink.input());
- matcher.output().connectTo(keypointSink.input('matches'));
- // done!
- pipeline.init(source, screen, greyscale, imageRectifier, nightvision, nightvisionMux, blur, detector, subpixel, clipper, borderClipper, denoiser, descriptor, keypointPortalSource, muxOfReferenceKeypoints, matcher, bufferOfReferenceKeypoints, muxOfBufferOfReferenceKeypoints, keypointRectifier, keypointSink, keypointPortalSink);
- /*
- const run = pipeline.run.bind(pipeline);
- pipeline.run = function() {
- console.time("TIME");
- return run().then(x => {
- console.timeEnd("TIME");
- return x;
- });
- };
- */
- return pipeline;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/states/training.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * training.ts
- * Training state of the Image Tracker
- */
-
-
-
-
-
- /**
- * Training state of the Image Tracker
- */
- class ImageTrackerTrainingState extends ImageTrackerState {
- /**
- * Constructor
- * @param imageTracker
- */
- constructor(imageTracker) {
- super('training', imageTracker);
- this._currentImageIndex = 0;
- this._image = [];
- // initialize the training map
- this._trainingMap = {
- referenceImageIndex: [],
- referenceImage: [],
- keypoints: []
- };
- }
- /**
- * Called as soon as this becomes the active state, just before update() runs for the first time
- * @param settings
- */
- onEnterState(settings) {
- const database = this._imageTracker.database;
- // validate
- if (database.count == 0)
- throw new TrainingError(`Can't train the Image Tracker: the Reference Image Database is empty`);
- // prepare to train...
- this._currentImageIndex = 0;
- this._image.length = 0;
- this._trainingMap.referenceImageIndex.length = 0;
- this._trainingMap.referenceImage.length = 0;
- this._trainingMap.keypoints.length = 0;
- // lock the database
- Utils.log(`Image Tracker: training using ${database.count} reference image${database.count != 1 ? 's' : ''}`);
- database._lock();
- // collect all images
- for (const referenceImage of database)
- this._image.push(referenceImage);
- }
- /**
- * Called just before the GPU processing
- * @returns promise
- */
- _beforeUpdate() {
- const arScreenSize = this.screenSize;
- const source = this._pipeline.node('source');
- const screen = this._pipeline.node('screen');
- const keypointScaler = this._pipeline.node('keypointScaler');
- // this shouldn't happen
- if (this._currentImageIndex >= this._image.length)
- return speedy_vision_default().Promise.reject(new IllegalOperationError());
- // set the appropriate training media
- const database = this._imageTracker.database;
- const referenceImage = this._image[this._currentImageIndex];
- const media = database._findMedia(referenceImage.name);
- source.media = media;
- // compute the appropriate size of the training image space
- const resolution = this._imageTracker.resolution;
- const scale = TRAIN_IMAGE_SCALE; // ORB is not scale-invariant
- const aspectRatioOfTrainingImage = media.width / media.height;
- /*
- let sin = 0, cos = 1;
-
- if((aspectRatioOfSourceVideo - 1) * (aspectRatioOfTrainingImage - 1) >= 0) {
- // training image and source video: both in landscape mode or both in portrait mode
- screen.size = Utils.resolution(resolution, aspectRatioOfTrainingImage);
- screen.size.width = Math.round(screen.size.width * scale);
- screen.size.height = Math.round(screen.size.height * scale);
- }
- else if(aspectRatioOfTrainingImage > aspectRatioOfSourceVideo) {
- // training image: portrait mode; source video: landscape mode
- screen.size = Utils.resolution(resolution, 1 / aspectRatioOfTrainingImage);
- screen.size.width = Math.round(screen.size.width * scale);
- screen.size.height = Math.round(screen.size.height * scale);
- sin = 1; cos = 0; // rotate 90deg
- }
- else {
- // training image: landscape mode; source video: portrait mode
- }
- */
- screen.size = Utils.resolution(resolution, aspectRatioOfTrainingImage);
- screen.size.width = Math.round(screen.size.width * scale);
- screen.size.height = Math.round(screen.size.height * scale);
- // convert keypoints from the training image space to AR screen space
- // let's pretend that trained keypoints belong to the AR screen space,
- // regardless of the size of the target image. This will make things
- // easier when computing the homography.
- /*
- const sw = arScreenSize.width / screen.size.width;
- const sh = arScreenSize.height / screen.size.height;
- */
- const sw = TRAIN_TARGET_NORMALIZED_SIZE / screen.size.width;
- const sh = TRAIN_TARGET_NORMALIZED_SIZE / screen.size.height;
- keypointScaler.transform = speedy_vision_default().Matrix(3, 3, [
- sw, 0, 0,
- 0, sh, 0,
- 0, 0, 1,
- ]);
- // log
- Utils.log(`Image Tracker: training using reference image "${referenceImage.name}" at ${screen.size.width}x${screen.size.height}...`);
- // done!
- return speedy_vision_default().Promise.resolve();
- }
- /**
- * Post processing that takes place just after the GPU processing
- * @param result pipeline results
- * @returns state output
- */
- _afterUpdate(result) {
- const referenceImage = this._image[this._currentImageIndex];
- const keypoints = result.keypoints;
- const image = result.image;
- // log
- Utils.log(`Image Tracker: found ${keypoints.length} keypoints in reference image "${referenceImage.name}"`);
- // set the training map, so that we can map all keypoints of the current image to the current image
- this._trainingMap.referenceImage.push(referenceImage);
- for (let i = 0; i < keypoints.length; i++) {
- this._trainingMap.keypoints.push(keypoints[i]);
- this._trainingMap.referenceImageIndex.push(this._currentImageIndex);
- }
- // the current image has been processed!
- ++this._currentImageIndex;
- // set output
- if (this._currentImageIndex >= this._image.length) {
- // finished training!
- return speedy_vision_default().Promise.resolve({
- //nextState: 'training',
- nextState: 'scanning',
- nextStateSettings: {
- keypoints: this._trainingMap.keypoints,
- },
- trackerOutput: {},
- //trackerOutput: { image, keypoints, screenSize: this.screenSize },
- });
- }
- else {
- // we're not done yet
- return speedy_vision_default().Promise.resolve({
- nextState: 'training',
- trackerOutput: {},
- //trackerOutput: { image, keypoints, screenSize: this.screenSize },
- });
- }
- }
- /**
- * Create & setup the pipeline
- * @returns pipeline
- */
- _createPipeline() {
- const pipeline = speedy_vision_default().Pipeline();
- const source = speedy_vision_default().Image.Source('source');
- const screen = speedy_vision_default().Transform.Resize('screen');
- const greyscale = speedy_vision_default().Filter.Greyscale();
- const blur = speedy_vision_default().Filter.GaussianBlur();
- const nightvision = speedy_vision_default().Filter.Nightvision();
- const nightvisionMux = speedy_vision_default().Image.Multiplexer('nightvisionMux');
- const pyramid = speedy_vision_default().Image.Pyramid();
- const detector = speedy_vision_default().Keypoint.Detector.FAST('fast');
- const descriptor = speedy_vision_default().Keypoint.Descriptor.ORB();
- const subpixel = speedy_vision_default().Keypoint.SubpixelRefiner();
- const blurredPyramid = speedy_vision_default().Image.Pyramid();
- const denoiser = speedy_vision_default().Filter.GaussianBlur();
- const clipper = speedy_vision_default().Keypoint.Clipper();
- const keypointScaler = speedy_vision_default().Keypoint.Transformer('keypointScaler');
- const keypointSink = speedy_vision_default().Keypoint.Sink('keypoints');
- const imageSink = speedy_vision_default().Image.Sink('image');
- source.media = null;
- screen.size = speedy_vision_default().Size(0, 0);
- blur.kernelSize = speedy_vision_default().Size(ORB_GAUSSIAN_KSIZE, ORB_GAUSSIAN_KSIZE);
- blur.sigma = speedy_vision_default().Vector2(ORB_GAUSSIAN_SIGMA, ORB_GAUSSIAN_SIGMA);
- nightvision.gain = NIGHTVISION_GAIN;
- nightvision.offset = NIGHTVISION_OFFSET;
- nightvision.decay = NIGHTVISION_DECAY;
- nightvision.quality = NIGHTVISION_QUALITY;
- nightvisionMux.port = SCAN_WITH_NIGHTVISION ? 1 : 0; // 1 = enable nightvision
- detector.levels = SCAN_PYRAMID_LEVELS;
- detector.scaleFactor = SCAN_PYRAMID_SCALEFACTOR;
- detector.threshold = SCAN_FAST_THRESHOLD;
- detector.capacity = 8192;
- subpixel.method = SUBPIXEL_METHOD;
- denoiser.kernelSize = speedy_vision_default().Size(SUBPIXEL_GAUSSIAN_KSIZE, SUBPIXEL_GAUSSIAN_KSIZE);
- denoiser.sigma = speedy_vision_default().Vector2(SUBPIXEL_GAUSSIAN_SIGMA, SUBPIXEL_GAUSSIAN_SIGMA);
- clipper.size = TRAIN_MAX_KEYPOINTS;
- keypointScaler.transform = speedy_vision_default().Matrix.Eye(3);
- keypointSink.turbo = false;
- // prepare input
- source.output().connectTo(screen.input());
- screen.output().connectTo(greyscale.input());
- // preprocess image
- greyscale.output().connectTo(nightvisionMux.input('in0'));
- greyscale.output().connectTo(nightvision.input());
- nightvision.output().connectTo(nightvisionMux.input('in1'));
- nightvisionMux.output().connectTo(pyramid.input());
- // keypoint detection
- pyramid.output().connectTo(detector.input());
- detector.output().connectTo(clipper.input());
- // keypoint refinement
- greyscale.output().connectTo(denoiser.input()); // reduce noise
- denoiser.output().connectTo(blurredPyramid.input());
- clipper.output().connectTo(subpixel.input('keypoints'));
- blurredPyramid.output().connectTo(subpixel.input('image'));
- // keypoint description
- greyscale.output().connectTo(blur.input());
- blur.output().connectTo(descriptor.input('image'));
- clipper.output().connectTo(descriptor.input('keypoints'));
- // prepare output
- descriptor.output().connectTo(keypointScaler.input());
- keypointScaler.output().connectTo(keypointSink.input());
- nightvisionMux.output().connectTo(imageSink.input());
- // done!
- pipeline.init(source, screen, greyscale, nightvision, nightvisionMux, pyramid, detector, blur, descriptor, clipper, denoiser, blurredPyramid, subpixel, keypointScaler, keypointSink, imageSink);
- return pipeline;
- }
- /**
- * Get reference image
- * @param keypointIndex -1 if not found
- * @returns reference image
- */
- referenceImageOfKeypoint(keypointIndex) {
- const imageIndex = this.referenceImageIndexOfKeypoint(keypointIndex);
- if (imageIndex < 0)
- return null;
- return this._trainingMap.referenceImage[imageIndex];
- }
- /**
- * Get reference image index
- * @param keypointIndex -1 if not found
- * @returns reference image index, or -1 if not found
- */
- referenceImageIndexOfKeypoint(keypointIndex) {
- const n = this._trainingMap.referenceImageIndex.length;
- if (keypointIndex < 0 || keypointIndex >= n)
- return -1;
- const imageIndex = this._trainingMap.referenceImageIndex[keypointIndex];
- if (imageIndex < 0 || imageIndex >= this._trainingMap.referenceImage.length)
- return -1;
- return imageIndex;
- }
- /**
- * Get keypoint of the trained set
- * @param keypointIndex -1 if not found
- * @returns a keypoint
- */
- referenceKeypoint(keypointIndex) {
- if (keypointIndex < 0 || keypointIndex >= this._trainingMap.keypoints.length)
- return null;
- return this._trainingMap.keypoints[keypointIndex];
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/states/scanning.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * scanning.ts
- * Scanning state of the Image Tracker
- */
-
-
-
-
- /** Default target space size (used when training) */
- const DEFAULT_TARGET_SPACE_SIZE = speedy_vision_default().Size(TRAIN_TARGET_NORMALIZED_SIZE, TRAIN_TARGET_NORMALIZED_SIZE);
- /** Port of the portal multiplexer: get new data from the camera */
- const PORT_CAMERA = 0;
- /** Port of the portal multiplexer: get previously memorized data */
- const PORT_MEMORY = 1;
- /**
- * Scanning state of the Image Tracker
- */
- class ImageTrackerScanningState extends ImageTrackerState {
- /**
- * Constructor
- * @param imageTracker
- */
- constructor(imageTracker) {
- super('scanning', imageTracker);
- this._counter = 0;
- this._bestScore = 0;
- this._bestHomography = speedy_vision_default().Matrix.Eye(3);
- }
- /**
- * Called as soon as this becomes the active state, just before update() runs for the first time
- * @param settings
- */
- onEnterState(settings) {
- const imagePortalMux = this._pipeline.node('imagePortalMux');
- const lshTables = this._pipeline.node('lshTables');
- const keypoints = settings.keypoints;
- // set attributes
- this._counter = 0;
- this._bestScore = 0;
- // reset the image memorization circuit
- imagePortalMux.port = PORT_CAMERA;
- // prepare the keypoint matcher
- if (keypoints !== undefined)
- lshTables.keypoints = keypoints;
- }
- /**
- * Post processing that takes place just after the GPU processing
- * @param result pipeline results
- * @returns state output
- */
- _afterUpdate(result) {
- const imagePortalMux = this._pipeline.node('imagePortalMux');
- const keypoints = result.keypoints;
- const matchedKeypoints = this._goodMatches(keypoints);
- // tracker output
- const trackerOutput = {
- keypoints: keypoints,
- screenSize: this.screenSize
- };
- // keep the last memorized image
- imagePortalMux.port = PORT_MEMORY;
- // have we found enough matches...?
- if (matchedKeypoints.length >= SCAN_MIN_MATCHES) {
- return this._findHomography(matchedKeypoints).then(([homography, score]) => {
- // have we found the best homography so far?
- if (score >= this._bestScore) {
- // store it only if we'll be running the pipeline again
- if (this._counter < SCAN_CONSECUTIVE_FRAMES - 1) {
- this._bestScore = score;
- this._bestHomography = homography;
- // memorize the last image, corresponding to the best homography(*)
- imagePortalMux.port = PORT_CAMERA;
- /*
-
- (*) technically speaking, this is not exactly the case. Since we're
- using turbo to download the keypoints, there's a slight difference
- between the data used to compute the homography and the last image.
- Still, assuming continuity of the video stream, this logic is
- good enough.
-
- */
- }
- }
- // find a polyline surrounding the target
- return this._findPolyline(homography, DEFAULT_TARGET_SPACE_SIZE);
- }).then(polyline => {
- // continue a little longer in the scanning state
- if (++this._counter < SCAN_CONSECUTIVE_FRAMES) {
- return {
- nextState: this.name,
- trackerOutput: Object.assign({ polyline: polyline }, trackerOutput),
- };
- }
- // this image should correspond to the best homography
- const snapshot = this._pipeline.node('imagePortalSink');
- // the reference image that we'll track
- const referenceImage = this._imageTracker._referenceImageOfKeypoint(matchedKeypoints[0].matches[0].index);
- // let's track the target!
- return {
- nextState: 'pre-tracking',
- nextStateSettings: {
- homography: this._bestHomography,
- snapshot: snapshot,
- referenceImage: referenceImage,
- },
- trackerOutput: Object.assign({ polyline: polyline }, trackerOutput),
- };
- }).catch(() => {
- // continue in the scanning state
- return {
- nextState: this.name,
- trackerOutput: trackerOutput,
- };
- });
- }
- else {
- // not enough matches...!
- this._counter = 0;
- this._bestScore = 0;
- }
- // we'll continue to scan the scene
- return speedy_vision_default().Promise.resolve({
- nextState: this.name,
- trackerOutput: trackerOutput,
- });
- }
- /**
- * Find "high quality" matches of a single reference image
- * @param keypoints
- * @returns high quality matches
- */
- _goodMatches(keypoints) {
- const matchedKeypointsPerImageIndex = Object.create(null);
- // filter "good matches"
- for (let j = keypoints.length - 1; j >= 0; j--) {
- const keypoint = keypoints[j];
- if (keypoint.matches[0].index >= 0 && keypoint.matches[1].index >= 0) {
- const d1 = keypoint.matches[0].distance, d2 = keypoint.matches[1].distance;
- // the best match should be "much better" than the second best match,
- // which means that they are "distinct enough"
- if (d1 <= SCAN_MATCH_RATIO * d2) {
- const idx1 = this._imageTracker._referenceImageIndexOfKeypoint(keypoint.matches[0].index);
- //const idx2 = this._imageTracker._referenceImageIndexOfKeypoint(keypoint.matches[1].index);
- //if(idx1 == idx2 && idx1 >= 0) {
- if (idx1 >= 0) {
- if (!Object.prototype.hasOwnProperty.call(matchedKeypointsPerImageIndex, idx1))
- matchedKeypointsPerImageIndex[idx1] = [];
- matchedKeypointsPerImageIndex[idx1].push(keypoint);
- }
- }
- }
- }
- // find the image with the most matches
- let matchedKeypoints = [];
- for (const imageIndex in matchedKeypointsPerImageIndex) {
- if (matchedKeypointsPerImageIndex[imageIndex].length > matchedKeypoints.length)
- matchedKeypoints = matchedKeypointsPerImageIndex[imageIndex];
- }
- // done!
- return matchedKeypoints;
- }
- /**
- * Find a homography matrix using matched keypoints
- * @param matchedKeypoints "good" matches only
- * @returns homography from reference image space to AR screen space & homography "quality" score
- */
- _findHomography(matchedKeypoints) {
- const srcCoords = [];
- const dstCoords = [];
- // find matching coordinates of the keypoints
- for (let i = matchedKeypoints.length - 1; i >= 0; i--) {
- const matchedKeypoint = matchedKeypoints[i];
- const referenceKeypoint = this._imageTracker._referenceKeypoint(matchedKeypoint.matches[0].index);
- if (referenceKeypoint != null) {
- srcCoords.push(referenceKeypoint.x);
- srcCoords.push(referenceKeypoint.y);
- dstCoords.push(matchedKeypoint.x);
- dstCoords.push(matchedKeypoint.y);
- }
- else {
- // this shouldn't happen
- return speedy_vision_default().Promise.reject(new DetectionError(`Invalid keypoint match index: ${matchedKeypoint.matches[0].index} from ${matchedKeypoint.toString()}`));
- }
- }
- // too few points?
- const n = srcCoords.length / 2;
- if (n < 4) {
- return speedy_vision_default().Promise.reject(new DetectionError(`Too few points to compute a homography`));
- }
- // compute a homography
- const src = speedy_vision_default().Matrix(2, n, srcCoords);
- const dst = speedy_vision_default().Matrix(2, n, dstCoords);
- const mask = speedy_vision_default().Matrix.Zeros(1, n);
- const homography = speedy_vision_default().Matrix.Zeros(3);
- return speedy_vision_default().Matrix.findHomography(homography, src, dst, {
- method: 'pransac',
- reprojectionError: SCAN_RANSAC_REPROJECTIONERROR,
- numberOfHypotheses: 512,
- bundleSize: 128,
- mask: mask,
- }).then(homography => {
- // check if this is a valid homography
- const a00 = homography.at(0, 0);
- if (Number.isNaN(a00))
- throw new DetectionError(`Can't compute homography`);
- // count the number of inliers
- const inliers = mask.read();
- let inlierCount = 0;
- for (let i = inliers.length - 1; i >= 0; i--)
- inlierCount += inliers[i];
- const score = inlierCount / inliers.length;
- // done!
- return [homography, score];
- });
- }
- /**
- * Create & setup the pipeline
- * @returns pipeline
- */
- _createPipeline() {
- const pipeline = speedy_vision_default().Pipeline();
- const source = speedy_vision_default().Image.Source('source');
- const screen = speedy_vision_default().Transform.Resize('screen');
- const greyscale = speedy_vision_default().Filter.Greyscale();
- const blur = speedy_vision_default().Filter.GaussianBlur();
- const nightvision = speedy_vision_default().Filter.Nightvision();
- const nightvisionMux = speedy_vision_default().Image.Multiplexer('nightvisionMux');
- const pyramid = speedy_vision_default().Image.Pyramid();
- const detector = speedy_vision_default().Keypoint.Detector.FAST();
- const descriptor = speedy_vision_default().Keypoint.Descriptor.ORB();
- const clipper = speedy_vision_default().Keypoint.Clipper();
- const lshTables = speedy_vision_default().Keypoint.Matcher.StaticLSHTables('lshTables');
- const knn = speedy_vision_default().Keypoint.Matcher.LSHKNN();
- const keypointSink = speedy_vision_default().Keypoint.SinkOfMatchedKeypoints('keypoints');
- const imagePortalSink = speedy_vision_default().Image.Portal.Sink('imagePortalSink');
- const imagePortalSource = speedy_vision_default().Image.Portal.Source('imagePortalSource');
- const imagePortalMux = speedy_vision_default().Image.Multiplexer('imagePortalMux');
- const imagePortalBuffer = speedy_vision_default().Image.Buffer();
- const imagePortalCopy = speedy_vision_default().Transform.Resize();
- //const imageSink = Speedy.Image.Sink('image');
- source.media = null;
- screen.size = speedy_vision_default().Size(0, 0);
- blur.kernelSize = speedy_vision_default().Size(ORB_GAUSSIAN_KSIZE, ORB_GAUSSIAN_KSIZE);
- blur.sigma = speedy_vision_default().Vector2(ORB_GAUSSIAN_SIGMA, ORB_GAUSSIAN_SIGMA);
- nightvision.gain = NIGHTVISION_GAIN;
- nightvision.offset = NIGHTVISION_OFFSET;
- nightvision.decay = NIGHTVISION_DECAY;
- nightvision.quality = NIGHTVISION_QUALITY;
- nightvisionMux.port = SCAN_WITH_NIGHTVISION ? 1 : 0; // 1 = enable nightvision
- detector.levels = SCAN_PYRAMID_LEVELS;
- detector.scaleFactor = SCAN_PYRAMID_SCALEFACTOR;
- detector.threshold = SCAN_FAST_THRESHOLD;
- detector.capacity = 2048;
- clipper.size = SCAN_MAX_KEYPOINTS;
- lshTables.keypoints = [];
- lshTables.numberOfTables = SCAN_LSH_TABLES;
- lshTables.hashSize = SCAN_LSH_HASHSIZE;
- knn.k = 2;
- knn.quality = 'default';
- //knn.quality = 'fastest';
- imagePortalSource.source = imagePortalSink;
- imagePortalMux.port = PORT_CAMERA; // 0 = camera stream; 1 = lock image
- imagePortalCopy.size = speedy_vision_default().Size(0, 0);
- imagePortalCopy.scale = speedy_vision_default().Vector2(1, 1);
- keypointSink.turbo = true;
- // prepare input
- source.output().connectTo(screen.input());
- screen.output().connectTo(greyscale.input());
- // preprocess image
- greyscale.output().connectTo(blur.input());
- greyscale.output().connectTo(nightvisionMux.input('in0'));
- greyscale.output().connectTo(nightvision.input());
- nightvision.output().connectTo(nightvisionMux.input('in1'));
- nightvisionMux.output().connectTo(pyramid.input());
- // keypoint detection
- pyramid.output().connectTo(detector.input());
- detector.output().connectTo(clipper.input());
- // keypoint description
- blur.output().connectTo(descriptor.input('image'));
- clipper.output().connectTo(descriptor.input('keypoints'));
- // keypoint matching
- descriptor.output().connectTo(knn.input('keypoints'));
- lshTables.output().connectTo(knn.input('lsh'));
- // prepare output
- clipper.output().connectTo(keypointSink.input());
- knn.output().connectTo(keypointSink.input('matches'));
- //pyramid.output().connectTo(imageSink.input());
- // memorize image
- source.output().connectTo(imagePortalBuffer.input());
- imagePortalBuffer.output().connectTo(imagePortalMux.input('in0'));
- imagePortalSource.output().connectTo(imagePortalCopy.input());
- imagePortalCopy.output().connectTo(imagePortalMux.input('in1'));
- imagePortalMux.output().connectTo(imagePortalSink.input());
- // done!
- pipeline.init(source, screen, greyscale, blur, nightvision, nightvisionMux, pyramid, detector, descriptor, clipper, lshTables, knn, keypointSink, imagePortalSink, imagePortalSource, imagePortalMux, imagePortalBuffer, imagePortalCopy);
- return pipeline;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/states/pre-tracking.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * pre-tracking.ts
- * Pre-tracking state of the Image Tracker
- */
-
-
-
-
-
- /** Default target space size (used when training) */
- const pre_tracking_DEFAULT_TARGET_SPACE_SIZE = speedy_vision_default().Size(TRAIN_TARGET_NORMALIZED_SIZE, TRAIN_TARGET_NORMALIZED_SIZE);
- /** Use the camera stream as the input of the pipeline */
- const PORT_CAMERA_IMAGE = 1;
- /** Use the reference image as the input of the pipeline */
- const PORT_REFERENCE_IMAGE = 0;
- /**
- * The pre-tracking state of the Image Tracker is a new training
- * phase for the particular, actual target we'll be tracking
- */
- class ImageTrackerPreTrackingState extends ImageTrackerState {
- /**
- * Constructor
- * @param imageTracker
- */
- constructor(imageTracker) {
- super('pre-tracking', imageTracker);
- this._homography = speedy_vision_default().Matrix.Eye(3);
- this._referenceImage = null;
- this._step = 'read-reference-image';
- this._referenceKeypoints = [];
- this._iterations = 0;
- }
- /**
- * Called as soon as this becomes the active state, just before update() runs for the first time
- * @param settings
- */
- onEnterState(settings) {
- const imagePortalSource = this._pipeline.node('imagePortalSource');
- const muxOfReferenceKeypoints = this._pipeline.node('muxOfReferenceKeypoints');
- const muxOfBufferOfReferenceKeypoints = this._pipeline.node('muxOfBufferOfReferenceKeypoints');
- const bufferOfReferenceKeypoints = this._pipeline.node('bufferOfReferenceKeypoints');
- const homography = settings.homography;
- const referenceImage = settings.referenceImage;
- const snapshot = settings.snapshot;
- // this shouldn't happen
- if (!referenceImage)
- throw new TrackingError(`Can't track a null reference image`);
- // set attributes
- this._homography = homography;
- this._referenceImage = referenceImage;
- this._step = 'read-reference-image';
- this._referenceKeypoints = [];
- this._iterations = 0;
- // setup the pipeline
- imagePortalSource.source = snapshot;
- muxOfReferenceKeypoints.port = 0;
- muxOfBufferOfReferenceKeypoints.port = 0;
- bufferOfReferenceKeypoints.frozen = false;
- }
- /**
- * Called just before the GPU processing
- * @returns promise
- */
- _beforeUpdate() {
- const referenceImage = this._referenceImage;
- const source = this._pipeline.node('source');
- const sourceMux = this._pipeline.node('sourceMux');
- const imageRectifier = this._pipeline.node('imageRectifier');
- const keypointRectifier = this._pipeline.node('keypointRectifier');
- const borderClipper = this._pipeline.node('borderClipper');
- const screenSize = this.screenSize;
- // set the source media to the reference image we're going to track
- const targetMedia = this._imageTracker.database._findMedia(referenceImage.name);
- source.media = targetMedia;
- // setup the source multiplexer
- if (this._step == 'read-reference-image')
- sourceMux.port = PORT_REFERENCE_IMAGE;
- else
- sourceMux.port = PORT_CAMERA_IMAGE;
- // clip keypoints from the borders of the target image
- borderClipper.imageSize = screenSize;
- borderClipper.borderSize = speedy_vision_default().Vector2(screenSize.width * TRACK_CLIPPING_BORDER, screenSize.height * TRACK_CLIPPING_BORDER);
- // rectify the image
- const rectify = (this._step == 'read-reference-image') ?
- this._findRectificationMatrixOfFullscreenImage(targetMedia, screenSize) :
- this._findRectificationMatrixOfCameraImage(this._homography, pre_tracking_DEFAULT_TARGET_SPACE_SIZE, targetMedia, screenSize);
- return rectify.then(rectificationMatrix => {
- imageRectifier.transform = rectificationMatrix;
- });
- }
- /**
- * Post processing that takes place just after the GPU processing
- * @param result pipeline results
- * @returns state output
- */
- _afterUpdate(result) {
- const referenceImage = this._referenceImage;
- const imagePortalSink = this._pipeline.node('imagePortal');
- const keypointPortalSink = this._pipeline.node('keypointPortalSink');
- const muxOfReferenceKeypoints = this._pipeline.node('muxOfReferenceKeypoints');
- const muxOfBufferOfReferenceKeypoints = this._pipeline.node('muxOfBufferOfReferenceKeypoints');
- const bufferOfReferenceKeypoints = this._pipeline.node('bufferOfReferenceKeypoints');
- const keypoints = result.keypoints;
- const image = result.image;
- // tracker output
- const trackerOutput = {
- keypoints: image !== undefined ? keypoints : undefined,
- image: image,
- screenSize: this.screenSize,
- };
- // decide what to do next
- switch (this._step) {
- case 'read-reference-image': {
- // enable matching
- muxOfReferenceKeypoints.port = 1;
- // store reference keypoints
- this._referenceKeypoints = keypoints;
- // next step
- this._step = 'warp-camera-image';
- return speedy_vision_default().Promise.resolve({
- nextState: 'pre-tracking',
- trackerOutput: trackerOutput,
- });
- }
- case 'warp-camera-image': {
- // freeze reference keypoints
- bufferOfReferenceKeypoints.frozen = true;
- muxOfBufferOfReferenceKeypoints.port = 1;
- // refine warp?
- if (++this._iterations < TRACK_REFINEMENT_ITERATIONS)
- this._step = 'warp-camera-image';
- else
- this._step = 'train-camera-image';
- // warp image & go to next step
- return this._findWarp(keypoints, this._referenceKeypoints).then(warp => this._homography.setTo(this._homography.times(warp))).then(_ => ({
- nextState: 'pre-tracking',
- trackerOutput: trackerOutput,
- })).catch(err => {
- Utils.warning(`Can't pre-track target image "${referenceImage.name}". ${err.toString()}`);
- return {
- nextState: 'scanning',
- trackerOutput: trackerOutput,
- };
- });
- }
- case 'train-camera-image': {
- // log
- Utils.log(`Took a snapshot of target image "${referenceImage.name}". Found ${keypoints.length} keypoints.`);
- // change the coordinates
- return this._changeSpace(this._homography, this.screenSize).then(homography => {
- // we're ready to track the target!
- return speedy_vision_default().Promise.resolve({
- //nextState: 'pre-tracking',
- nextState: 'tracking',
- trackerOutput: trackerOutput,
- nextStateSettings: {
- homography: homography,
- referenceImage: referenceImage,
- templateKeypoints: keypoints,
- keypointPortalSink: keypointPortalSink,
- imagePortalSink: imagePortalSink,
- screenSize: this.screenSize,
- },
- });
- });
- }
- }
- }
- /**
- * Find an adjustment warp between the camera image and the reference image
- * @param dstKeypoints destination
- * @param srcKeypoints source
- * @returns a promise that resolves to a 3x3 homography
- */
- _findWarp(dstKeypoints, srcKeypoints) {
- //return Speedy.Promise.resolve(Speedy.Matrix.Eye(3));
- const srcCoords = [];
- const dstCoords = [];
- // find matching coordinates of the keypoints
- for (let i = 0; i < dstKeypoints.length; i++) {
- const dstKeypoint = dstKeypoints[i];
- if (dstKeypoint.matches[0].index >= 0 && dstKeypoint.matches[1].index >= 0) {
- const d1 = dstKeypoint.matches[0].distance, d2 = dstKeypoint.matches[1].distance;
- // the best match should be "much better" than the second best match,
- // which means that they are "distinct enough"
- if (d1 <= TRACK_MATCH_RATIO * d2) {
- const srcKeypoint = srcKeypoints[dstKeypoint.matches[0].index];
- srcCoords.push(srcKeypoint.x);
- srcCoords.push(srcKeypoint.y);
- dstCoords.push(dstKeypoint.x);
- dstCoords.push(dstKeypoint.y);
- }
- }
- }
- // too few points?
- const n = srcCoords.length / 2;
- if (n < 4) {
- return speedy_vision_default().Promise.reject(new TrackingError('Too few points to compute a warp'));
- }
- // compute warp
- const model = speedy_vision_default().Matrix.Eye(3);
- return this._findKeypointWarp().then(transform =>
- // rectify keypoints
- speedy_vision_default().Matrix.applyAffineTransform(speedy_vision_default().Matrix.Zeros(2, 2 * n), speedy_vision_default().Matrix(2, 2 * n, srcCoords.concat(dstCoords)), transform.block(0, 1, 0, 2))).then(points =>
- // find warp
- speedy_vision_default().Matrix.findAffineTransform(model.block(0, 1, 0, 2), points.block(0, 1, 0, n - 1), points.block(0, 1, n, 2 * n - 1), {
- method: 'pransac',
- reprojectionError: TRACK_RANSAC_REPROJECTIONERROR,
- numberOfHypotheses: 512 * 4,
- bundleSize: 128,
- })).then(_ => {
- // validate the model
- const a00 = model.at(0, 0);
- if (Number.isNaN(a00))
- throw new TrackingError(`Can't compute warp: bad keypoints`);
- // done!
- return model;
- });
- }
- /**
- * Find a warp to be applied to the keypoints
- * @returns affine transform
- */
- _findKeypointWarp() {
- const referenceImage = this._referenceImage;
- const media = this._imageTracker.database._findMedia(referenceImage.name);
- const screenSize = this.screenSize;
- // no rotation is needed
- if (!this._mustRotateWarpedImage(media, screenSize))
- return speedy_vision_default().Promise.resolve(speedy_vision_default().Matrix.Eye(3));
- // rotate by 90 degrees clockwise around the pivot
- const px = screenSize.width / 2, py = screenSize.height / 2; // pivot
- return speedy_vision_default().Promise.resolve(speedy_vision_default().Matrix(3, 3, [
- 0, 1, 0,
- -1, 0, 0,
- py + px, py - px, 1,
- ]));
- }
- /**
- * Change the space of the homography in order to improve tracking quality
- * @param homography mapping coordinates from normalized target space to AR screen space
- * @param screenSize AR screen size
- * @returns homography mapping coordinates from AR screen space to AR screen space
- */
- _changeSpace(homography, screenSize) {
- const sw = screenSize.width, sh = screenSize.height;
- const screen = speedy_vision_default().Matrix(2, 4, [0, 0, sw, 0, sw, sh, 0, sh]);
- const mat = speedy_vision_default().Matrix.Zeros(3);
- return this._findPolylineCoordinates(homography, pre_tracking_DEFAULT_TARGET_SPACE_SIZE).then(polyline => speedy_vision_default().Matrix.perspective(mat, screen, polyline));
- }
- /**
- * Create & setup the pipeline
- * @returns pipeline
- */
- _createPipeline() {
- const pipeline = speedy_vision_default().Pipeline();
- const source = speedy_vision_default().Image.Source('source');
- const imagePortalSource = speedy_vision_default().Image.Portal.Source('imagePortalSource');
- const sourceMux = speedy_vision_default().Image.Multiplexer('sourceMux');
- const screen = speedy_vision_default().Transform.Resize('screen');
- const greyscale = speedy_vision_default().Filter.Greyscale();
- const imageRectifier = speedy_vision_default().Transform.PerspectiveWarp('imageRectifier');
- const nightvision = speedy_vision_default().Filter.Nightvision();
- const nightvisionMux = speedy_vision_default().Image.Multiplexer();
- const detector = speedy_vision_default().Keypoint.Detector.Harris();
- const descriptor = speedy_vision_default().Keypoint.Descriptor.ORB();
- const blur = speedy_vision_default().Filter.GaussianBlur();
- const clipper = speedy_vision_default().Keypoint.Clipper();
- const borderClipper = speedy_vision_default().Keypoint.BorderClipper('borderClipper');
- const denoiser = speedy_vision_default().Filter.GaussianBlur();
- const subpixel = speedy_vision_default().Keypoint.SubpixelRefiner();
- const matcher = speedy_vision_default().Keypoint.Matcher.BFKNN();
- const keypointRectifier = speedy_vision_default().Keypoint.Transformer('keypointRectifier');
- const keypointPortalSink = speedy_vision_default().Keypoint.Portal.Sink('keypointPortalSink');
- const keypointPortalSource = speedy_vision_default().Keypoint.Portal.Source('keypointPortalSource');
- const muxOfReferenceKeypoints = speedy_vision_default().Keypoint.Multiplexer('muxOfReferenceKeypoints');
- const bufferOfReferenceKeypoints = speedy_vision_default().Keypoint.Buffer('bufferOfReferenceKeypoints');
- const muxOfBufferOfReferenceKeypoints = speedy_vision_default().Keypoint.Multiplexer('muxOfBufferOfReferenceKeypoints');
- const keypointSink = speedy_vision_default().Keypoint.SinkOfMatchedKeypoints('keypoints');
- const imageSink = speedy_vision_default().Image.Sink('image');
- source.media = null;
- screen.size = speedy_vision_default().Size(0, 0);
- imagePortalSource.source = null;
- imageRectifier.transform = speedy_vision_default().Matrix.Eye(3);
- sourceMux.port = PORT_REFERENCE_IMAGE;
- nightvision.gain = NIGHTVISION_GAIN;
- nightvision.offset = NIGHTVISION_OFFSET;
- nightvision.decay = NIGHTVISION_DECAY;
- nightvision.quality = NIGHTVISION_QUALITY;
- nightvisionMux.port = TRACK_WITH_NIGHTVISION ? 1 : 0; // 1 = enable nightvision
- blur.kernelSize = speedy_vision_default().Size(ORB_GAUSSIAN_KSIZE, ORB_GAUSSIAN_KSIZE);
- blur.sigma = speedy_vision_default().Vector2(ORB_GAUSSIAN_SIGMA, ORB_GAUSSIAN_SIGMA);
- denoiser.kernelSize = speedy_vision_default().Size(SUBPIXEL_GAUSSIAN_KSIZE, SUBPIXEL_GAUSSIAN_KSIZE);
- denoiser.sigma = speedy_vision_default().Vector2(SUBPIXEL_GAUSSIAN_SIGMA, SUBPIXEL_GAUSSIAN_SIGMA);
- detector.quality = TRACK_HARRIS_QUALITY;
- detector.capacity = TRACK_DETECTOR_CAPACITY;
- subpixel.method = SUBPIXEL_METHOD;
- clipper.size = TRACK_MAX_KEYPOINTS;
- borderClipper.imageSize = screen.size;
- borderClipper.borderSize = speedy_vision_default().Vector2(0, 0);
- matcher.k = 2;
- keypointRectifier.transform = speedy_vision_default().Matrix.Eye(3);
- keypointPortalSource.source = keypointPortalSink;
- muxOfReferenceKeypoints.port = 0;
- muxOfBufferOfReferenceKeypoints.port = 0;
- bufferOfReferenceKeypoints.frozen = false;
- keypointSink.turbo = false;
- // prepare input
- source.output().connectTo(sourceMux.input('in0')); // port 0: reference image
- imagePortalSource.output().connectTo(sourceMux.input('in1')); // port 1: camera image (via portal)
- sourceMux.output().connectTo(screen.input());
- screen.output().connectTo(greyscale.input());
- // preprocess images
- greyscale.output().connectTo(imageRectifier.input());
- imageRectifier.output().connectTo(nightvisionMux.input('in0'));
- imageRectifier.output().connectTo(nightvision.input());
- nightvision.output().connectTo(nightvisionMux.input('in1'));
- nightvisionMux.output().connectTo(blur.input());
- // keypoint detection & clipping
- nightvisionMux.output().connectTo(detector.input());
- detector.output().connectTo(borderClipper.input());
- borderClipper.output().connectTo(clipper.input());
- // keypoint refinement
- imageRectifier.output().connectTo(denoiser.input());
- denoiser.output().connectTo(subpixel.input('image'));
- clipper.output().connectTo(subpixel.input('keypoints'));
- // keypoint description
- blur.output().connectTo(descriptor.input('image'));
- subpixel.output().connectTo(descriptor.input('keypoints'));
- // keypoint matching
- descriptor.output().connectTo(muxOfReferenceKeypoints.input('in0'));
- muxOfBufferOfReferenceKeypoints.output().connectTo(muxOfReferenceKeypoints.input('in1'));
- muxOfReferenceKeypoints.output().connectTo(matcher.input('database'));
- descriptor.output().connectTo(matcher.input('keypoints'));
- // store reference keypoints
- keypointPortalSource.output().connectTo(muxOfBufferOfReferenceKeypoints.input('in0'));
- bufferOfReferenceKeypoints.output().connectTo(muxOfBufferOfReferenceKeypoints.input('in1'));
- keypointPortalSource.output().connectTo(bufferOfReferenceKeypoints.input());
- // portals
- descriptor.output().connectTo(keypointPortalSink.input());
- // prepare output
- descriptor.output().connectTo(keypointRectifier.input());
- keypointRectifier.output().connectTo(keypointSink.input());
- matcher.output().connectTo(keypointSink.input('matches'));
- //imageRectifier.output().connectTo(imageSink.input());
- // done!
- pipeline.init(source, imagePortalSource, sourceMux, screen, greyscale, imageRectifier, nightvision, nightvisionMux, blur, detector, subpixel, clipper, borderClipper, denoiser, descriptor, keypointPortalSource, muxOfReferenceKeypoints, matcher, bufferOfReferenceKeypoints, muxOfBufferOfReferenceKeypoints, keypointRectifier, keypointSink, keypointPortalSink);
- return pipeline;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/image-tracker-event.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * image-tracker-event.ts
- * Events emitted by an Image Tracker
- */
-
- /**
- * An event emitted by an Image Tracker
- */
- class ImageTrackerEvent extends AREvent {
- /**
- * Constructor
- * @param type event type
- * @param referenceImage optional reference image
- */
- constructor(type, referenceImage) {
- super(type);
- this._referenceImage = referenceImage;
- }
- /**
- * Reference image
- */
- get referenceImage() {
- return this._referenceImage;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/geometry/camera-model.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * camera-model.ts
- * Camera model
- */
-
-
-
-
- /** Number of samples we'll be keeping to help calibrate the camera */
- const INTRISICS_SAMPLES = 401; //201; //31; // odd number
- /** Whether or not to auto-calibrate the camera */
- const FOVY_AUTODETECT = false; //true;
- /** A guess of the vertical field-of-view of a generic camera, in degrees */
- const FOVY_GUESS = 45; //50; // will be part of the viewing frustum
- /** Number of iterations used to refine the estimated pose */
- const POSE_ITERATIONS = 30;
- /** Number of samples used in the rotation filter */
- const ROTATION_FILTER_SAMPLES = 10;
- /** Number of samples used in the translation filter */
- const TRANSLATION_FILTER_SAMPLES = 10;
- /** Convert degrees to radians */
- const DEG2RAD = 0.017453292519943295; // pi / 180
- /** Convert radians to degrees */
- const RAD2DEG = 57.29577951308232; // 180 / pi
- /** Numerical tolerance */
- const EPSILON = 1e-6;
- /** Index of the horizontal focal length in the camera intrinsics matrix (column-major format) */
- const FX = 0;
- /** Index of the vertical focal length in the camera intrinsics matrix */
- const FY = 4;
- /** Index of the horizontal position of the principal point in the camera intrinsics matrix */
- const U0 = 6;
- /** Index of the vertical position of the principal point in the camera intrinsics matrix */
- const V0 = 7;
- /** Translation refinement: predefined buffers for efficiency */
- const TRANSLATION_REFINEMENT_BUFFERS = (() => {
- const l = 1.0;
- const x = [0, l, 0, -l, 0];
- const y = [-l, 0, l, 0, 0];
- const n = x.length;
- return Object.freeze({
- x, y,
- a1: new Array(n),
- a2: new Array(n),
- a3: new Array(n),
- m: new Array(3 * n * 3),
- v: new Array(3 * n),
- t: new Array(3),
- r: new Array(3 * n),
- c: new Array(3),
- Mc: new Array(3 * n),
- });
- })();
- /** Translation refinement: number of iterations */
- const TRANSLATION_REFINEMENT_ITERATIONS = 3; // 1; // 5;
- /** Translation refinement: number of samples */
- const TRANSLATION_REFINEMENT_SAMPLES = 5; // TRANSLATION_REFINEMENT_BUFFERS.x.length;
- /** Translation refinement: the triple of the number of samples */
- const TRANSLATION_REFINEMENT_SAMPLES_3X = 15; //3 * TRANSLATION_REFINEMENT_SAMPLES;
- /**
- * Camera model
- */
- class CameraModel {
- /**
- * Constructor
- */
- constructor() {
- this._screenSize = speedy_vision_default().Size(0, 0);
- this._matrix = speedy_vision_default().Matrix.Eye(3, 4);
- this._intrinsics = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // identity matrix
- this._extrinsics = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]; // no rotation & no translation [ R | t ] = [ I | 0 ]
- this._f = (new Array(INTRISICS_SAMPLES)).fill(this._intrinsics[FY]);
- this._fp = 0;
- this._partialRotationBuffer = [];
- this._translationBuffer = [];
- }
- /**
- * Initialize the model
- * @param screenSize
- */
- init(screenSize) {
- // validate
- if (screenSize.area() == 0)
- throw new IllegalArgumentError(`Can't initialize the camera model with screenSize = ${screenSize.toString()}`);
- // set the screen size
- this._screenSize.width = screenSize.width;
- this._screenSize.height = screenSize.height;
- // reset the model
- this._resetIntrinsics();
- this._resetExtrinsics();
- // log
- Utils.log(`Initializing the camera model...`);
- }
- /**
- * Release the model
- */
- release() {
- this.reset();
- return null;
- }
- /**
- * Update the camera model
- * @param homography 3x3 perspective transform
- * @param screenSize may change over time (e.g., when going from portrait to landscape or vice-versa)
- * @returns promise that resolves to a camera matrix
- */
- update(homography, screenSize) {
- // validate the shape of the homography
- if (homography.rows != 3 || homography.columns != 3)
- throw new IllegalArgumentError(`Camera model: provide a homography matrix`);
- // validate screenSize
- if (screenSize.area() == 0)
- throw new IllegalArgumentError(`Camera model: invalid screenSize = ${screenSize.toString()}`);
- // changed screen size?
- if (!this._screenSize.equals(screenSize)) {
- Utils.log(`Camera model: detected a change in screen size...`);
- // update the screen size
- this._screenSize.width = screenSize.width;
- this._screenSize.height = screenSize.height;
- // reset camera
- this.reset();
- }
- // read the entries of the homography
- const h = homography.read();
- const h11 = h[0], h12 = h[3], h13 = h[6], h21 = h[1], h22 = h[4], h23 = h[7], h31 = h[2], h32 = h[5], h33 = h[8];
- // validate the homography (homography matrices aren't singular)
- const det = h13 * (h21 * h32 - h22 * h31) - h23 * (h11 * h32 - h12 * h31) + h33 * (h11 * h22 - h12 * h21);
- if (Math.abs(det) < EPSILON) {
- Utils.warning(`Can't update the camera model using an invalid homography matrix`);
- return speedy_vision_default().Promise.resolve(this._matrix);
- }
- // estimate the focal length (auto-calibration)
- const f = this._estimateFocal(homography);
- if (f > 0)
- this._storeFocal(f);
- //console.log(this.fovy * RAD2DEG);
- // estimate the pose
- const pose = this._estimatePose(homography);
- this._storePose(pose);
- // compute the camera matrix
- const C = this.denormalizer();
- const K = speedy_vision_default().Matrix(3, 3, this._intrinsics);
- const E = speedy_vision_default().Matrix(3, 4, this._extrinsics);
- this._matrix.setToSync(K.times(E).times(C));
- //console.log("intrinsics -----------", K.toString());
- //console.log("matrix ----------------",this._matrix.toString());
- return speedy_vision_default().Promise.resolve(this._matrix);
- }
- /**
- * Reset camera model
- */
- reset() {
- this._resetIntrinsics();
- this._resetExtrinsics();
- }
- /**
- * The camera matrix that maps the 3D normalized space [-1,1]^3 to the
- * 2D AR screen space (measured in pixels)
- * @returns 3x4 camera matrix
- */
- get matrix() {
- return this._matrix;
- }
- /**
- * Camera intrinsics matrix
- * @returns 3x3 intrinsics matrix in column-major format
- */
- get intrinsics() {
- return this._intrinsics;
- }
- /**
- * Camera extrinsics matrix
- * @returns 3x4 extrinsics matrix [ R | t ] in column-major format
- */
- get extrinsics() {
- return this._extrinsics;
- }
- /**
- * Convert coordinates from normalized space [-1,1]^3 to a
- * "3D pixel space" based on the dimensions of the AR screen.
- *
- * We perform a 180-degrees rotation around the x-axis so that
- * it looks nicer (the y-axis grows downwards in image space).
- *
- * The final camera matrix is P = K * [ R | t ] * C, where
- * C is this conversion matrix. The intent behind this is to
- * make tracking independent of target and screen sizes.
- *
- * Reminder: we use a right-handed coordinate system in 3D!
- * In 2D image space the coordinate system is left-handed.
- *
- * @returns 4x4 conversion matrix C
- */
- denormalizer() {
- const w = this._screenSize.width / 2; // half width, in pixels
- const h = this._screenSize.height / 2; // half height, in pixels
- const d = Math.min(w, h); // virtual unit length, in pixels
- /*
- return Speedy.Matrix(4, 4, [
- 1, 0, 0, 0,
- 0,-1, 0, 0,
- 0, 0,-1, 0,
- w/d, h/d, 0, 1/d
- ]);
- */
- return speedy_vision_default().Matrix(4, 4, [
- d, 0, 0, 0,
- 0, -d, 0, 0,
- 0, 0, -d, 0,
- w, h, 0, 1,
- ]);
- }
- /**
- * Size of the AR screen space, in pixels
- * @returns size in pixels
- */
- get screenSize() {
- return this._screenSize;
- }
- /**
- * Focal length in pixel units (projection distance in the pinhole camera model)
- * same as (focal length in mm) * (number of pixels per world unit in pixels/mm)
- * @returns focal length
- */
- get focalLength() {
- return this._intrinsics[FY]; // fx == fy
- }
- /**
- * Horizontal field-of-view, given in radians
- * @returns vertical field-of-view
- */
- get fovx() {
- return 2 * Math.atan(this._intrinsics[U0] / this._intrinsics[FX]);
- }
- /**
- * Vertical field-of-view, given in radians
- * @returns vertical field-of-view
- */
- get fovy() {
- return 2 * Math.atan(this._intrinsics[V0] / this._intrinsics[FY]);
- }
- /**
- * Principal point
- * @returns principal point, in pixel coordinates
- */
- principalPoint() {
- return speedy_vision_default().Point2(this._intrinsics[U0], this._intrinsics[V0]);
- }
- /**
- * Reset camera extrinsics
- */
- _resetExtrinsics() {
- // set the rotation matrix to the identity
- this._extrinsics.fill(0);
- this._extrinsics[0] = this._extrinsics[4] = this._extrinsics[8] = 1;
- // reset filters
- this._partialRotationBuffer.length = 0;
- this._translationBuffer.length = 0;
- }
- /**
- * Reset camera intrinsics
- */
- _resetIntrinsics() {
- const u0 = this._screenSize.width / 2;
- const v0 = this._screenSize.height / 2;
- const f = v0 / Math.tan(DEG2RAD * FOVY_GUESS / 2);
- this._intrinsics[FX] = f;
- this._intrinsics[FY] = f;
- this._intrinsics[U0] = u0;
- this._intrinsics[V0] = v0;
- this._f.fill(this._intrinsics[FY]);
- this._fp = 0;
- }
- /**
- * Estimate the focal length
- * @param homography valid homography
- * @returns estimated focal length, or 0 on error
- */
- _estimateFocal(homography) {
- // auto-detect the focal length?
- if (!FOVY_AUTODETECT)
- return 0;
- // read the entries of the homography
- const h = homography.read();
- const h11 = h[0], h12 = h[3]; //, h13 = h[6];
- const h21 = h[1], h22 = h[4]; //, h23 = h[7];
- const h31 = h[2], h32 = h[5]; //, h33 = h[8];
- // read the principal point
- const u0 = this._intrinsics[U0];
- const v0 = this._intrinsics[V0];
- // estimate the focal length based on the orthogonality
- // constraint r1'r2 = 0 of a rotation matrix
- const f2 = -((h11 / h31 - u0) * (h12 / h32 - u0) + (h21 / h31 - v0) * (h22 / h32 - v0));
- // can't estimate it?
- if (f2 < 0)
- return this._intrinsics[FY];
- //return 0;
- // done!
- return Math.sqrt(f2);
- }
- /**
- * Store an estimated focal length
- * @param f estimated focal length
- */
- _storeFocal(f) {
- // store the focal length
- this._f[this._fp] = f;
- this._fp = (this._fp + 1) % INTRISICS_SAMPLES;
- // take the median of the estimated focal lengths
- const sorted = this._f.concat([]).sort((a, b) => a - b);
- const median = sorted[sorted.length >>> 1];
- // update the intrinsics matrix
- this._intrinsics[FX] = this._intrinsics[FY] = median;
- /*
- // test
- const u0 = this._intrinsics[U0];
- const v0 = this._intrinsics[V0];
- const fovx = 2 * Math.atan(u0 / median) * RAD2DEG;
- const fovy = 2 * Math.atan(v0 / median) * RAD2DEG;
- console.log('---------------');
- console.log("fov:",fovx,fovy);
- console.log("f:",median);
- */
- }
- /**
- * Compute a normalized homography H' = K^(-1) * H for an
- * ideal pinhole with f = 1 and principal point = (0,0)
- * @param homography homography H to be normalized
- * @param f focal length
- * @returns normalized homography H'
- */
- _normalizeHomography(homography, f = this._intrinsics[FY]) {
- const h = homography.read();
- const u0 = this._intrinsics[U0];
- const v0 = this._intrinsics[V0];
- const h11 = h[0] - u0 * h[2], h12 = h[3] - u0 * h[5], h13 = h[6] - u0 * h[8];
- const h21 = h[1] - v0 * h[2], h22 = h[4] - v0 * h[5], h23 = h[7] - v0 * h[8];
- const h31 = h[2] * f, h32 = h[5] * f, h33 = h[8] * f;
- return speedy_vision_default().Matrix(3, 3, [
- h11, h21, h31,
- h12, h22, h32,
- h13, h23, h33,
- ]);
- }
- /**
- * Estimate [ r1 | r2 | t ], where r1, r2 are orthonormal and t is a translation vector
- * @param normalizedHomography based on the ideal pinhole (where calibration K = I)
- * @returns a 3x3 matrix
- */
- _estimatePartialPose(normalizedHomography) {
- const h = normalizedHomography.read();
- const h11 = h[0], h12 = h[3], h13 = h[6];
- const h21 = h[1], h22 = h[4], h23 = h[7];
- const h31 = h[2], h32 = h[5], h33 = h[8];
- // select the sign so that t3 = tz > 0
- const sign = h33 >= 0 ? 1 : -1;
- // compute the scale factor
- const h1norm = Math.sqrt(h11 * h11 + h21 * h21 + h31 * h31);
- const h2norm = Math.sqrt(h12 * h12 + h22 * h22 + h32 * h32);
- //const scale = sign * 2 / (h1norm + h2norm);
- //const scale = sign / h1norm;
- //const scale = sign / h2norm;
- const scale = sign / Math.max(h1norm, h2norm); // this seems to work. why?
- // invalid homography?
- if (Number.isNaN(scale))
- return speedy_vision_default().Matrix(3, 3, (new Array(9)).fill(Number.NaN));
- // we expect h1norm to be approximately h2norm, but sometimes there is a lot of noise
- // if h1norm is not approximately h2norm, it means that the first two columns of
- // the normalized homography are not really encoding a rotation (up to a scale)
- // what is causing this? does h3 (and h33) tell us anything about it?
- // what about the intrinsics matrix? the principal point...? the fov...?
- //console.log("h1,h2",h1norm,h2norm);
- //console.log(normalizedHomography.toString());
- // recover the translation and the rotation
- const t1 = scale * h13;
- const t2 = scale * h23;
- const t3 = scale * h33;
- const r11 = scale * h11;
- const r21 = scale * h21;
- const r31 = scale * h31;
- const r12 = scale * h12;
- const r22 = scale * h22;
- const r32 = scale * h32;
- // refine the pose
- const r = this._refineRotation(r11, r21, r31, r12, r22, r32);
- const t = this._refineTranslation(normalizedHomography, r, [t1, t2, t3]);
- //const t = [t1, t2, t3]; // faster, but less accurate
- // done!
- return speedy_vision_default().Matrix(3, 3, r.concat(t)); // this is possibly NaN... why? homography...
- }
- /**
- * Make two non-zero and non-parallel input vectors, r1 and r2, orthonormal
- * @param r11 x of r1
- * @param r21 y of r1
- * @param r31 z of r1
- * @param r12 x of r2
- * @param r22 y of r2
- * @param r32 z of r2
- * @returns a 3x2 matrix R such that R'R = I (column-major format)
- */
- _refineRotation(r11, r21, r31, r12, r22, r32) {
- /*
-
- A little technique I figured out to correct the rotation vectors
- ----------------------------------------------------------------
-
- We are given two 3x1 column-vectors r1 and r2 as input in a 3x2 matrix
- R = [ r1 | r2 ]. We would like that R'R = I, but that won't be the case
- because vectors r1 and r2 are not perfectly orthonormal due to noise.
-
- Let's first notice that R'R is symmetric. You can easily check that its
- two eigenvalues are both real and positive (as long as r1, r2 != 0 and
- r1 is not parallel to r2, but we never take such vectors as input).
-
- R'R = [ r1'r1 r1'r2 ] is of rank 2, positive-definite
- [ r1'r2 r2'r2 ]
-
- We proceed by computing an eigendecomposition Q D Q' of R'R, where Q is
- chosen to be orthogonal and D is a diagonal matrix whose entries are
- the eigenvalues of R'R.
-
- Let LL' be the Cholesky decomposition of D. Such decomposition exists
- and is trivially computed: just take the square roots of the entries of
- D. Since L is diagonal, we have L = L'. Its inverse is also trivially
- computed - call it Linv.
-
- Now, define a 2x2 correction matrix C as follows:
-
- C = Q * Linv * Q'
-
- This matrix rotates the input vector, scales it by some amount, and
- then rotates it back to where it was (i.e., Q'Q = Q Q' = I).
-
- We compute RC in order to correct the rotation vectors. We take its
- two columns as the corrected vectors.
-
- In order to show that the two columns of RC are orthonormal, we can
- show that (RC)'(RC) = I. Indeed, noticing that C is symmetric, let's
- expand the expression:
-
- (RC)'(RC) = C'R'R C = C R'R C = (Q Linv Q') (Q D Q') (Q Linv Q') =
- Q Linv (Q'Q) D (Q'Q) Linv Q' = Q Linv D Linv Q' =
- Q Linv (L L) Linv Q' = Q (Linv L) (L Linv) Q' = Q Q' = I
-
- I have provided below a closed formula to correct the rotation vectors.
-
- What C does to R is very interesting: it makes the singular values
- become 1. If U S V' is a SVD of R, then R'R = V S^2 V'. The singular
- values of R are the square roots of the eigenvalues of R'R. Letting
- S = L and V = Q, it follows that RC = U S V' V Linv V' = U V'. This
- means that RC is equivalent to the correction "trick" using the SVD
- found in the computer vision literature (i.e., compute the SVD and
- return U V'). That "trick" is known to return the rotation matrix that
- minimizes the Frobenius norm of the difference between the input and
- the output. Consequently, the technique I have just presented is also
- optimal in that sense!
-
- By the way, the input matrix R does not need to be 3x2.
-
- */
- // compute the entries of R'R
- const r1tr1 = r11 * r11 + r21 * r21 + r31 * r31;
- const r2tr2 = r12 * r12 + r22 * r22 + r32 * r32;
- const r1tr2 = r11 * r12 + r21 * r22 + r31 * r32;
- // compute the two real eigenvalues of R'R
- const delta = (r1tr1 - r2tr2) * (r1tr1 - r2tr2) + 4 * r1tr2 * r1tr2;
- const sqrt = Math.sqrt(delta); // delta >= 0 always
- const eigval1 = (r1tr1 + r2tr2 + sqrt) / 2;
- const eigval2 = (r1tr1 + r2tr2 - sqrt) / 2;
- // compute two unit eigenvectors qi = (xi,yi) of R'R
- const alpha1 = (r2tr2 - eigval1) - r1tr2 * (1 + r1tr2) / (r1tr1 - eigval1);
- const x1 = Math.sqrt((alpha1 * alpha1) / (1 + alpha1 * alpha1));
- const y1 = x1 / alpha1;
- const alpha2 = (r2tr2 - eigval2) - r1tr2 * (1 + r1tr2) / (r1tr1 - eigval2);
- const x2 = Math.sqrt((alpha2 * alpha2) / (1 + alpha2 * alpha2));
- const y2 = x2 / alpha2;
- // compute the Cholesky decomposition LL' of the diagonal matrix D
- // whose entries are the two eigenvalues of R'R and then invert L
- const s1 = Math.sqrt(eigval1), s2 = Math.sqrt(eigval2); // singular values of R (pick s1 >= s2)
- const Linv = speedy_vision_default().Matrix(2, 2, [1 / s1, 0, 0, 1 / s2]); // L inverse
- // compute the correction matrix C = Q * Linv * Q', where Q = [q1|q2]
- // is orthogonal and Linv is computed as above
- const Q = speedy_vision_default().Matrix(2, 2, [x1, y1, x2, y2]);
- const Qt = speedy_vision_default().Matrix(2, 2, [x1, x2, y1, y2]);
- const C = Q.times(Linv).times(Qt);
- // correct the rotation vectors r1 and r2 using C
- const R = speedy_vision_default().Matrix(3, 2, [r11, r21, r31, r12, r22, r32]);
- return speedy_vision_default().Matrix(R.times(C)).read();
- }
- /**
- * Compute a refined translation vector
- * @param normalizedHomography ideal pinhole K = I
- * @param rot rotation vectors [ r1 | r2 ] in column-major format
- * @param t0 initial estimate for the translation vector
- * @returns 3x1 translation vector in column-major format
- */
- _refineTranslation(normalizedHomography, rot, t0) {
- /*
-
- Given a normalized homography H, the rotation vectors r1, r2, and a
- translation vector t, we know that [ r1 | r2 | t ] = s H for a non-zero
- scale factor s.
-
- If we take a homogeneous vector u = [ x y w ]' (i.e., w = 1), then
- [ r1 | r2 | t ] u is parallel to H u, which means that their cross
- product is zero:
-
- [ r1 | r2 | t ] u x H u = ( x r1 + y r2 + w t ) x H u = 0
-
- The following code finds an optimal translation vector t based on the
- above observation. H, r1, r2 are known.
-
- */
- const B = TRANSLATION_REFINEMENT_BUFFERS;
- const n = TRANSLATION_REFINEMENT_SAMPLES;
- const n3 = TRANSLATION_REFINEMENT_SAMPLES_3X;
- Utils.assert(B.x.length === n);
- const h = normalizedHomography.read();
- const h11 = h[0], h12 = h[3], h13 = h[6];
- const h21 = h[1], h22 = h[4], h23 = h[7];
- const h31 = h[2], h32 = h[5], h33 = h[8];
- const r11 = rot[0], r12 = rot[3];
- const r21 = rot[1], r22 = rot[4];
- const r31 = rot[2], r32 = rot[5];
- // get sample points (xi, yi), 0 <= i < n
- const x = B.x, y = B.y;
- // set auxiliary values: ai = H [ xi yi 1 ]'
- const a1 = B.a1, a2 = B.a2, a3 = B.a3;
- for (let i = 0; i < n; i++) {
- a1[i] = x[i] * h11 + y[i] * h12 + h13;
- a2[i] = x[i] * h21 + y[i] * h22 + h23;
- a3[i] = x[i] * h31 + y[i] * h32 + h33;
- }
- // solve M t = v for t; M: 3n x 3, v: 3n x 1, t: 3 x 1 (linear least squares)
- const m = B.m, v = B.v;
- for (let i = 0, k = 0; k < n; i += 3, k++) {
- m[i] = m[i + n3 + 1] = m[i + n3 + n3 + 2] = 0;
- m[i + n3] = -(m[i + 1] = a3[k]);
- m[i + 2] = -(m[i + n3 + n3] = a2[k]);
- m[i + n3 + n3 + 1] = -(m[i + n3 + 2] = a1[k]);
- v[i] = a3[k] * (x[k] * r21 + y[k] * r22) - a2[k] * (x[k] * r31 + y[k] * r32);
- v[i + 1] = -a3[k] * (x[k] * r11 + y[k] * r12) + a1[k] * (x[k] * r31 + y[k] * r32);
- v[i + 2] = a2[k] * (x[k] * r11 + y[k] * r12) - a1[k] * (x[k] * r21 + y[k] * r22);
- }
- /*
- // this works, but I want more lightweight
- const M = Speedy.Matrix(n3, 3, m);
- const v_ = Speedy.Matrix(n3, 1, v);
- return Speedy.Matrix(M.ldiv(v_)).read();
- */
- /*
-
- Gradient descent with optimal step size / learning rate
- -------------------------------------------------------
-
- Let's find the column-vector x that minimizes the error function
- E(x) = r'r, where r = Ax - b, using gradient descent. This is linear
- least squares. We want to find x easily, QUICKLY and iteratively.
-
- The update rule of gradient descent is set to:
-
- x := x - w * grad(E)
-
- where w is the learning rate and grad(E) is the gradient of E(x):
-
- grad(E) = 2 A'r = 2 A'(Ax - b) = 2 A'A x - 2 A'b
-
- Let's adjust w to make x "converge quickly". Define function S(w) as:
-
- S(w) = x - w * grad(E) (step)
-
- and another function F(w) as:
-
- F(w) = E(S(w))
-
- which is the error of the step. We minimize F by setting its derivative
- to zero:
-
- 0 = dF = dF dS
- dw dS dw
-
- What follows is a fair amount of algebra. Do the math and you'll find
- the following optimal update rule:
-
- (c'c)
- x := x - --------- c
- (Ac)'(Ac)
-
- where c = A'r = A'(Ax - b)
-
- */
- // initial guess
- const t = B.t;
- t[0] = t0[0];
- t[1] = t0[1];
- t[2] = t0[2];
- // gradient descent: super lightweight implementation
- const r = B.r, c = B.c, Mc = B.Mc;
- for (let it = 0; it < TRANSLATION_REFINEMENT_ITERATIONS; it++) {
- // compute residual r = Mt - v
- for (let i = 0; i < n3; i++) {
- r[i] = 0;
- for (let j = 0; j < 3; j++)
- r[i] += m[j * n3 + i] * t[j];
- r[i] -= v[i];
- }
- // compute c = M'r
- for (let i = 0; i < 3; i++) {
- c[i] = 0;
- for (let j = 0; j < n3; j++)
- c[i] += m[i * n3 + j] * r[j];
- }
- // compute Mc
- for (let i = 0; i < n3; i++) {
- Mc[i] = 0;
- for (let j = 0; j < 3; j++)
- Mc[i] += m[j * n3 + i] * c[j];
- }
- // compute num = c'c and den = (Mc)'(Mc)
- let num = 0, den = 0;
- for (let i = 0; i < 3; i++)
- num += c[i] * c[i];
- for (let i = 0; i < n3; i++)
- den += Mc[i] * Mc[i];
- // compute num / den
- const frc = num / den;
- if (Number.isNaN(frc))
- break;
- // iterate: t = t - (num / den) * c
- for (let i = 0; i < 3; i++)
- t[i] -= frc * c[i];
- }
- //console.log("OLD t:\n\n",t0.join('\n'));
- //console.log("new t:\n\n",t.join('\n'));
- // done!
- return t;
- }
- /**
- * Apply a smoothing filter to the partial pose
- * @param partialPose 3x3 [ r1 | r2 | t ]
- * @returns filtered partial pose
- */
- _filterPartialPose(partialPose) {
- const avg = new Array(9).fill(0);
- const entries = partialPose.read();
- const rotationBlock = entries.slice(0, 6);
- const translationBlock = entries.slice(6, 9);
- // how many samples should we store, at most?
- const div = (Settings.powerPreference == 'low-power') ? 1.5 : 1; // low-power ~ half the fps
- const N = Math.ceil(ROTATION_FILTER_SAMPLES / div);
- const M = Math.ceil(TRANSLATION_FILTER_SAMPLES / div);
- // is it a valid partial pose?
- if (!Number.isNaN(entries[0])) {
- // store samples
- this._partialRotationBuffer.unshift(rotationBlock);
- if (this._partialRotationBuffer.length > N)
- this._partialRotationBuffer.length = N;
- this._translationBuffer.unshift(translationBlock);
- if (this._translationBuffer.length > M)
- this._translationBuffer.length = M;
- }
- else if (this._partialRotationBuffer.length == 0) {
- // invalid pose, no samples
- return speedy_vision_default().Matrix.Eye(3);
- }
- // average *nearby* rotations
- const n = this._partialRotationBuffer.length;
- for (let i = 0; i < n; i++) {
- const r = this._partialRotationBuffer[i];
- for (let j = 0; j < 6; j++)
- avg[j] += r[j] / n;
- }
- const r = this._refineRotation(avg[0], avg[1], avg[2], avg[3], avg[4], avg[5]);
- // average translations
- const m = this._translationBuffer.length;
- for (let i = 0; i < m; i++) {
- const t = this._translationBuffer[i];
- for (let j = 0; j < 3; j++)
- avg[6 + j] += (m - i) * t[j] / ((m * m + m) / 2);
- //avg[6 + j] += t[j] / m;
- }
- const t = [avg[6], avg[7], avg[8]];
- // done!
- return speedy_vision_default().Matrix(3, 3, r.concat(t));
- }
- /**
- * Estimate extrinsics [ R | t ] given a partial pose [ r1 | r2 | t ]
- * @param partialPose
- * @returns 3x4 matrix
- */
- _estimateFullPose(partialPose) {
- const p = partialPose.read();
- const r11 = p[0], r12 = p[3], t1 = p[6];
- const r21 = p[1], r22 = p[4], t2 = p[7];
- const r31 = p[2], r32 = p[5], t3 = p[8];
- // r3 = +- ( r1 x r2 )
- let r13 = r21 * r32 - r31 * r22;
- let r23 = r31 * r12 - r11 * r32;
- let r33 = r11 * r22 - r21 * r12;
- // let's make sure that det R = +1 (keep the orientation)
- const det = r11 * (r22 * r33 - r23 * r32) - r21 * (r12 * r33 - r13 * r32) + r31 * (r12 * r23 - r13 * r22);
- if (det < 0) {
- r13 = -r13;
- r23 = -r23;
- r33 = -r33;
- }
- // done!
- return speedy_vision_default().Matrix(3, 4, [
- r11, r21, r31,
- r12, r22, r32,
- r13, r23, r33,
- t1, t2, t3,
- ]);
- }
- /**
- * Estimate the pose [ R | t ] given a homography in AR screen space
- * @param homography must be valid
- * @param f focal length
- * @returns 3x4 matrix
- */
- _estimatePose(homography, f = this._intrinsics[FY]) {
- const normalizedHomography = this._normalizeHomography(homography, f);
- const partialPose = speedy_vision_default().Matrix.Eye(3);
- // we want the estimated partial pose [ r1 | r2 | t ] to be as close
- // as possible to the normalized homography, up to a scale factor;
- // i.e., H * [ r1 | r2 | t ]^(-1) = s * I for a non-zero scalar s
- // it won't be a perfect equality due to noise in the homography
- const residual = speedy_vision_default().Matrix(normalizedHomography);
- for (let k = 0; k < POSE_ITERATIONS; k++) {
- // incrementally improve the partial pose
- const rt = this._estimatePartialPose(residual); // rt should converge to the identity matrix
- partialPose.setToSync(rt.times(partialPose));
- residual.setToSync(residual.times(rt.inverse()));
- //console.log("residual",residual.toString());
- }
- //console.log('-----------');
- /*
- // test
- const result = Speedy.Matrix.Zeros(3);
- result.setToSync(partialPose.times(normalizedHomography.inverse()));
- const m11 = result.at(0,0);
- result.setToSync(result.times(1/m11));
- console.log("Pose * NORMALIZED HOM^-1", result.toString());
- */
- /*
- const rt = partialPose.read();
- const r = rt.slice(0, 6);
- const t = this._refineTranslation(normalizedHomography, r, rt.slice(6, 9));
- const refinedPartialPose = Speedy.Matrix(3, 3, r.concat(t));
- const filteredPartialPose = this._filterPartialPose(refinedPartialPose);
- */
- // filter the partial pose
- const filteredPartialPose = this._filterPartialPose(partialPose);
- // estimate the full pose
- return this._estimateFullPose(filteredPartialPose);
- }
- /**
- * Store an estimated pose
- * @param pose 3x4 matrix
- */
- _storePose(pose) {
- this._extrinsics = pose.read();
- }
- }
-
- ;// CONCATENATED MODULE: ./src/geometry/pose.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * pose.ts
- * A pose represents a position and an orientation in a 3D space
- */
- /**
- * A pose represents a position and an orientation in a 3D space
- * (and sometimes a scale, too...)
- */
- class Pose {
- /**
- * Constructor
- * @param transform usually a rigid transform in a 3D space (e.g., world space, viewer space or other)
- */
- constructor(transform) {
- this._transform = transform;
- }
- /**
- * A transform describing the position and the orientation
- * of the pose relative to the 3D space to which it belongs
- */
- get transform() {
- return this._transform;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/geometry/transform.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * transform.ts
- * 3D geometrical transforms
- */
-
-
- /**
- * A 3D transformation
- */
- class BaseTransform {
- /**
- * Constructor
- * @param matrix a 4x4 matrix
- */
- constructor(matrix) {
- if (matrix.rows != 4 || matrix.columns != 4)
- throw new IllegalArgumentError('A 3D transform expects a 4x4 matrix');
- this._matrix = matrix;
- }
- /**
- * The 4x4 transformation matrix (read-only)
- */
- get matrix() {
- return this._matrix;
- }
- }
- /**
- * An invertible 3D transformation
- */
- class InvertibleTransform extends BaseTransform {
- /**
- * Constructor
- * @param matrix a 4x4 matrix
- */
- constructor(matrix) {
- // WARNING: we do not check if the matrix actually encodes an invertible transform!
- super(matrix);
- }
- /**
- * The inverse of the transform
- */
- get inverse() {
- const inverseMatrix = speedy_vision_default().Matrix(this._matrix.inverse());
- return new InvertibleTransform(inverseMatrix);
- }
- }
- /**
- * A 3D transformation described by translation, rotation and scale
- */
- class StandardTransform extends InvertibleTransform {
- // TODO: position, rotation and scale attributes
- /**
- * Constructor
- * @param matrix a 4x4 matrix
- */
- constructor(matrix) {
- // WARNING: we do not check if the matrix actually encodes a standard transform!
- super(matrix);
- }
- /**
- * The inverse of the transform
- */
- get inverse() {
- /*
-
- The inverse of a 4x4 standard transform T * R * S...
-
- [ RS t ] is [ ZR' -ZR't ]
- [ 0' 1 ] [ 0' 1 ]
-
- where S is 3x3, R is 3x3, t is 3x1, 0' is 1x3 and Z is the inverse of S
-
- */
- return super.inverse;
- }
- }
- /**
- * A 3D transformation described by position and orientation
- */
- class RigidTransform extends StandardTransform {
- // TODO: position and rotation attributes (need to decompose the matrix)
- /**
- * Constructor
- * @param matrix a 4x4 matrix
- */
- constructor(matrix) {
- // WARNING: we do not check if the matrix actually encodes a rigid transform!
- super(matrix);
- }
- /**
- * The inverse of the transform
- */
- get inverse() {
- /*
-
- The inverse of a 4x4 rigid transform
-
- [ R t ] is [ R' -R't ]
- [ 0' 1 ] [ 0' 1 ]
-
- where R is 3x3, t is 3x1 and 0' is 1x3
-
- */
- const m = this._matrix.read();
- if (m[15] == 0) // error? abs()??
- throw new IllegalOperationError('Not a rigid transform');
- const s = 1 / m[15]; // should be 1 (normalize homogeneous coordinates)
- const r11 = m[0] * s, r12 = m[4] * s, r13 = m[8] * s;
- const r21 = m[1] * s, r22 = m[5] * s, r23 = m[9] * s;
- const r31 = m[2] * s, r32 = m[6] * s, r33 = m[10] * s;
- const t1 = m[12] * s, t2 = m[13] * s, t3 = m[14] * s;
- const rt1 = r11 * t1 + r21 * t2 + r31 * t3;
- const rt2 = r12 * t1 + r22 * t2 + r32 * t3;
- const rt3 = r13 * t1 + r23 * t2 + r33 * t3;
- const inverseMatrix = speedy_vision_default().Matrix(4, 4, [
- r11, r12, r13, 0,
- r21, r22, r23, 0,
- r31, r32, r33, 0,
- -rt1, -rt2, -rt3, 1
- ]);
- return new RigidTransform(inverseMatrix);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/geometry/viewer-pose.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * viewer-pose.ts
- * The pose of a virtual camera in 3D world space at a moment in time
- */
-
-
-
- /**
- * The pose of a virtual camera in 3D world space at a moment in time
- */
- class ViewerPose extends Pose {
- /**
- * Constructor
- * @param camera camera model
- */
- constructor(camera) {
- // compute the view matrix and its inverse in AR screen space
- const viewMatrix = ViewerPose._computeViewMatrix(camera);
- const inverseTransform = new RigidTransform(viewMatrix);
- super(inverseTransform.inverse);
- this._viewMatrix = viewMatrix;
- }
- /**
- * This 4x4 matrix moves 3D points from world space to viewer space. We
- * assume that the camera is looking in the direction of the negative
- * z-axis (WebGL-friendly)
- */
- get viewMatrix() {
- return this._viewMatrix;
- }
- /**
- * Compute the view matrix in AR screen space, measured in pixels
- * @param camera
- * @returns a 4x4 matrix describing a rotation and a translation
- */
- static _computeViewMatrix(camera) {
- /*
-
- // this is the view matrix in AR screen space, measured in pixels
- // we augment the extrinsics matrix, making it 4x4 by adding a
- // [ 0 0 0 1 ] row. Below, E is a 3x4 extrinsics matrix
- const V = Speedy.Matrix(4, 4, [
- E[0], E[1], E[2], 0,
- E[3], E[4], E[5], 0,
- E[6], E[7], E[8], 0,
- E[9], E[10], E[11], 1
- ]);
-
- // we premultiply V by F, which performs a rotation around the
- // x-axis by 180 degrees, so that we get the 3D objects in front
- // of the camera pointing in the direction of the negative z-axis
- const F = Speedy.Matrix(4, 4, [
- 1, 0, 0, 0,
- 0,-1, 0, 0,
- 0, 0,-1, 0,
- 0, 0, 0, 1
- ]);
-
- Matrix F * V is matrix V with the second and third rows negated
-
- */
- const E = camera.extrinsics;
- return speedy_vision_default().Matrix(4, 4, [
- E[0], -E[1], -E[2], 0,
- E[3], -E[4], -E[5], 0,
- E[6], -E[7], -E[8], 0,
- E[9], -E[10], -E[11], 1
- ]);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/geometry/view.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * view.ts
- * A view of the 3D world at a moment in time,
- * featuring the means to project points into clip space
- */
-
-
-
- /** Default distance in pixels of the near plane to the optical center of the camera */
- const DEFAULT_NEAR = 1;
- /** Default distance in pixels of the far plane to the optical center of the camera */
- const DEFAULT_FAR = 20000;
- /**
- * A PerspectiveView is a View defining a symmetric frustum around the z-axis
- * (perspective projection)
- */
- class PerspectiveView {
- /**
- * Constructor
- * @param camera camera model
- * @param near distance of the near plane
- * @param far distance of the far plane
- */
- constructor(camera, near = DEFAULT_NEAR, far = DEFAULT_FAR) {
- const intrinsics = camera.intrinsics;
- const screenSize = camera.screenSize;
- this._near = Math.max(0, +near);
- this._far = Math.max(0, +far);
- if (this._near >= this._far)
- throw new IllegalArgumentError(`View expects near < far (found near = ${this._near} and far = ${this._far})`);
- this._aspect = screenSize.width / screenSize.height;
- this._tanOfHalfFovy = intrinsics[V0] / intrinsics[FY];
- this._projectionMatrix = PerspectiveView._computeProjectionMatrix(intrinsics, this._near, this._far);
- }
- /**
- * A 4x4 projection matrix for WebGL
- */
- get projectionMatrix() {
- return this._projectionMatrix;
- }
- /**
- * Aspect ratio of the frustum
- */
- get aspect() {
- return this._aspect;
- }
- /**
- * Vertical field-of-view of the frustum, measured in radians
- */
- get fovy() {
- return 2 * Math.atan(this._tanOfHalfFovy);
- }
- /**
- * Distance of the near plane
- */
- get near() {
- return this._near;
- }
- /**
- * Distance of the far plane
- */
- get far() {
- return this._far;
- }
- /**
- * Compute a perspective projection matrix for WebGL
- * @param K camera intrinsics
- * @param near distance of the near plane
- * @param far distance of the far plane
- */
- static _computeProjectionMatrix(K, near, far) {
- // we assume that the principal point is at the center of the image
- const top = near * (K[V0] / K[FY]);
- const right = near * (K[U0] / K[FX]);
- const bottom = -top, left = -right; // symmetric frustum
- // a derivation of this projection matrix can be found at
- // https://www.songho.ca/opengl/gl_projectionmatrix.html
- // http://learnwebgl.brown37.net/08_projections/projections_perspective.html
- return speedy_vision_default().Matrix(4, 4, [
- 2 * near / (right - left), 0, 0, 0,
- 0, 2 * near / (top - bottom), 0, 0,
- (right + left) / (right - left), (top + bottom) / (top - bottom), -(far + near) / (far - near), -1,
- 0, 0, -2 * far * near / (far - near), 0
- ]);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/geometry/viewer.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * view.ts
- * A viewer represents a virtual camera in 3D world space
- */
-
-
-
-
-
- /**
- * A viewer represents a virtual camera in 3D world space
- */
- class Viewer {
- /**
- * Constructor
- * @param camera camera model
- */
- constructor(camera) {
- this._pose = new ViewerPose(camera);
- this._views = [new PerspectiveView(camera)];
- }
- /**
- * The pose of this viewer
- */
- get pose() {
- return this._pose;
- }
- /**
- * The view of this viewer (only for monoscopic rendering)
- */
- get view() {
- /*
- if(this._views.length > 1)
- throw new IllegalOperationError('Use viewer.views for stereoscopic rendering');
- */
- return this._views[0];
- }
- /**
- * The views of this viewer
- */
- /*
- get views(): View[]
- {
- return this._views.concat([]);
- }
- */
- /**
- * Convert a pose from world space to viewer space
- * @param pose a pose in world space
- * @returns a pose in viewer space
- */
- convertToViewerSpace(pose) {
- const modelMatrix = pose.transform.matrix;
- const viewMatrix = this._pose.viewMatrix;
- const modelViewMatrix = speedy_vision_default().Matrix(viewMatrix.times(modelMatrix));
- const transform = new StandardTransform(modelViewMatrix);
- return new Pose(transform);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/states/tracking.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * tracking.ts
- * Tracking state of the Image Tracker
- */
-
-
-
-
-
-
-
-
-
-
-
- /** Whether or not we want to accelerate GPU-CPU transfers. Using turbo costs a slight delay on the tracking */
- const USE_TURBO = true;
- /** Number of PBOs; meaningful only when using turbo */
- const NUMBER_OF_PBOS = 2;
- /** Frame skipping; meaningful only when using turbo */
- const TURBO_SKIP = 2;
- /**
- * The tracking state of the Image Tracker tracks
- * keypoints of the image target and updates the
- * rectification matrix
- */
- class ImageTrackerTrackingState extends ImageTrackerState {
- /**
- * Constructor
- * @param imageTracker
- */
- constructor(imageTracker) {
- super('tracking', imageTracker);
- this._referenceImage = null;
- this._warpHomography = speedy_vision_default().Matrix.Eye(3);
- this._poseHomography = speedy_vision_default().Matrix.Eye(3);
- this._initialHomography = speedy_vision_default().Matrix.Eye(3);
- this._initialKeypoints = [];
- this._counter = 0;
- this._camera = new CameraModel();
- this._predictedKeypoints = [];
- this._lastPipelineOutput = { keypoints: [] };
- this._pipelineCounter = 0;
- this._lastOutput = {};
- this._lostCounter = 0;
- // we need at least 4 correspondences of points to compute a homography matrix
- Utils.assert(TRACK_MIN_MATCHES >= 4);
- }
- /**
- * Called as soon as this becomes the active state, just before update() runs for the first time
- * @param settings
- */
- onEnterState(settings) {
- const homography = settings.homography;
- const referenceImage = settings.referenceImage;
- const templateKeypoints = settings.templateKeypoints;
- const keypointPortalSink = settings.keypointPortalSink;
- const screenSize = settings.screenSize; // this.screenSize is not yet set
- const keypointPortalSource = this._pipeline.node('keypointPortalSource');
- // this shouldn't happen
- if (!referenceImage)
- throw new IllegalOperationError(`Can't track a null reference image`);
- // set attributes
- this._referenceImage = referenceImage;
- this._warpHomography = speedy_vision_default().Matrix(homography);
- this._poseHomography = speedy_vision_default().Matrix(homography);
- this._initialHomography = speedy_vision_default().Matrix(homography);
- this._initialKeypoints = templateKeypoints;
- this._counter = 0;
- this._predictedKeypoints = [];
- this._lastPipelineOutput = { keypoints: [] };
- this._pipelineCounter = 0;
- this._lastOutput = {};
- this._lostCounter = 0;
- // setup portals
- keypointPortalSource.source = keypointPortalSink;
- // setup camera
- this._camera.init(screenSize);
- // emit event
- const ev = new ImageTrackerEvent('targetfound', referenceImage);
- this._imageTracker.dispatchEvent(ev);
- // log
- Utils.log(`Tracking image "${referenceImage.name}"...`);
- }
- /**
- * Called when leaving the state
- */
- onLeaveState() {
- const referenceImage = this._referenceImage;
- // release the camera
- this._camera.release();
- // emit event
- const ev = new ImageTrackerEvent('targetlost', referenceImage);
- this._imageTracker.dispatchEvent(ev);
- }
- /**
- * Called just before the GPU processing
- * @returns promise
- */
- _beforeUpdate() {
- const imageRectifier = this._pipeline.node('imageRectifier');
- const borderClipper = this._pipeline.node('borderClipper');
- const keypointRectifier = this._pipeline.node('keypointRectifier');
- const screenSize = this.screenSize;
- /*
- // pause media (test)
- const source = this._pipeline.node('source') as SpeedyPipelineNodeImageSource;
- const media = source.media as SpeedyMedia;
- (media.source as HTMLVideoElement).pause();
- */
- // clip keypoints from the borders of the target image
- borderClipper.imageSize = screenSize;
- borderClipper.borderSize = speedy_vision_default().Vector2(screenSize.width * TRACK_CLIPPING_BORDER, screenSize.height * TRACK_CLIPPING_BORDER);
- // rectify the image
- return this._findImageWarp(this._warpHomography, screenSize).then(warp => {
- imageRectifier.transform = warp;
- });
- }
- /**
- * GPU processing
- * @returns promise with the pipeline results
- */
- _gpuUpdate() {
- //return super._gpuUpdate();
- // No turbo?
- if (!USE_TURBO || Settings.powerPreference == 'low-power')
- return super._gpuUpdate();
- // When using turbo, we reduce the GPU usage by skipping every other frame
- const counter = this._pipelineCounter;
- this._pipelineCounter = (this._pipelineCounter + 1) % TURBO_SKIP;
- // Skip frame
- if (counter != 0) {
- if (this._lastPipelineOutput.keypoints !== undefined) {
- this._predictedKeypoints = this._predictKeypoints(this._lastPipelineOutput.keypoints, this._initialKeypoints);
- }
- else
- this._predictedKeypoints.length = 0;
- this._lastPipelineOutput.keypoints = this._predictedKeypoints;
- return speedy_vision_default().Promise.resolve(this._lastPipelineOutput);
- }
- // Run the pipeline and store the results
- return super._gpuUpdate().then(results => {
- this._lastPipelineOutput = results;
- return results;
- });
- }
- /**
- * Post processing that takes place just after the GPU processing
- * @param result pipeline results
- * @returns state output
- */
- _afterUpdate(result) {
- const imageRectifier = this._pipeline.node('imageRectifier');
- const keypoints = result.keypoints;
- const image = result.image;
- const referenceImage = this._referenceImage;
- // find the best keypoint matches
- return this._preprocessMatches(keypoints, this._initialKeypoints).then(matches => {
- // find motion models
- return speedy_vision_default().Promise.all([
- this._findAffineMotion(matches),
- this._findPerspectiveMotion(matches)
- ]);
- }).then(([affineMotion, perspectiveMotion]) => {
- const lowPower = (Settings.powerPreference == 'low-power');
- const frozen = !(!USE_TURBO || lowPower || this._counter % TURBO_SKIP == 0);
- // update warp homography
- const delay = NUMBER_OF_PBOS * (!lowPower ? TURBO_SKIP : 1);
- const remainder = delay >>> 1; // we want remainder > 0, so it skips the first frame
- if (!USE_TURBO || this._counter % delay == remainder)
- this._warpHomography.setToSync(this._warpHomography.times(affineMotion));
- // update pose homography
- if (!frozen)
- this._poseHomography.setToSync(this._warpHomography.times(perspectiveMotion));
- // update counter
- this._counter = (this._counter + 1) % delay;
- // update the camera
- if (!frozen)
- return this._camera.update(this._poseHomography, this.screenSize);
- else
- return this._camera.matrix;
- }).then(_ => {
- // find the inverse of the rectification matrix
- const rectificationMatrix = imageRectifier.transform;
- const inverseRectificationMatrix = speedy_vision_default().Matrix(rectificationMatrix.inverse());
- // move keypoints from rectified space back to image space
- const n = keypoints.length;
- const coords = new Array(2 * n);
- for (let i = 0, j = 0; i < n; i++, j += 2) {
- coords[j] = keypoints[i].position.x;
- coords[j + 1] = keypoints[i].position.y;
- }
- return speedy_vision_default().Matrix.applyPerspectiveTransform(speedy_vision_default().Matrix.Zeros(2, n), speedy_vision_default().Matrix(2, n, coords), inverseRectificationMatrix);
- /*
- // test image center
- const coords2: number[] = new Array(2 * n);
- for(let i = 0, j = 0; i < n; i++, j += 2) {
- coords2[j] = this._imageTracker.screenSize.width / 2;
- coords2[j+1] = this._imageTracker.screenSize.height / 2;
- if(i % 2 == 0) {
- coords2[j] = this._imageTracker.screenSize.width / 4;
- coords2[j+1] = this._imageTracker.screenSize.height / 4;
- }
- }
-
- return Speedy.Matrix.applyPerspectiveTransform(
- Speedy.Matrix.Zeros(2, n),
- Speedy.Matrix(2, n, coords2),
- this._poseHomography
- //this._warpHomography
- );
- */
- }).then(mat => {
- /*
-
- const n = keypoints.length;
- const coords = mat.read();
-
- // ** this will interfere with the calculations when frame skipping is on **
-
- // get keypoints in image space
- for(let i = 0, j = 0; i < n; i++, j += 2) {
- keypoints[i].position.x = coords[j];
- keypoints[i].position.y = coords[j+1];
- }
-
- */
- // find a polyline surrounding the target
- return this._findPolyline(this._poseHomography, this.screenSize);
- //return this._findPolyline(this._warpHomography, this.screenSize);
- }).then(polyline => {
- // we let the target object be at the origin of the world space
- // (identity transform). We also perform a change of coordinates,
- // so that we move out from pixel space and into normalized space
- const modelMatrix = this._camera.denormalizer(); // ~ "identity matrix"
- const transform = new StandardTransform(modelMatrix);
- const pose = new Pose(transform);
- // given the current state of the camera model, we get a viewer
- // compatible with the pose of the target
- const viewer = new Viewer(this._camera);
- // the trackable object
- const trackable = {
- pose: pose,
- referenceImage: referenceImage
- };
- // the result generated by the image tracker
- const result = {
- tracker: this._imageTracker,
- trackables: [trackable],
- viewer: viewer
- };
- // build and save the output
- this._lastOutput = {
- exports: result,
- cameraMatrix: this._camera.matrix,
- homography: this._warpHomography,
- //keypoints: keypoints,
- screenSize: this.screenSize,
- image: image,
- polyline: polyline,
- };
- // we have successfully tracked the target in this frame
- this._lostCounter = 0;
- // done!
- return {
- nextState: 'tracking',
- trackerOutput: this._lastOutput
- };
- }).catch(err => {
- // give some tolerance to tracking errors
- if (err instanceof TrackingError) {
- if (++this._lostCounter <= TRACK_LOST_TOLERANCE) {
- //console.log("ABSORB",this._lostCounter,err.toString())
- // absorb the error
- return {
- nextState: 'tracking',
- trackerOutput: this._lastOutput
- };
- }
- }
- // lost tracking
- Utils.warning(`The target has been lost! ${err.toString()}`);
- this._camera.reset();
- // go back to the scanning state
- return {
- nextState: 'scanning',
- trackerOutput: {
- image: image,
- screenSize: this.screenSize,
- },
- };
- });
- }
- /**
- * Find quality matches between two sets of keypoints
- * @param currKeypoints keypoints of the current frame
- * @param prevKeypoints keypoints of the previous frame
- * @returns quality matches
- */
- _findQualityMatches(currKeypoints, prevKeypoints) {
- const result = [[], []];
- const n = currKeypoints.length;
- for (let i = 0; i < n; i++) {
- const currKeypoint = currKeypoints[i];
- if (currKeypoint.matches[0].index >= 0 && currKeypoint.matches[1].index >= 0) {
- const d1 = currKeypoint.matches[0].distance;
- const d2 = currKeypoint.matches[1].distance;
- if (d1 <= TRACK_MATCH_RATIO * d2) {
- const prevKeypoint = prevKeypoints[currKeypoint.matches[0].index];
- result[0].push(currKeypoint);
- result[1].push(prevKeypoint);
- }
- }
- }
- return result;
- }
- /**
- * Find a better spatial distribution of the input matches
- * @param matches quality matches
- * @returns refined quality matches
- */
- _refineQualityMatches(matches) {
- const currKeypoints = matches[0];
- const prevKeypoints = matches[1];
- // find a better spatial distribution of the keypoints
- const indices = this._distributeKeypoints(currKeypoints, TRACK_GRID_GRANULARITY);
- const n = indices.length; // number of refined matches
- // assemble output
- const result = [new Array(n), new Array(n)];
- for (let i = 0; i < n; i++) {
- result[0][i] = currKeypoints[indices[i]];
- result[1][i] = prevKeypoints[indices[i]];
- }
- // done!
- return result;
- }
- /**
- * Spatially distribute keypoints over a grid
- * @param keypoints keypoints to be distributed
- * @param gridCells number of grid elements in each axis
- * @returns a list of indices of keypoints[]
- */
- _distributeKeypoints(keypoints, gridCells) {
- // get the coordinates of the keypoints
- const n = keypoints.length;
- const points = new Array(2 * n);
- for (let i = 0, j = 0; i < n; i++, j += 2) {
- points[j] = keypoints[i].x;
- points[j + 1] = keypoints[i].y;
- }
- // normalize the coordinates to [0,1] x [0,1]
- this._normalizePoints(points);
- // distribute the keypoints over a grid
- const numberOfCells = gridCells * gridCells;
- const grid = (new Array(numberOfCells)).fill(-1);
- for (let i = 0, j = 0; i < n; i++, j += 2) {
- // find the grid location of the i-th point
- const xg = Math.floor(points[j] * gridCells); // 0 <= xg,yg < gridCells
- const yg = Math.floor(points[j + 1] * gridCells);
- // store the index of the i-th point in the grid
- grid[yg * gridCells + xg] = i;
- }
- // retrieve points of the grid
- const indices = [];
- for (let g = 0; g < numberOfCells; g++) {
- if (grid[g] >= 0) {
- const i = grid[g];
- indices.push(i);
- }
- }
- // done!
- return indices;
- }
- /**
- * Normalize points to [0,1)^2
- * @param points 2 x n matrix of points in column-major format
- * @returns points
- */
- _normalizePoints(points) {
- Utils.assert(points.length % 2 == 0);
- const n = points.length / 2;
- if (n == 0)
- return points;
- let xmin = Number.POSITIVE_INFINITY, xmax = Number.NEGATIVE_INFINITY;
- let ymin = Number.POSITIVE_INFINITY, ymax = Number.NEGATIVE_INFINITY;
- for (let i = 0, j = 0; i < n; i++, j += 2) {
- const x = points[j], y = points[j + 1];
- xmin = x < xmin ? x : xmin;
- ymin = y < ymin ? y : ymin;
- xmax = x > xmax ? x : xmax;
- ymax = y > ymax ? y : ymax;
- }
- const xlen = xmax - xmin + 1; // +1 is a correction factor, so that 0 <= x,y < 1
- const ylen = ymax - ymin + 1;
- for (let i = 0, j = 0; i < n; i++, j += 2) {
- points[j] = (points[j] - xmin) / xlen;
- points[j + 1] = (points[j + 1] - ymin) / ylen;
- }
- return points;
- }
- /**
- * Find a matrix with the coordinates of quality matches
- * @param matches n quality matches
- * @returns a 2 x 2n matrix split into two 2 x n blocks [ prevKeypoints | currKeypoints ]
- */
- _findMatrixOfMatches(matches) {
- const n = matches[0].length;
- Utils.assert(n > 0);
- // sets of keypoints
- const currKeypoints = matches[0];
- const prevKeypoints = matches[1];
- // get the coordinates of the keypoints of the set of refined matches
- const src = new Array(2 * n);
- const dst = new Array(2 * n);
- for (let i = 0, j = 0; i < n; i++, j += 2) {
- src[j] = prevKeypoints[i].x;
- src[j + 1] = prevKeypoints[i].y;
- dst[j] = currKeypoints[i].x;
- dst[j + 1] = currKeypoints[i].y;
- }
- // assemble the matrix
- return speedy_vision_default().Matrix(2, 2 * n, src.concat(dst));
- }
- /**
- * Preprocess keypoint matches
- * @param currKeypoints keypoints of the current frame
- * @param prevKeypoints keypoints of the previous frame
- * @returns a promise that is rejected if there are not enough "good" matches, or that is resolved to a
- * 2 x 2n matrix split into two 2 x n blocks [ source x,y coordinates | dest x,y coordinates ]
- */
- _preprocessMatches(currKeypoints, prevKeypoints) {
- // find and refine quality matches
- const qualityMatches = this._findQualityMatches(currKeypoints, prevKeypoints);
- const refinedMatches = this._refineQualityMatches(qualityMatches);
- // not enough matches?
- const n = refinedMatches[0].length;
- if (n < TRACK_MIN_MATCHES)
- return speedy_vision_default().Promise.reject(new TrackingError('Not enough data to compute a motion model'));
- // find matrix of matches
- const matrixOfMatches = this._findMatrixOfMatches(refinedMatches);
- // warp matrix of matches
- const result = speedy_vision_default().Matrix.Zeros(2, 2 * n);
- return this._findKeypointWarp().then(transform => speedy_vision_default().Matrix.applyAffineTransform(result, matrixOfMatches, transform.block(0, 1, 0, 2)));
- }
- /**
- * Find an affine motion model of the target image
- * @param preprocessedMatches 2 x 2n matrix split into two 2 x n blocks [ src | dest ]
- * @returns a promise that resolves to a 3x3 affine motion model (last row is [ 0 0 1 ])
- */
- _findAffineMotion(preprocessedMatches) {
- const model = speedy_vision_default().Matrix.Eye(3);
- const n = preprocessedMatches.columns / 2; // number of preprocessed matches
- // find motion model
- return speedy_vision_default().Matrix.findAffineTransform(model.block(0, 1, 0, 2), preprocessedMatches.block(0, 1, 0, n - 1), preprocessedMatches.block(0, 1, n, 2 * n - 1), {
- method: 'pransac',
- reprojectionError: TRACK_RANSAC_REPROJECTIONERROR,
- numberOfHypotheses: 512,
- bundleSize: 128,
- }).then(_ => {
- // validate the model
- const a00 = model.at(0, 0);
- if (Number.isNaN(a00))
- throw new TrackingError(`Can't compute affine motion model: bad keypoints`);
- // done!
- return model;
- });
- }
- /**
- * Find a perspective motion model of the target image
- * @param preprocessedMatches 2 x 2n matrix split into two 2 x n blocks [ src | dest ]
- * @returns a promise that resolves to a 3x3 perspective motion model
- */
- _findPerspectiveMotion(preprocessedMatches) {
- /*
-
- We can probably get more accurate motion estimates if we
- work in 3D rather than in 2D. We're currently estimating
- an affine transform in image space. What if we projected
- the keypoints into world space, estimated the camera motion
- (rotation and translation) that best describes the observed
- observed motion of the keypoints, and then projected things
- back to image space? Need to figure this out; we'll get a
- homography matrix.
-
- Note: keypoints are in rectified image space.
-
- Note: work with a 6 DoF perspective transform instead of 8.
-
- */
- const model = speedy_vision_default().Matrix.Zeros(3);
- const n = preprocessedMatches.columns / 2; // number of preprocessed matches
- // find motion model
- return speedy_vision_default().Matrix.findHomography(model, preprocessedMatches.block(0, 1, 0, n - 1), preprocessedMatches.block(0, 1, n, 2 * n - 1), {
- method: 'pransac',
- reprojectionError: TRACK_RANSAC_REPROJECTIONERROR,
- numberOfHypotheses: 512 * 2,
- bundleSize: 128 * 4, //*4
- }).then(_ => {
- // validate the model
- const a00 = model.at(0, 0);
- if (Number.isNaN(a00))
- throw new TrackingError(`Can't compute perspective motion model: bad keypoints`);
- // done!
- return model;
- });
- }
- /**
- * Find a rectification matrix to be applied to the target image
- * @param homography maps a reference image to the AR screen
- * @param media target
- * @param screenSize AR screen
- * @returns promise that resolves to a rectification matrix
- */
- _findImageWarp(homography, screenSize) {
- const referenceImage = this._referenceImage;
- const media = this._imageTracker.database._findMedia(referenceImage.name);
- const mat = speedy_vision_default().Matrix.Zeros(3);
- return this._findRectificationMatrixOfFullscreenImage(media, screenSize).then(warp => mat.setTo(warp.times(homography.inverse())));
- }
- /**
- * Find a warp to be applied to the keypoints
- * @returns affine transform
- */
- _findKeypointWarp() {
- const referenceImage = this._referenceImage;
- const media = this._imageTracker.database._findMedia(referenceImage.name);
- const screenSize = this.screenSize;
- const sw = screenSize.width, sh = screenSize.height;
- const mat = speedy_vision_default().Matrix.Eye(3, 3);
- // no rotation is needed
- if (!this._mustRotateWarpedImage(media, screenSize))
- return speedy_vision_default().Promise.resolve(mat);
- // rotate by 90 degrees clockwise and scale
- return speedy_vision_default().Matrix.affine(mat.block(0, 1, 0, 2), speedy_vision_default().Matrix(2, 3, [0, sh, 0, 0, sw, 0]), speedy_vision_default().Matrix(2, 3, [0, 0, sw, 0, sw, sh])).then(_ => mat);
- }
- /**
- * Predict the keypoints without actually looking at the image
- * @param curr keypoints at time t (will modify the contents)
- * @param initial keypoints at time t-1 (not just t = 0)
- * @returns keypoints at time t+1
- */
- _predictKeypoints(curr, initial) {
- // the target image is likely to be moving roughly in
- // the same manner as it was in the previous frame
- const next = [];
- const n = curr.length;
- for (let i = 0; i < n; i++) {
- const cur = curr[i];
- if (cur.matches[0].index < 0 || cur.matches[1].index < 0)
- continue;
- /*
- else if(cur.matches[0].distance > TRACK_MATCH_RATIO * cur.matches[1].distance)
- continue;
- */
- const ini = initial[cur.matches[0].index];
- const dx = cur.position.x - ini.position.x;
- const dy = cur.position.y - ini.position.y;
- // a better mathematical model is needed
- const alpha = 0.8; //0.2;
- cur.position.x = ini.position.x + alpha * dx;
- cur.position.y = ini.position.y + alpha * dy;
- next.push(cur);
- }
- // done!
- return next;
- }
- /**
- * Create & setup the pipeline
- * @returns pipeline
- */
- _createPipeline() {
- const pipeline = speedy_vision_default().Pipeline();
- const source = speedy_vision_default().Image.Source('source');
- const screen = speedy_vision_default().Transform.Resize('screen');
- const greyscale = speedy_vision_default().Filter.Greyscale();
- const imageRectifier = speedy_vision_default().Transform.PerspectiveWarp('imageRectifier');
- const nightvision = speedy_vision_default().Filter.Nightvision();
- const nightvisionMux = speedy_vision_default().Image.Multiplexer();
- const blur = speedy_vision_default().Filter.GaussianBlur();
- const detector = speedy_vision_default().Keypoint.Detector.Harris();
- const descriptor = speedy_vision_default().Keypoint.Descriptor.ORB();
- const matcher = speedy_vision_default().Keypoint.Matcher.BFKNN();
- const subpixel = speedy_vision_default().Keypoint.SubpixelRefiner();
- const denoiser = speedy_vision_default().Filter.GaussianBlur();
- const borderClipper = speedy_vision_default().Keypoint.BorderClipper('borderClipper');
- const clipper = speedy_vision_default().Keypoint.Clipper();
- const keypointRectifier = speedy_vision_default().Keypoint.Transformer('keypointRectifier');
- const keypointPortalSource = speedy_vision_default().Keypoint.Portal.Source('keypointPortalSource');
- const keypointSink = speedy_vision_default().Keypoint.SinkOfMatchedKeypoints('keypoints');
- const imageSink = speedy_vision_default().Image.Sink('image');
- source.media = null;
- screen.size = speedy_vision_default().Size(0, 0);
- imageRectifier.transform = speedy_vision_default().Matrix.Eye(3);
- nightvision.gain = NIGHTVISION_GAIN;
- nightvision.offset = NIGHTVISION_OFFSET;
- nightvision.decay = NIGHTVISION_DECAY;
- nightvision.quality = NIGHTVISION_QUALITY;
- nightvisionMux.port = TRACK_WITH_NIGHTVISION ? 1 : 0; // 1 = enable nightvision
- blur.kernelSize = speedy_vision_default().Size(ORB_GAUSSIAN_KSIZE, ORB_GAUSSIAN_KSIZE);
- blur.sigma = speedy_vision_default().Vector2(ORB_GAUSSIAN_SIGMA, ORB_GAUSSIAN_SIGMA);
- denoiser.kernelSize = speedy_vision_default().Size(SUBPIXEL_GAUSSIAN_KSIZE, SUBPIXEL_GAUSSIAN_KSIZE);
- denoiser.sigma = speedy_vision_default().Vector2(SUBPIXEL_GAUSSIAN_SIGMA, SUBPIXEL_GAUSSIAN_SIGMA);
- detector.quality = TRACK_HARRIS_QUALITY;
- detector.capacity = TRACK_DETECTOR_CAPACITY;
- subpixel.method = SUBPIXEL_METHOD;
- clipper.size = TRACK_MAX_KEYPOINTS;
- borderClipper.imageSize = screen.size;
- borderClipper.borderSize = speedy_vision_default().Vector2(0, 0);
- keypointRectifier.transform = speedy_vision_default().Matrix.Eye(3);
- matcher.k = 2;
- keypointPortalSource.source = null;
- keypointSink.turbo = USE_TURBO;
- // prepare input
- source.output().connectTo(screen.input());
- screen.output().connectTo(greyscale.input());
- // preprocess images
- greyscale.output().connectTo(imageRectifier.input());
- imageRectifier.output().connectTo(nightvisionMux.input('in0'));
- imageRectifier.output().connectTo(nightvision.input());
- nightvision.output().connectTo(nightvisionMux.input('in1'));
- // keypoint detection & clipping
- nightvisionMux.output().connectTo(detector.input());
- detector.output().connectTo(borderClipper.input());
- borderClipper.output().connectTo(clipper.input());
- // keypoint refinement
- imageRectifier.output().connectTo(denoiser.input());
- denoiser.output().connectTo(subpixel.input('image'));
- clipper.output().connectTo(subpixel.input('keypoints'));
- // keypoint description
- imageRectifier.output().connectTo(blur.input());
- blur.output().connectTo(descriptor.input('image'));
- subpixel.output().connectTo(descriptor.input('keypoints'));
- // keypoint matching
- keypointPortalSource.output().connectTo(matcher.input('database'));
- descriptor.output().connectTo(matcher.input('keypoints'));
- // prepare output
- descriptor.output().connectTo(keypointRectifier.input());
- //preMatcher.output().connectTo(keypointRectifier.input());
- keypointRectifier.output().connectTo(keypointSink.input());
- matcher.output().connectTo(keypointSink.input('matches'));
- //imageRectifier.output().connectTo(imageSink.input());
- // done!
- pipeline.init(source, screen, greyscale, imageRectifier, nightvision, nightvisionMux, blur, detector, subpixel, borderClipper, clipper, denoiser, descriptor, matcher, keypointPortalSource, keypointRectifier, keypointSink);
- return pipeline;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/image-tracker/image-tracker.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * image-tracker.ts
- * Image Tracker
- */
-
-
-
-
-
-
-
-
-
-
-
- /** A helper */
- const formatSize = (size) => `${size.width}x${size.height}`;
- /**
- * The ImageTracker tracks an image (one at a time)
- */
- class ImageTracker extends AREventTarget {
- /**
- * Constructor
- */
- constructor() {
- super();
- // the states
- this._state = {
- 'initial': new ImageTrackerInitialState(this),
- 'training': new ImageTrackerTrainingState(this),
- 'scanning': new ImageTrackerScanningState(this),
- 'pre-tracking': new ImageTrackerPreTrackingState(this),
- 'tracking': new ImageTrackerTrackingState(this),
- };
- // initial setup
- this._session = null;
- this._activeStateName = 'initial';
- this._lastOutput = {};
- this._database = new ReferenceImageDatabase();
- // user settings
- this._resolution = DEFAULT_TRACKING_RESOLUTION;
- }
- /**
- * The type of the tracker
- */
- get type() {
- return 'image-tracker';
- }
- /**
- * Current state name
- */
- get state() {
- return this._activeStateName;
- }
- /**
- * Reference Image Database
- * Must be configured before training the tracker
- */
- get database() {
- return this._database;
- }
- /**
- * Resolution of the AR screen space
- */
- get resolution() {
- return this._resolution;
- }
- /**
- * Resolution of the AR screen space
- */
- set resolution(resolution) {
- this._resolution = resolution;
- }
- /**
- * Size of the AR screen space, in pixels
- * @internal
- */
- get screenSize() {
- return this._state[this._activeStateName].screenSize;
- }
- /**
- * Last emitted output
- * @internal
- */
- get _output() {
- return this._lastOutput;
- }
- /**
- * Stats related to this tracker
- * @internal
- */
- get _stats() {
- return `${formatSize(this.screenSize)} ${this.state}`;
- }
- /**
- * Initialize this tracker
- * @param session
- * @returns promise that resolves after the tracker has been initialized
- * @internal
- */
- _init(session) {
- // store the session
- this._session = session;
- // initialize states
- for (const state of Object.values(this._state))
- state.init();
- // done!
- return speedy_vision_default().Promise.resolve();
- }
- /**
- * Release this tracker
- * @returns promise that resolves after the tracker has been released
- * @internal
- */
- _release() {
- // release states
- for (const state of Object.values(this._state))
- state.release();
- // unlink session
- this._session = null;
- // done!
- return speedy_vision_default().Promise.resolve();
- }
- /**
- * Update the tracker
- * @returns promise
- * @internal
- */
- _update() {
- // validate
- if (this._session == null)
- return speedy_vision_default().Promise.reject(new IllegalOperationError(`Uninitialized tracker`));
- // compute the screen size for image processing purposes
- // note: this may change over time...!
- const media = this._session.media;
- const aspectRatio = media.width / media.height;
- const screenSize = Utils.resolution(this._resolution, aspectRatio);
- // run the active state
- const activeState = this._state[this._activeStateName];
- return activeState.update(media, screenSize).then(({ trackerOutput, nextState, nextStateSettings }) => {
- // update the output of the tracker
- this._lastOutput = trackerOutput;
- // need to change the state?
- if (this._activeStateName != nextState) {
- activeState.onLeaveState();
- this._activeStateName = nextState;
- this._state[nextState].onEnterState(nextStateSettings || {});
- }
- });
- }
- /**
- * Get reference image
- * @param keypointIndex -1 if not found
- * @returns reference image
- * @internal
- */
- _referenceImageOfKeypoint(keypointIndex) {
- const training = this._state.training;
- return training.referenceImageOfKeypoint(keypointIndex);
- }
- /**
- * Get reference image index
- * @param keypointIndex -1 if not found
- * @returns reference image index, or -1 if not found
- * @internal
- */
- _referenceImageIndexOfKeypoint(keypointIndex) {
- const training = this._state.training;
- return training.referenceImageIndexOfKeypoint(keypointIndex);
- }
- /**
- * Get a keypoint of the trained set
- * @param keypointIndex
- * @returns a keypoint
- * @internal
- */
- _referenceKeypoint(keypointIndex) {
- const training = this._state.training;
- return training.referenceKeypoint(keypointIndex);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/trackers/tracker-factory.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * tracker-factory.ts
- * Tracker factory
- */
-
- /**
- * Tracker factory
- */
- class TrackerFactory {
- /**
- * Create an Image Tracker
- */
- static ImageTracker() {
- return new ImageTracker();
- }
- }
-
- ;// CONCATENATED MODULE: ./src/sources/media-source.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * media-source.ts
- * SpeedyMedia-based source of data
- */
-
-
-
- /**
- * SpeedyMedia-based source of data
- */
- class MediaSource {
- /**
- * Constructor
- */
- constructor(source) {
- this._media = null;
- this._source = source;
- }
- /**
- * A type-identifier of the source of data
- * @internal
- */
- get _type() {
- return 'video';
- }
- /**
- * Get media
- * @internal
- */
- get _data() {
- if (this._media == null)
- throw new IllegalOperationError(`The media of the source of data isn't loaded`);
- return this._media;
- }
- /**
- * Initialize this source of data
- * @returns a promise that resolves as soon as this source of data is initialized
- * @internal
- */
- _init() {
- return speedy_vision_default().load(this._source).then(media => {
- Utils.log(`Source of data is ${media.width}x${media.height}`);
- this._media = media;
- });
- }
- /**
- * Release this source of data
- * @returns a promise that resolves as soon as this source of data is released
- * @internal
- */
- _release() {
- if (this._media)
- this._media.release();
- this._media = null;
- return speedy_vision_default().Promise.resolve();
- }
- /**
- * A string featuring the size of the media, in pixels
- */
- get _size() {
- const media = this._media;
- if (media != null)
- return `${media.width}x${media.height}`;
- else
- return '-';
- }
- }
-
- ;// CONCATENATED MODULE: ./src/sources/video-source.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * video-source.ts
- * <video>-based source of data
- */
-
-
- /**
- * <video>-based source of data
- */
- class VideoSource extends MediaSource {
- /**
- * Constructor
- */
- constructor(video) {
- Utils.assert(video instanceof HTMLVideoElement, 'Expected a video element');
- super(video);
- }
- /**
- * Stats related to this source of data
- * @internal
- */
- get _stats() {
- return `${this._size} video`;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/sources/canvas-source.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * canvas-source.ts
- * <canvas>-based source of data
- */
-
-
- /**
- * <canvas>-based source of data
- */
- class CanvasSource extends MediaSource {
- /**
- * Constructor
- */
- constructor(canvas) {
- Utils.assert(canvas instanceof HTMLCanvasElement, 'Expected a canvas element');
- super(canvas);
- }
- /**
- * Stats related to this source of data
- * @internal
- */
- get _stats() {
- return `${this._size} canvas`;
- }
- }
-
- ;// CONCATENATED MODULE: ./src/sources/camera-source.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * camera-source.ts
- * Webcam-based source of data
- */
-
-
-
-
- /** Default options for camera sources */
- const DEFAULT_CAMERA_OPTIONS = {
- resolution: 'md',
- aspectRatio: 16 / 9,
- constraints: { facingMode: 'environment' },
- };
- /**
- * Webcam-based source of data
- */
- class CameraSource extends VideoSource {
- /**
- * Constructor
- */
- constructor(options) {
- const video = document.createElement('video');
- super(video);
- this._video = video;
- this._options = Object.assign({}, DEFAULT_CAMERA_OPTIONS, options);
- }
- /**
- * Camera resolution
- */
- get resolution() {
- return this._options.resolution;
- }
- /**
- * Stats related to this source of data
- * @internal
- */
- get _stats() {
- return `${this._size} webcam`;
- }
- /**
- * Initialize this source of data
- * @returns a promise that resolves as soon as this source of data is initialized
- * @internal
- */
- _init() {
- Utils.log('Accessing the webcam...');
- // validate
- if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia)
- throw new NotSupportedError('Unsupported browser: no navigator.mediaDevices.getUserMedia()');
- // set up media constraints
- const options = this._options;
- const size = Utils.resolution(options.resolution, options.aspectRatio);
- const constraints = {
- audio: false,
- video: Object.assign({ width: size.width, height: size.height }, options.constraints)
- };
- // load camera stream
- return new (speedy_vision_default()).Promise((resolve, reject) => {
- navigator.mediaDevices.getUserMedia(constraints).then(stream => {
- const video = this._video;
- video.onloadedmetadata = () => {
- video.play();
- Utils.log('Access to the webcam has been granted.');
- resolve(video);
- };
- video.srcObject = stream;
- video.muted = true;
- }).catch(err => {
- reject(new AccessDeniedError('Please give access to the webcam and reload the page.', err));
- });
- }).then(_ => super._init());
- }
- /**
- * Release this source of data
- * @returns a promise that resolves as soon as this source of data is released
- * @internal
- */
- _release() {
- const stream = this._video.srcObject;
- const tracks = stream.getTracks();
- // stop camera feed
- tracks.forEach(track => track.stop());
- this._video.onloadedmetadata = null;
- this._video.srcObject = null;
- // release the media
- return super._release();
- }
- }
-
- ;// CONCATENATED MODULE: ./src/sources/source-factory.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * source-factory.ts
- * Factory of sources of data
- */
-
-
-
- /**
- * Factory of sources of data
- */
- class SourceFactory {
- /**
- * Create a <video>-based source of data
- * @param video video element
- */
- static Video(video) {
- return new VideoSource(video);
- }
- /**
- * Create a <canvas>-based source of data
- * @param canvas canvas element
- */
- static Canvas(canvas) {
- return new CanvasSource(canvas);
- }
- /**
- * Create a Webcam-based source of data
- * @param options optional options object
- */
- static Camera(options = {}) {
- return new CameraSource(options);
- }
- }
-
- ;// CONCATENATED MODULE: ./src/main.ts
- /*
- * MARTINS.js Free Edition
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022 Alexandre Martins <alemartf(at)gmail.com>
- * https://github.com/alemart/martins-js
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License version 3
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * main.ts
- * Entry point
- */
-
-
-
-
-
-
-
- /**
- * GPU-accelerated Augmented Reality for the web
- */
- class Martins {
- /**
- * Start a new session
- * @param options
- * @returns a promise that resolves to a new session
- */
- static startSession(options) {
- return Session.instantiate(options);
- }
- /**
- * Trackers
- */
- static get Tracker() {
- return TrackerFactory;
- }
- /**
- * Sources of data
- */
- static get Source() {
- return SourceFactory;
- }
- /**
- * Create a viewport
- * @param settings
- * @returns a new viewport with the specified settings
- */
- static Viewport(settings) {
- return new BaseViewport(settings);
- }
- /**
- * Global Settings
- */
- static get Settings() {
- return Settings;
- }
- /**
- * Engine version
- */
- static get version() {
- if (false)
- {}
- else
- return "0.1.2-wip";
- }
- /**
- * Engine edition
- */
- static get edition() {
- return 'Free Edition';
- }
- /**
- * Speedy Vision
- */
- static get Speedy() {
- return (speedy_vision_default());
- }
- /**
- * Checks if the engine can be run in the browser the client is using
- * @returns true if the engine is compatible with the browser
- */
- static isSupported() {
- return Session.isSupported();
- }
- }
- // Freeze the namespace
- Object.freeze(Martins);
- // Add Speedy Vision to global scope
- ((window) => window.Speedy = window.Speedy || (speedy_vision_default()))(window);
- // Display a notice
- Utils.log(`MARTINS.js ${Martins.edition} version ${Martins.version}. ` +
- `GPU-accelerated Augmented Reality for the web by Alexandre Martins. ` +
- "https://github.com/alemart/martins-js");
-
- })();
-
- __webpack_exports__ = __webpack_exports__["default"];
- /******/ return __webpack_exports__;
- /******/ })()
- ;
- });
|