From c6e1876106ef9e129c8af7cca5f184b01d62cebe Mon Sep 17 00:00:00 2001 From: Mikkel Pedersen Date: Thu, 10 Aug 2023 12:05:17 +0200 Subject: [PATCH] fix(results): Change reading of resolution string --- .../icon/HB Extract HDR.png | Bin 1340 -> 1340 bytes .../json/HB_Extract_HDR.json | 4 ++-- .../src/HB Extract HDR.py | 17 +++++++++++++---- .../user_objects/HB Extract HDR.ghuser | Bin 6952 -> 7067 bytes 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/honeybee_grasshopper_radiance/icon/HB Extract HDR.png b/honeybee_grasshopper_radiance/icon/HB Extract HDR.png index 6a8b3bbbe4d6c68f16c115083142b1bef5f27ee8..63bb63d396ce99b5a0904b5a727f34cea79f6742 100644 GIT binary patch delta 1284 zcmV+f1^fEE3cL!CNPiM^000tn0p4aGcmMzd(Md!>R7gwZm3vTBRUF3;=o$tr?p=1- zdH-&xj}3;D)McqBGMmSBNwQwqD=1Ul}tq1Pmd-_ICjR@p5Lx=MVnmhKDFpS@D2>$Ey zFlBA-^RaF}CdC(FLda66oSkR$y1^Mwh(;K^;j^`yO!($zFRr%L;o9j&Tx>gyo>Q&3 z-rhiI91`oPdfezbhYP*8z=kE&st8f@+JSJhmS+pY_kSRB=PrEMc?~Y9WpK@G#*F=W z(C#gPTV@lOtXfLl->_gzMjb*T*Wl9|pF!w=>Acn+A3|iF(Fg7b)>I?Il!8L* zmmuYU@p3I}HXm5;heO!%BS-^48f_rWe}J^|~vAA>Y~oi80^eM~IZy z6Qa}kEq_$R6kud@88T8$D870Zr0@~)uI&J9qk%pRw9VH+Dt-m2>4QVn_Yf4_qy-*< zRR2Kv3Dct!V7+t+vXoQsjLL_%Xw*DjPl$3+a=ertxlzW#fda?x!@d33I??-BXtdI+M94-_bW00+y@P)(mh-_{ixy*CuWi=mfEs9X&#=k){w z;|kZ6W(2NFgSESxuAmX(#1^PSj0<=@A*#^0`BGE+;1<=|;2!f1#?n@3IW=?&7J3hN zyI959;V3K%)=P((OQX+sZkguNBbC@m|+SKr+SyLiVZ48sU{?cj(f&Rriw-z2x77)J|Bar9Wpb5l@Ok1gpr z4;5m!UA%7SD80s&z8HBJ4soq;jQIc~VonT0IUHjv;7I2RU(W(j1jO>1;iFuTu$Eh5 zM*p`V+TLxyVERdJw_i9Z?GUA%2Yr)%8FG61$&>O51QqviF%bf@NRykzGg0Iy2QGn delta 1284 zcmV+f1^fEE3cL!CNPiuN0013_0gvVJWdHyK(Md!>R7gwZm3vTBRUF3;=o$tr?p=1- zWgnM)Dv?DkBb0+mJaTwgyqA|CijJ6qpt4$O z&8XS)53MMta;?4{+&eNfh}OS-XFfA$X3zcn?)jZ__mKZjJb#}fL7EpsLR5Y)Cu%0o zGO6c=1$vJm3%Q^Fd-SYouY35u)3S(MNaL#Eme>kOf*F#{b&#*w16f2)omM8x;if-( zCOMEWH9s)+^O(&>e0#kgmpdA8Tgh<>C@89CBuSJG21;v)H zKq`TVi}kQty1o{#9;C?cl8|9V$@v z9Z2RxtSOBp!oWKedh16C!>6 zK(;8o598Jr4KFY#eD54|?o#kg<*;k{9jxy>=)^*3=nC` zCRVaGSc*&rnTHm0S4E=#Qa>cg9dL^-d4H%A@nbnXJ*EPwQn@q%yf=f?{hkSTEluPCFDVpp3Xe(U&xap^U2Hq-qdvlzsH3A$3H#_O*weZESF=Fme$kv}^eC1j#IP$& z82mN_J9=#w%pl2~whJev6N0p}V13drLP}4c)KGX+<256WZma{RczQgY$Xt2ouEXr| z6o~23lO(m#8>|~*<0*Q}orUbpN=PCLzEK6NDW!jwbGXO;eLQ(&nqpDhGOdq)gETUK uuWDBCY31Vh?Rsy&=qZGFbJIo$A%6pY4mh)RGb_FT0000 hdr_resolution / 3:\n msg = 'Recommended _resolution_ is one third or less of the _hdr resolution. \\n' \\\n 'Got {} for _resolution_ and {} for _hdr. Recommended _resolution_ \\n' \\\n 'is {} or lower.'\n give_warning(ghenv.Component, msg.format(resolution, hdr_resolution, \n int(hdr_resolution / 3)))\n else:\n if resolution is None: \n resolution = hdr_resolution\n if resolution > hdr_resolution:\n msg = 'Output image resolution ({}) is larger than input image \\n' \\\n 'resolution ({}). It is recommended that _resolution_ is equal \\n' \\\n 'to or less than input image resolution.'\n give_warning(ghenv.Component, msg.format(resolution, hdr_resolution))\n return resolution\n\n\nif all_required_inputs(ghenv.Component):\n # check if _view is Honeybee Radiance View\n assert isinstance(_view, View), \\\n 'Expected Honeybee Radiance View in _view. Got {}.'.format(type(_view))\n \n # check if header contains a view\n hdr_view = check_view_hdr(_hdr)\n \n # check view points\n check_view_points(_view, hdr_view)\n \n # check resolution\n resolution = check_resolution(_hdr, _resolution_, _view, hdr_view)\n \n # set up the paths for the various files used in translation\n img_dir = os.path.dirname(_hdr)\n input_image = os.path.basename(_hdr)\n commands = []\n \n # add the command to include exposure in the pixels\n expos_image = input_image.lower().replace('.hdr', '_e.hdr')\n ra_xyze = Ra_xyze(input=input_image, output=expos_image)\n ra_xyze.options.r = True\n ra_xyze.options.o = True\n commands.append(ra_xyze)\n \n # add the command to extract a view (HDR)\n view_identifier = _view.identifier\n view = os.path.basename(_view.to_file(img_dir))\n pinterp_image = input_image.lower().replace('.hdr', '_{}.hdr'.format(view_identifier))\n pinterp = Pinterp(output=pinterp_image, view=view, image=expos_image,\n zspec=1)\n pinterp.options.x = resolution\n pinterp.options.y = resolution\n commands.append(pinterp)\n hdr = os.path.join(img_dir, pinterp_image)\n \n # run the commands in series\n env = None\n if rad_folders.env != {}:\n env = rad_folders.env\n env = dict(os.environ, **env) if env else None\n for r_cmd in commands:\n r_cmd.run(env, cwd=img_dir)\n", + "code": "\nimport os\nimport subprocess\nimport re\n\ntry: # import honeybee_radiance_command dependencies\n from honeybee_radiance_command.pinterp import Pinterp\n from honeybee_radiance_command.ra_xyze import Ra_xyze\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance_command:\\n\\t{}'.format(e))\n\ntry: # import honeybee_radiance dependencies\n from honeybee_radiance.config import folders as rad_folders\n from honeybee_radiance.view import View\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance:\\n\\t{}'.format(e))\n\ntry: # import ladybug_{{cad}} dependencies\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, give_warning\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n# check the Radiance date of the installed radiance\ntry: # import lbt_recipes dependencies\n from lbt_recipes.version import check_radiance_date\nexcept ImportError as e:\n raise ImportError('\\nFailed to import lbt_recipes:\\n\\t{}'.format(e))\ncheck_radiance_date()\n\n\ndef check_view_hdr(hdr_path):\n \"\"\"Check if the header of the HDR image contains a view (VIEW=).\n \n A ValueError is raised if the image does not contain a valid view.\n A ValueError is raised if the view type is not -vta or -vth.\n \n Args:\n hdr_path: The path to an HDR image file.\n \"\"\"\n # set hdr_view to None\n hdr_view = None\n \n # read hdr image and search for a valid view\n with open(hdr_path, 'r') as hdr_file:\n for lineCount, line in enumerate(hdr_file):\n if lineCount < 200:\n low_line = line.lower()\n if not low_line.startswith('\\t'):\n if low_line.startswith('view='):\n hdr_view = View.from_string('hdr_view', line)\n else: # no need to check the rest of the document\n break\n if not hdr_view:\n raise ValueError(\n 'Connected _hdr image does not contain a valid view in the header.\\n'\n 'Note that indented views in the header will be ignored by pinterp.')\n if not hdr_view.type in ('a', 'h'):\n msg = 'Expected view type -vta or -vth in _hdr. Got view type -vt{}.'\n raise ValueError(msg.format(hdr_view.type))\n return hdr_view\n\n\ndef check_view_points(view, hdr_view):\n \"\"\"Check if view points of output view and input HDR are matching.\n \n A ValueError is raised if the view points are not matching.\n \n Args:\n view: A Honeybee Radiance View to extract.\n hdr_view: A Honeybee Radiance View from the input HDR.\n \"\"\"\n if not view.position == hdr_view.position:\n msg = 'View points of _view and _hdr are not matching.\\n' \\\n 'Got _view = {} and _hdr = {}.'\n raise ValueError(msg.format(view.position, hdr_view.position))\n\n\ndef check_resolution(hdr_path, resolution, view, hdr_view):\n \"\"\"Check the resolution of the output HDR as well as the input HDR.\n \n A warning is raised if the HDR image dimensions are not square. A warning is\n raised if the resolution is larger than one third of the HDR image\n resolution if converting a 360 FOV HDR to 180 FOV HDR. A warning is raised \n if the output resolution is larger than the input resolution.\n \n Args:\n hdr_path: The path to an HDR image file.\n resolution: The resolution of the extracted view from hdr_path.\n view: A Honeybee Radiance View to extract.\n hdr_view: A Honeybee Radiance View from the input HDR.\n \"\"\"\n # get the path the the getinfo command\n getinfo_exe = os.path.join(rad_folders.radbin_path, 'getinfo.exe') if \\\n os.name == 'nt' else os.path.join(rad_folders.radbin_path, 'getinfo')\n \n # run the getinfo command in a manner that lets us obtain the result\n cmds = [getinfo_exe, '-d', hdr_path]\n use_shell = True if os.name == 'nt' else False\n process = subprocess.Popen(cmds, stdout=subprocess.PIPE, shell=use_shell)\n stdout = process.communicate()\n img_dim = stdout[0]\n\n def get_dimensions(img_dim):\n dimensions = []\n for d in ['+X', '-Y']:\n regex = r'\\%s\\s+(\\d+)' % d\n matches = re.finditer(regex, img_dim, re.MULTILINE)\n dim = next(matches).groups()[0]\n dimensions.append(int(dim))\n return dimensions\n # check the X and Y dimensions of the image\n hdr_x, hdr_y = get_dimensions(img_dim)\n \n if hdr_x == hdr_y: \n hdr_resolution = hdr_x = hdr_y\n else:\n msg = 'It is recommended that image dimensions of _hdr are square.\\n' \\\n 'Got {} x {}.'\n give_warning(ghenv.Component, msg.format(hdr_x, hdr_y))\n \n # check resolution ratio of output image / input image\n if hdr_view.h_size == 360 and hdr_view.v_size == 360:\n if resolution is None: \n resolution = hdr_resolution / 3\n if resolution > hdr_resolution / 3:\n msg = 'Recommended _resolution_ is one third or less of the _hdr resolution. \\n' \\\n 'Got {} for _resolution_ and {} for _hdr. Recommended _resolution_ \\n' \\\n 'is {} or lower.'\n give_warning(ghenv.Component, msg.format(resolution, hdr_resolution, \n int(hdr_resolution / 3)))\n else:\n if resolution is None: \n resolution = hdr_resolution\n if resolution > hdr_resolution:\n msg = 'Output image resolution ({}) is larger than input image \\n' \\\n 'resolution ({}). It is recommended that _resolution_ is equal \\n' \\\n 'to or less than input image resolution.'\n give_warning(ghenv.Component, msg.format(resolution, hdr_resolution))\n return resolution\n\n\nif all_required_inputs(ghenv.Component):\n # check if _view is Honeybee Radiance View\n assert isinstance(_view, View), \\\n 'Expected Honeybee Radiance View in _view. Got {}.'.format(type(_view))\n \n # check if header contains a view\n hdr_view = check_view_hdr(_hdr)\n \n # check view points\n check_view_points(_view, hdr_view)\n \n # check resolution\n resolution = check_resolution(_hdr, _resolution_, _view, hdr_view)\n \n # set up the paths for the various files used in translation\n img_dir = os.path.dirname(_hdr)\n input_image = os.path.basename(_hdr)\n commands = []\n \n # add the command to include exposure in the pixels\n expos_image = input_image.lower().replace('.hdr', '_e.hdr')\n ra_xyze = Ra_xyze(input=input_image, output=expos_image)\n ra_xyze.options.r = True\n ra_xyze.options.o = True\n commands.append(ra_xyze)\n \n # add the command to extract a view (HDR)\n view_identifier = _view.identifier\n view = os.path.basename(_view.to_file(img_dir))\n pinterp_image = input_image.lower().replace('.hdr', '_{}.hdr'.format(view_identifier))\n pinterp = Pinterp(output=pinterp_image, view=view, image=expos_image,\n zspec=1)\n pinterp.options.x = resolution\n pinterp.options.y = resolution\n commands.append(pinterp)\n hdr = os.path.join(img_dir, pinterp_image)\n \n # run the commands in series\n env = None\n if rad_folders.env != {}:\n env = rad_folders.env\n env = dict(os.environ, **env) if env else None\n for r_cmd in commands:\n r_cmd.run(env, cwd=img_dir)\n", "category": "HB-Radiance", "name": "HB Extract HDR", "description": "Interpolate or extrapolate a High Dynamic Range (HDR) image file from another\nHDR image file.\n_\nRecommended use is to extract 180 FOV (-vh 180 -vv 180) angular or hemispherical\nHDR images from a 360 FOV (-vh 360 -vv 360) angular HDR image. Alternatively,\nconversions between 180 FOV angular and hemispherical HDR images can be made.\n-" diff --git a/honeybee_grasshopper_radiance/src/HB Extract HDR.py b/honeybee_grasshopper_radiance/src/HB Extract HDR.py index 2ad92d2..0f7f098 100644 --- a/honeybee_grasshopper_radiance/src/HB Extract HDR.py +++ b/honeybee_grasshopper_radiance/src/HB Extract HDR.py @@ -34,13 +34,14 @@ ghenv.Component.Name = 'HB Extract HDR' ghenv.Component.NickName = 'ExtractHDR' -ghenv.Component.Message = '1.6.0' +ghenv.Component.Message = '1.6.1' ghenv.Component.Category = 'HB-Radiance' ghenv.Component.SubCategory = '4 :: Results' ghenv.Component.AdditionalHelpFromDocStrings = '0' import os import subprocess +import re try: # import honeybee_radiance_command dependencies from honeybee_radiance_command.pinterp import Pinterp @@ -138,10 +139,18 @@ def check_resolution(hdr_path, resolution, view, hdr_view): process = subprocess.Popen(cmds, stdout=subprocess.PIPE, shell=use_shell) stdout = process.communicate() img_dim = stdout[0] - + + def get_dimensions(img_dim): + dimensions = [] + for d in ['+X', '-Y']: + regex = r'\%s\s+(\d+)' % d + matches = re.finditer(regex, img_dim, re.MULTILINE) + dim = next(matches).groups()[0] + dimensions.append(int(dim)) + return dimensions # check the X and Y dimensions of the image - hdr_x = int(img_dim.split(' ')[-1].strip()) - hdr_y = int(img_dim.split(' ')[-3].strip()) + hdr_x, hdr_y = get_dimensions(img_dim) + if hdr_x == hdr_y: hdr_resolution = hdr_x = hdr_y else: diff --git a/honeybee_grasshopper_radiance/user_objects/HB Extract HDR.ghuser b/honeybee_grasshopper_radiance/user_objects/HB Extract HDR.ghuser index 1f353456fd3d55086dcb6a9c3e017a189745c1ab..1d84af70e8203fd6de93c12048a8734d5db1d754 100644 GIT binary patch literal 7067 zcmV;M8)W2NS#?xg-LA)7i&JEv6nBbKT#LIG1{h!{K7+eMi@Q_2c=1AU_o4&E2G>G? zDPGE>@80jd``%t>Cnr1Smt-evC4U?oeF(^1)5-y44SjrCI7t6m7_vYJNJakd5%!Z< zua7D7hLKLK1hbp}Ug0PZOAZJH*}1s;{DZ+&mgUj`+JJ%1)*xJY5X9OY>28|n`Hw+I9%gYE1A@;=T$C$Kd@2k2}E0#za9PQa&k zTd?C3ad&Y70G(Z+_8@nBoG0ynD7o=*Ebwu3K-Ml!P9SF+kPW~C0s?>`0I18qJXu2l zd?LI6MNI<$E0?GJe+yimo_{&plTkYlN1*%PuI`_4?*745FhXi&Q&BbND2n*~zHOJQY0w_c!nfkA&^2 zYz#p{dItTwk=JeMy`R3BQIVI?Q&!zg^V8DPrx_+ZpvK@lHTV3!dwL2gz+vi=uGhnG z>pKt0il(l`;_K=|BQ`Q3BN-qCq7C;dp@>Rwu8*r?DJZGQXc==g+s^;EjWS5pZ8Lxq zoYGQJGabXb1PZbQ8utSOuYXUq`HeiXtn(xios0v4N5O_9&D-T!f&v-eu4rXsZwns% z@N&gg1l-}quuRly6JbPs2PW~BPbVzz|9uFO5=@%yJG!0M(~21P7(FaQp|%CK-m$U zPq}$^i1uD8&1gl+j?KpA9JQN0Tb|mMh0WzdMKee7xP0B3FYD{lV47#g6~3znVo|uA ztv!)17b>x|5z8U2jp=r{{nr4O(b0yz>xl)*2fhaE6FdRd$N8O&2GnBI&2)KJ;u48U&ixjUDm5WUgQD7mGJ z7s{y8)J}lOpx~y9?J_u9kd?zPTcfXH4De;-7(V!xQgrGfNzvRbm)-!Wr45vdUq_v_ z%a@|$jQFDV1j(^DL=wMga#4mo5WV6sOm5+th;*$4oi0T0QTi5pu9(EK6N=rCs(ThY z7S(D$FY#70+FjedBbBaAd#Eo2W%y@*=O4O;YO3f12c(L-2w9L*@WF}1LB}g1(sd+J z6+_*H>!#pbIwx(Nl(?Ku_RPh(wE(uyeJ?7=LiGnx(cg5TSNJOz*06p-wJNk`hlnkq z=jmXFji8%J6VX)BO~bz1hj`!P+;!&-q)byXx19OP?o<(;rj=Y<&W~A#;r_xUGz(tW zB`9noB~b4UK@^~O>fv#jhlCLKL7bVK7Oc^Rn9n)v(Ng@Wk47OEKCjnM!+sLq?)hRB z#S@aU1|eaNf74Pgkb}d>s5^y8dp{IrsC+WR=i0wSL*2$%OtVF+RSj$Ynp69ccx!~xq~08sd{Wj9h5KyhUL!AI_Oxm8(fqi1+Un)AreM2e zs_0@uFEnEQKVD*>$leRQRZTQJ1gvnZH$vPOx5;UcNvNt!}CJ|My4L`2V zVoeQqhb$6=&uL!R{h{ZM6r!tTkic~)N=RnaYTn^|z}h*K)gR#1~q?rR_@6`0d24Ddu@==$Bn zAUk5Ql_ioy`7z_@ZyQxn(2%c{wG72mafU$u4mBkYu+2ZykU-F<0h2vN5wN1!YVQ26)Ad1|&;8er>p|EWj9_}^*!w?lhBkrNXif0BhR7qAQ#8d_?A zC@F7D8F<{8VO6N93B=Y+%BqDoXOpd_Vz)KH;`GBKl0Y#*rO_LSYA zumnkF<}!5umDtT(D8cb%k^ybgkMYxl2GR6d&ay+j)c6YtNs9iWXYoo|)dicPEwQ!= zV_~z#W4dg-DEzj&>b!>@585Hm^WRVs#%*+GY$*(+p?MnzqmaaHcBDycyMGut;4PFH z4a3NpK^rqR{sAdOiAP{%5~=U|E4xA@6G+o5EV>4rj>c?q(~>?b~4xK`t)2^5I~CX`ckLya_Ey$C6rVQppg)lEqr z8z1N5hS_a;8zw7J^DxCRUu?K!uSU)Sdt<`5Mq_9S4hrS7Geod@WY5Gnnzs9+m5x1p z&1Gdn-)8jp6=(F*IS-VoM?V1G+v zz=3U+`E(Na>gNGk%Y0an{GfDLJNk)dEb6=&%$+Nvko>@}SP2!vR$xIj%DD}q$ zrHs2EYOCJJxjmM+gBNxCqeP`Knvd@*@67kOfjwp$L5ZrCvFN3KmXn+>`)qyLCw=q6 ztRG(S@X~O6v@+o=2MYnx`E#xEOxP0NU||mSN$V+!o+p#kyA%7*`eY=sKMc@+Ehrr4 z%RLXOMqzG0E&LD$s+Yz>7$L!4y%}jKtl)ixjPA2VXupV?X{De*pw&Qv@H%_-U5R`t z#6tzK6~W8Mj=(NT$L|%vXp&Bw85zfp#iEuW{gIyYY;+3w8^1%mJjhGj=YxaWS?2o- zRwR+6j_=cKUIGLXiX3Xtg-`oC;1I;k=S!NB%~JVoS{gGs_Rg&wmR?H!&}*lF(c@e8gTe9>0}8`-W--xtY7#cfY-sR4dOPVjuj(7LWWHby ziebYyBrJQn8j)7CP_(hpE-S%vqSS00-<}Voj9TU5c>~d`nnEY%@#wBLuJNOcggC@! zXEvw?$f!Fa8rUHPp><2+5CS5J>4CXSy&Vo+;-iEe#a5a(KRP(;Y5hzbH#L?&&~hJJ z+RH4{qfCB6<$OE3MLOj(Yuy-mk=j}0&Rv5ynL&3c*4`)Mr;sX_E#O0!h&2_Se%_~x z=S`|#KBF}dUy4LC;PtSMqx)fr(QO?i4BfFa_E}uh=sXR&peY|YZ_TrzQeO5=2@Nb_ zZ)9m;27TRFu(zp?i08_qDh@cGQ9{SldVdXXTtPQ9a%5|Z4SANnj)JCgBI3fkXMcUy z+Itzy!iU}wiQD(7Gk42a`ts7aSTUaU_&9;ad6_TDJL`APUa~7gn`R^#KyDzGOg)05 z)F^ycgpw!x%$^-P*=UDGhDv>paO>H(fV_12T!F2v7ix}7Vl&4*e&g=IUqBb9@VYfz z?(aGc+#_N7dzEeH7JnB#X?X-v#D|SCxwTmq06KRr!@B*k*WiL~473S`Fx&`bs%prv|CY@6}+NwA`0f;x;pmi0Bb_I~Gb)fcs7j^|^v%+WKgoq}kKNG+Z=bm_;K+=SY zY(q!LjYCN>9=yPX`VVm@!@3*&4k3X(3E zwcSz%gbuE?s9xk&rzYu&Ylvgh&=^DP`fxsC#x_g2%M>wUz>d>3Y(FLeRYjN#-#n{X zwJ+yz4c|w@HI`?~tkTw>mgd^$v^9E3k%Vb%No4c#vj=+^Iyom>ckeIb8EM%Q!3+H@ zO+e5^eG+-FvQ4-}H5$RlYx^@kNmqeECPxfSY9@_%eirUJyGj^d3bPpgUeDEJGHU>e z6UFM1WX-4G>lyDjVhW-33#cS`B@UGqT#)@crY-bFJ`e?4Z$)y9N9OkbdoJ5 z)Q;MjW1v^ktsQ8f%sn*6-HtMJ9$6G3o8+2PKjM=!8jQhMo*w_jllmKyWCdFCGmHkj z7gSCL>A`tbpsiGFbWsv_cLBKuL2wMG(Fg54P$+vU!Uw%LhEp`$=xuVu#n$p9R?=|v zqLa>whzfaV6V807iX{WK;M@ZHmfoc*?@3+ablA6mB32rERE_vpEk+i-Jd-)YTk0xV zcI`J%CKvnIz*3w;z;y|icLq-Yh49#QsCVD^C&07 zei{yCFiV@49$%3g#(6MuI<8?*dSY_%_Yo}?Et9k??VIWlLZJ;;{2Wrp2+F~NwZpwr z_Z`~&lE`|8rl+Q^YtLQW=dCvx_4)Hh)*-t0%+x zGDE?Lx)HHbY!q5xl%~ZqEinDNC z{$^V&s~^vj!@>tsrK5Dlj7!82+DxUn8jP9A_Q_ri1PR|_AVI9u0TMO2Wy&r%Af@no z6i5}5Ho+vP#L1;ruy`2yP7gROkGY$)DiNM(l$P;Qv2UQ#3Y3mosxO%3*%X0z&-Adl zyCdk%Q+}wQTDYUe-+btGS=3Q$s1bJl)hs5rC!v=`OATAj8psreErfo^=$r-}=uawE z70uWpOG!?stkwQ*S$0!?XC(a+h~=`u-hIM9#=fY=PK#{xav+8LGCi|6qOlF-hV0#Y z&&r;%M5x#Ro*bC{#dNrHfy5B0D_dc6UbRM}L6Y`SZEz{lUT+x2dU&udEZ1w>yQ1>M z)l14>t@wTFY$6f6pfJleQ5+3;=-OxE0A{+$!p6^s%+?+%U`5!sQrOW~#Aa2QVBuDP z4g-ew+U~G=nUDbvJ)8vWdwT`sYsT=4HBEj$4rUw&otFkM&->Fzg+R34H{1s|DD|{r zcZpT_@iU!%!IhcoGvMP}F&P?Y8nxB)>ai+hF^I}3dBrge3ej(S)slerr~PMj|w`k{S|Y_$H!yX+^N<)a6Q%WeJ&z1^Gf_& zxW#iamp)`qT0_xS=ISOgYHycJ?XdZ)oOOc1UP|Nd0>NAf6{%a@tKSNP&kblTd>Ngm zC*?kfk$9KfI3@R4duLBs<#}*(%`bow_Dvb9I3iOSGn4C*s@jtC3W= zim-I?wFOg(w|B0Aqfz$`+~zkIKSo?MukKGq5i4_!rYQ!TD@1k$H={?xs)Q#TG!N($ z@G{_LUAayN-1Hr6;@%!{k6YQcpJ7Wz=-G1EV7F8b=Aj_uf$pq#XOm9_U2D@%oxPc< z+T#*fgbl(GCaYa?PIj8Eg{v!4U;Z@yTjxb7CtUquLAn^+VMLmnQj~^U6 z%W~IKylWBBmtA?N7N&iV9l(KYmBIU#%L?_o4I9nlp76A&mCHY8JY+1#WhZZJ;fEBD zzm%rQ4cmV53=0ely z**?oq-Ky}-)(S|Rp2I^jxnRGm$gjolBJX)dz?u3A{W^{JcFXuVrd8$e)r90dlkcYc zi5KnFGX3E%+iN+Vz0=!rsTyy1IwFsRdWy@5G|0&hG8W;yX2)frKYhENab&HIFod~K zQz0VmD^%P-c2Nd!Kc<>rTQeOHxAUz2wGRd#H6N277q44|hA5cV+U8ezE$0QKXiwnQ z_6wU+nyooacbljWQ9rq8lDwLxO#E(?_m&IYXzdPXQfY6$y{_v%pY(8Ae7)*$UQ3@? zSP$JE%+9GKluQw+g|3*JmIb)}&NXsgbdtz3Z+E;(6###r6G`)Y2bZ{Zl5$hUc~1ZR zUfwV0?k?0kI^krwX6-aCqvbAgQre=k`NnX{l{s+aLMUeCopZ<8L-IE|`=dsOp-!u<*!si4pYxw5Qm&9z=Lf|A@v9!aDz0yH;Nh+M(MOXm8Vu3zF5k2cbM#j*d;G;?(D2^gux}dQq%d%t>-=p5jx$O_FqZHe^(fO zEA{z^jlS`_Mc~;ULkr81$&JA}*?ls@_8wjTNoi@oaY6n} zP0)mz%n9p$WQ2V=R3M86M^GxeayF`1wp&-JrDtZZv9dW_353v+pP>A+Va#% zf}9V0`_!`xynI~eH*kNowPQL=m0co&&esXY4e4)w3TLmcAmY7tb3yR|9PJ*YdtNJk z)hiQA)$`!}eNW@8#EvW1uU+^bGP+DGByP3{9)_hFGfe1m2fE!rh6Ig$H`aAZ*+Yi( z684{Y>`nYW{VuPs>(2f9aFayB;`nk1EvUfS{7=46ZRJo0@VjB-qP|qRpX=V}TaU-p z6_+z%+yZdWJ}pV0K%JB+$XrBf{r1)4!u`4jE&>+Uo>rA@y1eu-H@-h^a^n5!u-n%iMZ-cjQthJ;7}YCNwP~ z;d8usiVGjDILY)In)UPlb|os?K9^9DOCmaoozhFotkbbkCb3a}?#i&yxP8vW_42f3 zasAk#W_3&F&ZD==tR1SolVw$)O!D@ojQ3AdgPWv*6};e;1d3#UkU!6gmDSt&<_YWH zG}x2@DnH-aUoQRfAth{-{x$wl9_M=N3sC|K(|bVOkQ;>N)#gw9H<7_3nmVP8WL-5b{;?Cg{bNZeu!xYneIO% zci4z#%CjUKC!U5tzXX>bLNO&32<)YFE*b@|+E-;BNUk*=N6hD<4@%$~KC&5nj8&E6 z6~?7s6&K(2z>-tRfs`pZEIy@GKFInWxoV^e(C713xv}C8Ho~lw4u2fY>@hFJvk>P5 zCXGop)}*t!cnYwo+jo%!Svt<9dMk}xibn1q+mM0?dIE&~DNs`r%A!y@Mua@ZvY=#~ zR*$TlIFIuuYgMaDabF;<2sp^>7yan6q@Vu#`-xJ*SK<^2UJ{PmC9ubvF8o8^@v}7+ z1hbQ0?bUYU&vnUtlPj+8o-o70q@S0u4`Hb3)=7eX=}wT$~mTju1o~FIQ`Ux zvtFPyx@KChHLW#+O%0=kp)PiBi;-=`FJ+7a{?4@i!ja5mUc>P*$SV)@xVj!z|Nl=O-YbBZ7(fRE@oTu(I6WSD4SR zMi*U1g`VbZ0eT1Do&2sJ#v33UYz>8Z{=>mmlI7F_TG|33ATYKZ*aZZ$b%NVMA^+GX zk(42DFw6<+@Js|iVF0iP90vS11Ok+7t!)5uo)Dm;EeN0mgjjp8$)JsMX*# zWIkd+V*(4G5KQ{&YIf%EkX4MBpjeOf<7y9XZaN}rUQ`)y!(_T<-4?B(ruv4V@9SoF zOPjcQ|GN<%tC6AXyPL(c*IS2lPdV3Z(z5m3VId{RW@?i$8Fo`c`jYcd6wD3($0J^+ zU5V^??_bs5edD0z!HO3AmY5_1LcgVJEQIwz>d`S^m+PHhG*FgW;|7j8qm&txk1fbU zQ`qKjtpVzyxL;_+tQy^9YY&jtANo?j20p0kxQ{0*YXo2EWT|wI2RJWYiWIqaF|`E2 zE=xo*xGjbnX_~o*4=E*>=)DCNKU)*J4!qh0TKdD}78-lWKG z?2Bhi1wje`!6c%M3%f? zI_PUvMBocnQDX?Z+|aBfT*bAxkV@{hVWm#;HCqXRZXb

Qt9gW>J{SQnaq{ILHz; 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