From 414f2c4b6143993d575b9c6e55489d3b025ecfd1 Mon Sep 17 00:00:00 2001 From: Lukas Weber <32765578+lukasalexanderweber@users.noreply.github.com> Date: Sun, 11 Dec 2022 00:39:35 +0100 Subject: [PATCH] support polygon coordinate list (#19) --- README.md | 28 +++- ext/readme_imgs/from_polygon.png | Bin 0 -> 1646 bytes ext/readme_imgs/sample2.png | Bin 43433 -> 0 bytes ext/readme_imgs/sample3.png | Bin 61457 -> 0 bytes ext/readme_imgs/sample4.png | Bin 88498 -> 0 bytes ext/readme_imgs/sample5.png | Bin 56366 -> 0 bytes largestinteriorrectangle/__init__.py | 4 +- largestinteriorrectangle/lir.py | 36 +++-- .../lir_within_polygon.py | 42 ++++++ tests/context.py | 2 +- tests/test_lir.py | 135 +++++++++--------- tests/test_lir_basis.py | 86 +++++++++++ tests/test_lir_within_contour.py | 9 +- tests/test_lir_within_polygon.py | 79 ++++++++++ 14 files changed, 338 insertions(+), 83 deletions(-) create mode 100644 ext/readme_imgs/from_polygon.png delete mode 100644 ext/readme_imgs/sample2.png delete mode 100644 ext/readme_imgs/sample3.png delete mode 100644 ext/readme_imgs/sample4.png delete mode 100644 ext/readme_imgs/sample5.png create mode 100644 largestinteriorrectangle/lir_within_polygon.py create mode 100644 tests/test_lir_basis.py create mode 100644 tests/test_lir_within_polygon.py diff --git a/README.md b/README.md index dee09b6..055658a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Fast Largest Interior Rectangle calculation within a binary grid. -![sample1](https://github.com/lukasalexanderweber/lir/blob/main/ext/readme_imgs/sample1.png?raw=true) ![sample2](https://github.com/lukasalexanderweber/lir/blob/main/ext/readme_imgs/sample2.png?raw=true) ![sample4](https://github.com/lukasalexanderweber/lir/blob/main/ext/readme_imgs/sample5.png?raw=true) +![sample1](https://github.com/lukasalexanderweber/lir/blob/main/ext/readme_imgs/sample1.png?raw=true) :rocket: Through [Numba](https://github.com/numba/numba) the Python code is compiled to machine code for execution at native machine code speed! @@ -53,6 +53,32 @@ then calculate the rectangle. lir.lir(grid, contour) # array([2, 2, 4, 7]) ``` +You can also calculate the lir from a list of polygon coordinates. + +```python +import numpy as np +import cv2 as cv +import largestinteriorrectangle as lir + +polygon = np.array([[[20, 15], [210, 10], [220, 100], [100, 150], [20, 100]]], np.int32) +rectangle = lir.lir(polygon) + +img = np.zeros((160, 240, 3), dtype="uint8") + +cv.polylines(img, [polygon], True, (0, 0, 255), 1) +cv.rectangle(img, lir.pt1(rectangle), lir.pt2(rectangle), (255, 0, 0), 1) + +cv.imshow('lir', img) +cv.waitKey(0) +cv.destroyAllWindows() +``` + +![from_polygon](https://github.com/lukasalexanderweber/lir/blob/main/ext/readme_imgs/from_polygon.png?raw=true) + +In the background, a grid is created with `cv.fillPoly` (OpenCV is needed as optional dependency), on which the contour is computed and the lir based on contour is used. + +See also my [answer in this SO question](https://stackoverflow.com/questions/70362355/finding-largest-inscribed-rectangle-in-polygon/74736411#74736411). + ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/ext/readme_imgs/from_polygon.png b/ext/readme_imgs/from_polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..75001e4538ea17aa0118ce437c8214eac2cb84a8 GIT binary patch literal 1646 zcmcJQZ%h+s9LMW4;*2cIMyuW9yr(0+b$fC5 z{QKSK_x=5z-}4-B*z2-4Kf76@(PY=}w>okA2Yw=(p2F|w7w_-XXg0o7Z>?$aulxP$ zb?(CNE^qki+xx~>77l0K96!^u+m*fL>gfPGykAtTf>=hing;1*0O^JuvcHs-a{yUd zf7`B<2&Tci?Me}|$0|PK3Rhj}ql!zr#k=k7gDKb-Fgn9k@;RMSf-I9Ga&ioHcq4EG z(#M#UyezJg6K6b6J~auiAq%Jl>|bwyp~YURpKAT>GF9Ebp2=h68*NO7>goqBR=HI= zB+;$Y!MWE9bu&6vZq|E0+vN}%1u^PIM^thD0y!>Lu6{BK&#GSjyU7hqM~<3?6Vxnt zBv@YqeuR&r2u9#h;?#j&Cb`pjhg8j!GU6enh4#)eR7KAfCDZfrn%f( zL7eItyIMKHweX$_?+#)T<_gIUDPvR}ag@3M2Z@#^FfU+k8HuO=yC0YQV;RXVl&R~~ z0q{X4QPs>XQu?sKhZkP-fgU|RF~F0+(Qn10@`t0A1m2^2(m!XZ8&~eNcg3EHmAv8z zq$j$x#mt`9ctN!B=;~tKMVlC&FevqCIY^sr(Lh6Pc*MXCxA3S9mQ}FBu4rI-JGNkl z>v&WGJ>xcUb<&_%(6165Z-@r^a#}7L*eiYWaQRf0LoiKsX$K5N`*^|h@jRT>4vgZ( zW*!ZJZ(PFh(_PxQ(=<8{lO`WnttvVMl$k`W>7>8i2fPdO&;?c_26m+}+raQt(+Q}PdVX_wnz631db7@}J$<%cn(uQT$#z>G^cXcSCm t=(L4LIN$-DY6e_ z-}mqD>tlL)W)B(MuVMY>^<6Gk!_@x0hw*m-{~wQu%R7Dgbg%@S{8gv{;Hgum&OP_svth#q zKr)A-qM~KXmOcIS(_k4H87V+kAuA5)Kr|2)6$KdS>FI@qg%>VdC@3iS+XzGmmo8np zc=2LtYAV?Xfe@^nd1%e3F$CFzDNVutOMkf7*(SSWaW+k8#S~=Nn zzx{T)6_iOUx7KyVQ>hHaWKw(& zzmkE$+PZb?zWw%Fd=)7{X(Ie}jW?#HrO|u^15!}b&Ye3y`sgDO7N`ad8g%cy_lkMF_S$O`Cr;eDbu0PN zZDlHiitE;`ON&^$b}h~jR?JrA6=yv1=bwKL95@g@kfbvCupccngN%fxO`E>)#v3>o z0@^2_2Yz?$+BJFdSPb)G>HrUoO<580z!}Y_Lx&E4UGp$QBC!e3%E`&OeEBk} zu+$8IY4ChA2!8Zu_uqd%sGw>>aPau%1}&&y*YG5qm4%Z|hfC&UT;6xzeF#QN^d)=s zrXUeIY1OLLn{U2}eIP9D81vHN2Nj_p6zK19n9PV!HR=kniBp)ai}2us4}SB_H)eA# zmnT{itbF|O$Mm$#ZwRtYAEhn1ph* z;Xea_5m48*ZQBM67=XIaxuvOjcXVVzDt>i{iHRXTc<|uSqeoLKcLUTOvJny!6Wg_G zch_Ba9XxpO{Q2{+1vThEgu4Nfsz^3!p+?vy!mmvB!NxammmnN!@y!wLa@nLwlg!M_ zF=NK`@82J_!W-!o;gGVT3V?6kym`lt9eecX@$0X@o;-O{_%RbNr7|fd+LTNwDJgWP zAdk}9w{H)-lE+NKe_Z6pG`DQoa_G>Z>C>k(+}CutYp%{esSxf;MJR%@AoR#1kI;Up zk#t{bh>63`$jCTw;DGO4Nnt2Bd-m*6qecOhh*nn4(9)qP8vgFP@1A?^xv#$Z%8QN% zFli=C5ttd^E(F{7_;_reGEfmhUgZP16jY=*bB<%jj=l8KOZ<{9s`Mg)Lb>IY7}TY1 z5J*A2PdxDivmYW!NlDm;A5!r1{rBJh_SdGS!v1uOfjJ&>Y}1SbsfeM7k}&4sl%Y(mty{f3tc;RzQoL?#4%neDgud3 zn>T08ngxgOEnN^O5+@i`)ymn>;lrOCH?DQ-*6|4miZXMf!a`JxXLMIoo;ZMtM%P*r!qMznHfhqF z^@50qD`(F_ihsH;UxM~YS>NpPxstsA9T z4BN=g)*Op7oH1j@=FOW)6xbpgHf+eO3$&zv)46H6%G$)>JgK&bNiT)d<6jLy%K$_v zmp;faEF%GoikO!D@y8!riovNzaNba(?1hu& zMlHl!O_3eIw2JH%k+Fs^W)5KX!U=9zd16%~Au;i~>#oBwdST`OW;?*xK*=&k2HWZ~ zGvnjqnK+vEP%=zIy?Js29NK(%1{r zvSmvm+TfgJsvwjY&a7|2;H07O>H?@3nz0wUWiPv(*>!kx zzjMcXoF9Jpq3Yt;_))Uu%F&KZG+^$=RG&o$#whXd;lsS{!lTbC#_-W5ygF>40YK3c zX9XB&5qKT(z4zW@w9lSJM@=@-0Ja4*OrLoR0+!T3UvtIrO)|_MjDD+uwz+@7}aT~9ur*njqGxVrNkjzyB7=ib9*;?PIQ6q(!zRe9_Gf%4Z zEhkcq8o;PtvmA z;tMZC$Hs!0xr`I9E)7Um1wc%cIrD&sdc1 zYzJofaO1{}Amx5V?1md|U=3fbdN2%e{Gkn+3?{_%izEFw%#YJE*kg)$0g!rq79_iM z>&8KU;u~$it;2#^%_t}=eEs#;<&Yep5`%LOI(RD9pg{wbqe9FMU;&o@zR?vA}g&r8#9}&rwsH>r0&A226}@`}XY| z6viQZpvQ4@RmzG(Ja7$gx(esNVqw69T~Scs#sz^R#=w*FhVYOazv=kNz|~qsIk74p zSh53d*sx(75v|bD=aPtHJbwK6-rc*~CnYsKeL7F*;GzK6XMaz4CZqJA$Br%oVoR|0Sci*ksqzT9RIuFV00I=%5 zA|n~-^JZ;}7A-u031(dH-M)S0^5szt8*;v=9XPJ#Jb)JCdCZ9oI2=V<3KDuU&Y;?r zm-pXQt2l|pd8%ojwgPNm#xi}WUBIL=Ky25g%b3=!i_V?nt#~Mkx7$|xy479lJ zl$6Bdu@M6Y7U$=)9O9_SCK|x`F~Yz%mI4f;YuBdDfaK)A&z|L`GqSe{TFk>RCwAX( zf~ngjVq1*tSqmP?dFb?VJcez)}`2xICJJq&QSIM278>&+PhDmXyYST_TaYm za)GyFJb@>~w%&gG?OC&CF-t}r?q_4f+O%o&=%bHfXzbaky?U~PWeSFkkjNr3trqVk(lQQQ+e6~!Yp<$cQ*2cjqO@e5lgw=dqEkKgAYqijT^7X$;p~M z`xx5~4R6PF3Y$bq%!>pk$A>u3l}$0k9!MhJ5hB2N2@ibajvZEjQQ2 z5n~{1fUFBR5wixAK^S|g-3{G*&m1?l6SDU1YS2J=tf;8moSbFZ*#Ksq>Jo?GCYS4o zF+sL`&gFKAJa@q*s!dg5PkbCBEzMk`6Zh4C+c-GA7d-exfNzeH6dv_sSpQqIX8xi@ zj3dD5=%GU=EVrqZELK>e1wBt624=Dg&RvURST5I&`i~csyBB~L#*Ody@uC9XEaFR( zGiS|WgA<>5P!AkU0f2d+&$CFrb2?@62D>?%qTceC)iVO&NmYWL(G z6{BC{;~#t#1Q6wHIA}V-j|LdQ;i1@Nk(`_?du1Me_+egjMSJMb9QaxZyP;OCS``-; zhaV+idnlY=D@+1Q6GAB|DSYfkK8NCm*V=ZA9^Wf~7Le@Nv7=|to;)HJ?eU>MFk^Qi z2WFcCpMB!9nk-QO5+N`^{N++O3+C5edo3SPW70<5v>JbfYSTT?qK#^aB@Qq=$7e?& z;1N(D0j06)3H)?sBBX;E#s^yDG~Ws>?lw&5LXi)3rKP3u%>p7`6^BOMT*@rAPN!ZS zw^c-z^w7!KvuCjTS}xI{Ij6+QAi?)m2r2GS2ox!dw`Bzj$H;J` ztm;FkgCfxPYN{j#SVj0jg;hRcM=u3b8GDW1d*LygU>qnic=OUWu*(XV1GRg zT-1kSeCVNvGBY!ImjWZJ6lDp=?B7CE%^#GZ&+8eXnR2BtrkT+ZX;>V6&3DQ93?i5N z_U!{3ok2&qrQ_j@cmShT!HhnP&Wvx6(WBxMDxw;J3W4c|pMU=O-Me>F3yIJqu$PNk z_(GywdWlUUDRc}p9WU)je#zifsv?)v0;}{i!-o&g&CMmkCBe5M5-Usm zNVr5Wd->&;nP7xA01@F4j5laN>%|ve6cw0NPenrCS0lm+zkH>MK9xP@WY%)>B_KQj zr3jKvMBfcLB0S>;`IIl;JB1GXn;8z7awQh@%%p{=FwK18op;{3{`%{|jP951a)DPV ztq(ekj{f@VFMJEavj8n!w^@F2{_@K&{wdRc?A*CiJ7)m~h4Q*0tqRy+63nDh1z?4d zg97pjpJs0T^BK0g7D&J)OfvvsFv{mfnNv$jxjd1LKsHQ@PCF|r>+7$-W)!0*K^&u>;T%dpO=KX#GR%@COL$yB z3W^r}cu@}O5(?zkbmUSLO8dZ+X(G&xczZD>CWcq>sZ_1F8X>5PU>Mt4xNxD^v{V!n zg=)?W00fSY$j;721!#v~bHJ*Q%CIgK;oH&J7R#ZOVyJ?~s#*Z5Fjs`Nu|C8@cHxTO zpaKFiMB+_;Y}d9HR*p&y$f7uR;QjaCXKOwIv(o_|f{-M>_10UDJ@%MhF$)7!X1=LT z_#}d&IZ*~?R~AZ*COKncL0W?R541|4Qyh@)fj$Qt9CHEWPuB;glbj2bnH zmo2crjEs!KhYwQ_l~Ke@Aq;W2l1izauxHO6HuUi#0jJAQJK7I~2gIS}End8slL{fo zWgw+&ng=ada>m9+mPP2~$Y6F&mYKtrC@biEwGb3BvH;BsZ^7WRd-v|NMMBuPt*VNU z!5Ejhe`vPEs0^qGwvrHjV6Q`m4l7r#1T#V)m`MlLuU|hq`$H67j!V?|<05>$1UR7` z%Uil&_#?mrW2^)cU=P6jwB1)%HytG7o z82Hzy7E4{O*siI)dky#8Bs1tD6u(Rom@0s@ZDoY-tKV^ZlwT?)K7ijc@Kc%PE`FXa zaPHi>98P0%wBWfy)>cYG_PfA0G611}!g}BiFi0UHH~}OPMv3$1&);GM9tv{T55RU1 z(RwIo2hJ+O1Hhnn7HfE$fO$x2YATmJvJsjL4(YwX52*6;^0sZ;#@tzOvN>AtUo!#I zV1pb}dq$5$($dmcIOZ1b^{|^A%cIj`m5UVdg!V_Ph64sjAR+z`QCV-}!6^}@*n*Q} f@ZZmo-&UdH+J7V_?mM$mF_qe9aPRDtu^Imd4a1jS diff --git a/ext/readme_imgs/sample3.png b/ext/readme_imgs/sample3.png deleted file mode 100644 index be1b018e41c8d1de4296b0c43b872d14f09a48a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61457 zcmeI5Ymgk(b;o;V=dtgHw360nB?U}gAR}Z8ghWvyMG-?Hh^q)G=Yzk* zDJrQ-iZ7`O6~`Y)0wO@E9J>J7K!t}bT(Ync5(uIfizEaRLXQPW`<|WG&Q5-}&+hdw zJJZwM(;Cfc`_^i7r~BS>&OPUU&pG#YPxrsvwc$HW^;gszhS9X{yKC;||GoVCtJ)x+ zx3zqCiDCSav2M-kO+N}48~*9#f8?lNd=2;-@HOCTz}JAU0bc{Y1}?D%0+*P-&%Ww4 z5D1h2Nj96c9z@9cw10M*YXF4Rm<2rmCIMV-Wps43B*;pD)LO;47%$1mnv{R`l4u~; zf&~T8cq({S4wf{%dyoZ#K_D(tj*X3FGMSRhEClsWRbB(R7R=+Wv4x|;xOr!tesvEr z;3Wl?xpD~9IAuo^AD4EtWT%ZIh3m%Jf*MuAOMj&jiA18&Xh9h`n@lECsZ?(J$t`U` zVRE<0NAPN)kjn7j$EO~~!f z&BRjpKYR+Gd>tMhe)G*YckS8*)Hp)H^Gzn`KJf?OO-)S<52A@gVrXaxp(isac|^le z&xVp+TsIIEyYB9&qaK%|UiKSnbx~0>)%je-c33bR4#QMaqb{fwxLkA1HTT_j-`cfn zIVdbZ5*4xgh*{oy?>#_915WWdS;@{o1{H(=$naRSXwmxh>*vgw1KmT14!!u|i{Q+@ zj&w@~2?f69j#HoYwxUeo+!#(}U^wVUbmg?K{q|a2n4_ToC`qcbv-9q|@18$@J}seu z%wh#94?OSyA~M?DyLWeY zcYpH9C)7hVH5tZjku4HWO;ph)9*dhLszIW0grqsZ@TM(2!~aktN$`DYJ2@V$HQ z9S2!9OC`Vk#CJrJ3C4jiArOx>ajMw=;F94Gi0BBO=tQ_f-kcn@EE3}RtJ^JL6Pp(?E>ZY4+BGMOI@fPGN2cwOR>o;$1Sh7S?NCb74XIQY{ z6k&q7PDPeQhkpB8RHb4!2MaBWtf>_}n?o?t+Iqv?~KnsHwFg{HaK7}5=_WJUy+HR8Et8aHaE+fz(UHhPJwtcM`;=u zxbxk2gYh`iKftcf=xE7EVV^Iis>x(zzi38p@2xv`cEB_eQDX;r#p9QK7WplqEC^vf z_xGn*=IbL9s?P7ice+fR#cIk474)(~S>V;wlx=SxyYa^0!iBVtkBpFqz5l@nEc@`L z6kMA1baf0eSR z3$3F9x^a7GLZL8a9(*gEUS3;kHR;^)$?ZoD-0@@MkzTx_)s@XMGy=0mk6oUa z(4r}JVLOe8%x7n3=Fvxk^XCU6k>SC?m$q$t@uipWFSseQ)i z+qSJ*x$;|GT`leHupAp02z>HMpugX^`f6k8QUx-63B#E*BEZTFu$Hfw-1d(n(jOdW zb90oX@gzy?Q?qKRt)9w3tG>?9fe)E2s)C2{&{;ANtcEA%@APS7-#*fe zu`j<=y9ZoTN+iylIdlH}c@Tyh)9SStfqRfKDj;(B@L|%jkGi|By7J04K$gw2++`&l zm_6IryxG{eQB5xa*(5EX9VUIzg^-2RZck;IlxFC3OTb<~GK$erqKLiL9<&m7;sGHc zA;AY-F&p>qHy(S;IDVYHe}Of2=eiT0fQ-pwXa2YWnMfi7lZy;T4j(2|BZQJeCP71k zaqYFnRaaS$oc5J0Y6r`70K(VXxT(f&4u%S9ok#*IIlha)3)gVP6~?Jk1ReYZX&gKL zGiJ<)Mj{d`?cm!z$bhgg0Mx9a|F8iF{J~i~ZuIqyX9<#%>D=ogsKO>myk=!=U*XAP zbNF_fVLtH`bm;9hVllF6Ss#$w#LbdQbJa#&&AhmtOe6-{Ts5zaM%fKwm7z9-B;!F! z5`VdTF8fjyh*LJSIJM1hV}Kqs!FI%Ymd5zzk&!IBtf5d%OUqy;^TFP|@9o%e;Lsru zhUr0=os`7`&D?C6p zFgp6`&Ydqj@kH;13)meuhFhtGCsSb0l23=ZpvhW8?N;z7QgTjUCm@shWpAO&V1Qq> zm-1C?`bQ$%rucbJ57!bIA_&8$*g+VLy-87EhW{K_uZYN^(k~ZH1ON8x$8RZlFTh3M zu7Z`h!2xc_k=V?2%EUe302z2!@iGuNbN1{@ue`G3?YGa3j09O@n(3NN$UBwTZ+{6i zARyyfmU*ucF3ph)?(;J+L?IIdWG6oR>_4A;@=tquez0IcJC}^=>PBNRwxpfO;z)46 zaV0fiMk>rrBF-8U=Yrks^Dhfz!Um9W@5iUdkIy)Ibby;`_%T8_tmLpSh2OHeHK5!H zxsiFPM%5kWMIInQvy8AF0rKRG6WeLktvX*i8yb*oo4bQ#=fWYpgn@fcrUk^4;XH}c zjSUUlE~`MspIJ99UmpJv$~ znFl$UW(L|+;xB&);Y1Ei#IID32FBAbGd^sCD)hmw;;^l_n+EGHNBTgxXoV?yBJHkAd@8qNCND=Jw3f&d@+>G-V3() znJZT?y}BCjVq|_7Pc?uyVM*uIhlvP|bFw8J|98F3djWEDGIKWgann<~Pf23sdNn<0 z@E&AY5{b0%vpKod%=}4$Zs1p@tp*%>H49CFGZjg-Gjp{nv7qmtnxY!;%#$&G5Jm`R zXVzU5pQ0M{#V)Ao zGaqEr)9w`=wGXn2s(X4e^FcN}?OxGQ`yi{Rx~C^IA7o#(`})OypRjo^z*jr^-|T51 zu;g!#{JW1jd&=@#m7xJ2WMwGN?`ls2KFI9p$!}GL27Hi}p*+8nHU^`KrWF*e2?e6^$&lz{HB|D`L2321yA0@#P*73o(w!i8ycG1+OA3_n|NU%@5D)`k;18KwI{aU z%CQFisW8^JFma4o@Hh}?3JHK+L5VTzyWO% zfAu^C!&ta<>Bdc){v;ls|Ln8bp`kGz{_g|MjuEQ44PlbBj6^6Jjqq9~^R1XpdE(UzvBTHb3Rk7RW6XvnHlt}jro26zy=`QQLPa}V3BdjK*G?PNUOXGX~Y z@3WQ%Guo8GZ&uwJz>lbHs&Lrd#;o1Nf*mD*wlOv|c)y}VaXWqdHd93dat)xShIfb9 zUt`MPyaXA%72YK`bRM~dKez%&ZkZMb4@%*_xVcI-Tk7Zw-extX@59 zbo7naUfXxz0HISrR&qSnjMbHOVcj|a@RvQ7~-`eIsX023>aaWBT; zU{g)aE#Lg+H60zNPMkP!=n%1sQ?g5HMWUSBeq@OufpYHLxwqYV>kVtx%x!KS>*)!` z<1p6{w3fg>Th$tnf>*%Au`7R;OX(B{ow zU%tGqwl*^~r1tvFplQw9KRX39Aj>#xj6uy?Fk&Mk@pxP$mo%y5D9QqvP)C6JhK7!g zj^?H&?h@fW!+fgTpHJ!(KKbfrX(g>uB@IZd+3b|n8x^^76AZGdkl;{&iGpx@@wYJe*@6L#ck zp}L!k!b|G0J(mEuepumG%b9#b04R}6vYo+dK!FLd!eOJOMafE$PgSeZ!bEZKI=)LZ z2qrhy9=~RMv1Pz~Qrg;#y1Lx019Jbt!NF82MP+;wzLYTTy_%+sSEH_W{c)!f&iPK< zOr^%#NFAE}RP~%&B5*6}mnNn)F}+y7FrA5`<$??z ztOI&`dwB<|ilips6ui!zGq!FuKK)c3k^Ia%kinEUP>u7N7>VQmCI2uJfSUSxgXGdX z2^|Y#rvs_d;NYlizH3$6>1f$TE#8@M+X}~1LS*Fy>iF269%K7&%(3VNL|+%rVxlHx@2r+9@A~xTe1?IB>d*FC7Y=&ONBdPmULjhJdop8*-c3uLDzP zvP92h&K)`O{->YDO$3wo6rGaxiF?!%E%PVx%m{~9E?wHRV1c47bIg-jkxFq(Gl58v z)k-!sqi{wAAK%%DK?xwFVzr^r?Af!4nCzU5jEu1LFWa-Kdy(SCld1hMHB2>j5cbF; zkE~d+qGQe+__6w^nKv)+{qGyGmTRvw!{ZkI%E)XBgo?AZ;hnC`I0r^C-F|5J;J${`$OmKYIA# zm220k87>)!q)je3i7iBLyjhhy0yW6w6f`+2f02m6dv)i`VP`?z{*T`Z2D!cj;Dj^U zvR0TIkeLDmpi4X^P%d7)7=H%x;L5=u0?nPPxQe)1=t8<+q2S_jJ>wi8C6$B}puo(M zOCioihV5)_AqfU?_#d{nf7sA4#D1fAetsY3oM1sNWgb)s6L-3s2pDT@{BYK+D>^y= z8JJK`&Mn9!5Xl(p!r>Oyi5BApYABg&(}XGWni3r`eM?IVcE51pLL!miNG{F1QM&;d z2e-cb^0ALUh9<1>g$~@| zgdF%??8iWulm`wR=rs|>N!*xfBC~v=@umD9c}!uBpFjWfwrxB2??-0*KWapNcY-Ob zm;;9bQRvUtt!s%!OOnzYQ5{BKvu4e#S+ib!_0_lDdJF%eFo9r&QX^+ph6MssuXh7B8l7rP4FXNL8)L)Dc$8J&Sls1RQ;_SIKk{m?@X&6_t5W)RI~5R1iV z!$E}0!!=)VdvxbiUtiz5U>vv%BTvN1Um*@a1#pwkx;O%Y(Vuz-L=_jA zhn@?_9f7}imAETJ=y!AwX{fm3H^C!RBp zD2!it;RUwoV2S9UDwlUI$iU)`9Xr^_&7)xFtI~KOuWqMr!8W-<7Hm)r2N^ZR6GmKy28o+C0mDhw~z1x}W)*b(~T%!-|@=<(lV|8B8iv^=(M&FW2+V$AR* zR(Q)0yErljV(i_!mt_+jse?9=ih7>WwVciXr>8!ZqeqW!*|KHk%$bNNfD?Fq_St6# z4;~yCV8g*yp_dF2DFuinyTQHKcuMy58{}yKo#QD=YF=9h%>yh-9vzFX?)@-XKXFsz z10EtMxE|)ZguR036nky`R#l+^v9boG9Sgb#nZ<}fp6ojyftIFVd!_}Y;3@xLb!q^E z;`zk6AfX)#x(8VvqP0eHJyS`LH3m7IY6XuneLns{^i}+Yax^ef=Ys tr3T!C40UKY=c>ksSFSevIvRNR2QM5vvtaXglrq-cwqeaLzWtZ~{QtvE)Pn#3 diff --git a/ext/readme_imgs/sample4.png b/ext/readme_imgs/sample4.png deleted file mode 100644 index c907763cc5fc31d988a2bd34224cdddb14db8d6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88498 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 diff --git a/largestinteriorrectangle/__init__.py b/largestinteriorrectangle/__init__.py index a19dcaa..a1a8c9a 100644 --- a/largestinteriorrectangle/__init__.py +++ b/largestinteriorrectangle/__init__.py @@ -1,3 +1,3 @@ -from .lir import lir +from .lir import lir, pt1, pt2 -__version__ = "0.1.1" +__version__ = "0.2.0" diff --git a/largestinteriorrectangle/lir.py b/largestinteriorrectangle/lir.py index 6b38ddb..1ca30b0 100644 --- a/largestinteriorrectangle/lir.py +++ b/largestinteriorrectangle/lir.py @@ -1,19 +1,39 @@ from .lir_basis import largest_interior_rectangle as lir_basis from .lir_within_contour import largest_interior_rectangle \ as lir_within_contour +from .lir_within_polygon import largest_interior_rectangle \ + as lir_within_polygon -def lir(grid, contour=None): +def lir(data, contour=None): """ - Returns the Largest Interior Rectangle of a binary grid. - :param grid: 2D ndarray containing data with `bool` type. - :param contour: (optional) 2D ndarray with shape (n, 2) containing - xy values of a specific contour where the rectangle could start - (in all directions). + Computes the Largest Interior Rectangle. + :param data: Can be + 1. a 2D ndarray with shape (n, m) of type boolean. The lir is found within all True cells + 2. a 3D ndarray with shape (1, n, 2) with integer xy coordinates of a polygon in which the lir should be found + :param contour: (optional) 2D ndarray with shape (n, 2) containing xy values of a specific contour where the rectangle could start (in all directions). Only needed for case 1. :return: 1D ndarray with lir specification: x, y, width, height :rtype: ndarray """ + if len(data.shape) == 3: + return lir_within_polygon(data) if contour is None: - return lir_basis(grid) + return lir_basis(data) else: - return lir_within_contour(grid, contour) + return lir_within_contour(data, contour) + + +def pt1(lir): + """ + Helper function to compute pt1 of OpenCVs rectangle() from a lir + """ + assert lir.shape == (4,) + return (lir[0], lir[1]) + + +def pt2(lir): + """ + Helper function to compute pt2 of OpenCVs rectangle() from a lir + """ + assert lir.shape == (4,) + return (lir[0] + lir[2] - 1, lir[1] + lir[3] - 1) diff --git a/largestinteriorrectangle/lir_within_polygon.py b/largestinteriorrectangle/lir_within_polygon.py new file mode 100644 index 0000000..a029f03 --- /dev/null +++ b/largestinteriorrectangle/lir_within_polygon.py @@ -0,0 +1,42 @@ +import numpy as np + +from .lir_within_contour import largest_interior_rectangle as lir_contour + +cv = None # as an optional dependency opencv will only be imported if needed + + +def largest_interior_rectangle(polygon): + check_for_opencv() + origin, mask = create_mask_from_polygon(polygon) + contours, _ = cv.findContours(mask, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) + contour = contours[0][:, 0, :] + mask = mask > 0 + lir = lir_contour(mask, contour) + lir = lir.astype(np.int32) + lir[0:2] = lir[0:2] + origin + return lir + + +def create_mask_from_polygon(polygon): + assert polygon.shape[0] == 1 + assert polygon.shape[1] > 2 + assert polygon.shape[2] == 2 + check_for_opencv() + bbox = cv.boundingRect(polygon) + mask = np.zeros([bbox[3], bbox[2]], dtype=np.uint8) + zero_centered_x = polygon[:, :, 0] - bbox[0] + zero_centered_y = polygon[:, :, 1] - bbox[1] + polygon = np.dstack((zero_centered_x, zero_centered_y)) + cv.fillPoly(mask, polygon, 255) + origin = bbox[0:2] + return origin, mask + + +def check_for_opencv(): + global cv + if cv is None: + try: + import cv2 + cv = cv2 + except Exception: + raise ImportError('Missing optional dependency \'opencv-python\' to compute lir based on polygon. Use pip or conda to install it.') diff --git a/tests/context.py b/tests/context.py index 86ca1d7..9bf15ce 100644 --- a/tests/context.py +++ b/tests/context.py @@ -2,4 +2,4 @@ import os sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) -from largestinteriorrectangle import lir_basis, lir_within_contour \ No newline at end of file +from largestinteriorrectangle import lir, pt1, pt2, lir_basis, lir_within_contour, lir_within_polygon diff --git a/tests/test_lir.py b/tests/test_lir.py index 4ff891c..753b3a7 100644 --- a/tests/test_lir.py +++ b/tests/test_lir.py @@ -2,81 +2,86 @@ import os import numpy as np -import cv2 as cv -from .context import lir_basis as lir +from .context import lir, pt1, pt2 TEST_DIR = os.path.abspath(os.path.dirname(__file__)) +GRID = np.array([[0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0, 0], + [1, 1, 0, 0, 0, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0]], "bool") + class TestLIR(unittest.TestCase): - def test_lir(self): - - grid = np.array([[0, 0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 1, 1, 0, 0, 0], - [0, 0, 1, 1, 1, 1, 1, 0, 0], - [0, 0, 1, 1, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 1, 1, 1, 1, 0], - [0, 1, 1, 1, 1, 1, 1, 0, 0], - [0, 0, 1, 1, 1, 1, 0, 0, 0], - [0, 0, 1, 1, 1, 1, 0, 0, 0], - [1, 1, 1, 1, 1, 1, 0, 0, 0], - [1, 1, 0, 0, 0, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0]]) - grid = grid > 0 - - h = lir.horizontal_adjacency(grid) - v = lir.vertical_adjacency(grid) - span_map = lir.span_map(grid, h, v) - rect = lir.biggest_span_in_span_map(span_map) - rect2 = lir.largest_interior_rectangle(grid) + def test_lir_polygon(self): + polygon = np.array([[ + [10,10], + [150,10], + [100,100], + [-40,100]] + ], dtype=np.int32 ) + + rect = lir(polygon) + np.testing.assert_array_equal(rect, np.array([10, 10, 91, 91])) + + def test_lir_binary_mask(self): + + rect = lir(GRID) + np.testing.assert_array_equal(rect, np.array([2, 2, 4, 7])) + + def test_lir_binary_mask_with_contour(self): + contour = np.array([[2, 0], + [2, 1], + [2, 2], + [2, 3], + [2, 4], + [1, 5], + [2, 6], + [2, 7], + [1, 8], + [0, 8], + [0, 9], + [1, 9], + [2, 8], + [3, 8], + [4, 8], + [5, 9], + [6, 9], + [7, 9], + [8, 9], + [7, 9], + [6, 9], + [5, 8], + [5, 7], + [5, 6], + [6, 5], + [7, 4], + [7, 3], + [6, 2], + [5, 1], + [4, 1], + [3, 2], + [2, 1]], dtype=np.int32) + + + rect = lir(GRID, contour) np.testing.assert_array_equal(rect, np.array([2, 2, 4, 7])) - np.testing.assert_array_equal(rect, rect2) - - def test_spans(self): - grid = np.array([[1, 1, 1], - [1, 1, 0], - [1, 0, 0], - [1, 0, 0], - [1, 0, 0], - [1, 1, 1]]) - grid = grid > 0 - - h = lir.horizontal_adjacency(grid) - v = lir.vertical_adjacency(grid) - v_vector = lir.v_vector(v, 0, 0) - h_vector = lir.h_vector(h, 0, 0) - spans = lir.spans(h_vector, v_vector) - - np.testing.assert_array_equal(v_vector, np.array([6, 2, 1])) - np.testing.assert_array_equal(h_vector, np.array([3, 2, 1])) - np.testing.assert_array_equal(spans, np.array([[3, 1], - [2, 2], - [1, 6]])) - - def test_vector_size(self): - t0 = np.array([1, 1, 1, 1], dtype=np.uint32) - t1 = np.array([1, 1, 1, 0], dtype=np.uint32) - t2 = np.array([1, 1, 0, 1, 1, 0], dtype=np.uint32) - t3 = np.array([0, 0, 0, 0], dtype=np.uint32) - t4 = np.array([0, 1, 1, 1], dtype=np.uint32) - t5 = np.array([], dtype=np.uint32) - - self.assertEqual(lir.predict_vector_size(t0), 4) - self.assertEqual(lir.predict_vector_size(t1), 3) - self.assertEqual(lir.predict_vector_size(t2), 2) - self.assertEqual(lir.predict_vector_size(t3), 0) - self.assertEqual(lir.predict_vector_size(t4), 0) - self.assertEqual(lir.predict_vector_size(t5), 0) - - def test_img(self): - grid = cv.imread(os.path.join(TEST_DIR, "testdata", "mask.png"), 0) - grid = grid > 0 - rect = lir.largest_interior_rectangle(grid) - np.testing.assert_array_equal(rect, np.array([4, 20, 834, 213])) + def test_rectangle_pts(self): + rect = np.array([10, 10, 91, 91]) + self.assertEqual(pt1(rect), (10, 10)) + self.assertEqual(pt2(rect), (100, 100)) + def starttest(): unittest.main() diff --git a/tests/test_lir_basis.py b/tests/test_lir_basis.py new file mode 100644 index 0000000..41aea8c --- /dev/null +++ b/tests/test_lir_basis.py @@ -0,0 +1,86 @@ +import unittest +import os + +import numpy as np +import cv2 as cv + +from .context import lir_basis as lir + +TEST_DIR = os.path.abspath(os.path.dirname(__file__)) + + +class TestLIRbasis(unittest.TestCase): + + def test_lir(self): + + grid = np.array([[0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0, 0], + [1, 1, 0, 0, 0, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0]]) + grid = grid > 0 + + h = lir.horizontal_adjacency(grid) + v = lir.vertical_adjacency(grid) + span_map = lir.span_map(grid, h, v) + rect = lir.biggest_span_in_span_map(span_map) + rect2 = lir.largest_interior_rectangle(grid) + + np.testing.assert_array_equal(rect, np.array([2, 2, 4, 7])) + np.testing.assert_array_equal(rect, rect2) + + def test_spans(self): + grid = np.array([[1, 1, 1], + [1, 1, 0], + [1, 0, 0], + [1, 0, 0], + [1, 0, 0], + [1, 1, 1]]) + grid = grid > 0 + + h = lir.horizontal_adjacency(grid) + v = lir.vertical_adjacency(grid) + v_vector = lir.v_vector(v, 0, 0) + h_vector = lir.h_vector(h, 0, 0) + spans = lir.spans(h_vector, v_vector) + + np.testing.assert_array_equal(v_vector, np.array([6, 2, 1])) + np.testing.assert_array_equal(h_vector, np.array([3, 2, 1])) + np.testing.assert_array_equal(spans, np.array([[3, 1], + [2, 2], + [1, 6]])) + + def test_vector_size(self): + t0 = np.array([1, 1, 1, 1], dtype=np.uint32) + t1 = np.array([1, 1, 1, 0], dtype=np.uint32) + t2 = np.array([1, 1, 0, 1, 1, 0], dtype=np.uint32) + t3 = np.array([0, 0, 0, 0], dtype=np.uint32) + t4 = np.array([0, 1, 1, 1], dtype=np.uint32) + t5 = np.array([], dtype=np.uint32) + + self.assertEqual(lir.predict_vector_size(t0), 4) + self.assertEqual(lir.predict_vector_size(t1), 3) + self.assertEqual(lir.predict_vector_size(t2), 2) + self.assertEqual(lir.predict_vector_size(t3), 0) + self.assertEqual(lir.predict_vector_size(t4), 0) + self.assertEqual(lir.predict_vector_size(t5), 0) + + def test_img(self): + grid = cv.imread(os.path.join(TEST_DIR, "testdata", "mask.png"), 0) + grid = grid > 0 + rect = lir.largest_interior_rectangle(grid) + np.testing.assert_array_equal(rect, np.array([4, 20, 834, 213])) + + +def starttest(): + unittest.main() + + +if __name__ == "__main__": + starttest() diff --git a/tests/test_lir_within_contour.py b/tests/test_lir_within_contour.py index 7dbf3da..b4e4632 100644 --- a/tests/test_lir_within_contour.py +++ b/tests/test_lir_within_contour.py @@ -26,8 +26,7 @@ def test_grid(self): grid = np.uint8(grid * 255) - contours, _ = \ - cv.findContours(grid, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) + contours, _ = cv.findContours(grid, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) contour = contours[0][:, 0, :] grid = grid > 0 @@ -39,8 +38,7 @@ def test_grid(self): def test_img(self): grid = cv.imread(os.path.join(TEST_DIR, "testdata", "mask.png"), 0) - contours, _ = \ - cv.findContours(grid, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) + contours, _ = cv.findContours(grid, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) contour = contours[0][:, 0, :] grid = grid > 0 @@ -56,8 +54,7 @@ def test_multiple_shapes(self): grid = cv.imread(os.path.join(TEST_DIR, "testdata", "two_shapes.png"), 0) - contours, _ = \ - cv.findContours(grid, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) + contours, _ = cv.findContours(grid, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) contour1 = contours[0][:, 0, :] contour2 = contours[1][:, 0, :] diff --git a/tests/test_lir_within_polygon.py b/tests/test_lir_within_polygon.py new file mode 100644 index 0000000..8aa0895 --- /dev/null +++ b/tests/test_lir_within_polygon.py @@ -0,0 +1,79 @@ +import unittest +import os + +import numpy as np +import cv2 as cv + +from .context import lir_within_polygon as lir + +TEST_DIR = os.path.abspath(os.path.dirname(__file__)) + + +class TestLIRwithinPolygon(unittest.TestCase): + + def test_create_mask_from_polygon(self): + polygon = np.array([[ + [10,10], + [150,10], + [100,100], + [-40,100]] + ], dtype=np.int32) + + origin, mask = lir.create_mask_from_polygon(polygon) + + self.assertEqual(origin, (-40, 10)) + self.assertEqual(mask.shape, (91, 191)) + self.assertEqual(np.count_nonzero(mask == 255), 12831) + + def test_polygon(self): + polygon = np.array([[ + [10,10], + [150,10], + [100,100], + [-40,100]] + ], dtype=np.int32 ) + + rect = lir.largest_interior_rectangle(polygon) + np.testing.assert_array_equal(rect, np.array([10, 10, 91, 91])) + + def test_polygon2(self): + polygon = np.array([[ + [9,-7], + [12,-6], + [8,3], + [10,6], + [12,7], + [1,9], + [-8,7], + [-6,6], + [-4,6], + [-6,2], + [-6,0], + [-7,-5], + [-2,-7], + [1,-3], + [5,-7], + [8,-4], + ]], dtype=np.int32 ) + + rect = lir.largest_interior_rectangle(polygon) + np.testing.assert_array_equal(rect, np.array([-5, -3, 14, 12])) + + + def test_img(self): + grid = cv.imread(os.path.join(TEST_DIR, "testdata", "two_shapes.png"), 0) + + contours, _ = \ + cv.findContours(grid, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) + polygon = np.array([contours[0][:, 0, :]]) + + rect = lir.largest_interior_rectangle(polygon) + np.testing.assert_array_equal(rect, np.array([162, 62, 43, 44])) + + +def starttest(): + unittest.main() + + +if __name__ == "__main__": + starttest()