Source code for xmipp3.viewers.viewer_validate_fscq

# -*- coding: utf-8 -*-
# **************************************************************************
# *
# * Authors:     Erney Ramirez Aportela (eramirez@cnb.csic.es)
# *
# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 2 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307  USA
# *
# *  All comments concerning this program package may be sent to the
# *  e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************

import os
from tkinter import Tk, CENTER, Scrollbar, TOP, BOTH, Y

import numpy as np
from pwem.viewers import (LocalResolutionViewer, EmPlotter, ChimeraView,
                          ChimeraAttributeViewer)
from pyworkflow.gui import *
from pyworkflow.protocol.params import (LabelParam, EnumParam)
from pyworkflow.viewer import ProtocolViewer, DESKTOP_TKINTER
from xmipp3.protocols.protocol_validate_fscq import (XmippProtValFit, 
                                                     RESTA_FILE_MRC, 
                                                     OUTPUT_PDBMRC_FILE, 
                                                     RESTA_FILE_NORM)

[docs]class XmippProtValFitViewer(LocalResolutionViewer, ChimeraAttributeViewer): """ Visualization tools for validation fsc-q. FSC-Q is a Xmipp package for evaluate the map-to-model fit """ _label = 'viewer validation_fsc-q' _targets = [XmippProtValFit] _environments = [DESKTOP_TKINTER] RESIDUE = 0 ATOM = 1 def __init__(self, *args, **kwargs): ProtocolViewer.__init__(self, *args, **kwargs) def _defineParams(self, form): self._env = os.environ.copy() form.addSection(label='FSC-Q results') group = form.addGroup('Chimera visualization') group.addParam('displayVolume', LabelParam, important=True, label='Display FSC-Q Volume Output') group.addParam('displayNormVolume', LabelParam, important=True, label='Display FSC-Qr Volume Output') group.addParam('displayPDB', EnumParam, choices=['by residue', 'by atom'], default=0, important=True, display=EnumParam.DISPLAY_COMBO, label='Display FSC-Q on PDB Output', help='FSC-Q projected on the atomic model') group.addParam('displayNormPDB', EnumParam, choices=['by residue', 'by atom'], default=0, important=True, display=EnumParam.DISPLAY_COMBO, label='Display FSC-Qr on PDB Output', help='FSC-Qr projected on the atomic model') group = form.addGroup('Statistics') group.addParam('calculateFscqNeg', LabelParam, important=True, label='Amino acids with possible overfitting', help='Amino acids that have atoms with FSC-Q < -1' ' are determined. It is suggested that these amino acids ' ' be re-checked in order to improve the fit.') group.addParam('calculateFscqPos', LabelParam, important=True, label='Amino acids with low resolvability', help='Amino acids that have atoms with FSC-Q > 1' ' are determined. It is suggested that these amino acids ' ' be re-checked in order to improve the fit.') super()._defineParams(form) from pwem.wizards.wizard import ColorScaleWizardBase group = form.addGroup('Color settings') ColorScaleWizardBase.defineColorScaleParams(group, defaultLowest=-3, defaultHighest=3, defaultIntervals=21, defaultColorMap='RdBu_r') def _getVisualizeDict(self): self.protocol._createFilenameTemplates() visualizeDict = {'displayVolume': self._visualize_vol, 'displayNormVolume': self._visualize_norm_vol, 'displayPDB': self._visualize_pdb, 'displayNormPDB': self._visualize_norm_pdb, 'calculateFscqNeg': self._statistics, 'calculateFscqPos': self._statistics} visualizeDict.update(ChimeraAttributeViewer._getVisualizeDict(self)) return visualizeDict def _create_legend(self, scale): fnCmd = self.protocol._getExtraPath("chimera_output.py") f = open(fnCmd, 'w') f.write("from chimerax.core.commands import run\n") f.write("from chimerax.graphics.windowsize import window_size\n") f.write("try:\n") f.write(" from PyQt5.QtGui import QFontMetrics\n") f.write(" from PyQt5.QtGui import QFont\n") f.write("except ModuleNotFoundError:\n") f.write(" from PyQt6.QtGui import QFontMetrics\n") f.write(" from PyQt6.QtGui import QFont\n") f.write("run(session, 'set bgColor white')\n") # get window size so we can place labels properly f.write("v = session.main_view\n") f.write("vx,vy=v.window_size\n") # Calculate heights and Y positions: font, scale height and firstY f.write('font = QFont("Arial", 12)\n') f.write('f = QFontMetrics(font)\n') f.write('_height = 1 * f.height()/vy\n') # Font height f.write('_half_scale_height = _height * 3.5\n') # Full height of the scale f.write("_firstY= 0.5 + _half_scale_height\n") # Y location for first label val = scale f.write('scale = %f \n' % val) f.write("run(session, '2dlabel text -%.2f bgColor red xpos 0.01 ypos %f size 12' % (scale, _firstY)) \n") f.write("run(session, '2dlabel text -%.2f bgColor orange xpos 0.01 ypos %f size 12' % (scale/1.5, _firstY-_height)) \n") f.write("run(session, '2dlabel text -%.2f bgColor gold xpos 0.01 ypos %f size 12' % (scale/3, _firstY-2*_height)) \n") f.write("run(session, '2dlabel text %05.2f bgColor yellow xpos 0.01 ypos %f size 12' % (00.00, _firstY-3*_height)) \n") f.write("run(session, '2dlabel text %05.2f bgColor lime xpos 0.01 ypos %f size 12' % (scale/3, _firstY-4*_height)) \n") f.write("run(session, '2dlabel text %05.2f bgColor cyan xpos 0.01 ypos %f size 12' % (scale/1.5, _firstY-5*_height)) \n") f.write("run(session, '2dlabel text %05.2f bgColor dodger blue xpos 0.01 ypos %f size 12' % (scale, _firstY-6*_height)) \n") def _visualize_vol(self, obj, **args): self._create_legend(3) fnCmd = self.protocol._getExtraPath("chimera_output.py") f = open(fnCmd, 'a') f.write("run(session, 'open %s')\n" % self.protocol._getFileName(OUTPUT_PDBMRC_FILE)) f.write("run(session, 'open %s')\n" % self.protocol._getFileName(RESTA_FILE_MRC)) f.write("run(session, 'volume #2 voxelSize %s step 1')\n" % self.protocol.inputVolume.get().getSamplingRate() ) f.write("run(session, 'volume #3 voxelSize %s')\n" % self.protocol.inputVolume.get().getSamplingRate() ) f.write("run(session, 'vol #3 hide')\n") f.write("run(session, 'color sample #2 map #3 palette" " -3.0,red:-2.0,orange:-1.0,gold:0,yellow:1.0,lime:2.0,cyan:3.0,#1e90ff')\n") f.close() view = ChimeraView(fnCmd) return [view] def _visualize_norm_vol(self, obj, **args): self._create_legend(1.5) fnCmd = self.protocol._getExtraPath("chimera_output.py") f = open(fnCmd, 'a') f.write("run(session, 'open %s')\n" % self.protocol._getFileName(OUTPUT_PDBMRC_FILE)) f.write("run(session, 'open %s')\n" % self.protocol._getFileName(RESTA_FILE_NORM)) f.write("run(session, 'volume #2 voxelSize %s step 1')\n" % self.protocol.inputVolume.get().getSamplingRate() ) f.write("run(session, 'volume #3 voxelSize %s')\n" % self.protocol.inputVolume.get().getSamplingRate() ) f.write("run(session, 'vol #3 hide')\n") f.write("run(session, 'color sample #2 map #3 palette" " -1.5,red:-1.0,orange:-0.5,gold:0,yellow:0.5,lime:1.0,cyan:1.5,#1e90ff')\n") f.close() view = ChimeraView(fnCmd) return [view] def _visualize_pdb(self, obj, **args): self._create_legend(3) fnCmd = self.protocol._getExtraPath("chimera_output.py") f = open(fnCmd, 'a') f.write("run(session, 'open %s')\n" % self.protocol.getFSCQFile()) if self.displayPDB == self.RESIDUE: f.write("run(session, 'cartoon')\n") f.write("run(session, 'hide target ab')\n") f.write("run(session, 'color byattribute occupancy palette" " -3.0,red:-2.0,orange:-1.0,gold:0,yellow:1.0,lime:2.0,cyan:3.0,#1e90ff" " ave residue')\n") else: f.write("run(session, 'cartoon hide')\n") f.write("run(session, 'show target ab')\n") f.write("run(session, 'style stick')\n") f.write("run(session, 'color byattribute occupancy palette" " -3.0,red:-2.0,orange:-1.0,gold:0,yellow:1.0,lime:2.0,cyan:3.0,#1e90ff')\n") f.close() view = ChimeraView(fnCmd) return [view] def _visualize_norm_pdb(self, obj, **args): self._create_legend(1.5) fnCmd = self.protocol._getExtraPath("chimera_output.py") f = open(fnCmd, 'a') f.write("run(session, 'open %s')\n" % self.protocol.getNormFSCQFile()) if self.displayNormPDB == self.RESIDUE: f.write("run(session, 'cartoon')\n") f.write("run(session, 'hide target ab')\n") f.write("run(session, 'color byattribute occupancy palette" " -1.5,red:-1.0,orange:-0.5,gold:0,yellow:0.5,lime:1.0,cyan:1.5,#1e90ff" " ave residue')\n") else: f.write("run(session, 'cartoon hide')\n") f.write("run(session, 'show target ab')\n") f.write("run(session, 'style stick')\n") f.write("run(session, 'color byattribute occupancy palette" " -1.5,red:-1.0,orange:-0.5,gold:0,yellow:0.5,lime:1.0,cyan:1.5,#1e90ff')\n") f.close() view = ChimeraView(fnCmd) return [view] def _calculate_fscq(self, obj, **args): status = False overfittingList = [] poorfittingList = [] with open(os.path.abspath(self.protocol.getFSCQFile())) as f: linesData = f.readlines() for j, lin in enumerate(linesData): if lin.startswith('ATOM') or lin.startswith('HETATM'): resnumber = int(lin[22:26]) if status and resnumber == resnumberCtl: resname = lin[17:20].strip() chain = lin[21] fscq = float(lin[54:60]) currentFrag.append(fscq) elif status and resnumber != resnumberCtl: meanFscq = np.mean(currentFrag) currentFrag.sort() if currentFrag[0] <= -1: overfittingList.append((resname, resnumberCtl, chain, currentFrag[0], meanFscq)) if currentFrag[-1] >= 1: poorfittingList.append((resname, resnumberCtl, chain, currentFrag[-1], meanFscq)) currentFrag = [] resnumberCtl = resnumber resname = lin[17:20].strip() chain = lin[21] fscq = float(lin[54:60]) currentFrag.append(fscq) else: status = True currentFrag = [] resnumberCtl = resnumber resname = lin[17:20].strip() chain = lin[21] fscq = float(lin[54:60]) currentFrag.append(fscq) if obj == 'calculateFscqNeg': return overfittingList else: return poorfittingList def _statistics(self, obj, **args): mainFrame = Tk() statistics = Statistics('FSC-Q Statistics', mainFrame, statistics=self._calculate_fscq(obj, **args)) statistics.mainloop()
[docs]class Statistics(ttk.Frame): """ Windows to hold a plugin manager help """ def __init__(self, title, mainFrame, **kwargs): super().__init__(mainFrame) mainFrame.title(title) mainFrame.configure(width=1500, height=400) self.FrameTable = tk.PanedWindow(mainFrame, orient=tk.VERTICAL) self.FrameTable.pack(side=TOP, fill=BOTH, expand=Y) self.aminolist = kwargs['statistics'] self.fill_statistics() self.FrameTable.rowconfigure(0, weight=1) self.FrameTable.columnconfigure(0, weight=1) self.flag=False
[docs] def fill_statistics(self): self.addStatisticsTable(0, 0)
[docs] def addStatisticsTable(self, row, column): """ This methods shows some statistics values """ def fill_table(): try: for values in self.aminolist: if self.aminolist.index(values) % 2 == 0: self.Table.insert('', tk.END, text='', values=(values[0] + str(values[1]) + "-" + values[2], round(values[3], 2), round(values[4], 2)), tags=('even',)) else: self.Table.insert('', tk.END, text='', values=(values[0] + str(values[1]) + "-" + values[2], round(values[3], 2), round(values[4], 2)), tags=('odd',)) except Exception as e: pass self.columns = ("aminoacid", "fscq", "mean") self.columsText = ("Aminoacids", "atom FSC-Q", "Mean FSC-Q aminoacid") self.Table = ttk.Treeview(self.FrameTable, columns=self.columns) self.Table.grid(row=row, column=column, sticky='news') self.Table.tag_configure("heading", background='sky blue', foreground='black', font=('Calibri', 10, 'bold')) self.Table.tag_configure('even', background='white', foreground='black') self.Table.tag_configure('odd', background='gainsboro', foreground='black') self.Table.heading(self.columns[0], text=self.columsText[0]) self.Table.heading(self.columns[1], text=self.columsText[1]) self.Table.heading(self.columns[2], text=self.columsText[2]) self.Table.column("#0", width=0, minwidth=0, stretch=False) self.Table.column(self.columns[0], anchor=CENTER) self.Table.column(self.columns[1], anchor=CENTER) self.Table.column(self.columns[2], anchor=CENTER) yscroll = Scrollbar(self.FrameTable, orient='vertical', command=self.Table.yview) yscroll.grid(row=row, column=column + 1, sticky='news') self.Table.configure(yscrollcommand=yscroll.set) yscroll.configure(command=self.Table.yview) self.Table.bind("<Button-1>", self._orderTable, True) fill_table()
def _orderTable(self, event): x, y, widget = event.x, event.y, event.widget column = self.Table.identify_column(x) row = self.Table.identify_column(y) if row == '#1': # click over heading col = 0 if column == '#2': col = 1 elif column == '#3': col = 2 self.Table.heading(self.columns[col], text=self.columsText[col], command=lambda: \ self.treeview_sort_column(col, self.flag))
[docs] def treeview_sort_column(self, col, reverse): if col == 0: l = [(self.Table.set(k, col), k) for k in self.Table.get_children('')] else: l = [(float(self.Table.set(k, col)), k) for k in self.Table.get_children('')] l.sort(reverse=reverse) for index, (_, k) in enumerate(l): self.Table.move(k, '', index) self.Table.heading(col, command=lambda: self.treeview_sort_column(col, not reverse)) self.flag = not self.flag