geometric.py 245 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021
  1. """Augmenters that apply affine or similar transformations.
  2. List of augmenters:
  3. * :class:`Affine`
  4. * :class:`ScaleX`
  5. * :class:`ScaleY`
  6. * :class:`TranslateX`
  7. * :class:`TranslateY`
  8. * :class:`Rotate`
  9. * :class:`ShearX`
  10. * :class:`ShearY`
  11. * :class:`AffineCv2`
  12. * :class:`PiecewiseAffine`
  13. * :class:`PerspectiveTransform`
  14. * :class:`ElasticTransformation`
  15. * :class:`Rot90`
  16. * :class:`WithPolarWarping`
  17. * :class:`Jigsaw`
  18. """
  19. from __future__ import print_function, division, absolute_import
  20. import math
  21. import functools
  22. import numpy as np
  23. from scipy import ndimage
  24. from skimage import transform as tf
  25. import cv2
  26. import six.moves as sm
  27. import imgaug as ia
  28. from imgaug.imgaug import _normalize_cv2_input_arr_
  29. from imgaug.augmentables.polys import _ConcavePolygonRecoverer
  30. from . import meta
  31. from . import blur as blur_lib
  32. from . import size as size_lib
  33. from .. import parameters as iap
  34. from .. import dtypes as iadt
  35. from .. import random as iarandom
  36. _VALID_DTYPES_CV2_ORDER_0 = {"uint8", "uint16", "int8", "int16", "int32",
  37. "float16", "float32", "float64",
  38. "bool"}
  39. _VALID_DTYPES_CV2_ORDER_NOT_0 = {"uint8", "uint16", "int8", "int16",
  40. "float16", "float32", "float64",
  41. "bool"}
  42. # skimage | cv2
  43. # 0 | cv2.INTER_NEAREST
  44. # 1 | cv2.INTER_LINEAR
  45. # 2 | -
  46. # 3 | cv2.INTER_CUBIC
  47. # 4 | -
  48. _AFFINE_INTERPOLATION_ORDER_SKIMAGE_TO_CV2 = {
  49. 0: cv2.INTER_NEAREST,
  50. 1: cv2.INTER_LINEAR,
  51. 2: cv2.INTER_CUBIC,
  52. 3: cv2.INTER_CUBIC,
  53. 4: cv2.INTER_CUBIC
  54. }
  55. # constant, edge, symmetric, reflect, wrap
  56. # skimage | cv2
  57. # constant | cv2.BORDER_CONSTANT
  58. # edge | cv2.BORDER_REPLICATE
  59. # symmetric | cv2.BORDER_REFLECT
  60. # reflect | cv2.BORDER_REFLECT_101
  61. # wrap | cv2.BORDER_WRAP
  62. _AFFINE_MODE_SKIMAGE_TO_CV2 = {
  63. "constant": cv2.BORDER_CONSTANT,
  64. "edge": cv2.BORDER_REPLICATE,
  65. "symmetric": cv2.BORDER_REFLECT,
  66. "reflect": cv2.BORDER_REFLECT_101,
  67. "wrap": cv2.BORDER_WRAP
  68. }
  69. def _handle_order_arg(order, backend):
  70. # Peformance in skimage for Affine:
  71. # 1.0x order 0
  72. # 1.5x order 1
  73. # 3.0x order 3
  74. # 30.0x order 4
  75. # 60.0x order 5
  76. # measurement based on 256x256x3 batches, difference is smaller
  77. # on smaller images (seems to grow more like exponentially with image
  78. # size)
  79. if order == ia.ALL:
  80. if backend in ["auto", "cv2"]:
  81. return iap.Choice([0, 1, 3])
  82. # dont use order=2 (bi-quadratic) because that is apparently
  83. # currently not recommended (and throws a warning)
  84. return iap.Choice([0, 1, 3, 4, 5])
  85. if ia.is_single_integer(order):
  86. assert 0 <= order <= 5, (
  87. "Expected order's integer value to be in the interval [0, 5], "
  88. "got %d." % (order,))
  89. if backend == "cv2":
  90. assert order in [0, 1, 3], (
  91. "Backend \"cv2\" and order=%d was chosen, but cv2 backend "
  92. "can only handle order 0, 1 or 3." % (order,))
  93. return iap.Deterministic(order)
  94. if isinstance(order, list):
  95. assert all([ia.is_single_integer(val) for val in order]), (
  96. "Expected order list to only contain integers, "
  97. "got types %s." % (str([type(val) for val in order]),))
  98. assert all([0 <= val <= 5 for val in order]), (
  99. "Expected all of order's integer values to be in range "
  100. "0 <= x <= 5, got %s." % (str(order),))
  101. if backend == "cv2":
  102. assert all([val in [0, 1, 3] for val in order]), (
  103. "cv2 backend can only handle order 0, 1 or 3. Got order "
  104. "list of %s." % (order,))
  105. return iap.Choice(order)
  106. if isinstance(order, iap.StochasticParameter):
  107. return order
  108. raise Exception(
  109. "Expected order to be imgaug.ALL, int, list of int or "
  110. "StochasticParameter, got %s." % (type(order),))
  111. def _handle_cval_arg(cval):
  112. if cval == ia.ALL:
  113. # TODO change this so that it is dynamically created per image
  114. # (or once per dtype)
  115. return iap.Uniform(0, 255) # skimage transform expects float
  116. return iap.handle_continuous_param(
  117. cval, "cval", value_range=None, tuple_to_uniform=True,
  118. list_to_choice=True)
  119. # currently used for Affine and PiecewiseAffine
  120. # TODO use iap.handle_categorical_string_param() here
  121. def _handle_mode_arg(mode):
  122. if mode == ia.ALL:
  123. return iap.Choice(["constant", "edge", "symmetric",
  124. "reflect", "wrap"])
  125. if ia.is_string(mode):
  126. return iap.Deterministic(mode)
  127. if isinstance(mode, list):
  128. assert all([ia.is_string(val) for val in mode]), (
  129. "Expected list of modes to only contain strings, got "
  130. "types %s" % (", ".join([str(type(v)) for v in mode])))
  131. return iap.Choice(mode)
  132. if isinstance(mode, iap.StochasticParameter):
  133. return mode
  134. raise Exception(
  135. "Expected mode to be imgaug.ALL, a string, a list of strings "
  136. "or StochasticParameter, got %s." % (type(mode),))
  137. def _warp_affine_arr(arr, matrix, order=1, mode="constant", cval=0,
  138. output_shape=None, backend="auto"):
  139. if ia.is_single_integer(cval):
  140. cval = [cval] * len(arr.shape[2])
  141. # no changes to zero-sized arrays
  142. if arr.size == 0:
  143. return arr
  144. min_value, _center_value, max_value = \
  145. iadt.get_value_range_of_dtype(arr.dtype)
  146. cv2_bad_order = order not in [0, 1, 3]
  147. if order == 0:
  148. cv2_bad_dtype = (
  149. arr.dtype.name
  150. not in _VALID_DTYPES_CV2_ORDER_0)
  151. else:
  152. cv2_bad_dtype = (
  153. arr.dtype.name
  154. not in _VALID_DTYPES_CV2_ORDER_NOT_0
  155. )
  156. cv2_impossible = cv2_bad_order or cv2_bad_dtype
  157. use_skimage = (
  158. backend == "skimage"
  159. or (backend == "auto" and cv2_impossible)
  160. )
  161. if use_skimage:
  162. # cval contains 3 values as cv2 can handle 3, but
  163. # skimage only 1
  164. cval = cval[0]
  165. # skimage does not clip automatically
  166. cval = max(min(cval, max_value), min_value)
  167. image_warped = _warp_affine_arr_skimage(
  168. arr,
  169. matrix,
  170. cval=cval,
  171. mode=mode,
  172. order=order,
  173. output_shape=output_shape
  174. )
  175. else:
  176. assert not cv2_bad_dtype, (
  177. not cv2_bad_dtype,
  178. "cv2 backend in Affine got a dtype %s, which it "
  179. "cannot handle. Try using a different dtype or set "
  180. "order=0." % (
  181. arr.dtype,))
  182. image_warped = _warp_affine_arr_cv2(
  183. arr,
  184. matrix,
  185. cval=tuple([int(v) for v in cval]),
  186. mode=mode,
  187. order=order,
  188. output_shape=output_shape
  189. )
  190. return image_warped
  191. def _warp_affine_arr_skimage(arr, matrix, cval, mode, order, output_shape):
  192. iadt.gate_dtypes(
  193. arr,
  194. allowed=["bool",
  195. "uint8", "uint16", "uint32",
  196. "int8", "int16", "int32",
  197. "float16", "float32", "float64"],
  198. disallowed=["uint64", "uint128", "uint256",
  199. "int64", "int128", "int256",
  200. "float96", "float128", "float256"],
  201. augmenter=None)
  202. input_dtype = arr.dtype
  203. image_warped = tf.warp(
  204. arr,
  205. matrix.inverse,
  206. order=order,
  207. mode=mode,
  208. cval=cval,
  209. preserve_range=True,
  210. output_shape=output_shape,
  211. )
  212. # tf.warp changes all dtypes to float64, including uint8
  213. if input_dtype == np.bool_:
  214. image_warped = image_warped > 0.5
  215. else:
  216. image_warped = iadt.restore_dtypes_(image_warped, input_dtype)
  217. return image_warped
  218. def _warp_affine_arr_cv2(arr, matrix, cval, mode, order, output_shape):
  219. iadt.gate_dtypes(
  220. arr,
  221. allowed=["bool",
  222. "uint8", "uint16",
  223. "int8", "int16", "int32",
  224. "float16", "float32", "float64"],
  225. disallowed=["uint32", "uint64", "uint128", "uint256",
  226. "int64", "int128", "int256",
  227. "float96", "float128", "float256"],
  228. augmenter=None)
  229. if order != 0:
  230. assert arr.dtype.name != "int32", (
  231. "Affine only supports cv2-based transformations of int32 "
  232. "arrays when using order=0, but order was set to %d." % (
  233. order,))
  234. input_dtype = arr.dtype
  235. if input_dtype in [np.bool_, np.float16]:
  236. arr = arr.astype(np.float32)
  237. elif input_dtype == np.int8 and order != 0:
  238. arr = arr.astype(np.int16)
  239. dsize = (
  240. int(np.round(output_shape[1])),
  241. int(np.round(output_shape[0]))
  242. )
  243. # map key X from skimage to cv2 or fall back to key X
  244. mode = _AFFINE_MODE_SKIMAGE_TO_CV2.get(mode, mode)
  245. order = _AFFINE_INTERPOLATION_ORDER_SKIMAGE_TO_CV2.get(order, order)
  246. # TODO this uses always a tuple of 3 values for cval, even if
  247. # #chans != 3, works with 1d but what in other cases?
  248. nb_channels = arr.shape[-1]
  249. if nb_channels <= 3:
  250. # TODO this block can also be when order==0 for any nb_channels,
  251. # but was deactivated for now, because cval would always
  252. # contain 3 values and not nb_channels values
  253. image_warped = cv2.warpAffine(
  254. _normalize_cv2_input_arr_(arr),
  255. matrix.params[:2],
  256. dsize=dsize,
  257. flags=order,
  258. borderMode=mode,
  259. borderValue=cval
  260. )
  261. # cv2 warp drops last axis if shape is (H, W, 1)
  262. if image_warped.ndim == 2:
  263. image_warped = image_warped[..., np.newaxis]
  264. else:
  265. # warp each channel on its own, re-add channel axis, then stack
  266. # the result from a list of [H, W, 1] to (H, W, C).
  267. image_warped = [
  268. cv2.warpAffine(
  269. _normalize_cv2_input_arr_(arr[:, :, c]),
  270. matrix.params[:2],
  271. dsize=dsize,
  272. flags=order,
  273. borderMode=mode,
  274. borderValue=tuple([cval[0]])
  275. )
  276. for c in sm.xrange(nb_channels)]
  277. image_warped = np.stack(image_warped, axis=-1)
  278. if input_dtype.name == "bool":
  279. image_warped = image_warped > 0.5
  280. elif input_dtype.name in ["int8", "float16"]:
  281. image_warped = iadt.restore_dtypes_(image_warped, input_dtype)
  282. return image_warped
  283. def _compute_affine_warp_output_shape(matrix, input_shape):
  284. height, width = input_shape[:2]
  285. if height == 0 or width == 0:
  286. return matrix, input_shape
  287. # determine shape of output image
  288. corners = np.array([
  289. [0, 0],
  290. [0, height - 1],
  291. [width - 1, height - 1],
  292. [width - 1, 0]
  293. ])
  294. corners = matrix(corners)
  295. minc = corners[:, 0].min()
  296. minr = corners[:, 1].min()
  297. maxc = corners[:, 0].max()
  298. maxr = corners[:, 1].max()
  299. out_height = maxr - minr + 1
  300. out_width = maxc - minc + 1
  301. if len(input_shape) == 3:
  302. output_shape = np.ceil((out_height, out_width, input_shape[2]))
  303. else:
  304. output_shape = np.ceil((out_height, out_width))
  305. output_shape = tuple([int(v) for v in output_shape.tolist()])
  306. # fit output image in new shape
  307. translation = (-minc, -minr)
  308. matrix_to_fit = tf.SimilarityTransform(translation=translation)
  309. matrix = matrix + matrix_to_fit
  310. return matrix, output_shape
  311. # TODO allow -1 destinations
  312. def apply_jigsaw(arr, destinations):
  313. """Move cells of an image similar to a jigsaw puzzle.
  314. This function will split the image into ``rows x cols`` cells and
  315. move each cell to the target index given in `destinations`.
  316. Added in 0.4.0.
  317. **Supported dtypes**:
  318. * ``uint8``: yes; fully tested
  319. * ``uint16``: yes; fully tested
  320. * ``uint32``: yes; fully tested
  321. * ``uint64``: yes; fully tested
  322. * ``int8``: yes; fully tested
  323. * ``int16``: yes; fully tested
  324. * ``int32``: yes; fully tested
  325. * ``int64``: yes; fully tested
  326. * ``float16``: yes; fully tested
  327. * ``float32``: yes; fully tested
  328. * ``float64``: yes; fully tested
  329. * ``float128``: yes; fully tested
  330. * ``bool``: yes; fully tested
  331. Parameters
  332. ----------
  333. arr : ndarray
  334. Array with at least two dimensions denoting height and width.
  335. destinations : ndarray
  336. 2-dimensional array containing for each cell the id of the destination
  337. cell. The order is expected to a flattened c-order, i.e. row by row.
  338. The height of the image must be evenly divisible by the number of
  339. rows in this array. Analogous for the width and columns.
  340. Returns
  341. -------
  342. ndarray
  343. Modified image with cells moved according to `destinations`.
  344. """
  345. # pylint complains about unravel_index() here
  346. # pylint: disable=unbalanced-tuple-unpacking
  347. nb_rows, nb_cols = destinations.shape[0:2]
  348. assert arr.ndim >= 2, (
  349. "Expected array with at least two dimensions, but got %d with "
  350. "shape %s." % (arr.ndim, arr.shape))
  351. assert (arr.shape[0] % nb_rows) == 0, (
  352. "Expected image height to by divisible by number of rows, but got "
  353. "height %d and %d rows. Use cropping or padding to modify the image "
  354. "height or change the number of rows." % (arr.shape[0], nb_rows)
  355. )
  356. assert (arr.shape[1] % nb_cols) == 0, (
  357. "Expected image width to by divisible by number of columns, but got "
  358. "width %d and %d columns. Use cropping or padding to modify the image "
  359. "width or change the number of columns." % (arr.shape[1], nb_cols)
  360. )
  361. cell_height = arr.shape[0] // nb_rows
  362. cell_width = arr.shape[1] // nb_cols
  363. dest_rows, dest_cols = np.unravel_index(
  364. destinations.flatten(), (nb_rows, nb_cols))
  365. result = np.zeros_like(arr)
  366. i = 0
  367. for source_row in np.arange(nb_rows):
  368. for source_col in np.arange(nb_cols):
  369. # TODO vectorize coords computation
  370. dest_row, dest_col = dest_rows[i], dest_cols[i]
  371. source_y1 = source_row * cell_height
  372. source_y2 = source_y1 + cell_height
  373. source_x1 = source_col * cell_width
  374. source_x2 = source_x1 + cell_width
  375. dest_y1 = dest_row * cell_height
  376. dest_y2 = dest_y1 + cell_height
  377. dest_x1 = dest_col * cell_width
  378. dest_x2 = dest_x1 + cell_width
  379. source = arr[source_y1:source_y2, source_x1:source_x2]
  380. result[dest_y1:dest_y2, dest_x1:dest_x2] = source
  381. i += 1
  382. return result
  383. def apply_jigsaw_to_coords(coords, destinations, image_shape):
  384. """Move coordinates on an image similar to a jigsaw puzzle.
  385. This is the same as :func:`apply_jigsaw`, but moves coordinates within
  386. the cells.
  387. Added in 0.4.0.
  388. Parameters
  389. ----------
  390. coords : ndarray
  391. ``(N, 2)`` array denoting xy-coordinates.
  392. destinations : ndarray
  393. See :func:`apply_jigsaw`.
  394. image_shape : tuple of int
  395. ``(height, width, ...)`` shape of the image on which the
  396. coordinates are placed. Only height and width are required.
  397. Returns
  398. -------
  399. ndarray
  400. Moved coordinates.
  401. """
  402. # pylint complains about unravel_index() here
  403. # pylint: disable=unbalanced-tuple-unpacking
  404. nb_rows, nb_cols = destinations.shape[0:2]
  405. height, width = image_shape[0:2]
  406. cell_height = height // nb_rows
  407. cell_width = width // nb_cols
  408. dest_rows, dest_cols = np.unravel_index(
  409. destinations.flatten(), (nb_rows, nb_cols))
  410. result = np.copy(coords)
  411. # TODO vectorize this loop
  412. for i, (x, y) in enumerate(coords):
  413. ooi_x = (x < 0 or x >= width)
  414. ooi_y = (y < 0 or y >= height)
  415. if ooi_x or ooi_y:
  416. continue
  417. source_row = int(y // cell_height)
  418. source_col = int(x // cell_width)
  419. source_cell_idx = (source_row * nb_cols) + source_col
  420. dest_row = dest_rows[source_cell_idx]
  421. dest_col = dest_cols[source_cell_idx]
  422. source_y1 = source_row * cell_height
  423. source_x1 = source_col * cell_width
  424. dest_y1 = dest_row * cell_height
  425. dest_x1 = dest_col * cell_width
  426. result[i, 0] = dest_x1 + (x - source_x1)
  427. result[i, 1] = dest_y1 + (y - source_y1)
  428. return result
  429. def generate_jigsaw_destinations(nb_rows, nb_cols, max_steps, seed,
  430. connectivity=4):
  431. """Generate a destination pattern for :func:`apply_jigsaw`.
  432. Added in 0.4.0.
  433. Parameters
  434. ----------
  435. nb_rows : int
  436. Number of rows to split the image into.
  437. nb_cols : int
  438. Number of columns to split the image into.
  439. max_steps : int
  440. Maximum number of cells that each cell may be moved.
  441. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
  442. Seed value or alternatively RNG to use.
  443. If ``None`` the global RNG will be used.
  444. connectivity : int, optional
  445. Whether a diagonal move of a cell counts as one step
  446. (``connectivity=8``) or two steps (``connectivity=4``).
  447. Returns
  448. -------
  449. ndarray
  450. 2-dimensional array containing for each cell the id of the target
  451. cell.
  452. """
  453. assert connectivity in (4, 8), (
  454. "Expected connectivity of 4 or 8, got %d." % (connectivity,))
  455. random_state = iarandom.RNG(seed)
  456. steps = random_state.integers(0, max_steps, size=(nb_rows, nb_cols),
  457. endpoint=True)
  458. directions = random_state.integers(0, connectivity,
  459. size=(nb_rows, nb_cols, max_steps),
  460. endpoint=False)
  461. destinations = np.arange(nb_rows*nb_cols).reshape((nb_rows, nb_cols))
  462. for step in np.arange(max_steps):
  463. directions_step = directions[:, :, step]
  464. for y in np.arange(nb_rows):
  465. for x in np.arange(nb_cols):
  466. if steps[y, x] > 0:
  467. y_target, x_target = {
  468. 0: (y-1, x+0),
  469. 1: (y+0, x+1),
  470. 2: (y+1, x+0),
  471. 3: (y+0, x-1),
  472. 4: (y-1, x-1),
  473. 5: (y-1, x+1),
  474. 6: (y+1, x+1),
  475. 7: (y+1, x-1)
  476. }[directions_step[y, x]]
  477. y_target = max(min(y_target, nb_rows-1), 0)
  478. x_target = max(min(x_target, nb_cols-1), 0)
  479. target_steps = steps[y_target, x_target]
  480. if (y, x) != (y_target, x_target) and target_steps >= 1:
  481. source_dest = destinations[y, x]
  482. target_dest = destinations[y_target, x_target]
  483. destinations[y, x] = target_dest
  484. destinations[y_target, x_target] = source_dest
  485. steps[y, x] -= 1
  486. steps[y_target, x_target] -= 1
  487. return destinations
  488. class _AffineSamplingResult(object):
  489. def __init__(self, scale=None, translate=None, translate_mode="px",
  490. rotate=None, shear=None, cval=None, mode=None, order=None):
  491. self.scale = scale
  492. self.translate = translate
  493. self.translate_mode = translate_mode
  494. self.rotate = rotate
  495. self.shear = shear
  496. self.cval = cval
  497. self.mode = mode
  498. self.order = order
  499. # Added in 0.4.0.
  500. def get_affine_parameters(self, idx, arr_shape, image_shape):
  501. scale_y = self.scale[1][idx] # TODO 1 and 0 should be inverted here
  502. scale_x = self.scale[0][idx]
  503. translate_y = self.translate[1][idx] # TODO same as above
  504. translate_x = self.translate[0][idx]
  505. assert self.translate_mode in ["px", "percent"], (
  506. "Expected 'px' or 'percent', got '%s'." % (self.translate_mode,))
  507. if self.translate_mode == "percent":
  508. translate_y_px = translate_y * arr_shape[0]
  509. else:
  510. translate_y_px = (translate_y / image_shape[0]) * arr_shape[0]
  511. if self.translate_mode == "percent":
  512. translate_x_px = translate_x * arr_shape[1]
  513. else:
  514. translate_x_px = (translate_x / image_shape[1]) * arr_shape[1]
  515. rotate_deg = self.rotate[idx]
  516. shear_x_deg = self.shear[0][idx]
  517. shear_y_deg = self.shear[1][idx]
  518. rotate_rad, shear_x_rad, shear_y_rad = np.deg2rad([
  519. rotate_deg, shear_x_deg, shear_y_deg])
  520. # we add the _deg versions of rotate and shear here for PILAffine,
  521. # Affine itself only uses *_rad
  522. return {
  523. "scale_y": scale_y,
  524. "scale_x": scale_x,
  525. "translate_y_px": translate_y_px,
  526. "translate_x_px": translate_x_px,
  527. "rotate_rad": rotate_rad,
  528. "shear_y_rad": shear_y_rad,
  529. "shear_x_rad": shear_x_rad,
  530. "rotate_deg": rotate_deg,
  531. "shear_y_deg": shear_y_deg,
  532. "shear_x_deg": shear_x_deg
  533. }
  534. def to_matrix(self, idx, arr_shape, image_shape, fit_output,
  535. shift_add=(0.5, 0.5)):
  536. if 0 in image_shape:
  537. return tf.AffineTransform(), arr_shape
  538. height, width = arr_shape[0:2]
  539. params = self.get_affine_parameters(idx,
  540. arr_shape=arr_shape,
  541. image_shape=image_shape)
  542. # for images we use additional shifts of (0.5, 0.5) as otherwise
  543. # we get an ugly black border for 90deg rotations
  544. shift_y = height / 2.0 - shift_add[0]
  545. shift_x = width / 2.0 - shift_add[1]
  546. matrix_to_topleft = tf.SimilarityTransform(
  547. translation=[-shift_x, -shift_y])
  548. matrix_shear_y_rot = tf.AffineTransform(rotation=-3.141592/2)
  549. matrix_shear_y = tf.AffineTransform(shear=params["shear_y_rad"])
  550. matrix_shear_y_rot_inv = tf.AffineTransform(rotation=3.141592/2)
  551. matrix_transforms = tf.AffineTransform(
  552. scale=(params["scale_x"], params["scale_y"]),
  553. translation=(params["translate_x_px"], params["translate_y_px"]),
  554. rotation=params["rotate_rad"],
  555. shear=params["shear_x_rad"]
  556. )
  557. matrix_to_center = tf.SimilarityTransform(
  558. translation=[shift_x, shift_y])
  559. matrix = (matrix_to_topleft
  560. + matrix_shear_y_rot
  561. + matrix_shear_y
  562. + matrix_shear_y_rot_inv
  563. + matrix_transforms
  564. + matrix_to_center)
  565. if fit_output:
  566. return _compute_affine_warp_output_shape(matrix, arr_shape)
  567. return matrix, arr_shape
  568. # Added in 0.4.0.
  569. def to_matrix_cba(self, idx, arr_shape, fit_output, shift_add=(0.0, 0.0)):
  570. return self.to_matrix(idx, arr_shape, arr_shape, fit_output, shift_add)
  571. # Added in 0.4.0.
  572. def copy(self):
  573. return _AffineSamplingResult(
  574. scale=self.scale,
  575. translate=self.translate,
  576. translate_mode=self.translate_mode,
  577. rotate=self.rotate,
  578. shear=self.shear,
  579. cval=self.cval,
  580. mode=self.mode,
  581. order=self.order
  582. )
  583. def _is_identity_matrix(matrix, eps=1e-4):
  584. identity = np.float32([
  585. [1, 0, 0],
  586. [0, 1, 0],
  587. [0, 0, 1]
  588. ])
  589. return np.average(np.abs(identity - matrix.params)) <= eps
  590. class Affine(meta.Augmenter):
  591. """
  592. Augmenter to apply affine transformations to images.
  593. This is mostly a wrapper around the corresponding classes and functions
  594. in OpenCV and skimage.
  595. Affine transformations involve:
  596. - Translation ("move" image on the x-/y-axis)
  597. - Rotation
  598. - Scaling ("zoom" in/out)
  599. - Shear (move one side of the image, turning a square into a trapezoid)
  600. All such transformations can create "new" pixels in the image without a
  601. defined content, e.g. if the image is translated to the left, pixels
  602. are created on the right.
  603. A method has to be defined to deal with these pixel values. The
  604. parameters `cval` and `mode` of this class deal with this.
  605. Some transformations involve interpolations between several pixels
  606. of the input image to generate output pixel values. The parameter `order`
  607. deals with the method of interpolation used for this.
  608. .. note::
  609. While this augmenter supports segmentation maps and heatmaps that
  610. have a different size than the corresponding image, it is strongly
  611. recommended to use the same aspect ratios. E.g. for an image of
  612. shape ``(200, 100, 3)``, good segmap/heatmap array shapes also follow
  613. a ``2:1`` ratio and ideally are ``(200, 100, C)``, ``(100, 50, C)`` or
  614. ``(50, 25, C)``. Otherwise, transformations involving rotations or
  615. shearing will produce unaligned outputs.
  616. For performance reasons, there is no explicit validation of whether
  617. the aspect ratios are similar.
  618. **Supported dtypes**:
  619. if (backend="skimage", order in [0, 1]):
  620. * ``uint8``: yes; tested
  621. * ``uint16``: yes; tested
  622. * ``uint32``: yes; tested (1)
  623. * ``uint64``: no (2)
  624. * ``int8``: yes; tested
  625. * ``int16``: yes; tested
  626. * ``int32``: yes; tested (1)
  627. * ``int64``: no (2)
  628. * ``float16``: yes; tested
  629. * ``float32``: yes; tested
  630. * ``float64``: yes; tested
  631. * ``float128``: no (2)
  632. * ``bool``: yes; tested
  633. - (1) scikit-image converts internally to float64, which might
  634. affect the accuracy of large integers. In tests this seemed
  635. to not be an issue.
  636. - (2) results too inaccurate
  637. if (backend="skimage", order in [3, 4]):
  638. * ``uint8``: yes; tested
  639. * ``uint16``: yes; tested
  640. * ``uint32``: yes; tested (1)
  641. * ``uint64``: no (2)
  642. * ``int8``: yes; tested
  643. * ``int16``: yes; tested
  644. * ``int32``: yes; tested (1)
  645. * ``int64``: no (2)
  646. * ``float16``: yes; tested
  647. * ``float32``: yes; tested
  648. * ``float64``: limited; tested (3)
  649. * ``float128``: no (2)
  650. * ``bool``: yes; tested
  651. - (1) scikit-image converts internally to float64, which might
  652. affect the accuracy of large integers. In tests this seemed
  653. to not be an issue.
  654. - (2) results too inaccurate
  655. - (3) ``NaN`` around minimum and maximum of float64 value range
  656. if (backend="skimage", order=5]):
  657. * ``uint8``: yes; tested
  658. * ``uint16``: yes; tested
  659. * ``uint32``: yes; tested (1)
  660. * ``uint64``: no (2)
  661. * ``int8``: yes; tested
  662. * ``int16``: yes; tested
  663. * ``int32``: yes; tested (1)
  664. * ``int64``: no (2)
  665. * ``float16``: yes; tested
  666. * ``float32``: yes; tested
  667. * ``float64``: limited; not tested (3)
  668. * ``float128``: no (2)
  669. * ``bool``: yes; tested
  670. - (1) scikit-image converts internally to ``float64``, which
  671. might affect the accuracy of large integers. In tests
  672. this seemed to not be an issue.
  673. - (2) results too inaccurate
  674. - (3) ``NaN`` around minimum and maximum of float64 value range
  675. if (backend="cv2", order=0):
  676. * ``uint8``: yes; tested
  677. * ``uint16``: yes; tested
  678. * ``uint32``: no (1)
  679. * ``uint64``: no (2)
  680. * ``int8``: yes; tested
  681. * ``int16``: yes; tested
  682. * ``int32``: yes; tested
  683. * ``int64``: no (2)
  684. * ``float16``: yes; tested (3)
  685. * ``float32``: yes; tested
  686. * ``float64``: yes; tested
  687. * ``float128``: no (1)
  688. * ``bool``: yes; tested (3)
  689. - (1) rejected by cv2
  690. - (2) changed to ``int32`` by cv2
  691. - (3) mapped internally to ``float32``
  692. if (backend="cv2", order=1):
  693. * ``uint8``: yes; fully tested
  694. * ``uint16``: yes; tested
  695. * ``uint32``: no (1)
  696. * ``uint64``: no (2)
  697. * ``int8``: yes; tested (3)
  698. * ``int16``: yes; tested
  699. * ``int32``: no (2)
  700. * ``int64``: no (2)
  701. * ``float16``: yes; tested (4)
  702. * ``float32``: yes; tested
  703. * ``float64``: yes; tested
  704. * ``float128``: no (1)
  705. * ``bool``: yes; tested (4)
  706. - (1) rejected by cv2
  707. - (2) causes cv2 error: ``cv2.error: OpenCV(3.4.4)
  708. (...)imgwarp.cpp:1805: error:
  709. (-215:Assertion failed) ifunc != 0 in function 'remap'``
  710. - (3) mapped internally to ``int16``
  711. - (4) mapped internally to ``float32``
  712. if (backend="cv2", order=3):
  713. * ``uint8``: yes; tested
  714. * ``uint16``: yes; tested
  715. * ``uint32``: no (1)
  716. * ``uint64``: no (2)
  717. * ``int8``: yes; tested (3)
  718. * ``int16``: yes; tested
  719. * ``int32``: no (2)
  720. * ``int64``: no (2)
  721. * ``float16``: yes; tested (4)
  722. * ``float32``: yes; tested
  723. * ``float64``: yes; tested
  724. * ``float128``: no (1)
  725. * ``bool``: yes; tested (4)
  726. - (1) rejected by cv2
  727. - (2) causes cv2 error: ``cv2.error: OpenCV(3.4.4)
  728. (...)imgwarp.cpp:1805: error:
  729. (-215:Assertion failed) ifunc != 0 in function 'remap'``
  730. - (3) mapped internally to ``int16``
  731. - (4) mapped internally to ``float32``
  732. Parameters
  733. ----------
  734. scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
  735. Scaling factor to use, where ``1.0`` denotes "no change" and
  736. ``0.5`` is zoomed out to ``50`` percent of the original size.
  737. * If a single number, then that value will be used for all images.
  738. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  739. per image from the interval ``[a, b]``. That value will be
  740. used identically for both x- and y-axis.
  741. * If a list, then a random value will be sampled from that list
  742. per image (again, used for both x- and y-axis).
  743. * If a ``StochasticParameter``, then from that parameter a value
  744. will be sampled per image (again, used for both x- and y-axis).
  745. * If a dictionary, then it is expected to have the keys ``x``
  746. and/or ``y``. Each of these keys can have the same values as
  747. described above. Using a dictionary allows to set different
  748. values for the two axis and sampling will then happen
  749. *independently* per axis, resulting in samples that differ
  750. between the axes.
  751. translate_percent : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
  752. Translation as a fraction of the image height/width (x-translation,
  753. y-translation), where ``0`` denotes "no change" and ``0.5`` denotes
  754. "half of the axis size".
  755. * If ``None`` then equivalent to ``0.0`` unless `translate_px` has
  756. a value other than ``None``.
  757. * If a single number, then that value will be used for all images.
  758. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  759. per image from the interval ``[a, b]``. That sampled fraction
  760. value will be used identically for both x- and y-axis.
  761. * If a list, then a random value will be sampled from that list
  762. per image (again, used for both x- and y-axis).
  763. * If a ``StochasticParameter``, then from that parameter a value
  764. will be sampled per image (again, used for both x- and y-axis).
  765. * If a dictionary, then it is expected to have the keys ``x``
  766. and/or ``y``. Each of these keys can have the same values as
  767. described above. Using a dictionary allows to set different
  768. values for the two axis and sampling will then happen
  769. *independently* per axis, resulting in samples that differ
  770. between the axes.
  771. translate_px : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
  772. Translation in pixels.
  773. * If ``None`` then equivalent to ``0`` unless `translate_percent`
  774. has a value other than ``None``.
  775. * If a single int, then that value will be used for all images.
  776. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  777. per image from the discrete interval ``[a..b]``. That number
  778. will be used identically for both x- and y-axis.
  779. * If a list, then a random value will be sampled from that list
  780. per image (again, used for both x- and y-axis).
  781. * If a ``StochasticParameter``, then from that parameter a value
  782. will be sampled per image (again, used for both x- and y-axis).
  783. * If a dictionary, then it is expected to have the keys ``x``
  784. and/or ``y``. Each of these keys can have the same values as
  785. described above. Using a dictionary allows to set different
  786. values for the two axis and sampling will then happen
  787. *independently* per axis, resulting in samples that differ
  788. between the axes.
  789. rotate : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  790. Rotation in degrees (**NOT** radians), i.e. expected value range is
  791. around ``[-360, 360]``. Rotation happens around the *center* of the
  792. image, not the top left corner as in some other frameworks.
  793. * If a number, then that value will be used for all images.
  794. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  795. per image from the interval ``[a, b]`` and used as the rotation
  796. value.
  797. * If a list, then a random value will be sampled from that list
  798. per image.
  799. * If a ``StochasticParameter``, then this parameter will be used to
  800. sample the rotation value per image.
  801. shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
  802. Shear in degrees (**NOT** radians), i.e. expected value range is
  803. around ``[-360, 360]``, with reasonable values being in the range
  804. of ``[-45, 45]``.
  805. * If a number, then that value will be used for all images as
  806. the shear on the x-axis (no shear on the y-axis will be done).
  807. * If a tuple ``(a, b)``, then two value will be uniformly sampled
  808. per image from the interval ``[a, b]`` and be used as the
  809. x- and y-shear value.
  810. * If a list, then two random values will be sampled from that list
  811. per image, denoting x- and y-shear.
  812. * If a ``StochasticParameter``, then this parameter will be used
  813. to sample the x- and y-shear values per image.
  814. * If a dictionary, then similar to `translate_percent`, i.e. one
  815. ``x`` key and/or one ``y`` key are expected, denoting the
  816. shearing on the x- and y-axis respectively. The allowed datatypes
  817. are again ``number``, ``tuple`` ``(a, b)``, ``list`` or
  818. ``StochasticParameter``.
  819. order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  820. Interpolation order to use. Same meaning as in ``skimage``:
  821. * ``0``: ``Nearest-neighbor``
  822. * ``1``: ``Bi-linear`` (default)
  823. * ``2``: ``Bi-quadratic`` (not recommended by skimage)
  824. * ``3``: ``Bi-cubic``
  825. * ``4``: ``Bi-quartic``
  826. * ``5``: ``Bi-quintic``
  827. Method ``0`` and ``1`` are fast, ``3`` is a bit slower, ``4`` and
  828. ``5`` are very slow. If the backend is ``cv2``, the mapping to
  829. OpenCV's interpolation modes is as follows:
  830. * ``0`` -> ``cv2.INTER_NEAREST``
  831. * ``1`` -> ``cv2.INTER_LINEAR``
  832. * ``2`` -> ``cv2.INTER_CUBIC``
  833. * ``3`` -> ``cv2.INTER_CUBIC``
  834. * ``4`` -> ``cv2.INTER_CUBIC``
  835. As datatypes this parameter accepts:
  836. * If a single ``int``, then that order will be used for all images.
  837. * If a list, then a random value will be sampled from that list
  838. per image.
  839. * If ``imgaug.ALL``, then equivalant to list ``[0, 1, 3, 4, 5]``
  840. in case of ``backend=skimage`` and otherwise ``[0, 1, 3]``.
  841. * If ``StochasticParameter``, then that parameter is queried per
  842. image to sample the order value to use.
  843. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  844. The constant value to use when filling in newly created pixels.
  845. (E.g. translating by 1px to the right will create a new 1px-wide
  846. column of pixels on the left of the image). The value is only used
  847. when `mode=constant`. The expected value range is ``[0, 255]`` for
  848. ``uint8`` images. It may be a float value.
  849. * If this is a single number, then that value will be used
  850. (e.g. 0 results in black pixels).
  851. * If a tuple ``(a, b)``, then three values (for three image
  852. channels) will be uniformly sampled per image from the
  853. interval ``[a, b]``.
  854. * If a list, then a random value will be sampled from that list
  855. per image.
  856. * If ``imgaug.ALL`` then equivalent to tuple ``(0, 255)`.
  857. * If a ``StochasticParameter``, a new value will be sampled from
  858. the parameter per image.
  859. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  860. Method to use when filling in newly created pixels.
  861. Same meaning as in ``skimage`` (and :func:`numpy.pad`):
  862. * ``constant``: Pads with a constant value
  863. * ``edge``: Pads with the edge values of array
  864. * ``symmetric``: Pads with the reflection of the vector mirrored
  865. along the edge of the array.
  866. * ``reflect``: Pads with the reflection of the vector mirrored on
  867. the first and last values of the vector along each axis.
  868. * ``wrap``: Pads with the wrap of the vector along the axis.
  869. The first values are used to pad the end and the end values
  870. are used to pad the beginning.
  871. If ``cv2`` is chosen as the backend the mapping is as follows:
  872. * ``constant`` -> ``cv2.BORDER_CONSTANT``
  873. * ``edge`` -> ``cv2.BORDER_REPLICATE``
  874. * ``symmetric`` -> ``cv2.BORDER_REFLECT``
  875. * ``reflect`` -> ``cv2.BORDER_REFLECT_101``
  876. * ``wrap`` -> ``cv2.BORDER_WRAP``
  877. The datatype of the parameter may be:
  878. * If a single string, then that mode will be used for all images.
  879. * If a list of strings, then a random mode will be picked
  880. from that list per image.
  881. * If ``imgaug.ALL``, then a random mode from all possible modes
  882. will be picked.
  883. * If ``StochasticParameter``, then the mode will be sampled from
  884. that parameter per image, i.e. it must return only the above
  885. mentioned strings.
  886. fit_output : bool, optional
  887. Whether to modify the affine transformation so that the whole output
  888. image is always contained in the image plane (``True``) or accept
  889. parts of the image being outside the image plane (``False``).
  890. This can be thought of as first applying the affine transformation
  891. and then applying a second transformation to "zoom in" on the new
  892. image so that it fits the image plane,
  893. This is useful to avoid corners of the image being outside of the image
  894. plane after applying rotations. It will however negate translation
  895. and scaling.
  896. Note also that activating this may lead to image sizes differing from
  897. the input image sizes. To avoid this, wrap ``Affine`` in
  898. :class:`~imgaug.augmenters.size.KeepSizeByResize`,
  899. e.g. ``KeepSizeByResize(Affine(...))``.
  900. backend : str, optional
  901. Framework to use as a backend. Valid values are ``auto``, ``skimage``
  902. (scikit-image's warp) and ``cv2`` (OpenCV's warp).
  903. If ``auto`` is used, the augmenter will automatically try
  904. to use ``cv2`` whenever possible (order must be in ``[0, 1, 3]``). It
  905. will silently fall back to skimage if order/dtype is not supported by
  906. cv2. cv2 is generally faster than skimage. It also supports RGB cvals,
  907. while skimage will resort to intensity cvals (i.e. 3x the same value
  908. as RGB). If ``cv2`` is chosen and order is ``2`` or ``4``, it will
  909. automatically fall back to order ``3``.
  910. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  911. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  912. name : None or str, optional
  913. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  914. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  915. Old name for parameter `seed`.
  916. Its usage will not yet cause a deprecation warning,
  917. but it is still recommended to use `seed` now.
  918. Outdated since 0.4.0.
  919. deterministic : bool, optional
  920. Deprecated since 0.4.0.
  921. See method ``to_deterministic()`` for an alternative and for
  922. details about what the "deterministic mode" actually does.
  923. Examples
  924. --------
  925. >>> import imgaug.augmenters as iaa
  926. >>> aug = iaa.Affine(scale=2.0)
  927. Zoom in on all images by a factor of ``2``.
  928. >>> aug = iaa.Affine(translate_px=16)
  929. Translate all images on the x- and y-axis by 16 pixels (towards the
  930. bottom right) and fill up any new pixels with zero (black values).
  931. >>> aug = iaa.Affine(translate_percent=0.1)
  932. Translate all images on the x- and y-axis by ``10`` percent of their
  933. width/height (towards the bottom right). The pixel values are computed
  934. per axis based on that axis' size. Fill up any new pixels with zero
  935. (black values).
  936. >>> aug = iaa.Affine(rotate=35)
  937. Rotate all images by ``35`` *degrees*. Fill up any new pixels with zero
  938. (black values).
  939. >>> aug = iaa.Affine(shear=15)
  940. Shear all images by ``15`` *degrees*. Fill up any new pixels with zero
  941. (black values).
  942. >>> aug = iaa.Affine(translate_px=(-16, 16))
  943. Translate all images on the x- and y-axis by a random value
  944. between ``-16`` and ``16`` pixels (to the bottom right) and fill up any new
  945. pixels with zero (black values). The translation value is sampled once
  946. per image and is the same for both axis.
  947. >>> aug = iaa.Affine(translate_px={"x": (-16, 16), "y": (-4, 4)})
  948. Translate all images on the x-axis by a random value
  949. between ``-16`` and ``16`` pixels (to the right) and on the y-axis by a
  950. random value between ``-4`` and ``4`` pixels to the bottom. The sampling
  951. happens independently per axis, so even if both intervals were identical,
  952. the sampled axis-wise values would likely be different.
  953. This also fills up any new pixels with zero (black values).
  954. >>> aug = iaa.Affine(scale=2.0, order=[0, 1])
  955. Same as in the above `scale` example, but uses (randomly) either
  956. nearest neighbour interpolation or linear interpolation. If `order` is
  957. not specified, ``order=1`` would be used by default.
  958. >>> aug = iaa.Affine(translate_px=16, cval=(0, 255))
  959. Same as in the `translate_px` example above, but newly created pixels
  960. are now filled with a random color (sampled once per image and the
  961. same for all newly created pixels within that image).
  962. >>> aug = iaa.Affine(translate_px=16, mode=["constant", "edge"])
  963. Similar to the previous example, but the newly created pixels are
  964. filled with black pixels in half of all images (mode ``constant`` with
  965. default `cval` being ``0``) and in the other half of all images using
  966. ``edge`` mode, which repeats the color of the spatially closest pixel
  967. of the corresponding image edge.
  968. >>> aug = iaa.Affine(shear={"y": (-45, 45)})
  969. Shear images only on the y-axis. Set `shear` to ``shear=(-45, 45)`` to
  970. shear randomly on both axes, using for each image the same sample for
  971. both the x- and y-axis. Use ``shear={"x": (-45, 45), "y": (-45, 45)}``
  972. to get independent samples per axis.
  973. """
  974. def __init__(self, scale=None, translate_percent=None, translate_px=None,
  975. rotate=None, shear=None, order=1, cval=0, mode="constant",
  976. fit_output=False, backend="auto",
  977. seed=None, name=None,
  978. random_state="deprecated", deterministic="deprecated"):
  979. super(Affine, self).__init__(
  980. seed=seed, name=name,
  981. random_state=random_state, deterministic=deterministic)
  982. params = [scale, translate_percent, translate_px, rotate, shear]
  983. if all([p is None for p in params]):
  984. scale = {"x": (0.9, 1.1), "y": (0.9, 1.1)}
  985. translate_percent = {"x": (-0.1, 0.1), "y": (-0.1, 0.1)}
  986. rotate = (-15, 15)
  987. shear = {"x": (-10, 10), "y": (-10, 10)}
  988. else:
  989. scale = scale if scale is not None else 1.0
  990. rotate = rotate if rotate is not None else 0.0
  991. shear = shear if shear is not None else 0.0
  992. assert backend in ["auto", "skimage", "cv2"], (
  993. "Expected 'backend' to be \"auto\", \"skimage\" or \"cv2\", "
  994. "got %s." % (backend,))
  995. self.backend = backend
  996. self.order = _handle_order_arg(order, backend)
  997. self.cval = _handle_cval_arg(cval)
  998. self.mode = _handle_mode_arg(mode)
  999. self.scale = self._handle_scale_arg(scale)
  1000. self.translate = self._handle_translate_arg(
  1001. translate_px, translate_percent)
  1002. self.rotate = iap.handle_continuous_param(
  1003. rotate, "rotate", value_range=None, tuple_to_uniform=True,
  1004. list_to_choice=True)
  1005. self.shear, self._shear_param_type = self._handle_shear_arg(shear)
  1006. self.fit_output = fit_output
  1007. # Special order, mode and cval parameters for heatmaps and
  1008. # segmentation maps. These may either be None or a fixed value.
  1009. # Stochastic parameters are currently *not* supported.
  1010. # If set to None, the same values as for images will be used.
  1011. # That is really not recommended for the cval parameter.
  1012. #
  1013. # Segmentation map augmentation by default always pads with a
  1014. # constant value of 0 (background class id), and always uses nearest
  1015. # neighbour interpolation. While other pad modes and BG class ids
  1016. # could be used, the interpolation mode has to be NN as any other
  1017. # mode would lead to averaging class ids, which makes no sense to do.
  1018. self._order_heatmaps = 3
  1019. self._order_segmentation_maps = 0
  1020. self._mode_heatmaps = "constant"
  1021. self._mode_segmentation_maps = "constant"
  1022. self._cval_heatmaps = 0
  1023. self._cval_segmentation_maps = 0
  1024. @classmethod
  1025. def _handle_scale_arg(cls, scale):
  1026. if isinstance(scale, dict):
  1027. assert "x" in scale or "y" in scale, (
  1028. "Expected scale dictionary to contain at least key \"x\" or "
  1029. "key \"y\". Found neither of them.")
  1030. x = scale.get("x", 1.0)
  1031. y = scale.get("y", 1.0)
  1032. return (
  1033. iap.handle_continuous_param(
  1034. x, "scale['x']", value_range=(0+1e-4, None),
  1035. tuple_to_uniform=True, list_to_choice=True),
  1036. iap.handle_continuous_param(
  1037. y, "scale['y']", value_range=(0+1e-4, None),
  1038. tuple_to_uniform=True, list_to_choice=True)
  1039. )
  1040. return iap.handle_continuous_param(
  1041. scale, "scale", value_range=(0+1e-4, None),
  1042. tuple_to_uniform=True, list_to_choice=True)
  1043. @classmethod
  1044. def _handle_translate_arg(cls, translate_px, translate_percent):
  1045. # pylint: disable=no-else-return
  1046. if translate_percent is None and translate_px is None:
  1047. translate_px = 0
  1048. assert translate_percent is None or translate_px is None, (
  1049. "Expected either translate_percent or translate_px to be "
  1050. "provided, but neither of them was.")
  1051. if translate_percent is not None:
  1052. # translate by percent
  1053. if isinstance(translate_percent, dict):
  1054. assert "x" in translate_percent or "y" in translate_percent, (
  1055. "Expected translate_percent dictionary to contain at "
  1056. "least key \"x\" or key \"y\". Found neither of them.")
  1057. x = translate_percent.get("x", 0)
  1058. y = translate_percent.get("y", 0)
  1059. return (
  1060. iap.handle_continuous_param(
  1061. x, "translate_percent['x']", value_range=None,
  1062. tuple_to_uniform=True, list_to_choice=True),
  1063. iap.handle_continuous_param(
  1064. y, "translate_percent['y']", value_range=None,
  1065. tuple_to_uniform=True, list_to_choice=True),
  1066. "percent"
  1067. )
  1068. return (
  1069. iap.handle_continuous_param(
  1070. translate_percent, "translate_percent",
  1071. value_range=None, tuple_to_uniform=True,
  1072. list_to_choice=True),
  1073. None,
  1074. "percent"
  1075. )
  1076. else:
  1077. # translate by pixels
  1078. if isinstance(translate_px, dict):
  1079. assert "x" in translate_px or "y" in translate_px, (
  1080. "Expected translate_px dictionary to contain at "
  1081. "least key \"x\" or key \"y\". Found neither of them.")
  1082. x = translate_px.get("x", 0)
  1083. y = translate_px.get("y", 0)
  1084. return (
  1085. iap.handle_discrete_param(
  1086. x, "translate_px['x']", value_range=None,
  1087. tuple_to_uniform=True, list_to_choice=True,
  1088. allow_floats=False),
  1089. iap.handle_discrete_param(
  1090. y, "translate_px['y']", value_range=None,
  1091. tuple_to_uniform=True, list_to_choice=True,
  1092. allow_floats=False),
  1093. "px"
  1094. )
  1095. return (
  1096. iap.handle_discrete_param(
  1097. translate_px, "translate_px", value_range=None,
  1098. tuple_to_uniform=True, list_to_choice=True,
  1099. allow_floats=False),
  1100. None,
  1101. "px"
  1102. )
  1103. # Added in 0.4.0.
  1104. @classmethod
  1105. def _handle_shear_arg(cls, shear):
  1106. # pylint: disable=no-else-return
  1107. if isinstance(shear, dict):
  1108. assert "x" in shear or "y" in shear, (
  1109. "Expected shear dictionary to contain at "
  1110. "least key \"x\" or key \"y\". Found neither of them.")
  1111. x = shear.get("x", 0)
  1112. y = shear.get("y", 0)
  1113. return (
  1114. iap.handle_continuous_param(
  1115. x, "shear['x']", value_range=None,
  1116. tuple_to_uniform=True, list_to_choice=True),
  1117. iap.handle_continuous_param(
  1118. y, "shear['y']", value_range=None,
  1119. tuple_to_uniform=True, list_to_choice=True)
  1120. ), "dict"
  1121. else:
  1122. param_type = "other"
  1123. if ia.is_single_number(shear):
  1124. param_type = "single-number"
  1125. return iap.handle_continuous_param(
  1126. shear, "shear", value_range=None, tuple_to_uniform=True,
  1127. list_to_choice=True
  1128. ), param_type
  1129. # Added in 0.4.0.
  1130. def _augment_batch_(self, batch, random_state, parents, hooks):
  1131. samples = self._draw_samples(batch.nb_rows, random_state)
  1132. if batch.images is not None:
  1133. batch.images = self._augment_images_by_samples(batch.images,
  1134. samples)
  1135. if batch.heatmaps is not None:
  1136. batch.heatmaps = self._augment_maps_by_samples(
  1137. batch.heatmaps, samples, "arr_0to1", self._cval_heatmaps,
  1138. self._mode_heatmaps, self._order_heatmaps, "float32")
  1139. if batch.segmentation_maps is not None:
  1140. batch.segmentation_maps = self._augment_maps_by_samples(
  1141. batch.segmentation_maps, samples, "arr",
  1142. self._cval_segmentation_maps, self._mode_segmentation_maps,
  1143. self._order_segmentation_maps, "int32")
  1144. for augm_name in ["keypoints", "bounding_boxes", "polygons",
  1145. "line_strings"]:
  1146. augm_value = getattr(batch, augm_name)
  1147. if augm_value is not None:
  1148. for i, cbaoi in enumerate(augm_value):
  1149. matrix, output_shape = samples.to_matrix_cba(
  1150. i, cbaoi.shape, self.fit_output)
  1151. if (not _is_identity_matrix(matrix)
  1152. and not cbaoi.empty
  1153. and not 0 in cbaoi.shape[0:2]):
  1154. # TODO this is hacky
  1155. if augm_name == "bounding_boxes":
  1156. # Ensure that 4 points are used for bbs.
  1157. # to_keypoints_on_images() does return 4 points,
  1158. # to_xy_array() does not.
  1159. kpsoi = cbaoi.to_keypoints_on_image()
  1160. coords = kpsoi.to_xy_array()
  1161. coords_aug = tf.matrix_transform(coords,
  1162. matrix.params)
  1163. kpsoi = kpsoi.fill_from_xy_array_(coords_aug)
  1164. cbaoi = cbaoi.invert_to_keypoints_on_image_(
  1165. kpsoi)
  1166. else:
  1167. coords = cbaoi.to_xy_array()
  1168. coords_aug = tf.matrix_transform(coords,
  1169. matrix.params)
  1170. cbaoi = cbaoi.fill_from_xy_array_(coords_aug)
  1171. cbaoi.shape = output_shape
  1172. augm_value[i] = cbaoi
  1173. return batch
  1174. def _augment_images_by_samples(self, images, samples,
  1175. image_shapes=None,
  1176. return_matrices=False):
  1177. nb_images = len(images)
  1178. input_was_array = ia.is_np_array(images)
  1179. input_dtype = None if not input_was_array else images.dtype
  1180. result = []
  1181. if return_matrices:
  1182. matrices = [None] * nb_images
  1183. for i in sm.xrange(nb_images):
  1184. image = images[i]
  1185. image_shape = (image.shape if image_shapes is None
  1186. else image_shapes[i])
  1187. matrix, output_shape = samples.to_matrix(i, image.shape,
  1188. image_shape,
  1189. self.fit_output)
  1190. cval = samples.cval[i]
  1191. mode = samples.mode[i]
  1192. order = samples.order[i]
  1193. if not _is_identity_matrix(matrix):
  1194. image_warped = _warp_affine_arr(
  1195. image, matrix,
  1196. order=order, mode=mode, cval=cval,
  1197. output_shape=output_shape, backend=self.backend)
  1198. result.append(image_warped)
  1199. else:
  1200. result.append(image)
  1201. if return_matrices:
  1202. matrices[i] = matrix
  1203. # the shapes can change due to fit_output, then it may not be possible
  1204. # to return an array, even when the input was an array
  1205. if input_was_array:
  1206. nb_shapes = len({image.shape for image in result})
  1207. if nb_shapes == 1:
  1208. result = np.array(result, input_dtype)
  1209. if return_matrices:
  1210. result = (result, matrices)
  1211. return result
  1212. # Added in 0.4.0.
  1213. def _augment_maps_by_samples(self, augmentables, samples,
  1214. arr_attr_name, cval, mode, order, cval_dtype):
  1215. nb_images = len(augmentables)
  1216. samples = samples.copy()
  1217. if cval is not None:
  1218. samples.cval = np.full((nb_images, 1), cval, dtype=cval_dtype)
  1219. if mode is not None:
  1220. samples.mode = [mode] * nb_images
  1221. if order is not None:
  1222. samples.order = [order] * nb_images
  1223. arrs = [getattr(augmentable, arr_attr_name)
  1224. for augmentable in augmentables]
  1225. image_shapes = [augmentable.shape for augmentable in augmentables]
  1226. arrs_aug, matrices = self._augment_images_by_samples(
  1227. arrs, samples, image_shapes=image_shapes, return_matrices=True)
  1228. gen = zip(augmentables, arrs_aug, matrices, samples.order)
  1229. for augmentable_i, arr_aug, matrix, order_i in gen:
  1230. # skip augmented HM/SM arrs for which the images were not
  1231. # augmented due to being zero-sized
  1232. if 0 in augmentable_i.shape:
  1233. continue
  1234. # order=3 matches cubic interpolation and can cause values to go
  1235. # outside of the range [0.0, 1.0] not clear whether 4+ also do that
  1236. # We don't clip here for Segmentation Maps, because for these
  1237. # the value range isn't clearly limited to [0, 1] (and they should
  1238. # also never use order=3 to begin with).
  1239. # TODO add test for this
  1240. if order_i >= 3 and isinstance(augmentable_i, ia.HeatmapsOnImage):
  1241. arr_aug = np.clip(arr_aug, 0.0, 1.0, out=arr_aug)
  1242. setattr(augmentable_i, arr_attr_name, arr_aug)
  1243. if self.fit_output:
  1244. _, output_shape_i = _compute_affine_warp_output_shape(
  1245. matrix, augmentable_i.shape)
  1246. else:
  1247. output_shape_i = augmentable_i.shape
  1248. augmentable_i.shape = output_shape_i
  1249. return augmentables
  1250. def _draw_samples(self, nb_samples, random_state):
  1251. rngs = random_state.duplicate(12)
  1252. if isinstance(self.scale, tuple):
  1253. scale_samples = (
  1254. self.scale[0].draw_samples((nb_samples,), random_state=rngs[0]),
  1255. self.scale[1].draw_samples((nb_samples,), random_state=rngs[1]),
  1256. )
  1257. else:
  1258. scale_samples = self.scale.draw_samples((nb_samples,),
  1259. random_state=rngs[2])
  1260. scale_samples = (scale_samples, scale_samples)
  1261. if self.translate[1] is not None:
  1262. translate_samples = (
  1263. self.translate[0].draw_samples((nb_samples,),
  1264. random_state=rngs[3]),
  1265. self.translate[1].draw_samples((nb_samples,),
  1266. random_state=rngs[4]),
  1267. )
  1268. else:
  1269. translate_samples = self.translate[0].draw_samples(
  1270. (nb_samples,), random_state=rngs[5])
  1271. translate_samples = (translate_samples, translate_samples)
  1272. rotate_samples = self.rotate.draw_samples((nb_samples,),
  1273. random_state=rngs[6])
  1274. if self._shear_param_type == "dict":
  1275. shear_samples = (
  1276. self.shear[0].draw_samples((nb_samples,), random_state=rngs[7]),
  1277. self.shear[1].draw_samples((nb_samples,), random_state=rngs[8])
  1278. )
  1279. elif self._shear_param_type == "single-number":
  1280. # only shear on the x-axis if a single number was given
  1281. shear_samples = self.shear.draw_samples((nb_samples,),
  1282. random_state=rngs[7])
  1283. shear_samples = (shear_samples, np.zeros_like(shear_samples))
  1284. else:
  1285. shear_samples = self.shear.draw_samples((nb_samples,),
  1286. random_state=rngs[7])
  1287. shear_samples = (shear_samples, shear_samples)
  1288. cval_samples = self.cval.draw_samples((nb_samples, 3),
  1289. random_state=rngs[9])
  1290. mode_samples = self.mode.draw_samples((nb_samples,),
  1291. random_state=rngs[10])
  1292. order_samples = self.order.draw_samples((nb_samples,),
  1293. random_state=rngs[11])
  1294. return _AffineSamplingResult(
  1295. scale=scale_samples,
  1296. translate=translate_samples,
  1297. translate_mode=self.translate[2],
  1298. rotate=rotate_samples,
  1299. shear=shear_samples,
  1300. cval=cval_samples,
  1301. mode=mode_samples,
  1302. order=order_samples)
  1303. def get_parameters(self):
  1304. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  1305. return [
  1306. self.scale, self.translate, self.rotate, self.shear, self.order,
  1307. self.cval, self.mode, self.backend, self.fit_output]
  1308. class ScaleX(Affine):
  1309. """Apply affine scaling on the x-axis to input data.
  1310. This is a wrapper around :class:`Affine`.
  1311. Added in 0.4.0.
  1312. **Supported dtypes**:
  1313. See :class:`~imgaug.augmenters.geometric.Affine`.
  1314. Parameters
  1315. ----------
  1316. scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1317. Analogous to ``scale`` in :class:`Affine`, except that this scale
  1318. value only affects the x-axis. No dictionary input is allowed.
  1319. order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1320. See :class:`Affine`.
  1321. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1322. See :class:`Affine`.
  1323. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1324. See :class:`Affine`.
  1325. fit_output : bool, optional
  1326. See :class:`Affine`.
  1327. backend : str, optional
  1328. See :class:`Affine`.
  1329. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1330. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1331. name : None or str, optional
  1332. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1333. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1334. Old name for parameter `seed`.
  1335. Its usage will not yet cause a deprecation warning,
  1336. but it is still recommended to use `seed` now.
  1337. Outdated since 0.4.0.
  1338. deterministic : bool, optional
  1339. Deprecated since 0.4.0.
  1340. See method ``to_deterministic()`` for an alternative and for
  1341. details about what the "deterministic mode" actually does.
  1342. Examples
  1343. --------
  1344. >>> import imgaug.augmenters as iaa
  1345. >>> aug = iaa.ScaleX((0.5, 1.5))
  1346. Create an augmenter that scales images along the width to sizes between
  1347. ``50%`` and ``150%``. This does not change the image shape (i.e. height
  1348. and width), only the pixels within the image are remapped and potentially
  1349. new ones are filled in.
  1350. """
  1351. # Added in 0.4.0.
  1352. def __init__(self, scale=(0.5, 1.5), order=1, cval=0, mode="constant",
  1353. fit_output=False, backend="auto",
  1354. seed=None, name=None,
  1355. random_state="deprecated", deterministic="deprecated"):
  1356. super(ScaleX, self).__init__(
  1357. scale={"x": scale},
  1358. order=order,
  1359. cval=cval,
  1360. mode=mode,
  1361. fit_output=fit_output,
  1362. backend=backend,
  1363. seed=seed, name=name,
  1364. random_state=random_state, deterministic=deterministic)
  1365. class ScaleY(Affine):
  1366. """Apply affine scaling on the y-axis to input data.
  1367. This is a wrapper around :class:`Affine`.
  1368. Added in 0.4.0.
  1369. **Supported dtypes**:
  1370. See :class:`~imgaug.augmenters.geometric.Affine`.
  1371. Parameters
  1372. ----------
  1373. scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1374. Analogous to ``scale`` in :class:`Affine`, except that this scale
  1375. value only affects the y-axis. No dictionary input is allowed.
  1376. order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1377. See :class:`Affine`.
  1378. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1379. See :class:`Affine`.
  1380. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1381. See :class:`Affine`.
  1382. fit_output : bool, optional
  1383. See :class:`Affine`.
  1384. backend : str, optional
  1385. See :class:`Affine`.
  1386. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1387. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1388. name : None or str, optional
  1389. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1390. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1391. Old name for parameter `seed`.
  1392. Its usage will not yet cause a deprecation warning,
  1393. but it is still recommended to use `seed` now.
  1394. Outdated since 0.4.0.
  1395. deterministic : bool, optional
  1396. Deprecated since 0.4.0.
  1397. See method ``to_deterministic()`` for an alternative and for
  1398. details about what the "deterministic mode" actually does.
  1399. Examples
  1400. --------
  1401. >>> import imgaug.augmenters as iaa
  1402. >>> aug = iaa.ScaleY((0.5, 1.5))
  1403. Create an augmenter that scales images along the height to sizes between
  1404. ``50%`` and ``150%``. This does not change the image shape (i.e. height
  1405. and width), only the pixels within the image are remapped and potentially
  1406. new ones are filled in.
  1407. """
  1408. # Added in 0.4.0.
  1409. def __init__(self, scale=(0.5, 1.5), order=1, cval=0, mode="constant",
  1410. fit_output=False, backend="auto",
  1411. seed=None, name=None,
  1412. random_state="deprecated", deterministic="deprecated"):
  1413. super(ScaleY, self).__init__(
  1414. scale={"y": scale},
  1415. order=order,
  1416. cval=cval,
  1417. mode=mode,
  1418. fit_output=fit_output,
  1419. backend=backend,
  1420. seed=seed, name=name,
  1421. random_state=random_state, deterministic=deterministic)
  1422. # TODO make Affine more efficient for translation-only transformations
  1423. class TranslateX(Affine):
  1424. """Apply affine translation on the x-axis to input data.
  1425. This is a wrapper around :class:`Affine`.
  1426. Added in 0.4.0.
  1427. **Supported dtypes**:
  1428. See :class:`~imgaug.augmenters.geometric.Affine`.
  1429. Parameters
  1430. ----------
  1431. percent : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1432. Analogous to ``translate_percent`` in :class:`Affine`, except that
  1433. this translation value only affects the x-axis. No dictionary input
  1434. is allowed.
  1435. px : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
  1436. Analogous to ``translate_px`` in :class:`Affine`, except that
  1437. this translation value only affects the x-axis. No dictionary input
  1438. is allowed.
  1439. order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1440. See :class:`Affine`.
  1441. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1442. See :class:`Affine`.
  1443. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1444. See :class:`Affine`.
  1445. fit_output : bool, optional
  1446. See :class:`Affine`.
  1447. backend : str, optional
  1448. See :class:`Affine`.
  1449. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1450. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1451. name : None or str, optional
  1452. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1453. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1454. Old name for parameter `seed`.
  1455. Its usage will not yet cause a deprecation warning,
  1456. but it is still recommended to use `seed` now.
  1457. Outdated since 0.4.0.
  1458. deterministic : bool, optional
  1459. Deprecated since 0.4.0.
  1460. See method ``to_deterministic()`` for an alternative and for
  1461. details about what the "deterministic mode" actually does.
  1462. Examples
  1463. --------
  1464. >>> import imgaug.augmenters as iaa
  1465. >>> aug = iaa.TranslateX(px=(-20, 20))
  1466. Create an augmenter that translates images along the x-axis by
  1467. ``-20`` to ``20`` pixels.
  1468. >>> aug = iaa.TranslateX(percent=(-0.1, 0.1))
  1469. Create an augmenter that translates images along the x-axis by
  1470. ``-10%`` to ``10%`` (relative to the x-axis size).
  1471. """
  1472. # Added in 0.4.0.
  1473. def __init__(self, percent=None, px=None, order=1,
  1474. cval=0, mode="constant", fit_output=False, backend="auto",
  1475. seed=None, name=None,
  1476. random_state="deprecated", deterministic="deprecated"):
  1477. if percent is None and px is None:
  1478. percent = (-0.25, 0.25)
  1479. super(TranslateX, self).__init__(
  1480. translate_percent=({"x": percent} if percent is not None else None),
  1481. translate_px=({"x": px} if px is not None else None),
  1482. order=order,
  1483. cval=cval,
  1484. mode=mode,
  1485. fit_output=fit_output,
  1486. backend=backend,
  1487. seed=seed, name=name,
  1488. random_state=random_state, deterministic=deterministic)
  1489. # TODO make Affine more efficient for translation-only transformations
  1490. class TranslateY(Affine):
  1491. """Apply affine translation on the y-axis to input data.
  1492. This is a wrapper around :class:`Affine`.
  1493. Added in 0.4.0.
  1494. **Supported dtypes**:
  1495. See :class:`~imgaug.augmenters.geometric.Affine`.
  1496. Parameters
  1497. ----------
  1498. percent : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1499. Analogous to ``translate_percent`` in :class:`Affine`, except that
  1500. this translation value only affects the y-axis. No dictionary input
  1501. is allowed.
  1502. px : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
  1503. Analogous to ``translate_px`` in :class:`Affine`, except that
  1504. this translation value only affects the y-axis. No dictionary input
  1505. is allowed.
  1506. order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1507. See :class:`Affine`.
  1508. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1509. See :class:`Affine`.
  1510. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1511. See :class:`Affine`.
  1512. fit_output : bool, optional
  1513. See :class:`Affine`.
  1514. backend : str, optional
  1515. See :class:`Affine`.
  1516. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1517. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1518. name : None or str, optional
  1519. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1520. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1521. Old name for parameter `seed`.
  1522. Its usage will not yet cause a deprecation warning,
  1523. but it is still recommended to use `seed` now.
  1524. Outdated since 0.4.0.
  1525. deterministic : bool, optional
  1526. Deprecated since 0.4.0.
  1527. See method ``to_deterministic()`` for an alternative and for
  1528. details about what the "deterministic mode" actually does.
  1529. Examples
  1530. --------
  1531. >>> import imgaug.augmenters as iaa
  1532. >>> aug = iaa.TranslateY(px=(-20, 20))
  1533. Create an augmenter that translates images along the y-axis by
  1534. ``-20`` to ``20`` pixels.
  1535. >>> aug = iaa.TranslateY(percent=(-0.1, 0.1))
  1536. Create an augmenter that translates images along the y-axis by
  1537. ``-10%`` to ``10%`` (relative to the y-axis size).
  1538. """
  1539. # Added in 0.4.0.
  1540. def __init__(self, percent=None, px=None, order=1,
  1541. cval=0, mode="constant", fit_output=False, backend="auto",
  1542. seed=None, name=None,
  1543. random_state="deprecated", deterministic="deprecated"):
  1544. if percent is None and px is None:
  1545. percent = (-0.25, 0.25)
  1546. super(TranslateY, self).__init__(
  1547. translate_percent=({"y": percent} if percent is not None else None),
  1548. translate_px=({"y": px} if px is not None else None),
  1549. order=order,
  1550. cval=cval,
  1551. mode=mode,
  1552. fit_output=fit_output,
  1553. backend=backend,
  1554. seed=seed, name=name,
  1555. random_state=random_state, deterministic=deterministic)
  1556. class Rotate(Affine):
  1557. """Apply affine rotation on the y-axis to input data.
  1558. This is a wrapper around :class:`Affine`.
  1559. It is the same as ``Affine(rotate=<value>)``.
  1560. Added in 0.4.0.
  1561. **Supported dtypes**:
  1562. See :class:`~imgaug.augmenters.geometric.Affine`.
  1563. Parameters
  1564. ----------
  1565. rotate : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1566. See :class:`Affine`.
  1567. order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1568. See :class:`Affine`.
  1569. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1570. See :class:`Affine`.
  1571. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1572. See :class:`Affine`.
  1573. fit_output : bool, optional
  1574. See :class:`Affine`.
  1575. backend : str, optional
  1576. See :class:`Affine`.
  1577. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1578. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1579. name : None or str, optional
  1580. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1581. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1582. Old name for parameter `seed`.
  1583. Its usage will not yet cause a deprecation warning,
  1584. but it is still recommended to use `seed` now.
  1585. Outdated since 0.4.0.
  1586. deterministic : bool, optional
  1587. Deprecated since 0.4.0.
  1588. See method ``to_deterministic()`` for an alternative and for
  1589. details about what the "deterministic mode" actually does.
  1590. Examples
  1591. --------
  1592. >>> import imgaug.augmenters as iaa
  1593. >>> aug = iaa.Rotate((-45, 45))
  1594. Create an augmenter that rotates images by a random value between ``-45``
  1595. and ``45`` degress.
  1596. """
  1597. # Added in 0.4.0.
  1598. def __init__(self, rotate=(-30, 30), order=1, cval=0, mode="constant",
  1599. fit_output=False, backend="auto",
  1600. seed=None, name=None,
  1601. random_state="deprecated", deterministic="deprecated"):
  1602. super(Rotate, self).__init__(
  1603. rotate=rotate,
  1604. order=order,
  1605. cval=cval,
  1606. mode=mode,
  1607. fit_output=fit_output,
  1608. backend=backend,
  1609. seed=seed, name=name,
  1610. random_state=random_state, deterministic=deterministic)
  1611. class ShearX(Affine):
  1612. """Apply affine shear on the x-axis to input data.
  1613. This is a wrapper around :class:`Affine`.
  1614. Added in 0.4.0.
  1615. **Supported dtypes**:
  1616. See :class:`~imgaug.augmenters.geometric.Affine`.
  1617. Parameters
  1618. ----------
  1619. shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1620. Analogous to ``shear`` in :class:`Affine`, except that this shear
  1621. value only affects the x-axis. No dictionary input is allowed.
  1622. order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1623. See :class:`Affine`.
  1624. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1625. See :class:`Affine`.
  1626. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1627. See :class:`Affine`.
  1628. fit_output : bool, optional
  1629. See :class:`Affine`.
  1630. backend : str, optional
  1631. See :class:`Affine`.
  1632. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1633. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1634. name : None or str, optional
  1635. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1636. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1637. Old name for parameter `seed`.
  1638. Its usage will not yet cause a deprecation warning,
  1639. but it is still recommended to use `seed` now.
  1640. Outdated since 0.4.0.
  1641. deterministic : bool, optional
  1642. Deprecated since 0.4.0.
  1643. See method ``to_deterministic()`` for an alternative and for
  1644. details about what the "deterministic mode" actually does.
  1645. Examples
  1646. --------
  1647. >>> import imgaug.augmenters as iaa
  1648. >>> aug = iaa.ShearX((-20, 20))
  1649. Create an augmenter that shears images along the x-axis by random amounts
  1650. between ``-20`` and ``20`` degrees.
  1651. """
  1652. # Added in 0.4.0.
  1653. def __init__(self, shear=(-30, 30), order=1, cval=0, mode="constant",
  1654. fit_output=False, backend="auto",
  1655. seed=None, name=None,
  1656. random_state="deprecated", deterministic="deprecated"):
  1657. super(ShearX, self).__init__(
  1658. shear={"x": shear},
  1659. order=order,
  1660. cval=cval,
  1661. mode=mode,
  1662. fit_output=fit_output,
  1663. backend=backend,
  1664. seed=seed, name=name,
  1665. random_state=random_state, deterministic=deterministic)
  1666. class ShearY(Affine):
  1667. """Apply affine shear on the y-axis to input data.
  1668. This is a wrapper around :class:`Affine`.
  1669. Added in 0.4.0.
  1670. **Supported dtypes**:
  1671. See :class:`~imgaug.augmenters.geometric.Affine`.
  1672. Parameters
  1673. ----------
  1674. shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1675. Analogous to ``shear`` in :class:`Affine`, except that this shear
  1676. value only affects the y-axis. No dictionary input is allowed.
  1677. order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1678. See :class:`Affine`.
  1679. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1680. See :class:`Affine`.
  1681. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  1682. See :class:`Affine`.
  1683. fit_output : bool, optional
  1684. See :class:`Affine`.
  1685. backend : str, optional
  1686. See :class:`Affine`.
  1687. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1688. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1689. name : None or str, optional
  1690. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1691. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1692. Old name for parameter `seed`.
  1693. Its usage will not yet cause a deprecation warning,
  1694. but it is still recommended to use `seed` now.
  1695. Outdated since 0.4.0.
  1696. deterministic : bool, optional
  1697. Deprecated since 0.4.0.
  1698. See method ``to_deterministic()`` for an alternative and for
  1699. details about what the "deterministic mode" actually does.
  1700. Examples
  1701. --------
  1702. >>> import imgaug.augmenters as iaa
  1703. >>> aug = iaa.ShearY((-20, 20))
  1704. Create an augmenter that shears images along the y-axis by random amounts
  1705. between ``-20`` and ``20`` degrees.
  1706. """
  1707. # Added in 0.4.0.
  1708. def __init__(self, shear=(-30, 30), order=1, cval=0, mode="constant",
  1709. fit_output=False, backend="auto",
  1710. seed=None, name=None,
  1711. random_state="deprecated", deterministic="deprecated"):
  1712. super(ShearY, self).__init__(
  1713. shear={"y": shear},
  1714. order=order,
  1715. cval=cval,
  1716. mode=mode,
  1717. fit_output=fit_output,
  1718. backend=backend,
  1719. seed=seed, name=name,
  1720. random_state=random_state, deterministic=deterministic)
  1721. class AffineCv2(meta.Augmenter):
  1722. """
  1723. **Deprecated.** Augmenter to apply affine transformations to images using
  1724. cv2 (i.e. opencv) backend.
  1725. .. warning::
  1726. This augmenter is deprecated since 0.4.0.
  1727. Use ``Affine(..., backend='cv2')`` instead.
  1728. Affine transformations
  1729. involve:
  1730. - Translation ("move" image on the x-/y-axis)
  1731. - Rotation
  1732. - Scaling ("zoom" in/out)
  1733. - Shear (move one side of the image, turning a square into a trapezoid)
  1734. All such transformations can create "new" pixels in the image without a
  1735. defined content, e.g. if the image is translated to the left, pixels
  1736. are created on the right.
  1737. A method has to be defined to deal with these pixel values. The
  1738. parameters `cval` and `mode` of this class deal with this.
  1739. Some transformations involve interpolations between several pixels
  1740. of the input image to generate output pixel values. The parameter `order`
  1741. deals with the method of interpolation used for this.
  1742. Deprecated since 0.4.0.
  1743. **Supported dtypes**:
  1744. * ``uint8``: yes; fully tested
  1745. * ``uint16``: ?
  1746. * ``uint32``: ?
  1747. * ``uint64``: ?
  1748. * ``int8``: ?
  1749. * ``int16``: ?
  1750. * ``int32``: ?
  1751. * ``int64``: ?
  1752. * ``float16``: ?
  1753. * ``float32``: ?
  1754. * ``float64``: ?
  1755. * ``float128``: ?
  1756. * ``bool``: ?
  1757. Parameters
  1758. ----------
  1759. scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
  1760. Scaling factor to use, where ``1.0`` denotes \"no change\" and
  1761. ``0.5`` is zoomed out to ``50`` percent of the original size.
  1762. * If a single number, then that value will be used for all images.
  1763. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  1764. per image from the interval ``[a, b]``. That value will be
  1765. used identically for both x- and y-axis.
  1766. * If a list, then a random value will be sampled from that list
  1767. per image (again, used for both x- and y-axis).
  1768. * If a ``StochasticParameter``, then from that parameter a value
  1769. will be sampled per image (again, used for both x- and y-axis).
  1770. * If a dictionary, then it is expected to have the keys ``x``
  1771. and/or ``y``. Each of these keys can have the same values as
  1772. described above. Using a dictionary allows to set different
  1773. values for the two axis and sampling will then happen
  1774. *independently* per axis, resulting in samples that differ
  1775. between the axes.
  1776. translate_percent : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
  1777. Translation as a fraction of the image height/width (x-translation,
  1778. y-translation), where ``0`` denotes "no change" and ``0.5`` denotes
  1779. "half of the axis size".
  1780. * If ``None`` then equivalent to ``0.0`` unless `translate_px` has
  1781. a value other than ``None``.
  1782. * If a single number, then that value will be used for all images.
  1783. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  1784. per image from the interval ``[a, b]``. That sampled fraction
  1785. value will be used identically for both x- and y-axis.
  1786. * If a list, then a random value will be sampled from that list
  1787. per image (again, used for both x- and y-axis).
  1788. * If a ``StochasticParameter``, then from that parameter a value
  1789. will be sampled per image (again, used for both x- and y-axis).
  1790. * If a dictionary, then it is expected to have the keys ``x``
  1791. and/or ``y``. Each of these keys can have the same values as
  1792. described above. Using a dictionary allows to set different
  1793. values for the two axis and sampling will then happen
  1794. *independently* per axis, resulting in samples that differ
  1795. between the axes.
  1796. translate_px : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
  1797. Translation in pixels.
  1798. * If ``None`` then equivalent to ``0`` unless `translate_percent`
  1799. has a value other than ``None``.
  1800. * If a single int, then that value will be used for all images.
  1801. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  1802. per image from the discrete interval ``[a..b]``. That number
  1803. will be used identically for both x- and y-axis.
  1804. * If a list, then a random value will be sampled from that list
  1805. per image (again, used for both x- and y-axis).
  1806. * If a ``StochasticParameter``, then from that parameter a value
  1807. will be sampled per image (again, used for both x- and y-axis).
  1808. * If a dictionary, then it is expected to have the keys ``x``
  1809. and/or ``y``. Each of these keys can have the same values as
  1810. described above. Using a dictionary allows to set different
  1811. values for the two axis and sampling will then happen
  1812. *independently* per axis, resulting in samples that differ
  1813. between the axes.
  1814. rotate : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1815. Rotation in degrees (**NOT** radians), i.e. expected value range is
  1816. around ``[-360, 360]``. Rotation happens around the *center* of the
  1817. image, not the top left corner as in some other frameworks.
  1818. * If a number, then that value will be used for all images.
  1819. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  1820. per image from the interval ``[a, b]`` and used as the rotation
  1821. value.
  1822. * If a list, then a random value will be sampled from that list
  1823. per image.
  1824. * If a ``StochasticParameter``, then this parameter will be used to
  1825. sample the rotation value per image.
  1826. shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  1827. Shear in degrees (**NOT** radians), i.e. expected value range is
  1828. around ``[-360, 360]``.
  1829. * If a number, then that value will be used for all images.
  1830. * If a tuple ``(a, b)``, then a value will be uniformly sampled
  1831. per image from the interval ``[a, b]`` and be used as the
  1832. rotation value.
  1833. * If a list, then a random value will be sampled from that list
  1834. per image.
  1835. * If a ``StochasticParameter``, then this parameter will be used
  1836. to sample the shear value per image.
  1837. order : int or list of int or str or list of str or imaug.ALL or imgaug.parameters.StochasticParameter, optional
  1838. Interpolation order to use. Allowed are:
  1839. * ``cv2.INTER_NEAREST`` (nearest-neighbor interpolation)
  1840. * ``cv2.INTER_LINEAR`` (bilinear interpolation, used by default)
  1841. * ``cv2.INTER_CUBIC`` (bicubic interpolation over ``4x4`` pixel
  1842. neighborhood)
  1843. * ``cv2.INTER_LANCZOS4``
  1844. * string ``nearest`` (same as ``cv2.INTER_NEAREST``)
  1845. * string ``linear`` (same as ``cv2.INTER_LINEAR``)
  1846. * string ``cubic`` (same as ``cv2.INTER_CUBIC``)
  1847. * string ``lanczos4`` (same as ``cv2.INTER_LANCZOS``)
  1848. ``INTER_NEAREST`` (nearest neighbour interpolation) and
  1849. ``INTER_NEAREST`` (linear interpolation) are the fastest.
  1850. * If a single ``int``, then that order will be used for all images.
  1851. * If a string, then it must be one of: ``nearest``, ``linear``,
  1852. ``cubic``, ``lanczos4``.
  1853. * If an iterable of ``int``/``str``, then for each image a random
  1854. value will be sampled from that iterable (i.e. list of allowed
  1855. order values).
  1856. * If ``imgaug.ALL``, then equivalant to list ``[cv2.INTER_NEAREST,
  1857. cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_LANCZOS4]``.
  1858. * If ``StochasticParameter``, then that parameter is queried per
  1859. image to sample the order value to use.
  1860. cval : number or tuple of number or list of number or imaug.ALL or imgaug.parameters.StochasticParameter, optional
  1861. The constant value to use when filling in newly created pixels.
  1862. (E.g. translating by 1px to the right will create a new 1px-wide
  1863. column of pixels on the left of the image). The value is only used
  1864. when `mode=constant`. The expected value range is ``[0, 255]`` for
  1865. ``uint8`` images. It may be a float value.
  1866. * If this is a single number, then that value will be used
  1867. (e.g. 0 results in black pixels).
  1868. * If a tuple ``(a, b)``, then three values (for three image
  1869. channels) will be uniformly sampled per image from the
  1870. interval ``[a, b]``.
  1871. * If a list, then a random value will be sampled from that list
  1872. per image.
  1873. * If ``imgaug.ALL`` then equivalent to tuple ``(0, 255)`.
  1874. * If a ``StochasticParameter``, a new value will be sampled from
  1875. the parameter per image.
  1876. mode : int or str or list of str or list of int or imgaug.ALL or imgaug.parameters.StochasticParameter,
  1877. optional
  1878. Method to use when filling in newly created pixels.
  1879. Same meaning as in OpenCV's border mode. Let ``abcdefgh`` be an image's
  1880. content and ``|`` be an image boundary after which new pixels are
  1881. filled in, then the valid modes and their behaviour are the following:
  1882. * ``cv2.BORDER_REPLICATE``: ``aaaaaa|abcdefgh|hhhhhhh``
  1883. * ``cv2.BORDER_REFLECT``: ``fedcba|abcdefgh|hgfedcb``
  1884. * ``cv2.BORDER_REFLECT_101``: ``gfedcb|abcdefgh|gfedcba``
  1885. * ``cv2.BORDER_WRAP``: ``cdefgh|abcdefgh|abcdefg``
  1886. * ``cv2.BORDER_CONSTANT``: ``iiiiii|abcdefgh|iiiiiii``,
  1887. where ``i`` is the defined cval.
  1888. * ``replicate``: Same as ``cv2.BORDER_REPLICATE``.
  1889. * ``reflect``: Same as ``cv2.BORDER_REFLECT``.
  1890. * ``reflect_101``: Same as ``cv2.BORDER_REFLECT_101``.
  1891. * ``wrap``: Same as ``cv2.BORDER_WRAP``.
  1892. * ``constant``: Same as ``cv2.BORDER_CONSTANT``.
  1893. The datatype of the parameter may be:
  1894. * If a single ``int``, then it must be one of the ``cv2.BORDER_*``
  1895. constants.
  1896. * If a single string, then it must be one of: ``replicate``,
  1897. ``reflect``, ``reflect_101``, ``wrap``, ``constant``.
  1898. * If a list of ``int``/``str``, then per image a random mode will
  1899. be picked from that list.
  1900. * If ``imgaug.ALL``, then a random mode from all possible modes
  1901. will be picked.
  1902. * If ``StochasticParameter``, then the mode will be sampled from
  1903. that parameter per image, i.e. it must return only the above
  1904. mentioned strings.
  1905. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1906. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1907. name : None or str, optional
  1908. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  1909. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  1910. Old name for parameter `seed`.
  1911. Its usage will not yet cause a deprecation warning,
  1912. but it is still recommended to use `seed` now.
  1913. Outdated since 0.4.0.
  1914. deterministic : bool, optional
  1915. Deprecated since 0.4.0.
  1916. See method ``to_deterministic()`` for an alternative and for
  1917. details about what the "deterministic mode" actually does.
  1918. Examples
  1919. --------
  1920. >>> import imgaug.augmenters as iaa
  1921. >>> aug = iaa.AffineCv2(scale=2.0)
  1922. Zoom in on all images by a factor of ``2``.
  1923. >>> aug = iaa.AffineCv2(translate_px=16)
  1924. Translate all images on the x- and y-axis by 16 pixels (towards the
  1925. bottom right) and fill up any new pixels with zero (black values).
  1926. >>> aug = iaa.AffineCv2(translate_percent=0.1)
  1927. Translate all images on the x- and y-axis by ``10`` percent of their
  1928. width/height (towards the bottom right). The pixel values are computed
  1929. per axis based on that axis' size. Fill up any new pixels with zero
  1930. (black values).
  1931. >>> aug = iaa.AffineCv2(rotate=35)
  1932. Rotate all images by ``35`` *degrees*. Fill up any new pixels with zero
  1933. (black values).
  1934. >>> aug = iaa.AffineCv2(shear=15)
  1935. Shear all images by ``15`` *degrees*. Fill up any new pixels with zero
  1936. (black values).
  1937. >>> aug = iaa.AffineCv2(translate_px=(-16, 16))
  1938. Translate all images on the x- and y-axis by a random value
  1939. between ``-16`` and ``16`` pixels (to the bottom right) and fill up any new
  1940. pixels with zero (black values). The translation value is sampled once
  1941. per image and is the same for both axis.
  1942. >>> aug = iaa.AffineCv2(translate_px={"x": (-16, 16), "y": (-4, 4)})
  1943. Translate all images on the x-axis by a random value
  1944. between ``-16`` and ``16`` pixels (to the right) and on the y-axis by a
  1945. random value between ``-4`` and ``4`` pixels to the bottom. The sampling
  1946. happens independently per axis, so even if both intervals were identical,
  1947. the sampled axis-wise values would likely be different.
  1948. This also fills up any new pixels with zero (black values).
  1949. >>> aug = iaa.AffineCv2(scale=2.0, order=[0, 1])
  1950. Same as in the above `scale` example, but uses (randomly) either
  1951. nearest neighbour interpolation or linear interpolation. If `order` is
  1952. not specified, ``order=1`` would be used by default.
  1953. >>> aug = iaa.AffineCv2(translate_px=16, cval=(0, 255))
  1954. Same as in the `translate_px` example above, but newly created pixels
  1955. are now filled with a random color (sampled once per image and the
  1956. same for all newly created pixels within that image).
  1957. >>> aug = iaa.AffineCv2(translate_px=16, mode=["constant", "replicate"])
  1958. Similar to the previous example, but the newly created pixels are
  1959. filled with black pixels in half of all images (mode ``constant`` with
  1960. default `cval` being ``0``) and in the other half of all images using
  1961. ``replicate`` mode, which repeats the color of the spatially closest pixel
  1962. of the corresponding image edge.
  1963. """
  1964. def __init__(self, scale=1.0, translate_percent=None, translate_px=None,
  1965. rotate=0.0, shear=0.0, order=cv2.INTER_LINEAR, cval=0,
  1966. mode=cv2.BORDER_CONSTANT,
  1967. seed=None, name=None,
  1968. random_state="deprecated", deterministic="deprecated"):
  1969. super(AffineCv2, self).__init__(
  1970. seed=seed, name=name,
  1971. random_state=random_state, deterministic=deterministic)
  1972. # using a context on __init__ seems to produce no warning,
  1973. # so warn manually here
  1974. ia.warn_deprecated(
  1975. "AffineCv2 is deprecated. "
  1976. "Use imgaug.augmenters.geometric.Affine(..., backend='cv2') "
  1977. "instead.", stacklevel=4)
  1978. available_orders = [cv2.INTER_NEAREST, cv2.INTER_LINEAR,
  1979. cv2.INTER_CUBIC, cv2.INTER_LANCZOS4]
  1980. available_orders_str = ["nearest", "linear", "cubic", "lanczos4"]
  1981. if order == ia.ALL:
  1982. self.order = iap.Choice(available_orders)
  1983. elif ia.is_single_integer(order):
  1984. assert order in available_orders, (
  1985. "Expected order's integer value to be in %s, got %d." % (
  1986. str(available_orders), order))
  1987. self.order = iap.Deterministic(order)
  1988. elif ia.is_string(order):
  1989. assert order in available_orders_str, (
  1990. "Expected order to be in %s, got %s." % (
  1991. str(available_orders_str), order))
  1992. self.order = iap.Deterministic(order)
  1993. elif isinstance(order, list):
  1994. valid_types = all(
  1995. [ia.is_single_integer(val) or ia.is_string(val)
  1996. for val in order])
  1997. assert valid_types, (
  1998. "Expected order list to only contain integers/strings, got "
  1999. "types %s." % (str([type(val) for val in order]),))
  2000. valid_orders = all(
  2001. [val in available_orders + available_orders_str
  2002. for val in order])
  2003. assert valid_orders, (
  2004. "Expected all order values to be in %s, got %s." % (
  2005. available_orders + available_orders_str, str(order),))
  2006. self.order = iap.Choice(order)
  2007. elif isinstance(order, iap.StochasticParameter):
  2008. self.order = order
  2009. else:
  2010. raise Exception(
  2011. "Expected order to be imgaug.ALL, int, string, a list of"
  2012. "int/string or StochasticParameter, got %s." % (type(order),))
  2013. if cval == ia.ALL:
  2014. self.cval = iap.DiscreteUniform(0, 255)
  2015. else:
  2016. self.cval = iap.handle_discrete_param(
  2017. cval, "cval", value_range=(0, 255), tuple_to_uniform=True,
  2018. list_to_choice=True, allow_floats=True)
  2019. available_modes = [cv2.BORDER_REPLICATE, cv2.BORDER_REFLECT,
  2020. cv2.BORDER_REFLECT_101, cv2.BORDER_WRAP,
  2021. cv2.BORDER_CONSTANT]
  2022. available_modes_str = ["replicate", "reflect", "reflect_101",
  2023. "wrap", "constant"]
  2024. if mode == ia.ALL:
  2025. self.mode = iap.Choice(available_modes)
  2026. elif ia.is_single_integer(mode):
  2027. assert mode in available_modes, (
  2028. "Expected mode to be in %s, got %d." % (
  2029. str(available_modes), mode))
  2030. self.mode = iap.Deterministic(mode)
  2031. elif ia.is_string(mode):
  2032. assert mode in available_modes_str, (
  2033. "Expected mode to be in %s, got %s." % (
  2034. str(available_modes_str), mode))
  2035. self.mode = iap.Deterministic(mode)
  2036. elif isinstance(mode, list):
  2037. all_valid_types = all([
  2038. ia.is_single_integer(val) or ia.is_string(val) for val in mode])
  2039. assert all_valid_types, (
  2040. "Expected mode list to only contain integers/strings, "
  2041. "got types %s." % (str([type(val) for val in mode]),))
  2042. all_valid_modes = all([
  2043. val in available_modes + available_modes_str for val in mode])
  2044. assert all_valid_modes, (
  2045. "Expected all mode values to be in %s, got %s." % (
  2046. str(available_modes + available_modes_str), str(mode)))
  2047. self.mode = iap.Choice(mode)
  2048. elif isinstance(mode, iap.StochasticParameter):
  2049. self.mode = mode
  2050. else:
  2051. raise Exception(
  2052. "Expected mode to be imgaug.ALL, an int, a string, a list of "
  2053. "int/strings or StochasticParameter, got %s." % (type(mode),))
  2054. # scale
  2055. if isinstance(scale, dict):
  2056. assert "x" in scale or "y" in scale, (
  2057. "Expected scale dictionary to contain at "
  2058. "least key \"x\" or key \"y\". Found neither of them.")
  2059. x = scale.get("x", 1.0)
  2060. y = scale.get("y", 1.0)
  2061. self.scale = (
  2062. iap.handle_continuous_param(
  2063. x, "scale['x']", value_range=(0+1e-4, None),
  2064. tuple_to_uniform=True, list_to_choice=True),
  2065. iap.handle_continuous_param(
  2066. y, "scale['y']", value_range=(0+1e-4, None),
  2067. tuple_to_uniform=True, list_to_choice=True)
  2068. )
  2069. else:
  2070. self.scale = iap.handle_continuous_param(
  2071. scale, "scale", value_range=(0+1e-4, None),
  2072. tuple_to_uniform=True, list_to_choice=True)
  2073. # translate
  2074. if translate_percent is None and translate_px is None:
  2075. translate_px = 0
  2076. assert translate_percent is None or translate_px is None, (
  2077. "Expected either translate_percent or translate_px to be "
  2078. "provided, but neither of them was.")
  2079. if translate_percent is not None:
  2080. # translate by percent
  2081. if isinstance(translate_percent, dict):
  2082. assert "x" in translate_percent or "y" in translate_percent, (
  2083. "Expected translate_percent dictionary to contain at "
  2084. "least key \"x\" or key \"y\". Found neither of them.")
  2085. x = translate_percent.get("x", 0)
  2086. y = translate_percent.get("y", 0)
  2087. self.translate = (
  2088. iap.handle_continuous_param(
  2089. x, "translate_percent['x']", value_range=None,
  2090. tuple_to_uniform=True, list_to_choice=True),
  2091. iap.handle_continuous_param(
  2092. y, "translate_percent['y']", value_range=None,
  2093. tuple_to_uniform=True, list_to_choice=True)
  2094. )
  2095. else:
  2096. self.translate = iap.handle_continuous_param(
  2097. translate_percent, "translate_percent", value_range=None,
  2098. tuple_to_uniform=True, list_to_choice=True)
  2099. else:
  2100. # translate by pixels
  2101. if isinstance(translate_px, dict):
  2102. assert "x" in translate_px or "y" in translate_px, (
  2103. "Expected translate_px dictionary to contain at "
  2104. "least key \"x\" or key \"y\". Found neither of them.")
  2105. x = translate_px.get("x", 0)
  2106. y = translate_px.get("y", 0)
  2107. self.translate = (
  2108. iap.handle_discrete_param(
  2109. x, "translate_px['x']", value_range=None,
  2110. tuple_to_uniform=True, list_to_choice=True,
  2111. allow_floats=False),
  2112. iap.handle_discrete_param(
  2113. y, "translate_px['y']", value_range=None,
  2114. tuple_to_uniform=True, list_to_choice=True,
  2115. allow_floats=False)
  2116. )
  2117. else:
  2118. self.translate = iap.handle_discrete_param(
  2119. translate_px, "translate_px", value_range=None,
  2120. tuple_to_uniform=True, list_to_choice=True,
  2121. allow_floats=False)
  2122. self.rotate = iap.handle_continuous_param(
  2123. rotate, "rotate", value_range=None, tuple_to_uniform=True,
  2124. list_to_choice=True)
  2125. self.shear = iap.handle_continuous_param(
  2126. shear, "shear", value_range=None, tuple_to_uniform=True,
  2127. list_to_choice=True)
  2128. def _augment_images(self, images, random_state, parents, hooks):
  2129. nb_images = len(images)
  2130. scale_samples, translate_samples, rotate_samples, shear_samples, \
  2131. cval_samples, mode_samples, order_samples = self._draw_samples(
  2132. nb_images, random_state)
  2133. result = self._augment_images_by_samples(
  2134. images, scale_samples, translate_samples, rotate_samples,
  2135. shear_samples, cval_samples, mode_samples, order_samples)
  2136. return result
  2137. @classmethod
  2138. def _augment_images_by_samples(cls, images, scale_samples,
  2139. translate_samples, rotate_samples,
  2140. shear_samples, cval_samples, mode_samples,
  2141. order_samples):
  2142. # TODO change these to class attributes
  2143. order_str_to_int = {
  2144. "nearest": cv2.INTER_NEAREST,
  2145. "linear": cv2.INTER_LINEAR,
  2146. "cubic": cv2.INTER_CUBIC,
  2147. "lanczos4": cv2.INTER_LANCZOS4
  2148. }
  2149. mode_str_to_int = {
  2150. "replicate": cv2.BORDER_REPLICATE,
  2151. "reflect": cv2.BORDER_REFLECT,
  2152. "reflect_101": cv2.BORDER_REFLECT_101,
  2153. "wrap": cv2.BORDER_WRAP,
  2154. "constant": cv2.BORDER_CONSTANT
  2155. }
  2156. nb_images = len(images)
  2157. result = images
  2158. for i in sm.xrange(nb_images):
  2159. height, width = images[i].shape[0], images[i].shape[1]
  2160. shift_x = width / 2.0 - 0.5
  2161. shift_y = height / 2.0 - 0.5
  2162. scale_x, scale_y = scale_samples[0][i], scale_samples[1][i]
  2163. translate_x = translate_samples[0][i]
  2164. translate_y = translate_samples[1][i]
  2165. if ia.is_single_float(translate_y):
  2166. translate_y_px = int(
  2167. np.round(translate_y * images[i].shape[0]))
  2168. else:
  2169. translate_y_px = translate_y
  2170. if ia.is_single_float(translate_x):
  2171. translate_x_px = int(
  2172. np.round(translate_x * images[i].shape[1]))
  2173. else:
  2174. translate_x_px = translate_x
  2175. rotate = rotate_samples[i]
  2176. shear = shear_samples[i]
  2177. cval = cval_samples[i]
  2178. mode = mode_samples[i]
  2179. order = order_samples[i]
  2180. mode = (mode
  2181. if ia.is_single_integer(mode)
  2182. else mode_str_to_int[mode])
  2183. order = (order
  2184. if ia.is_single_integer(order)
  2185. else order_str_to_int[order])
  2186. any_change = (
  2187. scale_x != 1.0 or scale_y != 1.0
  2188. or translate_x_px != 0 or translate_y_px != 0
  2189. or rotate != 0 or shear != 0
  2190. )
  2191. if any_change:
  2192. matrix_to_topleft = tf.SimilarityTransform(
  2193. translation=[-shift_x, -shift_y])
  2194. matrix_transforms = tf.AffineTransform(
  2195. scale=(scale_x, scale_y),
  2196. translation=(translate_x_px, translate_y_px),
  2197. rotation=math.radians(rotate),
  2198. shear=math.radians(shear)
  2199. )
  2200. matrix_to_center = tf.SimilarityTransform(
  2201. translation=[shift_x, shift_y])
  2202. matrix = (matrix_to_topleft
  2203. + matrix_transforms
  2204. + matrix_to_center)
  2205. image_warped = cv2.warpAffine(
  2206. _normalize_cv2_input_arr_(images[i]),
  2207. matrix.params[:2],
  2208. dsize=(width, height),
  2209. flags=order,
  2210. borderMode=mode,
  2211. borderValue=tuple([int(v) for v in cval])
  2212. )
  2213. # cv2 warp drops last axis if shape is (H, W, 1)
  2214. if image_warped.ndim == 2:
  2215. image_warped = image_warped[..., np.newaxis]
  2216. # warp changes uint8 to float64, making this necessary
  2217. result[i] = image_warped
  2218. else:
  2219. result[i] = images[i]
  2220. return result
  2221. def _augment_heatmaps(self, heatmaps, random_state, parents, hooks):
  2222. nb_images = len(heatmaps)
  2223. scale_samples, translate_samples, rotate_samples, shear_samples, \
  2224. cval_samples, mode_samples, order_samples = self._draw_samples(
  2225. nb_images, random_state)
  2226. cval_samples = np.zeros((cval_samples.shape[0], 1), dtype=np.float32)
  2227. mode_samples = ["constant"] * len(mode_samples)
  2228. arrs = [heatmap_i.arr_0to1 for heatmap_i in heatmaps]
  2229. arrs_aug = self._augment_images_by_samples(
  2230. arrs, scale_samples, translate_samples, rotate_samples,
  2231. shear_samples, cval_samples, mode_samples, order_samples)
  2232. for heatmap_i, arr_aug in zip(heatmaps, arrs_aug):
  2233. heatmap_i.arr_0to1 = arr_aug
  2234. return heatmaps
  2235. def _augment_segmentation_maps(self, segmaps, random_state, parents, hooks):
  2236. nb_images = len(segmaps)
  2237. scale_samples, translate_samples, rotate_samples, shear_samples, \
  2238. cval_samples, mode_samples, order_samples = self._draw_samples(
  2239. nb_images, random_state)
  2240. cval_samples = np.zeros((cval_samples.shape[0], 1), dtype=np.float32)
  2241. mode_samples = ["constant"] * len(mode_samples)
  2242. order_samples = [0] * len(order_samples)
  2243. arrs = [segmaps_i.arr for segmaps_i in segmaps]
  2244. arrs_aug = self._augment_images_by_samples(
  2245. arrs, scale_samples, translate_samples, rotate_samples,
  2246. shear_samples, cval_samples, mode_samples, order_samples)
  2247. for segmaps_i, arr_aug in zip(segmaps, arrs_aug):
  2248. segmaps_i.arr = arr_aug
  2249. return segmaps
  2250. def _augment_keypoints(self, keypoints_on_images, random_state, parents,
  2251. hooks):
  2252. result = []
  2253. nb_images = len(keypoints_on_images)
  2254. scale_samples, translate_samples, rotate_samples, shear_samples, \
  2255. _cval_samples, _mode_samples, _order_samples = self._draw_samples(
  2256. nb_images, random_state)
  2257. for i, keypoints_on_image in enumerate(keypoints_on_images):
  2258. if not keypoints_on_image.keypoints:
  2259. # AffineCv2 does not change the image shape, hence we can skip
  2260. # all steps below if there are no keypoints
  2261. result.append(keypoints_on_image)
  2262. continue
  2263. height, width = keypoints_on_image.height, keypoints_on_image.width
  2264. shift_x = width / 2.0 - 0.5
  2265. shift_y = height / 2.0 - 0.5
  2266. scale_x, scale_y = scale_samples[0][i], scale_samples[1][i]
  2267. translate_x = translate_samples[0][i]
  2268. translate_y = translate_samples[1][i]
  2269. if ia.is_single_float(translate_y):
  2270. translate_y_px = int(
  2271. np.round(translate_y * keypoints_on_image.shape[0]))
  2272. else:
  2273. translate_y_px = translate_y
  2274. if ia.is_single_float(translate_x):
  2275. translate_x_px = int(
  2276. np.round(translate_x * keypoints_on_image.shape[1]))
  2277. else:
  2278. translate_x_px = translate_x
  2279. rotate = rotate_samples[i]
  2280. shear = shear_samples[i]
  2281. any_change = (
  2282. scale_x != 1.0 or scale_y != 1.0
  2283. or translate_x_px != 0 or translate_y_px != 0
  2284. or rotate != 0 or shear != 0
  2285. )
  2286. if any_change:
  2287. matrix_to_topleft = tf.SimilarityTransform(
  2288. translation=[-shift_x, -shift_y])
  2289. matrix_transforms = tf.AffineTransform(
  2290. scale=(scale_x, scale_y),
  2291. translation=(translate_x_px, translate_y_px),
  2292. rotation=math.radians(rotate),
  2293. shear=math.radians(shear)
  2294. )
  2295. matrix_to_center = tf.SimilarityTransform(
  2296. translation=[shift_x, shift_y])
  2297. matrix = (matrix_to_topleft
  2298. + matrix_transforms
  2299. + matrix_to_center)
  2300. coords = keypoints_on_image.to_xy_array()
  2301. coords_aug = tf.matrix_transform(coords, matrix.params)
  2302. kps_new = [kp.deepcopy(x=coords[0], y=coords[1])
  2303. for kp, coords
  2304. in zip(keypoints_on_image.keypoints, coords_aug)]
  2305. result.append(keypoints_on_image.deepcopy(
  2306. keypoints=kps_new,
  2307. shape=keypoints_on_image.shape
  2308. ))
  2309. else:
  2310. result.append(keypoints_on_image)
  2311. return result
  2312. def _augment_polygons(self, polygons_on_images, random_state, parents,
  2313. hooks):
  2314. return self._augment_polygons_as_keypoints(
  2315. polygons_on_images, random_state, parents, hooks)
  2316. def _augment_line_strings(self, line_strings_on_images, random_state,
  2317. parents, hooks):
  2318. return self._augment_line_strings_as_keypoints(
  2319. line_strings_on_images, random_state, parents, hooks)
  2320. def _augment_bounding_boxes(self, bounding_boxes_on_images, random_state,
  2321. parents, hooks):
  2322. return self._augment_bounding_boxes_as_keypoints(
  2323. bounding_boxes_on_images, random_state, parents, hooks)
  2324. def get_parameters(self):
  2325. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  2326. return [self.scale, self.translate, self.rotate, self.shear,
  2327. self.order, self.cval, self.mode]
  2328. def _draw_samples(self, nb_samples, random_state):
  2329. rngs = random_state.duplicate(11)
  2330. if isinstance(self.scale, tuple):
  2331. scale_samples = (
  2332. self.scale[0].draw_samples((nb_samples,),
  2333. random_state=rngs[0]),
  2334. self.scale[1].draw_samples((nb_samples,),
  2335. random_state=rngs[1]),
  2336. )
  2337. else:
  2338. scale_samples = self.scale.draw_samples((nb_samples,),
  2339. random_state=rngs[2])
  2340. scale_samples = (scale_samples, scale_samples)
  2341. if isinstance(self.translate, tuple):
  2342. translate_samples = (
  2343. self.translate[0].draw_samples((nb_samples,),
  2344. random_state=rngs[3]),
  2345. self.translate[1].draw_samples((nb_samples,),
  2346. random_state=rngs[4]),
  2347. )
  2348. else:
  2349. translate_samples = self.translate.draw_samples(
  2350. (nb_samples,), random_state=rngs[5])
  2351. translate_samples = (translate_samples, translate_samples)
  2352. valid_dts = ["int32", "int64", "float32", "float64"]
  2353. for i in sm.xrange(2):
  2354. assert translate_samples[i].dtype.name in valid_dts, (
  2355. "Expected translate_samples to have any dtype of %s. "
  2356. "Got %s." % (str(valid_dts), translate_samples[i].dtype.name,))
  2357. rotate_samples = self.rotate.draw_samples((nb_samples,),
  2358. random_state=rngs[6])
  2359. shear_samples = self.shear.draw_samples((nb_samples,),
  2360. random_state=rngs[7])
  2361. cval_samples = self.cval.draw_samples((nb_samples, 3),
  2362. random_state=rngs[8])
  2363. mode_samples = self.mode.draw_samples((nb_samples,),
  2364. random_state=rngs[9])
  2365. order_samples = self.order.draw_samples((nb_samples,),
  2366. random_state=rngs[10])
  2367. return (
  2368. scale_samples, translate_samples, rotate_samples, shear_samples,
  2369. cval_samples, mode_samples, order_samples
  2370. )
  2371. class _PiecewiseAffineSamplingResult(object):
  2372. def __init__(self, nb_rows, nb_cols, jitter, order, cval, mode):
  2373. self.nb_rows = nb_rows
  2374. self.nb_cols = nb_cols
  2375. self.order = order
  2376. self.jitter = jitter
  2377. self.cval = cval
  2378. self.mode = mode
  2379. def get_clipped_cval(self, idx, dtype):
  2380. min_value, _, max_value = iadt.get_value_range_of_dtype(dtype)
  2381. cval = self.cval[idx]
  2382. cval = max(min(cval, max_value), min_value)
  2383. return cval
  2384. class PiecewiseAffine(meta.Augmenter):
  2385. """
  2386. Apply affine transformations that differ between local neighbourhoods.
  2387. This augmenter places a regular grid of points on an image and randomly
  2388. moves the neighbourhood of these point around via affine transformations.
  2389. This leads to local distortions.
  2390. This is mostly a wrapper around scikit-image's ``PiecewiseAffine``.
  2391. See also ``Affine`` for a similar technique.
  2392. .. note::
  2393. This augmenter is very slow. See :ref:`performance`.
  2394. Try to use ``ElasticTransformation`` instead, which is at least 10x
  2395. faster.
  2396. .. note::
  2397. For coordinate-based inputs (keypoints, bounding boxes, polygons,
  2398. ...), this augmenter still has to perform an image-based augmentation,
  2399. which will make it significantly slower for such inputs than other
  2400. augmenters. See :ref:`performance`.
  2401. **Supported dtypes**:
  2402. * ``uint8``: yes; fully tested
  2403. * ``uint16``: yes; tested (1)
  2404. * ``uint32``: yes; tested (1) (2)
  2405. * ``uint64``: no (3)
  2406. * ``int8``: yes; tested (1)
  2407. * ``int16``: yes; tested (1)
  2408. * ``int32``: yes; tested (1) (2)
  2409. * ``int64``: no (3)
  2410. * ``float16``: yes; tested (1)
  2411. * ``float32``: yes; tested (1)
  2412. * ``float64``: yes; tested (1)
  2413. * ``float128``: no (3)
  2414. * ``bool``: yes; tested (1) (4)
  2415. - (1) Only tested with `order` set to ``0``.
  2416. - (2) scikit-image converts internally to ``float64``, which might
  2417. introduce inaccuracies. Tests showed that these inaccuracies
  2418. seemed to not be an issue.
  2419. - (3) Results too inaccurate.
  2420. - (4) Mapped internally to ``float64``.
  2421. Parameters
  2422. ----------
  2423. scale : float or tuple of float or imgaug.parameters.StochasticParameter, optional
  2424. Each point on the regular grid is moved around via a normal
  2425. distribution. This scale factor is equivalent to the normal
  2426. distribution's sigma. Note that the jitter (how far each point is
  2427. moved in which direction) is multiplied by the height/width of the
  2428. image if ``absolute_scale=False`` (default), so this scale can be
  2429. the same for different sized images.
  2430. Recommended values are in the range ``0.01`` to ``0.05`` (weak to
  2431. strong augmentations).
  2432. * If a single ``float``, then that value will always be used as
  2433. the scale.
  2434. * If a tuple ``(a, b)`` of ``float`` s, then a random value will
  2435. be uniformly sampled per image from the interval ``[a, b]``.
  2436. * If a list, then a random value will be picked from that list
  2437. per image.
  2438. * If a ``StochasticParameter``, then that parameter will be
  2439. queried to draw one value per image.
  2440. nb_rows : int or tuple of int or imgaug.parameters.StochasticParameter, optional
  2441. Number of rows of points that the regular grid should have.
  2442. Must be at least ``2``. For large images, you might want to pick a
  2443. higher value than ``4``. You might have to then adjust scale to lower
  2444. values.
  2445. * If a single ``int``, then that value will always be used as the
  2446. number of rows.
  2447. * If a tuple ``(a, b)``, then a value from the discrete interval
  2448. ``[a..b]`` will be uniformly sampled per image.
  2449. * If a list, then a random value will be picked from that list
  2450. per image.
  2451. * If a StochasticParameter, then that parameter will be queried to
  2452. draw one value per image.
  2453. nb_cols : int or tuple of int or imgaug.parameters.StochasticParameter, optional
  2454. Number of columns. Analogous to `nb_rows`.
  2455. order : int or list of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  2456. See :func:`~imgaug.augmenters.geometric.Affine.__init__`.
  2457. cval : int or float or tuple of float or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  2458. See :func:`~imgaug.augmenters.geometric.Affine.__init__`.
  2459. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  2460. See :func:`~imgaug.augmenters.geometric.Affine.__init__`.
  2461. absolute_scale : bool, optional
  2462. Take `scale` as an absolute value rather than a relative value.
  2463. polygon_recoverer : 'auto' or None or imgaug.augmentables.polygons._ConcavePolygonRecoverer, optional
  2464. The class to use to repair invalid polygons.
  2465. If ``"auto"``, a new instance of
  2466. :class`imgaug.augmentables.polygons._ConcavePolygonRecoverer`
  2467. will be created.
  2468. If ``None``, no polygon recoverer will be used.
  2469. If an object, then that object will be used and must provide a
  2470. ``recover_from()`` method, similar to
  2471. :class:`~imgaug.augmentables.polygons._ConcavePolygonRecoverer`.
  2472. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  2473. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  2474. name : None or str, optional
  2475. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  2476. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  2477. Old name for parameter `seed`.
  2478. Its usage will not yet cause a deprecation warning,
  2479. but it is still recommended to use `seed` now.
  2480. Outdated since 0.4.0.
  2481. deterministic : bool, optional
  2482. Deprecated since 0.4.0.
  2483. See method ``to_deterministic()`` for an alternative and for
  2484. details about what the "deterministic mode" actually does.
  2485. Examples
  2486. --------
  2487. >>> import imgaug.augmenters as iaa
  2488. >>> aug = iaa.PiecewiseAffine(scale=(0.01, 0.05))
  2489. Place a regular grid of points on each image and then randomly move each
  2490. point around by ``1`` to ``5`` percent (with respect to the image
  2491. height/width). Pixels between these points will be moved accordingly.
  2492. >>> aug = iaa.PiecewiseAffine(scale=(0.01, 0.05), nb_rows=8, nb_cols=8)
  2493. Same as the previous example, but uses a denser grid of ``8x8`` points
  2494. (default is ``4x4``). This can be useful for large images.
  2495. """
  2496. def __init__(self, scale=(0.0, 0.04), nb_rows=(2, 4), nb_cols=(2, 4),
  2497. order=1, cval=0, mode="constant", absolute_scale=False,
  2498. polygon_recoverer=None,
  2499. seed=None, name=None,
  2500. random_state="deprecated", deterministic="deprecated"):
  2501. super(PiecewiseAffine, self).__init__(
  2502. seed=seed, name=name,
  2503. random_state=random_state, deterministic=deterministic)
  2504. self.scale = iap.handle_continuous_param(
  2505. scale, "scale", value_range=(0, None), tuple_to_uniform=True,
  2506. list_to_choice=True)
  2507. self.jitter = iap.Normal(loc=0, scale=self.scale)
  2508. self.nb_rows = iap.handle_discrete_param(
  2509. nb_rows, "nb_rows", value_range=(2, None), tuple_to_uniform=True,
  2510. list_to_choice=True, allow_floats=False)
  2511. self.nb_cols = iap.handle_discrete_param(
  2512. nb_cols, "nb_cols", value_range=(2, None), tuple_to_uniform=True,
  2513. list_to_choice=True, allow_floats=False)
  2514. self.order = _handle_order_arg(order, backend="skimage")
  2515. self.cval = _handle_cval_arg(cval)
  2516. self.mode = _handle_mode_arg(mode)
  2517. self.absolute_scale = absolute_scale
  2518. self.polygon_recoverer = polygon_recoverer
  2519. if polygon_recoverer == "auto":
  2520. self.polygon_recoverer = _ConcavePolygonRecoverer()
  2521. # Special order, mode and cval parameters for heatmaps and
  2522. # segmentation maps. These may either be None or a fixed value.
  2523. # Stochastic parameters are currently *not* supported.
  2524. # If set to None, the same values as for images will be used.
  2525. # That is really not recommended for the cval parameter.
  2526. self._order_heatmaps = 3
  2527. self._order_segmentation_maps = 0
  2528. self._mode_heatmaps = "constant"
  2529. self._mode_segmentation_maps = "constant"
  2530. self._cval_heatmaps = 0
  2531. self._cval_segmentation_maps = 0
  2532. # Added in 0.4.0.
  2533. def _augment_batch_(self, batch, random_state, parents, hooks):
  2534. samples = self._draw_samples(batch.nb_rows, random_state)
  2535. if batch.images is not None:
  2536. batch.images = self._augment_images_by_samples(batch.images,
  2537. samples)
  2538. if batch.heatmaps is not None:
  2539. batch.heatmaps = self._augment_maps_by_samples(
  2540. batch.heatmaps, "arr_0to1", samples, self._cval_heatmaps,
  2541. self._mode_heatmaps, self._order_heatmaps)
  2542. if batch.segmentation_maps is not None:
  2543. batch.segmentation_maps = self._augment_maps_by_samples(
  2544. batch.segmentation_maps, "arr", samples,
  2545. self._cval_segmentation_maps, self._mode_segmentation_maps,
  2546. self._order_segmentation_maps)
  2547. # TODO add test for recoverer
  2548. if batch.polygons is not None:
  2549. func = functools.partial(
  2550. self._augment_keypoints_by_samples,
  2551. samples=samples)
  2552. batch.polygons = self._apply_to_polygons_as_keypoints(
  2553. batch.polygons, func, recoverer=self.polygon_recoverer)
  2554. for augm_name in ["keypoints", "bounding_boxes", "line_strings"]:
  2555. augm_value = getattr(batch, augm_name)
  2556. if augm_value is not None:
  2557. func = functools.partial(
  2558. self._augment_keypoints_by_samples,
  2559. samples=samples)
  2560. cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
  2561. setattr(batch, augm_name, cbaois)
  2562. return batch
  2563. # Added in 0.4.0.
  2564. def _augment_images_by_samples(self, images, samples):
  2565. iadt.gate_dtypes(
  2566. images,
  2567. allowed=["bool",
  2568. "uint8", "uint16", "uint32",
  2569. "int8", "int16", "int32",
  2570. "float16", "float32", "float64"],
  2571. disallowed=["uint64", "uint128", "uint256",
  2572. "int64", "int128", "int256",
  2573. "float96", "float128", "float256"],
  2574. augmenter=self)
  2575. result = images
  2576. for i, image in enumerate(images):
  2577. transformer = self._get_transformer(
  2578. image.shape, image.shape, samples.nb_rows[i],
  2579. samples.nb_cols[i], samples.jitter[i])
  2580. if transformer is not None:
  2581. input_dtype = image.dtype
  2582. if image.dtype.kind == "b":
  2583. image = image.astype(np.float64)
  2584. image_warped = tf.warp(
  2585. image,
  2586. transformer,
  2587. order=samples.order[i],
  2588. mode=samples.mode[i],
  2589. cval=samples.get_clipped_cval(i, image.dtype),
  2590. preserve_range=True,
  2591. output_shape=images[i].shape
  2592. )
  2593. if input_dtype.kind == "b":
  2594. image_warped = image_warped > 0.5
  2595. else:
  2596. # warp seems to change everything to float64, including
  2597. # uint8, making this necessary
  2598. image_warped = iadt.restore_dtypes_(
  2599. image_warped, input_dtype)
  2600. result[i] = image_warped
  2601. return result
  2602. # Added in 0.4.0.
  2603. def _augment_maps_by_samples(self, augmentables, arr_attr_name, samples,
  2604. cval, mode, order):
  2605. result = augmentables
  2606. for i, augmentable in enumerate(augmentables):
  2607. arr = getattr(augmentable, arr_attr_name)
  2608. transformer = self._get_transformer(
  2609. arr.shape, augmentable.shape, samples.nb_rows[i],
  2610. samples.nb_cols[i], samples.jitter[i])
  2611. if transformer is not None:
  2612. arr_warped = tf.warp(
  2613. arr,
  2614. transformer,
  2615. order=order if order is not None else samples.order[i],
  2616. mode=mode if mode is not None else samples.mode[i],
  2617. cval=cval if cval is not None else samples.cval[i],
  2618. preserve_range=True,
  2619. output_shape=arr.shape
  2620. )
  2621. # skimage converts to float64
  2622. arr_warped = arr_warped.astype(arr.dtype)
  2623. # TODO not entirely clear whether this breaks the value
  2624. # range -- Affine does
  2625. # TODO add test for this
  2626. # order=3 matches cubic interpolation and can cause values
  2627. # to go outside of the range [0.0, 1.0] not clear whether
  2628. # 4+ also do that
  2629. # We don't modify segmaps here, because they don't have a
  2630. # clear value range of [0, 1]
  2631. if order >= 3 and isinstance(augmentable, ia.HeatmapsOnImage):
  2632. arr_warped = np.clip(arr_warped, 0.0, 1.0, out=arr_warped)
  2633. setattr(augmentable, arr_attr_name, arr_warped)
  2634. return result
  2635. # Added in 0.4.0.
  2636. def _augment_keypoints_by_samples(self, kpsois, samples):
  2637. # pylint: disable=pointless-string-statement
  2638. result = []
  2639. for i, kpsoi in enumerate(kpsois):
  2640. h, w = kpsoi.shape[0:2]
  2641. transformer = self._get_transformer(
  2642. kpsoi.shape, kpsoi.shape, samples.nb_rows[i],
  2643. samples.nb_cols[i], samples.jitter[i])
  2644. if transformer is None or len(kpsoi.keypoints) == 0:
  2645. result.append(kpsoi)
  2646. else:
  2647. # Augmentation routine that only modifies keypoint coordinates
  2648. # This is efficient (coordinates of all other locations in the
  2649. # image are ignored). The code below should usually work, but
  2650. # for some reason augmented coordinates are often wildly off
  2651. # for large scale parameters (lots of jitter/distortion).
  2652. # The reason for that is unknown.
  2653. """
  2654. coords = keypoints_on_images[i].get_coords_array()
  2655. coords_aug = transformer.inverse(coords)
  2656. result.append(
  2657. ia.KeypointsOnImage.from_coords_array(
  2658. coords_aug,
  2659. shape=keypoints_on_images[i].shape
  2660. )
  2661. )
  2662. """
  2663. # TODO this could be done a little bit more efficient by
  2664. # removing first all KPs that are outside of the image
  2665. # plane so that no corresponding distance map has to
  2666. # be augmented
  2667. # Image based augmentation routine. Draws the keypoints on
  2668. # the image plane using distance maps (more accurate than
  2669. # just marking the points), then augments these images, then
  2670. # searches for the new (visual) location of the keypoints.
  2671. # Much slower than directly augmenting the coordinates, but
  2672. # here the only method that reliably works.
  2673. dist_maps = kpsoi.to_distance_maps(inverted=True)
  2674. dist_maps_warped = tf.warp(
  2675. dist_maps,
  2676. transformer,
  2677. order=1,
  2678. preserve_range=True,
  2679. output_shape=(kpsoi.shape[0], kpsoi.shape[1],
  2680. len(kpsoi.keypoints))
  2681. )
  2682. kps_aug = ia.KeypointsOnImage.from_distance_maps(
  2683. dist_maps_warped,
  2684. inverted=True,
  2685. threshold=0.01,
  2686. if_not_found_coords={"x": -1, "y": -1},
  2687. nb_channels=(
  2688. None if len(kpsoi.shape) < 3 else kpsoi.shape[2])
  2689. )
  2690. for kp, kp_aug in zip(kpsoi.keypoints, kps_aug.keypoints):
  2691. # Keypoints that were outside of the image plane before the
  2692. # augmentation were replaced with (-1, -1) by default (as
  2693. # they can't be drawn on the keypoint images).
  2694. within_image = (0 <= kp.x < w and 0 <= kp.y < h)
  2695. if within_image:
  2696. kp.x = kp_aug.x
  2697. kp.y = kp_aug.y
  2698. result.append(kpsoi)
  2699. return result
  2700. def _draw_samples(self, nb_images, random_state):
  2701. rss = random_state.duplicate(6)
  2702. nb_rows_samples = self.nb_rows.draw_samples((nb_images,),
  2703. random_state=rss[-6])
  2704. nb_cols_samples = self.nb_cols.draw_samples((nb_images,),
  2705. random_state=rss[-5])
  2706. order_samples = self.order.draw_samples((nb_images,),
  2707. random_state=rss[-4])
  2708. cval_samples = self.cval.draw_samples((nb_images,),
  2709. random_state=rss[-3])
  2710. mode_samples = self.mode.draw_samples((nb_images,),
  2711. random_state=rss[-2])
  2712. nb_rows_samples = np.clip(nb_rows_samples, 2, None)
  2713. nb_cols_samples = np.clip(nb_cols_samples, 2, None)
  2714. nb_cells = nb_rows_samples * nb_cols_samples
  2715. jitter = self.jitter.draw_samples((int(np.sum(nb_cells)), 2),
  2716. random_state=rss[-1])
  2717. jitter_by_image = []
  2718. counter = 0
  2719. for nb_cells_i in nb_cells:
  2720. jitter_img = jitter[counter:counter+nb_cells_i, :]
  2721. jitter_by_image.append(jitter_img)
  2722. counter += nb_cells_i
  2723. return _PiecewiseAffineSamplingResult(
  2724. nb_rows=nb_rows_samples, nb_cols=nb_cols_samples,
  2725. jitter=jitter_by_image,
  2726. order=order_samples, cval=cval_samples, mode=mode_samples)
  2727. def _get_transformer(self, augmentable_shape, image_shape, nb_rows,
  2728. nb_cols, jitter_img):
  2729. # get coords on y and x axis of points to move around
  2730. # these coordinates are supposed to be at the centers of each cell
  2731. # (otherwise the first coordinate would be at (0, 0) and could hardly
  2732. # be moved around before leaving the image),
  2733. # so we use here (half cell height/width to H/W minus half
  2734. # height/width) instead of (0, H/W)
  2735. # pylint: disable=no-else-return
  2736. y = np.linspace(0, augmentable_shape[0], nb_rows)
  2737. x = np.linspace(0, augmentable_shape[1], nb_cols)
  2738. # (H, W) and (H, W) for H=rows, W=cols
  2739. xx_src, yy_src = np.meshgrid(x, y)
  2740. # (1, HW, 2) => (HW, 2) for H=rows, W=cols
  2741. points_src = np.dstack([yy_src.flat, xx_src.flat])[0]
  2742. any_nonzero = np.any(jitter_img > 0)
  2743. if not any_nonzero:
  2744. return None
  2745. else:
  2746. # Without this, jitter gets changed between different augmentables.
  2747. # TODO if left out, only one test failed -- should be more
  2748. jitter_img = np.copy(jitter_img)
  2749. if self.absolute_scale:
  2750. if image_shape[0] > 0:
  2751. jitter_img[:, 0] = jitter_img[:, 0] / image_shape[0]
  2752. else:
  2753. jitter_img[:, 0] = 0.0
  2754. if image_shape[1] > 0:
  2755. jitter_img[:, 1] = jitter_img[:, 1] / image_shape[1]
  2756. else:
  2757. jitter_img[:, 1] = 0.0
  2758. jitter_img[:, 0] = jitter_img[:, 0] * augmentable_shape[0]
  2759. jitter_img[:, 1] = jitter_img[:, 1] * augmentable_shape[1]
  2760. points_dest = np.copy(points_src)
  2761. points_dest[:, 0] = points_dest[:, 0] + jitter_img[:, 0]
  2762. points_dest[:, 1] = points_dest[:, 1] + jitter_img[:, 1]
  2763. # Restrict all destination points to be inside the image plane.
  2764. # This is necessary, as otherwise keypoints could be augmented
  2765. # outside of the image plane and these would be replaced by
  2766. # (-1, -1), which would not conform with the behaviour of the
  2767. # other augmenters.
  2768. points_dest[:, 0] = np.clip(points_dest[:, 0],
  2769. 0, augmentable_shape[0]-1)
  2770. points_dest[:, 1] = np.clip(points_dest[:, 1],
  2771. 0, augmentable_shape[1]-1)
  2772. # tf.warp() results in qhull error if the points are identical,
  2773. # which is mainly the case if any axis is 0
  2774. has_low_axis = any([axis <= 1 for axis in augmentable_shape[0:2]])
  2775. has_zero_channels = (
  2776. (
  2777. augmentable_shape is not None
  2778. and len(augmentable_shape) == 3
  2779. and augmentable_shape[-1] == 0
  2780. )
  2781. or
  2782. (
  2783. image_shape is not None
  2784. and len(image_shape) == 3
  2785. and image_shape[-1] == 0
  2786. )
  2787. )
  2788. if has_low_axis or has_zero_channels:
  2789. return None
  2790. else:
  2791. matrix = tf.PiecewiseAffineTransform()
  2792. matrix.estimate(points_src[:, ::-1], points_dest[:, ::-1])
  2793. return matrix
  2794. def get_parameters(self):
  2795. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  2796. return [
  2797. self.scale, self.nb_rows, self.nb_cols, self.order, self.cval,
  2798. self.mode, self.absolute_scale]
  2799. class _PerspectiveTransformSamplingResult(object):
  2800. def __init__(self, matrices, max_heights, max_widths, cvals, modes):
  2801. self.matrices = matrices
  2802. self.max_heights = max_heights
  2803. self.max_widths = max_widths
  2804. self.cvals = cvals
  2805. self.modes = modes
  2806. # TODO add arg for image interpolation
  2807. class PerspectiveTransform(meta.Augmenter):
  2808. """
  2809. Apply random four point perspective transformations to images.
  2810. Each of the four points is placed on the image using a random distance from
  2811. its respective corner. The distance is sampled from a normal distribution.
  2812. As a result, most transformations don't change the image very much, while
  2813. some "focus" on polygons far inside the image.
  2814. The results of this augmenter have some similarity with ``Crop``.
  2815. Code partially from
  2816. http://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example/
  2817. **Supported dtypes**:
  2818. if (keep_size=False):
  2819. * ``uint8``: yes; fully tested
  2820. * ``uint16``: yes; tested
  2821. * ``uint32``: no (1)
  2822. * ``uint64``: no (2)
  2823. * ``int8``: yes; tested (3)
  2824. * ``int16``: yes; tested
  2825. * ``int32``: no (2)
  2826. * ``int64``: no (2)
  2827. * ``float16``: yes; tested (4)
  2828. * ``float32``: yes; tested
  2829. * ``float64``: yes; tested
  2830. * ``float128``: no (1)
  2831. * ``bool``: yes; tested (4)
  2832. - (1) rejected by opencv
  2833. - (2) leads to opencv error: cv2.error: ``OpenCV(3.4.4)
  2834. (...)imgwarp.cpp:1805: error: (-215:Assertion failed)
  2835. ifunc != 0 in function 'remap'``.
  2836. - (3) mapped internally to ``int16``.
  2837. - (4) mapped intenally to ``float32``.
  2838. if (keep_size=True):
  2839. minimum of (
  2840. ``imgaug.augmenters.geometric.PerspectiveTransform(keep_size=False)``,
  2841. :func:`~imgaug.imgaug.imresize_many_images`
  2842. )
  2843. Parameters
  2844. ----------
  2845. scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  2846. Standard deviation of the normal distributions. These are used to
  2847. sample the random distances of the subimage's corners from the full
  2848. image's corners. The sampled values reflect percentage values (with
  2849. respect to image height/width). Recommended values are in the range
  2850. ``0.0`` to ``0.1``.
  2851. * If a single number, then that value will always be used as the
  2852. scale.
  2853. * If a tuple ``(a, b)`` of numbers, then a random value will be
  2854. uniformly sampled per image from the interval ``(a, b)``.
  2855. * If a list of values, a random value will be picked from the
  2856. list per image.
  2857. * If a ``StochasticParameter``, then that parameter will be
  2858. queried to draw one value per image.
  2859. keep_size : bool, optional
  2860. Whether to resize image's back to their original size after applying
  2861. the perspective transform. If set to ``False``, the resulting images
  2862. may end up having different shapes and will always be a list, never
  2863. an array.
  2864. cval : number or tuple of number or list of number or imaug.ALL or imgaug.parameters.StochasticParameter, optional
  2865. The constant value used to fill up pixels in the result image that
  2866. didn't exist in the input image (e.g. when translating to the left,
  2867. some new pixels are created at the right). Such a fill-up with a
  2868. constant value only happens, when `mode` is ``constant``.
  2869. The expected value range is ``[0, 255]`` for ``uint8`` images.
  2870. It may be a float value.
  2871. * If this is a single int or float, then that value will be used
  2872. (e.g. 0 results in black pixels).
  2873. * If a tuple ``(a, b)``, then a random value is uniformly sampled
  2874. per image from the interval ``[a, b]``.
  2875. * If a list, then a random value will be sampled from that list
  2876. per image.
  2877. * If ``imgaug.ALL``, then equivalent to tuple ``(0, 255)``.
  2878. * If a ``StochasticParameter``, a new value will be sampled from
  2879. the parameter per image.
  2880. mode : int or str or list of str or list of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  2881. Parameter that defines the handling of newly created pixels.
  2882. Same meaning as in OpenCV's border mode. Let ``abcdefgh`` be an image's
  2883. content and ``|`` be an image boundary, then:
  2884. * ``cv2.BORDER_REPLICATE``: ``aaaaaa|abcdefgh|hhhhhhh``
  2885. * ``cv2.BORDER_CONSTANT``: ``iiiiii|abcdefgh|iiiiiii``, where
  2886. ``i`` is the defined cval.
  2887. * ``replicate``: Same as ``cv2.BORDER_REPLICATE``.
  2888. * ``constant``: Same as ``cv2.BORDER_CONSTANT``.
  2889. The datatype of the parameter may be:
  2890. * If a single ``int``, then it must be one of ``cv2.BORDER_*``.
  2891. * If a single string, then it must be one of: ``replicate``,
  2892. ``reflect``, ``reflect_101``, ``wrap``, ``constant``.
  2893. * If a list of ints/strings, then per image a random mode will be
  2894. picked from that list.
  2895. * If ``imgaug.ALL``, then a random mode from all possible modes
  2896. will be picked per image.
  2897. * If ``StochasticParameter``, then the mode will be sampled from
  2898. that parameter per image, i.e. it must return only the above
  2899. mentioned strings.
  2900. fit_output : bool, optional
  2901. If ``True``, the image plane size and position will be adjusted
  2902. to still capture the whole image after perspective transformation.
  2903. (Followed by image resizing if `keep_size` is set to ``True``.)
  2904. Otherwise, parts of the transformed image may be outside of the image
  2905. plane.
  2906. This setting should not be set to ``True`` when using large `scale`
  2907. values as it could lead to very large images.
  2908. Added in 0.4.0.
  2909. polygon_recoverer : 'auto' or None or imgaug.augmentables.polygons._ConcavePolygonRecoverer, optional
  2910. The class to use to repair invalid polygons.
  2911. If ``"auto"``, a new instance of
  2912. :class`imgaug.augmentables.polygons._ConcavePolygonRecoverer`
  2913. will be created.
  2914. If ``None``, no polygon recoverer will be used.
  2915. If an object, then that object will be used and must provide a
  2916. ``recover_from()`` method, similar to
  2917. :class:`~imgaug.augmentables.polygons._ConcavePolygonRecoverer`.
  2918. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  2919. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  2920. name : None or str, optional
  2921. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  2922. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  2923. Old name for parameter `seed`.
  2924. Its usage will not yet cause a deprecation warning,
  2925. but it is still recommended to use `seed` now.
  2926. Outdated since 0.4.0.
  2927. deterministic : bool, optional
  2928. Deprecated since 0.4.0.
  2929. See method ``to_deterministic()`` for an alternative and for
  2930. details about what the "deterministic mode" actually does.
  2931. Examples
  2932. --------
  2933. >>> import imgaug.augmenters as iaa
  2934. >>> aug = iaa.PerspectiveTransform(scale=(0.01, 0.15))
  2935. Apply perspective transformations using a random scale between ``0.01``
  2936. and ``0.15`` per image, where the scale is roughly a measure of how far
  2937. the perspective transformation's corner points may be distanced from the
  2938. image's corner points. Higher scale values lead to stronger "zoom-in"
  2939. effects (and thereby stronger distortions).
  2940. >>> aug = iaa.PerspectiveTransform(scale=(0.01, 0.15), keep_size=False)
  2941. Same as in the previous example, but images are not resized back to
  2942. the input image size after augmentation. This will lead to smaller
  2943. output images.
  2944. """
  2945. _BORDER_MODE_STR_TO_INT = {
  2946. "replicate": cv2.BORDER_REPLICATE,
  2947. "constant": cv2.BORDER_CONSTANT
  2948. }
  2949. def __init__(self, scale=(0.0, 0.06), cval=0, mode="constant",
  2950. keep_size=True, fit_output=False, polygon_recoverer="auto",
  2951. seed=None, name=None,
  2952. random_state="deprecated", deterministic="deprecated"):
  2953. super(PerspectiveTransform, self).__init__(
  2954. seed=seed, name=name,
  2955. random_state=random_state, deterministic=deterministic)
  2956. self.scale = iap.handle_continuous_param(
  2957. scale, "scale", value_range=(0, None), tuple_to_uniform=True,
  2958. list_to_choice=True)
  2959. self.jitter = iap.Normal(loc=0, scale=self.scale)
  2960. # setting these to 1x1 caused problems for large scales and polygon
  2961. # augmentation
  2962. # TODO there is now a recoverer for polygons - are these minima still
  2963. # needed/sensible?
  2964. self.min_width = 2
  2965. self.min_height = 2
  2966. self.cval = _handle_cval_arg(cval)
  2967. self.mode = self._handle_mode_arg(mode)
  2968. self.keep_size = keep_size
  2969. self.fit_output = fit_output
  2970. self.polygon_recoverer = polygon_recoverer
  2971. if polygon_recoverer == "auto":
  2972. self.polygon_recoverer = _ConcavePolygonRecoverer()
  2973. # Special order, mode and cval parameters for heatmaps and
  2974. # segmentation maps. These may either be None or a fixed value.
  2975. # Stochastic parameters are currently *not* supported.
  2976. # If set to None, the same values as for images will be used.
  2977. # That is really not recommended for the cval parameter.
  2978. self._order_heatmaps = cv2.INTER_LINEAR
  2979. self._order_segmentation_maps = cv2.INTER_NEAREST
  2980. self._mode_heatmaps = cv2.BORDER_CONSTANT
  2981. self._mode_segmentation_maps = cv2.BORDER_CONSTANT
  2982. self._cval_heatmaps = 0
  2983. self._cval_segmentation_maps = 0
  2984. # TODO unify this somehow with the global _handle_mode_arg() that is
  2985. # currently used for Affine and PiecewiseAffine
  2986. @classmethod
  2987. def _handle_mode_arg(cls, mode):
  2988. available_modes = [cv2.BORDER_REPLICATE, cv2.BORDER_CONSTANT]
  2989. available_modes_str = ["replicate", "constant"]
  2990. if mode == ia.ALL:
  2991. return iap.Choice(available_modes)
  2992. if ia.is_single_integer(mode):
  2993. assert mode in available_modes, (
  2994. "Expected mode to be in %s, got %d." % (
  2995. str(available_modes), mode))
  2996. return iap.Deterministic(mode)
  2997. if ia.is_string(mode):
  2998. assert mode in available_modes_str, (
  2999. "Expected mode to be in %s, got %s." % (
  3000. str(available_modes_str), mode))
  3001. return iap.Deterministic(mode)
  3002. if isinstance(mode, list):
  3003. valid_types = all([ia.is_single_integer(val) or ia.is_string(val)
  3004. for val in mode])
  3005. assert valid_types, (
  3006. "Expected mode list to only contain integers/strings, got "
  3007. "types %s." % (
  3008. ", ".join([str(type(val)) for val in mode]),))
  3009. valid_modes = all([val in available_modes + available_modes_str
  3010. for val in mode])
  3011. assert valid_modes, (
  3012. "Expected all mode values to be in %s, got %s." % (
  3013. str(available_modes + available_modes_str), str(mode)))
  3014. return iap.Choice(mode)
  3015. if isinstance(mode, iap.StochasticParameter):
  3016. return mode
  3017. raise Exception(
  3018. "Expected mode to be imgaug.ALL, an int, a string, a list "
  3019. "of int/strings or StochasticParameter, got %s." % (
  3020. type(mode),))
  3021. # Added in 0.4.0.
  3022. def _augment_batch_(self, batch, random_state, parents, hooks):
  3023. # Advance once, because below we always use random_state.copy() and
  3024. # hence the sampling calls actually don't change random_state's state.
  3025. # Without this, every call of the augmenter would produce the same
  3026. # results.
  3027. random_state.advance_()
  3028. samples_images = self._draw_samples(batch.get_rowwise_shapes(),
  3029. random_state.copy())
  3030. if batch.images is not None:
  3031. batch.images = self._augment_images_by_samples(batch.images,
  3032. samples_images)
  3033. if batch.heatmaps is not None:
  3034. samples = self._draw_samples(
  3035. [augmentable.arr_0to1.shape
  3036. for augmentable in batch.heatmaps],
  3037. random_state.copy())
  3038. batch.heatmaps = self._augment_maps_by_samples(
  3039. batch.heatmaps, "arr_0to1", samples, samples_images,
  3040. self._cval_heatmaps, self._mode_heatmaps, self._order_heatmaps)
  3041. if batch.segmentation_maps is not None:
  3042. samples = self._draw_samples(
  3043. [augmentable.arr.shape
  3044. for augmentable in batch.segmentation_maps],
  3045. random_state.copy())
  3046. batch.segmentation_maps = self._augment_maps_by_samples(
  3047. batch.segmentation_maps, "arr", samples, samples_images,
  3048. self._cval_segmentation_maps, self._mode_segmentation_maps,
  3049. self._order_segmentation_maps)
  3050. # large scale values cause invalid polygons (unclear why that happens),
  3051. # hence the recoverer
  3052. # TODO add test for recoverer
  3053. if batch.polygons is not None:
  3054. func = functools.partial(
  3055. self._augment_keypoints_by_samples,
  3056. samples_images=samples_images)
  3057. batch.polygons = self._apply_to_polygons_as_keypoints(
  3058. batch.polygons, func, recoverer=self.polygon_recoverer)
  3059. for augm_name in ["keypoints", "bounding_boxes", "line_strings"]:
  3060. augm_value = getattr(batch, augm_name)
  3061. if augm_value is not None:
  3062. func = functools.partial(
  3063. self._augment_keypoints_by_samples,
  3064. samples_images=samples_images)
  3065. cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
  3066. setattr(batch, augm_name, cbaois)
  3067. return batch
  3068. # Added in 0.4.0.
  3069. def _augment_images_by_samples(self, images, samples):
  3070. iadt.gate_dtypes(
  3071. images,
  3072. allowed=["bool",
  3073. "uint8", "uint16",
  3074. "int8", "int16",
  3075. "float16", "float32", "float64"],
  3076. disallowed=["uint32", "uint64", "uint128", "uint256",
  3077. "int32", "int64", "int128", "int256",
  3078. "float96", "float128", "float256"],
  3079. augmenter=self)
  3080. result = images
  3081. if not self.keep_size:
  3082. result = list(result)
  3083. gen = enumerate(zip(images, samples.matrices, samples.max_heights,
  3084. samples.max_widths, samples.cvals, samples.modes))
  3085. for i, (image, matrix, max_height, max_width, cval, mode) in gen:
  3086. input_dtype = image.dtype
  3087. if input_dtype.name in ["int8"]:
  3088. image = image.astype(np.int16)
  3089. elif input_dtype.name in ["bool", "float16"]:
  3090. image = image.astype(np.float32)
  3091. # cv2.warpPerspective only supports <=4 channels and errors
  3092. # on axes with size zero
  3093. nb_channels = image.shape[2]
  3094. has_zero_sized_axis = (image.size == 0)
  3095. if has_zero_sized_axis:
  3096. warped = image
  3097. elif nb_channels <= 4:
  3098. warped = cv2.warpPerspective(
  3099. _normalize_cv2_input_arr_(image),
  3100. matrix,
  3101. (max_width, max_height),
  3102. borderValue=cval,
  3103. borderMode=mode)
  3104. if warped.ndim == 2 and images[i].ndim == 3:
  3105. warped = np.expand_dims(warped, 2)
  3106. else:
  3107. # warp each channel on its own
  3108. # note that cv2 removes the channel axis in case of (H,W,1)
  3109. # inputs
  3110. warped = [
  3111. cv2.warpPerspective(
  3112. _normalize_cv2_input_arr_(image[..., c]),
  3113. matrix,
  3114. (max_width, max_height),
  3115. borderValue=cval[min(c, len(cval)-1)],
  3116. borderMode=mode,
  3117. flags=cv2.INTER_LINEAR
  3118. )
  3119. for c in sm.xrange(nb_channels)
  3120. ]
  3121. warped = np.stack(warped, axis=-1)
  3122. if self.keep_size and not has_zero_sized_axis:
  3123. h, w = image.shape[0:2]
  3124. warped = ia.imresize_single_image(warped, (h, w))
  3125. if input_dtype.name == "bool":
  3126. warped = warped > 0.5
  3127. elif warped.dtype.name != input_dtype.name:
  3128. warped = iadt.restore_dtypes_(warped, input_dtype)
  3129. result[i] = warped
  3130. return result
  3131. # Added in 0.4.0.
  3132. def _augment_maps_by_samples(self, augmentables, arr_attr_name,
  3133. samples, samples_images, cval, mode, flags):
  3134. result = augmentables
  3135. # estimate max_heights/max_widths for the underlying images
  3136. # this is only necessary if keep_size is False as then the underlying
  3137. # image sizes change and we need to update them here
  3138. # TODO this was re-used from before _augment_batch_() -- reoptimize
  3139. if self.keep_size:
  3140. max_heights_imgs = samples.max_heights
  3141. max_widths_imgs = samples.max_widths
  3142. else:
  3143. max_heights_imgs = samples_images.max_heights
  3144. max_widths_imgs = samples_images.max_widths
  3145. gen = enumerate(zip(augmentables, samples.matrices, samples.max_heights,
  3146. samples.max_widths))
  3147. for i, (augmentable_i, matrix, max_height, max_width) in gen:
  3148. arr = getattr(augmentable_i, arr_attr_name)
  3149. mode_i = mode
  3150. if mode is None:
  3151. mode_i = samples.modes[i]
  3152. cval_i = cval
  3153. if cval is None:
  3154. cval_i = samples.cvals[i]
  3155. nb_channels = arr.shape[2]
  3156. image_has_zero_sized_axis = (0 in augmentable_i.shape)
  3157. map_has_zero_sized_axis = (arr.size == 0)
  3158. if not image_has_zero_sized_axis:
  3159. if not map_has_zero_sized_axis:
  3160. warped = [
  3161. cv2.warpPerspective(
  3162. _normalize_cv2_input_arr_(arr[..., c]),
  3163. matrix,
  3164. (max_width, max_height),
  3165. borderValue=cval_i,
  3166. borderMode=mode_i,
  3167. flags=flags
  3168. )
  3169. for c in sm.xrange(nb_channels)
  3170. ]
  3171. warped = np.stack(warped, axis=-1)
  3172. setattr(augmentable_i, arr_attr_name, warped)
  3173. if self.keep_size:
  3174. h, w = arr.shape[0:2]
  3175. augmentable_i = augmentable_i.resize((h, w))
  3176. else:
  3177. new_shape = (
  3178. max_heights_imgs[i], max_widths_imgs[i]
  3179. ) + augmentable_i.shape[2:]
  3180. augmentable_i.shape = new_shape
  3181. result[i] = augmentable_i
  3182. return result
  3183. # Added in 0.4.0.
  3184. def _augment_keypoints_by_samples(self, kpsois, samples_images):
  3185. result = kpsois
  3186. gen = enumerate(zip(kpsois,
  3187. samples_images.matrices,
  3188. samples_images.max_heights,
  3189. samples_images.max_widths))
  3190. for i, (kpsoi, matrix, max_height, max_width) in gen:
  3191. image_has_zero_sized_axis = (0 in kpsoi.shape)
  3192. if not image_has_zero_sized_axis:
  3193. shape_orig = kpsoi.shape
  3194. shape_new = (max_height, max_width) + kpsoi.shape[2:]
  3195. kpsoi.shape = shape_new
  3196. if not kpsoi.empty:
  3197. kps_arr = kpsoi.to_xy_array()
  3198. warped = cv2.perspectiveTransform(
  3199. np.array([kps_arr], dtype=np.float32), matrix)
  3200. warped = warped[0]
  3201. for kp, coords in zip(kpsoi.keypoints, warped):
  3202. kp.x = coords[0]
  3203. kp.y = coords[1]
  3204. if self.keep_size:
  3205. kpsoi = kpsoi.on_(shape_orig)
  3206. result[i] = kpsoi
  3207. return result
  3208. # Added in 0.4.0.
  3209. def _draw_samples(self, shapes, random_state):
  3210. # pylint: disable=invalid-name
  3211. matrices = []
  3212. max_heights = []
  3213. max_widths = []
  3214. nb_images = len(shapes)
  3215. rngs = random_state.duplicate(3)
  3216. cval_samples = self.cval.draw_samples((nb_images, 3),
  3217. random_state=rngs[0])
  3218. mode_samples = self.mode.draw_samples((nb_images,),
  3219. random_state=rngs[1])
  3220. jitter = self.jitter.draw_samples((nb_images, 4, 2),
  3221. random_state=rngs[2])
  3222. # cv2 perspectiveTransform doesn't accept numpy arrays as cval
  3223. cval_samples_cv2 = cval_samples.tolist()
  3224. # if border modes are represented by strings, convert them to cv2
  3225. # border mode integers
  3226. if mode_samples.dtype.kind not in ["i", "u"]:
  3227. for mode, mapped_mode in self._BORDER_MODE_STR_TO_INT.items():
  3228. mode_samples[mode_samples == mode] = mapped_mode
  3229. # modify jitter to the four corner point coordinates
  3230. # some x/y values have to be modified from `jitter` to `1-jtter`
  3231. # for that
  3232. # TODO remove the abs() here. it currently only allows to "zoom-in",
  3233. # not to "zoom-out"
  3234. points = np.mod(np.abs(jitter), 1)
  3235. # top left -- no changes needed, just use jitter
  3236. # top right
  3237. points[:, 1, 0] = 1.0 - points[:, 1, 0] # w = 1.0 - jitter
  3238. # bottom right
  3239. points[:, 2, 0] = 1.0 - points[:, 2, 0] # w = 1.0 - jitter
  3240. points[:, 2, 1] = 1.0 - points[:, 2, 1] # h = 1.0 - jitter
  3241. # bottom left
  3242. points[:, 3, 1] = 1.0 - points[:, 3, 1] # h = 1.0 - jitter
  3243. for shape, points_i in zip(shapes, points):
  3244. h, w = shape[0:2]
  3245. points_i[:, 0] *= w
  3246. points_i[:, 1] *= h
  3247. # Obtain a consistent order of the points and unpack them
  3248. # individually.
  3249. # Warning: don't just do (tl, tr, br, bl) = _order_points(...)
  3250. # here, because the reordered points_i is used further below.
  3251. points_i = self._order_points(points_i)
  3252. (tl, tr, br, bl) = points_i
  3253. # compute the width of the new image, which will be the
  3254. # maximum distance between bottom-right and bottom-left
  3255. # x-coordiates or the top-right and top-left x-coordinates
  3256. min_width = None
  3257. max_width = None
  3258. while min_width is None or min_width < self.min_width:
  3259. width_top = np.sqrt(((tr[0]-tl[0])**2) + ((tr[1]-tl[1])**2))
  3260. width_bottom = np.sqrt(((br[0]-bl[0])**2) + ((br[1]-bl[1])**2))
  3261. max_width = int(max(width_top, width_bottom))
  3262. min_width = int(min(width_top, width_bottom))
  3263. if min_width < self.min_width:
  3264. step_size = (self.min_width - min_width)/2
  3265. tl[0] -= step_size
  3266. tr[0] += step_size
  3267. bl[0] -= step_size
  3268. br[0] += step_size
  3269. # compute the height of the new image, which will be the
  3270. # maximum distance between the top-right and bottom-right
  3271. # y-coordinates or the top-left and bottom-left y-coordinates
  3272. min_height = None
  3273. max_height = None
  3274. while min_height is None or min_height < self.min_height:
  3275. height_right = np.sqrt(((tr[0]-br[0])**2) + ((tr[1]-br[1])**2))
  3276. height_left = np.sqrt(((tl[0]-bl[0])**2) + ((tl[1]-bl[1])**2))
  3277. max_height = int(max(height_right, height_left))
  3278. min_height = int(min(height_right, height_left))
  3279. if min_height < self.min_height:
  3280. step_size = (self.min_height - min_height)/2
  3281. tl[1] -= step_size
  3282. tr[1] -= step_size
  3283. bl[1] += step_size
  3284. br[1] += step_size
  3285. # now that we have the dimensions of the new image, construct
  3286. # the set of destination points to obtain a "birds eye view",
  3287. # (i.e. top-down view) of the image, again specifying points
  3288. # in the top-left, top-right, bottom-right, and bottom-left
  3289. # order
  3290. # do not use width-1 or height-1 here, as for e.g. width=3, height=2
  3291. # the bottom right coordinate is at (3.0, 2.0) and not (2.0, 1.0)
  3292. dst = np.array([
  3293. [0, 0],
  3294. [max_width, 0],
  3295. [max_width, max_height],
  3296. [0, max_height]
  3297. ], dtype=np.float32)
  3298. # compute the perspective transform matrix and then apply it
  3299. m = cv2.getPerspectiveTransform(points_i, dst)
  3300. if self.fit_output:
  3301. m, max_width, max_height = self._expand_transform(m, (h, w))
  3302. matrices.append(m)
  3303. max_heights.append(max_height)
  3304. max_widths.append(max_width)
  3305. mode_samples = mode_samples.astype(np.int32)
  3306. return _PerspectiveTransformSamplingResult(
  3307. matrices, max_heights, max_widths, cval_samples_cv2,
  3308. mode_samples)
  3309. @classmethod
  3310. def _order_points(cls, pts):
  3311. # initialzie a list of coordinates that will be ordered
  3312. # such that the first entry in the list is the top-left,
  3313. # the second entry is the top-right, the third is the
  3314. # bottom-right, and the fourth is the bottom-left
  3315. pts_ordered = np.zeros((4, 2), dtype=np.float32)
  3316. # the top-left point will have the smallest sum, whereas
  3317. # the bottom-right point will have the largest sum
  3318. pointwise_sum = pts.sum(axis=1)
  3319. pts_ordered[0] = pts[np.argmin(pointwise_sum)]
  3320. pts_ordered[2] = pts[np.argmax(pointwise_sum)]
  3321. # now, compute the difference between the points, the
  3322. # top-right point will have the smallest difference,
  3323. # whereas the bottom-left will have the largest difference
  3324. diff = np.diff(pts, axis=1)
  3325. pts_ordered[1] = pts[np.argmin(diff)]
  3326. pts_ordered[3] = pts[np.argmax(diff)]
  3327. # return the ordered coordinates
  3328. return pts_ordered
  3329. # Added in 0.4.0.
  3330. @classmethod
  3331. def _expand_transform(cls, matrix, shape):
  3332. height, width = shape
  3333. # do not use width-1 or height-1 here, as for e.g. width=3, height=2
  3334. # the bottom right coordinate is at (3.0, 2.0) and not (2.0, 1.0)
  3335. rect = np.array([
  3336. [0, 0],
  3337. [width, 0],
  3338. [width, height],
  3339. [0, height]], dtype=np.float32)
  3340. dst = cv2.perspectiveTransform(np.array([rect]), matrix)[0]
  3341. # get min x, y over transformed 4 points
  3342. # then modify target points by subtracting these minima
  3343. # => shift to (0, 0)
  3344. dst -= dst.min(axis=0, keepdims=True)
  3345. dst = np.around(dst, decimals=0)
  3346. matrix_expanded = cv2.getPerspectiveTransform(rect, dst)
  3347. max_width, max_height = dst.max(axis=0)
  3348. return matrix_expanded, int(max_width), int(max_height)
  3349. def get_parameters(self):
  3350. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  3351. return [self.jitter, self.keep_size, self.cval, self.mode,
  3352. self.fit_output]
  3353. class _ElasticTransformationSamplingResult(object):
  3354. def __init__(self, random_states, alphas, sigmas, orders, cvals, modes):
  3355. self.random_states = random_states
  3356. self.alphas = alphas
  3357. self.sigmas = sigmas
  3358. self.orders = orders
  3359. self.cvals = cvals
  3360. self.modes = modes
  3361. # TODO add independent sigmas for x/y
  3362. # TODO add independent alphas for x/y
  3363. # TODO add backend arg
  3364. class ElasticTransformation(meta.Augmenter):
  3365. """
  3366. Transform images by moving pixels locally around using displacement fields.
  3367. The augmenter has the parameters ``alpha`` and ``sigma``. ``alpha``
  3368. controls the strength of the displacement: higher values mean that pixels
  3369. are moved further. ``sigma`` controls the smoothness of the displacement:
  3370. higher values lead to smoother patterns -- as if the image was below water
  3371. -- while low values will cause indivdual pixels to be moved very
  3372. differently from their neighbours, leading to noisy and pixelated images.
  3373. A relation of 10:1 seems to be good for ``alpha`` and ``sigma``, e.g.
  3374. ``alpha=10`` and ``sigma=1`` or ``alpha=50``, ``sigma=5``. For ``128x128``
  3375. a setting of ``alpha=(0, 70.0)``, ``sigma=(4.0, 6.0)`` may be a good
  3376. choice and will lead to a water-like effect.
  3377. Code here was initially inspired by
  3378. https://gist.github.com/chsasank/4d8f68caf01f041a6453e67fb30f8f5a
  3379. For a detailed explanation, see ::
  3380. Simard, Steinkraus and Platt
  3381. Best Practices for Convolutional Neural Networks applied to Visual
  3382. Document Analysis
  3383. in Proc. of the International Conference on Document Analysis and
  3384. Recognition, 2003
  3385. .. note::
  3386. For coordinate-based inputs (keypoints, bounding boxes, polygons,
  3387. ...), this augmenter still has to perform an image-based augmentation,
  3388. which will make it significantly slower for such inputs than other
  3389. augmenters. See :ref:`performance`.
  3390. **Supported dtypes**:
  3391. * ``uint8``: yes; fully tested (1)
  3392. * ``uint16``: yes; tested (1)
  3393. * ``uint32``: yes; tested (2)
  3394. * ``uint64``: limited; tested (3)
  3395. * ``int8``: yes; tested (1) (4) (5)
  3396. * ``int16``: yes; tested (4) (6)
  3397. * ``int32``: yes; tested (4) (6)
  3398. * ``int64``: limited; tested (3)
  3399. * ``float16``: yes; tested (1)
  3400. * ``float32``: yes; tested (1)
  3401. * ``float64``: yes; tested (1)
  3402. * ``float128``: no
  3403. * ``bool``: yes; tested (1) (7)
  3404. - (1) Always handled by ``cv2``.
  3405. - (2) Always handled by ``scipy``.
  3406. - (3) Only supported for ``order != 0``. Will fail for ``order=0``.
  3407. - (4) Mapped internally to ``float64`` when ``order=1``.
  3408. - (5) Mapped internally to ``int16`` when ``order>=2``.
  3409. - (6) Handled by ``cv2`` when ``order=0`` or ``order=1``, otherwise by
  3410. ``scipy``.
  3411. - (7) Mapped internally to ``float32``.
  3412. Parameters
  3413. ----------
  3414. alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  3415. Strength of the distortion field. Higher values mean that pixels are
  3416. moved further with respect to the distortion field's direction. Set
  3417. this to around 10 times the value of `sigma` for visible effects.
  3418. * If number, then that value will be used for all images.
  3419. * If tuple ``(a, b)``, then a random value will be uniformly
  3420. sampled per image from the interval ``[a, b]``.
  3421. * If a list, then for each image a random value will be sampled
  3422. from that list.
  3423. * If ``StochasticParameter``, then that parameter will be used to
  3424. sample a value per image.
  3425. sigma : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
  3426. Standard deviation of the gaussian kernel used to smooth the distortion
  3427. fields. Higher values (for ``128x128`` images around 5.0) lead to more
  3428. water-like effects, while lower values (for ``128x128`` images
  3429. around ``1.0`` and lower) lead to more noisy, pixelated images. Set
  3430. this to around 1/10th of `alpha` for visible effects.
  3431. * If number, then that value will be used for all images.
  3432. * If tuple ``(a, b)``, then a random value will be uniformly
  3433. sampled per image from the interval ``[a, b]``.
  3434. * If a list, then for each image a random value will be sampled
  3435. from that list.
  3436. * If ``StochasticParameter``, then that parameter will be used to
  3437. sample a value per image.
  3438. order : int or list of int or imaug.ALL or imgaug.parameters.StochasticParameter, optional
  3439. Interpolation order to use. Same meaning as in
  3440. :func:`scipy.ndimage.map_coordinates` and may take any integer value
  3441. in the range ``0`` to ``5``, where orders close to ``0`` are faster.
  3442. * If a single int, then that order will be used for all images.
  3443. * If a tuple ``(a, b)``, then a random value will be uniformly
  3444. sampled per image from the interval ``[a, b]``.
  3445. * If a list, then for each image a random value will be sampled
  3446. from that list.
  3447. * If ``imgaug.ALL``, then equivalant to list
  3448. ``[0, 1, 2, 3, 4, 5]``.
  3449. * If ``StochasticParameter``, then that parameter is queried per
  3450. image to sample the order value to use.
  3451. cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  3452. The constant intensity value used to fill in new pixels.
  3453. This value is only used if `mode` is set to ``constant``.
  3454. For standard ``uint8`` images (value range ``0`` to ``255``), this
  3455. value may also should also be in the range ``0`` to ``255``. It may
  3456. be a ``float`` value, even for images with integer dtypes.
  3457. * If this is a single number, then that value will be used
  3458. (e.g. ``0`` results in black pixels).
  3459. * If a tuple ``(a, b)``, then a random value will be uniformly
  3460. sampled per image from the interval ``[a, b]``.
  3461. * If a list, then a random value will be picked from that list per
  3462. image.
  3463. * If ``imgaug.ALL``, a value from the discrete range ``[0..255]``
  3464. will be sampled per image.
  3465. * If a ``StochasticParameter``, a new value will be sampled from
  3466. the parameter per image.
  3467. mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
  3468. Parameter that defines the handling of newly created pixels.
  3469. May take the same values as in :func:`scipy.ndimage.map_coordinates`,
  3470. i.e. ``constant``, ``nearest``, ``reflect`` or ``wrap``.
  3471. * If a single string, then that mode will be used for all images.
  3472. * If a list of strings, then per image a random mode will be picked
  3473. from that list.
  3474. * If ``imgaug.ALL``, then a random mode from all possible modes
  3475. will be picked.
  3476. * If ``StochasticParameter``, then the mode will be sampled from
  3477. that parameter per image, i.e. it must return only the above
  3478. mentioned strings.
  3479. polygon_recoverer : 'auto' or None or imgaug.augmentables.polygons._ConcavePolygonRecoverer, optional
  3480. The class to use to repair invalid polygons.
  3481. If ``"auto"``, a new instance of
  3482. :class`imgaug.augmentables.polygons._ConcavePolygonRecoverer`
  3483. will be created.
  3484. If ``None``, no polygon recoverer will be used.
  3485. If an object, then that object will be used and must provide a
  3486. ``recover_from()`` method, similar to
  3487. :class:`~imgaug.augmentables.polygons._ConcavePolygonRecoverer`.
  3488. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  3489. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  3490. name : None or str, optional
  3491. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  3492. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  3493. Old name for parameter `seed`.
  3494. Its usage will not yet cause a deprecation warning,
  3495. but it is still recommended to use `seed` now.
  3496. Outdated since 0.4.0.
  3497. deterministic : bool, optional
  3498. Deprecated since 0.4.0.
  3499. See method ``to_deterministic()`` for an alternative and for
  3500. details about what the "deterministic mode" actually does.
  3501. Examples
  3502. --------
  3503. >>> import imgaug.augmenters as iaa
  3504. >>> aug = iaa.ElasticTransformation(alpha=50.0, sigma=5.0)
  3505. Apply elastic transformations with a strength/alpha of ``50.0`` and
  3506. smoothness of ``5.0`` to all images.
  3507. >>> aug = iaa.ElasticTransformation(alpha=(0.0, 70.0), sigma=5.0)
  3508. Apply elastic transformations with a strength/alpha that comes
  3509. from the interval ``[0.0, 70.0]`` (randomly picked per image) and
  3510. with a smoothness of ``5.0``.
  3511. """
  3512. NB_NEIGHBOURING_KEYPOINTS = 3
  3513. NEIGHBOURING_KEYPOINTS_DISTANCE = 1.0
  3514. KEYPOINT_AUG_ALPHA_THRESH = 0.05
  3515. # even at high alphas we don't augment keypoints if the sigma is too low,
  3516. # because then the pixel movements are mostly gaussian noise anyways
  3517. KEYPOINT_AUG_SIGMA_THRESH = 1.0
  3518. _MAPPING_MODE_SCIPY_CV2 = {
  3519. "constant": cv2.BORDER_CONSTANT,
  3520. "nearest": cv2.BORDER_REPLICATE,
  3521. "reflect": cv2.BORDER_REFLECT_101,
  3522. "wrap": cv2.BORDER_WRAP
  3523. }
  3524. _MAPPING_ORDER_SCIPY_CV2 = {
  3525. 0: cv2.INTER_NEAREST,
  3526. 1: cv2.INTER_LINEAR,
  3527. 2: cv2.INTER_CUBIC,
  3528. 3: cv2.INTER_CUBIC,
  3529. 4: cv2.INTER_CUBIC,
  3530. 5: cv2.INTER_CUBIC
  3531. }
  3532. def __init__(self, alpha=(0.0, 40.0), sigma=(4.0, 8.0), order=3, cval=0,
  3533. mode="constant",
  3534. polygon_recoverer="auto",
  3535. seed=None, name=None,
  3536. random_state="deprecated", deterministic="deprecated"):
  3537. super(ElasticTransformation, self).__init__(
  3538. seed=seed, name=name,
  3539. random_state=random_state, deterministic=deterministic)
  3540. self.alpha = iap.handle_continuous_param(
  3541. alpha, "alpha", value_range=(0, None), tuple_to_uniform=True,
  3542. list_to_choice=True)
  3543. self.sigma = iap.handle_continuous_param(
  3544. sigma, "sigma", value_range=(0, None), tuple_to_uniform=True,
  3545. list_to_choice=True)
  3546. self.order = self._handle_order_arg(order)
  3547. self.cval = _handle_cval_arg(cval)
  3548. self.mode = self._handle_mode_arg(mode)
  3549. self.polygon_recoverer = polygon_recoverer
  3550. if polygon_recoverer == "auto":
  3551. self.polygon_recoverer = _ConcavePolygonRecoverer()
  3552. # Special order, mode and cval parameters for heatmaps and
  3553. # segmentation maps. These may either be None or a fixed value.
  3554. # Stochastic parameters are currently *not* supported.
  3555. # If set to None, the same values as for images will be used.
  3556. # That is really not recommended for the cval parameter.
  3557. #
  3558. self._order_heatmaps = 3
  3559. self._order_segmentation_maps = 0
  3560. self._mode_heatmaps = "constant"
  3561. self._mode_segmentation_maps = "constant"
  3562. self._cval_heatmaps = 0.0
  3563. self._cval_segmentation_maps = 0
  3564. @classmethod
  3565. def _handle_order_arg(cls, order):
  3566. if order == ia.ALL:
  3567. return iap.Choice([0, 1, 2, 3, 4, 5])
  3568. return iap.handle_discrete_param(
  3569. order, "order", value_range=(0, 5), tuple_to_uniform=True,
  3570. list_to_choice=True, allow_floats=False)
  3571. @classmethod
  3572. def _handle_mode_arg(cls, mode):
  3573. if mode == ia.ALL:
  3574. return iap.Choice(["constant", "nearest", "reflect", "wrap"])
  3575. if ia.is_string(mode):
  3576. return iap.Deterministic(mode)
  3577. if ia.is_iterable(mode):
  3578. assert all([ia.is_string(val) for val in mode]), (
  3579. "Expected mode list to only contain strings, got "
  3580. "types %s." % (
  3581. ", ".join([str(type(val)) for val in mode]),))
  3582. return iap.Choice(mode)
  3583. if isinstance(mode, iap.StochasticParameter):
  3584. return mode
  3585. raise Exception(
  3586. "Expected mode to be imgaug.ALL, a string, a list of strings "
  3587. "or StochasticParameter, got %s." % (type(mode),))
  3588. def _draw_samples(self, nb_images, random_state):
  3589. rss = random_state.duplicate(nb_images+5)
  3590. alphas = self.alpha.draw_samples((nb_images,), random_state=rss[-5])
  3591. sigmas = self.sigma.draw_samples((nb_images,), random_state=rss[-4])
  3592. orders = self.order.draw_samples((nb_images,), random_state=rss[-3])
  3593. cvals = self.cval.draw_samples((nb_images,), random_state=rss[-2])
  3594. modes = self.mode.draw_samples((nb_images,), random_state=rss[-1])
  3595. return _ElasticTransformationSamplingResult(
  3596. rss[0:-5], alphas, sigmas, orders, cvals, modes)
  3597. # Added in 0.4.0.
  3598. def _augment_batch_(self, batch, random_state, parents, hooks):
  3599. # pylint: disable=invalid-name
  3600. if batch.images is not None:
  3601. iadt.gate_dtypes(
  3602. batch.images,
  3603. allowed=["bool",
  3604. "uint8", "uint16", "uint32", "uint64",
  3605. "int8", "int16", "int32", "int64",
  3606. "float16", "float32", "float64"],
  3607. disallowed=["uint128", "uint256",
  3608. "int128", "int256",
  3609. "float96", "float128", "float256"],
  3610. augmenter=self)
  3611. shapes = batch.get_rowwise_shapes()
  3612. samples = self._draw_samples(len(shapes), random_state)
  3613. for i, shape in enumerate(shapes):
  3614. dx, dy = self._generate_shift_maps(
  3615. shape[0:2],
  3616. alpha=samples.alphas[i],
  3617. sigma=samples.sigmas[i],
  3618. random_state=samples.random_states[i])
  3619. if batch.images is not None:
  3620. batch.images[i] = self._augment_image_by_samples(
  3621. batch.images[i], i, samples, dx, dy)
  3622. if batch.heatmaps is not None:
  3623. batch.heatmaps[i] = self._augment_hm_or_sm_by_samples(
  3624. batch.heatmaps[i], i, samples, dx, dy, "arr_0to1",
  3625. self._cval_heatmaps, self._mode_heatmaps,
  3626. self._order_heatmaps)
  3627. if batch.segmentation_maps is not None:
  3628. batch.segmentation_maps[i] = self._augment_hm_or_sm_by_samples(
  3629. batch.segmentation_maps[i], i, samples, dx, dy, "arr",
  3630. self._cval_segmentation_maps, self._mode_segmentation_maps,
  3631. self._order_segmentation_maps)
  3632. if batch.keypoints is not None:
  3633. batch.keypoints[i] = self._augment_kpsoi_by_samples(
  3634. batch.keypoints[i], i, samples, dx, dy)
  3635. if batch.bounding_boxes is not None:
  3636. batch.bounding_boxes[i] = self._augment_bbsoi_by_samples(
  3637. batch.bounding_boxes[i], i, samples, dx, dy)
  3638. if batch.polygons is not None:
  3639. batch.polygons[i] = self._augment_psoi_by_samples(
  3640. batch.polygons[i], i, samples, dx, dy)
  3641. if batch.line_strings is not None:
  3642. batch.line_strings[i] = self._augment_lsoi_by_samples(
  3643. batch.line_strings[i], i, samples, dx, dy)
  3644. return batch
  3645. # Added in 0.4.0.
  3646. def _augment_image_by_samples(self, image, row_idx, samples, dx, dy):
  3647. # pylint: disable=invalid-name
  3648. min_value, _center_value, max_value = \
  3649. iadt.get_value_range_of_dtype(image.dtype)
  3650. cval = max(min(samples.cvals[row_idx], max_value), min_value)
  3651. input_dtype = image.dtype
  3652. if image.dtype.name == "float16":
  3653. image = image.astype(np.float32)
  3654. image_aug = self._map_coordinates(
  3655. image, dx, dy,
  3656. order=samples.orders[row_idx],
  3657. cval=cval,
  3658. mode=samples.modes[row_idx])
  3659. if image.dtype.name != input_dtype.name:
  3660. image_aug = iadt.restore_dtypes_(image_aug, input_dtype)
  3661. return image_aug
  3662. # Added in 0.4.0.
  3663. def _augment_hm_or_sm_by_samples(self, augmentable, row_idx, samples,
  3664. dx, dy, arr_attr_name, cval, mode, order):
  3665. # pylint: disable=invalid-name
  3666. cval = cval if cval is not None else samples.cvals[row_idx]
  3667. mode = mode if mode is not None else samples.modes[row_idx]
  3668. order = order if order is not None else samples.orders[row_idx]
  3669. # note that we do not have to check for zero-sized axes here,
  3670. # because _generate_shift_maps(), _map_coordinates(), .resize()
  3671. # and np.clip() are all known to handle arrays with zero-sized axes
  3672. arr = getattr(augmentable, arr_attr_name)
  3673. if arr.shape[0:2] == augmentable.shape[0:2]:
  3674. arr_warped = self._map_coordinates(
  3675. arr, dx, dy, order=order, cval=cval, mode=mode)
  3676. # interpolation in map_coordinates() can cause some values to
  3677. # be below/above 1.0, so we clip here
  3678. if order >= 3 and isinstance(augmentable, ia.HeatmapsOnImage):
  3679. arr_warped = np.clip(arr_warped, 0.0, 1.0, out=arr_warped)
  3680. setattr(augmentable, arr_attr_name, arr_warped)
  3681. else:
  3682. # Heatmaps/Segmaps do not have the same size as augmented
  3683. # images. This may result in indices of moved pixels being
  3684. # different. To prevent this, we use the same image size as
  3685. # for the base images, but that requires resizing the heatmaps
  3686. # temporarily to the image sizes.
  3687. height_orig, width_orig = arr.shape[0:2]
  3688. augmentable = augmentable.resize(augmentable.shape[0:2])
  3689. arr = getattr(augmentable, arr_attr_name)
  3690. # TODO will it produce similar results to first downscale the
  3691. # shift maps and then remap? That would make the remap
  3692. # step take less operations and would also mean that the
  3693. # heatmaps wouldnt have to be scaled up anymore. It would
  3694. # also simplify the code as this branch could be merged
  3695. # with the one above.
  3696. arr_warped = self._map_coordinates(
  3697. arr, dx, dy, order=order, cval=cval, mode=mode)
  3698. # interpolation in map_coordinates() can cause some values to
  3699. # be below/above 1.0, so we clip here
  3700. if order >= 3 and isinstance(augmentable, ia.HeatmapsOnImage):
  3701. arr_warped = np.clip(arr_warped, 0.0, 1.0, out=arr_warped)
  3702. setattr(augmentable, arr_attr_name, arr_warped)
  3703. augmentable = augmentable.resize((height_orig, width_orig))
  3704. return augmentable
  3705. # Added in 0.4.0.
  3706. def _augment_kpsoi_by_samples(self, kpsoi, row_idx, samples, dx, dy):
  3707. # pylint: disable=misplaced-comparison-constant, invalid-name
  3708. height, width = kpsoi.shape[0:2]
  3709. alpha = samples.alphas[row_idx]
  3710. sigma = samples.sigmas[row_idx]
  3711. # TODO add test for keypoint alignment when keypoints are empty
  3712. # Note: this block must be placed after _generate_shift_maps() to
  3713. # keep samples aligned
  3714. # Note: we should stop for zero-sized axes early here, event though
  3715. # there is a height/width check for each keypoint, because the
  3716. # channel number can also be zero
  3717. image_has_zero_sized_axes = (0 in kpsoi.shape)
  3718. params_below_thresh = (
  3719. alpha <= self.KEYPOINT_AUG_ALPHA_THRESH
  3720. or sigma <= self.KEYPOINT_AUG_SIGMA_THRESH)
  3721. if kpsoi.empty or image_has_zero_sized_axes or params_below_thresh:
  3722. # ElasticTransformation does not change the shape, hence we can
  3723. # skip the below steps
  3724. return kpsoi
  3725. for kp in kpsoi.keypoints:
  3726. within_image_plane = (0 <= kp.x < width and 0 <= kp.y < height)
  3727. if within_image_plane:
  3728. kp_neighborhood = kp.generate_similar_points_manhattan(
  3729. self.NB_NEIGHBOURING_KEYPOINTS,
  3730. self.NEIGHBOURING_KEYPOINTS_DISTANCE,
  3731. return_array=True
  3732. )
  3733. # We can clip here, because we made sure above that the
  3734. # keypoint is inside the image plane. Keypoints at the
  3735. # bottom row or right columns might be rounded outside
  3736. # the image plane, which we prevent here. We reduce
  3737. # neighbours to only those within the image plane as only
  3738. # for such points we know where to move them.
  3739. xx = np.round(kp_neighborhood[:, 0]).astype(np.int32)
  3740. yy = np.round(kp_neighborhood[:, 1]).astype(np.int32)
  3741. inside_image_mask = np.logical_and(
  3742. np.logical_and(0 <= xx, xx < width),
  3743. np.logical_and(0 <= yy, yy < height)
  3744. )
  3745. xx = xx[inside_image_mask]
  3746. yy = yy[inside_image_mask]
  3747. xxyy = np.concatenate(
  3748. [xx[:, np.newaxis], yy[:, np.newaxis]],
  3749. axis=1)
  3750. xxyy_aug = np.copy(xxyy).astype(np.float32)
  3751. xxyy_aug[:, 0] += dx[yy, xx]
  3752. xxyy_aug[:, 1] += dy[yy, xx]
  3753. med = ia.compute_geometric_median(xxyy_aug)
  3754. # uncomment to use average instead of median
  3755. # med = np.average(xxyy_aug, 0)
  3756. kp.x = med[0]
  3757. kp.y = med[1]
  3758. return kpsoi
  3759. # Added in 0.4.0.
  3760. def _augment_psoi_by_samples(self, psoi, row_idx, samples, dx, dy):
  3761. # pylint: disable=invalid-name
  3762. func = functools.partial(self._augment_kpsoi_by_samples,
  3763. row_idx=row_idx, samples=samples, dx=dx, dy=dy)
  3764. return self._apply_to_polygons_as_keypoints(
  3765. psoi, func, recoverer=self.polygon_recoverer)
  3766. # Added in 0.4.0.
  3767. def _augment_lsoi_by_samples(self, lsoi, row_idx, samples, dx, dy):
  3768. # pylint: disable=invalid-name
  3769. func = functools.partial(self._augment_kpsoi_by_samples,
  3770. row_idx=row_idx, samples=samples, dx=dx, dy=dy)
  3771. return self._apply_to_cbaois_as_keypoints(lsoi, func)
  3772. # Added in 0.4.0.
  3773. def _augment_bbsoi_by_samples(self, bbsoi, row_idx, samples, dx, dy):
  3774. # pylint: disable=invalid-name
  3775. func = functools.partial(self._augment_kpsoi_by_samples,
  3776. row_idx=row_idx, samples=samples, dx=dx, dy=dy)
  3777. return self._apply_to_cbaois_as_keypoints(bbsoi, func)
  3778. def get_parameters(self):
  3779. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  3780. return [self.alpha, self.sigma, self.order, self.cval, self.mode]
  3781. @classmethod
  3782. def _generate_shift_maps(cls, shape, alpha, sigma, random_state):
  3783. # pylint: disable=protected-access, invalid-name
  3784. assert len(shape) == 2, ("Expected 2d shape, got %s." % (shape,))
  3785. ksize = blur_lib._compute_gaussian_blur_ksize(sigma)
  3786. ksize = ksize + 1 if ksize % 2 == 0 else ksize
  3787. padding = ksize
  3788. h, w = shape[0:2]
  3789. h_pad = h + 2*padding
  3790. w_pad = w + 2*padding
  3791. # The step of random number generation could be batched, so that
  3792. # random numbers are sampled once for the whole batch. Would get rid
  3793. # of creating many random_states.
  3794. dxdy_unsmoothed = random_state.random((2 * h_pad, w_pad)) * 2 - 1
  3795. dx_unsmoothed = dxdy_unsmoothed[0:h_pad, :]
  3796. dy_unsmoothed = dxdy_unsmoothed[h_pad:, :]
  3797. # TODO could this also work with an average blur? would probably be
  3798. # faster
  3799. dx = blur_lib.blur_gaussian_(dx_unsmoothed, sigma) * alpha
  3800. dy = blur_lib.blur_gaussian_(dy_unsmoothed, sigma) * alpha
  3801. if padding > 0:
  3802. dx = dx[padding:-padding, padding:-padding]
  3803. dy = dy[padding:-padding, padding:-padding]
  3804. return dx, dy
  3805. @classmethod
  3806. def _map_coordinates(cls, image, dx, dy, order=1, cval=0, mode="constant"):
  3807. """Remap pixels in an image according to x/y shift maps.
  3808. **Supported dtypes**:
  3809. if (backend="scipy" and order=0):
  3810. * ``uint8``: yes
  3811. * ``uint16``: yes
  3812. * ``uint32``: yes
  3813. * ``uint64``: no (1)
  3814. * ``int8``: yes
  3815. * ``int16``: yes
  3816. * ``int32``: yes
  3817. * ``int64``: no (2)
  3818. * ``float16``: yes
  3819. * ``float32``: yes
  3820. * ``float64``: yes
  3821. * ``float128``: no (3)
  3822. * ``bool``: yes
  3823. - (1) produces array filled with only 0
  3824. - (2) produces array filled with <min_value> when testing
  3825. with <max_value>
  3826. - (3) causes: 'data type no supported'
  3827. if (backend="scipy" and order>0):
  3828. * ``uint8``: yes (1)
  3829. * ``uint16``: yes (1)
  3830. * ``uint32``: yes (1)
  3831. * ``uint64``: yes (1)
  3832. * ``int8``: yes (1)
  3833. * ``int16``: yes (1)
  3834. * ``int32``: yes (1)
  3835. * ``int64``: yes (1)
  3836. * ``float16``: yes (1)
  3837. * ``float32``: yes (1)
  3838. * ``float64``: yes (1)
  3839. * ``float128``: no (2)
  3840. * ``bool``: yes
  3841. - (1) rather loose test, to avoid having to re-compute the
  3842. interpolation
  3843. - (2) causes: 'data type no supported'
  3844. if (backend="cv2" and order=0):
  3845. * ``uint8``: yes
  3846. * ``uint16``: yes
  3847. * ``uint32``: no (1)
  3848. * ``uint64``: no (2)
  3849. * ``int8``: yes
  3850. * ``int16``: yes
  3851. * ``int32``: yes
  3852. * ``int64``: no (2)
  3853. * ``float16``: yes
  3854. * ``float32``: yes
  3855. * ``float64``: yes
  3856. * ``float128``: no (3)
  3857. * ``bool``: no (4)
  3858. - (1) causes: src data type = 6 is not supported
  3859. - (2) silently converts to int32
  3860. - (3) causes: src data type = 13 is not supported
  3861. - (4) causes: src data type = 0 is not supported
  3862. if (backend="cv2" and order=1):
  3863. * ``uint8``: yes
  3864. * ``uint16``: yes
  3865. * ``uint32``: no (1)
  3866. * ``uint64``: no (2)
  3867. * ``int8``: no (2)
  3868. * ``int16``: no (2)
  3869. * ``int32``: no (2)
  3870. * ``int64``: no (2)
  3871. * ``float16``: yes
  3872. * ``float32``: yes
  3873. * ``float64``: yes
  3874. * ``float128``: no (3)
  3875. * ``bool``: no (4)
  3876. - (1) causes: src data type = 6 is not supported
  3877. - (2) causes: OpenCV(3.4.5) (...)/imgwarp.cpp:1805:
  3878. error: (-215:Assertion failed) ifunc != 0 in function
  3879. 'remap'
  3880. - (3) causes: src data type = 13 is not supported
  3881. - (4) causes: src data type = 0 is not supported
  3882. if (backend="cv2" and order>=2):
  3883. * ``uint8``: yes
  3884. * ``uint16``: yes
  3885. * ``uint32``: no (1)
  3886. * ``uint64``: no (2)
  3887. * ``int8``: no (2)
  3888. * ``int16``: yes
  3889. * ``int32``: no (2)
  3890. * ``int64``: no (2)
  3891. * ``float16``: yes
  3892. * ``float32``: yes
  3893. * ``float64``: yes
  3894. * ``float128``: no (3)
  3895. * ``bool``: no (4)
  3896. - (1) causes: src data type = 6 is not supported
  3897. - (2) causes: OpenCV(3.4.5) (...)/imgwarp.cpp:1805:
  3898. error: (-215:Assertion failed) ifunc != 0 in function
  3899. 'remap'
  3900. - (3) causes: src data type = 13 is not supported
  3901. - (4) causes: src data type = 0 is not supported
  3902. """
  3903. # pylint: disable=invalid-name
  3904. if image.size == 0:
  3905. return np.copy(image)
  3906. if order == 0 and image.dtype.name in ["uint64", "int64"]:
  3907. raise Exception(
  3908. "dtypes uint64 and int64 are only supported in "
  3909. "ElasticTransformation for order=0, got order=%d with "
  3910. "dtype=%s." % (order, image.dtype.name))
  3911. input_dtype = image.dtype
  3912. if image.dtype.name == "bool":
  3913. image = image.astype(np.float32)
  3914. elif order == 1 and image.dtype.name in ["int8", "int16", "int32"]:
  3915. image = image.astype(np.float64)
  3916. elif order >= 2 and image.dtype.name == "int8":
  3917. image = image.astype(np.int16)
  3918. elif order >= 2 and image.dtype.name == "int32":
  3919. image = image.astype(np.float64)
  3920. shrt_max = 32767 # maximum of datatype `short`
  3921. backend = "cv2"
  3922. if order == 0:
  3923. bad_dtype_cv2 = (
  3924. image.dtype.name in [
  3925. "uint32", "uint64",
  3926. "int64",
  3927. "float128",
  3928. "bool"]
  3929. )
  3930. elif order == 1:
  3931. bad_dtype_cv2 = (
  3932. image.dtype.name in [
  3933. "uint32", "uint64",
  3934. "int8", "int16", "int32", "int64",
  3935. "float128",
  3936. "bool"]
  3937. )
  3938. else:
  3939. bad_dtype_cv2 = (
  3940. image.dtype.name in [
  3941. "uint32", "uint64",
  3942. "int8", "int32", "int64",
  3943. "float128",
  3944. "bool"]
  3945. )
  3946. bad_dx_shape_cv2 = (dx.shape[0] >= shrt_max or dx.shape[1] >= shrt_max)
  3947. bad_dy_shape_cv2 = (dy.shape[0] >= shrt_max or dy.shape[1] >= shrt_max)
  3948. if bad_dtype_cv2 or bad_dx_shape_cv2 or bad_dy_shape_cv2:
  3949. backend = "scipy"
  3950. assert image.ndim == 3, (
  3951. "Expected 3-dimensional image, got %d dimensions." % (image.ndim,))
  3952. result = np.copy(image)
  3953. height, width = image.shape[0:2]
  3954. if backend == "scipy":
  3955. h, w = image.shape[0:2]
  3956. y, x = np.meshgrid(
  3957. np.arange(h).astype(np.float32),
  3958. np.arange(w).astype(np.float32),
  3959. indexing="ij")
  3960. x_shifted = x + (-1) * dx
  3961. y_shifted = y + (-1) * dy
  3962. for c in sm.xrange(image.shape[2]):
  3963. remapped_flat = ndimage.interpolation.map_coordinates(
  3964. image[..., c],
  3965. (y_shifted.flatten(), x_shifted.flatten()),
  3966. order=order,
  3967. cval=cval,
  3968. mode=mode
  3969. )
  3970. remapped = remapped_flat.reshape((height, width))
  3971. result[..., c] = remapped
  3972. else:
  3973. h, w, nb_channels = image.shape
  3974. y, x = np.meshgrid(
  3975. np.arange(h).astype(np.float32),
  3976. np.arange(w).astype(np.float32),
  3977. indexing="ij")
  3978. x_shifted = x + (-1) * dx
  3979. y_shifted = y + (-1) * dy
  3980. if image.dtype.kind == "f":
  3981. cval = float(cval)
  3982. else:
  3983. cval = int(cval)
  3984. border_mode = cls._MAPPING_MODE_SCIPY_CV2[mode]
  3985. interpolation = cls._MAPPING_ORDER_SCIPY_CV2[order]
  3986. is_nearest_neighbour = (interpolation == cv2.INTER_NEAREST)
  3987. map1, map2 = cv2.convertMaps(
  3988. x_shifted, y_shifted, cv2.CV_16SC2,
  3989. nninterpolation=is_nearest_neighbour)
  3990. # remap only supports up to 4 channels
  3991. if nb_channels <= 4:
  3992. result = cv2.remap(
  3993. _normalize_cv2_input_arr_(image),
  3994. map1, map2, interpolation=interpolation,
  3995. borderMode=border_mode, borderValue=(cval, cval, cval))
  3996. if image.ndim == 3 and result.ndim == 2:
  3997. result = result[..., np.newaxis]
  3998. else:
  3999. current_chan_idx = 0
  4000. result = []
  4001. while current_chan_idx < nb_channels:
  4002. channels = image[..., current_chan_idx:current_chan_idx+4]
  4003. result_c = cv2.remap(
  4004. _normalize_cv2_input_arr_(channels),
  4005. map1, map2, interpolation=interpolation,
  4006. borderMode=border_mode, borderValue=(cval, cval, cval))
  4007. if result_c.ndim == 2:
  4008. result_c = result_c[..., np.newaxis]
  4009. result.append(result_c)
  4010. current_chan_idx += 4
  4011. result = np.concatenate(result, axis=2)
  4012. if result.dtype.name != input_dtype.name:
  4013. result = iadt.restore_dtypes_(result, input_dtype)
  4014. return result
  4015. class Rot90(meta.Augmenter):
  4016. """
  4017. Rotate images clockwise by multiples of 90 degrees.
  4018. This could also be achieved using ``Affine``, but ``Rot90`` is
  4019. significantly more efficient.
  4020. **Supported dtypes**:
  4021. if (keep_size=False):
  4022. * ``uint8``: yes; fully tested
  4023. * ``uint16``: yes; tested
  4024. * ``uint32``: yes; tested
  4025. * ``uint64``: yes; tested
  4026. * ``int8``: yes; tested
  4027. * ``int16``: yes; tested
  4028. * ``int32``: yes; tested
  4029. * ``int64``: yes; tested
  4030. * ``float16``: yes; tested
  4031. * ``float32``: yes; tested
  4032. * ``float64``: yes; tested
  4033. * ``float128``: yes; tested
  4034. * ``bool``: yes; tested
  4035. if (keep_size=True):
  4036. minimum of (
  4037. ``imgaug.augmenters.geometric.Rot90(keep_size=False)``,
  4038. :func:`~imgaug.imgaug.imresize_many_images`
  4039. )
  4040. Parameters
  4041. ----------
  4042. k : int or list of int or tuple of int or imaug.ALL or imgaug.parameters.StochasticParameter, optional
  4043. How often to rotate clockwise by 90 degrees.
  4044. * If a single ``int``, then that value will be used for all images.
  4045. * If a tuple ``(a, b)``, then a random value will be uniformly
  4046. sampled per image from the discrete interval ``[a..b]``.
  4047. * If a list, then for each image a random value will be sampled
  4048. from that list.
  4049. * If ``imgaug.ALL``, then equivalant to list ``[0, 1, 2, 3]``.
  4050. * If ``StochasticParameter``, then that parameter is queried per
  4051. image to sample the value to use.
  4052. keep_size : bool, optional
  4053. After rotation by an odd-valued `k` (e.g. 1 or 3), the resulting image
  4054. may have a different height/width than the original image.
  4055. If this parameter is set to ``True``, then the rotated
  4056. image will be resized to the input image's size. Note that this might
  4057. also cause the augmented image to look distorted.
  4058. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  4059. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  4060. name : None or str, optional
  4061. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  4062. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  4063. Old name for parameter `seed`.
  4064. Its usage will not yet cause a deprecation warning,
  4065. but it is still recommended to use `seed` now.
  4066. Outdated since 0.4.0.
  4067. deterministic : bool, optional
  4068. Deprecated since 0.4.0.
  4069. See method ``to_deterministic()`` for an alternative and for
  4070. details about what the "deterministic mode" actually does.
  4071. Examples
  4072. --------
  4073. >>> import imgaug.augmenters as iaa
  4074. >>> aug = iaa.Rot90(1)
  4075. Rotate all images by 90 degrees.
  4076. Resize these images afterwards to keep the size that they had before
  4077. augmentation.
  4078. This may cause the images to look distorted.
  4079. >>> aug = iaa.Rot90([1, 3])
  4080. Rotate all images by 90 or 270 degrees.
  4081. Resize these images afterwards to keep the size that they had before
  4082. augmentation.
  4083. This may cause the images to look distorted.
  4084. >>> aug = iaa.Rot90((1, 3))
  4085. Rotate all images by 90, 180 or 270 degrees.
  4086. Resize these images afterwards to keep the size that they had before
  4087. augmentation.
  4088. This may cause the images to look distorted.
  4089. >>> aug = iaa.Rot90((1, 3), keep_size=False)
  4090. Rotate all images by 90, 180 or 270 degrees.
  4091. Does not resize to the original image size afterwards, i.e. each image's
  4092. size may change.
  4093. """
  4094. def __init__(self, k=1, keep_size=True,
  4095. seed=None, name=None,
  4096. random_state="deprecated", deterministic="deprecated"):
  4097. super(Rot90, self).__init__(
  4098. seed=seed, name=name,
  4099. random_state=random_state, deterministic=deterministic)
  4100. if k == ia.ALL:
  4101. k = [0, 1, 2, 3]
  4102. self.k = iap.handle_discrete_param(
  4103. k, "k", value_range=None, tuple_to_uniform=True,
  4104. list_to_choice=True, allow_floats=False)
  4105. self.keep_size = keep_size
  4106. def _draw_samples(self, nb_images, random_state):
  4107. return self.k.draw_samples((nb_images,), random_state=random_state)
  4108. # Added in 0.4.0.
  4109. def _augment_batch_(self, batch, random_state, parents, hooks):
  4110. # pylint: disable=invalid-name
  4111. ks = self._draw_samples(batch.nb_rows, random_state)
  4112. if batch.images is not None:
  4113. batch.images = self._augment_arrays_by_samples(
  4114. batch.images, ks, self.keep_size, ia.imresize_single_image)
  4115. if batch.heatmaps is not None:
  4116. batch.heatmaps = self._augment_maps_by_samples(
  4117. batch.heatmaps, "arr_0to1", ks)
  4118. if batch.segmentation_maps is not None:
  4119. batch.segmentation_maps = self._augment_maps_by_samples(
  4120. batch.segmentation_maps, "arr", ks)
  4121. for augm_name in ["keypoints", "bounding_boxes", "polygons",
  4122. "line_strings"]:
  4123. augm_value = getattr(batch, augm_name)
  4124. if augm_value is not None:
  4125. func = functools.partial(
  4126. self._augment_keypoints_by_samples,
  4127. ks=ks)
  4128. cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
  4129. setattr(batch, augm_name, cbaois)
  4130. return batch
  4131. @classmethod
  4132. def _augment_arrays_by_samples(cls, arrs, ks, keep_size, resize_func):
  4133. # pylint: disable=invalid-name
  4134. input_was_array = ia.is_np_array(arrs)
  4135. input_dtype = arrs.dtype if input_was_array else None
  4136. arrs_aug = []
  4137. for arr, k_i in zip(arrs, ks):
  4138. # adding axes here rotates clock-wise instead of ccw
  4139. arr_aug = np.rot90(arr, k_i, axes=(1, 0))
  4140. do_resize = (
  4141. keep_size
  4142. and arr.shape != arr_aug.shape
  4143. and resize_func is not None)
  4144. if do_resize:
  4145. arr_aug = resize_func(arr_aug, arr.shape[0:2])
  4146. arrs_aug.append(arr_aug)
  4147. if keep_size and input_was_array:
  4148. n_shapes = len({arr.shape for arr in arrs_aug})
  4149. if n_shapes == 1:
  4150. arrs_aug = np.array(arrs_aug, dtype=input_dtype)
  4151. return arrs_aug
  4152. # Added in 0.4.0.
  4153. def _augment_maps_by_samples(self, augmentables, arr_attr_name, ks):
  4154. # pylint: disable=invalid-name
  4155. arrs = [getattr(map_i, arr_attr_name) for map_i in augmentables]
  4156. arrs_aug = self._augment_arrays_by_samples(
  4157. arrs, ks, self.keep_size, None)
  4158. maps_aug = []
  4159. gen = zip(augmentables, arrs, arrs_aug, ks)
  4160. for augmentable_i, arr, arr_aug, k_i in gen:
  4161. shape_orig = arr.shape
  4162. setattr(augmentable_i, arr_attr_name, arr_aug)
  4163. if self.keep_size:
  4164. augmentable_i = augmentable_i.resize(shape_orig[0:2])
  4165. elif k_i % 2 == 1:
  4166. h, w = augmentable_i.shape[0:2]
  4167. augmentable_i.shape = tuple(
  4168. [w, h] + list(augmentable_i.shape[2:]))
  4169. else:
  4170. # keep_size was False, but rotated by a multiple of 2,
  4171. # hence height and width do not change
  4172. pass
  4173. maps_aug.append(augmentable_i)
  4174. return maps_aug
  4175. # Added in 0.4.0.
  4176. def _augment_keypoints_by_samples(self, keypoints_on_images, ks):
  4177. # pylint: disable=invalid-name
  4178. result = []
  4179. for kpsoi_i, k_i in zip(keypoints_on_images, ks):
  4180. shape_orig = kpsoi_i.shape
  4181. if (k_i % 4) == 0:
  4182. result.append(kpsoi_i)
  4183. else:
  4184. k_i = int(k_i) % 4 # this is also correct when k_i is negative
  4185. h, w = kpsoi_i.shape[0:2]
  4186. h_aug, w_aug = (h, w) if (k_i % 2) == 0 else (w, h)
  4187. for kp in kpsoi_i.keypoints:
  4188. y, x = kp.y, kp.x
  4189. yr, xr = y, x
  4190. wr, hr = w, h
  4191. for _ in sm.xrange(k_i):
  4192. # for int coordinates this would instead be
  4193. # xr, yr = (hr - 1) - yr, xr
  4194. # here we assume that coordinates are always
  4195. # subpixel-accurate
  4196. xr, yr = hr - yr, xr
  4197. wr, hr = hr, wr
  4198. kp.x = xr
  4199. kp.y = yr
  4200. shape_aug = tuple([h_aug, w_aug] + list(kpsoi_i.shape[2:]))
  4201. kpsoi_i.shape = shape_aug
  4202. if self.keep_size and (h, w) != (h_aug, w_aug):
  4203. kpsoi_i = kpsoi_i.on_(shape_orig)
  4204. kpsoi_i.shape = shape_orig
  4205. result.append(kpsoi_i)
  4206. return result
  4207. def get_parameters(self):
  4208. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  4209. return [self.k, self.keep_size]
  4210. # TODO semipolar
  4211. class WithPolarWarping(meta.Augmenter):
  4212. """Augmenter that applies other augmenters in a polar-transformed space.
  4213. This augmenter first transforms an image into a polar representation,
  4214. then applies its child augmenter, then transforms back to cartesian
  4215. space. The polar representation is still in the image's input dtype
  4216. (i.e. ``uint8`` stays ``uint8``) and can be visualized. It can be thought
  4217. of as an "unrolled" version of the image, where previously circular lines
  4218. appear straight. Hence, applying child augmenters in that space can lead
  4219. to circular effects. E.g. replacing rectangular pixel areas in the polar
  4220. representation with black pixels will lead to curved black areas in
  4221. the cartesian result.
  4222. This augmenter can create new pixels in the image. It will fill these
  4223. with black pixels. For segmentation maps it will fill with class
  4224. id ``0``. For heatmaps it will fill with ``0.0``.
  4225. This augmenter is limited to arrays with a height and/or width of
  4226. ``32767`` or less.
  4227. .. warning::
  4228. When augmenting coordinates in polar representation, it is possible
  4229. that these are shifted outside of the polar image, but are inside the
  4230. image plane after transforming back to cartesian representation,
  4231. usually on newly created pixels (i.e. black backgrounds).
  4232. These coordinates are currently not removed. It is recommended to
  4233. not use very strong child transformations when also augmenting
  4234. coordinate-based augmentables.
  4235. .. warning::
  4236. For bounding boxes, this augmenter suffers from the same problem as
  4237. affine rotations applied to bounding boxes, i.e. the resulting
  4238. bounding boxes can have unintuitive (seemingly wrong) appearance.
  4239. This is due to coordinates being "rotated" that are inside the
  4240. bounding box, but do not fall on the object and actually are
  4241. background.
  4242. It is recommended to use this augmenter with caution when augmenting
  4243. bounding boxes.
  4244. .. warning::
  4245. For polygons, this augmenter should not be combined with
  4246. augmenters that perform automatic polygon recovery for invalid
  4247. polygons, as the polygons will frequently appear broken in polar
  4248. representation and their "fixed" version will be very broken in
  4249. cartesian representation. Augmenters that perform such polygon
  4250. recovery are currently ``PerspectiveTransform``, ``PiecewiseAffine``
  4251. and ``ElasticTransformation``.
  4252. Added in 0.4.0.
  4253. **Supported dtypes**:
  4254. * ``uint8``: yes; fully tested
  4255. * ``uint16``: yes; tested
  4256. * ``uint32``: no (1)
  4257. * ``uint64``: no (2)
  4258. * ``int8``: yes; tested
  4259. * ``int16``: yes; tested
  4260. * ``int32``: yes; tested
  4261. * ``int64``: no (2)
  4262. * ``float16``: yes; tested (3)
  4263. * ``float32``: yes; tested
  4264. * ``float64``: yes; tested
  4265. * ``float128``: no (1)
  4266. * ``bool``: yes; tested (4)
  4267. - (1) OpenCV produces error
  4268. ``TypeError: Expected cv::UMat for argument 'src'``
  4269. - (2) OpenCV produces array of nothing but zeros.
  4270. - (3) Mapepd to ``float32``.
  4271. - (4) Mapped to ``uint8``.
  4272. Parameters
  4273. ----------
  4274. children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
  4275. One or more augmenters to apply to images after they were transformed
  4276. to polar representation.
  4277. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  4278. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  4279. name : None or str, optional
  4280. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  4281. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  4282. Old name for parameter `seed`.
  4283. Its usage will not yet cause a deprecation warning,
  4284. but it is still recommended to use `seed` now.
  4285. Outdated since 0.4.0.
  4286. deterministic : bool, optional
  4287. Deprecated since 0.4.0.
  4288. See method ``to_deterministic()`` for an alternative and for
  4289. details about what the "deterministic mode" actually does.
  4290. Examples
  4291. --------
  4292. >>> import imgaug.augmenters as iaa
  4293. >>> aug = iaa.WithPolarWarping(iaa.CropAndPad(percent=(-0.1, 0.1)))
  4294. Apply cropping and padding in polar representation, then warp back to
  4295. cartesian representation.
  4296. >>> aug = iaa.WithPolarWarping(
  4297. >>> iaa.Affine(
  4298. >>> translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)},
  4299. >>> rotate=(-35, 35),
  4300. >>> scale=(0.8, 1.2),
  4301. >>> shear={"x": (-15, 15), "y": (-15, 15)}
  4302. >>> )
  4303. >>> )
  4304. Apply affine transformations in polar representation.
  4305. >>> aug = iaa.WithPolarWarping(iaa.AveragePooling((2, 8)))
  4306. Apply average pooling in polar representation. This leads to circular
  4307. bins.
  4308. """
  4309. # Added in 0.4.0.
  4310. def __init__(self, children,
  4311. seed=None, name=None,
  4312. random_state="deprecated", deterministic="deprecated"):
  4313. super(WithPolarWarping, self).__init__(
  4314. seed=seed, name=name,
  4315. random_state=random_state, deterministic=deterministic)
  4316. self.children = meta.handle_children_list(children, self.name, "then")
  4317. # Added in 0.4.0.
  4318. def _augment_batch_(self, batch, random_state, parents, hooks):
  4319. if batch.images is not None:
  4320. iadt.gate_dtypes(
  4321. batch.images,
  4322. allowed=["bool",
  4323. "uint8", "uint16",
  4324. "int8", "int16", "int32",
  4325. "float16", "float32", "float64"],
  4326. disallowed=["uint32", "uint64", "uint128", "uint256",
  4327. "int64", "int128", "int256",
  4328. "float96", "float128", "float256"],
  4329. augmenter=self)
  4330. with batch.propagation_hooks_ctx(self, hooks, parents):
  4331. batch, inv_data_bbs = self._convert_bbs_to_polygons_(batch)
  4332. inv_data = {}
  4333. for column in batch.columns:
  4334. func = getattr(self, "_warp_%s_" % (column.name,))
  4335. col_aug, inv_data_col = func(column.value)
  4336. setattr(batch, column.attr_name, col_aug)
  4337. inv_data[column.name] = inv_data_col
  4338. batch = self.children.augment_batch_(batch,
  4339. parents=parents + [self],
  4340. hooks=hooks)
  4341. for column in batch.columns:
  4342. func = getattr(self, "_invert_warp_%s_" % (column.name,))
  4343. col_unaug = func(column.value, inv_data[column.name])
  4344. setattr(batch, column.attr_name, col_unaug)
  4345. batch = self._invert_convert_bbs_to_polygons_(batch, inv_data_bbs)
  4346. return batch
  4347. # Added in 0.4.0.
  4348. @classmethod
  4349. def _convert_bbs_to_polygons_(cls, batch):
  4350. batch_contained_polygons = batch.polygons is not None
  4351. if batch.bounding_boxes is None:
  4352. return batch, (False, batch_contained_polygons)
  4353. psois = [bbsoi.to_polygons_on_image() for bbsoi in batch.bounding_boxes]
  4354. psois = [psoi.subdivide_(2) for psoi in psois]
  4355. # Mark Polygons that are really Bounding Boxes
  4356. for psoi in psois:
  4357. for polygon in psoi.polygons:
  4358. if polygon.label is None:
  4359. polygon.label = "$$IMGAUG_BB_AS_POLYGON"
  4360. else:
  4361. polygon.label = polygon.label + ";$$IMGAUG_BB_AS_POLYGON"
  4362. # Merge Fake-Polygons into existing Polygons
  4363. if batch.polygons is None:
  4364. batch.polygons = psois
  4365. else:
  4366. for psoi, bbs_as_psoi in zip(batch.polygons, psois):
  4367. assert psoi.shape == bbs_as_psoi.shape, (
  4368. "Expected polygons and bounding boxes to have the same "
  4369. ".shape value, got %s and %s." % (psoi.shape,
  4370. bbs_as_psoi.shape))
  4371. psoi.polygons.extend(bbs_as_psoi.polygons)
  4372. batch.bounding_boxes = None
  4373. return batch, (True, batch_contained_polygons)
  4374. # Added in 0.4.0.
  4375. @classmethod
  4376. def _invert_convert_bbs_to_polygons_(cls, batch, inv_data):
  4377. batch_contained_bbs, batch_contained_polygons = inv_data
  4378. if not batch_contained_bbs:
  4379. return batch
  4380. bbsois = []
  4381. for psoi in batch.polygons:
  4382. polygons = []
  4383. bbs = []
  4384. for polygon in psoi.polygons:
  4385. is_bb = False
  4386. if polygon.label is None:
  4387. is_bb = False
  4388. elif polygon.label == "$$IMGAUG_BB_AS_POLYGON":
  4389. polygon.label = None
  4390. is_bb = True
  4391. elif polygon.label.endswith(";$$IMGAUG_BB_AS_POLYGON"):
  4392. polygon.label = \
  4393. polygon.label[:-len(";$$IMGAUG_BB_AS_POLYGON")]
  4394. is_bb = True
  4395. if is_bb:
  4396. bbs.append(polygon.to_bounding_box())
  4397. else:
  4398. polygons.append(polygon)
  4399. psoi.polygons = polygons
  4400. bbsoi = ia.BoundingBoxesOnImage(bbs, shape=psoi.shape)
  4401. bbsois.append(bbsoi)
  4402. batch.bounding_boxes = bbsois
  4403. if not batch_contained_polygons:
  4404. batch.polygons = None
  4405. return batch
  4406. # Added in 0.4.0.
  4407. @classmethod
  4408. def _warp_images_(cls, images):
  4409. return cls._warp_arrays(images, False)
  4410. # Added in 0.4.0.
  4411. @classmethod
  4412. def _invert_warp_images_(cls, images_warped, inv_data):
  4413. return cls._invert_warp_arrays(images_warped, False, inv_data)
  4414. # Added in 0.4.0.
  4415. @classmethod
  4416. def _warp_heatmaps_(cls, heatmaps):
  4417. return cls._warp_maps_(heatmaps, "arr_0to1", False)
  4418. # Added in 0.4.0.
  4419. @classmethod
  4420. def _invert_warp_heatmaps_(cls, heatmaps_warped, inv_data):
  4421. return cls._invert_warp_maps_(heatmaps_warped, "arr_0to1", False,
  4422. inv_data)
  4423. # Added in 0.4.0.
  4424. @classmethod
  4425. def _warp_segmentation_maps_(cls, segmentation_maps):
  4426. return cls._warp_maps_(segmentation_maps, "arr", True)
  4427. # Added in 0.4.0.
  4428. @classmethod
  4429. def _invert_warp_segmentation_maps_(cls, segmentation_maps_warped,
  4430. inv_data):
  4431. return cls._invert_warp_maps_(segmentation_maps_warped, "arr", True,
  4432. inv_data)
  4433. # Added in 0.4.0.
  4434. @classmethod
  4435. def _warp_keypoints_(cls, kpsois):
  4436. return cls._warp_cbaois_(kpsois)
  4437. # Added in 0.4.0.
  4438. @classmethod
  4439. def _invert_warp_keypoints_(cls, kpsois_warped, image_shapes_orig):
  4440. return cls._invert_warp_cbaois_(kpsois_warped, image_shapes_orig)
  4441. # Added in 0.4.0.
  4442. @classmethod
  4443. def _warp_bounding_boxes_(cls, bbsois): # pylint: disable=useless-return
  4444. assert bbsois is None, ("Expected BBs to have been converted "
  4445. "to polygons.")
  4446. return None
  4447. # Added in 0.4.0.
  4448. @classmethod
  4449. def _invert_warp_bounding_boxes_(cls, bbsois_warped, _image_shapes_orig): # pylint: disable=useless-return
  4450. assert bbsois_warped is None, ("Expected BBs to have been converted "
  4451. "to polygons.")
  4452. return None
  4453. # Added in 0.4.0.
  4454. @classmethod
  4455. def _warp_polygons_(cls, psois):
  4456. return cls._warp_cbaois_(psois)
  4457. # Added in 0.4.0.
  4458. @classmethod
  4459. def _invert_warp_polygons_(cls, psois_warped, image_shapes_orig):
  4460. return cls._invert_warp_cbaois_(psois_warped, image_shapes_orig)
  4461. # Added in 0.4.0.
  4462. @classmethod
  4463. def _warp_line_strings_(cls, lsois):
  4464. return cls._warp_cbaois_(lsois)
  4465. # Added in 0.4.0.
  4466. @classmethod
  4467. def _invert_warp_line_strings_(cls, lsois_warped, image_shapes_orig):
  4468. return cls._invert_warp_cbaois_(lsois_warped, image_shapes_orig)
  4469. # Added in 0.4.0.
  4470. @classmethod
  4471. def _warp_arrays(cls, arrays, interpolation_nearest):
  4472. if arrays is None:
  4473. return None, None
  4474. flags = cv2.WARP_FILL_OUTLIERS + cv2.WARP_POLAR_LINEAR
  4475. if interpolation_nearest:
  4476. flags += cv2.INTER_NEAREST
  4477. arrays_warped = []
  4478. shapes_orig = []
  4479. for arr in arrays:
  4480. if 0 in arr.shape:
  4481. arrays_warped.append(arr)
  4482. shapes_orig.append(arr.shape)
  4483. continue
  4484. input_dtype = arr.dtype.name
  4485. if input_dtype == "bool":
  4486. arr = arr.astype(np.uint8) * 255
  4487. elif input_dtype == "float16":
  4488. arr = arr.astype(np.float32)
  4489. height, width = arr.shape[0:2]
  4490. # remap limitation, see docs for warpPolar()
  4491. assert height <= 32767 and width <= 32767, (
  4492. "WithPolarWarping._warp_arrays() can currently only handle "
  4493. "arrays with axis sizes below 32767, but got shape %s. This "
  4494. "is an OpenCV limitation." % (arr.shape,))
  4495. dest_size = (0, 0)
  4496. center_xy = (width/2, height/2)
  4497. max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
  4498. if arr.ndim == 3 and arr.shape[-1] > 512:
  4499. arr_warped = np.stack(
  4500. [cv2.warpPolar(_normalize_cv2_input_arr_(arr[..., c_idx]),
  4501. dest_size, center_xy, max_radius, flags)
  4502. for c_idx in np.arange(arr.shape[-1])],
  4503. axis=-1)
  4504. else:
  4505. arr_warped = cv2.warpPolar(_normalize_cv2_input_arr_(arr),
  4506. dest_size, center_xy, max_radius,
  4507. flags)
  4508. if arr_warped.ndim == 2 and arr.ndim == 3:
  4509. arr_warped = arr_warped[:, :, np.newaxis]
  4510. if input_dtype == "bool":
  4511. arr_warped = (arr_warped > 128)
  4512. elif input_dtype == "float16":
  4513. arr_warped = arr_warped.astype(np.float16)
  4514. arrays_warped.append(arr_warped)
  4515. shapes_orig.append(arr.shape)
  4516. return arrays_warped, shapes_orig
  4517. # Added in 0.4.0.
  4518. @classmethod
  4519. def _invert_warp_arrays(cls, arrays_warped, interpolation_nearest,
  4520. inv_data):
  4521. shapes_orig = inv_data
  4522. if arrays_warped is None:
  4523. return None
  4524. flags = (cv2.WARP_FILL_OUTLIERS + cv2.WARP_POLAR_LINEAR
  4525. + cv2.WARP_INVERSE_MAP)
  4526. if interpolation_nearest:
  4527. flags += cv2.INTER_NEAREST
  4528. # TODO this does per iteration almost the same as _warp_arrays()
  4529. # make DRY
  4530. arrays_inv = []
  4531. for arr_warped, shape_orig in zip(arrays_warped, shapes_orig):
  4532. if 0 in arr_warped.shape:
  4533. arrays_inv.append(arr_warped)
  4534. continue
  4535. input_dtype = arr_warped.dtype.name
  4536. if input_dtype == "bool":
  4537. arr_warped = arr_warped.astype(np.uint8) * 255
  4538. elif input_dtype == "float16":
  4539. arr_warped = arr_warped.astype(np.float32)
  4540. height, width = shape_orig[0:2]
  4541. # remap limitation, see docs for warpPolar()
  4542. assert (arr_warped.shape[0] <= 32767
  4543. and arr_warped.shape[1] <= 32767), (
  4544. "WithPolarWarping._warp_arrays() can currently only "
  4545. "handle arrays with axis sizes below 32767, but got "
  4546. "shape %s. This is an OpenCV limitation." % (
  4547. arr_warped.shape,))
  4548. dest_size = (width, height)
  4549. center_xy = (width/2, height/2)
  4550. max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
  4551. if arr_warped.ndim == 3 and arr_warped.shape[-1] > 512:
  4552. arr_inv = np.stack(
  4553. [cv2.warpPolar(
  4554. _normalize_cv2_input_arr_(arr_warped[..., c_idx]),
  4555. dest_size, center_xy, max_radius, flags)
  4556. for c_idx in np.arange(arr_warped.shape[-1])],
  4557. axis=-1)
  4558. else:
  4559. arr_inv = cv2.warpPolar(
  4560. _normalize_cv2_input_arr_(arr_warped),
  4561. dest_size, center_xy, max_radius, flags)
  4562. if arr_inv.ndim == 2 and arr_warped.ndim == 3:
  4563. arr_inv = arr_inv[:, :, np.newaxis]
  4564. if input_dtype == "bool":
  4565. arr_inv = (arr_inv > 128)
  4566. elif input_dtype == "float16":
  4567. arr_inv = arr_inv.astype(np.float16)
  4568. arrays_inv.append(arr_inv)
  4569. return arrays_inv
  4570. # Added in 0.4.0.
  4571. @classmethod
  4572. def _warp_maps_(cls, maps, arr_attr_name, interpolation_nearest):
  4573. if maps is None:
  4574. return None, None
  4575. skipped = [False] * len(maps)
  4576. arrays = []
  4577. shapes_imgs_orig = []
  4578. for i, map_i in enumerate(maps):
  4579. if 0 in map_i.shape:
  4580. skipped[i] = True
  4581. arrays.append(np.zeros((0, 0), dtype=np.int32))
  4582. shapes_imgs_orig.append(map_i.shape)
  4583. else:
  4584. arrays.append(getattr(map_i, arr_attr_name))
  4585. shapes_imgs_orig.append(map_i.shape)
  4586. arrays_warped, warparr_inv_data = cls._warp_arrays(
  4587. arrays, interpolation_nearest)
  4588. shapes_imgs_warped = cls._warp_shape_tuples(shapes_imgs_orig)
  4589. for i, map_i in enumerate(maps):
  4590. if not skipped[i]:
  4591. map_i.shape = shapes_imgs_warped[i]
  4592. setattr(map_i, arr_attr_name, arrays_warped[i])
  4593. return maps, (shapes_imgs_orig, warparr_inv_data, skipped)
  4594. # Added in 0.4.0.
  4595. @classmethod
  4596. def _invert_warp_maps_(cls, maps_warped, arr_attr_name,
  4597. interpolation_nearest, invert_data):
  4598. if maps_warped is None:
  4599. return None
  4600. shapes_imgs_orig, warparr_inv_data, skipped = invert_data
  4601. arrays_warped = []
  4602. for i, map_warped in enumerate(maps_warped):
  4603. if skipped[i]:
  4604. arrays_warped.append(np.zeros((0, 0), dtype=np.int32))
  4605. else:
  4606. arrays_warped.append(getattr(map_warped, arr_attr_name))
  4607. arrays_inv = cls._invert_warp_arrays(arrays_warped,
  4608. interpolation_nearest,
  4609. warparr_inv_data)
  4610. for i, map_i in enumerate(maps_warped):
  4611. if not skipped[i]:
  4612. map_i.shape = shapes_imgs_orig[i]
  4613. setattr(map_i, arr_attr_name, arrays_inv[i])
  4614. return maps_warped
  4615. # Added in 0.4.0.
  4616. @classmethod
  4617. def _warp_coords(cls, coords, image_shapes):
  4618. if coords is None:
  4619. return None, None
  4620. image_shapes_warped = cls._warp_shape_tuples(image_shapes)
  4621. flags = cv2.WARP_POLAR_LINEAR
  4622. coords_warped = []
  4623. for coords_i, shape, shape_warped in zip(coords, image_shapes,
  4624. image_shapes_warped):
  4625. if 0 in shape:
  4626. coords_warped.append(coords_i)
  4627. continue
  4628. height, width = shape[0:2]
  4629. dest_size = (shape_warped[1], shape_warped[0])
  4630. center_xy = (width/2, height/2)
  4631. max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
  4632. coords_i_warped = cls.warpPolarCoords(
  4633. coords_i, dest_size, center_xy, max_radius, flags)
  4634. coords_warped.append(coords_i_warped)
  4635. return coords_warped, image_shapes
  4636. # Added in 0.4.0.
  4637. @classmethod
  4638. def _invert_warp_coords(cls, coords_warped, image_shapes_after_aug,
  4639. inv_data):
  4640. image_shapes_orig = inv_data
  4641. if coords_warped is None:
  4642. return None
  4643. flags = cv2.WARP_POLAR_LINEAR + cv2.WARP_INVERSE_MAP
  4644. coords_inv = []
  4645. gen = enumerate(zip(coords_warped, image_shapes_orig))
  4646. for i, (coords_i_warped, shape_orig) in gen:
  4647. if 0 in shape_orig:
  4648. coords_inv.append(coords_i_warped)
  4649. continue
  4650. shape_warped = image_shapes_after_aug[i]
  4651. height, width = shape_orig[0:2]
  4652. dest_size = (shape_warped[1], shape_warped[0])
  4653. center_xy = (width/2, height/2)
  4654. max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
  4655. coords_i_inv = cls.warpPolarCoords(coords_i_warped,
  4656. dest_size, center_xy,
  4657. max_radius, flags)
  4658. coords_inv.append(coords_i_inv)
  4659. return coords_inv
  4660. # Added in 0.4.0.
  4661. @classmethod
  4662. def _warp_cbaois_(cls, cbaois):
  4663. if cbaois is None:
  4664. return None, None
  4665. coords = [cbaoi.to_xy_array() for cbaoi in cbaois]
  4666. image_shapes = [cbaoi.shape for cbaoi in cbaois]
  4667. image_shapes_warped = cls._warp_shape_tuples(image_shapes)
  4668. coords_warped, inv_data = cls._warp_coords(coords, image_shapes)
  4669. for i, (cbaoi, coords_i_warped) in enumerate(zip(cbaois,
  4670. coords_warped)):
  4671. cbaoi = cbaoi.fill_from_xy_array_(coords_i_warped)
  4672. cbaoi.shape = image_shapes_warped[i]
  4673. cbaois[i] = cbaoi
  4674. return cbaois, inv_data
  4675. # Added in 0.4.0.
  4676. @classmethod
  4677. def _invert_warp_cbaois_(cls, cbaois_warped, image_shapes_orig):
  4678. if cbaois_warped is None:
  4679. return None
  4680. coords = [cbaoi.to_xy_array() for cbaoi in cbaois_warped]
  4681. image_shapes_after_aug = [cbaoi.shape for cbaoi in cbaois_warped]
  4682. coords_warped = cls._invert_warp_coords(coords, image_shapes_after_aug,
  4683. image_shapes_orig)
  4684. cbaois = cbaois_warped
  4685. for i, (cbaoi, coords_i_warped) in enumerate(zip(cbaois,
  4686. coords_warped)):
  4687. cbaoi = cbaoi.fill_from_xy_array_(coords_i_warped)
  4688. cbaoi.shape = image_shapes_orig[i]
  4689. cbaois[i] = cbaoi
  4690. return cbaois
  4691. # Added in 0.4.0.
  4692. @classmethod
  4693. def _warp_shape_tuples(cls, shapes):
  4694. # pylint: disable=invalid-name
  4695. pi = np.pi
  4696. result = []
  4697. for shape in shapes:
  4698. if 0 in shape:
  4699. result.append(shape)
  4700. continue
  4701. height, width = shape[0:2]
  4702. max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
  4703. # np.round() is here a replacement for cvRound(). It is not fully
  4704. # clear whether the two functions behave exactly identical in all
  4705. # situations.
  4706. # See
  4707. # https://github.com/opencv/opencv/blob/master/
  4708. # modules/core/include/opencv2/core/fast_math.hpp
  4709. # for OpenCV's implementation.
  4710. width = int(np.round(max_radius))
  4711. height = int(np.round(max_radius * pi))
  4712. result.append(tuple([height, width] + list(shape[2:])))
  4713. return result
  4714. # Added in 0.4.0.
  4715. @classmethod
  4716. def warpPolarCoords(cls, src, dsize, center, maxRadius, flags):
  4717. # See
  4718. # https://docs.opencv.org/3.4.8/da/d54/group__imgproc__transform.html
  4719. # for the equations
  4720. # or also
  4721. # https://github.com/opencv/opencv/blob/master/modules/imgproc/src/
  4722. # imgwarp.cpp
  4723. #
  4724. # pylint: disable=invalid-name, no-else-return
  4725. assert dsize[0] > 0
  4726. assert dsize[1] > 0
  4727. dsize_width = dsize[0]
  4728. dsize_height = dsize[1]
  4729. center_x = center[0]
  4730. center_y = center[1]
  4731. if np.logical_and(flags, cv2.WARP_INVERSE_MAP):
  4732. rho = src[:, 0]
  4733. phi = src[:, 1]
  4734. Kangle = dsize_height / (2*np.pi)
  4735. angleRad = phi / Kangle
  4736. if np.bitwise_and(flags, cv2.WARP_POLAR_LOG):
  4737. Klog = dsize_width / np.log(maxRadius)
  4738. magnitude = np.exp(rho / Klog)
  4739. else:
  4740. Klin = dsize_width / maxRadius
  4741. magnitude = rho / Klin
  4742. x = center_x + magnitude * np.cos(angleRad)
  4743. y = center_y + magnitude * np.sin(angleRad)
  4744. x = x[:, np.newaxis]
  4745. y = y[:, np.newaxis]
  4746. return np.concatenate([x, y], axis=1)
  4747. else:
  4748. x = src[:, 0]
  4749. y = src[:, 1]
  4750. Kangle = dsize_height / (2*np.pi)
  4751. Klin = dsize_width / maxRadius
  4752. I_x, I_y = (x - center_x, y - center_y)
  4753. magnitude_I, angle_I = cv2.cartToPolar(I_x, I_y)
  4754. phi = Kangle * angle_I
  4755. # TODO add semilog support here
  4756. rho = Klin * magnitude_I
  4757. return np.concatenate([rho, phi], axis=1)
  4758. # Added in 0.4.0.
  4759. def get_parameters(self):
  4760. """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
  4761. return []
  4762. # Added in 0.4.0.
  4763. def get_children_lists(self):
  4764. """See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
  4765. return [self.children]
  4766. # Added in 0.4.0.
  4767. def _to_deterministic(self):
  4768. aug = self.copy()
  4769. aug.children = aug.children.to_deterministic()
  4770. aug.deterministic = True
  4771. aug.random_state = self.random_state.derive_rng_()
  4772. return aug
  4773. # Added in 0.4.0.
  4774. def __str__(self):
  4775. pattern = (
  4776. "%s("
  4777. "name=%s, children=%s, deterministic=%s"
  4778. ")")
  4779. return pattern % (self.__class__.__name__, self.name,
  4780. self.children, self.deterministic)
  4781. class Jigsaw(meta.Augmenter):
  4782. """Move cells within images similar to jigsaw patterns.
  4783. .. note::
  4784. This augmenter will by default pad images until their height is a
  4785. multiple of `nb_rows`. Analogous for `nb_cols`.
  4786. .. note::
  4787. This augmenter will resize heatmaps and segmentation maps to the
  4788. image size, then apply similar padding as for the corresponding images
  4789. and resize back to the original map size. That also means that images
  4790. may change in shape (due to padding), but heatmaps/segmaps will not
  4791. change. For heatmaps/segmaps, this deviates from pad augmenters that
  4792. will change images and heatmaps/segmaps in corresponding ways and then
  4793. keep the heatmaps/segmaps at the new size.
  4794. .. warning::
  4795. This augmenter currently only supports augmentation of images,
  4796. heatmaps, segmentation maps and keypoints. Other augmentables,
  4797. i.e. bounding boxes, polygons and line strings, will result in errors.
  4798. Added in 0.4.0.
  4799. **Supported dtypes**:
  4800. See :func:`~imgaug.augmenters.geometric.apply_jigsaw`.
  4801. Parameters
  4802. ----------
  4803. nb_rows : int or list of int or tuple of int or imgaug.parameters.StochasticParameter, optional
  4804. How many rows the jigsaw pattern should have.
  4805. * If a single ``int``, then that value will be used for all images.
  4806. * If a tuple ``(a, b)``, then a random value will be uniformly
  4807. sampled per image from the discrete interval ``[a..b]``.
  4808. * If a list, then for each image a random value will be sampled
  4809. from that list.
  4810. * If ``StochasticParameter``, then that parameter is queried per
  4811. image to sample the value to use.
  4812. nb_cols : int or list of int or tuple of int or imgaug.parameters.StochasticParameter, optional
  4813. How many cols the jigsaw pattern should have.
  4814. * If a single ``int``, then that value will be used for all images.
  4815. * If a tuple ``(a, b)``, then a random value will be uniformly
  4816. sampled per image from the discrete interval ``[a..b]``.
  4817. * If a list, then for each image a random value will be sampled
  4818. from that list.
  4819. * If ``StochasticParameter``, then that parameter is queried per
  4820. image to sample the value to use.
  4821. max_steps : int or list of int or tuple of int or imgaug.parameters.StochasticParameter, optional
  4822. How many steps each jigsaw cell may be moved.
  4823. * If a single ``int``, then that value will be used for all images.
  4824. * If a tuple ``(a, b)``, then a random value will be uniformly
  4825. sampled per image from the discrete interval ``[a..b]``.
  4826. * If a list, then for each image a random value will be sampled
  4827. from that list.
  4828. * If ``StochasticParameter``, then that parameter is queried per
  4829. image to sample the value to use.
  4830. allow_pad : bool, optional
  4831. Whether to allow automatically padding images until they are evenly
  4832. divisible by ``nb_rows`` and ``nb_cols``.
  4833. seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  4834. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  4835. name : None or str, optional
  4836. See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
  4837. random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
  4838. Old name for parameter `seed`.
  4839. Its usage will not yet cause a deprecation warning,
  4840. but it is still recommended to use `seed` now.
  4841. Outdated since 0.4.0.
  4842. deterministic : bool, optional
  4843. Deprecated since 0.4.0.
  4844. See method ``to_deterministic()`` for an alternative and for
  4845. details about what the "deterministic mode" actually does.
  4846. Examples
  4847. --------
  4848. >>> import imgaug.augmenters as iaa
  4849. >>> aug = iaa.Jigsaw(nb_rows=10, nb_cols=10)
  4850. Create a jigsaw augmenter that splits images into ``10x10`` cells
  4851. and shifts them around by ``0`` to ``2`` steps (default setting).
  4852. >>> aug = iaa.Jigsaw(nb_rows=(1, 4), nb_cols=(1, 4))
  4853. Create a jigsaw augmenter that splits each image into ``1`` to ``4``
  4854. cells along each axis.
  4855. >>> aug = iaa.Jigsaw(nb_rows=10, nb_cols=10, max_steps=(1, 5))
  4856. Create a jigsaw augmenter that moves the cells in each image by a random
  4857. amount between ``1`` and ``5`` times (decided per image). Some images will
  4858. be barely changed, some will be fairly distorted.
  4859. """
  4860. # Added in 0.4.0.
  4861. def __init__(self, nb_rows=(3, 10), nb_cols=(3, 10), max_steps=1,
  4862. allow_pad=True,
  4863. seed=None, name=None,
  4864. random_state="deprecated", deterministic="deprecated"):
  4865. super(Jigsaw, self).__init__(
  4866. seed=seed, name=name,
  4867. random_state=random_state, deterministic=deterministic)
  4868. self.nb_rows = iap.handle_discrete_param(
  4869. nb_rows, "nb_rows", value_range=(1, None), tuple_to_uniform=True,
  4870. list_to_choice=True, allow_floats=False)
  4871. self.nb_cols = iap.handle_discrete_param(
  4872. nb_cols, "nb_cols", value_range=(1, None), tuple_to_uniform=True,
  4873. list_to_choice=True, allow_floats=False)
  4874. self.max_steps = iap.handle_discrete_param(
  4875. max_steps, "max_steps", value_range=(0, None),
  4876. tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
  4877. self.allow_pad = allow_pad
  4878. # Added in 0.4.0.
  4879. def _augment_batch_(self, batch, random_state, parents, hooks):
  4880. samples = self._draw_samples(batch, random_state)
  4881. # We resize here heatmaps/segmaps early to the image size in order to
  4882. # avoid problems where the jigsaw cells don't fit perfectly into
  4883. # the heatmap/segmap arrays or there are minor padding-related
  4884. # differences.
  4885. # TODO This step could most likely be avoided.
  4886. # TODO add something like
  4887. # 'with batch.maps_resized_to_image_sizes(): ...'
  4888. batch, maps_shapes_orig = self._resize_maps(batch)
  4889. if self.allow_pad:
  4890. # this is a bit more difficult than one might expect, because we
  4891. # (a) might have different numbers of rows/cols per image
  4892. # (b) might have different shapes per image
  4893. # (c) have non-image data that also requires padding
  4894. # TODO enable support for stochastic parameters in
  4895. # PadToMultiplesOf, then we can simple use two
  4896. # DeterministicLists here to generate rowwise values
  4897. for i in np.arange(len(samples.destinations)):
  4898. padder = size_lib.CenterPadToMultiplesOf(
  4899. width_multiple=samples.nb_cols[i],
  4900. height_multiple=samples.nb_rows[i],
  4901. seed=random_state
  4902. )
  4903. row = batch.subselect_rows_by_indices([i])
  4904. row = padder.augment_batch_(row, parents=parents + [self],
  4905. hooks=hooks)
  4906. batch = batch.invert_subselect_rows_by_indices_([i], row)
  4907. if batch.images is not None:
  4908. for i, image in enumerate(batch.images):
  4909. image[...] = apply_jigsaw(image, samples.destinations[i])
  4910. if batch.heatmaps is not None:
  4911. for i, heatmap in enumerate(batch.heatmaps):
  4912. heatmap.arr_0to1 = apply_jigsaw(heatmap.arr_0to1,
  4913. samples.destinations[i])
  4914. if batch.segmentation_maps is not None:
  4915. for i, segmap in enumerate(batch.segmentation_maps):
  4916. segmap.arr = apply_jigsaw(segmap.arr, samples.destinations[i])
  4917. if batch.keypoints is not None:
  4918. for i, kpsoi in enumerate(batch.keypoints):
  4919. xy = kpsoi.to_xy_array()
  4920. xy[...] = apply_jigsaw_to_coords(xy,
  4921. samples.destinations[i],
  4922. image_shape=kpsoi.shape)
  4923. kpsoi.fill_from_xy_array_(xy)
  4924. has_other_cbaoi = any([getattr(batch, attr_name) is not None
  4925. for attr_name
  4926. in ["bounding_boxes", "polygons",
  4927. "line_strings"]])
  4928. if has_other_cbaoi:
  4929. raise NotImplementedError(
  4930. "Jigsaw currently only supports augmentation of images, "
  4931. "heatmaps, segmentation maps and keypoints. "
  4932. "Explicitly not supported are: bounding boxes, polygons "
  4933. "and line strings.")
  4934. # We don't crop back to the original size, partly because it is
  4935. # rather cumbersome to implement, partly because the padded
  4936. # borders might have been moved into the inner parts of the image
  4937. batch = self._invert_resize_maps(batch, maps_shapes_orig)
  4938. return batch
  4939. # Added in 0.4.0.
  4940. def _draw_samples(self, batch, random_state):
  4941. nb_images = batch.nb_rows
  4942. nb_rows = self.nb_rows.draw_samples((nb_images,),
  4943. random_state=random_state)
  4944. nb_cols = self.nb_cols.draw_samples((nb_images,),
  4945. random_state=random_state)
  4946. max_steps = self.max_steps.draw_samples((nb_images,),
  4947. random_state=random_state)
  4948. destinations = []
  4949. for i in np.arange(nb_images):
  4950. destinations.append(
  4951. generate_jigsaw_destinations(
  4952. nb_rows[i], nb_cols[i], max_steps[i],
  4953. seed=random_state)
  4954. )
  4955. samples = _JigsawSamples(nb_rows, nb_cols, max_steps, destinations)
  4956. return samples
  4957. # Added in 0.4.0.
  4958. @classmethod
  4959. def _resize_maps(cls, batch):
  4960. # skip computation of rowwise shapes
  4961. if batch.heatmaps is None and batch.segmentation_maps is None:
  4962. return batch, (None, None)
  4963. image_shapes = batch.get_rowwise_shapes()
  4964. batch.heatmaps, heatmaps_shapes_orig = cls._resize_maps_single_list(
  4965. batch.heatmaps, "arr_0to1", image_shapes)
  4966. batch.segmentation_maps, sm_shapes_orig = cls._resize_maps_single_list(
  4967. batch.segmentation_maps, "arr", image_shapes)
  4968. return batch, (heatmaps_shapes_orig, sm_shapes_orig)
  4969. # Added in 0.4.0.
  4970. @classmethod
  4971. def _resize_maps_single_list(cls, augmentables, arr_attr_name,
  4972. image_shapes):
  4973. if augmentables is None:
  4974. return None, None
  4975. shapes_orig = []
  4976. augms_resized = []
  4977. for augmentable, image_shape in zip(augmentables, image_shapes):
  4978. shape_orig = getattr(augmentable, arr_attr_name).shape
  4979. augm_rs = augmentable.resize(image_shape[0:2])
  4980. augms_resized.append(augm_rs)
  4981. shapes_orig.append(shape_orig)
  4982. return augms_resized, shapes_orig
  4983. # Added in 0.4.0.
  4984. @classmethod
  4985. def _invert_resize_maps(cls, batch, shapes_orig):
  4986. batch.heatmaps = cls._invert_resize_maps_single_list(
  4987. batch.heatmaps, shapes_orig[0])
  4988. batch.segmentation_maps = cls._invert_resize_maps_single_list(
  4989. batch.segmentation_maps, shapes_orig[1])
  4990. return batch
  4991. # Added in 0.4.0.
  4992. @classmethod
  4993. def _invert_resize_maps_single_list(cls, augmentables, shapes_orig):
  4994. if shapes_orig is None:
  4995. return None
  4996. augms_resized = []
  4997. for augmentable, shape_orig in zip(augmentables, shapes_orig):
  4998. augms_resized.append(augmentable.resize(shape_orig[0:2]))
  4999. return augms_resized
  5000. # Added in 0.4.0.
  5001. def get_parameters(self):
  5002. return [self.nb_rows, self.nb_cols, self.max_steps, self.allow_pad]
  5003. # Added in 0.4.0.
  5004. class _JigsawSamples(object):
  5005. # Added in 0.4.0.
  5006. def __init__(self, nb_rows, nb_cols, max_steps, destinations):
  5007. self.nb_rows = nb_rows
  5008. self.nb_cols = nb_cols
  5009. self.max_steps = max_steps
  5010. self.destinations = destinations