From 171a1aeda71da109bc99ef629cba93529f74a69d Mon Sep 17 00:00:00 2001 From: fabrice <fabrice.allain@pasteur.fr> Date: Mon, 11 Apr 2016 13:53:41 +0200 Subject: [PATCH] Bug Fixe: N_factor convert to int during mapfilter step. Float values wasn't taken into account before ... --- ariaec/commands.py | 2 +- ariaec/commands.pyc | Bin 10416 -> 10429 bytes ariaec/conf/aria_ec.ini | 1 - ariaec/maplot.py | 52 +++++++++++++++++++++------------------- ariaec/maplot.pyc | Bin 5130 -> 5288 bytes ariaec/protmap.py | 32 +++++++++++++++---------- ariaec/protmap.pyc | Bin 47987 -> 48260 bytes 7 files changed, 49 insertions(+), 38 deletions(-) diff --git a/ariaec/commands.py b/ariaec/commands.py index 4e80c19..866b10c 100644 --- a/ariaec/commands.py +++ b/ariaec/commands.py @@ -195,7 +195,7 @@ class AriaEcCommand: default=False, help="Use secondary structure index") parser.add_argument("--prefix", dest="prefix", default="", - help="File name") + help="Contact map name", nargs="+") return parser def create_settings(self): diff --git a/ariaec/commands.pyc b/ariaec/commands.pyc index e58e41ac9f3a8f238b0b9abc9e6e13b9066d6d5a..6cb3f4ce9085f01222fdf50166463871c2fb80a2 100644 GIT binary patch delta 70 zcmV-M0J;CLQN2+J1M>|Erd<nHu?UVC0brBq5G1p^88HC~WC{Re3jl)w1helNwE_<i c0000(Z*Fv9V{{;GVQ`bI9rXbXv!Wgj3KH-Y$N&HU delta 57 zcmV-90LK5lQLs@61M>|EFj%`*u?UVC0a}yk5G1p^88HC^g8>7x^%}JT2nhfH07hwS PWs|ub^#KR7uO1ExFvk*f diff --git a/ariaec/conf/aria_ec.ini b/ariaec/conf/aria_ec.ini index 7468bfc..9cfbf07 100644 --- a/ariaec/conf/aria_ec.ini +++ b/ariaec/conf/aria_ec.ini @@ -131,7 +131,6 @@ pickle_output: no [contactmap] ; -------------------------- Contactmap parameters --------------------------- # ; Plot settings -n_factor: 1.5 save_fig: True size_fig: 10 plot_ext: pdf diff --git a/ariaec/maplot.py b/ariaec/maplot.py index ba351a5..0dfe9f2 100644 --- a/ariaec/maplot.py +++ b/ariaec/maplot.py @@ -37,13 +37,13 @@ class AriaEcContactMap(object): if not self.settings.contactmap.args.get("onlyreport", False): self.settings.make_infra() # ----------------------------- Input -------------------------------- # - if self.settings.contactmap.args.get("prefix"): - self.outprefix = "_".join((get_filename( - self.settings.contactmap.args.get("seq", None)), - self.settings.contactmap.args.get("prefix", ""))) + if self.settings.contactmap.args.get("prefix") and len( + self.settings.contactmap.args.get("infiles")) == len( + self.settings.contactmap.args.get("prefix")): + self.outprefix = self.settings.contactmap.args.get("prefix", "") else: - self.outprefix = get_filename( - self.settings.contactmap.args.get("seq", None)) + self.outprefix = get_filename(self.settings.contactmap.args.get("seq", + None)) # Load Sequence file self.protein.set_aa_sequence(self.settings.contactmap.args.get("seq", None)) # Load secondary structure prediction file @@ -70,11 +70,13 @@ class AriaEcContactMap(object): logger.info("%s map set as reference" % fo.filetype.capitalize()) self.refmap = fo.mapdict self.reftype = fo.filetype - self.refname = fo.filename + self.refname = fo.filename if type(self.outprefix) != list \ + else self.outprefix[idx] if not self.settings.contactmap.args.get("nofilter"): self.filter(fo.mapdict, fo.filetype, fo.contactlist, self.protein, clashlist=fo.clashlist, - outprefix=self.outprefix, + outprefix=self.outprefix[idx] if type( + self.outprefix) == list else self.outprefix, outdir=self.settings.outdir) # else: # Use only position filter @@ -82,7 +84,9 @@ class AriaEcContactMap(object): # self.protein, clashlist=fo.clashlist, # outprefix=self.outprefix, # outdir=self.settings.outdir, mapfilters="pos") - self.allresmap[(fo.filename, fo.filetype)] = fo.mapdict + self.allresmap[(fo.filename if type(self.outprefix) != list else + self.outprefix[idx], fo.filetype, + fo.filepath)] = fo.mapdict try: refmap = self.refmap["contactmap"] @@ -99,7 +103,8 @@ class AriaEcContactMap(object): if self.settings.contactmap.config.get("save_fig") and not \ self.settings.contactmap.args.get("onlyreport", False): refmap.saveplot(outdir=outdir, - outprefix="_".join((self.outprefix, "pdb")), + outprefix=self.outprefix[0] if type( + self.outprefix) == list else self.outprefix, **plotparams) if self.settings.contactmap.args.get("merge", None): @@ -120,20 +125,20 @@ class AriaEcContactMap(object): self.allresmap[mergekey] = {} self.allresmap[mergekey]["contactmap"] = up_map - for mapname, mapt in self.allresmap.keys(): + for mapname, mapt, mapath in self.allresmap.keys(): - prefix = "_".join((self.outprefix, mapname, self.refname)) + prefix = "_".join((mapname, self.refname)) if mapname == self.refname: + if not self.settings.contactmap.args.get("onlyreport", False): + refmap.write_contacts(mapname, + outdir=outdir, + scoremap=self.refmap.get("scoremap", + None)) continue - if self.settings.contactmap.args.get("onlyreport", False): - refmap.write_contacts("_".join((self.outprefix, mapt)), - outdir=outdir, - scoremap=self.refmap.get("scoremap", - None)) - continue - scoremap = self.allresmap[(mapname, mapt)].get('scoremap', None) + scoremap = self.allresmap[(mapname, mapt, mapath)].get( + 'scoremap', None) # if self.allresmap[mapt].get("contactmap") is not None and \ # self.allresmap[mapt].get("scoremap") is not None: # Get top contact map/list @@ -143,10 +148,9 @@ class AriaEcContactMap(object): # human_idx=True)[:nb_c] # elif self.allresmap[mapt].get("contactmap") is not None: # If no score given, use all contact list - cmpmap = self.allresmap[(mapname, mapt)]["contactmap"] - cmplist = self.allresmap[(mapname, mapt)][ - 'contactmap'].contact_list( - human_idx=True) + cmpmap = self.allresmap[(mapname, mapt, mapath)]["contactmap"] + cmplist = self.allresmap[(mapname, mapt, mapath)][ + 'contactmap'].contact_list(human_idx=True) # else: # logger.warning("%s map doesn't have any score related. Can't " # "define top list related to this map" % mapt) @@ -165,7 +169,7 @@ class AriaEcContactMap(object): # TODO: elementwise error with compare method # Write cmp stats if not self.settings.contactmap.args.get("onlyreport", False): - cmpmap.write_contacts("_".join((self.outprefix, mapt)), + cmpmap.write_contacts(mapname, scoremap=scoremap, outdir=outdir) cmpmap.compare_contactmap(refmap, cmplist, prefix, diff --git a/ariaec/maplot.pyc b/ariaec/maplot.pyc index f774d93fd4dbee644841d77ba4619ebb527daa0e..121f8e6c3cf140d785e1ede07d7763fb4714676b 100644 GIT binary patch delta 1887 zcmZuyOKcle6g_WdY>)rHeC(ugnl%0;X+m29Ed=pV7IcBsx=2Q)Q3*@}nS@DfkE&Ff z!AO?c3aNnfR2B%twqjGE?h%_V*sx&51|&8}h+SE*;&bmCJ0DW{&C`4Do_p`PpZEMx z?S9?V|4hcd{v_V~#{5-J4FSl~r@tl8>BztI&$^G{#&HL*4cH;r5!fcI2zr9Y6GY*@ zXiG!;@iE0PEE7GA&#x!o%)m_^!$e_$F=YP^bi&EMjWH3Gh0qDHCYM4ty2`zETr<Tw zR$5t1!^#Xy55p$M85FJT;An)PhS<9#+);Iuhh1RpmDOc&O@mKEho`~Hfg|}`b14Ny zSb2E5oDEmI?xfO7u#$TcTRPgeHBW~<1t-=Qk$#aQorZ&f<NX{Cv1Vsf=iHtmtFTX~ zc}&eS3Qs`{t8E$92oAgKvQHxRkH-25rdo8ueZN)^6IHB&o}ml!9@uAW^@yE5^_TT0 zQ0Jq7O${45mRplr^Jb92i7u=HioSuSGY7-1V=IRCm%0#w6EzE~$l(ch4)#2(F|~07 z-nb2HP>sfM3$W&}LLfgh$V~-)5JJ#vVbyDstlM5PnEOEw%Bx1Ko1kNl&Q60O_Nc^1 zbJR~!S*a$tlm{vDLbwZ!Gjr4~tdsrrYrh^kE@HY7?EMZef|bjwzrZ))nQ#}BnJVl> z^r(3$LUOhhMtes&C&5#UWA!M!s0vK=n;*m8y?td_H%qZcM!(ml<YhB|q6#lToy*xy z!b_s$#ZVfhMa`O)ADY8m0;I4^2SIgd!|3JKBZEe>wPVm|QlNVU)hh#%E2|Ohnhrk> zKLMXQ?G)@Y@Pf-ejSY1H{mj7$x7%;WauSlOhS{}3T!*s^?w2BH;0YD!%>IqmM9T`% z=;xiN$2mMN%?(!Mr)7ZkgGT!wLZu-$h;(1~94WKNJ_oA|_h~AE@ad`&rzLnfw(4cM zJ-p0-Uq${LEj(9MrViQ)R)~w#c@XZ`{uvcOjk}AUBqut+SL9->#9`+KVFxZ$ANGVM zSnw<}@Q4R_tt-t5UK9h4L85xPE|}+e#q%)3;DHIa9l8qE?0`dPir+O(+ANdimZmo? zNN|(h&?~ih`ETrEAwn<urn9lRz20d=Y3=GXb=F(*YP|aJZoDi+O+HNA6Yt22$rt6P z$;FT$z;*d;^3BXVts19!TA-<RLDo}ehHAV|(&QR6t+n6nH0shxP2}0|_PW#H|Bwc6 z8hQCu>Oq!a?#<nGh2P%nxbn4hZM?#ODo;6<n=~=W-8J_;*-KwCS<BwpbmWg|TMWq; zGUr8A-pDvPH%yZE*BICsr)A@;{3CN~;s;tnD~Y(^Z(L-wahgkFN*fkw`Ej<KEs4A+ z&@U%+Q4^WytMa?-C>5YAf67kHO=<-#O&SRirY|YN+K5QR#z-e`7U(Q3VkBQM2q7!E fm&C68Apf@fKKDxa3X^zVX7kN(i<cLqlmFsBM4eho delta 1719 zcmah}O=x3P6#njeFE2kY|LITCrs;2*q@A=gwt_PR6sd|Js1c0FjIrf4LtopB`9UhY zm_=|DL2$T?tHG5Ecf&AyK@<dWF)+9-3T|AApj*$m?`7H|BDp#5+;h)4_q*RY_kLaZ zv>ej^j72{D^5-`ngnrfI830N8>+cHmbosab#qbx{K{yurQG5b82AlvKvV>sM07e={ z;$KT9>@Y92#;eUZ+AIH`gxq+S1e~OoQx*c22+6fTUc4$_3cNpz@piW7b{<SZ*}bR) z?9>?2Bpd>hLe5T)O;hA*h?C#YpH`*}oGeH0+<1iR8U{3UxEkyX4mEUJTAv{0U{Ao+ zWz8&Et`29OLz@fob8})i$<e)UHHN-OPH^%{&M3L0HX%$?YPt;D+cpk6JBGA^k)ew= z{9sO0RQkM<GfZTy6Vn<Wcn6z<okMP5pwV8(zR_RBVFcZIT?oNoOR#4cljxV>RAA4l zNn7BpkAW8Pm&a|uzJwie8U)Tz49u}kRW?<X7dW>2l`dZ~vYSy-T%vuAvAaa<wBH_# zm=3h)SKJNYGiXBIF*Cy@zfHQ|8SHhwXiJ=X5X8yvaD&AEDFz|9A@tW3j3S&%7*UTA z;tX<!Cc3A($Bk2EKaLK=4J)8IHI3lp?uB6aO=#xow&B-snZw*D!<?dM;WHhj{$N65 zaAPRA5fr9LDcdVD6`mYYDdMP+!S{|U?^<WupwZ}@>NFZuypyY{1;(&<Zj?3dCkun< z1)5w-Wm-T>SC|ucV&R5M2Re@pN;hUp-ZMk;x)B<7j{{8p#>HgnFieqGk)n#5Q<M~+ z+)z!N<)q_SJ_k9z$78R7cYDq+D^k3cE8^N&7~7pq*sJJYrV5Ec8jS1;+!W_?liV3D z^}L6vB%6_}D3hF8?p00yU{5KG749Q?D%#=kfmh^%NMZQcpj`iXR&NiLeIzu&u3HSj zB_Ooznoj~LIS-_VWTfkYfo?0H=OHePclR5A3%2Tf+iOLyYrPw>&PC%6r&oknrO}{= z@Qrd+-m+d!3wl4BeVt@)zf%v>S3a^zR*;l~cI)71zxVJjYgLGfoQvHRP5ChPY>>U* zv-jj5vCFa)e>+*Fd3}S$CW-RX@=pB93l*NnNc0U7yWN|;`WnvyBwpM->h1R<q#f@b z?l)<p?yhokZXLAioW9O)dp#x1#C`cgq7*Ojw9Fz+LWYyC$-U%($jG0P&rqcTsdlhF z%gbBxqtxxWA4!E)5SHNotVn8E5vFfR)5W4Jr;D+K$kH!O)?{!&zLTC7lkz0JSj}q% zEg?*8mLg3N6%ms2k!g`6EgZ_yEFmJ~v0;#h{677hIFk8^J$W<py!ko<sLA`8hI!2D JH5r|__Yc#-JK+ET diff --git a/ariaec/protmap.py b/ariaec/protmap.py index 9074655..93a6c4c 100644 --- a/ariaec/protmap.py +++ b/ariaec/protmap.py @@ -200,11 +200,12 @@ class ProteinMap(Map): minticks = tickmin(self, shift=1) # Nb graduations self._maplot = sns.heatmap(self, square=True, cbar=False, - linewidths=1, vmax=1, vmin=-1, + linewidths=0.5, vmax=1, vmin=-1, cmap=sns.diverging_palette(20, 220, n=7, as_cmap=True), xticklabels=minticks[0], - yticklabels=minticks[1]) + yticklabels=minticks[1], + linecolor="grey") return self._maplot def saveplot(self, outdir='', outprefix="protein", size_fig=10, @@ -269,7 +270,7 @@ class ProteinMap(Map): protmap.__class__.__name__, self.__class__.__name__)) return None else: - cmplist = protmap.contact_list() + cmplist = protmap.contact_list(human_idx=True) ymax = len(self.sequence) - 1 if protmap.contact_flags: @@ -286,19 +287,23 @@ class ProteinMap(Map): color = pal[i] mark = Line2D.filled_markers[i] for x, y in zip(xind, yind): - self.maplot.axes.scatter(x, y, s=10, c=color, - linewidths=0, alpha=alpha, + self.maplot.axes.scatter(x, y, s=8, c=color, + linewidths=0.1, alpha=alpha, marker=mark) else: + logger.info("Contact list: %s" % cmplist) xind = [x - .5 for x in zip(*cmplist)[0] + zip(*cmplist)[1]] yind = [ymax - y + 1.5 for y in zip(*cmplist)[1] + zip(*cmplist)[0]] + logger.debug("Xind: %s" % xind) + logger.debug("Yind: %s" % yind) color = "red" # width = [0.3 for _ in xind] # for x, y, h in zip(xind, yind, width): for x, y in zip(xind, yind): - self.maplot.axes.scatter(x, y, s=10, c=color, linewidths=0, + self.maplot.axes.scatter(x, y, s=30, c=color, + linewidths=0, alpha=alpha) if save_fig: self.saveplot(**kwargs) @@ -1239,7 +1244,9 @@ class MapFilter: # Contactmap always filtered # TODO: could set a treshold instead of n_factor - nb_c = int(len(mapdict["contactmap"].sequence) * int( + logger.info("Setting contact number with treshold %s" % + self.settings.get("n_factor")) + nb_c = int(len(mapdict["contactmap"].sequence) * float( self.settings.get("n_factor"))) nb_c = nb_c if nb_c < len(mapdict["contactmap"].contactset()) else len( mapdict["contactmap"].contactset()) @@ -1292,8 +1299,8 @@ class MapFilter: for clash_t in sorted(clash_dict.keys()): if clash_dict[clash_t] and raw_contact in clash_dict[clash_t]: clash = meta_clash[clash_t]["flag"] - meta_clash[clash_t]["msg"] += """\ -{clash_type} flag at pair {pair_nb} : res {res1} and res {res2} {clash_desc} + meta_clash[clash_t]["msg"] += """ +{clash_type} flag at pair {pair_nb} : res {res1} and res {res2} {clash_desc}\ """.format(clash_type=clash, pair_nb=icontact + 1, clash_desc=desc_dict.get(raw_contact, ''), res1=contact[0], res2=contact[1]) @@ -1305,9 +1312,9 @@ class MapFilter: else: op = "added" ctype = clash - meta_clash[clash_t]["warn"] += r"\n/!\ Clash: {clash_desc} {" \ - r"clash} flag for contact " \ - r"{res_pos} ({res1}, {res2})".format( + meta_clash[clash_t]["warn"] += "\n/!\ Clash: {clash_desc} {" \ + "clash} flag for contact " \ + "{res_pos} ({res1}, {res2})".format( clash_desc=op, clash=ctype, res_pos=icontact + 1, res1=contact[0], res2=contact[1]) @@ -1315,6 +1322,7 @@ class MapFilter: titleprint(out, progname=__doc__, desc='Contacts filter') for flt in ("nd", "pos", "cons", "ssclash", "cys"): out.write(''' + {filter_desc} {hd} '''.format(filter_desc=meta_clash[flt]["desc"].capitalize(), diff --git a/ariaec/protmap.pyc b/ariaec/protmap.pyc index 61242a0e543a28f9c03da61b0534eb27a1e2a040..ebcc12d81cb6c0cffbdbad8743f6fb9a35d0eb05 100644 GIT binary patch delta 5023 zcmbtYdr*|u6~Fgec41i_yWlQQ1r%_70KO6*RZxP@K$H&^jUk(bZ-Iqnf!$AnVOJC~ zNfeCeiB=;<Y<x5^G3Y{k(55kInwhpqOf+%Y#KdWuOxljqHl~?8+Dy;69|Fl_rhk-W zf82ZSx#ymH?m6e4%dIb^hrgD>b+@7;V>g@5gnv6e>Pf~}fU!Ep`~Wc%=5GpTN(57M zOffPgk|`#phOs)NL@_0rDKSimWr~@p2G%J_iiHI@Q{q^X5-(ahY>i`zm349>N_8;i zy)C}XZiC(l%Z)X-ApYDafR87*5>z9$J#Me7s?pQv!=)Ij@wv9ZinOaqHsaG6rDz@} zCGt3K<whQfpV-js^y{2&3t1-4;QUIca8eoPpThpk41NgCXYSx1hYGVIIPZnKvpe;@ zDCBQ&CHocr73`n<JN|8`(%#1TcktnqRQ_l9cFLxEakr>PC*wgqQzL~rOt=H4gt5e+ zfi_kb#eDB_HF~&R7lYiy5e#Q)EUPm!MVlE;#7>C?BUo#)q*~zFsVVJItf(T51x>?~ z116Rfh-CE=^Ic}xxnE+<msv21p=H!K7Kmcqj41||G};McNeM+2W)?&R@DRdTB25~{ zgE8NqgnXluPy$XG$J&FjEP%U+Wr=vQ2{dyh51LsZf(48$DQID8JgXb0b>diSsn8;Z z1<bUQqKd`rKHN|=BQfYGR#s<YbqS;hiX8EhfMwK00_Z)+9EbDuyt#cRFK@oc%Dw+$ zY9gykBKHaCnGG$)hqtUByJfBO<6G8Cqgx)~qb-j}5L^D<j@!kS7f85eT0fbQ*T!h; z4--&;StFnyqd{8l{(%h^{4X1z)8qMSHZV|_pba{uFy7oPjV}-2QQwfrcquFp#&$8r zF)S3u%IsD+HGP_1wxg56d()>SMqoqb2vxEVvF6Ex^mCyA%sJM4zZpq)HF{NNm74Bx z`_%>MS$;oF+2HmnBb|-I9aV=TK9}O2fPd|&ke9PCpQ1r7Mi7G@msh@nDAfdq35p0% zRff)vT2NFvnw%b&s=DMOa4aV?dmC}=B#v+df0Yx5d~z;PR}r)k9E6W@Ci7Dep8FS@ z5%=S#A;u%63=HCA9wx<sA#ak^!gYKWe(&TK{1|kpJcAoTcjlEu@}AJc^Y`oc>yWbO zgq38fdV~jkZdBw^Y{`$odyA%;&QcSz7n8fdl)sAahV}WA67~?G2SMoA<Y`nDw@(J> z$zN=KjmCaMaE;(SxRGx&y+h3_1XqC<SozzKP>^NXPlQ_p`(Sy&7Ctw0so*V%zY2Q_ z_pJGV=m`iyu_m9}s|us~Ri9i!V;>TbdF>Wz3HUd*R1-p#c8qk2vZgKg?(%U1G`HUm zcNHb@Uxzjo9psx{MP4^Kgd4xiABm8qq|<NioN76eOiOkVG&?2JF()B3e2Qp?2uRm4 znweqsvJ~C}^~+KWR+J;#pnF*x{}>{cm*;OI-h%{51VTtJwT=;-BybUsE6J}A&?(C) z1QR%9Bv$QRk!-OTd2DP{L{zj<7p03c#pZ^lt-!NS=kRh$GmnD4;(neEr6mE(>2H=4 za-Iy9)xZ-%$5-ohhKVF>67<VQlly4}p?@Y3vk0;Y4nuv}bbbU5l;xSO6O-_VHh8CO zRlFS;REiFt%kL0|9)i5J9eg~TUHg(DK-@uSS=XwkJ*^DgTDOez0$5P)v@Rym1qgNu zA?@UfpugP9*M!nGc=h}^lo~fe+K>TmR)oV5#~X0zerMVWnt@*?=OAdUNh7V}u%!52 z{c;>{fj-Bj=vE2O4!_L6WP8Z_Kq41|J<#Rc6m~bVL5wpYY?fTY;ZpfVF$Pdo`47=L z3LC1nSXUyKOkgKD@+#`Cf={a^qE(Gb?#4w#5D`rZX#qZtTI&gB5YYWE%LJ2&*+L*J z+lMsa&kfEd&8kajERR^*phsC`K2FUO1i~MF0@sxWTQM=+Lm-ArsI{7dC1tIixGLa+ z3-}Bush-W#V0(4ENjTIzAqCPC6@5C+_!1&&GI$|Ot+|Ov{_z5ox>NTSPnQZc-#Lr@ z<5r<4>8<U5B1xGmBIsFT3_lO8b)9@L^zXVho->scnT{Z846n1nWj7GJ5HadrM~F5H zj(KL9=hBRM1Vcoe56x-=AA+)mZOIFXD(osOtho{~B|vG5Tlmv&VUzbW33q;}d;=6% z44LSJwHWj*SMjLCu`@1{!?-DYwa6`m*BdA2l_JqjM9uGn87rtOtXxB**c*1QF{Zr? zElu;1(?~Uu(?z(rXreIjNnzbW9DWvNHeZoyHA8*^t-c=a2BSZX*TNM4PW~AT__r<6 zf{%o`2u94OX*L>#!ii=POErQPFPf?T4j-u}^|eU70bQG4mOR>m|AAk%+`~P1s_{}C zhn(6PiRqyG!2^;`2idK=^d6k?Rp@-{ISwnfKL9<oVcZB;AKJ>jkk?TQLv67<4t}|9 zHhkB125J3)7X4<Nk_^*=e=rDZoq))#sb(({HJj0eDmRJQPi>9knQ&%nzqtvCn%juP z+=6c7(6=ISHoV*Jh1?D|+{o2OIXxbKRin>^QLqqK4VW8$Y~B_m7Qyq|*X0ypON~LR z*Z3MYH&t$NG`Q5-M#UaR2O(4vzAjX1MwQ(9c$`rThdT;*GhFXbMSm@<?@Y@OXGwd* zY8#z~?qHuxD$2B;953>$z5(0eq3=2ibX<i6+r7eK&2Z+?FRZ6<ns#8L*Ru*2AKSy# zQ1On>Iq!git_iCsv5Qc5R{FJDOQ_d}WRZ03lteXe+(8QqZ|uSr7Rz{(c^dsLEx-1{ z$*za6FrU~R;s#=nyW#y^>v5S`kNdG6Kk@j)B@|(z>i7E{4NlePZvEksblKbKj{M=0 zdr4v!7<Z?ktSP%6=KDgYclSxW3s&!amtTS`$TfBl4;4I6`oz6_JDh`E{4y+kqS~^Z zMvfwAiTP)kl<KPaHJG$-npK3o$c!SYDf`OTp=#eGgSbx)LHE9S$$g~K0t7pquRO*S z@Zr7#5(-=rxfXuEKZc)u`u+XJxSM!++>Z2N)o&h|$-5z~?+KB_zX_f1dtH|~qO#_W z1GG4S)*YnoAUu6+VyPIR{usvps~fT%kLIQWw1U`YFXW!ctQ6T$SVY_gbR2RY0c8|% z<SV_3t97Z**XWZUCv`GtbkrUp5~%H%S|neq6^F0kUKlv>oM{`4O`(}XP=E3w|7$4w z=d*c=53N?iSkOonPcTv-mezWvq=wXIVx^f8tPY>1_};{N8ee7-!xtyMCB%C|#Ts8c z((nnIRA(W&8V5f;mDx`H_~BPuJ(dI@1>%`Eek}X#;ULP^p0hT#5DN%e4&OVHwgf$R zNtk#r(Hn*c^zKb%^+x8KuN$6`_`MnJ$T2!)m?eqU5qjSiYNa<KNy7nrd}-u`UVK9% zIeBCe@9jnKJuyZtWEfpTn^a~Wj~4bHh6~TV#<xQI^VvxvF6ngqR4!)u9o23RC6*3& z>G`~MJ#-*ckct?s@;LpqR66-5BdH8CklS06K3uz|dp9>!x_s#my4BirmF~Aj57woA zQFD16)%Z$52kFL@z;?R6nDU&Ta_zb%#i_cGF<n8)m{{+z(^Fro??mPi)yhufSEF!1 z)6b!9X>$4HHAuzKeB*Rditw;G$RN_At5tP*6<Ij%I^ZwZ+DGbdc?pRZH7td%T#8^9 z(JoTIaNIN+h@&Mb*Sft;zD8B}I_jq>;2*BjiScdHLKt$0rf6vpP32JBRjT|F(R8rq zg_-<{s7BI;^f3-c72YKd$1`{n_^V+~|I#ocAID)!e+J(kI@CWy7yk>P1A&3$)0F{7 zAF+jtd?vTUx1lV|Xc+@{gi-83#UlyOJdl9Lx?^A&kA$}diXnAyn&Cd0TmdD6$$Bc! zBcWli7*p`Y!7O~a+!)N#TLsnl;xgV2YhE1RE@E9g5yWl&327jXK*awC)TJHD4-otl zLEEsi$*np)?uT5$==L}o{UfbSHW9WIPcY4F!eUgoXhwCZPKR(V*~T%O%9m&g9X7h* zH!sfT?~CjB8dAT81LrpJF!<u!iu|j@C6-7gR`L*e4?!<MJ;51*Hwi@j^AWYgBjuWQ zTLzJ^5&m@k4!#-^FRWbrC<$^B*XPtaO^`+KG|`SD2#08LdKIT%o<;p|9QM1Mm5n~H z9Dt)2;_;tPgBRvvU2*fm6rK$U7uWL};Jr8}g=$%uN-mkoAekx~nXYR&NXk^=17f9V PwQaz5r!CPI8!!JC(z|;5 delta 4810 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 R*ecVXPal<@nVys?{}(bQN4Ed~ -- GitLab