diff --git a/web/libs/editor/tests/e2e/examples/data/sample-sin.json b/web/libs/editor/tests/e2e/examples/data/sample-sin.json
new file mode 100644
index 000000000000..9b21e1609309
--- /dev/null
+++ b/web/libs/editor/tests/e2e/examples/data/sample-sin.json
@@ -0,0 +1,3010 @@
+{
+ "timeseries": {
+ "time": [
+ 1602707685290,
+ 1602707695271,
+ 1602707705259,
+ 1602707715317,
+ 1602707725263,
+ 1602707735312,
+ 1602707745282,
+ 1602707755319,
+ 1602707765270,
+ 1602707775244,
+ 1602707785251,
+ 1602707795280,
+ 1602707805276,
+ 1602707815269,
+ 1602707825292,
+ 1602707835287,
+ 1602707845268,
+ 1602707855242,
+ 1602707865313,
+ 1602707875299,
+ 1602707885260,
+ 1602707895270,
+ 1602707905238,
+ 1602707915252,
+ 1602707925257,
+ 1602707935303,
+ 1602707945258,
+ 1602707955262,
+ 1602707965268,
+ 1602707975307,
+ 1602707985294,
+ 1602707995296,
+ 1602708005259,
+ 1602708015238,
+ 1602708025239,
+ 1602708035277,
+ 1602708045278,
+ 1602708055242,
+ 1602708065278,
+ 1602708075280,
+ 1602708085319,
+ 1602708095281,
+ 1602708105263,
+ 1602708115254,
+ 1602708125246,
+ 1602708135232,
+ 1602708145233,
+ 1602708155327,
+ 1602708165276,
+ 1602708175246,
+ 1602708185240,
+ 1602708195247,
+ 1602708205272,
+ 1602708215251,
+ 1602708225279,
+ 1602708235247,
+ 1602708245232,
+ 1602708255325,
+ 1602708265242,
+ 1602708275272,
+ 1602708285276,
+ 1602708295279,
+ 1602708305310,
+ 1602708315255,
+ 1602708325232,
+ 1602708335271,
+ 1602708345305,
+ 1602708355253,
+ 1602708365241,
+ 1602708375312,
+ 1602708385229,
+ 1602708395260,
+ 1602708405272,
+ 1602708415296,
+ 1602708425307,
+ 1602708435274,
+ 1602708445276,
+ 1602708455288,
+ 1602708465323,
+ 1602708475229,
+ 1602708485239,
+ 1602708495238,
+ 1602708505281,
+ 1602708515255,
+ 1602708525319,
+ 1602708535270,
+ 1602708545279,
+ 1602708555323,
+ 1602708565308,
+ 1602708575299,
+ 1602708585300,
+ 1602708595278,
+ 1602708605267,
+ 1602708615236,
+ 1602708625268,
+ 1602708635261,
+ 1602708645253,
+ 1602708655288,
+ 1602708665293,
+ 1602708675237,
+ 1602708685322,
+ 1602708695229,
+ 1602708705296,
+ 1602708715311,
+ 1602708725264,
+ 1602708735275,
+ 1602708745296,
+ 1602708755266,
+ 1602708765263,
+ 1602708775254,
+ 1602708785315,
+ 1602708795247,
+ 1602708805312,
+ 1602708815248,
+ 1602708825304,
+ 1602708835302,
+ 1602708845256,
+ 1602708855280,
+ 1602708865328,
+ 1602708875305,
+ 1602708885298,
+ 1602708895274,
+ 1602708905287,
+ 1602708915249,
+ 1602708925267,
+ 1602708935284,
+ 1602708945252,
+ 1602708955269,
+ 1602708965314,
+ 1602708975281,
+ 1602708985277,
+ 1602708995294,
+ 1602709005273,
+ 1602709015288,
+ 1602709025268,
+ 1602709035251,
+ 1602709045308,
+ 1602709055325,
+ 1602709065294,
+ 1602709075286,
+ 1602709085229,
+ 1602709095324,
+ 1602709105242,
+ 1602709115271,
+ 1602709125262,
+ 1602709135314,
+ 1602709145261,
+ 1602709155314,
+ 1602709165230,
+ 1602709175274,
+ 1602709185303,
+ 1602709195261,
+ 1602709205293,
+ 1602709215301,
+ 1602709225315,
+ 1602709235249,
+ 1602709245251,
+ 1602709255321,
+ 1602709265234,
+ 1602709275245,
+ 1602709285325,
+ 1602709295276,
+ 1602709305267,
+ 1602709315294,
+ 1602709325297,
+ 1602709335318,
+ 1602709345243,
+ 1602709355325,
+ 1602709365238,
+ 1602709375276,
+ 1602709385252,
+ 1602709395273,
+ 1602709405245,
+ 1602709415271,
+ 1602709425258,
+ 1602709435278,
+ 1602709445244,
+ 1602709455281,
+ 1602709465270,
+ 1602709475299,
+ 1602709485292,
+ 1602709495257,
+ 1602709505303,
+ 1602709515297,
+ 1602709525292,
+ 1602709535274,
+ 1602709545231,
+ 1602709555277,
+ 1602709565273,
+ 1602709575285,
+ 1602709585230,
+ 1602709595230,
+ 1602709605252,
+ 1602709615291,
+ 1602709625302,
+ 1602709635235,
+ 1602709645243,
+ 1602709655262,
+ 1602709665267,
+ 1602709675291,
+ 1602709685255,
+ 1602709695321,
+ 1602709705314,
+ 1602709715239,
+ 1602709725242,
+ 1602709735286,
+ 1602709745293,
+ 1602709755284,
+ 1602709765264,
+ 1602709775265,
+ 1602709785275,
+ 1602709795296,
+ 1602709805283,
+ 1602709815328,
+ 1602709825268,
+ 1602709835315,
+ 1602709845266,
+ 1602709855255,
+ 1602709865289,
+ 1602709875320,
+ 1602709885281,
+ 1602709895279,
+ 1602709905241,
+ 1602709915294,
+ 1602709925236,
+ 1602709935292,
+ 1602709945266,
+ 1602709955234,
+ 1602709965242,
+ 1602709975255,
+ 1602709985231,
+ 1602709995300,
+ 1602710005241,
+ 1602710015288,
+ 1602710025260,
+ 1602710035317,
+ 1602710045289,
+ 1602710055292,
+ 1602710065251,
+ 1602710075312,
+ 1602710085307,
+ 1602710095290,
+ 1602710105327,
+ 1602710115293,
+ 1602710125318,
+ 1602710135228,
+ 1602710145292,
+ 1602710155239,
+ 1602710165267,
+ 1602710175305,
+ 1602710185320,
+ 1602710195281,
+ 1602710205274,
+ 1602710215316,
+ 1602710225266,
+ 1602710235293,
+ 1602710245325,
+ 1602710255267,
+ 1602710265299,
+ 1602710275278,
+ 1602710285255,
+ 1602710295300,
+ 1602710305261,
+ 1602710315301,
+ 1602710325269,
+ 1602710335308,
+ 1602710345284,
+ 1602710355304,
+ 1602710365284,
+ 1602710375234,
+ 1602710385231,
+ 1602710395267,
+ 1602710405289,
+ 1602710415303,
+ 1602710425269,
+ 1602710435261,
+ 1602710445266,
+ 1602710455260,
+ 1602710465288,
+ 1602710475245,
+ 1602710485328,
+ 1602710495308,
+ 1602710505316,
+ 1602710515262,
+ 1602710525301,
+ 1602710535298,
+ 1602710545323,
+ 1602710555235,
+ 1602710565300,
+ 1602710575244,
+ 1602710585267,
+ 1602710595275,
+ 1602710605262,
+ 1602710615280,
+ 1602710625323,
+ 1602710635254,
+ 1602710645286,
+ 1602710655324,
+ 1602710665310,
+ 1602710675324,
+ 1602710685231,
+ 1602710695309,
+ 1602710705290,
+ 1602710715310,
+ 1602710725286,
+ 1602710735298,
+ 1602710745236,
+ 1602710755245,
+ 1602710765313,
+ 1602710775257,
+ 1602710785298,
+ 1602710795292,
+ 1602710805276,
+ 1602710815230,
+ 1602710825262,
+ 1602710835257,
+ 1602710845264,
+ 1602710855249,
+ 1602710865291,
+ 1602710875268,
+ 1602710885242,
+ 1602710895232,
+ 1602710905267,
+ 1602710915299,
+ 1602710925319,
+ 1602710935265,
+ 1602710945236,
+ 1602710955294,
+ 1602710965238,
+ 1602710975258,
+ 1602710985233,
+ 1602710995296,
+ 1602711005234,
+ 1602711015322,
+ 1602711025250,
+ 1602711035252,
+ 1602711045264,
+ 1602711055248,
+ 1602711065326,
+ 1602711075283,
+ 1602711085255,
+ 1602711095234,
+ 1602711105250,
+ 1602711115323,
+ 1602711125237,
+ 1602711135232,
+ 1602711145250,
+ 1602711155250,
+ 1602711165256,
+ 1602711175317,
+ 1602711185287,
+ 1602711195275,
+ 1602711205290,
+ 1602711215282,
+ 1602711225313,
+ 1602711235290,
+ 1602711245287,
+ 1602711255231,
+ 1602711265229,
+ 1602711275269,
+ 1602711285255,
+ 1602711295298,
+ 1602711305321,
+ 1602711315306,
+ 1602711325250,
+ 1602711335267,
+ 1602711345315,
+ 1602711355270,
+ 1602711365265,
+ 1602711375302,
+ 1602711385309,
+ 1602711395267,
+ 1602711405261,
+ 1602711415302,
+ 1602711425308,
+ 1602711435312,
+ 1602711445324,
+ 1602711455322,
+ 1602711465280,
+ 1602711475246,
+ 1602711485243,
+ 1602711495307,
+ 1602711505282,
+ 1602711515273,
+ 1602711525318,
+ 1602711535245,
+ 1602711545259,
+ 1602711555250,
+ 1602711565326,
+ 1602711575235,
+ 1602711585325,
+ 1602711595299,
+ 1602711605273,
+ 1602711615262,
+ 1602711625273,
+ 1602711635277,
+ 1602711645259,
+ 1602711655317,
+ 1602711665284,
+ 1602711675233,
+ 1602711685316,
+ 1602711695308,
+ 1602711705324,
+ 1602711715313,
+ 1602711725276,
+ 1602711735236,
+ 1602711745327,
+ 1602711755288,
+ 1602711765247,
+ 1602711775255,
+ 1602711785264,
+ 1602711795320,
+ 1602711805240,
+ 1602711815324,
+ 1602711825228,
+ 1602711835316,
+ 1602711845236,
+ 1602711855277,
+ 1602711865235,
+ 1602711875303,
+ 1602711885247,
+ 1602711895297,
+ 1602711905254,
+ 1602711915285,
+ 1602711925273,
+ 1602711935285,
+ 1602711945311,
+ 1602711955316,
+ 1602711965241,
+ 1602711975254,
+ 1602711985281,
+ 1602711995326,
+ 1602712005299,
+ 1602712015283,
+ 1602712025317,
+ 1602712035233,
+ 1602712045303,
+ 1602712055287,
+ 1602712065262,
+ 1602712075291,
+ 1602712085325,
+ 1602712095313,
+ 1602712105268,
+ 1602712115303,
+ 1602712125315,
+ 1602712135299,
+ 1602712145279,
+ 1602712155278,
+ 1602712165318,
+ 1602712175287,
+ 1602712185297,
+ 1602712195308,
+ 1602712205246,
+ 1602712215242,
+ 1602712225327,
+ 1602712235265,
+ 1602712245322,
+ 1602712255249,
+ 1602712265266,
+ 1602712275320,
+ 1602712285235,
+ 1602712295242,
+ 1602712305328,
+ 1602712315278,
+ 1602712325303,
+ 1602712335237,
+ 1602712345291,
+ 1602712355293,
+ 1602712365241,
+ 1602712375327,
+ 1602712385311,
+ 1602712395294,
+ 1602712405263,
+ 1602712415262,
+ 1602712425235,
+ 1602712435232,
+ 1602712445307,
+ 1602712455243,
+ 1602712465269,
+ 1602712475276,
+ 1602712485254,
+ 1602712495310,
+ 1602712505282,
+ 1602712515292,
+ 1602712525250,
+ 1602712535324,
+ 1602712545295,
+ 1602712555310,
+ 1602712565325,
+ 1602712575228,
+ 1602712585286,
+ 1602712595228,
+ 1602712605323,
+ 1602712615281,
+ 1602712625229,
+ 1602712635270,
+ 1602712645325,
+ 1602712655277,
+ 1602712665311,
+ 1602712675320,
+ 1602712685234,
+ 1602712695294,
+ 1602712705288,
+ 1602712715298,
+ 1602712725244,
+ 1602712735308,
+ 1602712745322,
+ 1602712755305,
+ 1602712765238,
+ 1602712775280,
+ 1602712785286,
+ 1602712795270,
+ 1602712805266,
+ 1602712815296,
+ 1602712825271,
+ 1602712835256,
+ 1602712845286,
+ 1602712855327,
+ 1602712865305,
+ 1602712875236,
+ 1602712885235,
+ 1602712895320,
+ 1602712905240,
+ 1602712915286,
+ 1602712925256,
+ 1602712935281,
+ 1602712945274,
+ 1602712955319,
+ 1602712965277,
+ 1602712975270,
+ 1602712985280,
+ 1602712995272,
+ 1602713005239,
+ 1602713015296,
+ 1602713025229,
+ 1602713035314,
+ 1602713045235,
+ 1602713055266,
+ 1602713065289,
+ 1602713075313,
+ 1602713085291,
+ 1602713095322,
+ 1602713105285,
+ 1602713115283,
+ 1602713125276,
+ 1602713135232,
+ 1602713145287,
+ 1602713155289,
+ 1602713165315,
+ 1602713175273,
+ 1602713185301,
+ 1602713195283,
+ 1602713205273,
+ 1602713215242,
+ 1602713225285,
+ 1602713235240,
+ 1602713245273,
+ 1602713255281,
+ 1602713265230,
+ 1602713275235,
+ 1602713285238,
+ 1602713295244,
+ 1602713305299,
+ 1602713315232,
+ 1602713325260,
+ 1602713335277,
+ 1602713345241,
+ 1602713355297,
+ 1602713365283,
+ 1602713375292,
+ 1602713385231,
+ 1602713395241,
+ 1602713405269,
+ 1602713415285,
+ 1602713425237,
+ 1602713435276,
+ 1602713445249,
+ 1602713455253,
+ 1602713465300,
+ 1602713475323,
+ 1602713485294,
+ 1602713495285,
+ 1602713505231,
+ 1602713515277,
+ 1602713525272,
+ 1602713535315,
+ 1602713545238,
+ 1602713555248,
+ 1602713565253,
+ 1602713575229,
+ 1602713585267,
+ 1602713595237,
+ 1602713605286,
+ 1602713615260,
+ 1602713625308,
+ 1602713635298,
+ 1602713645327,
+ 1602713655242,
+ 1602713665318,
+ 1602713675231,
+ 1602713685233,
+ 1602713695317,
+ 1602713705237,
+ 1602713715279,
+ 1602713725288,
+ 1602713735323,
+ 1602713745231,
+ 1602713755293,
+ 1602713765241,
+ 1602713775256,
+ 1602713785313,
+ 1602713795313,
+ 1602713805300,
+ 1602713815232,
+ 1602713825246,
+ 1602713835268,
+ 1602713845312,
+ 1602713855260,
+ 1602713865274,
+ 1602713875281,
+ 1602713885233,
+ 1602713895308,
+ 1602713905239,
+ 1602713915268,
+ 1602713925259,
+ 1602713935241,
+ 1602713945317,
+ 1602713955270,
+ 1602713965290,
+ 1602713975267,
+ 1602713985324,
+ 1602713995286,
+ 1602714005306,
+ 1602714015266,
+ 1602714025325,
+ 1602714035252,
+ 1602714045298,
+ 1602714055248,
+ 1602714065324,
+ 1602714075315,
+ 1602714085266,
+ 1602714095264,
+ 1602714105297,
+ 1602714115326,
+ 1602714125240,
+ 1602714135296,
+ 1602714145299,
+ 1602714155245,
+ 1602714165243,
+ 1602714175277,
+ 1602714185277,
+ 1602714195272,
+ 1602714205235,
+ 1602714215250,
+ 1602714225283,
+ 1602714235253,
+ 1602714245316,
+ 1602714255236,
+ 1602714265248,
+ 1602714275312,
+ 1602714285258,
+ 1602714295232,
+ 1602714305285,
+ 1602714315313,
+ 1602714325270,
+ 1602714335273,
+ 1602714345324,
+ 1602714355262,
+ 1602714365314,
+ 1602714375292,
+ 1602714385235,
+ 1602714395321,
+ 1602714405321,
+ 1602714415257,
+ 1602714425237,
+ 1602714435259,
+ 1602714445327,
+ 1602714455304,
+ 1602714465282,
+ 1602714475246,
+ 1602714485328,
+ 1602714495285,
+ 1602714505254,
+ 1602714515261,
+ 1602714525229,
+ 1602714535311,
+ 1602714545294,
+ 1602714555261,
+ 1602714565316,
+ 1602714575318,
+ 1602714585258,
+ 1602714595275,
+ 1602714605279,
+ 1602714615306,
+ 1602714625246,
+ 1602714635315,
+ 1602714645322,
+ 1602714655240,
+ 1602714665244,
+ 1602714675274,
+ 1602714685279,
+ 1602714695237,
+ 1602714705281,
+ 1602714715242,
+ 1602714725327,
+ 1602714735289,
+ 1602714745242,
+ 1602714755251,
+ 1602714765293,
+ 1602714775258,
+ 1602714785275,
+ 1602714795309,
+ 1602714805316,
+ 1602714815236,
+ 1602714825246,
+ 1602714835272,
+ 1602714845320,
+ 1602714855276,
+ 1602714865259,
+ 1602714875276,
+ 1602714885247,
+ 1602714895311,
+ 1602714905290,
+ 1602714915231,
+ 1602714925324,
+ 1602714935321,
+ 1602714945238,
+ 1602714955328,
+ 1602714965280,
+ 1602714975282,
+ 1602714985229,
+ 1602714995256,
+ 1602715005274,
+ 1602715015230,
+ 1602715025313,
+ 1602715035258,
+ 1602715045322,
+ 1602715055275,
+ 1602715065259,
+ 1602715075301,
+ 1602715085259,
+ 1602715095291,
+ 1602715105269,
+ 1602715115278,
+ 1602715125307,
+ 1602715135251,
+ 1602715145270,
+ 1602715155243,
+ 1602715165264,
+ 1602715175327,
+ 1602715185262,
+ 1602715195265,
+ 1602715205301,
+ 1602715215289,
+ 1602715225316,
+ 1602715235272,
+ 1602715245237,
+ 1602715255299,
+ 1602715265292,
+ 1602715275309,
+ 1602715285268,
+ 1602715295248,
+ 1602715305302,
+ 1602715315269,
+ 1602715325235,
+ 1602715335313,
+ 1602715345295,
+ 1602715355280,
+ 1602715365245,
+ 1602715375311,
+ 1602715385328,
+ 1602715395270,
+ 1602715405276,
+ 1602715415295,
+ 1602715425283,
+ 1602715435255,
+ 1602715445282,
+ 1602715455265,
+ 1602715465304,
+ 1602715475326,
+ 1602715485328,
+ 1602715495249,
+ 1602715505286,
+ 1602715515266,
+ 1602715525231,
+ 1602715535281,
+ 1602715545243,
+ 1602715555302,
+ 1602715565229,
+ 1602715575305,
+ 1602715585320,
+ 1602715595277,
+ 1602715605323,
+ 1602715615274,
+ 1602715625247,
+ 1602715635261,
+ 1602715645263,
+ 1602715655264,
+ 1602715665287,
+ 1602715675268,
+ 1602715685232,
+ 1602715695296,
+ 1602715705271,
+ 1602715715265,
+ 1602715725260,
+ 1602715735264,
+ 1602715745290,
+ 1602715755255,
+ 1602715765299,
+ 1602715775285,
+ 1602715785302,
+ 1602715795264,
+ 1602715805294,
+ 1602715815318,
+ 1602715825280,
+ 1602715835313,
+ 1602715845316,
+ 1602715855246,
+ 1602715865234,
+ 1602715875243,
+ 1602715885312,
+ 1602715895309,
+ 1602715905289,
+ 1602715915300,
+ 1602715925307,
+ 1602715935273,
+ 1602715945322,
+ 1602715955295,
+ 1602715965306,
+ 1602715975294,
+ 1602715985257,
+ 1602715995243,
+ 1602716005312,
+ 1602716015276,
+ 1602716025301,
+ 1602716035259,
+ 1602716045269,
+ 1602716055249,
+ 1602716065286,
+ 1602716075267,
+ 1602716085249,
+ 1602716095274,
+ 1602716105303,
+ 1602716115234,
+ 1602716125316,
+ 1602716135318,
+ 1602716145253,
+ 1602716155244,
+ 1602716165261,
+ 1602716175238,
+ 1602716185268,
+ 1602716195255,
+ 1602716205268,
+ 1602716215278,
+ 1602716225242,
+ 1602716235305,
+ 1602716245255,
+ 1602716255249,
+ 1602716265263,
+ 1602716275298,
+ 1602716285275,
+ 1602716295267,
+ 1602716305287,
+ 1602716315272,
+ 1602716325317,
+ 1602716335242,
+ 1602716345247,
+ 1602716355294,
+ 1602716365303,
+ 1602716375244,
+ 1602716385234,
+ 1602716395279,
+ 1602716405318,
+ 1602716415323,
+ 1602716425290,
+ 1602716435298,
+ 1602716445316,
+ 1602716455269,
+ 1602716465275,
+ 1602716475307,
+ 1602716485306,
+ 1602716495323,
+ 1602716505247,
+ 1602716515240,
+ 1602716525248,
+ 1602716535234,
+ 1602716545301,
+ 1602716555318,
+ 1602716565274,
+ 1602716575256,
+ 1602716585304,
+ 1602716595269,
+ 1602716605321,
+ 1602716615254,
+ 1602716625270,
+ 1602716635281,
+ 1602716645312,
+ 1602716655260,
+ 1602716665258,
+ 1602716675328,
+ 1602716685233,
+ 1602716695307,
+ 1602716705298,
+ 1602716715310,
+ 1602716725243,
+ 1602716735242,
+ 1602716745291,
+ 1602716755298,
+ 1602716765260,
+ 1602716775240,
+ 1602716785274,
+ 1602716795310,
+ 1602716805324,
+ 1602716815251,
+ 1602716825282,
+ 1602716835268,
+ 1602716845308,
+ 1602716855317,
+ 1602716865240,
+ 1602716875264,
+ 1602716885270,
+ 1602716895327,
+ 1602716905264,
+ 1602716915245,
+ 1602716925266,
+ 1602716935273,
+ 1602716945233,
+ 1602716955320,
+ 1602716965271,
+ 1602716975268,
+ 1602716985281,
+ 1602716995308,
+ 1602717005294,
+ 1602717015291,
+ 1602717025240,
+ 1602717035305,
+ 1602717045266,
+ 1602717055300,
+ 1602717065268,
+ 1602717075324,
+ 1602717085257,
+ 1602717095266,
+ 1602717105233,
+ 1602717115258,
+ 1602717125290,
+ 1602717135251,
+ 1602717145247,
+ 1602717155230,
+ 1602717165265,
+ 1602717175275,
+ 1602717185250,
+ 1602717195270,
+ 1602717205278,
+ 1602717215252,
+ 1602717225241,
+ 1602717235288,
+ 1602717245264,
+ 1602717255280,
+ 1602717265309,
+ 1602717275262,
+ 1602717285261,
+ 1602717295327,
+ 1602717305304,
+ 1602717315310,
+ 1602717325246,
+ 1602717335245,
+ 1602717345255,
+ 1602717355231,
+ 1602717365261,
+ 1602717375308,
+ 1602717385258,
+ 1602717395277,
+ 1602717405280,
+ 1602717415283,
+ 1602717425290,
+ 1602717435290,
+ 1602717445251,
+ 1602717455285,
+ 1602717465240,
+ 1602717475301,
+ 1602717485229,
+ 1602717495323,
+ 1602717505262,
+ 1602717515254,
+ 1602717525311,
+ 1602717535275,
+ 1602717545254,
+ 1602717555284,
+ 1602717565325,
+ 1602717575301,
+ 1602717585258,
+ 1602717595315,
+ 1602717605310,
+ 1602717615282,
+ 1602717625244,
+ 1602717635306,
+ 1602717645266,
+ 1602717655274,
+ 1602717665280,
+ 1602717675273
+ ],
+ "one": [
+ 37.03620982977559,
+ 16.8961637786484,
+ 49.29810756459162,
+ 66.41300313283884,
+ 34.77100583247356,
+ 58.11684303722001,
+ 84.35846790031925,
+ 50.727889108984215,
+ 90.56566064825813,
+ 93.10636411810053,
+ 99.25549063297521,
+ 94.90613396884045,
+ 114.33633602827135,
+ 95.83111506644275,
+ 83.48146950170867,
+ 103.61445992488154,
+ 146.06931528855873,
+ 144.48361191286608,
+ 149.59479594730706,
+ 154.20929908058707,
+ 165.39135728978562,
+ 123.83270419031122,
+ 161.40287948278828,
+ 129.2083612075148,
+ 185.373455128006,
+ 171.6384434542441,
+ 173.80071440011466,
+ 161.09072281077206,
+ 160.10006569000004,
+ 171.92741097359263,
+ 184.03989131424015,
+ 165.7452092442041,
+ 205.40153222875813,
+ 184.2415239843076,
+ 164.69151590761925,
+ 172.56631635558531,
+ 193.58977203042156,
+ 164.9529133641715,
+ 219.9107443415817,
+ 204.8812683532066,
+ 209.5000983832279,
+ 189.06696340286788,
+ 215.19805569917747,
+ 211.91195073388596,
+ 174.25700854649656,
+ 189.33533464774382,
+ 174.85465556768068,
+ 228.7372593810509,
+ 188.73144633280356,
+ 227.6571454806826,
+ 180.08383918343824,
+ 178.65981295212205,
+ 174.29443425653452,
+ 174.9538312071245,
+ 197.56047498714494,
+ 195.42225877687437,
+ 167.78613932762613,
+ 206.14839910167393,
+ 168.60699992102533,
+ 169.11465797498047,
+ 191.33602973577771,
+ 181.871157263041,
+ 207.80457709887122,
+ 184.29555559160255,
+ 172.59328591380597,
+ 161.37011349649774,
+ 164.79598423325328,
+ 188.2481830379336,
+ 192.5711015009636,
+ 200.56759657948237,
+ 169.60107719421944,
+ 150.57796828257545,
+ 173.61599355332075,
+ 155.85135087747912,
+ 158.66778126316206,
+ 173.39359507274892,
+ 185.0815740680075,
+ 179.48573585362377,
+ 172.19990147400537,
+ 171.49876003156433,
+ 138.28388010176616,
+ 179.82863027910494,
+ 178.91710248085684,
+ 161.25210131895778,
+ 158.7418643121636,
+ 121.24075198340927,
+ 118.83482999410825,
+ 147.77276076683043,
+ 122.88558103236045,
+ 143.44302089304676,
+ 112.41446648684244,
+ 141.17594014253856,
+ 147.31630754511247,
+ 127.41896802261135,
+ 125.24894478755083,
+ 141.28979620351234,
+ 146.8474598497446,
+ 143.32431747777525,
+ 155.37724615575425,
+ 142.7490361686633,
+ 119.92760940131483,
+ 107.05447353171692,
+ 126.71656776178429,
+ 137.48467671185654,
+ 115.7345726665841,
+ 154.34556335422963,
+ 101.52504447156892,
+ 112.39499999177751,
+ 104.99423993324147,
+ 149.30570151410467,
+ 106.6517649378346,
+ 150.26601350226935,
+ 133.74797568254357,
+ 135.97987206024143,
+ 101.62795509240237,
+ 142.50793419054088,
+ 141.5369822077268,
+ 137.23845045158643,
+ 129.34468335856388,
+ 127.76456802515216,
+ 109.311240264339,
+ 129.41308517549925,
+ 155.19411309575645,
+ 144.92931541892526,
+ 140.0553113711135,
+ 137.00825067593087,
+ 149.58472556753472,
+ 152.48285277949637,
+ 153.93671129214954,
+ 136.51212374131836,
+ 119.64652356572955,
+ 125.73439970137842,
+ 135.53361119285282,
+ 108.26319237266961,
+ 122.61051389434184,
+ 137.81987511013278,
+ 129.78985880244568,
+ 161.30496870788636,
+ 140.3733400195162,
+ 146.92263437593206,
+ 104.99617824863171,
+ 136.63854870776737,
+ 158.47340372179374,
+ 106.0446989838948,
+ 140.7521759965705,
+ 102.85489835310128,
+ 122.64136529226928,
+ 114.96366261271923,
+ 105.87503400073706,
+ 111.40985615256243,
+ 107.08045338991971,
+ 121.38972169184161,
+ 113.51238421532739,
+ 143.01689140447476,
+ 123.60761347406782,
+ 119.99812499524461,
+ 100.71972716897332,
+ 99.5163945446057,
+ 143.1120586489253,
+ 133.30108178475905,
+ 132.89379804709614,
+ 83.37783827481934,
+ 109.89769144152183,
+ 117.44289736716598,
+ 119.17566308868481,
+ 119.92657011426579,
+ 78.69510835446546,
+ 94.12567314877718,
+ 65.13964540111374,
+ 108.05979123139656,
+ 114.15277617034228,
+ 58.988423735500675,
+ 89.54796260375758,
+ 101.78778332797506,
+ 60.43235749926134,
+ 69.17704305488604,
+ 62.312990407280424,
+ 90.78387731222799,
+ 84.61437368218691,
+ 75.12302227911084,
+ 62.088045911657474,
+ 66.49677421488457,
+ 35.12570004940379,
+ 44.828590994037235,
+ 17.836661711066615,
+ 36.37229221395488,
+ 21.238619654773366,
+ 52.40801076004285,
+ 43.364293869657594,
+ 18.529441630534905,
+ 15.34307848649093,
+ -1.8454189533429926,
+ 14.172331748694468,
+ 32.35045829787427,
+ -9.272967458495902,
+ 24.64619318375083,
+ 16.16564111842803,
+ -17.26538253146252,
+ -15.464674805384874,
+ 15.213959978372323,
+ 14.790591752149197,
+ 5.253293068278076,
+ -6.566243684750333,
+ -14.505745534656615,
+ 17.45828780611518,
+ 13.93987208314168,
+ -41.26591091347484,
+ -0.17478275300927493,
+ 0.8632635380559535,
+ -9.521304851379647,
+ 0.5516823576515506,
+ 7.962911617163236,
+ -19.949359831624616,
+ -5.772697517030437,
+ -39.73193856629793,
+ -50.68352861987721,
+ -18.219593994212985,
+ -23.139899620420273,
+ -31.91796828918963,
+ -8.592271131257725,
+ -20.338985562631336,
+ -16.91781331752267,
+ 2.288291438688667,
+ 12.337276311041848,
+ -10.93160373190402,
+ 14.627937056361631,
+ 4.073936585970436,
+ -29.082382786776403,
+ -8.681959635946093,
+ 24.018732235080464,
+ -10.506983406915325,
+ 25.74730100566084,
+ 12.763014656403264,
+ 32.95417006482374,
+ 31.79161849008122,
+ -11.17799047780355,
+ 24.10809189187118,
+ 0.6344873707933942,
+ -3.3987857482314014,
+ 3.934274340875774,
+ 37.13276477370155,
+ 6.905315230293392,
+ 55.431069010645565,
+ 68.7941698799516,
+ 73.83093619436103,
+ 22.710685575725453,
+ 77.55811643006547,
+ 66.7710548846955,
+ 60.044689848155585,
+ 57.034995024501576,
+ 82.38810744729071,
+ 56.06673058437107,
+ 62.47577749938577,
+ 59.67202529656878,
+ 63.18449211838183,
+ 80.27801229621852,
+ 117.06009833647803,
+ 113.95469569648515,
+ 98.48051748922529,
+ 78.42215740322227,
+ 84.48336260660759,
+ 115.88997728294774,
+ 115.32604472875315,
+ 98.97172465509628,
+ 107.06649373557877,
+ 117.20623938616967,
+ 138.12149889339548,
+ 115.0685889114704,
+ 130.89332153087807,
+ 115.074590790773,
+ 165.1210479848923,
+ 153.8300498654595,
+ 150.8047420184761,
+ 166.9799341482218,
+ 166.62165208905668,
+ 155.37191892107123,
+ 130.51967048411336,
+ 189.30495166823,
+ 157.03931456016738,
+ 192.95933338991654,
+ 184.90526400892378,
+ 151.61765416982368,
+ 158.58923931393974,
+ 184.60214891055116,
+ 165.52305102709576,
+ 184.14361412139772,
+ 200.486923707275,
+ 176.6422523741312,
+ 156.85579927212535,
+ 200.22459209003298,
+ 181.88754942894002,
+ 169.78224768512732,
+ 170.1578006229115,
+ 191.89138626831704,
+ 192.503666533134,
+ 171.93510446734854,
+ 151.982509372278,
+ 150.19532447719763,
+ 149.9359593988638,
+ 183.63849798066684,
+ 159.7000652770664,
+ 176.02340868187127,
+ 179.8176391080522,
+ 180.77596712458387,
+ 175.5610819429371,
+ 155.76911505411437,
+ 149.3133842628528,
+ 141.95718886560414,
+ 131.0680972731796,
+ 113.5963164056549,
+ 147.1738055201289,
+ 152.34372809687235,
+ 123.05025839769478,
+ 137.4282259001729,
+ 136.38659874156002,
+ 142.91473318697436,
+ 133.04175416846923,
+ 146.51444562642462,
+ 98.0974337487937,
+ 100.89523164524557,
+ 108.35182105847065,
+ 95.04757003548215,
+ 118.76378378961488,
+ 125.0754551621323,
+ 79.44083160631332,
+ 106.60954271449964,
+ 76.48802846520358,
+ 72.66107495860933,
+ 54.08094807926947,
+ 89.15655259967272,
+ 83.48611285944318,
+ 91.14689839854748,
+ 81.95468468105555,
+ 40.36831166977962,
+ 75.70439158511631,
+ 64.269420917759,
+ 36.23205734223097,
+ 73.25604089580284,
+ 53.16954534876516,
+ 33.110040995902786,
+ 35.26943459105837,
+ 56.367977661663375,
+ 63.574588943940256,
+ 19.451865016666513,
+ 58.5940505599862,
+ 56.030171788553396,
+ 36.036359123227605,
+ 32.71482911959865,
+ 16.09237137468998,
+ 28.18765643191366,
+ 32.011417592294514,
+ 10.600671643587503,
+ 22.880390390389273,
+ 13.096309360754244,
+ 9.607538154468969,
+ -0.7604935301237727,
+ -2.3762818833525188,
+ -15.338179239447495,
+ -14.845983382852653,
+ 12.593979599749588,
+ 14.765549745607633,
+ 22.830965903437775,
+ 13.020406432275692,
+ 20.758424737218267,
+ -14.107573317905924,
+ -12.86911418702007,
+ 7.793152421689573,
+ 2.5627432447345306,
+ 6.63471280389324,
+ 18.822671834159813,
+ 8.995727831216634,
+ 20.643134960839262,
+ -5.035279673808567,
+ -14.249089725765568,
+ -4.185126472740954,
+ 11.89870108752553,
+ 13.778914297462485,
+ 10.122893927049894,
+ 19.235688000894946,
+ -23.11495849450944,
+ -30.37801901286177,
+ 19.741993944462585,
+ -21.411058825099033,
+ -6.563311544496152,
+ -13.542727119885527,
+ 11.778464595564976,
+ -22.089150403921607,
+ -24.29893155292889,
+ -11.90040008285792,
+ -20.856837880839347,
+ 2.6090057821249673,
+ -20.463974837644326,
+ -34.80042266337493,
+ -46.28185386070532,
+ -25.12881720007126,
+ -17.455583730873386,
+ -13.778287704616105,
+ -3.1131832256002667,
+ -43.94859565008565,
+ -50.702670560208105,
+ -25.667548630231785,
+ -36.56471163526952,
+ -20.123700247532426,
+ -21.460888810689646,
+ -43.03024156966646,
+ -47.30554390676319,
+ -65.39323502216949,
+ -65.43333590236432,
+ -32.319175501513705,
+ -25.28880464351284,
+ -41.52106166852366,
+ -49.88631810381539,
+ -78.72667107829841,
+ -73.83312240285166,
+ -87.87797922987824,
+ -63.2990682914059,
+ -79.28097718172785,
+ -90.51020560842235,
+ -70.06585379390468,
+ -79.86788394171573,
+ -113.8078864913276,
+ -66.26393707874948,
+ -115.28719850746006,
+ -72.49192312499574,
+ -112.471551197887,
+ -136.30705944377732,
+ -98.34071675429107,
+ -112.27288860186896,
+ -109.64312890465942,
+ -127.19460646898966,
+ -133.75593686863792,
+ -104.95332226348057,
+ -125.94287951709315,
+ -153.98745431987416,
+ -165.0213869945511,
+ -137.814725868225,
+ -157.51167762546297,
+ -167.58829421418008,
+ -168.28726500466774,
+ -131.5876920217247,
+ -156.42711746603948,
+ -136.71014098610124,
+ -139.2999723703883,
+ -145.6576378994739,
+ -175.37543539409364,
+ -181.653580221646,
+ -166.9565506300392,
+ -156.64597656783053,
+ -210.75997817765005,
+ -202.55666319096488,
+ -212.6571435863847,
+ -215.55830462279698,
+ -180.8814639274485,
+ -177.30489425581902,
+ -197.47434510709746,
+ -187.1739874672055,
+ -203.91873605298994,
+ -209.90488595803242,
+ -180.23256443157445,
+ -213.8955949105111,
+ -220.2995256710347,
+ -183.93998417330351,
+ -221.71594867616597,
+ -199.41753781224315,
+ -191.5711878850026,
+ -204.0853304505439,
+ -237.53462114951319,
+ -216.31111030430588,
+ -216.93071999849877,
+ -204.9049516644199,
+ -226.0569518783836,
+ -229.4222442584649,
+ -189.51656383524977,
+ -215.56057083096707,
+ -199.42879559594263,
+ -196.13375496245533,
+ -183.0279179837235,
+ -203.9837504405301,
+ -169.73380794746527,
+ -199.53660967477873,
+ -194.82697302885623,
+ -206.01376986029214,
+ -161.46146238097307,
+ -186.43547351094395,
+ -195.41796084285969,
+ -194.9838669987035,
+ -185.02936489124795,
+ -157.78094788127666,
+ -190.3418322346723,
+ -178.9477070901038,
+ -178.38351749653643,
+ -141.31141571876796,
+ -171.14487819582322,
+ -158.96185317116652,
+ -119.35608787781379,
+ -116.31681004845515,
+ -126.00590460554132,
+ -133.16844493022117,
+ -136.1061810404656,
+ -110.48992640014527,
+ -126.61438170240021,
+ -116.29413665920367,
+ -74.52336613423645,
+ -83.98518794890848,
+ -105.8713736062597,
+ -91.91121264770348,
+ -50.9753050791814,
+ -63.738724788767925,
+ -52.639410361223085,
+ -63.51591788315696,
+ -64.50607520558593,
+ -54.17108393230111,
+ -26.100048087959706,
+ -27.67059349650959,
+ -11.629654079091566,
+ -53.18023206204362,
+ -52.892205399162656,
+ -31.272687591157617,
+ -15.84121386554181,
+ -0.4435031191872838,
+ -11.897533752440445,
+ -9.689911239600011,
+ -19.749716215749878,
+ 12.347412789668489,
+ -7.629938266891719,
+ 32.0159838071728,
+ -2.715068195716576,
+ 46.40400837671401,
+ 44.88831620891453,
+ 36.648600109468624,
+ 9.37300122281486,
+ 60.57833051765774,
+ 35.9911620010534,
+ 35.80177932156548,
+ 47.59002593872466,
+ 34.80655280047216,
+ 64.10925429234507,
+ 56.37715209072721,
+ 82.57283479422571,
+ 69.31709098745375,
+ 40.67050215175826,
+ 75.86373208124229,
+ 84.486308941667,
+ 47.36895517891964,
+ 65.89844976185256,
+ 81.07353543420365,
+ 81.88144958320805,
+ 87.21367173804228,
+ 93.54117369159349,
+ 61.61675739253328,
+ 48.015869650315246,
+ 40.117586645245865,
+ 88.89746953558344,
+ 99.98796558671894,
+ 51.15497535543708,
+ 53.25315371285361,
+ 83.03941150067685,
+ 58.48137085157417,
+ 52.900188440297526,
+ 67.8684289059646,
+ 85.65389089127706,
+ 75.71647364159972,
+ 86.84898984976839,
+ 61.49407340530006,
+ 89.51543655378671,
+ 76.20833655762496,
+ 55.601645423962566,
+ 84.56735110113578,
+ 40.13247181645257,
+ 77.45550458977658,
+ 34.55963933491154,
+ 52.660068978426665,
+ 86.62886789652327,
+ 30.40030670786043,
+ 57.46387348674696,
+ 83.098847282247,
+ 40.482125133092225,
+ 73.16204346388011,
+ 30.4857007993763,
+ 22.645336323266026,
+ 56.0200794863957,
+ 51.77469250126795,
+ 67.1546560196787,
+ 57.87691608694495,
+ 17.41759720151727,
+ 54.878109106701125,
+ 47.493283372059445,
+ 64.57786770483617,
+ 48.993636626937906,
+ 34.1951786665862,
+ 36.3257696640557,
+ 14.568419490363965,
+ 63.23317546125337,
+ 26.418963234489517,
+ 15.31591248775866,
+ 53.99452831401371,
+ 22.985585125801872,
+ 15.244261841435254,
+ 15.23320499798178,
+ 59.28898624752186,
+ 48.983103088031285,
+ 18.450586805496407,
+ 35.37513701009135,
+ 35.31874809381071,
+ 62.220022028913625,
+ 25.20158903554477,
+ 72.47043701948445,
+ 49.47528245085166,
+ 17.745819537598152,
+ 57.03771073288418,
+ 62.3287043719062,
+ 65.662768941524,
+ 63.25011497635633,
+ 23.289204458704578,
+ 41.393225930912166,
+ 73.59501176387027,
+ 41.535832393379785,
+ 64.37236779550251,
+ 72.73765654594138,
+ 45.48900839495211,
+ 36.31587332826628,
+ 73.06956417695692,
+ 55.1598276517079,
+ 56.29052053695725,
+ 34.383252772599526,
+ 43.887287166806885,
+ 53.070289648631835,
+ 56.72548828406186,
+ 66.76859604323485,
+ 72.38274423960786,
+ 85.6341943921511,
+ 67.89554907928215,
+ 42.3776667606313,
+ 43.00662936071367,
+ 73.85951401947919,
+ 50.97403825523812,
+ 68.654898648826,
+ 69.76645870972168,
+ 91.80786484702291,
+ 94.20892590318653,
+ 73.50291525633878,
+ 63.23291432088306,
+ 73.88421574602562,
+ 70.49146714322764,
+ 102.71380005964126,
+ 99.42153275576707,
+ 92.06989401296948,
+ 111.50301413714867,
+ 94.66342298368384,
+ 103.29591018789162,
+ 91.84402167849802,
+ 83.94310914943563,
+ 82.8125796151381,
+ 73.3562654303183,
+ 101.80043137225377,
+ 107.80662349082351,
+ 95.27722522080565,
+ 71.81467649892133,
+ 86.60010093790301,
+ 84.95639197669398,
+ 47.8226256516017,
+ 64.12146255424244,
+ 45.09154603073557,
+ 76.42982041356987,
+ 50.46009054508337,
+ 78.6166059326094,
+ 92.86183373911632,
+ 38.297782341368205,
+ 58.9299706177996,
+ 55.89894202222745,
+ 77.8233515654263,
+ 33.02241035573655,
+ 78.62455450093327,
+ 31.598734151030524,
+ 21.067593095542144,
+ 20.191034028285053,
+ 30.77286098281669,
+ 12.218727266680862,
+ 63.65774554204725,
+ 41.923375149758186,
+ 58.12104212072491,
+ 29.605547811809856,
+ 22.59694723494056,
+ 15.487356055957473,
+ 3.974129006086411,
+ -0.20028201406429247,
+ 14.490088845662548,
+ -9.463395126295264,
+ -9.036101872829681,
+ -15.991779736900444,
+ -29.087589733227002,
+ 24.80789681699087,
+ 24.08515277928742,
+ 18.643593559037782,
+ -10.048052302910243,
+ -22.296350099365313,
+ -19.900681526080522,
+ -40.51956856261426,
+ 7.995279615065584,
+ -23.75762059263617,
+ -36.21280451581157,
+ -51.30653628838034,
+ -14.973451850015927,
+ -12.102776734108886,
+ -22.92141011390678,
+ -38.03739604911054,
+ -43.27241280945676,
+ -30.319328991987007,
+ -24.66755064542329,
+ -50.80561689390146,
+ -7.482679556753894,
+ -36.392132826717656,
+ -44.708312560033676,
+ -24.58273469245448,
+ -36.019147755802365,
+ -9.07348428587322,
+ -49.734001673806816,
+ -50.75452558860551,
+ -40.34966717413306,
+ -47.7580042804002,
+ -17.99447417787585,
+ -11.560948317519355,
+ -44.83684970566581,
+ -32.21333876901835,
+ -59.11691388047349,
+ -43.53779144648816,
+ -53.77359463006016,
+ -34.9652704766076,
+ -23.07934600270852,
+ -26.026669192313005,
+ 2.385634574131913,
+ -18.70104485669859,
+ -11.300389325321689,
+ 6.125584826302301,
+ 23.37806794844431,
+ 22.687228094063634,
+ -12.781593968580395,
+ 4.024895629623511,
+ -18.177437536079594,
+ 0.2663084532167517,
+ 29.698074030663772,
+ 34.02883642190601,
+ 33.43024786117799,
+ 23.76733022232676,
+ 54.17568555199081,
+ 9.443772300891151,
+ 50.71547323216697,
+ 30.62246906621458,
+ 56.47090430219434,
+ 60.74774647230859,
+ 58.0637733531682,
+ 45.639675601221555,
+ 45.08497419547462,
+ 82.30094212732857,
+ 64.96007607663661,
+ 67.1067875438888,
+ 101.27912636885212,
+ 113.44260613045327,
+ 117.63961207716953,
+ 76.57700523076957,
+ 106.78276552404587,
+ 135.63445748275967,
+ 125.46532424590501,
+ 109.41361019604425,
+ 111.1898571652688,
+ 154.03133140720854,
+ 110.16651672260186,
+ 115.1117379105762,
+ 151.97073079358177,
+ 167.2799953757663,
+ 150.92207831359377,
+ 189.06944558917516,
+ 177.98134138898382,
+ 183.59158986891072,
+ 168.9854230066182,
+ 157.19661953986713,
+ 191.54398019286833,
+ 185.581256719504,
+ 216.8584439554592,
+ 212.03212705829267,
+ 214.94509266903424,
+ 184.0472317716919,
+ 184.45100053169907,
+ 215.94288942018233,
+ 221.46986307563412,
+ 217.31009278486258,
+ 205.70291032222957,
+ 191.05597234194158,
+ 216.56367975266082,
+ 242.13532768493792,
+ 198.5688100198425,
+ 224.47493819269562,
+ 206.43918139577872,
+ 195.77914977448964,
+ 219.7158477209509,
+ 226.9544745423874,
+ 247.08763173720297,
+ 230.1312912650799,
+ 233.48892202541003,
+ 226.53187497367105,
+ 236.7443173336522,
+ 209.51612565284768,
+ 246.44058255732867,
+ 216.6046096558702,
+ 237.94045335342133,
+ 204.39929606976298,
+ 198.8653653946592,
+ 196.71700144176478,
+ 208.57619221588976,
+ 248.6702103768165,
+ 247.72840295120278,
+ 213.30271282901322,
+ 229.53134212998327,
+ 226.62201260012984,
+ 222.59591450075877,
+ 228.00479951463853,
+ 212.16892618454526,
+ 225.26419263148503,
+ 234.77964676970612,
+ 225.16698144456706,
+ 210.54173452558967,
+ 197.62070284837154,
+ 227.67369671642822,
+ 192.69235751914007,
+ 209.37202977785307,
+ 171.84575020834876,
+ 204.35901720000254,
+ 180.710386928622,
+ 215.93323544468197,
+ 169.28193247452643,
+ 160.78259662298672,
+ 212.11358448981866,
+ 168.1205288104478,
+ 165.22595195634003,
+ 174.00640560164337,
+ 181.49192271846607,
+ 201.27697673975803,
+ 176.3898072199412,
+ 172.82166493933633,
+ 199.2119263934316,
+ 139.7113400862038,
+ 139.44835065899542,
+ 167.656089617775,
+ 163.19864272385533,
+ 142.13590943908247,
+ 160.65441254454788,
+ 132.79006006458502,
+ 146.8007870451806,
+ 129.8221440428195,
+ 154.9981676706181,
+ 156.239647368755,
+ 174.37444772112264,
+ 141.54411202897427,
+ 126.37902605644943,
+ 126.63064036066785,
+ 144.475538193849,
+ 133.9653096913545,
+ 126.68079606860756,
+ 167.2200079640907,
+ 128.302397128574,
+ 165.95081194859833,
+ 155.6886986075325,
+ 124.0639520337419,
+ 124.5822340552818,
+ 159.50629965722086,
+ 171.86733728017842,
+ 132.58024511107442,
+ 149.43199725504644,
+ 115.28529859850413,
+ 138.1141289726093,
+ 162.71643472655472,
+ 124.63362898737334,
+ 121.22246289627155,
+ 132.20688964015653,
+ 120.051631816624,
+ 138.17205091830962,
+ 167.0686961702507,
+ 112.58890625024416,
+ 120.8056168337006,
+ 154.8765892293838,
+ 118.2925043142191,
+ 119.33542414059771,
+ 117.60165284894256,
+ 124.73164337030329,
+ 139.39521246705908,
+ 130.22309574168804,
+ 158.01501184558757,
+ 126.6341012253639,
+ 125.64055822192584,
+ 149.1414581737555,
+ 149.1749365230358,
+ 110.08734856226843,
+ 148.66243454279754,
+ 104.62526535773817,
+ 107.88768216514309,
+ 99.52703512581778,
+ 148.1683219154004,
+ 141.9569617235268,
+ 146.17232589785766,
+ 131.21033237251078,
+ 111.03055071737319,
+ 124.61430379706525,
+ 138.95203938686598,
+ 131.9123936043433,
+ 104.87510720051091,
+ 125.57058377977525,
+ 110.48817921955823,
+ 74.54514500049592,
+ 125.03382650808186,
+ 97.15929624757464,
+ 97.9433585837035,
+ 73.0017002826365,
+ 77.19261849445766,
+ 87.25230366657115,
+ 78.55546487918744,
+ 81.43247977022205,
+ 99.04188759502969,
+ 88.51690074505214,
+ 74.14598371176263,
+ 40.55976338644172,
+ 82.3399003384706,
+ 56.57608109007839,
+ 71.97539752267141,
+ 44.49605497703074,
+ 61.137446214883624,
+ 26.855959703764693,
+ 5.759185976780195,
+ 32.469607474829914,
+ 10.019461328137524,
+ 4.2380292782364535,
+ -1.8170551054217547,
+ -18.73290516935153,
+ -0.7243773353155305,
+ -0.7455892024436537,
+ -12.506165623330268,
+ -37.314621455031485,
+ -50.26847159607498,
+ -23.684054749044577,
+ -43.714897249838884,
+ -27.560834125382158,
+ -65.92156522987374,
+ -65.80289160938011,
+ -75.78156002111317,
+ -78.37774602993623,
+ -61.72990301702971,
+ -55.03187231073241,
+ -84.4936660460418,
+ -95.04128748737972,
+ -107.84100080068161,
+ -71.86684355142842,
+ -116.42026333988275,
+ -115.6197642656727,
+ -102.30226919293312,
+ -126.75550054723168,
+ -103.24100123123502,
+ -107.29371421953914,
+ -82.27400557125733,
+ -104.79244662967926,
+ -107.5573254870045,
+ -100.4536883913838,
+ -119.6513430965621,
+ -132.76253575875796,
+ -153.38419853777066,
+ -161.60708969802897,
+ -110.62759268400096,
+ -163.5760629792642,
+ -148.28160645329808,
+ -139.6796520256861,
+ -120.76878034790437,
+ -117.01145098266258,
+ -135.32272959100268,
+ -145.7653120340161,
+ -175.64702357968375,
+ -122.84965099640303,
+ -136.66369209205288,
+ -141.55679415700865,
+ -177.0439967777879,
+ -130.82127859560322,
+ -153.2307284872943,
+ -149.5885922447043,
+ -173.87683551499896,
+ -128.21427480771,
+ -167.5073700396225,
+ -136.4587090769843,
+ -117.20404582492924,
+ -115.7483250025667,
+ -163.08837968081792,
+ -165.9571799635692,
+ -151.49876950021172,
+ -132.75583227211303,
+ -149.44035340544457,
+ -131.46093129514352,
+ -113.67776700524311,
+ -106.9053290330692,
+ -150.15662250802313,
+ -131.3581649406843,
+ -137.18732218023973,
+ -126.53294282815509,
+ -101.34478878028523,
+ -89.1798376237136
+ ],
+ "two": [
+ 0,
+ -20.140046051127193,
+ 32.40194378594322,
+ 17.11489556824722,
+ -31.641997300365276,
+ 23.345837204746445,
+ 26.241624863099247,
+ -33.63057879133504,
+ 39.837771539273916,
+ 2.5407034698423985,
+ 6.1491265148746805,
+ -4.34935666413476,
+ 19.430202059430897,
+ -18.5052209618286,
+ -12.349645564734075,
+ 20.13299042317287,
+ 42.45485536367718,
+ -1.5857033756926455,
+ 5.111184034440981,
+ 4.61450313328001,
+ 11.182058209198544,
+ -41.5586530994744,
+ 37.57017529247706,
+ -32.19451827527348,
+ 56.16509392049119,
+ -13.735011673761875,
+ 2.162270945870546,
+ -12.709991589342593,
+ -0.9906571207720276,
+ 11.827345283592592,
+ 12.112480340647522,
+ -18.29468207003606,
+ 39.65632298455404,
+ -21.160008244450523,
+ -19.550008076688357,
+ 7.8748004479660665,
+ 21.023455674836242,
+ -28.63685866625005,
+ 54.9578309774102,
+ -15.029475988375111,
+ 4.618830030021314,
+ -20.433134980360023,
+ 26.13109229630959,
+ -3.286104965291514,
+ -37.654942187389395,
+ 15.078326101247256,
+ -14.48067908006314,
+ 53.882603813370224,
+ -40.00581304824735,
+ 38.925699147879044,
+ -47.57330629724436,
+ -1.4240262313161907,
+ -4.3653786955875375,
+ 0.6593969505899793,
+ 22.60664378002045,
+ -2.1382162102705706,
+ -27.63611944924824,
+ 38.3622597740478,
+ -37.5413991806486,
+ 0.5076580539551401,
+ 22.22137176079724,
+ -9.464872472736715,
+ 25.93341983583022,
+ -23.50902150726867,
+ -11.70226967779658,
+ -11.223172417308234,
+ 3.4258707367555417,
+ 23.452198804680307,
+ 4.322918463030021,
+ 7.9964950785187625,
+ -30.966519385262927,
+ -19.023108911643988,
+ 23.038025270745294,
+ -17.76464267584163,
+ 2.816430385682935,
+ 14.725813809586867,
+ 11.68797899525859,
+ -5.595838214383747,
+ -7.285834379618393,
+ -0.7011414424410418,
+ -33.21487992979817,
+ 41.544750177338784,
+ -0.9115277982481018,
+ -17.665001161899056,
+ -2.5102370067941706,
+ -37.501112328754346,
+ -2.405921989301021,
+ 28.937930772722183,
+ -24.88717973446998,
+ 20.557439860686316,
+ -31.02855440620432,
+ 28.761473655696122,
+ 6.140367402573901,
+ -19.897339522501113,
+ -2.170023235060526,
+ 16.040851415961512,
+ 5.557663646232271,
+ -3.5231423719693566,
+ 12.052928677978997,
+ -12.62820998709094,
+ -22.821426767348484,
+ -12.873135869597903,
+ 19.662094230067368,
+ 10.768108950072246,
+ -21.750104045272437,
+ 38.610990687645526,
+ -52.82051888266071,
+ 10.869955520208592,
+ -7.4007600585360365,
+ 44.311461580863195,
+ -42.65393657627007,
+ 43.614248564434746,
+ -16.518037819725777,
+ 2.231896377697865,
+ -34.35191696783906,
+ 40.879979098138506,
+ -0.9709519828140856,
+ -4.298531756140363,
+ -7.8937670930225465,
+ -1.580115333411726,
+ -18.453327760813153,
+ 20.101844911160242,
+ 25.781027920257202,
+ -10.264797676831193,
+ -4.874004047811752,
+ -3.047060695182637,
+ 12.576474891603851,
+ 2.8981272119616506,
+ 1.45385851265317,
+ -17.424587550831177,
+ -16.86560017558881,
+ 6.0878761356488695,
+ 9.799211491474395,
+ -27.27041882018321,
+ 14.347321521672228,
+ 15.209361215790949,
+ -8.030016307687106,
+ 31.51510990544068,
+ -20.931628688370154,
+ 6.5492943564158566,
+ -41.926456127300355,
+ 31.642370459135662,
+ 21.834855014026374,
+ -52.42870473789894,
+ 34.70747701267568,
+ -37.89727764346921,
+ 19.786466939168008,
+ -7.677702679550052,
+ -9.088628611982173,
+ 5.534822151825367,
+ -4.329402762642715,
+ 14.309268301921904,
+ -7.877337476514228,
+ 29.504507189147375,
+ -19.409277930406944,
+ -3.6094884788232093,
+ -19.278397826271288,
+ -1.2033326243676186,
+ 43.59566410431961,
+ -9.810976864166264,
+ -0.40728373766290815,
+ -49.5159597722768,
+ 26.51985316670249,
+ 7.545205925644154,
+ 1.732765721518831,
+ 0.7509070255809718,
+ -41.231461759800325,
+ 15.43056479431172,
+ -28.986027747663442,
+ 42.92014583028282,
+ 6.092984938945719,
+ -55.164352434841604,
+ 30.559538868256908,
+ 12.23982072421748,
+ -41.35542582871372,
+ 8.744685555624699,
+ -6.864052647605618,
+ 28.470886904947562,
+ -6.169503630041078,
+ -9.491351403076067,
+ -13.034976367453368,
+ 4.408728303227093,
+ -31.371074165480778,
+ 9.702890944633445,
+ -26.99192928297062,
+ 18.535630502888264,
+ -15.133672559181512,
+ 31.169391105269483,
+ -9.043716890385255,
+ -24.83485223912269,
+ -3.186363144043975,
+ -17.188497439833924,
+ 16.01775070203746,
+ 18.1781265491798,
+ -41.62342575637017,
+ 33.91916064224674,
+ -8.4805520653228,
+ -33.43102364989055,
+ 1.800707726077647,
+ 30.678634783757197,
+ -0.42336822622312553,
+ -9.537298683871121,
+ -11.819536753028409,
+ -7.939501849906282,
+ 31.964033340771795,
+ -3.5184157229735007,
+ -55.20578299661652,
+ 41.091128160465566,
+ 1.0380462910652284,
+ -10.3845683894356,
+ 10.072987209031197,
+ 7.4112292595116855,
+ -27.912271448787852,
+ 14.176662314594179,
+ -33.95924104926749,
+ -10.951590053579281,
+ 32.46393462566422,
+ -4.920305626207288,
+ -8.778068668769357,
+ 23.325697157931906,
+ -11.746714431373611,
+ 3.421172245108668,
+ 19.206104756211335,
+ 10.04898487235318,
+ -23.268880042945867,
+ 25.55954078826565,
+ -10.554000470391195,
+ -33.156319372746836,
+ 20.40042315083031,
+ 32.70069187102656,
+ -34.52571564199579,
+ 36.25428441257617,
+ -12.984286349257577,
+ 20.191155408420478,
+ -1.1625515747425226,
+ -42.96960896788477,
+ 35.28608236967473,
+ -23.473604521077785,
+ -4.033273119024796,
+ 7.3330600891071755,
+ 33.19849043282578,
+ -30.22744954340816,
+ 48.525753780352176,
+ 13.363100869306031,
+ 5.036766314409434,
+ -51.12025061863558,
+ 54.84743085434002,
+ -10.787061545369966,
+ -6.726365036539917,
+ -3.0096948236540086,
+ 25.35311242278913,
+ -26.321376862919635,
+ 6.409046915014699,
+ -2.8037522028169874,
+ 3.5124668218130495,
+ 17.093520177836687,
+ 36.782086040259514,
+ -3.1054026399928887,
+ -15.474178207259854,
+ -20.05836008600302,
+ 6.061205203385313,
+ 31.406614676340155,
+ -0.5639325541945936,
+ -16.354320073656865,
+ 8.094769080482493,
+ 10.139745650590896,
+ 20.915259507225812,
+ -23.052909981925083,
+ 15.82473261940767,
+ -15.818730740105067,
+ 50.0464571941193,
+ -11.2909981194328,
+ -3.025307846983395,
+ 16.17519212974568,
+ -0.3582820591651057,
+ -11.249733167985454,
+ -24.852248436957865,
+ 58.78528118411663,
+ -32.265637108062606,
+ 35.920018829749154,
+ -8.054069380992757,
+ -33.2876098391001,
+ 6.971585144116062,
+ 26.012909596611422,
+ -19.079097883455404,
+ 18.620563094301957,
+ 16.34330958587728,
+ -23.844671333143793,
+ -19.78645310200585,
+ 43.36879281790763,
+ -18.337042661092966,
+ -12.105301743812703,
+ 0.3755529377841924,
+ 21.733585645405526,
+ 0.6122802648169738,
+ -20.56856206578547,
+ -19.952595095070535,
+ -1.7871848950803724,
+ -0.25936507833384326,
+ 33.702538581803054,
+ -23.938432703600455,
+ 16.323343404804888,
+ 3.794230426180917,
+ 0.9583280165316808,
+ -5.214885181646764,
+ -19.791966888822742,
+ -6.455730791261573,
+ -7.356195397248655,
+ -10.889091592424535,
+ -17.4717808675247,
+ 33.57748911447399,
+ 5.16992257674346,
+ -29.293469699177578,
+ 14.377967502478114,
+ -1.0416271586128687,
+ 6.528134445414338,
+ -9.872979018505134,
+ 13.472691457955392,
+ -48.41701187763091,
+ 2.7977978964518684,
+ 7.456589413225075,
+ -13.304251022988495,
+ 23.716213754132724,
+ 6.311671372517424,
+ -45.634623555818976,
+ 27.168711108186315,
+ -30.121514249296055,
+ -3.8269535065942506,
+ -18.580126879339865,
+ 35.07560452040325,
+ -5.67043974022954,
+ 7.660785539104296,
+ -9.192213717491924,
+ -41.58637301127593,
+ 35.33607991533669,
+ -11.434970667357305,
+ -28.037363575528033,
+ 37.02398355357187,
+ -20.08649554703768,
+ -20.059504352862376,
+ 2.159393595155585,
+ 21.098543070605004,
+ 7.2066112822768815,
+ -44.12272392727374,
+ 39.142185543319684,
+ -2.5638787714328046,
+ -19.99381266532579,
+ -3.3215300036289577,
+ -16.62245774490867,
+ 12.09528505722368,
+ 3.8237611603808546,
+ -21.41074594870701,
+ 12.27971874680177,
+ -9.78408102963503,
+ -3.488771206285275,
+ -10.368031684592742,
+ -1.615788353228746,
+ -12.961897356094976,
+ 0.4921958565948419,
+ 27.43996298260224,
+ 2.1715701458580448,
+ 8.065416157830143,
+ -9.810559471162083,
+ 7.738018304942575,
+ -34.865998055124194,
+ 1.238459130885854,
+ 20.662266608709643,
+ -5.2304091769550425,
+ 4.07196955915871,
+ 12.187959030266573,
+ -9.82694400294318,
+ 11.647407129622628,
+ -25.67841463464783,
+ -9.213810051957001,
+ 10.063963253024614,
+ 16.083827560266485,
+ 1.8802132099369544,
+ -3.656020370412591,
+ 9.112794073845052,
+ -42.35064649540439,
+ -7.263060518352329,
+ 50.120012957324356,
+ -41.15305276956162,
+ 14.847747280602881,
+ -6.979415575389375,
+ 25.321191715450503,
+ -33.867614999486584,
+ -2.2097811490072843,
+ 12.39853147007097,
+ -8.956437797981426,
+ 23.465843662964314,
+ -23.072980619769293,
+ -14.336447825730605,
+ -11.481431197330387,
+ 21.153036660634058,
+ 7.673233469197875,
+ 3.677296026257281,
+ 10.665104479015838,
+ -40.835412424485384,
+ -6.754074910122455,
+ 25.03512192997632,
+ -10.897163005037733,
+ 16.441011387737092,
+ -1.3371885631572198,
+ -21.569352758976812,
+ -4.275302337096733,
+ -18.0876911154063,
+ -0.04010088019482794,
+ 33.114160400850615,
+ 7.030370858000865,
+ -16.232257025010817,
+ -8.365256435291734,
+ -28.840352974483018,
+ 4.893548675446752,
+ -14.044856827026578,
+ 24.578910938472333,
+ -15.981908890321947,
+ -11.229228426694505,
+ 20.444351814517674,
+ -9.802030147811053,
+ -33.94000254961186,
+ 47.543949412578115,
+ -49.02326142871058,
+ 42.795275382464325,
+ -39.97962807289126,
+ -23.835508245890324,
+ 37.966342689486254,
+ -13.93217184757789,
+ 2.6297596972095363,
+ -17.55147756433024,
+ -6.561330399648256,
+ 28.802614605157345,
+ -20.989557253612574,
+ -28.044574802781014,
+ -11.033932674676947,
+ 27.20666112632611,
+ -19.69695175723797,
+ -10.076616588717116,
+ -0.6989707904876639,
+ 36.69957298294304,
+ -24.83942544431477,
+ 19.71697647993824,
+ -2.5898313842870664,
+ -6.357665529085608,
+ -29.71779749461973,
+ -6.278144827552353,
+ 14.697029591606793,
+ 10.310574062208673,
+ -54.114001609819525,
+ 8.203314986685172,
+ -10.100480395419822,
+ -2.901161036412276,
+ 34.67684069534849,
+ 3.5765696716294713,
+ -20.16945085127844,
+ 10.300357639891956,
+ -16.744748585784436,
+ -5.986149905042481,
+ 29.672321526457978,
+ -33.66303047893666,
+ -6.403930760523593,
+ 36.359541497731186,
+ -37.77596450286245,
+ 22.29841086392281,
+ 7.846349927240567,
+ -12.514142565541306,
+ -33.44929069896929,
+ 21.2235108452073,
+ -0.6196096941928886,
+ 12.025768334078862,
+ -21.152000213963674,
+ -3.365292380081314,
+ 39.90568042321513,
+ -26.044006995717297,
+ 16.131775235024435,
+ 3.2950406334873037,
+ 13.105836978731816,
+ -20.955832456806576,
+ 34.24994249306482,
+ -29.802801727313465,
+ 4.709636645922501,
+ -11.186796831435913,
+ 44.55230747931907,
+ -24.974011129970876,
+ -8.982487331915735,
+ 0.43409384415619456,
+ 9.95450210745554,
+ 27.24841700997129,
+ -32.560884353395636,
+ 11.394125144568505,
+ 0.5641895935673631,
+ 37.07210177776847,
+ -29.833462477055264,
+ 12.1830250246567,
+ 39.60576529335273,
+ 3.039277829358639,
+ -9.689094557086165,
+ -7.162540324679853,
+ -2.9377361102444297,
+ 25.616254640320335,
+ -16.124455302254944,
+ 10.320245043196536,
+ 41.77077052496722,
+ -9.461821814672021,
+ -21.88618565735122,
+ 13.960160958556216,
+ 40.935907568522076,
+ -12.763419709586522,
+ 11.09931442754484,
+ -10.876507521933874,
+ -0.9901573224289706,
+ 10.334991273284821,
+ 28.071035844341402,
+ -1.570545408549883,
+ 16.040939417418024,
+ -41.550577982952056,
+ 0.28802666288096646,
+ 21.61951780800504,
+ 15.431473725615806,
+ 15.397710746354527,
+ -11.454030633253161,
+ 2.207622512840434,
+ -10.059804976149866,
+ 32.09712900541837,
+ -19.97735105656021,
+ 39.64592207406452,
+ -34.73105200288938,
+ 49.119076572430586,
+ -1.5156921677994788,
+ -8.239716099445907,
+ -27.275598886653764,
+ 51.20532929484288,
+ -24.587168516604343,
+ -0.1893826794879132,
+ 11.788246617159174,
+ -12.783473138252496,
+ 29.30270149187291,
+ -7.732102201617863,
+ 26.195682703498505,
+ -13.25574380677196,
+ -28.646588835695496,
+ 35.19322992948403,
+ 8.622576860424715,
+ -37.11735376274736,
+ 18.52949458293292,
+ 15.175085672351088,
+ 0.8079141490044037,
+ 5.332222154834227,
+ 6.327501953551206,
+ -31.924416299060205,
+ -13.600887742218035,
+ -7.898283005069381,
+ 48.779882890337575,
+ 11.090496051135503,
+ -48.83299023128186,
+ 2.098178357416529,
+ 29.78625778782324,
+ -24.55804064910268,
+ -5.581182411276643,
+ 14.96824046566708,
+ 17.785461985312452,
+ -9.937417249677338,
+ 11.132516208168667,
+ -25.35491644446833,
+ 28.021363148486657,
+ -13.307099996161753,
+ -20.606691133662395,
+ 28.96570567717322,
+ -44.43487928468321,
+ 37.32303277332401,
+ -42.89586525486504,
+ 18.100429643515128,
+ 33.96879891809661,
+ -56.228561188662844,
+ 27.063566778886532,
+ 25.63497379550003,
+ -42.61672214915477,
+ 32.679918330787885,
+ -42.67634266450381,
+ -7.840364476110274,
+ 33.37474316312968,
+ -4.245386985127752,
+ 15.379963518410747,
+ -9.277739932733745,
+ -40.45931888542768,
+ 37.46051190518385,
+ -7.38482573464168,
+ 17.08458433277672,
+ -15.58423107789826,
+ -14.798457960351705,
+ 2.130590997469497,
+ -21.757350173691734,
+ 48.6647559708894,
+ -36.81421222676385,
+ -11.103050746730858,
+ 38.67861582625505,
+ -31.008943188211834,
+ -7.741323284366619,
+ -0.01105684345347413,
+ 44.05578124954008,
+ -10.305883159490577,
+ -30.53251628253488,
+ 16.924550204594944,
+ -0.05638891628063902,
+ 26.901273935102914,
+ -37.018432993368855,
+ 47.26884798393968,
+ -22.995154568632792,
+ -31.729462913253506,
+ 39.29189119528603,
+ 5.29099363902202,
+ 3.3340645696178086,
+ -2.412653965167678,
+ -39.960910517651755,
+ 18.104021472207588,
+ 32.20178583295811,
+ -32.05917937049049,
+ 22.836535402122728,
+ 8.365288750438864,
+ -27.24864815098927,
+ -9.17313506668583,
+ 36.75369084869064,
+ -17.909736525249016,
+ 1.1306928852493527,
+ -21.907267764357726,
+ 9.504034394207359,
+ 9.18300248182495,
+ 3.6551986354300254,
+ 10.04310775917299,
+ 5.614148196373009,
+ 13.251450152543242,
+ -17.738645312868954,
+ -25.517882318650848,
+ 0.6289626000823674,
+ 30.852884658765525,
+ -22.88547576424107,
+ 17.680860393587885,
+ 1.1115600608956697,
+ 22.041406137301237,
+ 2.4010610561636128,
+ -20.706010646847744,
+ -10.27000093545572,
+ 10.651301425142556,
+ -3.3927486027979796,
+ 32.22233291641362,
+ -3.2922673038741834,
+ -7.35163874279759,
+ 19.433120124179183,
+ -16.839591153464823,
+ 8.632487204207777,
+ -11.451888509393598,
+ -7.900912529062396,
+ -1.1305295342975228,
+ -9.456314184819803,
+ 28.444165941935466,
+ 6.006192118569743,
+ -12.529398270017865,
+ -23.462548721884318,
+ 14.785424438981678,
+ -1.643708961209029,
+ -37.13376632509228,
+ 16.298836902640744,
+ -19.02991652350687,
+ 31.3382743828343,
+ -25.969729868486503,
+ 28.156515387526035,
+ 14.245227806506918,
+ -54.564051397748116,
+ 20.632188276431393,
+ -3.0310285955721454,
+ 21.92440954319884,
+ -44.800941209689746,
+ 45.602144145196725,
+ -47.025820349902745,
+ -10.53114105548838,
+ -0.8765590672570909,
+ 10.581826954531639,
+ -18.55413371613583,
+ 51.439018275366394,
+ -21.734370392289065,
+ 16.197666970966722,
+ -28.515494308915052,
+ -7.008600576869295,
+ -7.109591178983088,
+ -11.513227049871062,
+ -4.174411020150703,
+ 14.69037085972684,
+ -23.953483971957812,
+ 0.42729325346558333,
+ -6.955677864070763,
+ -13.095809996326558,
+ 53.89548655021787,
+ -0.7227440377034497,
+ -5.441559220249637,
+ -28.691645861948025,
+ -12.24829779645507,
+ 2.395668573284791,
+ -20.61888703653374,
+ 48.514848177679845,
+ -31.752900207701753,
+ -12.455183923175401,
+ -15.093731772568773,
+ 36.333084438364416,
+ 2.8706751159070407,
+ -10.818633379797895,
+ -15.11598593520376,
+ -5.23501676034622,
+ 12.953083817469754,
+ 5.651778346563717,
+ -26.138066248478168,
+ 43.322937337147565,
+ -28.909453269963763,
+ -8.31617973331602,
+ 20.125577867579196,
+ -11.436413063347885,
+ 26.945663469929144,
+ -40.660517387933595,
+ -1.0205239147986944,
+ 10.40485841447245,
+ -7.40833710626714,
+ 29.76353010252435,
+ 6.433525860356497,
+ -33.27590138814646,
+ 12.623510936647463,
+ -26.90357511145514,
+ 15.579122433985333,
+ -10.235803183572003,
+ 18.80832415345256,
+ 11.885924473899081,
+ -2.947323189604486,
+ 28.412303766444918,
+ -21.086679430830504,
+ 7.400655531376902,
+ 17.42597415162399,
+ 17.25248312214201,
+ -0.6908398543806769,
+ -35.46882206264403,
+ 16.806489598203907,
+ -22.202333165703106,
+ 18.443745989296346,
+ 29.43176557744702,
+ 4.330762391242235,
+ -0.5985885607280181,
+ -9.66291763885123,
+ 30.40835532966405,
+ -44.731913251099655,
+ 41.27170093127582,
+ -20.09300416595239,
+ 25.84843523597976,
+ 4.27684217011425,
+ -2.68397311914039,
+ -12.424097751946647,
+ -0.5547014057469326,
+ 37.215967931853946,
+ -17.340866050691957,
+ 2.1467114672521888,
+ 34.17233882496332,
+ 12.163479761601153,
+ 4.19700594671626,
+ -41.06260684639996,
+ 30.2057602932763,
+ 28.8516919587138,
+ -10.169133236854663,
+ -16.051714049860763,
+ 1.7762469692245588,
+ 42.841474241939736,
+ -43.864814684606685,
+ 4.94522118797434,
+ 36.85899288300557,
+ 15.30926458218454,
+ -16.357917062172532,
+ 38.14736727558139,
+ -11.088104200191339,
+ 5.610248479926895,
+ -14.606166862292525,
+ -11.788803466751062,
+ 34.3473606530012,
+ -5.962723473364321,
+ 31.277187235955182,
+ -4.826316897166521,
+ 2.9129656107415656,
+ -30.897860897342326,
+ 0.40376876000715356,
+ 31.49188888848326,
+ 5.526973655451798,
+ -4.159770290771547,
+ -11.607182462633006,
+ -14.646937980287987,
+ 25.507707410719235,
+ 25.571647932277102,
+ -43.56651766509543,
+ 25.90612817285313,
+ -18.0357567969169,
+ -10.660031621289079,
+ 23.936697946461265,
+ 7.2386268214364975,
+ 20.133157194815567,
+ -16.95634047212306,
+ 3.3576307603301245,
+ -6.957047051738982,
+ 10.21244235998114,
+ -27.228191680804514,
+ 36.92445690448099,
+ -29.83597290145846,
+ 21.335843697551127,
+ -33.54115728365835,
+ -5.5339306751037896,
+ -2.148363952894414,
+ 11.859190774124983,
+ 40.09401816092674,
+ -0.9418074256137174,
+ -34.425690122189565,
+ 16.228629300970056,
+ -2.909329529853437,
+ -4.026098099371069,
+ 5.408885013879768,
+ -15.835873330093278,
+ 13.095266446939775,
+ 9.515454138221088,
+ -9.612665325139062,
+ -14.625246918977382,
+ -12.921031677218139,
+ 30.05299386805669,
+ -34.98133919728815,
+ 16.679672258712998,
+ -37.52627956950431,
+ 32.51326699165378,
+ -23.64863027138054,
+ 35.22284851605997,
+ -46.65130297015554,
+ -8.499335851539712,
+ 51.33098786683195,
+ -43.993055679370855,
+ -2.8945768541077825,
+ 8.78045364530334,
+ 7.485517116822706,
+ 19.785054021291955,
+ -24.887169519816837,
+ -3.56814228060486,
+ 26.39026145409528,
+ -59.50058630722782,
+ -0.2629894272083675,
+ 28.207738958779572,
+ -4.457446893919666,
+ -21.06273328477286,
+ 18.518503105465413,
+ -27.86435247996286,
+ 14.010726980595592,
+ -16.97864300236111,
+ 25.1760236277986,
+ 1.2414796981368852,
+ 18.134800352367648,
+ -32.83033569214837,
+ -15.165085972524835,
+ 0.2516143042184211,
+ 17.844897833181136,
+ -10.510228502494499,
+ -7.284513622746928,
+ 40.53921189548315,
+ -38.91761083551671,
+ 37.64841482002433,
+ -10.262113341065827,
+ -31.624746573790603,
+ 0.5182820215399033,
+ 34.92406560193906,
+ 12.361037622957554,
+ -39.287092169104,
+ 16.85175214397202,
+ -34.14669865654231,
+ 22.82883037410518,
+ 24.602305753945416,
+ -38.08280573918138,
+ -3.4111660911017907,
+ 10.984426743884981,
+ -12.155257823532537,
+ 18.120419101685627,
+ 28.896645251941067,
+ -54.479789920006525,
+ 8.216710583456432,
+ 34.0709723956832,
+ -36.584084915164695,
+ 1.0429198263786077,
+ -1.7337712916551453,
+ 7.129990521360725,
+ 14.663569096755793,
+ -9.17211672537104,
+ 27.791916103899524,
+ -31.38091062022366,
+ -0.993543003438063,
+ 23.500899951829652,
+ 0.033478349280301245,
+ -39.087587960767365,
+ 38.57508598052911,
+ -44.03716918505937,
+ 3.262416807404918,
+ -8.360647039325315,
+ 48.64128678958264,
+ -6.211360191873609,
+ 4.215364174330858,
+ -14.961993525346884,
+ -20.17978165513759,
+ 13.583753079692059,
+ 14.33773558980073,
+ -7.039645782522683,
+ -27.037286403832383,
+ 20.695476579264337,
+ -15.082404560217014,
+ -35.94303421906231,
+ 50.48868150758594,
+ -27.874530260507214,
+ 0.784062336128855,
+ -24.941658301066994,
+ 4.190918211821156,
+ 10.05968517211349,
+ -8.696838787383712,
+ 2.877014891034605,
+ 17.609407824807647,
+ -10.524986849977552,
+ -14.370917033289516,
+ -33.5862203253209,
+ 41.780136952028876,
+ -25.76381924839221,
+ 15.399316432593025,
+ -27.479342545640677,
+ 16.641391237852886,
+ -34.28148651111893,
+ -21.096773726984498,
+ 26.71042149804972,
+ -22.45014614669239,
+ -5.7814320499010705,
+ -6.055084383658208,
+ -16.915850063929774,
+ 18.008527834036,
+ -0.021211867128123174,
+ -11.760576420886615,
+ -24.808455831701217,
+ -12.953850141043496,
+ 26.584416847030404,
+ -20.030842500794307,
+ 16.154063124456727,
+ -38.36073110449158,
+ 0.11867362049362384,
+ -9.978668411733054,
+ -2.596186008823068,
+ 16.64784301290652,
+ 6.698030706297303,
+ -29.461793735309385,
+ -10.547621441337924,
+ -12.799713313301893,
+ 35.974157249253196,
+ -44.553419788454335,
+ 0.8004990742100517,
+ 13.317495072739575,
+ -24.453231354298552,
+ 23.51449931599666,
+ -4.052712988304123,
+ 25.019708648281807,
+ -22.518441058421928,
+ -2.764878857325243,
+ 7.103637095620698,
+ -19.197654705178294,
+ -13.111192662195862,
+ -20.621662779012695,
+ -8.222891160258314,
+ 50.97949701402801,
+ -52.94847029526325,
+ 15.294456525966126,
+ 8.601954427611986,
+ 18.910871677781728,
+ 3.7573293652417874,
+ -18.311278608340103,
+ -10.442582443013407,
+ -29.881711545667656,
+ 52.79737258328072,
+ -13.814041095649856,
+ -4.8931020649557695,
+ -35.48720262077924,
+ 46.22271818218468,
+ -22.409449891691082,
+ 3.6421362425899986,
+ -24.288243270294657,
+ 45.66256070728895,
+ -39.293095231912474,
+ 31.048660962638195,
+ 19.254663252055053,
+ 1.4557208223625366,
+ -47.34005467825122,
+ -2.868800282751266,
+ 14.458410463357467,
+ 18.742937228098697,
+ -16.684521133331543,
+ 17.979422110301044,
+ 17.783164289900412,
+ 6.772437972173918,
+ -43.25129347495394,
+ 18.79845756733883,
+ -5.829157239555428,
+ 10.654379352084646,
+ 25.18815404786986,
+ 12.164951156571632
+ ]
+ }
+}
\ No newline at end of file
diff --git a/web/libs/editor/tests/e2e/examples/image-keypoints.js b/web/libs/editor/tests/e2e/examples/image-keypoints.js
index b5b813705cb0..ed7d1cd0b967 100644
--- a/web/libs/editor/tests/e2e/examples/image-keypoints.js
+++ b/web/libs/editor/tests/e2e/examples/image-keypoints.js
@@ -9,7 +9,7 @@ const config = `
`;
const data = {
- image: 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg',
+ image: 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg',
};
const result = [
@@ -18,14 +18,14 @@ const result = [
from_name: 'tag',
to_name: 'img',
image_rotation: 0,
- original_height: 501,
- original_width: 800,
+ original_height: 576,
+ original_width: 768,
type: 'keypointlabels',
origin: 'manual',
value: {
x: 49.60000000000001,
y: 52.34042553191488,
- width: 0.6471078324314267,
+ width: 0.6120428759942558,
keypointlabels: ['Hello'],
},
},
@@ -34,14 +34,14 @@ const result = [
from_name: 'tag',
to_name: 'img',
image_rotation: 0,
- original_height: 501,
- original_width: 800,
+ original_height: 576,
+ original_width: 768,
type: 'keypointlabels',
origin: 'manual',
value: {
x: 47.73333333333334,
y: 52.765957446808514,
- width: 0.6666666666666666,
+ width: 0.6305418719211823,
keypointlabels: ['World'],
},
},
diff --git a/web/libs/editor/tests/e2e/fragments/AtImageView.js b/web/libs/editor/tests/e2e/fragments/AtImageView.js
index ad2ccf152eca..ff02528e080a 100644
--- a/web/libs/editor/tests/e2e/fragments/AtImageView.js
+++ b/web/libs/editor/tests/e2e/fragments/AtImageView.js
@@ -79,6 +79,12 @@ module.exports = {
I.waitForVisible('canvas', 5);
},
+ async getNaturalSize() {
+ const sizes = await I.executeScript(Helpers.getNaturalSize);
+
+ return sizes;
+ },
+
async getCanvasSize() {
const sizes = await I.executeScript(Helpers.getCanvasSize);
diff --git a/web/libs/editor/tests/e2e/tests/audio/audio-errors.test.js b/web/libs/editor/tests/e2e/tests/audio/audio-errors.test.js
index 74af7d18905d..40380abae70d 100644
--- a/web/libs/editor/tests/e2e/tests/audio/audio-errors.test.js
+++ b/web/libs/editor/tests/e2e/tests/audio/audio-errors.test.js
@@ -49,7 +49,7 @@ Scenario('Check if audio decoder error handler is showing', async function({ I,
annotations: [{ id: 'test', result: annotations }],
config,
data: {
- url: '/public/files/video.mp4', // mp4 is not supported by audio decoder
+ url: '/files/video.mp4', // mp4 is not supported by audio decoder
},
});
diff --git a/web/libs/editor/tests/e2e/tests/helpers.js b/web/libs/editor/tests/e2e/tests/helpers.js
index 4ab0c40e1333..ff4caf203bca 100644
--- a/web/libs/editor/tests/e2e/tests/helpers.js
+++ b/web/libs/editor/tests/e2e/tests/helpers.js
@@ -1,3 +1,5 @@
+const assert = require('assert');
+
/**
* Load custom example
* @param {object} params
@@ -157,7 +159,7 @@ const createAddEventListenerScript = (eventName, callback) => {
* Wait for the main Image object to be loaded
*/
const waitForImage = () => {
- return new Promise((resolve) => {
+ return new Promise((resolve, reject) => {
const img = document.querySelector('[alt=LS]');
if (!img || img.complete) return resolve();
@@ -165,6 +167,8 @@ const waitForImage = () => {
img.onload = () => {
setTimeout(resolve, 100);
};
+ // if image is not loaded in 10 seconds, reject
+ setTimeout(reject, 10000);
});
};
@@ -232,7 +236,7 @@ const convertToFixed = (data, fractionDigits = 2) => {
if (['string', 'number'].includes(typeof data)) {
const n = Number(data);
- return Number.isNaN(n) ? data : Number.isInteger(n) ? n : +Number(n).toFixed(fractionDigits);
+ return Number.isNaN(n) ? data : Number.isInteger(n) ? n : +n.toFixed(fractionDigits);
}
if (Array.isArray(data)) {
return data.map(n => convertToFixed(n, fractionDigits));
@@ -531,6 +535,14 @@ async function generateImageUrl({ width, height }) {
return canvas.toDataURL();
}
+const getNaturalSize = () => {
+ const imageObject = window.Htx.annotationStore.selected.objects.find(o => o.type === 'image');
+
+ return {
+ width: imageObject.naturalWidth,
+ height: imageObject.naturalHeight,
+ };
+};
const getCanvasSize = () => {
const imageObject = window.Htx.annotationStore.selected.objects.find(o => o.type === 'image');
@@ -813,6 +825,16 @@ function hasSelectedRegion() {
return !!Htx.annotationStore.selected.highlightedNode;
}
+async function doDrawingAction(I, { msg, fromX, fromY, toX, toY }) {
+ I.usePlaywrightTo(msg, async ({ browser, browserContext, page }) => {
+ await page.mouse.move(fromX, fromY);
+ await page.mouse.down();
+ await page.mouse.move(toX, toY);
+ await page.mouse.up();
+ });
+ I.wait(1); // Ensure that the tool is fully finished being created.
+}
+
// `mulberry32` (simple generator with a 32-bit state)
function createRandomWithSeed(seed) {
return function() {
@@ -823,6 +845,7 @@ function createRandomWithSeed(seed) {
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
+
function createRandomIntWithSeed(seed) {
const random = createRandomWithSeed(seed);
@@ -857,6 +880,7 @@ module.exports = {
areEqualRGB,
hasKonvaPixelColorAtPoint,
getKonvaPixelColorFromPoint,
+ getNaturalSize,
getCanvasSize,
getImageSize,
getImageFrameSize,
@@ -883,6 +907,7 @@ module.exports = {
omitBy,
dumpJSON,
+ doDrawingAction,
createRandomWithSeed,
createRandomIntWithSeed,
};
diff --git a/web/libs/editor/tests/e2e/tests/image.gestures.test.js b/web/libs/editor/tests/e2e/tests/image.gestures.test.js
index bea2c3c614c4..5985f4ca23fb 100644
--- a/web/libs/editor/tests/e2e/tests/image.gestures.test.js
+++ b/web/libs/editor/tests/e2e/tests/image.gestures.test.js
@@ -36,7 +36,7 @@ const createShape = {
byMultipleClicks(x, y, radius, opts = {}) {
const points = [];
- for (let i = 5; i--; ) {
+ for (let i = 5; i--;) {
points.push([x + Math.sin(((2 * Math.PI) / 5) * i) * radius, y - Math.cos(((2 * Math.PI) / 5) * i) * radius]);
points.push([
x + (Math.sin(((2 * Math.PI) / 5) * (i - 0.5)) * radius) / 3,
@@ -209,7 +209,7 @@ Scenario('Creating regions by various gestures', async function({ I, AtImageView
for (const [idx, region] of Object.entries(regions)) {
I.pressKey(region.hotKey);
AtImageView[region.action](...region.params);
- AtSidebar.seeRegions(+idx+1);
+ AtSidebar.seeRegions(+idx + 1);
}
const result = await I.executeScript(serialize);
diff --git a/web/libs/editor/tests/e2e/tests/image.magic-wand.test.js b/web/libs/editor/tests/e2e/tests/image.magic-wand.test.js
index a012f383a6f6..96cef8ff7b26 100644
--- a/web/libs/editor/tests/e2e/tests/image.magic-wand.test.js
+++ b/web/libs/editor/tests/e2e/tests/image.magic-wand.test.js
@@ -1,9 +1,9 @@
const {
initLabelStudio,
+ doDrawingAction,
hasKonvaPixelColorAtPoint,
setKonvaLayersOpacity,
serialize,
- waitForImage,
} = require('./helpers');
const assert = require('assert');
@@ -42,8 +42,6 @@ const annotationEmpty = {
result: [],
};
-// TODO: Change these URLs to heartex URLs, and ensure the heartex bucket allows CORS access
-// for these to work.
const data = {
'image': [
'http://htx-pub.s3.amazonaws.com/samples/magicwand/magic_wand_scale_1_20200902_015806_26_2235_1B_AnalyticMS_00750_00750.jpg',
@@ -54,16 +52,6 @@ const data = {
'thumb': 'http://htx-pub.s3.amazonaws.com/samples/magicwand/magic_wand_thumbnail_20200902_015806_26_2235_1B_AnalyticMS_00750_00750.jpg',
};
-async function magicWand(I, { msg, fromX, fromY, toX, toY }) {
- I.usePlaywrightTo(msg, async ({ page }) => {
- await page.mouse.move(fromX, fromY);
- await page.mouse.down();
- await page.mouse.move(toX, toY);
- await page.mouse.up();
- });
- I.wait(1); // Ensure that the magic wand brush region is fully finished being created.
-}
-
async function assertMagicWandPixel(I, x, y, assertValue, rgbArray, msg) {
const hasPixel = await I.executeScript(hasKonvaPixelColorAtPoint, [x, y, rgbArray, 1]);
@@ -101,8 +89,8 @@ Scenario('Make sure the magic wand works in a variety of scenarios', async funct
AtSidebar.seeRegions(0);
I.say('Magic wanding clouds with cloud class in upper left of image');
- await magicWand(I, { msg: 'Fill in clouds upper left', fromX: 258, fromY: 214, toX: 650, toY: 650 });
- await magicWand(I, { msg: 'Fill in clouds lower left', fromX: 337, fromY: 777, toX: 650, toY: 650 });
+ await doDrawingAction(I, { msg: 'Fill in clouds upper left', fromX: 258, fromY: 214, toX: 650, toY: 650 });
+ await doDrawingAction(I, { msg: 'Fill in clouds lower left', fromX: 337, fromY: 777, toX: 650, toY: 650 });
I.say('Ensuring repeated magic wands back to back with same class collapsed into single region');
AtSidebar.seeRegions(1);
@@ -173,7 +161,7 @@ Scenario('Make sure the magic wand works in a variety of scenarios', async funct
I.pressKey('2');
I.say('Magic wanding cloud shadows with cloud shadow class in center of zoomed image');
- await magicWand(I, { msg: 'Cloud shadow in middle of image', fromX: 390, fromY: 500, toX: 500, toY: 500 });
+ await doDrawingAction(I, { msg: 'Cloud shadow in middle of image', fromX: 390, fromY: 500, toX: 500, toY: 500 });
I.say('Ensuring new cloud shadow magic wand region gets added to sidebar');
AtSidebar.seeRegions(2);
diff --git a/web/libs/editor/tests/e2e/tests/image.selected-region.test.js b/web/libs/editor/tests/e2e/tests/image.selected-region.test.js
new file mode 100644
index 000000000000..bbe11e435c4b
--- /dev/null
+++ b/web/libs/editor/tests/e2e/tests/image.selected-region.test.js
@@ -0,0 +1,110 @@
+/* global Feature, Scenario */
+
+const {
+ doDrawingAction,
+ initLabelStudio,
+ waitForImage,
+} = require('./helpers');
+const assert = require('assert');
+
+Feature('Test Image Region Stay Selected Between Tools');
+
+const PLANET = {
+ color: '#00FF00',
+ rgbArray: [0, 255, 0],
+};
+const MOONWALKER = {
+ color: '#0000FF',
+ rgbArray: [0, 0, 255],
+};
+
+const config = `
+
+
+
+
+
+
+
+
+ `;
+
+const annotationEmpty = {
+ id: '1000',
+ result: [],
+};
+
+const data = {
+ image:
+ 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg',
+};
+
+async function testRegion(testType, toolAccelerator, I, LabelStudio, AtImageView, AtSidebar) {
+ const params = {
+ config,
+ data,
+ annotations: [annotationEmpty],
+ };
+
+ LabelStudio.setFeatureFlags({
+ 'fflag_feat_front_dev_4081_magic_wand_tool': true,
+ });
+
+ I.amOnPage('/');
+
+ I.executeScript(initLabelStudio, params);
+
+ AtImageView.waitForImage();
+ await AtImageView.lookForStage();
+ I.executeScript(waitForImage);
+
+ I.say(`Select ${testType} & planet class`);
+ I.pressKey(toolAccelerator);
+ I.pressKey('1');
+
+ I.say('There should be no regions initially');
+ AtSidebar.seeRegions(0);
+
+ I.say(`${testType} initial region`);
+ await doDrawingAction(I, { msg: `Initial ${testType}`, fromX: 150, fromY: 110, toX: 150+50, toY: 110+50 });
+
+ I.say('There should now be a single region');
+ AtSidebar.seeRegions(1);
+
+ I.say(`Using Eraser on ${testType} region`);
+ I.pressKey('E');
+ I.usePlaywrightTo('Erasing', async ({ browser, browserContext, page }) => {
+ await page.mouse.move(150, 150);
+ await page.mouse.down();
+ await page.mouse.move(150+100, 150);
+ await page.mouse.up();
+ });
+
+ I.say(`Doing another ${testType} with same class after erasing`);
+ I.pressKey(toolAccelerator);
+ await doDrawingAction(I, { msg: `${testType} after erasing`, fromX: 280, fromY: 480, toX: 280+50, toY: 480+50 });
+
+ I.say('There should still only be one region');
+ AtSidebar.seeRegions(1);
+
+ I.say('Zooming and selecting pan tool');
+ I.click('button[aria-label="zoom-in"]');
+ I.click('button[aria-label="zoom-in"]');
+ I.pressKey('H');
+
+ I.say(`Doing another ${testType} after zooming and selecting pan tool`);
+ I.pressKey(toolAccelerator);
+ await doDrawingAction(I, { msg: `${testType} after zoom and pan selected`,
+ fromX: 400, fromY: 200, toX: 400+15, toY: 400+15 });
+
+ I.say('There should still only be one region');
+ AtSidebar.seeRegions(1);
+}
+
+Scenario('Selected brush region should stay between tools', async function({ I, LabelStudio, AtImageView, AtSidebar }) {
+ await testRegion('brush', 'B', I, LabelStudio, AtImageView, AtSidebar);
+});
+
+Scenario('Selected Magic Wand region should stay between tools', async function({ I, LabelStudio, AtImageView, AtSidebar }) {
+ await testRegion('magicwand', 'W', I, LabelStudio, AtImageView, AtSidebar);
+});
diff --git a/web/libs/editor/tests/e2e/tests/image.test.js b/web/libs/editor/tests/e2e/tests/image.test.js
index 6ece71a08ee5..60f7f387c124 100644
--- a/web/libs/editor/tests/e2e/tests/image.test.js
+++ b/web/libs/editor/tests/e2e/tests/image.test.js
@@ -148,10 +148,10 @@ Scenario('Image with perRegion tags', async function({ I, AtImageView, AtSidebar
assert.deepStrictEqual(result[0].value.rectanglelabels, ['Moonwalker']);
});
-const outOfBoundsFFs = new DataTable(['FF_DEV_3793'])
+const outOfBoundsFFs = new DataTable(['FF_DEV_3793']);
outOfBoundsFFs.add([true]);
-outOfBoundsFFs.add([false])
+outOfBoundsFFs.add([false]);
Data(outOfBoundsFFs)
.Scenario('Can\'t create rectangles outside of canvas', async ({
diff --git a/web/libs/editor/tests/e2e/tests/image.transformer.test.js b/web/libs/editor/tests/e2e/tests/image.transformer.test.js
index e294e6c1a74e..b8ee2e1c18ce 100644
--- a/web/libs/editor/tests/e2e/tests/image.transformer.test.js
+++ b/web/libs/editor/tests/e2e/tests/image.transformer.test.js
@@ -1,1269 +1,1269 @@
-const assert = require('assert');
-const Asserts = require('../utils/asserts');
-const Helpers = require('./helpers');
-
-Feature('Image transformer');
-
-const IMAGE =
- 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg';
-
-const annotationEmpty = {
- id: '1000',
- result: [],
-};
-
-const getParamsWithShape = (shape, params = '') => ({
- config: `
-
-
- <${shape} ${params} name="tag" toName="img" />
- `,
- data: { image: IMAGE },
- annotations: [annotationEmpty],
-});
-
-const getParamsWithLabels = (shape) => ({
- config: `
-
-
- <${shape}Labels name="tag" toName="img">
-
- ${shape}Labels>
- `,
- data: { image: IMAGE },
- annotations: [annotationEmpty],
-});
-
-const shapes = {
- Rectangle: {
- drawAction: 'drawByDrag',
- hasTransformer: true,
- hasRotator: true,
- hasMoveToolTransformer: true,
- hasMultiSelectionTransformer: true,
- hasMultiSelectionRotator: true,
- hotKey: 'r',
- byBBox(x, y, width, height) {
- return {
- params: [x, y, width, height],
- result: { width, height, rotation: 0, x, y },
- };
- },
- },
- Ellipse: {
- drawAction: 'drawByDrag',
- hasTransformer: true,
- hasRotator: true,
- hasMoveToolTransformer: true,
- hasMultiSelectionTransformer: true,
- hasMultiSelectionRotator: true,
- hotKey: 'o',
- byBBox(x, y, width, height) {
- return {
- params: [x + width / 2, y + height / 2, width / 2, height / 2],
- result: { radiusX: width / 2, radiusY: height / 2, rotation: 0, x: x + width / 2, y: y + height / 2 },
- };
- },
- },
- Polygon: {
- drawAction: 'drawByClickingPoints',
- hasTransformer: false,
- hasRotator: false,
- hasMoveToolTransformer: true,
- hasMultiSelectionTransformer: true,
- hasMultiSelectionRotator: false,
- hotKey: 'p',
- byBBox(x, y, width, height) {
- const points = [];
-
- points.push([x, y]);
- points.push([x + width, y]);
- points.push([x + width / 2, y + height / 2]);
- points.push([x + width, y + height]);
- points.push([x, y + height]);
- return {
- params: [[...points, points[0]]],
- result: {
- points,
- },
- };
- },
- },
- KeyPoint: {
- drawAction: 'clickAt',
- hasTransformer: false,
- hasRotator: false,
- hasMoveToolTransformer: false,
- hasMultiSelectionTransformer: true,
- hasMultiSelectionRotator: false,
- hotKey: 'k',
- params: 'strokeWidth="2"',
- byBBox(x, y, width, height) {
- return {
- params: [x + width / 2, y + height / 2],
- result: {
- x: x + width / 2,
- y: y + height / 2,
- width: 2,
- },
- };
- },
- },
-};
-
-function drawShapeByBbox(Shape, x, y, width, height, where) {
- where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params);
-}
-
-const shapesTable = new DataTable(['shapeName']);
-
-for (const shapeName of Object.keys(shapes)) {
- shapesTable.add([shapeName]);
-}
-
-Data(shapesTable).Scenario('Check transformer existing for different shapes, their amount and modes.', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- const bbox1 = {
- x: 100,
- y: 100,
- width: 200,
- height: 200,
- };
- const bbox2 = {
- x: 400,
- y: 100,
- width: 200,
- height: 200,
- };
- const getCenter = bbox => [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
- let isTransformerExist;
-
- LabelStudio.init(getParamsWithLabels(shapeName));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
-
- // Draw two regions
- I.pressKey('1');
- drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
- AtSidebar.seeRegions(1);
- I.pressKey('1');
- drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
- AtSidebar.seeRegions(2);
-
- // Check that it wasn't a cause to show a transformer
- isTransformerExist = await AtImageView.isTransformerExist();
- assert.strictEqual(isTransformerExist, false);
-
- // Select the first region
- AtImageView.clickAt(...getCenter(bbox1));
- AtSidebar.seeSelectedRegion();
-
- // Match if transformer exist with expectations in single selected mode
- isTransformerExist = await AtImageView.isTransformerExist();
- assert.strictEqual(isTransformerExist, Shape.hasTransformer);
-
- // Match if rotator at transformer exist with expectations in single selected mode
- isTransformerExist = await AtImageView.isRotaterExist();
- assert.strictEqual(isTransformerExist, Shape.hasRotator);
-
- // Switch to move tool
- I.pressKey('v');
-
- // Match if rotator at transformer exist with expectations in single selected mode with move tool chosen
- isTransformerExist = await AtImageView.isTransformerExist();
- assert.strictEqual(isTransformerExist, Shape.hasMoveToolTransformer);
-
- // Deselect the previous selected region
- I.pressKey(['u']);
-
- // Select 2 regions
- AtImageView.drawThroughPoints([
- [bbox1.x - 5, bbox1.y - 5],
- [bbox2.x + bbox2.width + 5, bbox2.y + bbox2.height + 5],
- ], 'steps', 10);
-
- // Match if transformer exist with expectations in multiple selected mode
- isTransformerExist = await AtImageView.isTransformerExist();
- assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionTransformer);
-
- // Match if rotator exist with expectations in multiple selected mode
- isTransformerExist = await AtImageView.isRotaterExist();
- assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionRotator);
-});
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer))
- .Scenario('Resizing a single region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
- const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
-
- // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, 50, 50, 100, 100, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Select the shape
- AtImageView.clickAt(100, 100);
- AtSidebar.seeSelectedRegion();
-
- // Switch to move tool to force appearance of transformer
- I.pressKey('v');
- const isTransformerExist = await AtImageView.isTransformerExist();
-
- assert.strictEqual(isTransformerExist, true);
-
-
- // Transform the shape
- // Move the top anchor up for 50px (limited by image border) => {x1:50,y1:0,x2:150,y2:150}
- AtImageView.drawByDrag(100, 50, 0, -100);
- // Move the left anchor left for 50px (limited by image border) => {x1:0,y1:0,x2:150,y2:150}
- AtImageView.drawByDrag(50, 75, -300, -100);
- // Move the right anchor left for 50px => {x1:0,y1:0,x2:100,y2:150}
- AtImageView.drawByDrag(150, 75, -50, 0);
- // Move the bottom anchor down for 100px => {x1:0,y1:0,x2:100,y2:250}
- AtImageView.drawByDrag(50, 150, 10, 100);
- // Move the right-bottom anchor right for 200px and down for 50px => {x1:0,y1:0,x2:300,y2:300}
- AtImageView.drawByDrag(100, 250, 200, 50);
- // Check resulting sizes
- const rectangleResult = await LabelStudio.serialize();
- const exceptedResult = Shape.byBBox(0, 0, 300, 300).result;
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult));
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer))
- .Scenario('Resizing a single region with zoom', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
-
- LabelStudio.setFeatureFlags({
- 'ff_front_dev_2394_zoomed_transforms_260522_short': true,
- });
-
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
- const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
-
- // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, 50, 50, 300, 300, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Select the shape
- AtImageView.clickAt(100, 100);
- AtSidebar.seeSelectedRegion();
-
- // Switch to move tool to force appearance of transformer
- I.pressKey('v');
- const isTransformerExist = await AtImageView.isTransformerExist();
-
- assert.strictEqual(isTransformerExist, true);
-
- AtImageView.setZoom(3, 0, 0);
-
- // Transform the shape
- AtImageView.drawByDrag(150, 150, -150, -150);
- I.wait(1);
-
- AtImageView.drawByDrag(0, 0, -300, -100);
- I.wait(1);
-
- AtImageView.drawByDrag(0, 0, 150, 150);
- I.wait(1);
-
- // Check resulting sizes
- const rectangleResult = await LabelStudio.serialize();
-
- I.wait(10);
-
- const exceptedResult = Shape.byBBox(50, 50, 300, 300).result;
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult), 0);
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
- .Scenario('Simple rotating', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- // Draw a region in bbox {x1:40%,y1:40%,x2:60%,y2:60%}
- const rectangle = {
- x: canvasSize.width * .4,
- y: canvasSize.height * .4,
- width: canvasSize.width * .2,
- height: canvasSize.height * .2,
- };
- const rectangleCenter = {
- x: rectangle.x + rectangle.width / 2,
- y: rectangle.y + rectangle.height / 2,
- };
-
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
- AtSidebar.seeRegions(1);
-
-
- // Select the shape and check that transformer appears
- AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
- AtSidebar.seeSelectedRegion();
-
- // Switch to move tool to force appearance of transformer
- I.pressKey('v');
- const isTransformerExist = await AtImageView.isTransformerExist();
-
- assert.strictEqual(isTransformerExist, true);
-
- // The rotator anchor must be above top anchor by 50 pixels
- const rotatorPosition = {
- x: rectangleCenter.x,
- y: rectangle.y - 50,
- };
-
- // Rotate for 45 degrees clockwise
- AtImageView.drawThroughPoints(
- [
- [rotatorPosition.x, rotatorPosition.y],
- [rectangleCenter.x + 500, rectangleCenter.y - 500],
- ], 'steps', 5);
-
- // Check resulting rotation
- const rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(Math.round(rectangleResult[0].value.rotation), 45);
-
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
- .Scenario('Rotating of unrotatable region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- // Draw a region which we cannot rotate 'cause of position near the image's border {x1:0,y1:20%,x2:20%,y2:50%}
- const rectangle = {
- x: 0,
- y: canvasSize.height * .2,
- width: canvasSize.width * .2,
- height: canvasSize.height * .3,
- };
- const rectangleCenter = {
- x: rectangle.x + rectangle.width / 2,
- y: rectangle.y + rectangle.height / 2,
- };
-
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Select the shape and check that transformer appears
- AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
- AtSidebar.seeSelectedRegion();
-
- // Switch to move tool to force appearance of transformer
- I.pressKey('v');
- const isTransformerExist = await AtImageView.isTransformerExist();
-
- assert.strictEqual(isTransformerExist, true);
-
- // The rotator anchor must be above top anchor by 50 pixels
- const rotatorPosition = {
- x: rectangleCenter.x,
- y: rectangle.y - 50,
- };
-
- // Rotate for 45 degrees clockwise
- AtImageView.drawByDrag(rotatorPosition.x, rotatorPosition.y, rectangleCenter.y - rotatorPosition.y + 100, -100);
-
- // Check the region hasn't been rotated
- const rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 0);
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
- .Scenario('Broke the limits with rotation', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- {
- // Draw a region which have limitation at rotating by bbox {x1:5,y1:100,x2:305,y2:350}
- const rectangle = {
- x: 5,
- y: 100,
- width: 300,
- height: 300,
- };
- const rectangleCenter = {
- x: rectangle.x + rectangle.width / 2,
- y: rectangle.y + rectangle.height / 2,
- };
-
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Select the shape and check that transformer appears
- AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
- AtSidebar.seeSelectedRegion();
-
- // Switch to move tool to force appearance of transformer
- I.pressKey('v');
- const isTransformerExist = await AtImageView.isTransformerExist();
-
- assert.strictEqual(isTransformerExist, true);
-
- // The rotator anchor must be above top anchor by 50 pixels
- const rotatorPosition = {
- x: rectangleCenter.x,
- y: rectangle.y - 50,
- };
-
- // Rotate for 45 degrees clockwise
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [rectangleCenter.x + 500, rectangleCenter.y - 500],
- ], 'steps', 200);
-
- // Check that we cannot rotate it like this
- let rectangleResult = await LabelStudio.serialize();
-
- assert.notStrictEqual(
- Math.round(rectangleResult[0].value.rotation),
- 0,
- 'Region must be rotated',
- );
- assert.notStrictEqual(
- Math.round(rectangleResult[0].value.rotation),
- 45,
- 'Angle must not be 45 degrees',
- );
-
- // Undo changes
- I.pressKey(['CommandOrControl', 'z']);
-
- // Rotate for 90 degrees clockwise instead
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [rectangle.x + rectangle.width + 100, rectangleCenter.y],
- [rectangle.x + rectangle.width + 200, rectangleCenter.y],
- ], 'steps', 200);
-
- // Check the resulted rotation
- rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90, 'Angle must be 90 degrees');
- // remove region
- I.pressKey('Backspace');
- }
-
- I.say('Check that it works same way with right border');
-
- {
- // Draw a region which have limitation at rotating by bbox {x1:100% - 305,y1:100,x2:100% - 5,y2:350}
- const rectangle = {
- x: canvasSize.width - 305,
- y: 100,
- width: 300,
- height: 300,
- };
- const rectangleCenter = {
- x: rectangle.x + rectangle.width / 2,
- y: rectangle.y + rectangle.height / 2,
- };
-
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Select the shape and check that transformer appears
- AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
- AtSidebar.seeSelectedRegion();
-
- // Switch to move tool to force appearance of transformer
- I.pressKey('v');
- const isTransformerExist = await AtImageView.isTransformerExist();
-
- assert.strictEqual(isTransformerExist, true);
-
- // The rotator anchor must be above top anchor by 50 pixels
- const rotatorPosition = {
- x: rectangleCenter.x,
- y: rectangle.y - 50,
- };
-
- // Rotate for 45 degrees clockwise
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [rectangleCenter.x + 500, rectangleCenter.y - 500],
- ], 'steps', 200);
-
- // Check the resulted rotation
- let rectangleResult = await LabelStudio.serialize();
-
- assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 0);
- assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 45);
-
- // Undo changes
- I.pressKey(['CommandOrControl', 'z']);
-
- // Rotate for 90 degrees clockwise instead
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [rectangle.x + rectangle.width + 100, rectangleCenter.y],
- [rectangle.x + rectangle.width + 200, rectangleCenter.y],
- ], 'steps', 200);
-
- // Check that we cannot rotate it like this
- rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90);
- }
-
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
- .Scenario('Check the initial rotation of transformer for the single region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
-
- const bbox = {
- x: 100,
- y: 100,
- width: 100,
- height: 100,
- };
- const bboxCenter = {
- x: bbox.x + bbox.width / 2,
- y: bbox.y + bbox.height / 2,
- };
-
- // Draw a region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Select it
- AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
- AtSidebar.seeSelectedRegion();
-
- // Switch to move tool to force appearance of transformer
- I.pressKey('v');
- const isTransformerExist = await AtImageView.isTransformerExist();
-
- assert.strictEqual(isTransformerExist, true);
-
- // The rotator anchor must be above top anchor by 50 pixels
- let rotatorPosition = {
- x: bboxCenter.x,
- y: bbox.y - 50,
- };
-
- // Rotate for 90 degrees clockwise
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [bbox.x + bbox.width + 100, bboxCenter.y],
- [bbox.x + bbox.width + 200, bboxCenter.y],
- ], 'steps', 10);
-
- // Unselect current region
- I.pressKey('u');
- AtSidebar.dontSeeSelectedRegion();
-
- // Select it again
- AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
- AtSidebar.seeSelectedRegion();
-
- // The trick is that we turn it further, based on the assumption that transformer appears in rotated state on region selection
- // So let's try to rotate it
- // The rotator anchor must be to the right of the right anchor by 50 pixels
-
- rotatorPosition = {
- x: bbox.x + bbox.width + 50,
- y: bboxCenter.y,
- };
-
- // Rotate for 90 degrees clockwise once again
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [bboxCenter.x, bbox.y + bbox.height + 100],
- [bboxCenter.x, bbox.y + bbox.height + 200],
- ], 'steps', 10);
-
- // Check that region has been rotated for 180 degrees
- const rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180);
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
- .Scenario('Check the initial rotation of transformer for the couple of regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
-
- const bbox1 = {
- x: 100,
- y: 100,
- width: 40,
- height: 40,
- };
-
- const bbox2 = {
- x: 160,
- y: 160,
- width: 40,
- height: 40,
- };
-
- const transformerBbox = {
- x: bbox1.x,
- y: bbox1.y,
- width: bbox2.x + bbox2.width - bbox1.x,
- height: bbox2.y + bbox2.height - bbox1.y,
- };
- const transformerBboxCenter = {
- x: transformerBbox.x + transformerBbox.width / 2,
- y: transformerBbox.y + transformerBbox.height / 2,
- };
-
- // Draw the first region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Draw the second region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
- AtSidebar.seeRegions(2);
-
- // Switch to move tool and select them
- I.pressKey('v');
- AtImageView.drawThroughPoints([
- [transformerBbox.x - 20, transformerBbox.y - 20],
- [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
- ]);
- AtSidebar.seeSelectedRegion();
-
- // The rotator anchor must be above top anchor by 50 pixels
- const rotatorPosition = {
- x: transformerBboxCenter.x,
- y: transformerBbox.y - 50,
- };
-
- // Rotate for 180 degrees clockwise
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [transformerBboxCenter.x + 100, transformerBboxCenter.y + 100],
- [transformerBboxCenter.x, transformerBboxCenter.y + 100],
- [transformerBboxCenter.x, transformerBboxCenter.y + 200],
- ], 'steps', 10);
-
- // Unselect current regions
- I.pressKey('u');
- AtSidebar.dontSeeSelectedRegion();
-
- // Select them again
- AtImageView.drawThroughPoints([
- [transformerBbox.x - 20, transformerBbox.y - 20],
- [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
- ]);
- AtSidebar.seeSelectedRegion();
-
- // So we have couple of rotated regions, let's check if rotates still appears above the top anchor
-
- // Rotate for 90 degrees clockwise
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [transformerBboxCenter.x + 100, transformerBboxCenter.y],
- [transformerBboxCenter.x + 200, transformerBboxCenter.y],
- ], 'steps', 10);
-
- // Check that region has been rotated for (180 + 90) degrees
- const rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180 + 90);
- });
-
-// KeyPoints are transformed unpredictable so for now just skip them
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer && shapeName !== 'KeyPoint'))
- .Scenario('Transforming of multiple regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
- const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
-
- const bbox1 = {
- x: 100,
- y: 100,
- width: 50,
- height: 50,
- };
-
- const bbox2 = {
- x: 150,
- y: 150,
- width: 50,
- height: 50,
- };
-
- const transformerBbox = {
- x: bbox1.x,
- y: bbox1.y,
- width: bbox2.x + bbox2.width - bbox1.x,
- height: bbox2.y + bbox2.height - bbox1.y,
- };
- const transformerBboxCenter = {
- get x() {
- return transformerBbox.x + transformerBbox.width / 2;
- },
- get y() {
- return transformerBbox.y + transformerBbox.height / 2;
- },
- };
-
- // Draw the first region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Draw the second region
- I.pressKey(Shape.hotKey);
- I.pressKeyDown('Control');
- drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
- I.pressKeyUp('Control');
- AtSidebar.seeRegions(2);
-
- // Switch to move tool and select them
- I.pressKey('v');
- AtImageView.drawThroughPoints([
- [transformerBbox.x - 20, transformerBbox.y - 20],
- [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
- ]);
- AtSidebar.seeSelectedRegion();
- // Scale the shapes vertically
- AtImageView.drawByDrag(transformerBboxCenter.x, transformerBbox.y + transformerBbox.height, 0, 50);
- transformerBbox.height += 50;
- AtSidebar.seeSelectedRegion();
- // Scale the shapes horizontally
- AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBboxCenter.y, 50, 0);
- transformerBbox.width += 50;
- AtSidebar.seeSelectedRegion();
- // Scale the shapes in both directions
- AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBbox.y + transformerBbox.height, 50, 50);
- transformerBbox.height += 50;
- transformerBbox.width += 50;
- AtSidebar.seeSelectedRegion();
-
- // Check resulting sizes
- const rectangleResult = await LabelStudio.serialize();
- const exceptedResult1 = Shape.byBBox(bbox1.x, bbox1.y, bbox1.width + 50, bbox1.height + 50).result;
- const exceptedResult2 = Shape.byBBox(bbox2.x + 50, bbox2.y + 50, bbox2.width + 50, bbox2.height + 50).result;
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1));
- Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2));
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer))
- .Scenario('Move regions by drag', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
- const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
-
- const bbox1 = {
- x: 100,
- y: 100,
- width: 20,
- height: 20,
- };
- const bbox1Center = {
- x: bbox1.x + bbox1.width / 2,
- y: bbox1.y + bbox1.height / 2,
- };
-
- const bbox2 = {
- x: 140,
- y: 140,
- width: 20,
- height: 20,
- };
- const bbox2Center = {
- x: bbox2.x + bbox2.width / 2,
- y: bbox2.y + bbox2.height / 2,
- };
-
- const transformerBbox = {
- x: bbox1.x,
- y: bbox1.y,
- width: bbox2.x + bbox2.width - bbox1.x,
- height: bbox2.y + bbox2.height - bbox1.y,
- };
- const transformerBboxCenter = {
- x: transformerBbox.x + transformerBbox.width / 2,
- y: transformerBbox.y + transformerBbox.height / 2,
- };
-
- // Draw the first region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Draw the second region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
- AtSidebar.seeRegions(2);
-
- if (shapeName === 'KeyPoint') {
- // Draw more points to get more space in transformer
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox1.x, bbox1.y, 0, 0, AtImageView);
- AtSidebar.seeRegions(3);
-
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox2.x + bbox2.width, bbox2.y + bbox2.height, 0, 0, AtImageView);
- AtSidebar.seeRegions(4);
- }
-
- // Switch to move tool and select them
- I.pressKey('v');
- AtImageView.drawThroughPoints([
- [transformerBbox.x - 20, transformerBbox.y - 20],
- [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
- ]);
- AtSidebar.seeSelectedRegion();
-
- const dragShapes = (startPoint, shift, rememberShift = true) => {
- AtImageView.drawThroughPoints([
- [startPoint.x, startPoint.y],
- [startPoint.x + shift.x, startPoint.y + shift.y],
- [startPoint.x + shift.x, startPoint.y + shift.y],
- ], 'steps', 10);
- AtSidebar.seeSelectedRegion();
-
- if (rememberShift) {
- bbox1Center.x += shift.x;
- bbox1Center.y += shift.y;
- bbox2Center.x += shift.x;
- bbox2Center.y += shift.y;
- transformerBboxCenter.x += shift.x;
- transformerBboxCenter.y += shift.y;
- }
- };
-
- // Drag shapes by holding onto the first region
- dragShapes(bbox1Center, { x: 100, y: 0 });
- // Drag shapes by holding onto the second region
- dragShapes(bbox2Center, { x: 0, y: 100 });
- // Drag shapes by holding onto the transformer background
- dragShapes(transformerBboxCenter, { x: 150, y: 150 }, false);
- // Move back throught history to check that transformer's background moving with it
- I.pressKey(['Control', 'z']);
- // Drag shapes by holding onto the transformer background again
- dragShapes(transformerBboxCenter, { x: 100, y: 100 }, false);
-
-
- // Check that dragging was successful
- const rectangleResult = await LabelStudio.serialize();
- const exceptedResult1 = Shape.byBBox(bbox1.x + 200, bbox1.y + 200, bbox1.width, bbox1.height).result;
- const exceptedResult2 = Shape.byBBox(bbox2.x + 200, bbox2.y + 200, bbox2.width, bbox2.height).result;
-
- Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1));
- Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2));
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
- .Scenario('Limitation of dragging a single rotated region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- const bbox = {
- x: canvasSize.width / 2 - 50,
- y: canvasSize.height / 2 - 50,
- width: 100,
- height: 100,
- };
- const bboxCenter = {
- x: bbox.x + bbox.width / 2,
- y: bbox.y + bbox.height / 2,
- };
-
- // Draw a region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Select it
- AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
- AtSidebar.seeSelectedRegion();
-
- // Switch to move tool to force appearance of transformer
- I.pressKey('v');
- const isTransformerExist = await AtImageView.isTransformerExist();
-
- assert.strictEqual(isTransformerExist, true);
-
- // The rotator anchor must be above top anchor by 50 pixels
- const rotatorPosition = {
- x: bboxCenter.x,
- y: bbox.y - 50,
- };
-
- // Rotate for 180 degrees clockwise
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [bboxCenter.x + 100, bboxCenter.y],
- [bboxCenter.x, bboxCenter.y + 100],
- [bboxCenter.x, bboxCenter.y + 200],
- ], 'steps', 10);
-
- // When we have the rotated region, we need to check its behavior when we drag it across the borders of the image
- let rectangleResult;
-
- I.say('Drag the region over the left border');
- AtImageView.drawThroughPoints([
- [bboxCenter.x, bboxCenter.y],
- [-500, bboxCenter.y],
- ], 'steps', 20);
- AtSidebar.seeSelectedRegion();
- // moving of the region should be constrained by borders
- rectangleResult = await LabelStudio.serialize();
- Asserts.deepEqualWithTolerance(
- rectangleResult[0].value.x * canvasSize.width / 100,
- Shape.byBBox(bbox.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x,
- );
- // reset position by undo
- I.pressKey(['Control', 'z']);
-
- I.say('Drag the region over the top border');
- AtImageView.drawThroughPoints([
- [bboxCenter.x, bboxCenter.y],
- [bboxCenter.x, -500],
- ], 'steps', 20);
- AtSidebar.seeSelectedRegion();
- // moving of the region should be constrained by borders
- rectangleResult = await LabelStudio.serialize();
- Asserts.deepEqualWithTolerance(
- rectangleResult[0].value.y * canvasSize.height / 100,
- Shape.byBBox(bbox.x + bbox.width, bbox.height, -bbox.width, -bbox.height).result.y,
- );
- // reset position by undo
- I.pressKey(['Control', 'z']);
-
- I.say('Drag the region over the right border');
- AtImageView.drawThroughPoints([
- [bboxCenter.x, bboxCenter.y],
- [canvasSize.width + 500, bboxCenter.y],
- ], 'steps', 20);
- AtSidebar.seeSelectedRegion();
- // moving of the region should be constrained by borders
- rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(
- rectangleResult[0].value.x * canvasSize.width / 100,
- Shape.byBBox(canvasSize.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x,
- );
- // reset position by undo
- I.pressKey(['Control', 'z']);
-
- I.say('Drag the region over the bottom border');
- AtImageView.drawThroughPoints([
- [bboxCenter.x, bboxCenter.y],
- [bboxCenter.x, canvasSize.height + 500],
- ], 'steps', 20);
- AtSidebar.seeSelectedRegion();
- // moving of the region should be constrained by borders
- rectangleResult = await LabelStudio.serialize();
- Asserts.deepEqualWithTolerance(
- rectangleResult[0].value.y * canvasSize.height / 100,
- Shape.byBBox(bbox.x + bbox.width, canvasSize.height, -bbox.width, -bbox.height).result.y,
- );
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
- .Scenario('Limitation of dragging a couple of rotated regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- const bbox1 = {
- x: canvasSize.width / 2 - 50,
- y: canvasSize.height / 2 - 50,
- width: 50,
- height: 50,
- };
-
- const bbox2 = {
- x: canvasSize.width / 2,
- y: canvasSize.height / 2,
- width: 50,
- height: 50,
- };
-
- const transformerBbox = {
- x: bbox1.x,
- y: bbox1.y,
- width: bbox2.x + bbox2.width - bbox1.x,
- height: bbox2.y + bbox2.height - bbox1.y,
- };
- const transformerBboxCenter = {
- get x() {
- return transformerBbox.x + transformerBbox.width / 2;
- },
- get y() {
- return transformerBbox.y + transformerBbox.height / 2;
- },
- };
-
- // Draw the first region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Draw the second region
- I.pressKey(Shape.hotKey);
- I.pressKeyDown('Control');
- drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
- I.pressKeyUp('Control');
- AtSidebar.seeRegions(2);
-
- // Select them by move tool
- I.pressKey('v');
- AtImageView.drawThroughPoints(
- [
- [transformerBbox.x - 50, transformerBbox.y - 50],
- [transformerBbox.x + transformerBbox.width + 50, transformerBbox.y + transformerBbox.height + 50],
- ], 'steps', 10,
- );
- AtSidebar.seeSelectedRegion();
-
- // The rotator anchor must be above top anchor by 50 pixels
- const rotatorPosition = {
- x: transformerBboxCenter.x,
- y: transformerBbox.y - 50,
- };
-
- // Rotate for 180 degrees clockwise
- AtImageView.drawThroughPoints([
- [rotatorPosition.x, rotatorPosition.y],
- [transformerBboxCenter.x + 100, transformerBboxCenter.y],
- [transformerBboxCenter.x, transformerBboxCenter.y + 100],
- [transformerBboxCenter.x, transformerBboxCenter.y + 200],
- ], 'steps', 10);
-
- // When we have the rotated region, we need to check its behavior when we drag it across the borders of the image
- let rectangleResult;
-
- I.say('Drag the region over the left border');
- AtImageView.drawThroughPoints([
- [transformerBboxCenter.x, transformerBboxCenter.y],
- [-500, transformerBboxCenter.y],
- ], 'steps', 20);
- AtSidebar.seeSelectedRegion();
- // moving of the region should be constrained by borders
- rectangleResult = await LabelStudio.serialize();
- Asserts.deepEqualWithTolerance(
- rectangleResult[0].value.x * canvasSize.width / 100,
- Shape.byBBox(transformerBbox.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x,
- );
- Asserts.deepEqualWithTolerance(
- rectangleResult[1].value.x * canvasSize.width / 100,
- Shape.byBBox(bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x,
- );
- // reset position by undo
- I.pressKey(['Control', 'z']);
-
- I.say('Drag the region over the top border');
- AtImageView.drawThroughPoints([
- [transformerBboxCenter.x, transformerBboxCenter.y],
- [transformerBboxCenter.x, -500],
- ], 'steps', 20);
- AtSidebar.seeSelectedRegion();
- // moving of the region should be constrained by borders
- rectangleResult = await LabelStudio.serialize();
- Asserts.deepEqualWithTolerance(
- rectangleResult[0].value.y * canvasSize.height / 100,
- Shape.byBBox(transformerBbox.x + transformerBbox.width, transformerBbox.height, -bbox1.width, -bbox1.height).result.y,
- );
- Asserts.deepEqualWithTolerance(
- rectangleResult[1].value.y * canvasSize.height / 100,
- Shape.byBBox(transformerBbox.x + bbox2.width, bbox2.height, -bbox2.width, -bbox2.height).result.y,
- );
- // reset position by undo
- I.pressKey(['Control', 'z']);
-
- I.say('Drag the region over the right border');
- AtImageView.drawThroughPoints([
- [transformerBboxCenter.x, transformerBboxCenter.y],
- [canvasSize.width + 500, transformerBboxCenter.y],
- ], 'steps', 20);
- AtSidebar.seeSelectedRegion();
- // moving of the region should be constrained by borders
- rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(
- rectangleResult[0].value.x * canvasSize.width / 100,
- Shape.byBBox(canvasSize.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x,
- );
- Asserts.deepEqualWithTolerance(
- rectangleResult[1].value.x * canvasSize.width / 100,
- Shape.byBBox(canvasSize.width - transformerBbox.width + bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x,
- );
- // reset position by undo
- I.pressKey(['Control', 'z']);
-
- I.say('Drag the region over the bottom border');
- AtImageView.drawThroughPoints([
- [transformerBboxCenter.x, transformerBboxCenter.y],
- [transformerBboxCenter.x, canvasSize.height + 500],
- ], 'steps', 20);
- AtSidebar.seeSelectedRegion();
- // moving of the region should be constrained by borders
- rectangleResult = await LabelStudio.serialize();
- Asserts.deepEqualWithTolerance(
- rectangleResult[0].value.y * canvasSize.height / 100,
- Shape.byBBox(transformerBbox.x + transformerBbox.width, canvasSize.height, -bbox1.width, -bbox1.height).result.y,
- );
- Asserts.deepEqualWithTolerance(
- rectangleResult[1].value.y * canvasSize.height / 100,
- Shape.byBBox(transformerBbox.x + bbox2.width, canvasSize.height - transformerBbox.height + bbox2.height, -bbox2.width, -bbox2.height).result.y,
- );
- });
-
-Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasRotator))
- .Scenario('Rotating the region near the border', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
- const { shapeName } = current;
- const Shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- const bbox = {
- x: canvasSize.width - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50,
- y: canvasSize.height - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50,
- width: 100,
- height: 100,
- };
-
- const bboxCenter = {
- x: bbox.x + bbox.width / 2,
- y: bbox.y + bbox.height / 2,
- };
-
- // Draw the region
- I.pressKey(Shape.hotKey);
- drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
- AtSidebar.seeRegions(1);
-
- // Select it
- AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
- AtSidebar.seeSelectedRegion();
-
- // The rotator anchor must be above top anchor by 50 pixels
- const rotatorPosition = {
- x: bboxCenter.x,
- y: bbox.y - 50,
- };
-
- // Check 7 different rotations
- const rotatorWayPoints = [[rotatorPosition.x, rotatorPosition.y]];
- const angle45 = Math.PI / 4;
-
- for (let i = 0; i < 8; i++) {
- const angle = angle45 * i;
-
- rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 100, bboxCenter.y - Math.cos(angle) * 100]);
- rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 1000, bboxCenter.y - Math.cos(angle) * 1000]);
-
- // Rotate clockwise by 45 * i degrees
- AtImageView.drawThroughPoints(rotatorWayPoints, 'steps', 10);
- AtSidebar.seeSelectedRegion();
- // Check that rotating was successful
- const rectangleResult = await LabelStudio.serialize();
-
- Asserts.deepEqualWithTolerance(
- Math.round(rectangleResult[0].value.rotation),
- 45 * i,
- );
-
- // undo rotation
- I.pressKey(['Control', 'z']);
- // clear unnecessary waypoints
- rotatorWayPoints.pop();
- }
- });
+const assert = require('assert');
+const Asserts = require('../utils/asserts');
+const Helpers = require('./helpers');
+
+Feature('Image transformer');
+
+const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg';
+
+const annotationEmpty = {
+ id: '1000',
+ result: [],
+};
+
+const getParamsWithShape = (shape, params = '') => ({
+ config: `
+
+
+ <${shape} ${params} name="tag" toName="img" />
+ `,
+ data: { image: IMAGE },
+ annotations: [annotationEmpty],
+});
+
+const getParamsWithLabels = (shape) => ({
+ config: `
+
+
+ <${shape}Labels name="tag" toName="img">
+
+ ${shape}Labels>
+ `,
+ data: { image: IMAGE },
+ annotations: [annotationEmpty],
+});
+
+const shapes = {
+ Rectangle: {
+ drawAction: 'drawByDrag',
+ hasTransformer: true,
+ hasRotator: true,
+ hasMoveToolTransformer: true,
+ hasMultiSelectionTransformer: true,
+ hasMultiSelectionRotator: true,
+ hotKey: 'r',
+ byBBox(x, y, width, height) {
+ return {
+ params: [x, y, width, height],
+ result: { width, height, rotation: 0, x, y },
+ };
+ },
+ },
+ Ellipse: {
+ drawAction: 'drawByDrag',
+ hasTransformer: true,
+ hasRotator: true,
+ hasMoveToolTransformer: true,
+ hasMultiSelectionTransformer: true,
+ hasMultiSelectionRotator: true,
+ hotKey: 'o',
+ byBBox(x, y, width, height) {
+ return {
+ params: [x + width / 2, y + height / 2, width / 2, height / 2],
+ result: { radiusX: width / 2, radiusY: height / 2, rotation: 0, x: x + width / 2, y: y + height / 2 },
+ };
+ },
+ },
+ Polygon: {
+ drawAction: 'drawByClickingPoints',
+ hasTransformer: false,
+ hasRotator: false,
+ hasMoveToolTransformer: true,
+ hasMultiSelectionTransformer: true,
+ hasMultiSelectionRotator: false,
+ hotKey: 'p',
+ byBBox(x, y, width, height) {
+ const points = [];
+
+ points.push([x, y]);
+ points.push([x + width, y]);
+ points.push([x + width / 2, y + height / 2]);
+ points.push([x + width, y + height]);
+ points.push([x, y + height]);
+ return {
+ params: [[...points, points[0]]],
+ result: {
+ points,
+ },
+ };
+ },
+ },
+ KeyPoint: {
+ drawAction: 'clickAt',
+ hasTransformer: false,
+ hasRotator: false,
+ hasMoveToolTransformer: false,
+ hasMultiSelectionTransformer: true,
+ hasMultiSelectionRotator: false,
+ hotKey: 'k',
+ params: 'strokeWidth="2"',
+ byBBox(x, y, width, height) {
+ return {
+ params: [x + width / 2, y + height / 2],
+ result: {
+ x: x + width / 2,
+ y: y + height / 2,
+ width: 2,
+ },
+ };
+ },
+ },
+};
+
+function drawShapeByBbox(Shape, x, y, width, height, where) {
+ where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params);
+}
+
+const shapesTable = new DataTable(['shapeName']);
+
+for (const shapeName of Object.keys(shapes)) {
+ shapesTable.add([shapeName]);
+}
+
+Data(shapesTable).Scenario('Check transformer existing for different shapes, their amount and modes.', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ const bbox1 = {
+ x: 100,
+ y: 100,
+ width: 200,
+ height: 200,
+ };
+ const bbox2 = {
+ x: 400,
+ y: 100,
+ width: 200,
+ height: 200,
+ };
+ const getCenter = bbox => [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
+ let isTransformerExist;
+
+ LabelStudio.init(getParamsWithLabels(shapeName));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+
+ // Draw two regions
+ I.pressKey('1');
+ drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
+ AtSidebar.seeRegions(1);
+ I.pressKey('1');
+ drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
+ AtSidebar.seeRegions(2);
+
+ // Check that it wasn't a cause to show a transformer
+ isTransformerExist = await AtImageView.isTransformerExist();
+ assert.strictEqual(isTransformerExist, false);
+
+ // Select the first region
+ AtImageView.clickAt(...getCenter(bbox1));
+ AtSidebar.seeSelectedRegion();
+
+ // Match if transformer exist with expectations in single selected mode
+ isTransformerExist = await AtImageView.isTransformerExist();
+ assert.strictEqual(isTransformerExist, Shape.hasTransformer);
+
+ // Match if rotator at transformer exist with expectations in single selected mode
+ isTransformerExist = await AtImageView.isRotaterExist();
+ assert.strictEqual(isTransformerExist, Shape.hasRotator);
+
+ // Switch to move tool
+ I.pressKey('v');
+
+ // Match if rotator at transformer exist with expectations in single selected mode with move tool chosen
+ isTransformerExist = await AtImageView.isTransformerExist();
+ assert.strictEqual(isTransformerExist, Shape.hasMoveToolTransformer);
+
+ // Deselect the previous selected region
+ I.pressKey(['u']);
+
+ // Select 2 regions
+ AtImageView.drawThroughPoints([
+ [bbox1.x - 5, bbox1.y - 5],
+ [bbox2.x + bbox2.width + 5, bbox2.y + bbox2.height + 5],
+ ], 'steps', 10);
+
+ // Match if transformer exist with expectations in multiple selected mode
+ isTransformerExist = await AtImageView.isTransformerExist();
+ assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionTransformer);
+
+ // Match if rotator exist with expectations in multiple selected mode
+ isTransformerExist = await AtImageView.isRotaterExist();
+ assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionRotator);
+});
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer))
+ .Scenario('Resizing a single region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+ const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
+
+ // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, 50, 50, 100, 100, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Select the shape
+ AtImageView.clickAt(100, 100);
+ AtSidebar.seeSelectedRegion();
+
+ // Switch to move tool to force appearance of transformer
+ I.pressKey('v');
+ const isTransformerExist = await AtImageView.isTransformerExist();
+
+ assert.strictEqual(isTransformerExist, true);
+
+
+ // Transform the shape
+ // Move the top anchor up for 50px (limited by image border) => {x1:50,y1:0,x2:150,y2:150}
+ AtImageView.drawByDrag(100, 50, 0, -100);
+ // Move the left anchor left for 50px (limited by image border) => {x1:0,y1:0,x2:150,y2:150}
+ AtImageView.drawByDrag(50, 75, -300, -100);
+ // Move the right anchor left for 50px => {x1:0,y1:0,x2:100,y2:150}
+ AtImageView.drawByDrag(150, 75, -50, 0);
+ // Move the bottom anchor down for 100px => {x1:0,y1:0,x2:100,y2:250}
+ AtImageView.drawByDrag(50, 150, 10, 100);
+ // Move the right-bottom anchor right for 200px and down for 50px => {x1:0,y1:0,x2:300,y2:300}
+ AtImageView.drawByDrag(100, 250, 200, 50);
+ // Check resulting sizes
+ const rectangleResult = await LabelStudio.serialize();
+ const exceptedResult = Shape.byBBox(0, 0, 300, 300).result;
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult));
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer))
+ .Scenario('Resizing a single region with zoom', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ LabelStudio.setFeatureFlags({
+ 'ff_front_dev_2394_zoomed_transforms_260522_short': true,
+ 'fflag_fix_front_dev_3377_image_regions_shift_on_resize_280922_short': true,
+ 'fflag_fix_front_dev_3793_relative_coords_short': true,
+ });
+
+ I.amOnPage('/');
+
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const naturalSize = await AtImageView.getNaturalSize();
+ const canvasSize = await AtImageView.getCanvasSize();
+ // region sizes are relative (0 to 100) so we have to convert sizes we use for them...
+ // ...relatively to displayed image size, which is canvas size when we open the page
+ const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
+
+ // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, 50, 50, 300, 300, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Select the shape
+ AtImageView.clickAt(100, 100);
+ AtSidebar.seeSelectedRegion();
+
+ // Switch to move tool to force appearance of transformer
+ I.pressKey('v');
+ const isTransformerExist = await AtImageView.isTransformerExist();
+
+ assert.strictEqual(isTransformerExist, true);
+
+ // we display an image to fit to canvas size on page load, so initial zoom is not 1;
+ // to do an x3 zoom we have to calculate current zoom and multiply it by 3
+ AtImageView.setZoom(3 * canvasSize.width / naturalSize.width, 0, 0);
+
+ // Transform the shape
+ AtImageView.drawByDrag(150, 150, -150, -150);
+
+ AtImageView.drawByDrag(0, 0, -300, -100);
+
+ AtImageView.drawByDrag(0, 0, 150, 150);
+
+ // Check resulting sizes
+ const rectangleResult = await LabelStudio.serialize();
+ const exceptedResult = Shape.byBBox(50, 50, 300, 300).result;
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult), 2);
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
+ .Scenario('Simple rotating', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ // Draw a region in bbox {x1:40%,y1:40%,x2:60%,y2:60%}
+ const rectangle = {
+ x: canvasSize.width * .4,
+ y: canvasSize.height * .4,
+ width: canvasSize.width * .2,
+ height: canvasSize.height * .2,
+ };
+ const rectangleCenter = {
+ x: rectangle.x + rectangle.width / 2,
+ y: rectangle.y + rectangle.height / 2,
+ };
+
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+
+ // Select the shape and check that transformer appears
+ AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
+ AtSidebar.seeSelectedRegion();
+
+ // Switch to move tool to force appearance of transformer
+ I.pressKey('v');
+ const isTransformerExist = await AtImageView.isTransformerExist();
+
+ assert.strictEqual(isTransformerExist, true);
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ const rotatorPosition = {
+ x: rectangleCenter.x,
+ y: rectangle.y - 50,
+ };
+
+ // Rotate for 45 degrees clockwise
+ AtImageView.drawThroughPoints(
+ [
+ [rotatorPosition.x, rotatorPosition.y],
+ [rectangleCenter.x + 500, rectangleCenter.y - 500],
+ ], 'steps', 5);
+
+ // Check resulting rotation
+ const rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(Math.round(rectangleResult[0].value.rotation), 45);
+
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
+ .Scenario('Rotating of unrotatable region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ // Draw a region which we cannot rotate 'cause of position near the image's border {x1:0,y1:20%,x2:20%,y2:50%}
+ const rectangle = {
+ x: 0,
+ y: canvasSize.height * .2,
+ width: canvasSize.width * .2,
+ height: canvasSize.height * .3,
+ };
+ const rectangleCenter = {
+ x: rectangle.x + rectangle.width / 2,
+ y: rectangle.y + rectangle.height / 2,
+ };
+
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Select the shape and check that transformer appears
+ AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
+ AtSidebar.seeSelectedRegion();
+
+ // Switch to move tool to force appearance of transformer
+ I.pressKey('v');
+ const isTransformerExist = await AtImageView.isTransformerExist();
+
+ assert.strictEqual(isTransformerExist, true);
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ const rotatorPosition = {
+ x: rectangleCenter.x,
+ y: rectangle.y - 50,
+ };
+
+ // Rotate for 45 degrees clockwise
+ AtImageView.drawByDrag(rotatorPosition.x, rotatorPosition.y, rectangleCenter.y - rotatorPosition.y + 100, -100);
+
+ // Check the region hasn't been rotated
+ const rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 0);
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
+ .Scenario('Broke the limits with rotation', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ {
+ // Draw a region which have limitation at rotating by bbox {x1:5,y1:100,x2:305,y2:350}
+ const rectangle = {
+ x: 5,
+ y: 100,
+ width: 300,
+ height: 300,
+ };
+ const rectangleCenter = {
+ x: rectangle.x + rectangle.width / 2,
+ y: rectangle.y + rectangle.height / 2,
+ };
+
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Select the shape and check that transformer appears
+ AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
+ AtSidebar.seeSelectedRegion();
+
+ // Switch to move tool to force appearance of transformer
+ I.pressKey('v');
+ const isTransformerExist = await AtImageView.isTransformerExist();
+
+ assert.strictEqual(isTransformerExist, true);
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ const rotatorPosition = {
+ x: rectangleCenter.x,
+ y: rectangle.y - 50,
+ };
+
+ // Rotate for 45 degrees clockwise
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [rectangleCenter.x + 500, rectangleCenter.y - 500],
+ ], 'steps', 200);
+
+ // Check that we cannot rotate it like this
+ let rectangleResult = await LabelStudio.serialize();
+
+ assert.notStrictEqual(
+ Math.round(rectangleResult[0].value.rotation),
+ 0,
+ 'Region must be rotated',
+ );
+ assert.notStrictEqual(
+ Math.round(rectangleResult[0].value.rotation),
+ 45,
+ 'Angle must not be 45 degrees',
+ );
+
+ // Undo changes
+ I.pressKey(['CommandOrControl', 'z']);
+
+ // Rotate for 90 degrees clockwise instead
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [rectangle.x + rectangle.width + 100, rectangleCenter.y],
+ [rectangle.x + rectangle.width + 200, rectangleCenter.y],
+ ], 'steps', 200);
+
+ // Check the resulted rotation
+ rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90, 'Angle must be 90 degrees');
+ // remove region
+ I.pressKey('Backspace');
+ }
+
+ I.say('Check that it works same way with right border');
+
+ {
+ // Draw a region which have limitation at rotating by bbox {x1:100% - 305,y1:100,x2:100% - 5,y2:350}
+ const rectangle = {
+ x: canvasSize.width - 305,
+ y: 100,
+ width: 300,
+ height: 300,
+ };
+ const rectangleCenter = {
+ x: rectangle.x + rectangle.width / 2,
+ y: rectangle.y + rectangle.height / 2,
+ };
+
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Select the shape and check that transformer appears
+ AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
+ AtSidebar.seeSelectedRegion();
+
+ // Switch to move tool to force appearance of transformer
+ I.pressKey('v');
+ const isTransformerExist = await AtImageView.isTransformerExist();
+
+ assert.strictEqual(isTransformerExist, true);
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ const rotatorPosition = {
+ x: rectangleCenter.x,
+ y: rectangle.y - 50,
+ };
+
+ // Rotate for 45 degrees clockwise
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [rectangleCenter.x + 500, rectangleCenter.y - 500],
+ ], 'steps', 200);
+
+ // Check the resulted rotation
+ let rectangleResult = await LabelStudio.serialize();
+
+ assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 0);
+ assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 45);
+
+ // Undo changes
+ I.pressKey(['CommandOrControl', 'z']);
+
+ // Rotate for 90 degrees clockwise instead
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [rectangle.x + rectangle.width + 100, rectangleCenter.y],
+ [rectangle.x + rectangle.width + 200, rectangleCenter.y],
+ ], 'steps', 200);
+
+ // Check that we cannot rotate it like this
+ rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90);
+ }
+
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
+ .Scenario('Check the initial rotation of transformer for the single region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+
+ const bbox = {
+ x: 100,
+ y: 100,
+ width: 100,
+ height: 100,
+ };
+ const bboxCenter = {
+ x: bbox.x + bbox.width / 2,
+ y: bbox.y + bbox.height / 2,
+ };
+
+ // Draw a region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Select it
+ AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
+ AtSidebar.seeSelectedRegion();
+
+ // Switch to move tool to force appearance of transformer
+ I.pressKey('v');
+ const isTransformerExist = await AtImageView.isTransformerExist();
+
+ assert.strictEqual(isTransformerExist, true);
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ let rotatorPosition = {
+ x: bboxCenter.x,
+ y: bbox.y - 50,
+ };
+
+ // Rotate for 90 degrees clockwise
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [bbox.x + bbox.width + 100, bboxCenter.y],
+ [bbox.x + bbox.width + 200, bboxCenter.y],
+ ], 'steps', 10);
+
+ // Unselect current region
+ I.pressKey('u');
+ AtSidebar.dontSeeSelectedRegion();
+
+ // Select it again
+ AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
+ AtSidebar.seeSelectedRegion();
+
+ // The trick is that we turn it further, based on the assumption that transformer appears in rotated state on region selection
+ // So let's try to rotate it
+ // The rotator anchor must be to the right of the right anchor by 50 pixels
+
+ rotatorPosition = {
+ x: bbox.x + bbox.width + 50,
+ y: bboxCenter.y,
+ };
+
+ // Rotate for 90 degrees clockwise once again
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [bboxCenter.x, bbox.y + bbox.height + 100],
+ [bboxCenter.x, bbox.y + bbox.height + 200],
+ ], 'steps', 10);
+
+ // Check that region has been rotated for 180 degrees
+ const rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180);
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
+ .Scenario('Check the initial rotation of transformer for the couple of regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+
+ const bbox1 = {
+ x: 100,
+ y: 100,
+ width: 40,
+ height: 40,
+ };
+
+ const bbox2 = {
+ x: 160,
+ y: 160,
+ width: 40,
+ height: 40,
+ };
+
+ const transformerBbox = {
+ x: bbox1.x,
+ y: bbox1.y,
+ width: bbox2.x + bbox2.width - bbox1.x,
+ height: bbox2.y + bbox2.height - bbox1.y,
+ };
+ const transformerBboxCenter = {
+ x: transformerBbox.x + transformerBbox.width / 2,
+ y: transformerBbox.y + transformerBbox.height / 2,
+ };
+
+ // Draw the first region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Draw the second region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
+ AtSidebar.seeRegions(2);
+
+ // Switch to move tool and select them
+ I.pressKey('v');
+ AtImageView.drawThroughPoints([
+ [transformerBbox.x - 20, transformerBbox.y - 20],
+ [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
+ ]);
+ AtSidebar.seeSelectedRegion();
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ const rotatorPosition = {
+ x: transformerBboxCenter.x,
+ y: transformerBbox.y - 50,
+ };
+
+ // Rotate for 180 degrees clockwise
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [transformerBboxCenter.x + 100, transformerBboxCenter.y + 100],
+ [transformerBboxCenter.x, transformerBboxCenter.y + 100],
+ [transformerBboxCenter.x, transformerBboxCenter.y + 200],
+ ], 'steps', 10);
+
+ // Unselect current regions
+ I.pressKey('u');
+ AtSidebar.dontSeeSelectedRegion();
+
+ // Select them again
+ AtImageView.drawThroughPoints([
+ [transformerBbox.x - 20, transformerBbox.y - 20],
+ [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
+ ]);
+ AtSidebar.seeSelectedRegion();
+
+ // So we have couple of rotated regions, let's check if rotates still appears above the top anchor
+
+ // Rotate for 90 degrees clockwise
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [transformerBboxCenter.x + 100, transformerBboxCenter.y],
+ [transformerBboxCenter.x + 200, transformerBboxCenter.y],
+ ], 'steps', 10);
+
+ // Check that region has been rotated for (180 + 90) degrees
+ const rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180 + 90);
+ });
+
+// KeyPoints are transformed unpredictable so for now just skip them
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer && shapeName !== 'KeyPoint'))
+ .Scenario('Transforming of multiple regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+ const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
+
+ const bbox1 = {
+ x: 100,
+ y: 100,
+ width: 50,
+ height: 50,
+ };
+
+ const bbox2 = {
+ x: 150,
+ y: 150,
+ width: 50,
+ height: 50,
+ };
+
+ const transformerBbox = {
+ x: bbox1.x,
+ y: bbox1.y,
+ width: bbox2.x + bbox2.width - bbox1.x,
+ height: bbox2.y + bbox2.height - bbox1.y,
+ };
+ const transformerBboxCenter = {
+ get x() {
+ return transformerBbox.x + transformerBbox.width / 2;
+ },
+ get y() {
+ return transformerBbox.y + transformerBbox.height / 2;
+ },
+ };
+
+ // Draw the first region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Draw the second region
+ I.pressKey(Shape.hotKey);
+ I.pressKeyDown('Control');
+ drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
+ I.pressKeyUp('Control');
+ AtSidebar.seeRegions(2);
+
+ // Switch to move tool and select them
+ I.pressKey('v');
+ AtImageView.drawThroughPoints([
+ [transformerBbox.x - 20, transformerBbox.y - 20],
+ [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
+ ]);
+ AtSidebar.seeSelectedRegion();
+ // Scale the shapes vertically
+ AtImageView.drawByDrag(transformerBboxCenter.x, transformerBbox.y + transformerBbox.height, 0, 50);
+ transformerBbox.height += 50;
+ AtSidebar.seeSelectedRegion();
+ // Scale the shapes horizontally
+ AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBboxCenter.y, 50, 0);
+ transformerBbox.width += 50;
+ AtSidebar.seeSelectedRegion();
+ // Scale the shapes in both directions
+ AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBbox.y + transformerBbox.height, 50, 50);
+ transformerBbox.height += 50;
+ transformerBbox.width += 50;
+ AtSidebar.seeSelectedRegion();
+
+ // Check resulting sizes
+ const rectangleResult = await LabelStudio.serialize();
+ const exceptedResult1 = Shape.byBBox(bbox1.x, bbox1.y, bbox1.width + 50, bbox1.height + 50).result;
+ const exceptedResult2 = Shape.byBBox(bbox2.x + 50, bbox2.y + 50, bbox2.width + 50, bbox2.height + 50).result;
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1));
+ Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2));
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer))
+ .Scenario('Move regions by drag', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+ const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
+
+ const bbox1 = {
+ x: 100,
+ y: 100,
+ width: 20,
+ height: 20,
+ };
+ const bbox1Center = {
+ x: bbox1.x + bbox1.width / 2,
+ y: bbox1.y + bbox1.height / 2,
+ };
+
+ const bbox2 = {
+ x: 140,
+ y: 140,
+ width: 20,
+ height: 20,
+ };
+ const bbox2Center = {
+ x: bbox2.x + bbox2.width / 2,
+ y: bbox2.y + bbox2.height / 2,
+ };
+
+ const transformerBbox = {
+ x: bbox1.x,
+ y: bbox1.y,
+ width: bbox2.x + bbox2.width - bbox1.x,
+ height: bbox2.y + bbox2.height - bbox1.y,
+ };
+ const transformerBboxCenter = {
+ x: transformerBbox.x + transformerBbox.width / 2,
+ y: transformerBbox.y + transformerBbox.height / 2,
+ };
+
+ // Draw the first region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Draw the second region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
+ AtSidebar.seeRegions(2);
+
+ if (shapeName === 'KeyPoint') {
+ // Draw more points to get more space in transformer
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox1.x, bbox1.y, 0, 0, AtImageView);
+ AtSidebar.seeRegions(3);
+
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox2.x + bbox2.width, bbox2.y + bbox2.height, 0, 0, AtImageView);
+ AtSidebar.seeRegions(4);
+ }
+
+ // Switch to move tool and select them
+ I.pressKey('v');
+ AtImageView.drawThroughPoints([
+ [transformerBbox.x - 20, transformerBbox.y - 20],
+ [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
+ ]);
+ AtSidebar.seeSelectedRegion();
+
+ const dragShapes = (startPoint, shift, rememberShift = true) => {
+ AtImageView.drawThroughPoints([
+ [startPoint.x, startPoint.y],
+ [startPoint.x + shift.x, startPoint.y + shift.y],
+ [startPoint.x + shift.x, startPoint.y + shift.y],
+ ], 'steps', 10);
+ AtSidebar.seeSelectedRegion();
+
+ if (rememberShift) {
+ bbox1Center.x += shift.x;
+ bbox1Center.y += shift.y;
+ bbox2Center.x += shift.x;
+ bbox2Center.y += shift.y;
+ transformerBboxCenter.x += shift.x;
+ transformerBboxCenter.y += shift.y;
+ }
+ };
+
+ // Drag shapes by holding onto the first region
+ dragShapes(bbox1Center, { x: 100, y: 0 });
+ // Drag shapes by holding onto the second region
+ dragShapes(bbox2Center, { x: 0, y: 100 });
+ // Drag shapes by holding onto the transformer background
+ dragShapes(transformerBboxCenter, { x: 150, y: 150 }, false);
+ // Move back throught history to check that transformer's background moving with it
+ I.pressKey(['CommandOrControl', 'z']);
+ // Drag shapes by holding onto the transformer background again
+ dragShapes(transformerBboxCenter, { x: 100, y: 100 }, false);
+
+
+ // Check that dragging was successful
+ const rectangleResult = await LabelStudio.serialize();
+ const exceptedResult1 = Shape.byBBox(bbox1.x + 200, bbox1.y + 200, bbox1.width, bbox1.height).result;
+ const exceptedResult2 = Shape.byBBox(bbox2.x + 200, bbox2.y + 200, bbox2.width, bbox2.height).result;
+
+ Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1));
+ Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2));
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
+ .Scenario('Limitation of dragging a single rotated region', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ const bbox = {
+ x: canvasSize.width / 2 - 50,
+ y: canvasSize.height / 2 - 50,
+ width: 100,
+ height: 100,
+ };
+ const bboxCenter = {
+ x: bbox.x + bbox.width / 2,
+ y: bbox.y + bbox.height / 2,
+ };
+
+ // Draw a region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Select it
+ AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
+ AtSidebar.seeSelectedRegion();
+
+ // Switch to move tool to force appearance of transformer
+ I.pressKey('v');
+ const isTransformerExist = await AtImageView.isTransformerExist();
+
+ assert.strictEqual(isTransformerExist, true);
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ const rotatorPosition = {
+ x: bboxCenter.x,
+ y: bbox.y - 50,
+ };
+
+ // Rotate for 180 degrees clockwise
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [bboxCenter.x + 100, bboxCenter.y],
+ [bboxCenter.x, bboxCenter.y + 100],
+ [bboxCenter.x, bboxCenter.y + 200],
+ ], 'steps', 10);
+
+ // When we have the rotated region, we need to check its behavior when we drag it across the borders of the image
+ let rectangleResult;
+
+ I.say('Drag the region over the left border');
+ AtImageView.drawThroughPoints([
+ [bboxCenter.x, bboxCenter.y],
+ [-500, bboxCenter.y],
+ ], 'steps', 20);
+ AtSidebar.seeSelectedRegion();
+ // moving of the region should be constrained by borders
+ rectangleResult = await LabelStudio.serialize();
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[0].value.x * canvasSize.width / 100,
+ Shape.byBBox(bbox.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x,
+ );
+ // reset position by undo
+ I.pressKey(['CommandOrControl', 'z']);
+
+ I.say('Drag the region over the top border');
+ AtImageView.drawThroughPoints([
+ [bboxCenter.x, bboxCenter.y],
+ [bboxCenter.x, -500],
+ ], 'steps', 20);
+ AtSidebar.seeSelectedRegion();
+ // moving of the region should be constrained by borders
+ rectangleResult = await LabelStudio.serialize();
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[0].value.y * canvasSize.height / 100,
+ Shape.byBBox(bbox.x + bbox.width, bbox.height, -bbox.width, -bbox.height).result.y,
+ );
+ // reset position by undo
+ I.pressKey(['CommandOrControl', 'z']);
+
+ I.say('Drag the region over the right border');
+ AtImageView.drawThroughPoints([
+ [bboxCenter.x, bboxCenter.y],
+ [canvasSize.width + 500, bboxCenter.y],
+ ], 'steps', 20);
+ AtSidebar.seeSelectedRegion();
+ // moving of the region should be constrained by borders
+ rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[0].value.x * canvasSize.width / 100,
+ Shape.byBBox(canvasSize.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x,
+ );
+ // reset position by undo
+ I.pressKey(['CommandOrControl', 'z']);
+
+ I.say('Drag the region over the bottom border');
+ AtImageView.drawThroughPoints([
+ [bboxCenter.x, bboxCenter.y],
+ [bboxCenter.x, canvasSize.height + 500],
+ ], 'steps', 20);
+ AtSidebar.seeSelectedRegion();
+ // moving of the region should be constrained by borders
+ rectangleResult = await LabelStudio.serialize();
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[0].value.y * canvasSize.height / 100,
+ Shape.byBBox(bbox.x + bbox.width, canvasSize.height, -bbox.width, -bbox.height).result.y,
+ );
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator))
+ .Scenario('Limitation of dragging a couple of rotated regions', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ const bbox1 = {
+ x: canvasSize.width / 2 - 50,
+ y: canvasSize.height / 2 - 50,
+ width: 50,
+ height: 50,
+ };
+
+ const bbox2 = {
+ x: canvasSize.width / 2,
+ y: canvasSize.height / 2,
+ width: 50,
+ height: 50,
+ };
+
+ const transformerBbox = {
+ x: bbox1.x,
+ y: bbox1.y,
+ width: bbox2.x + bbox2.width - bbox1.x,
+ height: bbox2.y + bbox2.height - bbox1.y,
+ };
+ const transformerBboxCenter = {
+ get x() {
+ return transformerBbox.x + transformerBbox.width / 2;
+ },
+ get y() {
+ return transformerBbox.y + transformerBbox.height / 2;
+ },
+ };
+
+ // Draw the first region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Draw the second region
+ I.pressKey(Shape.hotKey);
+ I.pressKeyDown('Control');
+ drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
+ I.pressKeyUp('Control');
+ AtSidebar.seeRegions(2);
+
+ // Select them by move tool
+ I.pressKey('v');
+ AtImageView.drawThroughPoints(
+ [
+ [transformerBbox.x - 50, transformerBbox.y - 50],
+ [transformerBbox.x + transformerBbox.width + 50, transformerBbox.y + transformerBbox.height + 50],
+ ], 'steps', 10,
+ );
+ AtSidebar.seeSelectedRegion();
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ const rotatorPosition = {
+ x: transformerBboxCenter.x,
+ y: transformerBbox.y - 50,
+ };
+
+ // Rotate for 180 degrees clockwise
+ AtImageView.drawThroughPoints([
+ [rotatorPosition.x, rotatorPosition.y],
+ [transformerBboxCenter.x + 100, transformerBboxCenter.y],
+ [transformerBboxCenter.x, transformerBboxCenter.y + 100],
+ [transformerBboxCenter.x, transformerBboxCenter.y + 200],
+ ], 'steps', 10);
+
+ // When we have the rotated region, we need to check its behavior when we drag it across the borders of the image
+ let rectangleResult;
+
+ I.say('Drag the region over the left border');
+ AtImageView.drawThroughPoints([
+ [transformerBboxCenter.x, transformerBboxCenter.y],
+ [-500, transformerBboxCenter.y],
+ ], 'steps', 20);
+ AtSidebar.seeSelectedRegion();
+ // moving of the region should be constrained by borders
+ rectangleResult = await LabelStudio.serialize();
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[0].value.x * canvasSize.width / 100,
+ Shape.byBBox(transformerBbox.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x,
+ );
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[1].value.x * canvasSize.width / 100,
+ Shape.byBBox(bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x,
+ );
+ // reset position by undo
+ I.pressKey(['CommandOrControl', 'z']);
+
+ I.say('Drag the region over the top border');
+ AtImageView.drawThroughPoints([
+ [transformerBboxCenter.x, transformerBboxCenter.y],
+ [transformerBboxCenter.x, -500],
+ ], 'steps', 20);
+ AtSidebar.seeSelectedRegion();
+ // moving of the region should be constrained by borders
+ rectangleResult = await LabelStudio.serialize();
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[0].value.y * canvasSize.height / 100,
+ Shape.byBBox(transformerBbox.x + transformerBbox.width, transformerBbox.height, -bbox1.width, -bbox1.height).result.y,
+ );
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[1].value.y * canvasSize.height / 100,
+ Shape.byBBox(transformerBbox.x + bbox2.width, bbox2.height, -bbox2.width, -bbox2.height).result.y,
+ );
+ // reset position by undo
+ I.pressKey(['CommandOrControl', 'z']);
+
+ I.say('Drag the region over the right border');
+ AtImageView.drawThroughPoints([
+ [transformerBboxCenter.x, transformerBboxCenter.y],
+ [canvasSize.width + 500, transformerBboxCenter.y],
+ ], 'steps', 20);
+ AtSidebar.seeSelectedRegion();
+ // moving of the region should be constrained by borders
+ rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[0].value.x * canvasSize.width / 100,
+ Shape.byBBox(canvasSize.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x,
+ );
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[1].value.x * canvasSize.width / 100,
+ Shape.byBBox(canvasSize.width - transformerBbox.width + bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x,
+ );
+ // reset position by undo
+ I.pressKey(['CommandOrControl', 'z']);
+
+ I.say('Drag the region over the bottom border');
+ AtImageView.drawThroughPoints([
+ [transformerBboxCenter.x, transformerBboxCenter.y],
+ [transformerBboxCenter.x, canvasSize.height + 500],
+ ], 'steps', 20);
+ AtSidebar.seeSelectedRegion();
+ // moving of the region should be constrained by borders
+ rectangleResult = await LabelStudio.serialize();
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[0].value.y * canvasSize.height / 100,
+ Shape.byBBox(transformerBbox.x + transformerBbox.width, canvasSize.height, -bbox1.width, -bbox1.height).result.y,
+ );
+ Asserts.deepEqualWithTolerance(
+ rectangleResult[1].value.y * canvasSize.height / 100,
+ Shape.byBBox(transformerBbox.x + bbox2.width, canvasSize.height - transformerBbox.height + bbox2.height, -bbox2.width, -bbox2.height).result.y,
+ );
+ });
+
+Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasRotator))
+ .Scenario('Rotating the region near the border', async ({ I, LabelStudio, AtImageView, AtSidebar, current }) => {
+ const { shapeName } = current;
+ const Shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ const bbox = {
+ x: canvasSize.width - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50,
+ y: canvasSize.height - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50,
+ width: 100,
+ height: 100,
+ };
+
+ const bboxCenter = {
+ x: bbox.x + bbox.width / 2,
+ y: bbox.y + bbox.height / 2,
+ };
+
+ // Draw the region
+ I.pressKey(Shape.hotKey);
+ drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
+ AtSidebar.seeRegions(1);
+
+ // Select it
+ AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
+ AtSidebar.seeSelectedRegion();
+
+ // The rotator anchor must be above top anchor by 50 pixels
+ const rotatorPosition = {
+ x: bboxCenter.x,
+ y: bbox.y - 50,
+ };
+
+ // Check 7 different rotations
+ const rotatorWayPoints = [[rotatorPosition.x, rotatorPosition.y]];
+ const angle45 = Math.PI / 4;
+
+ for (let i = 0; i < 8; i++) {
+ const angle = angle45 * i;
+
+ rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 100, bboxCenter.y - Math.cos(angle) * 100]);
+ rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 1000, bboxCenter.y - Math.cos(angle) * 1000]);
+
+ // Rotate clockwise by 45 * i degrees
+ AtImageView.drawThroughPoints(rotatorWayPoints, 'steps', 10);
+ AtSidebar.seeSelectedRegion();
+ // Check that rotating was successful
+ const rectangleResult = await LabelStudio.serialize();
+
+ Asserts.deepEqualWithTolerance(
+ Math.round(rectangleResult[0].value.rotation),
+ 45 * i,
+ );
+
+ // undo rotation
+ I.pressKey(['CommandOrControl', 'z']);
+ // clear unnecessary waypoints
+ rotatorWayPoints.pop();
+ }
+ });
diff --git a/web/libs/editor/tests/e2e/tests/maxUsage.test.js b/web/libs/editor/tests/e2e/tests/maxUsage.test.js
index 0299b7e344b5..fc2111838081 100644
--- a/web/libs/editor/tests/e2e/tests/maxUsage.test.js
+++ b/web/libs/editor/tests/e2e/tests/maxUsage.test.js
@@ -1,340 +1,340 @@
-Feature('Max usage');
-
-const IMAGE = 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg';
-
-const createImageToolsConfig = ({ maxUsage }) => `
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-const createImageLabelsConfig = ({ maxUsage }) => `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `;
-
-const shapes = {
- Rectangle: {
- drawAction: 'drawByDrag',
- shortcut: 'r',
- hotkey: '1',
- byBBox(x, y, width, height) {
- return {
- params: [x, y, width, height],
- };
- },
- },
- Ellipse: {
- drawAction: 'drawByDrag',
- shortcut: 'o',
- hotkey: '2',
- byBBox(x, y, width, height) {
- return {
- params: [x + width / 2, y + height / 2, width / 2, height / 2],
- };
- },
- },
- Brush: {
- drawAction: 'clickAt',
- shortcut: 'b',
- hotkey: '3',
- byBBox(x, y, width, height) {
- return {
- params: [x + width / 2, y + height / 2],
- };
- },
- },
- KeyPoint: {
- drawAction: 'clickAt',
- shortcut: 'k',
- hotkey: '4',
- byBBox(x, y, width, height) {
- return {
- params: [x + width / 2, y + height / 2],
- };
- },
- },
- Polygon: {
- drawAction: 'drawByClickingPoints',
- shortcut: 'p',
- hotkey: '5',
- byBBox(x, y, width, height) {
- const points = [];
-
- points.push([x, y]);
- points.push([x + width, y]);
- points.push([x + width / 2, y + height]);
- return {
- params: [[...points, points[0]]],
- };
- },
- },
-};
-
-function drawShapeByBbox(Shape, x, y, width, height, where) {
- where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params);
-}
-
-const maxUsageImageToolsDataTable = new DataTable(['maxUsage', 'shapeName']);
-
-[1,3].forEach(maxUsage => {
- Object.keys(shapes).forEach(shapeName => {
- maxUsageImageToolsDataTable.add([maxUsage, shapeName]);
- });
-});
-
-const maxUsageDataTable = new DataTable(['maxUsage']);
-
-[1,3].forEach(maxUsage => {
- maxUsageDataTable.add([maxUsage]);
-});
-
-Data(maxUsageImageToolsDataTable).Scenario('Max usages of separated labels in ImageView on region creating', async function({ I, LabelStudio, AtImageView, AtSidebar, current }) {
- const { maxUsage, shapeName } = current;
- const shape = shapes[shapeName];
- const annotations = [];
-
- for (let k = 0; k < maxUsage; k++) {
- annotations.push({
- 'value': {
- 'x': k,
- 'y': 1,
- 'width': 0.6666666666666666,
- 'labels': [
- 'Label_1',
- ],
- },
- 'id': k,
- 'from_name': 'Labels',
- 'to_name': 'img',
- 'type': 'labels',
- });
- }
-
- I.amOnPage('/');
- LabelStudio.setFeatureFlags({
- fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
- });
- LabelStudio.init({
- config: createImageToolsConfig({ maxUsage }),
- data: {
- image: IMAGE,
- },
- annotations: [{
- id: 'test',
- result: annotations,
- }],
- });
- await AtImageView.waitForImage();
- await AtImageView.lookForStage();
- AtSidebar.seeRegions(maxUsage);
-
-
- I.pressKey('1');
- I.pressKey(shape.shortcut);
- AtImageView.clickAt(50, 50);
-
- I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
-});
-
-Data(maxUsageImageToolsDataTable).Scenario('Max usages of labels in ImageView on region creating', async function({ I, LabelStudio, AtImageView, AtSidebar, current }) {
- const { maxUsage, shapeName } = current;
- const shape = shapes[shapeName];
-
- I.amOnPage('/');
- LabelStudio.setFeatureFlags({
- fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
- });
- LabelStudio.init({
- config: createImageLabelsConfig({ maxUsage }),
- data: {
- image: IMAGE,
- },
- });
-
- await AtImageView.waitForImage();
- await AtImageView.lookForStage();
- AtSidebar.seeRegions(0);
-
- for (let k = 0; k < maxUsage; k++) {
- I.pressKey(shape.hotkey);
- drawShapeByBbox(shape, 1 + 50 * k, 1, 30,30, AtImageView);
- I.pressKey('u');
- }
-
- I.pressKey(shape.hotkey);
- AtImageView.clickAt(50, 50);
-
- I.see(`You can't use ${shapeName}_1 more than ${maxUsage} time(s)`);
-});
-
-Data(maxUsageDataTable).Scenario('Max usages of labels in Audio on region creation', async function({ I, LabelStudio, AtSidebar, AtAudioView, current }) {
- const { maxUsage } = current;
-
- LabelStudio.setFeatureFlags({
- fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
- ff_front_dev_2715_audio_3_280722_short: true,
- });
- I.amOnPage('/');
- LabelStudio.init({
- config: `
-
-
-
-
-
-
-`,
- data: {
- audio: 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3',
- },
- });
-
- await AtAudioView.waitForAudio();
- await AtAudioView.lookForStage();
- AtSidebar.seeRegions(0);
-
- for (let k = 0; k < maxUsage; k++) {
- I.pressKey('1');
- AtAudioView.dragAudioElement(10 + 40 * k,30);
- I.pressKey('u');
- }
- I.pressKey('1');
- AtAudioView.dragAudioElement(10 + 40 * maxUsage,30);
-
- AtSidebar.seeRegions(maxUsage);
- I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
-});
-
-Data(maxUsageDataTable).Scenario('Max usages of labels in RichText on region creation', async function({ I, LabelStudio, AtSidebar, AtRichText, current }) {
- const { maxUsage } = current;
-
- I.amOnPage('/');
- LabelStudio.setFeatureFlags({
- fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
- });
- LabelStudio.init({
- config: `
-
-
-
-
-
-
-`,
- data: {
- url: 'https://htx-pub.s3.amazonaws.com/example.txt',
- },
- });
-
- LabelStudio.waitForObjectsReady();
- AtSidebar.seeRegions(0);
-
- for (let k = 0; k < maxUsage; k++) {
- I.pressKey('1');
- AtRichText.selectTextByGlobalOffset(1 + 5 * k, 5 * (k + 1));
- I.pressKey('u');
- }
- I.pressKey('1');
- AtRichText.selectTextByGlobalOffset(1 + 5 * maxUsage, 5 * (maxUsage + 1));
-
- I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
-});
-
-Data(maxUsageDataTable).Scenario('Max usages of labels in Paragraphs on region creation', async function({ I, LabelStudio, AtSidebar, AtParagraphs, current }) {
- const { maxUsage } = current;
-
- I.amOnPage('/');
- LabelStudio.setFeatureFlags({
- fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
- });
- LabelStudio.init({
- config: `
-
-
-
-
-
-
-`,
- data: require('../examples/text-paragraphs').data,
- });
-
- LabelStudio.waitForObjectsReady();
- AtSidebar.seeRegions(0);
-
- for (let k = 0; k < maxUsage; k++) {
- I.pressKey('1');
- AtParagraphs.selectTextByOffset(k + 1, 0, 3);
- I.pressKey('u');
- }
- I.pressKey('1');
- AtParagraphs.selectTextByOffset(maxUsage + 1, 0, 3);
-
- I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
-});
-
-Data(maxUsageDataTable).Scenario('Max usages of labels in Timeseries on region creation', async function({ I, LabelStudio, AtSidebar, AtTimeSeries, current }) {
- const { maxUsage } = current;
-
- I.amOnPage('/');
- LabelStudio.setFeatureFlags({
- fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
- });
- LabelStudio.init({
- config: `
-
-
-
-
-
-
-
-
-
-`,
- data: require('../examples/data/sample-sin.json'),
- });
-
- LabelStudio.waitForObjectsReady();
- AtSidebar.seeRegions(0);
- await AtTimeSeries.lookForStage();
-
- for (let k = 0; k < maxUsage; k++) {
- I.pressKey('1');
- AtTimeSeries.drawByDrag(1 + k * 20, 10);
- I.pressKey('u');
- }
- I.pressKey('1');
- AtTimeSeries.drawByDrag(1 + maxUsage * 20, 10);
-
- I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
-});
+Feature('Max usage');
+
+const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg';
+
+const createImageToolsConfig = ({ maxUsage }) => `
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+const createImageLabelsConfig = ({ maxUsage }) => `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+const shapes = {
+ Rectangle: {
+ drawAction: 'drawByDrag',
+ shortcut: 'r',
+ hotkey: '1',
+ byBBox(x, y, width, height) {
+ return {
+ params: [x, y, width, height],
+ };
+ },
+ },
+ Ellipse: {
+ drawAction: 'drawByDrag',
+ shortcut: 'o',
+ hotkey: '2',
+ byBBox(x, y, width, height) {
+ return {
+ params: [x + width / 2, y + height / 2, width / 2, height / 2],
+ };
+ },
+ },
+ Brush: {
+ drawAction: 'clickAt',
+ shortcut: 'b',
+ hotkey: '3',
+ byBBox(x, y, width, height) {
+ return {
+ params: [x + width / 2, y + height / 2],
+ };
+ },
+ },
+ KeyPoint: {
+ drawAction: 'clickAt',
+ shortcut: 'k',
+ hotkey: '4',
+ byBBox(x, y, width, height) {
+ return {
+ params: [x + width / 2, y + height / 2],
+ };
+ },
+ },
+ Polygon: {
+ drawAction: 'drawByClickingPoints',
+ shortcut: 'p',
+ hotkey: '5',
+ byBBox(x, y, width, height) {
+ const points = [];
+
+ points.push([x, y]);
+ points.push([x + width, y]);
+ points.push([x + width / 2, y + height]);
+ return {
+ params: [[...points, points[0]]],
+ };
+ },
+ },
+};
+
+function drawShapeByBbox(Shape, x, y, width, height, where) {
+ where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params);
+}
+
+const maxUsageImageToolsDataTable = new DataTable(['maxUsage', 'shapeName']);
+
+[1,3].forEach(maxUsage => {
+ Object.keys(shapes).forEach(shapeName => {
+ maxUsageImageToolsDataTable.add([maxUsage, shapeName]);
+ });
+});
+
+const maxUsageDataTable = new DataTable(['maxUsage']);
+
+[1,3].forEach(maxUsage => {
+ maxUsageDataTable.add([maxUsage]);
+});
+
+Data(maxUsageImageToolsDataTable).Scenario('Max usages of separated labels in ImageView on region creating', async function({ I, LabelStudio, AtImageView, AtSidebar, current }) {
+ const { maxUsage, shapeName } = current;
+ const shape = shapes[shapeName];
+ const annotations = [];
+
+ for (let k = 0; k < maxUsage; k++) {
+ annotations.push({
+ 'value': {
+ 'x': k,
+ 'y': 1,
+ 'width': 0.6666666666666666,
+ 'labels': [
+ 'Label_1',
+ ],
+ },
+ 'id': k,
+ 'from_name': 'Labels',
+ 'to_name': 'img',
+ 'type': 'labels',
+ });
+ }
+
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags({
+ fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
+ });
+ LabelStudio.init({
+ config: createImageToolsConfig({ maxUsage }),
+ data: {
+ image: IMAGE,
+ },
+ annotations: [{
+ id: 'test',
+ result: annotations,
+ }],
+ });
+ await AtImageView.waitForImage();
+ await AtImageView.lookForStage();
+ AtSidebar.seeRegions(maxUsage);
+
+
+ I.pressKey('1');
+ I.pressKey(shape.shortcut);
+ AtImageView.clickAt(50, 50);
+
+ I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
+});
+
+Data(maxUsageImageToolsDataTable).Scenario('Max usages of labels in ImageView on region creating', async function({ I, LabelStudio, AtImageView, AtSidebar, current }) {
+ const { maxUsage, shapeName } = current;
+ const shape = shapes[shapeName];
+
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags({
+ fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
+ });
+ LabelStudio.init({
+ config: createImageLabelsConfig({ maxUsage }),
+ data: {
+ image: IMAGE,
+ },
+ });
+
+ await AtImageView.waitForImage();
+ await AtImageView.lookForStage();
+ AtSidebar.seeRegions(0);
+
+ for (let k = 0; k < maxUsage; k++) {
+ I.pressKey(shape.hotkey);
+ drawShapeByBbox(shape, 1 + 50 * k, 1, 30,30, AtImageView);
+ I.pressKey('u');
+ }
+
+ I.pressKey(shape.hotkey);
+ AtImageView.clickAt(50, 50);
+
+ I.see(`You can't use ${shapeName}_1 more than ${maxUsage} time(s)`);
+});
+
+Data(maxUsageDataTable).Scenario('Max usages of labels in Audio on region creation', async function({ I, LabelStudio, AtSidebar, AtAudioView, current }) {
+ const { maxUsage } = current;
+
+ LabelStudio.setFeatureFlags({
+ fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
+ ff_front_dev_2715_audio_3_280722_short: true,
+ });
+ I.amOnPage('/');
+ LabelStudio.init({
+ config: `
+
+
+
+
+
+
+`,
+ data: {
+ audio: 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3',
+ },
+ });
+
+ await AtAudioView.waitForAudio();
+ await AtAudioView.lookForStage();
+ AtSidebar.seeRegions(0);
+
+ for (let k = 0; k < maxUsage; k++) {
+ I.pressKey('1');
+ AtAudioView.dragAudioElement(10 + 40 * k,30);
+ I.pressKey('u');
+ }
+ I.pressKey('1');
+ AtAudioView.dragAudioElement(10 + 40 * maxUsage,30);
+
+ AtSidebar.seeRegions(maxUsage);
+ I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
+});
+
+Data(maxUsageDataTable).Scenario('Max usages of labels in RichText on region creation', async function({ I, LabelStudio, AtSidebar, AtRichText, current }) {
+ const { maxUsage } = current;
+
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags({
+ fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
+ });
+ LabelStudio.init({
+ config: `
+
+
+
+
+
+
+`,
+ data: {
+ url: 'https://htx-pub.s3.amazonaws.com/example.txt',
+ },
+ });
+
+ LabelStudio.waitForObjectsReady();
+ AtSidebar.seeRegions(0);
+
+ for (let k = 0; k < maxUsage; k++) {
+ I.pressKey('1');
+ AtRichText.selectTextByGlobalOffset(1 + 5 * k, 5 * (k + 1));
+ I.pressKey('u');
+ }
+ I.pressKey('1');
+ AtRichText.selectTextByGlobalOffset(1 + 5 * maxUsage, 5 * (maxUsage + 1));
+
+ I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
+});
+
+Data(maxUsageDataTable).Scenario('Max usages of labels in Paragraphs on region creation', async function({ I, LabelStudio, AtSidebar, AtParagraphs, current }) {
+ const { maxUsage } = current;
+
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags({
+ fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
+ });
+ LabelStudio.init({
+ config: `
+
+
+
+
+
+
+`,
+ data: require('../examples/text-paragraphs').data,
+ });
+
+ LabelStudio.waitForObjectsReady();
+ AtSidebar.seeRegions(0);
+
+ for (let k = 0; k < maxUsage; k++) {
+ I.pressKey('1');
+ AtParagraphs.selectTextByOffset(k + 1, 0, 3);
+ I.pressKey('u');
+ }
+ I.pressKey('1');
+ AtParagraphs.selectTextByOffset(maxUsage + 1, 0, 3);
+
+ I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
+});
+
+Data(maxUsageDataTable).Scenario('Max usages of labels in Timeseries on region creation', async function({ I, LabelStudio, AtSidebar, AtTimeSeries, current }) {
+ const { maxUsage } = current;
+
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags({
+ fflag_fix_front_dev_3666_max_usages_on_region_creation_171122_short: true,
+ });
+ LabelStudio.init({
+ config: `
+
+
+
+
+
+
+
+
+
+`,
+ data: require('../examples/data/sample-sin.json'),
+ });
+
+ LabelStudio.waitForObjectsReady();
+ AtSidebar.seeRegions(0);
+ await AtTimeSeries.lookForStage();
+
+ for (let k = 0; k < maxUsage; k++) {
+ I.pressKey('1');
+ AtTimeSeries.drawByDrag(1 + k * 20, 10);
+ I.pressKey('u');
+ }
+ I.pressKey('1');
+ AtTimeSeries.drawByDrag(1 + maxUsage * 20, 10);
+
+ I.see(`You can't use Label_1 more than ${maxUsage} time(s)`);
+});
diff --git a/web/libs/editor/tests/e2e/tests/ner-text.test.js b/web/libs/editor/tests/e2e/tests/ner-text.test.js
index fca960f77bc8..2c95c7379481 100644
--- a/web/libs/editor/tests/e2e/tests/ner-text.test.js
+++ b/web/libs/editor/tests/e2e/tests/ner-text.test.js
@@ -259,3 +259,26 @@ Scenario('NER Text with SECURE MODE', async function({ I, LabelStudio }) {
window.LS_SECURE_MODE = window.OLD_LS_SECURE_MODE;
});
});
+
+Scenario('NER Text regions in Outliner', async function({ I, LabelStudio }) {
+ const params = {
+ annotations: [{ id: 'TestCmpl', result: resultsFromUrl }],
+ config: configUrl,
+ data: { url },
+ };
+
+ I.amOnPage('/');
+ // enabling both flags for New UI, but will work with Otliner one only as well
+ LabelStudio.setFeatureFlags({
+ ff_front_1170_outliner_030222_short: true,
+ fflag_feat_front_dev_3873_labeling_ui_improvements_short: true,
+ });
+ LabelStudio.init(params);
+
+ I.waitForElement('.lsf-richtext__line', 60);
+
+ I.see('American political leader');
+
+ I.seeElement(locate('.lsf-outliner-item').withText(resultsFromUrl[0].value.text));
+ I.seeElement(locate('.lsf-outliner-item').withText(resultsFromUrl[1].value.text));
+});
diff --git a/web/libs/editor/tests/e2e/tests/ocr.test.js b/web/libs/editor/tests/e2e/tests/ocr.test.js
index 83869984b024..cb82ce333eb1 100644
--- a/web/libs/editor/tests/e2e/tests/ocr.test.js
+++ b/web/libs/editor/tests/e2e/tests/ocr.test.js
@@ -154,7 +154,7 @@ Scenario('Drawing multiple blank regions and then attaching labels', async ({ I,
'ff_front_1170_outliner_030222_short': true,
});
I.amOnPage('/');
- LabelStudio.init({ config: createConfig( ), data });
+ LabelStudio.init({ config: createConfig(), data });
AtImageView.waitForImage();
AtSettings.open();
AtSettings.setGeneralSettings({
@@ -192,12 +192,12 @@ Scenario('Drawing multiple blank regions and then attaching labels', async ({ I,
}
session('Deserialization', () => {
I.amOnPage('/');
- LabelStudio.init({ config: createConfig( ), data, annotations: [{ id: 'test', result: results }] });
+ LabelStudio.init({ config: createConfig(), data, annotations: [{ id: 'test', result: results }] });
AtImageView.waitForImage();
AtOutliner.seeRegions(regions.length);
for (const [idx, region] of Object.entries(regions)) {
if (region.text) {
- I.seeInField(AtOutliner.locateRegionIndex((+idx)+1).find('.lsf-textarea-tag__input'), region.text);
+ I.seeInField(AtOutliner.locateRegionIndex((+idx) + 1).find('.lsf-textarea-tag__input'), region.text);
}
}
});
diff --git a/web/libs/editor/tests/e2e/tests/paragraphs-filter.test.js b/web/libs/editor/tests/e2e/tests/paragraphs-filter.test.js
index 9ebdbdbea2e5..7d3f58fc6a3e 100644
--- a/web/libs/editor/tests/e2e/tests/paragraphs-filter.test.js
+++ b/web/libs/editor/tests/e2e/tests/paragraphs-filter.test.js
@@ -1,600 +1,600 @@
-const assert = require('assert');
-const { omitBy } = require('./helpers');
-
-Feature('Paragraphs filter');
-
-const AUDIO = 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3';
-
-const ANNOTATIONS = [
- {
- 'result': [
- {
- 'id':'ryzr4QdL93',
- 'from_name':'ner',
- 'to_name':'text',
- 'source':'$dialogue',
- 'type':'paragraphlabels',
- 'value':{
- 'start':'2',
- 'end':'4',
- 'startOffset':0,
- 'endOffset':134,
- 'paragraphlabels': ['Important Stuff'],
- 'text': 'Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?I dont know. Thats a good question.Thats when you know you found somebody really special. When you can just shut the door closed a minute, and comfortably share silence.',
- },
- },
- ],
- },
-];
-
-const DATA = {
- audio: AUDIO,
- dialogue: [
- {
- start: 3.1,
- end: 5.6,
- author: 'Mia Wallace',
- text: 'Dont you hate that?',
- },
- {
- start: 4.2,
- duration: 3.1,
- author: 'Vincent Vega:',
- text: 'Hate what?',
- },
- {
- author: 'Mia Wallace:',
- text: 'Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?',
- },
- {
- start: 90,
- author: 'Vincent Vega:',
- text: 'I dont know. Thats a good question.',
- },
- {
- author: 'Mia Wallace:',
- text:
- 'Thats when you know you found somebody really special. When you can just shut the door closed a minute, and comfortably share silence.',
- },
- ],
-};
-
-const CONFIG = `
-
-
-
-
-
-
-
-`;
-
-const FEATURE_FLAGS = {
- ff_front_dev_2669_paragraph_author_filter_210622_short: true,
- fflag_fix_front_dev_2918_labeling_filtered_paragraphs_250822_short: true,
-};
-
-Scenario('Create two results using excluding a phrase by the filter', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
- const params = {
- data: DATA,
- config: CONFIG,
- };
-
- I.amOnPage('/');
-
- LabelStudio.setFeatureFlags(FEATURE_FLAGS);
- LabelStudio.init(params);
- AtSidebar.seeRegions(0);
-
- I.say('Select 2 regions in the consecutive phrases of the one person');
-
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Hate what?'),
- 5,
- AtParagraphs.locateText('Hate what?'),
- 10,
- );
-
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('I dont know. Thats a good question.'),
- 0,
- AtParagraphs.locateText('I dont know. Thats a good question.'),
- 11,
- );
- AtSidebar.seeRegions(2);
-
- I.say('Take a snapshot');
- const twoActionsResult = LabelStudio.serialize();
-
- I.say('Reset to initial state');
- LabelStudio.init(params);
- AtSidebar.seeRegions(0);
-
- I.say('Filter the phrases by that person.');
- AtParagraphs.clickFilter('Vincent Vega:');
-
- I.say('Try to get the same result in one action');
-
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Hate what?'),
- 5,
- AtParagraphs.locateText('I dont know. Thats a good question.'),
- 11,
- );
- AtSidebar.seeRegions(2);
-
- I.say('Take a second snapshot');
- const oneActionResult = LabelStudio.serialize();
-
- I.say('The results should be identical');
-
- assert.deepStrictEqual(twoActionsResult, oneActionResult);
-
-});
-
-Scenario('Check different cases ', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
- const dialogue = [
- 1,// 1
- 3,// 2
- 1,// 3
- 2,// 4
- 3,// 5
- 1,// 6
- 2,// 7
- 1,// 8
- 3,// 9
- 1,// 10
- ].map((authorId, idx)=>({
- start: idx+1,
- end: idx+2,
- author: `Author ${authorId}`,
- text: `Message ${idx+1}`,
- }));
- const params = {
- config: CONFIG,
- data: {
- audio: AUDIO,
- dialogue,
- },
- };
-
- I.amOnPage('/');
-
- LabelStudio.setFeatureFlags(FEATURE_FLAGS);
- LabelStudio.init(params);
- AtSidebar.seeRegions(0);
-
- I.say('Hide Author 3');
- AtParagraphs.clickFilter('Author 1', 'Author 2');
-
- I.say('Make regions by selecting everything');
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Message 1'),
- 0,
- AtParagraphs.locateText('Message 10'),
- 10,
- );
-
- I.say('There should be 4 new regions');
- AtSidebar.seeRegions(4);
- {
- const result = await LabelStudio.serialize();
-
- assert.strictEqual(result.length, 4);
-
- assert.deepStrictEqual(omitBy(result[0].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '0',
- 'end': '0',
- 'startOffset': 0,
- 'endOffset': 9,
- 'text': 'Message 1',
- });
-
- assert.deepStrictEqual(omitBy(result[1].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '2',
- 'end': '3',
- 'startOffset': 0,
- 'endOffset': 9,
- 'text': 'Message 3\n\nMessage 4',
- });
-
- assert.deepStrictEqual(omitBy(result[2].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '5',
- 'end': '7',
- 'startOffset': 0,
- 'endOffset': 9,
- 'text': 'Message 6\n\nMessage 7\n\nMessage 8',
- });
-
- assert.deepStrictEqual(omitBy(result[3].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '9',
- 'end': '9',
- 'startOffset': 0,
- 'endOffset': 10,
- 'text': 'Message 10',
- });
- }
-
- I.say('Test the overlaps of regions #1');
- AtLabels.clickLabel('Important Stuff');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Message 3'),
- 4,
- AtParagraphs.locateText('Message 8'),
- 4,
- );
- AtSidebar.seeRegions(6);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(omitBy(result[4].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '2',
- 'end': '3',
- 'startOffset': 4,
- 'endOffset': 9,
- 'text': 'age 3\n\nMessage 4',
- });
-
- assert.deepStrictEqual(omitBy(result[5].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '5',
- 'end': '7',
- 'startOffset': 0,
- 'endOffset': 4,
- 'text': 'Message 6\n\nMessage 7\n\nMess',
- });
- }
-
- I.say('Test the overlaps of regions #2');
- AtParagraphs.clickFilter('Author 2', 'Author 3');
- AtLabels.clickLabel('Important Stuff');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('age 3'),
- 4,
- AtParagraphs.locateText('age 8'),
- 3,
- );
- AtSidebar.seeRegions(9);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(omitBy(result[6].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '2',
- 'end': '2',
- 'startOffset': 8,
- 'endOffset': 9,
- 'text': '3',
- });
-
- assert.deepStrictEqual(omitBy(result[7].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '4',
- 'end': '5',
- 'startOffset': 0,
- 'endOffset': 9,
- 'text': 'Message 5\n\nMessage 6',
- });
-
- assert.deepStrictEqual(omitBy(result[8].value, (v, key)=> key === 'paragraphlabels'), {
- 'start': '7',
- 'end': '7',
- 'startOffset': 0,
- 'endOffset': 7,
- 'text': 'Message',
- });
- }
-});
-
-Scenario(
- 'Check start and end indices do not leak to other lines',
- async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
- const dialogue = [
- 1, // 1
- 3, // 2
- 1, // 3
- 2, // 4
- 3, // 5
- 1, // 6
- 2, // 7
- 1, // 8
- 3, // 9
- 1, // 10
- 3, // 11
- 2, // 12
- 3, // 13
- 2, // 14
- ].map((authorId, idx) => ({
- start: idx + 1,
- end: idx + 2,
- author: `Author ${authorId}`,
- text: `Message ${idx + 1}`,
- }));
- const params = {
- config: CONFIG,
- data: {
- audio: AUDIO,
- dialogue,
- },
- };
-
- LabelStudio.setFeatureFlags(FEATURE_FLAGS);
- I.amOnPage('/');
-
- LabelStudio.init(params);
- AtSidebar.seeRegions(0);
-
- I.say(
- 'Test selection from the end of one turn to end of the one below correctly creates a single region with proper start,startOffset,end,endOffset',
- );
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Message 8'),
- 9,
- AtParagraphs.locateText('Message 9'),
- 9,
- );
- AtSidebar.seeRegions(1);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- omitBy(result[0].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '8',
- end: '8',
- startOffset: 0,
- endOffset: 9,
- text: 'Message 9',
- },
- );
- }
-
- I.say(
- 'Test selection from the end of one turn to the very start of another below correctly creates a single region with proper start,startOffset,end,endOffset',
- );
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Message 8'),
- 9,
- AtParagraphs.locateText('Message 10'),
- 0,
- );
- AtSidebar.seeRegions(2);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- omitBy(result[1].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '8',
- end: '8',
- startOffset: 0,
- endOffset: 9,
- text: 'Message 9',
- },
- );
- }
-
- I.say(
- 'Test selection from the end of one turn to end of ones below across collapsed text correctly creates regions with proper start,startOffset,end,endOffset',
- );
- AtParagraphs.clickFilter('Author 2', 'Author 3');
- AtLabels.clickLabel('Important Stuff');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Message 2'),
- 9,
- AtParagraphs.locateText('Message 8'),
- 9,
- );
- AtSidebar.seeRegions(4);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- omitBy(result[2].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '3',
- end: '4',
- startOffset: 0,
- endOffset: 9,
- text: 'Message 4\n\nMessage 5',
- },
- );
- assert.deepStrictEqual(
- omitBy(result[3].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '6',
- end: '6',
- startOffset: 0,
- endOffset: 9,
- text: 'Message 7',
- },
- );
- }
-
- I.say(
- 'Test selection from the end of one turn to very start of ones below across collapsed text correctly creates creates regions with proper start,startOffset,end,endOffset',
- );
- AtLabels.clickLabel('Other');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Message 2'),
- 9,
- AtParagraphs.locateText('Message 8'),
- 0,
- );
- AtSidebar.seeRegions(6);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- omitBy(result[4].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '3',
- end: '4',
- startOffset: 0,
- endOffset: 9,
- text: 'Message 4\n\nMessage 5',
- },
- );
- assert.deepStrictEqual(
- omitBy(result[5].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '6',
- end: '6',
- startOffset: 0,
- endOffset: 9,
- text: 'Message 7',
- },
- );
- }
-
- I.say(
- 'Test selection from the end of Message 11 to the start of Message 14 to get region over Message 12 and Message 13',
- );
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Message 11'),
- 10,
- AtParagraphs.locateText('Message 14'),
- 0,
- );
- AtSidebar.seeRegions(7);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- omitBy(result[6].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '11',
- end: '12',
- startOffset: 0,
- endOffset: 10,
- text: 'Message 12\n\nMessage 13',
- },
- );
- }
- },
-);
-
-Scenario('Selecting the end character on a paragraph phrase to the very start of other phrases includes all selected phrases', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
- const params = {
- data: DATA,
- config: CONFIG,
- };
-
- I.amOnPage('/');
-
- LabelStudio.setFeatureFlags(FEATURE_FLAGS);
- LabelStudio.init(params);
- AtSidebar.seeRegions(0);
-
- I.say('Select 2 regions in the consecutive phrases');
-
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Dont you hate that?'),
- 18,
- AtParagraphs.locateText('Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?'),
- 0,
- );
-
- AtSidebar.seeRegions(1);
-
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- omitBy(result[0].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '0',
- end: '1',
- startOffset: 18,
- endOffset: 10,
- text: '?\n\nHate what?',
- },
- );
-});
-
-Scenario('Selecting the end character on a paragraph phrase to the very start of other phrases includes all selected phrases except the very last one', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
- const params = {
- data: {
- ...DATA,
- dialogue: DATA.dialogue.map(d => [d, { ...d, text: `${d.text}2` }]).flat(),
- },
- config: CONFIG,
- };
-
- I.amOnPage('/');
-
- LabelStudio.setFeatureFlags(FEATURE_FLAGS);
- LabelStudio.init(params);
- AtSidebar.seeRegions(0);
-
-
- I.say('Select 2 regions in the consecutive phrases of the one person');
- AtParagraphs.clickFilter('Vincent Vega');
- AtLabels.clickLabel('Random talk');
- AtParagraphs.setSelection(
- AtParagraphs.locateText('Hate what?2'),
- 10,
- AtParagraphs.locateText('I dont know. Thats a good question.2'),
- 0,
- );
-
- AtSidebar.seeRegions(2);
-
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- omitBy(result[0].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '3',
- end: '3',
- startOffset: 10,
- endOffset: 11,
- text: '2',
- },
- );
- assert.deepStrictEqual(
- omitBy(result[1].value, (v, key) => key === 'paragraphlabels'),
- {
- start: '6',
- end: '6',
- startOffset: 0,
- endOffset: 35,
- text: 'I dont know. Thats a good question.',
- },
- );
-});
-
-Scenario('Initializing a paragraph region range should not include author names in text', async ({ I, LabelStudio, AtSidebar }) => {
- const params = {
- data: DATA,
- annotations: ANNOTATIONS,
- config: CONFIG,
- };
-
- I.amOnPage('/');
- LabelStudio.setFeatureFlags(FEATURE_FLAGS);
-
- const [{ result : [region] }] = ANNOTATIONS;
- const { paragraphlabels: _paragraphlabels, ...value } = region.value;
-
- LabelStudio.init(params);
- AtSidebar.seeRegions(1);
-
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- omitBy(result[0].value, (v, key) => key === 'paragraphlabels'),
- value,
- );
-});
+const assert = require('assert');
+const { omitBy } = require('./helpers');
+
+Feature('Paragraphs filter');
+
+const AUDIO = 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3';
+
+const ANNOTATIONS = [
+ {
+ 'result': [
+ {
+ 'id': 'ryzr4QdL93',
+ 'from_name': 'ner',
+ 'to_name': 'text',
+ 'source': '$dialogue',
+ 'type': 'paragraphlabels',
+ 'value': {
+ 'start': '2',
+ 'end': '4',
+ 'startOffset': 0,
+ 'endOffset': 134,
+ 'paragraphlabels': ['Important Stuff'],
+ 'text': 'Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?I dont know. Thats a good question.Thats when you know you found somebody really special. When you can just shut the door closed a minute, and comfortably share silence.',
+ },
+ },
+ ],
+ },
+];
+
+const DATA = {
+ audio: AUDIO,
+ dialogue: [
+ {
+ start: 3.1,
+ end: 5.6,
+ author: 'Mia Wallace',
+ text: 'Dont you hate that?',
+ },
+ {
+ start: 4.2,
+ duration: 3.1,
+ author: 'Vincent Vega:',
+ text: 'Hate what?',
+ },
+ {
+ author: 'Mia Wallace:',
+ text: 'Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?',
+ },
+ {
+ start: 90,
+ author: 'Vincent Vega:',
+ text: 'I dont know. Thats a good question.',
+ },
+ {
+ author: 'Mia Wallace:',
+ text:
+ 'Thats when you know you found somebody really special. When you can just shut the door closed a minute, and comfortably share silence.',
+ },
+ ],
+};
+
+const CONFIG = `
+
+
+
+
+
+
+
+`;
+
+const FEATURE_FLAGS = {
+ ff_front_dev_2669_paragraph_author_filter_210622_short: true,
+ fflag_fix_front_dev_2918_labeling_filtered_paragraphs_250822_short: true,
+};
+
+Scenario('Create two results using excluding a phrase by the filter', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
+ const params = {
+ data: DATA,
+ config: CONFIG,
+ };
+
+ I.amOnPage('/');
+
+ LabelStudio.setFeatureFlags(FEATURE_FLAGS);
+ LabelStudio.init(params);
+ AtSidebar.seeRegions(0);
+
+ I.say('Select 2 regions in the consecutive phrases of the one person');
+
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Hate what?'),
+ 5,
+ AtParagraphs.locateText('Hate what?'),
+ 10,
+ );
+
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('I dont know. Thats a good question.'),
+ 0,
+ AtParagraphs.locateText('I dont know. Thats a good question.'),
+ 11,
+ );
+ AtSidebar.seeRegions(2);
+
+ I.say('Take a snapshot');
+ const twoActionsResult = LabelStudio.serialize();
+
+ I.say('Reset to initial state');
+ LabelStudio.init(params);
+ AtSidebar.seeRegions(0);
+
+ I.say('Filter the phrases by that person.');
+ AtParagraphs.clickFilter('Vincent Vega:');
+
+ I.say('Try to get the same result in one action');
+
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Hate what?'),
+ 5,
+ AtParagraphs.locateText('I dont know. Thats a good question.'),
+ 11,
+ );
+ AtSidebar.seeRegions(2);
+
+ I.say('Take a second snapshot');
+ const oneActionResult = LabelStudio.serialize();
+
+ I.say('The results should be identical');
+
+ assert.deepStrictEqual(twoActionsResult, oneActionResult);
+
+});
+
+Scenario('Check different cases ', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
+ const dialogue = [
+ 1,// 1
+ 3,// 2
+ 1,// 3
+ 2,// 4
+ 3,// 5
+ 1,// 6
+ 2,// 7
+ 1,// 8
+ 3,// 9
+ 1,// 10
+ ].map((authorId, idx) => ({
+ start: idx + 1,
+ end: idx + 2,
+ author: `Author ${authorId}`,
+ text: `Message ${idx + 1}`,
+ }));
+ const params = {
+ config: CONFIG,
+ data: {
+ audio: AUDIO,
+ dialogue,
+ },
+ };
+
+ I.amOnPage('/');
+
+ LabelStudio.setFeatureFlags(FEATURE_FLAGS);
+ LabelStudio.init(params);
+ AtSidebar.seeRegions(0);
+
+ I.say('Hide Author 3');
+ AtParagraphs.clickFilter('Author 1', 'Author 2');
+
+ I.say('Make regions by selecting everything');
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Message 1'),
+ 0,
+ AtParagraphs.locateText('Message 10'),
+ 10,
+ );
+
+ I.say('There should be 4 new regions');
+ AtSidebar.seeRegions(4);
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.strictEqual(result.length, 4);
+
+ assert.deepStrictEqual(omitBy(result[0].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '0',
+ 'end': '0',
+ 'startOffset': 0,
+ 'endOffset': 9,
+ 'text': 'Message 1',
+ });
+
+ assert.deepStrictEqual(omitBy(result[1].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '2',
+ 'end': '3',
+ 'startOffset': 0,
+ 'endOffset': 9,
+ 'text': 'Message 3\n\nMessage 4',
+ });
+
+ assert.deepStrictEqual(omitBy(result[2].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '5',
+ 'end': '7',
+ 'startOffset': 0,
+ 'endOffset': 9,
+ 'text': 'Message 6\n\nMessage 7\n\nMessage 8',
+ });
+
+ assert.deepStrictEqual(omitBy(result[3].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '9',
+ 'end': '9',
+ 'startOffset': 0,
+ 'endOffset': 10,
+ 'text': 'Message 10',
+ });
+ }
+
+ I.say('Test the overlaps of regions #1');
+ AtLabels.clickLabel('Important Stuff');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Message 3'),
+ 4,
+ AtParagraphs.locateText('Message 8'),
+ 4,
+ );
+ AtSidebar.seeRegions(6);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(omitBy(result[4].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '2',
+ 'end': '3',
+ 'startOffset': 4,
+ 'endOffset': 9,
+ 'text': 'age 3\n\nMessage 4',
+ });
+
+ assert.deepStrictEqual(omitBy(result[5].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '5',
+ 'end': '7',
+ 'startOffset': 0,
+ 'endOffset': 4,
+ 'text': 'Message 6\n\nMessage 7\n\nMess',
+ });
+ }
+
+ I.say('Test the overlaps of regions #2');
+ AtParagraphs.clickFilter('Author 2', 'Author 3');
+ AtLabels.clickLabel('Important Stuff');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('age 3'),
+ 4,
+ AtParagraphs.locateText('age 8'),
+ 3,
+ );
+ AtSidebar.seeRegions(9);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(omitBy(result[6].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '2',
+ 'end': '2',
+ 'startOffset': 8,
+ 'endOffset': 9,
+ 'text': '3',
+ });
+
+ assert.deepStrictEqual(omitBy(result[7].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '4',
+ 'end': '5',
+ 'startOffset': 0,
+ 'endOffset': 9,
+ 'text': 'Message 5\n\nMessage 6',
+ });
+
+ assert.deepStrictEqual(omitBy(result[8].value, (v, key) => key === 'paragraphlabels'), {
+ 'start': '7',
+ 'end': '7',
+ 'startOffset': 0,
+ 'endOffset': 7,
+ 'text': 'Message',
+ });
+ }
+});
+
+Scenario(
+ 'Check start and end indices do not leak to other lines',
+ async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
+ const dialogue = [
+ 1, // 1
+ 3, // 2
+ 1, // 3
+ 2, // 4
+ 3, // 5
+ 1, // 6
+ 2, // 7
+ 1, // 8
+ 3, // 9
+ 1, // 10
+ 3, // 11
+ 2, // 12
+ 3, // 13
+ 2, // 14
+ ].map((authorId, idx) => ({
+ start: idx + 1,
+ end: idx + 2,
+ author: `Author ${authorId}`,
+ text: `Message ${idx + 1}`,
+ }));
+ const params = {
+ config: CONFIG,
+ data: {
+ audio: AUDIO,
+ dialogue,
+ },
+ };
+
+ LabelStudio.setFeatureFlags(FEATURE_FLAGS);
+ I.amOnPage('/');
+
+ LabelStudio.init(params);
+ AtSidebar.seeRegions(0);
+
+ I.say(
+ 'Test selection from the end of one turn to end of the one below correctly creates a single region with proper start,startOffset,end,endOffset',
+ );
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Message 8'),
+ 9,
+ AtParagraphs.locateText('Message 9'),
+ 9,
+ );
+ AtSidebar.seeRegions(1);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ omitBy(result[0].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '8',
+ end: '8',
+ startOffset: 0,
+ endOffset: 9,
+ text: 'Message 9',
+ },
+ );
+ }
+
+ I.say(
+ 'Test selection from the end of one turn to the very start of another below correctly creates a single region with proper start,startOffset,end,endOffset',
+ );
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Message 8'),
+ 9,
+ AtParagraphs.locateText('Message 10'),
+ 0,
+ );
+ AtSidebar.seeRegions(2);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ omitBy(result[1].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '8',
+ end: '8',
+ startOffset: 0,
+ endOffset: 9,
+ text: 'Message 9',
+ },
+ );
+ }
+
+ I.say(
+ 'Test selection from the end of one turn to end of ones below across collapsed text correctly creates regions with proper start,startOffset,end,endOffset',
+ );
+ AtParagraphs.clickFilter('Author 2', 'Author 3');
+ AtLabels.clickLabel('Important Stuff');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Message 2'),
+ 9,
+ AtParagraphs.locateText('Message 8'),
+ 9,
+ );
+ AtSidebar.seeRegions(4);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ omitBy(result[2].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '3',
+ end: '4',
+ startOffset: 0,
+ endOffset: 9,
+ text: 'Message 4\n\nMessage 5',
+ },
+ );
+ assert.deepStrictEqual(
+ omitBy(result[3].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '6',
+ end: '6',
+ startOffset: 0,
+ endOffset: 9,
+ text: 'Message 7',
+ },
+ );
+ }
+
+ I.say(
+ 'Test selection from the end of one turn to very start of ones below across collapsed text correctly creates creates regions with proper start,startOffset,end,endOffset',
+ );
+ AtLabels.clickLabel('Other');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Message 2'),
+ 9,
+ AtParagraphs.locateText('Message 8'),
+ 0,
+ );
+ AtSidebar.seeRegions(6);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ omitBy(result[4].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '3',
+ end: '4',
+ startOffset: 0,
+ endOffset: 9,
+ text: 'Message 4\n\nMessage 5',
+ },
+ );
+ assert.deepStrictEqual(
+ omitBy(result[5].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '6',
+ end: '6',
+ startOffset: 0,
+ endOffset: 9,
+ text: 'Message 7',
+ },
+ );
+ }
+
+ I.say(
+ 'Test selection from the end of Message 11 to the start of Message 14 to get region over Message 12 and Message 13',
+ );
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Message 11'),
+ 10,
+ AtParagraphs.locateText('Message 14'),
+ 0,
+ );
+ AtSidebar.seeRegions(7);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ omitBy(result[6].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '11',
+ end: '12',
+ startOffset: 0,
+ endOffset: 10,
+ text: 'Message 12\n\nMessage 13',
+ },
+ );
+ }
+ },
+);
+
+Scenario('Selecting the end character on a paragraph phrase to the very start of other phrases includes all selected phrases', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
+ const params = {
+ data: DATA,
+ config: CONFIG,
+ };
+
+ I.amOnPage('/');
+
+ LabelStudio.setFeatureFlags(FEATURE_FLAGS);
+ LabelStudio.init(params);
+ AtSidebar.seeRegions(0);
+
+ I.say('Select 2 regions in the consecutive phrases');
+
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Dont you hate that?'),
+ 18,
+ AtParagraphs.locateText('Uncomfortable silences. Why do we feel its necessary to yak about nonsense in order to be comfortable?'),
+ 0,
+ );
+
+ AtSidebar.seeRegions(1);
+
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ omitBy(result[0].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '0',
+ end: '1',
+ startOffset: 18,
+ endOffset: 10,
+ text: '?\n\nHate what?',
+ },
+ );
+});
+
+Scenario('Selecting the end character on a paragraph phrase to the very start of other phrases includes all selected phrases except the very last one', async ({ I, LabelStudio, AtSidebar, AtParagraphs, AtLabels }) => {
+ const params = {
+ data: {
+ ...DATA,
+ dialogue: DATA.dialogue.map(d => [d, { ...d, text: `${d.text}2` }]).flat(),
+ },
+ config: CONFIG,
+ };
+
+ I.amOnPage('/');
+
+ LabelStudio.setFeatureFlags(FEATURE_FLAGS);
+ LabelStudio.init(params);
+ AtSidebar.seeRegions(0);
+
+
+ I.say('Select 2 regions in the consecutive phrases of the one person');
+ AtParagraphs.clickFilter('Vincent Vega');
+ AtLabels.clickLabel('Random talk');
+ AtParagraphs.setSelection(
+ AtParagraphs.locateText('Hate what?2'),
+ 10,
+ AtParagraphs.locateText('I dont know. Thats a good question.2'),
+ 0,
+ );
+
+ AtSidebar.seeRegions(2);
+
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ omitBy(result[0].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '3',
+ end: '3',
+ startOffset: 10,
+ endOffset: 11,
+ text: '2',
+ },
+ );
+ assert.deepStrictEqual(
+ omitBy(result[1].value, (v, key) => key === 'paragraphlabels'),
+ {
+ start: '6',
+ end: '6',
+ startOffset: 0,
+ endOffset: 35,
+ text: 'I dont know. Thats a good question.',
+ },
+ );
+});
+
+Scenario('Initializing a paragraph region range should not include author names in text', async ({ I, LabelStudio, AtSidebar }) => {
+ const params = {
+ data: DATA,
+ annotations: ANNOTATIONS,
+ config: CONFIG,
+ };
+
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags(FEATURE_FLAGS);
+
+ const [{ result: [region] }] = ANNOTATIONS;
+ const { paragraphlabels: _paragraphlabels, ...value } = region.value;
+
+ LabelStudio.init(params);
+ AtSidebar.seeRegions(1);
+
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ omitBy(result[0].value, (v, key) => key === 'paragraphlabels'),
+ value,
+ );
+});
diff --git a/web/libs/editor/tests/e2e/tests/regression-tests/brush-relations.test.js b/web/libs/editor/tests/e2e/tests/regression-tests/brush-relations.test.js
index 451e4534b48d..4b748d9a8172 100644
--- a/web/libs/editor/tests/e2e/tests/regression-tests/brush-relations.test.js
+++ b/web/libs/editor/tests/e2e/tests/regression-tests/brush-relations.test.js
@@ -1,103 +1,103 @@
-Feature('Brush relations').tag('@regress');
-
-const IMAGE = 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg';
-
-const config = `
-
-
-
-
- `;
-
-function getPointAtSpiral(t, v , w) {
- return { x: v * t * Math.cos(w * t), y: v * t * Math.sin(w * t) };
-}
-
-function generateSpiralPoints(x0, y0, R, v , w) {
- let t = 1, x, y;
- const points = [];
-
- do {
- ({ x, y } = getPointAtSpiral(t++, v, w));
- points.push([x + x0, y + y0]);
- } while (x ** 2 + y ** 2 < R ** 2);
- return points;
-}
-
-Scenario('Brush relations shouldn\'t crash everything', async ({ I, LabelStudio, AtImageView, AtSidebar }) => {
- const params = {
- config,
- data: { image: IMAGE },
- };
-
- I.amOnPage('/');
- LabelStudio.init(params);
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
- const regionsCentralPoints = [];
-
- // create 4 brush regions
- for (let i = 0; i < 4; i++) {
- // find start position
- const x = canvasSize.width / 4 * ((i % 2) * 2 + 1);
- const y = canvasSize.height / 4 * ((Math.floor(i / 2) % 2) * 2 + 1);
- // generate points in a spiral
- const points = generateSpiralPoints(x, y, Math.min(canvasSize.width / 6, canvasSize.height / 6), .4, Math.PI / 18);
-
- // select the brush label
- I.pressKey('1');
- // draw a brush region
- AtImageView.drawThroughPoints(points);
- AtSidebar.seeRegions(i+1);
- // unselect the region
- I.pressKey('u');
- // save the central point
- regionsCentralPoints.push({ x, y });
- }
-
- // Check that we can create a relation between the brush regions
- {
- // switch to the move tool for easy region selecting
- I.pressKey('v');
- // select the first region
- AtImageView.clickAt(regionsCentralPoints[0].x, regionsCentralPoints[0].y);
- // create relation to the second region
- I.pressKey(['alt', 'r']);
- AtImageView.clickAt(regionsCentralPoints[1].x, regionsCentralPoints[1].y);
- // check that the relation has been created
- AtSidebar.seeRelations(1);
- }
-
- // Check that relations work fine on a brush restoration (from rle)
- {
- // export annotation
- const annotation = await LabelStudio.serialize();
-
- // reload LS with that datalabel studio logo
- LabelStudio.init({
- ...params,
- annotations: [{ id: 'imported', result: annotation }],
- });
-
- AtImageView.waitForImage();
- // Check that relation still exist
- AtSidebar.seeRelations(1);
-
- // Try to create new relation with restored regions
- {
- // switch to the move tool for easy region selecting
- I.pressKey('v');
- // select the third region
- AtImageView.clickAt(regionsCentralPoints[2].x, regionsCentralPoints[2].y);
- // create relation to the fourth region
- I.pressKey(['alt', 'r']);
- AtImageView.clickAt(regionsCentralPoints[3].x, regionsCentralPoints[3].y);
- // check that the relation has been created
- AtSidebar.seeRelations(2);
- }
-
- /// The potential errors should be caught by `errorsCollector` plugin
- }
-});
+Feature('Brush relations').tag('@regress');
+
+const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg';
+
+const config = `
+
+
+
+
+ `;
+
+function getPointAtSpiral(t, v , w) {
+ return { x: v * t * Math.cos(w * t), y: v * t * Math.sin(w * t) };
+}
+
+function generateSpiralPoints(x0, y0, R, v , w) {
+ let t = 1, x, y;
+ const points = [];
+
+ do {
+ ({ x, y } = getPointAtSpiral(t++, v, w));
+ points.push([x + x0, y + y0]);
+ } while (x ** 2 + y ** 2 < R ** 2);
+ return points;
+}
+
+Scenario('Brush relations shouldn\'t crash everything', async ({ I, LabelStudio, AtImageView, AtSidebar }) => {
+ const params = {
+ config,
+ data: { image: IMAGE },
+ };
+
+ I.amOnPage('/');
+ LabelStudio.init(params);
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+ const regionsCentralPoints = [];
+
+ // create 4 brush regions
+ for (let i = 0; i < 4; i++) {
+ // find start position
+ const x = canvasSize.width / 4 * ((i % 2) * 2 + 1);
+ const y = canvasSize.height / 4 * ((Math.floor(i / 2) % 2) * 2 + 1);
+ // generate points in a spiral
+ const points = generateSpiralPoints(x, y, Math.min(canvasSize.width / 6, canvasSize.height / 6), .4, Math.PI / 18);
+
+ // select the brush label
+ I.pressKey('1');
+ // draw a brush region
+ AtImageView.drawThroughPoints(points);
+ AtSidebar.seeRegions(i + 1);
+ // unselect the region
+ I.pressKey('u');
+ // save the central point
+ regionsCentralPoints.push({ x, y });
+ }
+
+ // Check that we can create a relation between the brush regions
+ {
+ // switch to the move tool for easy region selecting
+ I.pressKey('v');
+ // select the first region
+ AtImageView.clickAt(regionsCentralPoints[0].x, regionsCentralPoints[0].y);
+ // create relation to the second region
+ I.pressKey(['alt', 'r']);
+ AtImageView.clickAt(regionsCentralPoints[1].x, regionsCentralPoints[1].y);
+ // check that the relation has been created
+ AtSidebar.seeRelations(1);
+ }
+
+ // Check that relations work fine on a brush restoration (from rle)
+ {
+ // export annotation
+ const annotation = await LabelStudio.serialize();
+
+ // reload LS with that datalabel studio logo
+ LabelStudio.init({
+ ...params,
+ annotations: [{ id: 'imported', result: annotation }],
+ });
+
+ AtImageView.waitForImage();
+ // Check that relation still exist
+ AtSidebar.seeRelations(1);
+
+ // Try to create new relation with restored regions
+ {
+ // switch to the move tool for easy region selecting
+ I.pressKey('v');
+ // select the third region
+ AtImageView.clickAt(regionsCentralPoints[2].x, regionsCentralPoints[2].y);
+ // create relation to the fourth region
+ I.pressKey(['alt', 'r']);
+ AtImageView.clickAt(regionsCentralPoints[3].x, regionsCentralPoints[3].y);
+ // check that the relation has been created
+ AtSidebar.seeRelations(2);
+ }
+
+ /// The potential errors should be caught by `errorsCollector` plugin
+ }
+});
diff --git a/web/libs/editor/tests/e2e/tests/regression-tests/dynamic-choices.test.js b/web/libs/editor/tests/e2e/tests/regression-tests/dynamic-choices.test.js
index 53e3e9ad88ae..28020b51cecc 100644
--- a/web/libs/editor/tests/e2e/tests/regression-tests/dynamic-choices.test.js
+++ b/web/libs/editor/tests/e2e/tests/regression-tests/dynamic-choices.test.js
@@ -1,80 +1,80 @@
-const assert = require('assert');
-
-Feature('Dynamic choices').tag('@regress');
-
-Scenario('Hotkeys for dynamic choices', async ({ I, LabelStudio })=>{
- const params = {
- config: `
-
-
-
-
-
-`,
- data: {
- text: 'Some text',
- choices: [
- {
- value: 'Header 1',
- children: [
- {
- value: 'Option 1.1',
- },
- {
- value: 'Option 1.2',
- },
- ],
- },
- {
- value: 'Header 2',
- children: [
- {
- value: 'Option 2.1',
- },
- {
- value: 'Option 2.2',
- },
- {
- value: 'Option 2.3',
- hotkey: 'q',
- },
- ],
- },
- ],
- },
- annotations: [{
- id: 'test',
- result: [],
- }],
- };
-
- I.amOnPage('/');
-
- LabelStudio.init(params);
-
- I.see('Header 1');
- I.see('Option 1.1');
- I.see('Header 2');
- I.see('Option 2.2');
-
- I.say('Select some choices by pressing hotkeys');
-
- I.pressKey('1');
- I.pressKey('q');
- I.pressKey('s');
-
- I.say('Check the result');
-
- I.seeElement('.ant-checkbox-checked [name=\'Header 1\']');
- I.seeElement('.ant-checkbox-indeterminate [name=\'Header 2\']');
- I.seeElement('.ant-checkbox-checked [name=\'Option 1.1\']');
- I.seeElement('.ant-checkbox-checked [name=\'Option 1.2\']');
- I.seeElement('.ant-checkbox-checked [name=\'Option 2.3\']');
- I.seeElement('.ant-checkbox-checked [name=\'Static option\']');
-
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(result.length, 1);
- assert.deepStrictEqual(result[0].value.choices, [['Static option'], ['Header 1'], ['Header 1', 'Option 1.1'], ['Header 1', 'Option 1.2'], ['Header 2', 'Option 2.3']]);
-
-});
+const assert = require('assert');
+
+Feature('Dynamic choices').tag('@regress');
+
+Scenario('Hotkeys for dynamic choices', async ({ I, LabelStudio }) => {
+ const params = {
+ config: `
+
+
+
+
+
+`,
+ data: {
+ text: 'Some text',
+ choices: [
+ {
+ value: 'Header 1',
+ children: [
+ {
+ value: 'Option 1.1',
+ },
+ {
+ value: 'Option 1.2',
+ },
+ ],
+ },
+ {
+ value: 'Header 2',
+ children: [
+ {
+ value: 'Option 2.1',
+ },
+ {
+ value: 'Option 2.2',
+ },
+ {
+ value: 'Option 2.3',
+ hotkey: 'q',
+ },
+ ],
+ },
+ ],
+ },
+ annotations: [{
+ id: 'test',
+ result: [],
+ }],
+ };
+
+ I.amOnPage('/');
+
+ LabelStudio.init(params);
+
+ I.see('Header 1');
+ I.see('Option 1.1');
+ I.see('Header 2');
+ I.see('Option 2.2');
+
+ I.say('Select some choices by pressing hotkeys');
+
+ I.pressKey('1');
+ I.pressKey('q');
+ I.pressKey('s');
+
+ I.say('Check the result');
+
+ I.seeElement('.ant-checkbox-checked [name=\'Header 1\']');
+ I.seeElement('.ant-checkbox-indeterminate [name=\'Header 2\']');
+ I.seeElement('.ant-checkbox-checked [name=\'Option 1.1\']');
+ I.seeElement('.ant-checkbox-checked [name=\'Option 1.2\']');
+ I.seeElement('.ant-checkbox-checked [name=\'Option 2.3\']');
+ I.seeElement('.ant-checkbox-checked [name=\'Static option\']');
+
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(result.length, 1);
+ assert.deepStrictEqual(result[0].value.choices, [['Static option'], ['Header 1'], ['Header 1', 'Option 1.1'], ['Header 1', 'Option 1.2'], ['Header 2', 'Option 2.3']]);
+
+});
diff --git a/web/libs/editor/tests/e2e/tests/regression-tests/image-ctrl-drawing.test.js b/web/libs/editor/tests/e2e/tests/regression-tests/image-ctrl-drawing.test.js
index d67580b6e624..01e5fc3f3531 100644
--- a/web/libs/editor/tests/e2e/tests/regression-tests/image-ctrl-drawing.test.js
+++ b/web/libs/editor/tests/e2e/tests/regression-tests/image-ctrl-drawing.test.js
@@ -206,6 +206,9 @@ Scenario('How it works without ctrl', async function({ I, LabelStudio, AtSidebar
for (const regionPair of regionPairs) {
const [outerRegion, innerRegion] = regionPair;
+ // Brush is not relevant in this case anymore (it will not interact with other regions)
+ if (innerRegion.shape === 'Brush') continue;
+
LabelStudio.init(params);
AtImageView.waitForImage();
AtSidebar.seeRegions(0);
diff --git a/web/libs/editor/tests/e2e/tests/regression-tests/image-width.test.js b/web/libs/editor/tests/e2e/tests/regression-tests/image-width.test.js
index c8889c01d393..c19eca425887 100644
--- a/web/libs/editor/tests/e2e/tests/regression-tests/image-width.test.js
+++ b/web/libs/editor/tests/e2e/tests/regression-tests/image-width.test.js
@@ -1,44 +1,44 @@
-const assert = require('assert');
-
-Feature('Image width parameter').tag('@regress');
-
-const IMAGE = 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg';
-
-const config = `
-
-
-
- `;
-
-Scenario('Setting width 50% shouldn\'t break canvas size on resize of working area', async ({ I, LabelStudio, AtImageView, AtSidebar }) => {
- const params = {
- config,
- data: { image: IMAGE },
- };
-
- I.amOnPage('/');
- LabelStudio.init(params);
- AtImageView.waitForImage();
- AtSidebar.seeRegions(0);
- await AtImageView.lookForStage();
- let canvasSize = await AtImageView.getCanvasSize();
- let imageSize = await AtImageView.getImageSize();
-
- // The sizes of the canvas and image element should be equal (or almost equal)
- assert.strictEqual(Math.ceil(imageSize.width), Math.ceil(canvasSize.width));
- assert.strictEqual(Math.ceil(imageSize.height), Math.ceil(canvasSize.height));
-
- // Create full-size region
- // This step is just for visual identification of the bug
- AtImageView.drawByDrag(5, 5, canvasSize.width - 10, canvasSize.height - 10);
-
- I.resizeWindow(800, 900);
- I.resizeWindow(1200, 900);
-
- canvasSize = await AtImageView.getCanvasSize();
- imageSize = await AtImageView.getImageSize();
-
- // The sizes should still be equal (or almost equal)
- assert.strictEqual(Math.ceil(imageSize.width), Math.ceil(canvasSize.width));
- assert.strictEqual(Math.ceil(imageSize.height), Math.ceil(canvasSize.height));
-});
+const assert = require('assert');
+
+Feature('Image width parameter').tag('@regress');
+
+const IMAGE = 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg';
+
+const config = `
+
+
+
+ `;
+
+Scenario('Setting width 50% shouldn\'t break canvas size on resize of working area', async ({ I, LabelStudio, AtImageView, AtSidebar }) => {
+ const params = {
+ config,
+ data: { image: IMAGE },
+ };
+
+ I.amOnPage('/');
+ LabelStudio.init(params);
+ AtImageView.waitForImage();
+ AtSidebar.seeRegions(0);
+ await AtImageView.lookForStage();
+ let canvasSize = await AtImageView.getCanvasSize();
+ let imageSize = await AtImageView.getImageSize();
+
+ // The sizes of the canvas and image element should be equal (or almost equal)
+ assert.strictEqual(Math.ceil(imageSize.width), Math.ceil(canvasSize.width));
+ assert.strictEqual(Math.ceil(imageSize.height), Math.ceil(canvasSize.height));
+
+ // Create full-size region
+ // This step is just for visual identification of the bug
+ AtImageView.drawByDrag(5, 5, canvasSize.width - 10, canvasSize.height - 10);
+
+ I.resizeWindow(800, 900);
+ I.resizeWindow(1200, 900);
+
+ canvasSize = await AtImageView.getCanvasSize();
+ imageSize = await AtImageView.getImageSize();
+
+ // The sizes should still be equal (or almost equal)
+ assert.strictEqual(Math.ceil(imageSize.width), Math.ceil(canvasSize.width));
+ assert.strictEqual(Math.ceil(imageSize.height), Math.ceil(canvasSize.height));
+});
diff --git a/web/libs/editor/tests/e2e/tests/regression-tests/preselected-choices.test.js b/web/libs/editor/tests/e2e/tests/regression-tests/preselected-choices.test.js
index 7a2cfd198726..226f91fdfefc 100644
--- a/web/libs/editor/tests/e2e/tests/regression-tests/preselected-choices.test.js
+++ b/web/libs/editor/tests/e2e/tests/regression-tests/preselected-choices.test.js
@@ -1,91 +1,91 @@
-const assert = require('assert');
-
-Feature('Preselected choices');
-
-Scenario('Make a duplicate of annotation with preselected choices', async ({ I, LabelStudio, AtTopbar })=>{
- const params = {
- config: `
-
-
-
-
-
-
-`,
- data: {
- text: 'Some text',
- },
- annotations: [{
- id: 'test',
- result: [
- {
- from_name: 'choices',
- id: 'mDp7Hpbw6_',
- origin: 'manual',
- to_name: 'text',
- type: 'choices',
- value: {
- choices: ['Option 2'],
- },
- },
- ],
- }],
- };
-
- I.amOnPage('/');
- LabelStudio.init(params);
- // Try to create copy of current annotation
- AtTopbar.click('[aria-label="Copy Annotation"]');
- const duplicateResult = await LabelStudio.serialize();
-
- // Make sure there are no results other than the copied ones
- assert.deepStrictEqual(duplicateResult.length, 1);
- assert.deepStrictEqual(duplicateResult[0].value.choices, ['Option 2']);
-
- // Create new annotation
- I.click('[aria-label="Annotations List Toggle"]');
- I.click('[aria-label="Create Annotation"]');
- const annotationWithPresetValues = await LabelStudio.serialize();
-
- // Check that there is only the result come from selecting by default
- assert.deepStrictEqual(annotationWithPresetValues.length, 1);
- assert.deepStrictEqual(annotationWithPresetValues[0].value.choices, ['Option 1']);
-});
-
-Scenario('Make a duplicate of empty annotation with preselected choices', async ({ I, LabelStudio, AtTopbar })=>{
- const params = {
- config: `
-
-
-
-
-
-
-`,
- data: {
- text: 'Some text',
- },
- annotations: [{
- id: 'test',
- result: [],
- }],
- };
-
- I.amOnPage('/');
- LabelStudio.init(params);
- // Try to create copy of current annotation
- AtTopbar.click('[aria-label="Copy Annotation"]');
- const duplicateResult = await LabelStudio.serialize();
-
- // Make sure there are no preselected results
- assert.deepStrictEqual(duplicateResult.length, 0);
-
- // Create new annotation
- I.click('[aria-label="Annotations List Toggle"]');
- I.click('[aria-label="Create Annotation"]');
- const annotationWithPresetValues = await LabelStudio.serialize();
-
- // Check that there is only the result come from selecting by default
- assert.deepStrictEqual(annotationWithPresetValues.length, 1);
- assert.deepStrictEqual(annotationWithPresetValues[0].value.choices, ['Option 1']);
-});
+const assert = require('assert');
+
+Feature('Preselected choices');
+
+Scenario('Make a duplicate of annotation with preselected choices', async ({ I, LabelStudio, AtTopbar }) => {
+ const params = {
+ config: `
+
+
+
+
+
+
+`,
+ data: {
+ text: 'Some text',
+ },
+ annotations: [{
+ id: 'test',
+ result: [
+ {
+ from_name: 'choices',
+ id: 'mDp7Hpbw6_',
+ origin: 'manual',
+ to_name: 'text',
+ type: 'choices',
+ value: {
+ choices: ['Option 2'],
+ },
+ },
+ ],
+ }],
+ };
+
+ I.amOnPage('/');
+ LabelStudio.init(params);
+ // Try to create copy of current annotation
+ AtTopbar.click('[aria-label="Copy Annotation"]');
+ const duplicateResult = await LabelStudio.serialize();
+
+ // Make sure there are no results other than the copied ones
+ assert.deepStrictEqual(duplicateResult.length, 1);
+ assert.deepStrictEqual(duplicateResult[0].value.choices, ['Option 2']);
+
+ // Create new annotation
+ I.click('[aria-label="Annotations List Toggle"]');
+ I.click('[aria-label="Create Annotation"]');
+ const annotationWithPresetValues = await LabelStudio.serialize();
+
+ // Check that there is only the result come from selecting by default
+ assert.deepStrictEqual(annotationWithPresetValues.length, 1);
+ assert.deepStrictEqual(annotationWithPresetValues[0].value.choices, ['Option 1']);
+});
+
+Scenario('Make a duplicate of empty annotation with preselected choices', async ({ I, LabelStudio, AtTopbar }) => {
+ const params = {
+ config: `
+
+
+
+
+
+
+`,
+ data: {
+ text: 'Some text',
+ },
+ annotations: [{
+ id: 'test',
+ result: [],
+ }],
+ };
+
+ I.amOnPage('/');
+ LabelStudio.init(params);
+ // Try to create copy of current annotation
+ AtTopbar.click('[aria-label="Copy Annotation"]');
+ const duplicateResult = await LabelStudio.serialize();
+
+ // Make sure there are no preselected results
+ assert.deepStrictEqual(duplicateResult.length, 0);
+
+ // Create new annotation
+ I.click('[aria-label="Annotations List Toggle"]');
+ I.click('[aria-label="Create Annotation"]');
+ const annotationWithPresetValues = await LabelStudio.serialize();
+
+ // Check that there is only the result come from selecting by default
+ assert.deepStrictEqual(annotationWithPresetValues.length, 1);
+ assert.deepStrictEqual(annotationWithPresetValues[0].value.choices, ['Option 1']);
+});
diff --git a/web/libs/editor/tests/e2e/tests/regression-tests/richtext.test.js b/web/libs/editor/tests/e2e/tests/regression-tests/richtext.test.js
index 542c8977130a..4b3d71734bc6 100644
--- a/web/libs/editor/tests/e2e/tests/regression-tests/richtext.test.js
+++ b/web/libs/editor/tests/e2e/tests/regression-tests/richtext.test.js
@@ -293,7 +293,7 @@ Scenario('Neighboring nested regions misplacement', async ({ I, LabelStudio, AtS
},
]);
- Data(startBeforeEndParams).Scenario('Start before end problem', async ({ I, LabelStudio, AtSidebar, AtRichText, current }) => {
+ Data(startBeforeEndParams).Scenario('Start before end problem', async ({ I, LabelStudio, AtSidebar, current }) => {
const { tag, content, range } = current;
LabelStudio.setFeatureFlags({
diff --git a/web/libs/editor/tests/e2e/tests/taxonomy.test.js b/web/libs/editor/tests/e2e/tests/taxonomy.test.js
index 2fd8b96297fa..840bba6f9373 100644
--- a/web/libs/editor/tests/e2e/tests/taxonomy.test.js
+++ b/web/libs/editor/tests/e2e/tests/taxonomy.test.js
@@ -1,4 +1,3 @@
-/* global Before */
const assert = require('assert');
Feature('Taxonomy');
diff --git a/web/libs/editor/tests/e2e/tests/text-area.test.js b/web/libs/editor/tests/e2e/tests/text-area.test.js
index 6187dec5631f..6a2dfdea6fb8 100644
--- a/web/libs/editor/tests/e2e/tests/text-area.test.js
+++ b/web/libs/editor/tests/e2e/tests/text-area.test.js
@@ -1,6 +1,3 @@
-
-/* global Feature, Scenario */
-
const assert = require('assert');
const { serialize } = require('./helpers');
diff --git a/web/libs/editor/tests/e2e/tests/textarea.skip-duplicates.test.js b/web/libs/editor/tests/e2e/tests/textarea.skip-duplicates.test.js
index 60069fe9688e..2114eb6d7703 100644
--- a/web/libs/editor/tests/e2e/tests/textarea.skip-duplicates.test.js
+++ b/web/libs/editor/tests/e2e/tests/textarea.skip-duplicates.test.js
@@ -1,528 +1,534 @@
-const assert = require('assert');
-
-Feature('Skip duplicates (textarea)');
-
-const SKIP_DUPLICATES_ERROR = 'There is already an entry with that text. Please enter unique text.';
-
-const scenarioDataTable = new DataTable(['scenarioKey']);
-
-const SK_SIMPLE = 'Simple textarea';
-const SK_PER_REGION = 'Per region';
-const SK_OCR = 'Ocr';
-
-scenarioDataTable.add([SK_SIMPLE]);
-scenarioDataTable.add([SK_PER_REGION]);
-scenarioDataTable.add([SK_OCR]);
-
-const SCENARIO_PARAMS = {
- [SK_SIMPLE]: {
- data: { question: 'Is it a question?' },
- config: `
-
-
-`,
- fieldSelector: '[name="answer"]',
- text: 'Isn\'t it?',
- textAlt: 'isn\'t IT?',
- textOther: 'Maybe',
- },
- [SK_PER_REGION]: {
- data: { 'image': 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Html_headers.png/640px-Html_headers.png' },
- config: `
-
-
-
-
-`,
- annotations: [{
- id: 'test',
- result: [{
- id: 'id_1',
- from_name: 'imageRectangle',
- to_name: 'image',
- type: 'rectangle',
- value: {
- 'x': 0.625,
- 'y': 1.183431952662722,
- 'width': 34.375,
- 'height': 5.719921104536489,
- },
- }],
- }],
- shouldSelectRegion: true,
- fieldSelector: '[name="text"]',
- text: 'The "H1" Header',
- textAlt: 'the "h1" HEADER',
- textOther: 'Wrong text',
- },
- [SK_OCR]: {
- data: { 'image': 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Html_headers.png/640px-Html_headers.png' },
- config: `
-
-
-
-
-`,
- annotations: [{
- id: 'test',
- result: [{
- id: 'id_1',
- from_name: 'imageRectangle',
- to_name: 'image',
- type: 'rectangle',
- value: {
- 'x': 0.625,
- 'y': 1.183431952662722,
- 'width': 34.375,
- 'height': 5.719921104536489,
- },
- }],
- }],
- shouldSelectRegion: true,
- fieldSelector: '.lsf-textarea-tag__form input',
- text: 'The "H1" Header',
- textAlt: 'the "h1" HEADER',
- textOther: 'Wrong text',
- },
-};
-
-Data(scenarioDataTable).Scenario('Skip duplicate values on entering', async ({ I, LabelStudio, AtSidebar, Modals, current }) => {
- const scenarioKey = current.scenarioKey;
- const {
- data,
- config,
- annotations,
- shouldSelectRegion,
- fieldSelector,
- text,
- textAlt,
- textOther,
- } = SCENARIO_PARAMS[scenarioKey];
-
- I.amOnPage('/');
- LabelStudio.init({
- data,
- config,
- annotations,
- });
- LabelStudio.waitForObjectsReady();
-
- if (shouldSelectRegion) AtSidebar.clickRegion(1);
-
- I.fillField(fieldSelector, text);
- I.pressKey('Enter');
-
- I.fillField(fieldSelector, text);
- I.pressKey('Enter');
-
- Modals.seeWarning(SKIP_DUPLICATES_ERROR);
- Modals.closeWarning();
-
- I.fillField(fieldSelector, textAlt);
- I.pressKey('Enter');
-
- Modals.seeWarning(SKIP_DUPLICATES_ERROR);
- Modals.closeWarning();
-
- I.fillField(fieldSelector, textOther);
- I.pressKey('Enter');
-
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- I.fillField(fieldSelector, textOther);
- I.pressKey('Enter');
-
- Modals.seeWarning(SKIP_DUPLICATES_ERROR);
-
- I.fillField(fieldSelector, text);
- I.pressKey('Enter');
-
- Modals.seeWarning(SKIP_DUPLICATES_ERROR);
-});
-
-Scenario('Independent skip duplicate values', async ({ I, LabelStudio, AtSidebar, Modals }) => {
- I.amOnPage('/');
- LabelStudio.init({
- data: { letter: 'Aa' },
- config: `
-
-
-
-
-
-
-
-
-`,
- annotations: [{
- id: 'test',
- result: [
- {
- id: 'letter_A',
- from_name: 'label',
- to_name: 'letter',
- type: 'labels',
- value: { start: 0, end: 1, labels: ['Letter A'], text: 'A' },
- },
- {
- id: 'letter_a',
- from_name: 'label',
- to_name: 'letter',
- type: 'labels',
- value: { start: 1, end: 2, labels: ['Letter A'], text: 'a' },
- },
- ],
- }],
- });
-
- I.fillField('[name="perText"]', 'A');
- I.pressKey('Enter');
-
- I.fillField('[name="perText2"]', 'A');
- I.pressKey('Enter');
-
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- AtSidebar.clickRegion(1);
-
- I.fillField('[name="perRegion"]', 'A');
- I.pressKey('Enter');
-
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- I.fillField(AtSidebar.locateSelectedRegion('.lsf-textarea-tag__form input'), 'A');
- I.pressKey('Enter');
-
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- AtSidebar.clickRegion(2);
-
- I.fillField('[name="perRegion"]', 'A');
- I.pressKey('Enter');
-
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- I.fillField(AtSidebar.locateSelectedRegion('.lsf-textarea-tag__form input'), 'A');
- I.pressKey('Enter');
-
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-});
-
-Scenario('Skip duplicate values on editing', async ({ I, LabelStudio, AtOutliner, Modals }) => {
- I.amOnPage('/');
- LabelStudio.setFeatureFlags({
- ff_front_1170_outliner_030222_short: true,
- });
- LabelStudio.init({
- data: { letter: 'Aa' },
- config: `
-
-
-
-
-
-
-
-`,
- annotations: [{
- id: 'test',
- result: [
- {
- id: 'letter_A',
- from_name: 'label',
- to_name: 'letter',
- type: 'labels',
- value: { start: 0, end: 1, labels: ['Letter A'], text: 'A' },
- },
- {
- id: 'letter_a',
- from_name: 'label',
- to_name: 'letter',
- type: 'labels',
- value: { start: 1, end: 2, labels: ['Letter A'], text: 'a' },
- },
- ],
- }],
- });
- LabelStudio.waitForObjectsReady();
- AtOutliner.seeRegions(2);
-
- I.say('Check perText Textarea regions editing');
- {
- I.say('Create some random values in perText Textarea');
- I.fillField('[name="perText"]', 'A');
- I.pressKey('Enter');
- I.fillField('[name="perText"]', '1');
- I.pressKey('Enter');
- I.fillField('[name="perText"]', 'letter');
- I.pressKey('Enter');
- I.fillField('[name="perText"]', 'last');
- I.pressKey('Enter');
-
- I.say('Try to create duplicate value by editing');
-
- I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(1));
- I.pressKey('Backspace');
- I.pressKey('A');
- I.pressKey('Enter');
-
- Modals.seeWarning(SKIP_DUPLICATES_ERROR);
- Modals.closeWarning();
-
- I.say('Check that these changes were not committed');
-
- I.see('1', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
- I.dontSee('A', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
-
- I.say('Delete second region and check results after that');
- I.click(locate('[aria-label="Delete Region"]'), locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
-
- I.see('A', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=1]')));
- I.see('letter', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
- I.see('last', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=3]')));
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[2].value.text,
- ['A', 'letter', 'last'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['A', 'letter', 'last'])} but ${result[2].value.text} was given.`,
- );
- }
-
- I.say('Check that skip duplication allow us to keep the same value after editing without errors');
-
- I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(1));
- I.pressKey(['CommandOrControl', 'a']);
- I.pressKey('Backspace');
- Array.from('letter').forEach(v => {
- I.pressKey(v);
- });
- I.pressKey('Enter');
-
- I.see('letter', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[2].value.text,
- ['A', 'letter', 'last'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['A', 'letter', 'last'])} but ${result[2].value.text} was given.`,
- );
- }
-
- I.say('Check that skip duplication allow us to set different value by editing and do not get errors');
-
- I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(1));
- I.pressKey(['CommandOrControl', 'a']);
- I.pressKey('Backspace');
- Array.from('other').forEach(v => {
- I.pressKey(v);
- });
- I.pressKey('Enter');
-
- I.see('other', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[2].value.text,
- ['A', 'other', 'last'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['A', 'other', 'last'])} but ${result[2].value.text} was given.`,
- );
- }
- }
-
- I.say('Check perRegion Textarea regions editing');
- {
- AtOutliner.clickRegion(1);
- I.say('Create some random values in perRegion Textarea');
- I.fillField('[name="perRegion"]', 'a');
- I.pressKey('Enter');
- I.fillField('[name="perRegion"]', '1');
- I.pressKey('Enter');
- I.fillField('[name="perRegion"]', 'letter');
- I.pressKey('Enter');
- I.fillField('[name="perRegion"]', 'last');
- I.pressKey('Enter');
-
- I.say('Try to create duplicate value by editing');
-
- I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(2));
- I.pressKey('Backspace');
- I.pressKey('a');
- I.pressKey('Enter');
-
- Modals.seeWarning(SKIP_DUPLICATES_ERROR);
- Modals.closeWarning();
-
- I.say('Check that these changes were not committed');
- I.see('1', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
- I.dontSee('a', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
-
- I.say('Delete second region and check results after that');
- I.click(locate('[aria-label="Delete Region"]'), locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
-
- I.see('a', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=1]')));
- I.see('letter', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
- I.see('last', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=3]')));
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[1].value.text,
- ['a', 'letter', 'last'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['a', 'letter', 'last'])} but ${result[1].value.text} was given.`,
- );
- }
-
- I.say('Check that skip duplication allow us to keep the same value after editing without errors');
-
- I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(2));
- I.pressKey(['CommandOrControl', 'a']);
- I.pressKey('Backspace');
- Array.from('letter').forEach(v => {
- I.pressKey(v);
- });
- I.pressKey('Enter');
-
- I.see('letter', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[1].value.text,
- ['a', 'letter', 'last'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['a', 'letter', 'last'])} but ${result[1].value.text} was given.`,
- );
- }
-
- I.say('Check that skip duplication allow us to set different value by editing and do not get errors');
-
- I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(2));
- I.pressKey(['CommandOrControl', 'a']);
- I.pressKey('Backspace');
- Array.from('other').forEach(v => {
- I.pressKey(v);
- });
- I.pressKey('Enter');
-
- I.see('other', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[1].value.text,
- ['a', 'other', 'last'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['a', 'other', 'last'])} but ${result[1].value.text} was given.`,
- );
- }
- }
-
- I.say('Check ocr-like perRegion Textarea regions editing');
- {
- AtOutliner.clickRegion(2);
-
- I.say('Create some random values in ocr-like perRegion Textarea');
- I.fillField(AtOutliner.locateSelectedItem('.lsf-textarea-tag__form input'), 'One');
- I.pressKey('Enter');
- I.fillField(AtOutliner.locateSelectedItem('.lsf-textarea-tag__form input'), 'Two');
- I.pressKey('Enter');
- I.fillField(AtOutliner.locateSelectedItem('.lsf-textarea-tag__form input'), 'Three');
- I.pressKey('Enter');
- I.fillField(AtOutliner.locateSelectedItem('.lsf-textarea-tag__form input'), 'Four');
- I.pressKey('Enter');
-
- I.say('Try to create duplicate value by editing');
-
- I.click(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')));
- I.pressKey(['CommandOrControl', 'a']);
- I.pressKey('Backspace');
- I.pressKey('O');
- I.pressKey('n');
- I.pressKey('e');
- I.pressKey('Enter');
-
- Modals.seeWarning(SKIP_DUPLICATES_ERROR);
- Modals.closeWarning();
-
- I.say('Check that these changes were not committed');
- I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'Two');
- I.dontSeeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'One');
-
- I.say('Delete second region and check results after that');
- I.click(locate('[aria-label="Delete Region"]'), AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2)')));
-
- I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(1) input')), 'One');
- I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'Three');
- I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(3) input')), 'Four');
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[3].value.text,
- ['One', 'Three', 'Four'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['One', 'Three', 'Four'])} but ${result[3].value.text} was given.`,
- );
- }
-
- I.say('Check that skip duplication allow us to keep the same value after editing without errors');
-
- I.click(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')));
- I.pressKey(['CommandOrControl', 'a']);
- I.pressKey('Backspace');
- Array.from('Three').forEach(v => {
- I.pressKey(v);
- });
- I.pressKey('Enter');
-
- I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'Three');
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[3].value.text,
- ['One', 'Three', 'Four'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['One', 'Three', 'Four'])} but ${result[3].value.text} was given.`,
- );
- }
-
- I.say('Check that skip duplication allow us to set different value by editing and do not get errors');
-
- I.click(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')));
- I.pressKey(['CommandOrControl', 'a']);
- I.pressKey('Backspace');
- Array.from('other').forEach(v => {
- I.pressKey(v);
- });
- I.pressKey('Enter');
-
- I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'other');
- Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
-
- {
- const result = await LabelStudio.serialize();
-
- assert.deepStrictEqual(
- result[3].value.text,
- ['One', 'other', 'Four'],
- `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['One', 'other', 'Four'])} but ${result[3].value.text} was given.`,
- );
- }
- }
-});
+const assert = require('assert');
+
+Feature('Skip duplicates (textarea)');
+
+const SKIP_DUPLICATES_ERROR = 'There is already an entry with that text. Please enter unique text.';
+
+const scenarioDataTable = new DataTable(['scenarioKey']);
+
+const SK_SIMPLE = 'Simple textarea';
+const SK_PER_REGION = 'Per region';
+const SK_OCR = 'Ocr';
+
+scenarioDataTable.add([SK_SIMPLE]);
+scenarioDataTable.add([SK_PER_REGION]);
+scenarioDataTable.add([SK_OCR]);
+
+const SCENARIO_PARAMS = {
+ [SK_SIMPLE]: {
+ data: { question: 'Is it a question?' },
+ config: `
+
+
+`,
+ fieldSelector: '[name="answer"]',
+ text: 'Isn\'t it?',
+ textAlt: 'isn\'t IT?',
+ textOther: 'Maybe',
+ },
+ [SK_PER_REGION]: {
+ data: { 'image': 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Html_headers.png/640px-Html_headers.png' },
+ config: `
+
+
+
+
+`,
+ annotations: [{
+ id: 'test',
+ result: [{
+ id: 'id_1',
+ from_name: 'imageRectangle',
+ to_name: 'image',
+ type: 'rectangle',
+ value: {
+ 'x': 0.625,
+ 'y': 1.183431952662722,
+ 'width': 34.375,
+ 'height': 5.719921104536489,
+ },
+ }],
+ }],
+ shouldSelectRegion: true,
+ fieldSelector: '[name="text"]',
+ text: 'The "H1" Header',
+ textAlt: 'the "h1" HEADER',
+ textOther: 'Wrong text',
+ },
+ [SK_OCR]: {
+ data: { 'image': 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Html_headers.png/640px-Html_headers.png' },
+ config: `
+
+
+
+
+`,
+ annotations: [{
+ id: 'test',
+ result: [{
+ id: 'id_1',
+ from_name: 'imageRectangle',
+ to_name: 'image',
+ type: 'rectangle',
+ value: {
+ 'x': 0.625,
+ 'y': 1.183431952662722,
+ 'width': 34.375,
+ 'height': 5.719921104536489,
+ },
+ }],
+ }],
+ shouldSelectRegion: true,
+ fieldSelector: '.lsf-textarea-tag__form input',
+ text: 'The "H1" Header',
+ textAlt: 'the "h1" HEADER',
+ textOther: 'Wrong text',
+ },
+};
+
+Data(scenarioDataTable).Scenario('Skip duplicate values on entering', async ({ I, LabelStudio, AtSidebar, Modals, current }) => {
+ const scenarioKey = current.scenarioKey;
+ const {
+ data,
+ config,
+ annotations,
+ shouldSelectRegion,
+ fieldSelector,
+ text,
+ textAlt,
+ textOther,
+ } = SCENARIO_PARAMS[scenarioKey];
+
+ I.amOnPage('/');
+ LabelStudio.init({
+ data,
+ config,
+ annotations,
+ });
+ LabelStudio.waitForObjectsReady();
+
+ if (shouldSelectRegion) AtSidebar.clickRegion(1);
+
+ I.fillField(fieldSelector, text);
+ I.pressKey('Enter');
+
+ I.fillField(fieldSelector, text);
+ I.pressKey('Enter');
+
+ Modals.seeWarning(SKIP_DUPLICATES_ERROR);
+ Modals.closeWarning();
+
+ I.fillField(fieldSelector, textAlt);
+ I.pressKey('Enter');
+
+ Modals.seeWarning(SKIP_DUPLICATES_ERROR);
+ Modals.closeWarning();
+
+ I.fillField(fieldSelector, textOther);
+ I.pressKey('Enter');
+
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ I.fillField(fieldSelector, textOther);
+ I.pressKey('Enter');
+
+ Modals.seeWarning(SKIP_DUPLICATES_ERROR);
+ Modals.closeWarning();
+
+ I.fillField(fieldSelector, text);
+ I.pressKey('Enter');
+
+ Modals.seeWarning(SKIP_DUPLICATES_ERROR);
+ Modals.closeWarning();
+
+ // check that there are no warnings and errors
+ I.updateAnnotation();
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+});
+
+Scenario('Independent skip duplicate values', async ({ I, LabelStudio, AtSidebar, Modals }) => {
+ I.amOnPage('/');
+ LabelStudio.init({
+ data: { letter: 'Aa' },
+ config: `
+
+
+
+
+
+
+
+
+`,
+ annotations: [{
+ id: 'test',
+ result: [
+ {
+ id: 'letter_A',
+ from_name: 'label',
+ to_name: 'letter',
+ type: 'labels',
+ value: { start: 0, end: 1, labels: ['Letter A'], text: 'A' },
+ },
+ {
+ id: 'letter_a',
+ from_name: 'label',
+ to_name: 'letter',
+ type: 'labels',
+ value: { start: 1, end: 2, labels: ['Letter A'], text: 'a' },
+ },
+ ],
+ }],
+ });
+
+ I.fillField('[name="perText"]', 'A');
+ I.pressKey('Enter');
+
+ I.fillField('[name="perText2"]', 'A');
+ I.pressKey('Enter');
+
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ AtSidebar.clickRegion(1);
+
+ I.fillField('[name="perRegion"]', 'A');
+ I.pressKey('Enter');
+
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ I.fillField(AtSidebar.locateSelectedRegion('.lsf-textarea-tag__form input'), 'A');
+ I.pressKey('Enter');
+
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ AtSidebar.clickRegion(2);
+
+ I.fillField('[name="perRegion"]', 'A');
+ I.pressKey('Enter');
+
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ I.fillField(AtSidebar.locateSelectedRegion('.lsf-textarea-tag__form input'), 'A');
+ I.pressKey('Enter');
+
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+});
+
+Scenario('Skip duplicate values on editing', async ({ I, LabelStudio, AtOutliner, Modals }) => {
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags({
+ ff_front_1170_outliner_030222_short: true,
+ });
+ LabelStudio.init({
+ data: { letter: 'Aa' },
+ config: `
+
+
+
+
+
+
+
+`,
+ annotations: [{
+ id: 'test',
+ result: [
+ {
+ id: 'letter_A',
+ from_name: 'label',
+ to_name: 'letter',
+ type: 'labels',
+ value: { start: 0, end: 1, labels: ['Letter A'], text: 'A' },
+ },
+ {
+ id: 'letter_a',
+ from_name: 'label',
+ to_name: 'letter',
+ type: 'labels',
+ value: { start: 1, end: 2, labels: ['Letter A'], text: 'a' },
+ },
+ ],
+ }],
+ });
+ LabelStudio.waitForObjectsReady();
+ AtOutliner.seeRegions(2);
+
+ I.say('Check perText Textarea regions editing');
+ {
+ I.say('Create some random values in perText Textarea');
+ I.fillField('[name="perText"]', 'A');
+ I.pressKey('Enter');
+ I.fillField('[name="perText"]', '1');
+ I.pressKey('Enter');
+ I.fillField('[name="perText"]', 'letter');
+ I.pressKey('Enter');
+ I.fillField('[name="perText"]', 'last');
+ I.pressKey('Enter');
+
+ I.say('Try to create duplicate value by editing');
+
+ I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(1));
+ I.pressKey('Backspace');
+ I.pressKey('A');
+ I.pressKey('Enter');
+
+ Modals.seeWarning(SKIP_DUPLICATES_ERROR);
+ Modals.closeWarning();
+
+ I.say('Check that these changes were not committed');
+
+ I.see('1', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+ I.dontSee('A', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+
+ I.say('Delete second region and check results after that');
+ I.click(locate('[aria-label="Delete Region"]'), locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+
+ I.see('A', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=1]')));
+ I.see('letter', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+ I.see('last', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=3]')));
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[2].value.text,
+ ['A', 'letter', 'last'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['A', 'letter', 'last'])} but ${result[2].value.text} was given.`,
+ );
+ }
+
+ I.say('Check that skip duplication allow us to keep the same value after editing without errors');
+
+ I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(1));
+ I.pressKey(['CommandOrControl', 'a']);
+ I.pressKey('Backspace');
+ Array.from('letter').forEach(v => {
+ I.pressKey(v);
+ });
+ I.pressKey('Enter');
+
+ I.see('letter', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[2].value.text,
+ ['A', 'letter', 'last'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['A', 'letter', 'last'])} but ${result[2].value.text} was given.`,
+ );
+ }
+
+ I.say('Check that skip duplication allow us to set different value by editing and do not get errors');
+
+ I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(1));
+ I.pressKey(['CommandOrControl', 'a']);
+ I.pressKey('Backspace');
+ Array.from('other').forEach(v => {
+ I.pressKey(v);
+ });
+ I.pressKey('Enter');
+
+ I.see('other', locate('.lsf-text-area').at(1).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[2].value.text,
+ ['A', 'other', 'last'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['A', 'other', 'last'])} but ${result[2].value.text} was given.`,
+ );
+ }
+ }
+
+ I.say('Check perRegion Textarea regions editing');
+ {
+ AtOutliner.clickRegion(1);
+ I.say('Create some random values in perRegion Textarea');
+ I.fillField('[name="perRegion"]', 'a');
+ I.pressKey('Enter');
+ I.fillField('[name="perRegion"]', '1');
+ I.pressKey('Enter');
+ I.fillField('[name="perRegion"]', 'letter');
+ I.pressKey('Enter');
+ I.fillField('[name="perRegion"]', 'last');
+ I.pressKey('Enter');
+
+ I.say('Try to create duplicate value by editing');
+
+ I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(2));
+ I.pressKey('Backspace');
+ I.pressKey('a');
+ I.pressKey('Enter');
+
+ Modals.seeWarning(SKIP_DUPLICATES_ERROR);
+ Modals.closeWarning();
+
+ I.say('Check that these changes were not committed');
+ I.see('1', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+ I.dontSee('a', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+
+ I.say('Delete second region and check results after that');
+ I.click(locate('[aria-label="Delete Region"]'), locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+
+ I.see('a', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=1]')));
+ I.see('letter', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+ I.see('last', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=3]')));
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[1].value.text,
+ ['a', 'letter', 'last'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['a', 'letter', 'last'])} but ${result[1].value.text} was given.`,
+ );
+ }
+
+ I.say('Check that skip duplication allow us to keep the same value after editing without errors');
+
+ I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(2));
+ I.pressKey(['CommandOrControl', 'a']);
+ I.pressKey('Backspace');
+ Array.from('letter').forEach(v => {
+ I.pressKey(v);
+ });
+ I.pressKey('Enter');
+
+ I.see('letter', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[1].value.text,
+ ['a', 'letter', 'last'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['a', 'letter', 'last'])} but ${result[1].value.text} was given.`,
+ );
+ }
+
+ I.say('Check that skip duplication allow us to set different value by editing and do not get errors');
+
+ I.click(locate('[aria-label="Edit Region"]').inside('[data-testid="textarea-region"]').at(2), locate('.lsf-text-area').at(2));
+ I.pressKey(['CommandOrControl', 'a']);
+ I.pressKey('Backspace');
+ Array.from('other').forEach(v => {
+ I.pressKey(v);
+ });
+ I.pressKey('Enter');
+
+ I.see('other', locate('.lsf-text-area').at(2).find(locate('.//*[./@data-testid = \'textarea-region\'][position()=2]')));
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[1].value.text,
+ ['a', 'other', 'last'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['a', 'other', 'last'])} but ${result[1].value.text} was given.`,
+ );
+ }
+ }
+
+ I.say('Check ocr-like perRegion Textarea regions editing');
+ {
+ AtOutliner.clickRegion(2);
+
+ I.say('Create some random values in ocr-like perRegion Textarea');
+ I.fillField(AtOutliner.locateSelectedItem('.lsf-textarea-tag__form input'), 'One');
+ I.pressKey('Enter');
+ I.fillField(AtOutliner.locateSelectedItem('.lsf-textarea-tag__form input'), 'Two');
+ I.pressKey('Enter');
+ I.fillField(AtOutliner.locateSelectedItem('.lsf-textarea-tag__form input'), 'Three');
+ I.pressKey('Enter');
+ I.fillField(AtOutliner.locateSelectedItem('.lsf-textarea-tag__form input'), 'Four');
+ I.pressKey('Enter');
+
+ I.say('Try to create duplicate value by editing');
+
+ I.click(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')));
+ I.pressKey(['CommandOrControl', 'a']);
+ I.pressKey('Backspace');
+ I.pressKey('O');
+ I.pressKey('n');
+ I.pressKey('e');
+ I.pressKey('Enter');
+
+ Modals.seeWarning(SKIP_DUPLICATES_ERROR);
+ Modals.closeWarning();
+
+ I.say('Check that these changes were not committed');
+ I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'Two');
+ I.dontSeeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'One');
+
+ I.say('Delete second region and check results after that');
+ I.click(locate('[aria-label="Delete Region"]'), AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2)')));
+
+ I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(1) input')), 'One');
+ I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'Three');
+ I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(3) input')), 'Four');
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[3].value.text,
+ ['One', 'Three', 'Four'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['One', 'Three', 'Four'])} but ${result[3].value.text} was given.`,
+ );
+ }
+
+ I.say('Check that skip duplication allow us to keep the same value after editing without errors');
+
+ I.click(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')));
+ I.pressKey(['CommandOrControl', 'a']);
+ I.pressKey('Backspace');
+ Array.from('Three').forEach(v => {
+ I.pressKey(v);
+ });
+ I.pressKey('Enter');
+
+ I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'Three');
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[3].value.text,
+ ['One', 'Three', 'Four'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['One', 'Three', 'Four'])} but ${result[3].value.text} was given.`,
+ );
+ }
+
+ I.say('Check that skip duplication allow us to set different value by editing and do not get errors');
+
+ I.click(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')));
+ I.pressKey(['CommandOrControl', 'a']);
+ I.pressKey('Backspace');
+ Array.from('other').forEach(v => {
+ I.pressKey(v);
+ });
+ I.pressKey('Enter');
+
+ I.seeInField(AtOutliner.locateSelectedItem(locate('.lsf-textarea-tag__item:nth-child(2) input')), 'other');
+ Modals.dontSeeWarning(SKIP_DUPLICATES_ERROR);
+
+ {
+ const result = await LabelStudio.serialize();
+
+ assert.deepStrictEqual(
+ result[3].value.text,
+ ['One', 'other', 'Four'],
+ `There should be 3 specific text lines in the textarea result: ${JSON.stringify(['One', 'other', 'Four'])} but ${result[3].value.text} was given.`,
+ );
+ }
+ }
+});
diff --git a/web/libs/editor/tests/e2e/tests/timeseries.test.js b/web/libs/editor/tests/e2e/tests/timeseries.test.js
index 836c894e0a32..a7ecfec8a49c 100644
--- a/web/libs/editor/tests/e2e/tests/timeseries.test.js
+++ b/web/libs/editor/tests/e2e/tests/timeseries.test.js
@@ -285,7 +285,7 @@ Scenario('TimeSeries with optimized data', async ({ I, LabelStudio, AtTimeSeries
I.say(`I see ${timestamp}`);
assert(timestamp === lastTimestamp || timestamp - lastTimestamp === 1,
- `Timestamps should not be skipped. Got ${lastTimestamp} and ${timestamp} but ${timestamp - 1} is missed`);
+ `Timestamps should not be skipped. Got ${lastTimestamp} and ${timestamp} but ${timestamp - 1} is missed`);
}
lastTimestamp = timestamp;
}
diff --git a/web/libs/editor/tests/e2e/tests/toggle-visibility.test.js b/web/libs/editor/tests/e2e/tests/toggle-visibility.test.js
index 78731b1ee4fd..4f96d81744b9 100644
--- a/web/libs/editor/tests/e2e/tests/toggle-visibility.test.js
+++ b/web/libs/editor/tests/e2e/tests/toggle-visibility.test.js
@@ -207,7 +207,7 @@ examples.forEach(example => {
examplesTable.add([title, config, data, result]);
});
-Data(examplesTable).Scenario('Check visibility switcher through all examples', ({ I, AtSidebar, current })=> {
+Data(examplesTable).Scenario('Check visibility switcher through all examples', ({ I, AtSidebar, current }) => {
const { config, data, result } = current;
const params = { annotations: [{ id: 'test', result }], config, data };
diff --git a/web/libs/editor/tests/e2e/tests/unfinished-polygons.test.js b/web/libs/editor/tests/e2e/tests/unfinished-polygons.test.js
index 8c61bf9a84fa..e9d29783cefa 100644
--- a/web/libs/editor/tests/e2e/tests/unfinished-polygons.test.js
+++ b/web/libs/editor/tests/e2e/tests/unfinished-polygons.test.js
@@ -1,606 +1,606 @@
-const { saveDraftLocally, getLocallySavedDraft } = require('./helpers');
-const assert = require('assert');
-
-
-Feature('Unfinished polygons');
-
-const IMAGE =
- 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg';
-
-const CONFIG = `
-
-
-
-
-
-
-
-`;
-
-const CONFIG_MULTIPLE = `
-
-
-
-
-
-
-
-
-`;
-
-const FLAGS = {
- ff_feat_front_DEV_2576_undo_key_points_polygon_short: true,
- ff_front_dev_2431_delete_polygon_points_080622_short: true,
- ff_front_dev_2432_auto_save_polygon_draft_210622_short: true,
-};
-
-Scenario('Drafts for unfinished polygons', async function({ I, LabelStudio, AtLabels, AtImageView }) {
- I.amOnPage('/');
- LabelStudio.init({
- config: CONFIG,
- data: {
- image: IMAGE,
- },
- params: {
- onSubmitDraft: saveDraftLocally,
- },
- });
- LabelStudio.setFeatureFlags(FLAGS);
-
- AtImageView.waitForImage();
-
- await AtImageView.lookForStage();
-
- I.say('start drawing polygon without finishing it');
- AtLabels.clickLabel('Hello');
- AtImageView.drawByClickingPoints([[50,50], [100, 50], [100, 80]]);
-
- I.say('wait until autosave');
- I.waitForFunction(()=>!!window.LSDraft, .5);
- I.say('check result');
- const draft = await I.executeScript(getLocallySavedDraft);
-
- assert.strictEqual(draft[0].value.points.length, 3);
- assert.strictEqual(draft[0].value.closed, false);
-});
-
-Scenario('Saving polygon drawing steps to history', async function({ I, LabelStudio, AtLabels, AtImageView }) {
- I.amOnPage('/');
- LabelStudio.setFeatureFlags(FLAGS);
- LabelStudio.init({
- config: CONFIG,
- data: {
- image: IMAGE,
- },
- });
-
- AtImageView.waitForImage();
-
- await AtImageView.lookForStage();
-
- I.say('put one point of polygon');
- AtLabels.clickLabel('Hello');
- AtImageView.drawByClick(50, 50);
-
- I.say('check current history size');
- let historyStepsCount = await I.executeScript(()=>window.Htx.annotationStore.selected.history.history.length);
-
- assert.strictEqual(historyStepsCount, 2);
-
- I.say('try to draw some more points and close polygon');
- AtImageView.drawByClick(100, 50);
- AtImageView.drawByClick(125, 100);
- AtImageView.drawByClick(50, 50);
-
- I.say('check current history size and result');
- historyStepsCount = await I.executeScript(()=>window.Htx.annotationStore.selected.history.history.length);
- assert.strictEqual(historyStepsCount, 5);
- let result = await LabelStudio.serialize();
-
- assert.strictEqual(result[0].value.points.length, 3);
- assert.strictEqual(result[0].value.closed, true);
-
- I.say('try to undo closing and 2 last points');
- I.click('button[aria-label=Undo]');
- I.click('button[aria-label=Undo]');
- I.click('button[aria-label=Undo]');
- I.say('check current history index and result');
- historyStepsCount = await I.executeScript(()=>window.Htx.annotationStore.selected.history.undoIdx);
- assert.strictEqual(historyStepsCount, 1);
- result = await LabelStudio.serialize();
- assert.strictEqual(result[0].value.points.length, 1);
- assert.strictEqual(result[0].value.closed, false);
-
-});
-
-Scenario('Init an annotation with old format of closed polygon result', async function({ I, LabelStudio, AtImageView }) {
- I.amOnPage('/');
- LabelStudio.setFeatureFlags(FLAGS);
- LabelStudio.init({
- config: CONFIG,
- data: {
- image: IMAGE,
- },
- annotations: [
- {
- id: 'test',
- result: [
- {
- 'original_width': 2242,
- 'original_height': 2802,
- 'image_rotation': 0,
- 'value': {
- 'points': [
- [
- 22.38442822384428,
- 27.042801556420233,
- ],
- [
- 77.61557177615572,
- 24.90272373540856,
- ],
- [
- 48.90510948905109,
- 76.07003891050584,
- ],
- ],
- 'polygonlabels': [
- 'Hello',
- ],
- },
- 'id': 'tNe7Bjmydb',
- 'from_name': 'tag',
- 'to_name': 'img',
- 'type': 'polygonlabels',
- 'origin': 'manual',
- },
- ],
- },
- ],
- });
-
- AtImageView.waitForImage();
-
- const result = await LabelStudio.serialize();
-
- assert.strictEqual(result[0].value.points.length, 3);
- assert.strictEqual(result[0].value.closed, true);
-});
-
-Scenario('Init an annotation with result of new format of polygon results', async function({ I, LabelStudio, AtImageView }) {
- I.amOnPage('/');
- LabelStudio.setFeatureFlags(FLAGS);
- LabelStudio.init({
- config: CONFIG,
- data: {
- image: IMAGE,
- },
- annotations: [
- {
- id: 'test',
- result: [
- {
- 'original_width': 2242,
- 'original_height': 2802,
- 'image_rotation': 0,
- 'value': {
- 'points': [
- [
- 40,
- 40,
- ],
- [
- 50,
- 40,
- ],
- [
- 50,
- 50,
- ],
- [
- 40,
- 50,
- ],
- ],
- 'closed': true,
- 'polygonlabels': [
- 'World',
- ],
- },
- 'id': 'tNe7Bjmydb_2',
- 'from_name': 'tag',
- 'to_name': 'img',
- 'type': 'polygonlabels',
- 'origin': 'manual',
- },
- {
- 'original_width': 2242,
- 'original_height': 2802,
- 'image_rotation': 0,
- 'value': {
- 'points': [
- [
- 10,
- 10,
- ],
- [
- 30,
- 10,
- ],
- [
- 20,
- 20,
- ],
- ],
- 'closed': false,
- 'polygonlabels': [
- 'Hello',
- ],
- },
- 'id': 'tNe7Bjmydb',
- 'from_name': 'tag',
- 'to_name': 'img',
- 'type': 'polygonlabels',
- 'origin': 'manual',
- },
- ],
- },
- ],
- });
-
- AtImageView.waitForImage();
-
- I.say('check loaded regions');
- let result = await LabelStudio.serialize();
-
- assert.strictEqual(result.length, 2);
- assert.strictEqual(result[0].value.points.length, 4);
- assert.strictEqual(result[0].value.closed, true);
- assert.strictEqual(result[1].value.points.length, 3);
- assert.strictEqual(result[1].value.closed, false);
-
- I.say('try to continue drawing loaded unfinished region');
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .40 );
- result = await LabelStudio.serialize();
- assert.strictEqual(result[1].value.points.length, 4);
- assert.strictEqual(result[1].value.closed, false);
-
- I.say('try to close loaded region');
- AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
- result = await LabelStudio.serialize();
- assert.strictEqual(result[1].value.points.length, 4);
- assert.strictEqual(result[1].value.closed, true);
-
- // I.say("check that it is possible to go back throught history");
- // I.pressKey(['CommandOrControl', 'Z']);
- // result = await LabelStudio.serialize();
- // assert.strictEqual(result[1].value.points.length, 4);
- // assert.strictEqual(result[1].value.closed, false);
- //
- // I.say("check that it is possible to close this region again");
- // AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
- // result = await LabelStudio.serialize();
- // assert.strictEqual(result[1].value.points.length, 4);
- // assert.strictEqual(result[1].value.closed, true);
-
-});
-
-Scenario('Removing a polygon by going back through history', async function({ I, LabelStudio, AtLabels, AtImageView }) {
- I.amOnPage('/');
- LabelStudio.setFeatureFlags(FLAGS);
- LabelStudio.init({
- config: CONFIG,
- data: {
- image: IMAGE,
- },
- params: {
- onSubmitDraft: saveDraftLocally,
- },
- });
-
- AtImageView.waitForImage();
-
- await AtImageView.lookForStage();
-
- I.say('start drawing polygon');
- AtLabels.clickLabel('Hello');
- AtImageView.drawByClickingPoints([[50,50], [100, 50]]);
-
- I.say('revert all changes and creating of the region');
- I.pressKey(['CommandOrControl', 'Z']);
- I.pressKey(['CommandOrControl', 'Z']);
-
- I.say('polygon should disappear and polygon tool should be switched of');
- let result = await LabelStudio.serialize();
-
- assert.strictEqual(result.length, 0);
-
- I.say('try to draw after that');
- AtImageView.drawByClickingPoints([[50,50], [100, 50]]);
-
- I.say('check if it was possible to do this (it shouldn\'t)');
- result = await LabelStudio.serialize();
- assert.strictEqual(result.length, 0);
-
- I.say('check if there were any errors');
- // The potential errors should be caught by `errorsCollector` plugin
-});
-
-Scenario('Continue annotating after closing region from draft', async function({ I, LabelStudio, AtLabels, AtImageView }) {
- I.amOnPage('/');
- LabelStudio.setFeatureFlags(FLAGS);
- LabelStudio.init({
- config: CONFIG,
- data: {
- image: IMAGE,
- },
- annotations: [
- {
- id: 'test',
- result: [
- {
- 'original_width': 2242,
- 'original_height': 2802,
- 'image_rotation': 0,
- 'value': {
- 'points': [
- [
- 10,
- 10,
- ],
- [
- 30,
- 10,
- ],
- [
- 20,
- 20,
- ],
- ],
- 'closed': false,
- 'polygonlabels': [
- 'Hello',
- ],
- },
- 'id': 'tNe7Bjmydb',
- 'from_name': 'tag',
- 'to_name': 'img',
- 'type': 'polygonlabels',
- 'origin': 'manual',
- },
- ],
- },
- ],
- });
-
- AtImageView.waitForImage();
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- I.say('close loaded region');
- AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
-
- I.say('try to create another region');
- AtLabels.clickLabel('World');
-
- AtImageView.drawByClickingPoints([
- [canvasSize.width * .40, canvasSize.height * .40],
- [canvasSize.width * .50, canvasSize.height * .40],
- [canvasSize.width * .50, canvasSize.height * .50],
- [canvasSize.width * .40, canvasSize.height * .50],
- [canvasSize.width * .40, canvasSize.height * .40],
- ]);
-
- const result = await LabelStudio.serialize();
-
- assert.strictEqual(result.length, 2);
- assert.strictEqual(result[1].value.points.length, 4);
- assert.strictEqual(result[1].value.closed, true);
-
-});
-
-Scenario('Change label on unfinished polygons', async function({ I, LabelStudio, AtLabels, AtImageView }) {
- I.amOnPage('/');
- LabelStudio.init({
- config: CONFIG,
- data: {
- image: IMAGE,
- },
- params: {
- onSubmitDraft: saveDraftLocally,
- },
- });
- LabelStudio.setFeatureFlags(FLAGS);
-
- AtImageView.waitForImage();
-
- await AtImageView.lookForStage();
-
- I.say('start drawing polygon without finishing it');
- AtLabels.clickLabel('Hello');
- AtImageView.drawByClickingPoints([[50,50], [100, 50], [100, 80]]);
- AtLabels.clickLabel('World');
-
- I.say('wait until autosave');
- I.waitForFunction(()=>!!window.LSDraft, .5);
- I.say('check result');
- const draft = await I.executeScript(getLocallySavedDraft);
-
- assert.strictEqual(draft[0].value.polygonlabels[0], 'World');
-});
-
-
-const selectedLabelsVariants = new DataTable(['labels']);
-
-selectedLabelsVariants.add([['Label 1']]);
-selectedLabelsVariants.add([['Label 2', 'Label 3']]);
-
-Data(selectedLabelsVariants).Scenario('Indicate selected labels', async function({ I, LabelStudio, AtLabels, AtImageView, current }) {
- const { labels } = current;
-
- I.amOnPage('/');
- LabelStudio.setFeatureFlags(FLAGS);
- LabelStudio.init({
- config: CONFIG_MULTIPLE,
- data: {
- image: IMAGE,
- },
- annotations: [
- {
- id: 'test',
- result: [
- {
- 'original_width': 2242,
- 'original_height': 2802,
- 'image_rotation': 0,
- 'value': {
- 'points': [
- [
- 10,
- 10,
- ],
- [
- 30,
- 10,
- ],
- [
- 20,
- 20,
- ],
- ],
- 'closed': false,
- 'polygonlabels': labels,
- },
- 'id': 'tNe7Bjmydb',
- 'from_name': 'tag',
- 'to_name': 'img',
- 'type': 'polygonlabels',
- 'origin': 'manual',
- },
- ],
- },
- ],
- });
-
- AtImageView.waitForImage();
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- I.say('check if we see an indication of selected labels after resuming from draft');
- for (const label of labels) {
- AtLabels.seeSelectedLabel(label);
- }
-
- I.say('close loaded region');
- AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
-
- I.say('check that we do not see an indication of selected after region completion');
- for (const label of labels) {
- AtLabels.dontSeeSelectedLabel(label);
- }
-
- I.say('check if we see an indication of selected labels after going back through the history');
- I.pressKey(['CommandOrControl', 'Z']);
- for (const label of labels) {
- AtLabels.seeSelectedLabel(label);
- }
-
-});
-
-const selectedPolygonAfterCreatingVariants = new DataTable(['shouldSelect', 'description']);
-
-selectedPolygonAfterCreatingVariants.add([false, 'Without set setting']);
-selectedPolygonAfterCreatingVariants.add([true, 'With set setting']);
-
-Data(selectedPolygonAfterCreatingVariants).Scenario('Select polygon after creating from unfinished draft', async function({ I, LabelStudio, AtImageView, AtSidebar, AtSettings, current }) {
- const { shouldSelect, description } = current;
-
- I.say(description);
- I.amOnPage('/');
- LabelStudio.setFeatureFlags(FLAGS);
- LabelStudio.init({
- config: CONFIG,
- data: {
- image: IMAGE,
- },
- annotations: [
- {
- id: 'test',
- result: [
- {
- 'original_width': 2242,
- 'original_height': 2802,
- 'image_rotation': 0,
- 'value': {
- 'points': [
- [
- 10,
- 10,
- ],
- [
- 30,
- 10,
- ],
- [
- 20,
- 20,
- ],
- ],
- 'closed': false,
- 'polygonlabels': [
- 'Hello',
- ],
- },
- 'id': 'tNe7Bjmydb',
- 'from_name': 'tag',
- 'to_name': 'img',
- 'type': 'polygonlabels',
- 'origin': 'manual',
- },
- ],
- },
- ],
- });
-
- if (shouldSelect) {
- AtSettings.open();
- AtSettings.setGeneralSettings({
- [AtSettings.GENERAL_SETTINGS.AUTO_SELECT_REGION]: shouldSelect,
- });
- AtSettings.close();
- }
-
- AtImageView.waitForImage();
- await AtImageView.lookForStage();
- const canvasSize = await AtImageView.getCanvasSize();
-
- I.say('close loaded region');
- AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
-
- I.say(`check that region ${shouldSelect ? 'is' : 'is not'} selected`);
- if (shouldSelect) {
- AtSidebar.seeSelectedRegion();
- } else {
- AtSidebar.dontSeeSelectedRegion();
- }
-
- I.say('unselect regions');
- I.pressKey('u');
- AtSidebar.dontSeeSelectedRegion();
-
- I.say('go back through the history');
- I.pressKey(['CommandOrControl', 'Z']);
- AtSidebar.dontSeeSelectedRegion();
-
- I.say('repeat creation and checking');
- AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
- if (shouldSelect) {
- AtSidebar.seeSelectedRegion();
- } else {
- AtSidebar.dontSeeSelectedRegion();
- }
-
-});
+const { saveDraftLocally, getLocallySavedDraft } = require('./helpers');
+const assert = require('assert');
+
+
+Feature('Unfinished polygons');
+
+const IMAGE =
+ 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg';
+
+const CONFIG = `
+
+
+
+
+
+
+
+`;
+
+const CONFIG_MULTIPLE = `
+
+
+
+
+
+
+
+
+`;
+
+const FLAGS = {
+ ff_feat_front_DEV_2576_undo_key_points_polygon_short: true,
+ ff_front_dev_2431_delete_polygon_points_080622_short: true,
+ ff_front_dev_2432_auto_save_polygon_draft_210622_short: true,
+};
+
+Scenario('Drafts for unfinished polygons', async function({ I, LabelStudio, AtLabels, AtImageView }) {
+ I.amOnPage('/');
+ LabelStudio.init({
+ config: CONFIG,
+ data: {
+ image: IMAGE,
+ },
+ params: {
+ onSubmitDraft: saveDraftLocally,
+ },
+ });
+ LabelStudio.setFeatureFlags(FLAGS);
+
+ AtImageView.waitForImage();
+
+ await AtImageView.lookForStage();
+
+ I.say('start drawing polygon without finishing it');
+ AtLabels.clickLabel('Hello');
+ AtImageView.drawByClickingPoints([[50,50], [100, 50], [100, 80]]);
+
+ I.say('wait until autosave');
+ I.waitForFunction(() => !!window.LSDraft, .5);
+ I.say('check result');
+ const draft = await I.executeScript(getLocallySavedDraft);
+
+ assert.strictEqual(draft[0].value.points.length, 3);
+ assert.strictEqual(draft[0].value.closed, false);
+});
+
+Scenario('Saving polygon drawing steps to history', async function({ I, LabelStudio, AtLabels, AtImageView }) {
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags(FLAGS);
+ LabelStudio.init({
+ config: CONFIG,
+ data: {
+ image: IMAGE,
+ },
+ });
+
+ AtImageView.waitForImage();
+
+ await AtImageView.lookForStage();
+
+ I.say('put one point of polygon');
+ AtLabels.clickLabel('Hello');
+ AtImageView.drawByClick(50, 50);
+
+ I.say('check current history size');
+ let historyStepsCount = await I.executeScript(() => window.Htx.annotationStore.selected.history.history.length);
+
+ assert.strictEqual(historyStepsCount, 2);
+
+ I.say('try to draw some more points and close polygon');
+ AtImageView.drawByClick(100, 50);
+ AtImageView.drawByClick(125, 100);
+ AtImageView.drawByClick(50, 50);
+
+ I.say('check current history size and result');
+ historyStepsCount = await I.executeScript(() => window.Htx.annotationStore.selected.history.history.length);
+ assert.strictEqual(historyStepsCount, 5);
+ let result = await LabelStudio.serialize();
+
+ assert.strictEqual(result[0].value.points.length, 3);
+ assert.strictEqual(result[0].value.closed, true);
+
+ I.say('try to undo closing and 2 last points');
+ I.click('button[aria-label=Undo]');
+ I.click('button[aria-label=Undo]');
+ I.click('button[aria-label=Undo]');
+ I.say('check current history index and result');
+ historyStepsCount = await I.executeScript(() => window.Htx.annotationStore.selected.history.undoIdx);
+ assert.strictEqual(historyStepsCount, 1);
+ result = await LabelStudio.serialize();
+ assert.strictEqual(result[0].value.points.length, 1);
+ assert.strictEqual(result[0].value.closed, false);
+
+});
+
+Scenario('Init an annotation with old format of closed polygon result', async function({ I, LabelStudio, AtImageView }) {
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags(FLAGS);
+ LabelStudio.init({
+ config: CONFIG,
+ data: {
+ image: IMAGE,
+ },
+ annotations: [
+ {
+ id: 'test',
+ result: [
+ {
+ 'original_width': 2242,
+ 'original_height': 2802,
+ 'image_rotation': 0,
+ 'value': {
+ 'points': [
+ [
+ 22.38442822384428,
+ 27.042801556420233,
+ ],
+ [
+ 77.61557177615572,
+ 24.90272373540856,
+ ],
+ [
+ 48.90510948905109,
+ 76.07003891050584,
+ ],
+ ],
+ 'polygonlabels': [
+ 'Hello',
+ ],
+ },
+ 'id': 'tNe7Bjmydb',
+ 'from_name': 'tag',
+ 'to_name': 'img',
+ 'type': 'polygonlabels',
+ 'origin': 'manual',
+ },
+ ],
+ },
+ ],
+ });
+
+ AtImageView.waitForImage();
+
+ const result = await LabelStudio.serialize();
+
+ assert.strictEqual(result[0].value.points.length, 3);
+ assert.strictEqual(result[0].value.closed, true);
+});
+
+Scenario('Init an annotation with result of new format of polygon results', async function({ I, LabelStudio, AtImageView }) {
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags(FLAGS);
+ LabelStudio.init({
+ config: CONFIG,
+ data: {
+ image: IMAGE,
+ },
+ annotations: [
+ {
+ id: 'test',
+ result: [
+ {
+ 'original_width': 2242,
+ 'original_height': 2802,
+ 'image_rotation': 0,
+ 'value': {
+ 'points': [
+ [
+ 40,
+ 40,
+ ],
+ [
+ 50,
+ 40,
+ ],
+ [
+ 50,
+ 50,
+ ],
+ [
+ 40,
+ 50,
+ ],
+ ],
+ 'closed': true,
+ 'polygonlabels': [
+ 'World',
+ ],
+ },
+ 'id': 'tNe7Bjmydb_2',
+ 'from_name': 'tag',
+ 'to_name': 'img',
+ 'type': 'polygonlabels',
+ 'origin': 'manual',
+ },
+ {
+ 'original_width': 2242,
+ 'original_height': 2802,
+ 'image_rotation': 0,
+ 'value': {
+ 'points': [
+ [
+ 10,
+ 10,
+ ],
+ [
+ 30,
+ 10,
+ ],
+ [
+ 20,
+ 20,
+ ],
+ ],
+ 'closed': false,
+ 'polygonlabels': [
+ 'Hello',
+ ],
+ },
+ 'id': 'tNe7Bjmydb',
+ 'from_name': 'tag',
+ 'to_name': 'img',
+ 'type': 'polygonlabels',
+ 'origin': 'manual',
+ },
+ ],
+ },
+ ],
+ });
+
+ AtImageView.waitForImage();
+
+ I.say('check loaded regions');
+ let result = await LabelStudio.serialize();
+
+ assert.strictEqual(result.length, 2);
+ assert.strictEqual(result[0].value.points.length, 4);
+ assert.strictEqual(result[0].value.closed, true);
+ assert.strictEqual(result[1].value.points.length, 3);
+ assert.strictEqual(result[1].value.closed, false);
+
+ I.say('try to continue drawing loaded unfinished region');
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .40);
+ result = await LabelStudio.serialize();
+ assert.strictEqual(result[1].value.points.length, 4);
+ assert.strictEqual(result[1].value.closed, false);
+
+ I.say('try to close loaded region');
+ AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
+ result = await LabelStudio.serialize();
+ assert.strictEqual(result[1].value.points.length, 4);
+ assert.strictEqual(result[1].value.closed, true);
+
+ // I.say("check that it is possible to go back throught history");
+ // I.pressKey(['CommandOrControl', 'Z']);
+ // result = await LabelStudio.serialize();
+ // assert.strictEqual(result[1].value.points.length, 4);
+ // assert.strictEqual(result[1].value.closed, false);
+ //
+ // I.say("check that it is possible to close this region again");
+ // AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
+ // result = await LabelStudio.serialize();
+ // assert.strictEqual(result[1].value.points.length, 4);
+ // assert.strictEqual(result[1].value.closed, true);
+
+});
+
+Scenario('Removing a polygon by going back through history', async function({ I, LabelStudio, AtLabels, AtImageView }) {
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags(FLAGS);
+ LabelStudio.init({
+ config: CONFIG,
+ data: {
+ image: IMAGE,
+ },
+ params: {
+ onSubmitDraft: saveDraftLocally,
+ },
+ });
+
+ AtImageView.waitForImage();
+
+ await AtImageView.lookForStage();
+
+ I.say('start drawing polygon');
+ AtLabels.clickLabel('Hello');
+ AtImageView.drawByClickingPoints([[50,50], [100, 50]]);
+
+ I.say('revert all changes and creating of the region');
+ I.pressKey(['CommandOrControl', 'Z']);
+ I.pressKey(['CommandOrControl', 'Z']);
+
+ I.say('polygon should disappear and polygon tool should be switched of');
+ let result = await LabelStudio.serialize();
+
+ assert.strictEqual(result.length, 0);
+
+ I.say('try to draw after that');
+ AtImageView.drawByClickingPoints([[50,50], [100, 50]]);
+
+ I.say('check if it was possible to do this (it shouldn\'t)');
+ result = await LabelStudio.serialize();
+ assert.strictEqual(result.length, 0);
+
+ I.say('check if there were any errors');
+ // The potential errors should be caught by `errorsCollector` plugin
+});
+
+Scenario('Continue annotating after closing region from draft', async function({ I, LabelStudio, AtLabels, AtImageView }) {
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags(FLAGS);
+ LabelStudio.init({
+ config: CONFIG,
+ data: {
+ image: IMAGE,
+ },
+ annotations: [
+ {
+ id: 'test',
+ result: [
+ {
+ 'original_width': 2242,
+ 'original_height': 2802,
+ 'image_rotation': 0,
+ 'value': {
+ 'points': [
+ [
+ 10,
+ 10,
+ ],
+ [
+ 30,
+ 10,
+ ],
+ [
+ 20,
+ 20,
+ ],
+ ],
+ 'closed': false,
+ 'polygonlabels': [
+ 'Hello',
+ ],
+ },
+ 'id': 'tNe7Bjmydb',
+ 'from_name': 'tag',
+ 'to_name': 'img',
+ 'type': 'polygonlabels',
+ 'origin': 'manual',
+ },
+ ],
+ },
+ ],
+ });
+
+ AtImageView.waitForImage();
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ I.say('close loaded region');
+ AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
+
+ I.say('try to create another region');
+ AtLabels.clickLabel('World');
+
+ AtImageView.drawByClickingPoints([
+ [canvasSize.width * .40, canvasSize.height * .40],
+ [canvasSize.width * .50, canvasSize.height * .40],
+ [canvasSize.width * .50, canvasSize.height * .50],
+ [canvasSize.width * .40, canvasSize.height * .50],
+ [canvasSize.width * .40, canvasSize.height * .40],
+ ]);
+
+ const result = await LabelStudio.serialize();
+
+ assert.strictEqual(result.length, 2);
+ assert.strictEqual(result[1].value.points.length, 4);
+ assert.strictEqual(result[1].value.closed, true);
+
+});
+
+Scenario('Change label on unfinished polygons', async function({ I, LabelStudio, AtLabels, AtImageView }) {
+ I.amOnPage('/');
+ LabelStudio.init({
+ config: CONFIG,
+ data: {
+ image: IMAGE,
+ },
+ params: {
+ onSubmitDraft: saveDraftLocally,
+ },
+ });
+ LabelStudio.setFeatureFlags(FLAGS);
+
+ AtImageView.waitForImage();
+
+ await AtImageView.lookForStage();
+
+ I.say('start drawing polygon without finishing it');
+ AtLabels.clickLabel('Hello');
+ AtImageView.drawByClickingPoints([[50,50], [100, 50], [100, 80]]);
+ AtLabels.clickLabel('World');
+
+ I.say('wait until autosave');
+ I.waitForFunction(() => !!window.LSDraft, .5);
+ I.say('check result');
+ const draft = await I.executeScript(getLocallySavedDraft);
+
+ assert.strictEqual(draft[0].value.polygonlabels[0], 'World');
+});
+
+
+const selectedLabelsVariants = new DataTable(['labels']);
+
+selectedLabelsVariants.add([['Label 1']]);
+selectedLabelsVariants.add([['Label 2', 'Label 3']]);
+
+Data(selectedLabelsVariants).Scenario('Indicate selected labels', async function({ I, LabelStudio, AtLabels, AtImageView, current }) {
+ const { labels } = current;
+
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags(FLAGS);
+ LabelStudio.init({
+ config: CONFIG_MULTIPLE,
+ data: {
+ image: IMAGE,
+ },
+ annotations: [
+ {
+ id: 'test',
+ result: [
+ {
+ 'original_width': 2242,
+ 'original_height': 2802,
+ 'image_rotation': 0,
+ 'value': {
+ 'points': [
+ [
+ 10,
+ 10,
+ ],
+ [
+ 30,
+ 10,
+ ],
+ [
+ 20,
+ 20,
+ ],
+ ],
+ 'closed': false,
+ 'polygonlabels': labels,
+ },
+ 'id': 'tNe7Bjmydb',
+ 'from_name': 'tag',
+ 'to_name': 'img',
+ 'type': 'polygonlabels',
+ 'origin': 'manual',
+ },
+ ],
+ },
+ ],
+ });
+
+ AtImageView.waitForImage();
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ I.say('check if we see an indication of selected labels after resuming from draft');
+ for (const label of labels) {
+ AtLabels.seeSelectedLabel(label);
+ }
+
+ I.say('close loaded region');
+ AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
+
+ I.say('check that we do not see an indication of selected after region completion');
+ for (const label of labels) {
+ AtLabels.dontSeeSelectedLabel(label);
+ }
+
+ I.say('check if we see an indication of selected labels after going back through the history');
+ I.pressKey(['CommandOrControl', 'Z']);
+ for (const label of labels) {
+ AtLabels.seeSelectedLabel(label);
+ }
+
+});
+
+const selectedPolygonAfterCreatingVariants = new DataTable(['shouldSelect', 'description']);
+
+selectedPolygonAfterCreatingVariants.add([false, 'Without set setting']);
+selectedPolygonAfterCreatingVariants.add([true, 'With set setting']);
+
+Data(selectedPolygonAfterCreatingVariants).Scenario('Select polygon after creating from unfinished draft', async function({ I, LabelStudio, AtImageView, AtSidebar, AtSettings, current }) {
+ const { shouldSelect, description } = current;
+
+ I.say(description);
+ I.amOnPage('/');
+ LabelStudio.setFeatureFlags(FLAGS);
+ LabelStudio.init({
+ config: CONFIG,
+ data: {
+ image: IMAGE,
+ },
+ annotations: [
+ {
+ id: 'test',
+ result: [
+ {
+ 'original_width': 2242,
+ 'original_height': 2802,
+ 'image_rotation': 0,
+ 'value': {
+ 'points': [
+ [
+ 10,
+ 10,
+ ],
+ [
+ 30,
+ 10,
+ ],
+ [
+ 20,
+ 20,
+ ],
+ ],
+ 'closed': false,
+ 'polygonlabels': [
+ 'Hello',
+ ],
+ },
+ 'id': 'tNe7Bjmydb',
+ 'from_name': 'tag',
+ 'to_name': 'img',
+ 'type': 'polygonlabels',
+ 'origin': 'manual',
+ },
+ ],
+ },
+ ],
+ });
+
+ if (shouldSelect) {
+ AtSettings.open();
+ AtSettings.setGeneralSettings({
+ [AtSettings.GENERAL_SETTINGS.AUTO_SELECT_REGION]: shouldSelect,
+ });
+ AtSettings.close();
+ }
+
+ AtImageView.waitForImage();
+ await AtImageView.lookForStage();
+ const canvasSize = await AtImageView.getCanvasSize();
+
+ I.say('close loaded region');
+ AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
+
+ I.say(`check that region ${shouldSelect ? 'is' : 'is not'} selected`);
+ if (shouldSelect) {
+ AtSidebar.seeSelectedRegion();
+ } else {
+ AtSidebar.dontSeeSelectedRegion();
+ }
+
+ I.say('unselect regions');
+ I.pressKey('u');
+ AtSidebar.dontSeeSelectedRegion();
+
+ I.say('go back through the history');
+ I.pressKey(['CommandOrControl', 'Z']);
+ AtSidebar.dontSeeSelectedRegion();
+
+ I.say('repeat creation and checking');
+ AtImageView.drawByClick(canvasSize.width * .10, canvasSize.height * .10);
+ if (shouldSelect) {
+ AtSidebar.seeSelectedRegion();
+ } else {
+ AtSidebar.dontSeeSelectedRegion();
+ }
+
+});