From 23a7d4bc2f4d21ff42844fdb83ad6b7d8a0a6795 Mon Sep 17 00:00:00 2001 From: Jenke Scheen Date: Wed, 17 Apr 2024 08:36:47 +0200 Subject: [PATCH] surface coloring MVP --- choppa/.DS_Store | Bin 6148 -> 8196 bytes choppa/render/Template.html | 19 +- choppa/render/logoplots.py | 41 +- choppa/render/out.html | 919 ++++++++++++++++++------------------ choppa/render/pose.html | 4 +- choppa/render/render.py | 113 ++++- 6 files changed, 596 insertions(+), 500 deletions(-) diff --git a/choppa/.DS_Store b/choppa/.DS_Store index 49630a9b920a6a6e45993ba5e4d576746fe48524..00f01af9af10c7c13cfb443637a41d1cb1fdd769 100644 GIT binary patch literal 8196 zcmeHMONHTqXo54Zyy#y(cn!krJhTgKZj!cy^ zK;nVK1BnL`4h3_{?SjW$qrcYV!9FixaLBMxC9Ppw!jRn%$r_sB_fA*<=1>HY!Br zu(uE#5Ussccf2sujjX28&99s)aJH(x9-m$cgH>dj9dodR{?9RS=KYPYjVR% zpZq3K*HT^6x79Ui`W$n3WK_=^^DFe=dnY2t_rhU22ubFRc_A%uu;hFD0-N`blzg{5 z?uB`ysZ?-FWw#L7J+7N|9<#&I$UYJAyRpy@qCR0b>JXN<qwG{(an5*<$NXt@DAjZUh?Y%QHjJJ}(Mm__$!`;}c`f1)0Zma5gGKL7rU?z4WZDN;oP(cCjIUu)#$kvPcpuSi1YRdl@r%!h^_Bg zrV{6kS+Tl&1*yA?*-}llcc>I!h9Xow)wxciC^G6|wS5z*kc!^DMWc8!>SOi6Z5j`8 zg9$yAYVV_w)c;P^^Xvlqj9p~YMAe_!FYGt=H&h~M3TZ4C6m7>Ebm2a1#unU<0c^(( z3=&y$7{V|{F-BCih^$B9AV7$S2>Upu@C2U1(?r)7@FHHq%Q%bIh`4X!Exe2KxKKmH z!I?zFN3}$(WCPFjy`$7#CGSi~qTR9w>Ljw2=l=sY|Nei=<0rtx1BnN2e-EI(KikiL z0^IBk%X95CU60Vk3%8q+Qb4HUCcIsw<0P;CVMzDsxGxjSDJiLk%K!c$K>UhL&i~~6 K7xcShtA7As{2wR) delta 179 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{MGjdEU6q~50$jCG?VE1GL8J5ZX0uqe7CTj>3 zYD!gC>lzvunCmDQT3FQTC{$Y-8R#gOn3>epa&m|&>strKXXoVR<@ZcJARy1!yLq-) z4&%m#oh*ylIXDEFfrbNt05_0u1=+K)@H_Klei=&zkT4?y0~3^H0ntDX$VDJWGHj0L HnZpbK^Qa+N diff --git a/choppa/render/Template.html b/choppa/render/Template.html index 9d8e7b9..cc197e9 100644 --- a/choppa/render/Template.html +++ b/choppa/render/Template.html @@ -165,22 +165,9 @@ // set protein sticks and surface viewer.setStyle({model: 0}, {stick: {colorscheme: "whiteCarbon", radius:0.15}}); // define a coloring function based on our residue ranges. We can't call .addSurface separate times because the surfaces won't be merged nicely. - var colorAsSnake = function(atom) { if (['155_A','196_A','186_A','227_A','284_A','33_A','93_A','6_A','180_A','191_A','142_A','153_A','222_A','279_A','156_A','1_A','69_A','233_A','244_A','263_A','198_A','285_A','121_A','277_A','94_A','228_A','45_A','18_A','220_A','37_A','116_A','261_A','67_A','78_A','212_A','280_A','272_A','21_A','234_A','264_A','81_A','160_A','283_A','24_A','84_A','106_A','46_A','259_A','8_A','19_A','232_A','243_A','224_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff8a6c' - } else if (['166_A','117_A','128_A','139_A','22_A','79_A','235_A','41_A','52_A','197_A','109_A','120_A','265_A','3_A','14_A','82_A','216_A','295_A','44_A','189_A','63_A','208_A','150_A','161_A','306_A','112_A','268_A','74_A','85_A','219_A','287_A','36_A','181_A','115_A','238_A','143_A','66_A','200_A','211_A','28_A','173_A','230_A','192_A','203_A','9_A','145_A','290_A','39_A','118_A','252_A','175_A','58_A','80_A','214_A','137_A','271_A','282_A','293_A','31_A','205_A','99_A','195_A','206_A','168_A','110_A','42_A','187_A','140_A','151_A','170_A','53_A','64_A','266_A','26_A','122_A','34_A','56_A','113_A','258_A','269_A','209_A','182_A','124_A','135_A','231_A','154_A','165_A','299_A','10_A','146_A','291_A','29_A','40_A','174_A','242_A','2_A','147_A','59_A','215_A','302_A','51_A','176_A','138_A','149_A','294_A','217_A','111_A','245_A','256_A','5_A','179_A','207_A','218_A','130_A','275_A','286_A','13_A','248_A','131_A','54_A','210_A','267_A','16_A','172_A','141_A','114_A','171_A','76_A','133_A','144_A','278_A','289_A','27_A','201_A','95_A','240_A','163_A','57_A','202_A','164_A','126_A','183_A','194_A','262_A','11_A','136_A','98_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ffffff' - } else if (['105_B','116_B','261_B','10_B','67_B','78_B','89_B','212_B','223_B','146_B','29_B','40_B','280_B','174_B','185_B','291_B','108_B','242_B','2_B','253_B','136_B','147_B','59_B','70_B','204_B','215_B','98_B','272_B','21_B','155_B','166_B','177_B','302_B','234_B','167_A','117_B','128_B','139_B','264_B','196_B','101_B','158_B','283_B','294_B','32_B','43_B','25_A','100_B','111_B','245_B','256_B','5_B','62_B','73_B','207_B','218_B','130_B','275_B','13_B','24_B','35_B','169_B','286_B','92_B','226_B','237_B','248_B','131_B','305_B','54_B','188_B','199_B','210_B','93_B','267_B','16_B','150_B','161_B','172_B','229_B','112_B','123_B','180_B','191_B','65_B','142_B','153_B','278_B','289_B','27_B','84_B','95_B','240_B','46_B','57_B','68_B','202_B','297_B','125_B','8_B','19_B','30_B','164_B','259_B','270_B','87_B','221_B','232_B','243_B','281_B','126_B','300_B','38_B','49_B','183_B','194_B','77_B','251_B','11_B','262_B','145_B','156_B','269_B','213_B','224_B','96_B','107_B','118_B','175_B','60_B','137_B','273_B','22_B','205_B','79_B','90_B','235_B','292_B','303_B','41_B','52_B','186_B','197_B','109_B','120_B','254_B','265_B','3_B','14_B','148_B','71_B','82_B','216_B','227_B','110_B','284_B','33_B','44_B','167_B','178_B','189_B','72_B','246_B','129_B','140_B','151_B','63_B','208_B','91_B','102_B','25_B','159_B','170_B','55_B','132_B','257_B','268_B','6_B','295_B','74_B','85_B','219_B','276_B','36_B','47_B','287_B','181_B','298_B','104_B','115_B','238_B','249_B','260_B','299_B','143_B','66_B','200_B','211_B','222_B','134_B','17_B','28_B','162_B','173_B','279_B','230_B','241_B','124_B','135_B','192_B','203_B','86_B','97_B','9_B','154_B','165_B','290_B','39_B','127_B','252_B','1_B','184_B','58_B','69_B','80_B','214_B','271_B','282_B','293_B','20_B','31_B','88_B','99_B','233_B','244_B','301_B','50_B','61_B','195_B','206_B','62_A','263_B','12_B','23_B','157_B','168_B','274_B','51_B','225_B','236_B','119_B','42_B','176_B','187_B','198_B','81_B','255_B','4_B','138_B','149_B','160_B','285_B','217_B','179_B','304_B','53_B','64_B','121_B','266_B','277_B','15_B','26_B','83_B','94_B','228_B','239_B','122_B','34_B','45_B','56_B','190_B','296_B','113_B','7_B','18_B','247_B','141_B','152_B','258_B','75_B','209_B','220_B','103_B','114_B','288_B','221_A','37_B','171_B','182_B','76_B','250_B','133_B','144_B','201_B','106_B','231_B','163_B','48_B','193_B'].includes(atom.resi+'_'+atom.chain)){ - return '#642df0' - } else if (['60_A','90_A','101_A','158_A','292_A','303_A','254_A','148_A','71_A','178_A','55_A','123_A','257_A','298_A','104_A','260_A','134_A','162_A','77_A','107_A','20_A','88_A','301_A','50_A','61_A','12_A','23_A','157_A','225_A','236_A','72_A','255_A','4_A','129_A','91_A','102_A','159_A','304_A','15_A','239_A','296_A','190_A','247_A','7_A','152_A','288_A','250_A','97_A','48_A','105_A','127_A','89_A','185_A','253_A','204_A','177_A','119_A','32_A','43_A','100_A','73_A','35_A','169_A','92_A','226_A','237_A','305_A','188_A','199_A','229_A','103_A','65_A','297_A','68_A','125_A','281_A','270_A','30_A','87_A','300_A','38_A','49_A','251_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff9e83' - } else if (['273_A','276_A','47_A','249_A','17_A','86_A','193_A','223_A','70_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff7454' - } else if (['246_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff3f25' - } else if (['241_A','96_A','274_A','83_A','75_A','184_A','213_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff5c3d' - } else if (['132_A','108_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff0707' + var colorAsSnake = function(atom) { + atom_residx = String(atom.resi) + {{SURFACE_COLOR_INSERT}} }}; viewer.addSurface("MS", {colorfunc: colorAsSnake, opacity: 0.9}) viewer.setStyle({bonds: 0}, {sphere:{radius:0.5}}); //water molecules diff --git a/choppa/render/logoplots.py b/choppa/render/logoplots.py index 6f1becd..d3a8059 100644 --- a/choppa/render/logoplots.py +++ b/choppa/render/logoplots.py @@ -30,6 +30,41 @@ 'Y': '#84380b', 'X': '#000000', # add 'X' so that LogoMaker doesn't log to stdout } +WHITE_EMPTY_SQUARE = "iVBORw0KGgoAAAANSUhEUgAAAJYAAACfCAIAAACUbLd9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABhElEQVR4nO3RwQkAIBDAMHX/nc8hfEghmaDQPTOLsvM7gFcW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5l1WGwQ7i50I0AAAAABJRU5ErkJggg==" + +def render_singleres_logoplot(res): + """ + Renders a simple, single-letter 'logoplot', normally for showing the wildtype residue. The plot + is square and is typically rendered top center downstream. + """ + _, ax = plt.subplots(figsize=(4, 4)) + + # create Logo object + logomaker.Logo( + pd.DataFrame({res:1}, index=[0]), + font_name="Sans Serif", + color_scheme=LOGOPLOT_WITHOUT_CONF_COLORSCHEME, + flip_below=False, + show_spines=True, + ax=ax + ) + + plt.xticks([]) + plt.yticks([]) + # plt.savefig("debug_logoplot.png", dpi=70, bbox_inches="tight") # uncomment for testing + # plt object directly to base64 string instead of tmpfile + lp_bytes = io.BytesIO() + plt.savefig( + lp_bytes, + format='png', + dpi=70, # DPI 70 seems to be ~smallest we can get away with + bbox_inches="tight", + ) + lp_bytes.seek(0) + lp_base64 = base64.b64encode(lp_bytes.read()) + plt.close() + + return lp_base64 class LogoPlot(): """ @@ -70,8 +105,8 @@ def divide_fitness_types(self): else: fit_mutants[mutant['aa']] = [mutant['fitness']] - return {wildtype: wildtype_fitness}, unfit_mutants, fit_mutants - + return {wildtype: wildtype_fitness}, unfit_mutants, fit_mutants + def render_logoplot(self, mutants, global_min_confidence=False, global_max_confidence=False, lhs=True, wildtype=False): """ Creates a logoplot as a base64 string. Also annotes with confidence values if present. @@ -80,7 +115,7 @@ def render_logoplot(self, mutants, global_min_confidence=False, global_max_confi """ if len(mutants) == 0: # this can happen when there are no mutants in this category. Return an empty white-sqare base64 instead. - return "iVBORw0KGgoAAAANSUhEUgAAAJYAAACfCAIAAACUbLd9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABhElEQVR4nO3RwQkAIBDAMHX/nc8hfEghmaDQPTOLsvM7gFcW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5lmYZ2GehXkW5l1WGwQ7i50I0AAAAABJRU5ErkJggg==" + return WHITE_EMPTY_SQUARE plt.switch_backend('Agg') # prevents plt from opening a figure on OS if wildtype: # we want this to be a bit smaller and square because it'll always have 1 residue. _, ax = plt.subplots(figsize=(4, 4)) diff --git a/choppa/render/out.html b/choppa/render/out.html index 5a882bd..851d16e 100644 --- a/choppa/render/out.html +++ b/choppa/render/out.html @@ -138,1480 +138,1480 @@ @@ -4177,22 +4177,25 @@ // set protein sticks and surface viewer.setStyle({model: 0}, {stick: {colorscheme: "whiteCarbon", radius:0.15}}); // define a coloring function based on our residue ranges. We can't call .addSurface separate times because the surfaces won't be merged nicely. - var colorAsSnake = function(atom) { if (['155_A','196_A','186_A','227_A','284_A','33_A','93_A','6_A','180_A','191_A','142_A','153_A','222_A','279_A','156_A','1_A','69_A','233_A','244_A','263_A','198_A','285_A','121_A','277_A','94_A','228_A','45_A','18_A','220_A','37_A','116_A','261_A','67_A','78_A','212_A','280_A','272_A','21_A','234_A','264_A','81_A','160_A','283_A','24_A','84_A','106_A','46_A','259_A','8_A','19_A','232_A','243_A','224_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff8a6c' - } else if (['166_A','117_A','128_A','139_A','22_A','79_A','235_A','41_A','52_A','197_A','109_A','120_A','265_A','3_A','14_A','82_A','216_A','295_A','44_A','189_A','63_A','208_A','150_A','161_A','306_A','112_A','268_A','74_A','85_A','219_A','287_A','36_A','181_A','115_A','238_A','143_A','66_A','200_A','211_A','28_A','173_A','230_A','192_A','203_A','9_A','145_A','290_A','39_A','118_A','252_A','175_A','58_A','80_A','214_A','137_A','271_A','282_A','293_A','31_A','205_A','99_A','195_A','206_A','168_A','110_A','42_A','187_A','140_A','151_A','170_A','53_A','64_A','266_A','26_A','122_A','34_A','56_A','113_A','258_A','269_A','209_A','182_A','124_A','135_A','231_A','154_A','165_A','299_A','10_A','146_A','291_A','29_A','40_A','174_A','242_A','2_A','147_A','59_A','215_A','302_A','51_A','176_A','138_A','149_A','294_A','217_A','111_A','245_A','256_A','5_A','179_A','207_A','218_A','130_A','275_A','286_A','13_A','248_A','131_A','54_A','210_A','267_A','16_A','172_A','141_A','114_A','171_A','76_A','133_A','144_A','278_A','289_A','27_A','201_A','95_A','240_A','163_A','57_A','202_A','164_A','126_A','183_A','194_A','262_A','11_A','136_A','98_A'].includes(atom.resi+'_'+atom.chain)){ + var colorAsSnake = function(atom) { + atom_residx = String(atom.resi) + if (['10','14','15','18','19','20','21','22','25','27','31','32','33','37','86','88','95','98','100','101','103','108','111','135','136','138','145','146','148','153','158','165'].includes(atom_residx)){ return '#ffffff' - } else if (['105_B','116_B','261_B','10_B','67_B','78_B','89_B','212_B','223_B','146_B','29_B','40_B','280_B','174_B','185_B','291_B','108_B','242_B','2_B','253_B','136_B','147_B','59_B','70_B','204_B','215_B','98_B','272_B','21_B','155_B','166_B','177_B','302_B','234_B','167_A','117_B','128_B','139_B','264_B','196_B','101_B','158_B','283_B','294_B','32_B','43_B','25_A','100_B','111_B','245_B','256_B','5_B','62_B','73_B','207_B','218_B','130_B','275_B','13_B','24_B','35_B','169_B','286_B','92_B','226_B','237_B','248_B','131_B','305_B','54_B','188_B','199_B','210_B','93_B','267_B','16_B','150_B','161_B','172_B','229_B','112_B','123_B','180_B','191_B','65_B','142_B','153_B','278_B','289_B','27_B','84_B','95_B','240_B','46_B','57_B','68_B','202_B','297_B','125_B','8_B','19_B','30_B','164_B','259_B','270_B','87_B','221_B','232_B','243_B','281_B','126_B','300_B','38_B','49_B','183_B','194_B','77_B','251_B','11_B','262_B','145_B','156_B','269_B','213_B','224_B','96_B','107_B','118_B','175_B','60_B','137_B','273_B','22_B','205_B','79_B','90_B','235_B','292_B','303_B','41_B','52_B','186_B','197_B','109_B','120_B','254_B','265_B','3_B','14_B','148_B','71_B','82_B','216_B','227_B','110_B','284_B','33_B','44_B','167_B','178_B','189_B','72_B','246_B','129_B','140_B','151_B','63_B','208_B','91_B','102_B','25_B','159_B','170_B','55_B','132_B','257_B','268_B','6_B','295_B','74_B','85_B','219_B','276_B','36_B','47_B','287_B','181_B','298_B','104_B','115_B','238_B','249_B','260_B','299_B','143_B','66_B','200_B','211_B','222_B','134_B','17_B','28_B','162_B','173_B','279_B','230_B','241_B','124_B','135_B','192_B','203_B','86_B','97_B','9_B','154_B','165_B','290_B','39_B','127_B','252_B','1_B','184_B','58_B','69_B','80_B','214_B','271_B','282_B','293_B','20_B','31_B','88_B','99_B','233_B','244_B','301_B','50_B','61_B','195_B','206_B','62_A','263_B','12_B','23_B','157_B','168_B','274_B','51_B','225_B','236_B','119_B','42_B','176_B','187_B','198_B','81_B','255_B','4_B','138_B','149_B','160_B','285_B','217_B','179_B','304_B','53_B','64_B','121_B','266_B','277_B','15_B','26_B','83_B','94_B','228_B','239_B','122_B','34_B','45_B','56_B','190_B','296_B','113_B','7_B','18_B','247_B','141_B','152_B','258_B','75_B','209_B','220_B','103_B','114_B','288_B','221_A','37_B','171_B','182_B','76_B','250_B','133_B','144_B','201_B','106_B','231_B','163_B','48_B','193_B'].includes(atom.resi+'_'+atom.chain)){ - return '#642df0' - } else if (['60_A','90_A','101_A','158_A','292_A','303_A','254_A','148_A','71_A','178_A','55_A','123_A','257_A','298_A','104_A','260_A','134_A','162_A','77_A','107_A','20_A','88_A','301_A','50_A','61_A','12_A','23_A','157_A','225_A','236_A','72_A','255_A','4_A','129_A','91_A','102_A','159_A','304_A','15_A','239_A','296_A','190_A','247_A','7_A','152_A','288_A','250_A','97_A','48_A','105_A','127_A','89_A','185_A','253_A','204_A','177_A','119_A','32_A','43_A','100_A','73_A','35_A','169_A','92_A','226_A','237_A','305_A','188_A','199_A','229_A','103_A','65_A','297_A','68_A','125_A','281_A','270_A','30_A','87_A','300_A','38_A','49_A','251_A'].includes(atom.resi+'_'+atom.chain)){ + } else if (['9','16','17','23','24','26','28','29','34','35','38','87','89','90','91','94','99','105','106','109','110','112','137','140','141','142','143','147','150','151','152','154','155','159','160','161','162','163','164'].includes(atom_residx)){ return '#ff9e83' - } else if (['273_A','276_A','47_A','249_A','17_A','86_A','193_A','223_A','70_A'].includes(atom.resi+'_'+atom.chain)){ + } else if (['11','12','30','36','92','93','96','97','102','104','113','139','144','149','156'].includes(atom_residx)){ + return '#ff8a6c' + } else if (['13','107','134','157'].includes(atom_residx)){ return '#ff7454' - } else if (['246_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff3f25' - } else if (['241_A','96_A','274_A','83_A','75_A','184_A','213_A'].includes(atom.resi+'_'+atom.chain)){ + } else if ([].includes(atom_residx)){ return '#ff5c3d' - } else if (['132_A','108_A'].includes(atom.resi+'_'+atom.chain)){ + } else if ([].includes(atom_residx)){ + return '#ff3f25' + } else if ([].includes(atom_residx)){ return '#ff0707' + } else if (['5','6','7','8','39','40','41','42','43','44','45','46','47','48','49','50','51','52','53','54','55','56','57','58','59','60','61','62','63','64','65','66','67','68','69','70','71','72','73','74','75','76','77','78','79','80','81','82','83','84','85','114','115','116','117','118','119','120','121','122','123','124','125','126','127','128','129','130','131','132','133','166','167','168'].includes(atom_residx)){ + return '#642df0' + }}; viewer.addSurface("MS", {colorfunc: colorAsSnake, opacity: 0.9}) viewer.setStyle({bonds: 0}, {sphere:{radius:0.5}}); //water molecules diff --git a/choppa/render/pose.html b/choppa/render/pose.html index 17222af..eb6c81c 100644 --- a/choppa/render/pose.html +++ b/choppa/render/pose.html @@ -11437,7 +11437,7 @@ viewer.setStyle({model: 0}, {stick: {colorscheme: "whiteCarbon", radius:0.15}}); // define a coloring function based on our residue ranges. We can't call .addSurface separate times because the surfaces won't be merged nicely. var colorAsSnake = function(atom) { if (['155_A','196_A','186_A','227_A','284_A','33_A','93_A','6_A','180_A','191_A','142_A','153_A','222_A','279_A','156_A','1_A','69_A','233_A','244_A','263_A','198_A','285_A','121_A','277_A','94_A','228_A','45_A','18_A','220_A','37_A','116_A','261_A','67_A','78_A','212_A','280_A','272_A','21_A','234_A','264_A','81_A','160_A','283_A','24_A','84_A','106_A','46_A','259_A','8_A','19_A','232_A','243_A','224_A'].includes(atom.resi+'_'+atom.chain)){ - return '#ff8a6c' + return '#ff8a6c' } else if (['166_A','117_A','128_A','139_A','22_A','79_A','235_A','41_A','52_A','197_A','109_A','120_A','265_A','3_A','14_A','82_A','216_A','295_A','44_A','189_A','63_A','208_A','150_A','161_A','306_A','112_A','268_A','74_A','85_A','219_A','287_A','36_A','181_A','115_A','238_A','143_A','66_A','200_A','211_A','28_A','173_A','230_A','192_A','203_A','9_A','145_A','290_A','39_A','118_A','252_A','175_A','58_A','80_A','214_A','137_A','271_A','282_A','293_A','31_A','205_A','99_A','195_A','206_A','168_A','110_A','42_A','187_A','140_A','151_A','170_A','53_A','64_A','266_A','26_A','122_A','34_A','56_A','113_A','258_A','269_A','209_A','182_A','124_A','135_A','231_A','154_A','165_A','299_A','10_A','146_A','291_A','29_A','40_A','174_A','242_A','2_A','147_A','59_A','215_A','302_A','51_A','176_A','138_A','149_A','294_A','217_A','111_A','245_A','256_A','5_A','179_A','207_A','218_A','130_A','275_A','286_A','13_A','248_A','131_A','54_A','210_A','267_A','16_A','172_A','141_A','114_A','171_A','76_A','133_A','144_A','278_A','289_A','27_A','201_A','95_A','240_A','163_A','57_A','202_A','164_A','126_A','183_A','194_A','262_A','11_A','136_A','98_A'].includes(atom.resi+'_'+atom.chain)){ return '#ffffff' } else if (['105_B','116_B','261_B','10_B','67_B','78_B','89_B','212_B','223_B','146_B','29_B','40_B','280_B','174_B','185_B','291_B','108_B','242_B','2_B','253_B','136_B','147_B','59_B','70_B','204_B','215_B','98_B','272_B','21_B','155_B','166_B','177_B','302_B','234_B','167_A','117_B','128_B','139_B','264_B','196_B','101_B','158_B','283_B','294_B','32_B','43_B','25_A','100_B','111_B','245_B','256_B','5_B','62_B','73_B','207_B','218_B','130_B','275_B','13_B','24_B','35_B','169_B','286_B','92_B','226_B','237_B','248_B','131_B','305_B','54_B','188_B','199_B','210_B','93_B','267_B','16_B','150_B','161_B','172_B','229_B','112_B','123_B','180_B','191_B','65_B','142_B','153_B','278_B','289_B','27_B','84_B','95_B','240_B','46_B','57_B','68_B','202_B','297_B','125_B','8_B','19_B','30_B','164_B','259_B','270_B','87_B','221_B','232_B','243_B','281_B','126_B','300_B','38_B','49_B','183_B','194_B','77_B','251_B','11_B','262_B','145_B','156_B','269_B','213_B','224_B','96_B','107_B','118_B','175_B','60_B','137_B','273_B','22_B','205_B','79_B','90_B','235_B','292_B','303_B','41_B','52_B','186_B','197_B','109_B','120_B','254_B','265_B','3_B','14_B','148_B','71_B','82_B','216_B','227_B','110_B','284_B','33_B','44_B','167_B','178_B','189_B','72_B','246_B','129_B','140_B','151_B','63_B','208_B','91_B','102_B','25_B','159_B','170_B','55_B','132_B','257_B','268_B','6_B','295_B','74_B','85_B','219_B','276_B','36_B','47_B','287_B','181_B','298_B','104_B','115_B','238_B','249_B','260_B','299_B','143_B','66_B','200_B','211_B','222_B','134_B','17_B','28_B','162_B','173_B','279_B','230_B','241_B','124_B','135_B','192_B','203_B','86_B','97_B','9_B','154_B','165_B','290_B','39_B','127_B','252_B','1_B','184_B','58_B','69_B','80_B','214_B','271_B','282_B','293_B','20_B','31_B','88_B','99_B','233_B','244_B','301_B','50_B','61_B','195_B','206_B','62_A','263_B','12_B','23_B','157_B','168_B','274_B','51_B','225_B','236_B','119_B','42_B','176_B','187_B','198_B','81_B','255_B','4_B','138_B','149_B','160_B','285_B','217_B','179_B','304_B','53_B','64_B','121_B','266_B','277_B','15_B','26_B','83_B','94_B','228_B','239_B','122_B','34_B','45_B','56_B','190_B','296_B','113_B','7_B','18_B','247_B','141_B','152_B','258_B','75_B','209_B','220_B','103_B','114_B','288_B','221_A','37_B','171_B','182_B','76_B','250_B','133_B','144_B','201_B','106_B','231_B','163_B','48_B','193_B'].includes(atom.resi+'_'+atom.chain)){ @@ -11446,7 +11446,7 @@ return '#ff9e83' } else if (['273_A','276_A','47_A','249_A','17_A','86_A','193_A','223_A','70_A'].includes(atom.resi+'_'+atom.chain)){ return '#ff7454' - } else if (['246_A'].includes(atom.resi+'_'+atom.chain)){ + } else if (['246_A'].includes(atom.resi+'_'+atom.chain)){ return '#ff3f25' } else if (['241_A','96_A','274_A','83_A','75_A','184_A','213_A'].includes(atom.resi+'_'+atom.chain)){ return '#ff5c3d' diff --git a/choppa/render/render.py b/choppa/render/render.py index c16e15d..9d0ae0b 100644 --- a/choppa/render/render.py +++ b/choppa/render/render.py @@ -1,13 +1,12 @@ import logging, sys import pymol2 -from io import StringIO from rdkit import Chem import math -from concurrent.futures import ThreadPoolExecutor, as_completed from tqdm import tqdm from choppa.render.utils import show_contacts, get_ligand_resnames_from_pdb_str, split_pdb_str -from choppa.render.logoplots import LogoPlot +from choppa.render.logoplots import LogoPlot, WHITE_EMPTY_SQUARE, render_singleres_logoplot + logging.basicConfig(stream=sys.stdout, level=logging.INFO) logger = logging.getLogger() @@ -288,15 +287,15 @@ def get_logoplot_dict(self, confidence_lims, multiprocess=False): else: for idx, residue_fitness_dict in tqdm(self.fitness_dict.items()): - # catch if res has no fitness, would throw an error in logoplot generation. + # catch if res has no fitness, create empty logoplots instead (but show the wildtype) if not 'aa' in residue_fitness_dict['wildtype']: logoplot_dict[idx] = { 'fitness_aligned_index': idx, 'fitness_csv_index': idx, 'logoplots_base64' : { - 'wildtype' : "", - 'fit' : "", - 'unfit' : "" + 'wildtype' : render_singleres_logoplot("".join(residue_fitness_dict['wildtype'])), + 'fit' : WHITE_EMPTY_SQUARE, + 'unfit' : WHITE_EMPTY_SQUARE, }} continue @@ -317,10 +316,76 @@ def get_logoplot_dict(self, confidence_lims, multiprocess=False): return logoplot_dict - # also add in interactions dict + def get_surface_coloring_dict(self): + """ + Based on fitness coloring, creates a dict where keys are colors, values are residue numbers. + """ + hex_color_codes = [ + "#ffffff", + "#ff9e83", + "#ff8a6c", + "#ff7454", + "#ff5c3d", + "#ff3f25", + "#ff0707", + "#642df0", + ] + color_res_dict = { color:[] for color in hex_color_codes } + for resindex, fitness_data in self.fitness_dict.items(): + if not 'mutants' in fitness_data: + # no fitness data for this residue after alignment between Fitness CSV and input PDB + color_res_dict["#642df0"].append(resindex) # makes residue surface blue + continue + fit_mutants = [mut for mut in fitness_data['mutants'] if mut['fitness'] > self.fitness_threshold] + if len(fit_mutants) <= 6: # color by increasing redness the more fit mutants there are + color_res_dict[hex_color_codes[len(fit_mutants)]].append(resindex) + else: # if there are more than 5 fit mutants just color it the most red - super mutable in any case + color_res_dict[hex_color_codes[6]].append(resindex) + + return color_res_dict + + def surface_coloring_dict_to_js(self, color_res_dict): + """ + Transforms a dictionary of residue indices per color (hex) to a JavaScript-compatible + string. + """ + residue_coloring_function_js = "" + start = True + for color, residues in color_res_dict.items(): + residues = [ + f"'{res}'" for res in residues + ] # need to wrap the string in quotes *within* the JS code + if start: + residue_coloring_function_js += ( + "if ([" + + ",".join(residues) + + "].includes(atom_residx)){ \n return '" + + color + + "' \n " + ) + start = False + else: + residue_coloring_function_js += ( + "} else if ([" + + ",".join(residues) + + "].includes(atom_residx)){ \n return '" + + color + + "' \n " + ) + return residue_coloring_function_js + + def get_interaction_dict(self): + """ + Generates interactions to be displayed on the interactive HTML view. Interactions are colored + by the same rules as for PyMOL (`render.PublicationView()`). + """ + + """ + intn_dict = {'0_MET165.A': {'lig_at_x': '11.594', 'lig_at_y': '-1.029', 'lig_at_z': '22.265', 'prot_at_x': '12.038', 'prot_at_y': '1.138', 'prot_at_z': '19.397', 'type': 'hydrophobic_interaction', 'color': '#ffffff'}, '1_GLU166.A': {'lig_at_x': '5.139', 'lig_at_y': '1.557', 'lig_at_z': '19.297', 'prot_at_x': '7.624', 'prot_at_y': '4.118', 'prot_at_z': '18.338', 'type': 'hydrophobic_interaction', 'color': '#ffffff'}, '2_GLU166.A': {'lig_at_x': '9.020', 'lig_at_y': '1.593', 'lig_at_z': '21.267', 'prot_at_x': '9.672', 'prot_at_y': '2.793', 'prot_at_z': '18.548', 'type': 'hydrogen_bond', 'color': '#008000'}, '3_HIS41.A': {'lig_at_x': '12.093', 'lig_at_y': '0.095', 'lig_at_z': '22.863', 'prot_at_x': '11.997', 'prot_at_y': '-5.242', 'prot_at_z': '21.702', 'type': 'pi_stack', 'color': '#ffffff'}} + """ - def inject_stuff_in_template(self, sdf_str, pdb_str, logoplot_dict, template="Template.html", out_file="out.html"): + def inject_stuff_in_template(self, sdf_str, pdb_str, surface_coloring, logoplot_dict, template="Template.html", out_file="out.html"): """" Replaces parts of a template HTML with relevant bits of data to get to a HTML view of the (ligand-) protein, its fitness and its interactions (if any). @@ -364,16 +429,15 @@ def inject_stuff_in_template(self, sdf_str, pdb_str, logoplot_dict, template="Te line = line.replace("{{SDF_INSERT}}", f"{sdf_str}") # logoplots are a bit more complicated, need to add all those DIVs - if "{{LOGOPLOTS_INSERTS}}" in line: - line = line.replace("{{LOGOPLOTS_INSERTS}}", logoplot_divs) + line = line.replace("{{LOGOPLOTS_INSERTS}}", logoplot_divs) - # add in interactions - fout.write(line) - - # then add interactions + # add in surface coloring + line = line.replace("{{SURFACE_COLOR_INSERT}}", surface_coloring) + + # finally add interactions - # then add surface coloring - + fout.write(line) + @@ -391,8 +455,15 @@ def render(self): # get the strings for the PDB (prot) and the SDF (lig, if present) lig_sdf_str, prot_pdb_str = split_pdb_str(self.complex_pdb_str) - # do a dirty HTML generation using the logoplot and fitness dicts. - self.inject_stuff_in_template(lig_sdf_str, prot_pdb_str, logoplot_dict) + # define the coloring of the surface + surface_coloring = self.surface_coloring_dict_to_js(self.get_surface_coloring_dict()) + # define the interactions to show + pass + + # # do a dirty HTML generation using the logoplot and fitness dicts. + self.inject_stuff_in_template(lig_sdf_str, prot_pdb_str, surface_coloring, logoplot_dict) + + @@ -403,8 +474,8 @@ def render(self): from choppa.IO.input import FitnessFactory, ComplexFactory - fitness_dict = FitnessFactory(TOY_FITNESS_DATA_COMPLETE, - confidence_colname="confidence" + fitness_dict = FitnessFactory(TOY_FITNESS_DATA_SECTIONED, + # confidence_colname="confidence" ).get_fitness_basedict() complex = ComplexFactory(TOY_COMPLEX).load_pdb() complex_rdkit = ComplexFactory(TOY_COMPLEX).load_pdb_rdkit()