"""
Implementation of Qt dialog boxes used by multiple sequence viewer.
Copyright Schrodinger, LLC. All rights reserved.
"""
# Contributors: Piotr Rotkiewicz
import glob
import math
import os
from past.utils import old_div
from schrodinger.infra import canvas2d
try:
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
except:
QtCore = None
QtGui = None
QtWidgets = None
# Canvas license for 2D tooltips
try:
from schrodinger.application.canvas.base import ChmLicenseShared_isValid
from schrodinger.application.canvas.utils import get_license
if not ChmLicenseShared_isValid():
canvas_license = get_license("LICENSE_SHARED")
except:
raise
canvas_license = None
_BLAST_RESULTS_DIALOG = None
_BLAST_SETTINGS_DIALOG = None
[docs]def createBlastSettingsDialog(parent, execute=True):
dialog = BlastSettingsDialog(parent)
if execute:
_BLAST_SETTINGS_DIALOG.exec_()
return dialog
[docs]class BlastSettingsDialog(QtWidgets.QDialog):
[docs] def __init__(self, parent):
# Initialize base class.
QtWidgets.QDialog.__init__(self, parent)
self.setWindowTitle("Blast Search Settings")
self.viewer = parent
#Start Job button
self.button_remove = QtWidgets.QPushButton(self)
self.button_remove.setText("Start Job")
self.button_remove.setDefault(True)
self.button_remove.setToolTip("Start BLAST search job")
self.button_remove.clicked.connect(self.accept)
#Cancel button
self.button_cancel = QtWidgets.QPushButton(self)
self.button_cancel.setText("Cancel")
self.button_cancel.setToolTip("Cancel search")
self.button_cancel.clicked.connect(self.reject)
self.button_layout = QtWidgets.QHBoxLayout()
self.button_layout.addWidget(self.button_remove)
self.button_layout.addWidget(self.button_cancel)
self.layout = QtWidgets.QVBoxLayout(self)
options_layout = QtWidgets.QVBoxLayout()
program_choice_layout = QtWidgets.QHBoxLayout()
program_choice_layout.addWidget(QtWidgets.QLabel("Search Tool:"))
self.blast_button = QtWidgets.QRadioButton("BLAST")
self.blast_button.setChecked(True)
self.blast_button.setToolTip("Single iteration BLAST search")
self.blast_button.clicked.connect(self.blastButtonClicked)
program_choice_layout.addWidget(self.blast_button)
self.psiblast_button = QtWidgets.QRadioButton("PSI-BLAST")
self.psiblast_button.setChecked(False)
self.psiblast_button.setToolTip(
"Position-specific iterative BLAST search")
program_choice_layout.addWidget(self.psiblast_button)
self.psiblast_button.clicked.connect(self.psiblastButtonClicked)
options_layout.addLayout(program_choice_layout)
server_choice_layout = QtWidgets.QHBoxLayout()
server_choice_layout.addWidget(QtWidgets.QLabel("BLAST Server:"))
self.local_button = QtWidgets.QRadioButton("Local")
self.local_button.setChecked(True)
self.local_button.setToolTip("Use local BLAST installation")
server_choice_layout.addWidget(self.local_button)
self.remote_button = QtWidgets.QRadioButton("Remote (NCBI)")
self.remote_button.setChecked(False)
self.remote_button.setToolTip(
"Use remote BLAST server at http://www.ncbi.nlm.hiv.gov")
server_choice_layout.addWidget(self.remote_button)
self.server_choice_group = QtWidgets.QButtonGroup()
self.server_choice_group.addButton(self.local_button)
self.server_choice_group.addButton(self.remote_button)
options_layout.addLayout(server_choice_layout)
options_layout.addWidget(QtWidgets.QLabel("Similarity Matrix:"))
self.matrix_choice = QtWidgets.QComboBox(self)
self.matrix_choice.setToolTip("Substitution matrix")
for matrix in ["BLOSUM62", "BLOSUM80", "BLOSUM45", "PAM30", "PAM70"]:
self.matrix_choice.addItem(matrix)
options_layout.addWidget(self.matrix_choice)
options_layout.addWidget(QtWidgets.QLabel("Gap Costs:"))
self.gap_choice = QtWidgets.QComboBox(self)
for gap in [
"Exist: 11 Extend: 1", "Exist: 10 Extend: 1",
"Exist: 12 Extend: 1", "Exist: 9 Extend: 2",
"Exist: 8 Extend: 2", "Exist: 7 Extend: 2"
]:
self.gap_choice.addItem(gap)
options_layout.addWidget(self.gap_choice)
self.evalue_input = QtWidgets.QDoubleSpinBox(self)
self.evalue_input.setValue(1.0)
self.evalue_input.setMinimum(-10.0)
self.evalue_input.setMaximum(10.0)
self.evalue_input.setEnabled(True)
self.evalue_label = QtWidgets.QLabel("Expectation Value Threshold:")
self.evalue_label.setEnabled(True)
options_layout.addWidget(self.evalue_label)
evalue_layout = QtWidgets.QHBoxLayout()
evalue_layout.addWidget(QtWidgets.QLabel("10 ^ "))
evalue_layout.addWidget(self.evalue_input)
evalue_layout.addStretch()
options_layout.addLayout(evalue_layout)
word_choice_layout = QtWidgets.QHBoxLayout()
word_choice_layout.addWidget(QtWidgets.QLabel("Word Size:"))
self.word_2_button = QtWidgets.QRadioButton("2")
self.word_2_button.setChecked(False)
word_choice_layout.addWidget(self.word_2_button)
self.word_3_button = QtWidgets.QRadioButton("3")
self.word_3_button.setChecked(True)
word_choice_layout.addWidget(self.word_3_button)
word_choice_layout.addStretch()
self.word_size_group = QtWidgets.QButtonGroup()
self.word_size_group.addButton(self.word_2_button)
self.word_size_group.addButton(self.word_3_button)
options_layout.addLayout(word_choice_layout)
self.filter_query = QtWidgets.QCheckBox()
self.filter_query.setText("Filter Query")
options_layout.addWidget(self.filter_query)
self.iter_input = QtWidgets.QSpinBox(self)
self.iter_input.setValue(3)
self.iter_input.setMinimum(1)
self.iter_input.setMaximum(10)
self.iter_input.setEnabled(False)
self.iter_label = QtWidgets.QLabel("Number of Iterations:")
self.iter_label.setEnabled(False)
options_layout.addWidget(self.iter_label)
options_layout.addWidget(self.iter_input)
self.threshold_input = QtWidgets.QDoubleSpinBox(self)
self.threshold_input.setDecimals(3)
self.threshold_input.setValue(0.005)
self.threshold_input.setMinimum(1e-10)
self.threshold_input.setMaximum(1e6)
self.threshold_input.setEnabled(False)
self.threshold_label = QtWidgets.QLabel("Inclusion Threshold:")
self.threshold_label.setEnabled(False)
options_layout.addWidget(self.threshold_label)
options_layout.addWidget(self.threshold_input)
options_layout.addStretch()
#self.download_pdb = QtWidgets.QCheckBox()
# self.download_pdb.setChecked(False)
#self.download_pdb.setText("Download PDB for all templates")
# options_layout.addWidget(self.download_pdb)
database_layout = QtWidgets.QVBoxLayout()
database_layout.addWidget(QtWidgets.QLabel("Database:"))
self.database_choice = QtWidgets.QComboBox(self)
self.database_choice.addItem("NCBI PDB (non redundant)")
self.database_choice.addItem("NCBI PDB (all)")
self.database_choice.addItem("NCBI NR")
self.database_choice.setCurrentIndex(1)
database_layout.addWidget(self.database_choice)
database_layout.addStretch()
columns_layout = QtWidgets.QHBoxLayout()
columns_layout.addLayout(options_layout)
columns_layout.addLayout(database_layout)
self.layout.addLayout(columns_layout)
button_layout = QtWidgets.QHBoxLayout()
button_layout.addStretch()
button_layout.addWidget(self.button_remove)
button_layout.addWidget(self.button_cancel)
self.layout.addLayout(button_layout)
self.sizePolicy().setVerticalPolicy(QtWidgets.QSizePolicy.Maximum)
[docs] def runRemotely(self):
return self.remote_button.isChecked()
[docs] def getMatrix(self):
return self.matrix_choice.currentText()
[docs] def getExpValue(self):
return math.pow(10.0, self.evalue_input.value())
[docs] def getProgramName(self):
if self.blast_button.isChecked():
return "blastp"
else:
return "blastpgp"
[docs] def getGapCosts(self):
gap_costs = [(11, 1), (12, 1), (9, 2), (8, 2), (7, 2)]
return gap_costs[self.gap_choice.currentIndex()]
[docs] def getIThreshold(self):
return self.threshold_input.value()
[docs] def getNIter(self):
return self.iter_input.value()
[docs] def getWordSize(self):
if self.word_3_button.isChecked():
return 3
return 2
[docs] def getDatabase(self):
if self.database_choice.currentIndex() == 2:
return "nr"
return "pdb"
[docs] def getExpandHits(self):
if self.database_choice.currentIndex() == 1:
return True
return False
[docs] def getFilter(self):
if self.filter_query.isChecked():
return "true"
return "false"
[docs] def getAllSettings(self):
"""
Returns all settings.
"""
settings = {}
settings["database"] = self.getDatabase()
settings["matrix"] = self.getMatrix()
settings["filter"] = self.getFilter()
settings["ehits"] = self.getExpandHits()
settings["evalue"] = self.getExpValue()
settings["word_size"] = self.getWordSize()
settings["progname"] = self.getProgramName()
settings["i_threshold"] = self.getIThreshold()
settings["n_iter"] = self.getNIter()
gap_open, gap_extend = self.getGapCosts()
settings["gap_open"] = gap_open
settings["gap_extend"] = gap_extend
run_remotely = self.runRemotely()
settings["remotely"] = run_remotely
# BIOLUM-3968 - Limit remmote BLAST to 200 hits
if run_remotely:
settings['-num_hits'] = 200
return settings
[docs]class BlastResultsDialog(QtWidgets.QDialog):
"""
This class implements a BLAST search results panel.
"""
[docs] def __init__(self, parent):
# Initialize base class.
QtWidgets.QDialog.__init__(self, parent)
self.viewer = parent
self.setWindowTitle("BLAST Search Results")
self.selectButton = QtWidgets.QPushButton("Select Top")
self.topInput = QtWidgets.QLineEdit()
self.topInput.setValidator(QtGui.QIntValidator(0, 0, self.topInput))
self.topInput.setText("0")
self.clearButton = QtWidgets.QPushButton("Clear")
self.closeButton = QtWidgets.QPushButton("Close")
self.incorporateButton = QtWidgets.QPushButton(
"Incorporate Selected Rows")
self.spacer = QtWidgets.QWidget()
self.spacer.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Preferred)
self.buttonLayout = QtWidgets.QHBoxLayout()
self.buttonLayout.addWidget(self.selectButton)
self.buttonLayout.addWidget(self.topInput)
self.buttonLayout.addWidget(self.spacer)
self.buttonLayout.addWidget(self.clearButton)
self.buttonLayout.addWidget(self.closeButton)
self.buttonLayout.addWidget(self.incorporateButton)
self.header_labels = [
"ID", "E-value", "Score", "Length", "Identity", "Similarity",
"Homology", "% Gaps", "Sequence Name", "PDB Title",
"PDB Compound Name", "PDB Source", "Exp. Method", "Resolution",
"PDB Heteroatoms", "PDB Family"
]
self.resultsTable = QtWidgets.QTableWidget(0, len(self.header_labels))
self.resultsTable.setAlternatingRowColors(True)
self.table_font = self.resultsTable.font()
self.table_font.setPointSize(10)
self.resultsTable.setFont(self.table_font)
self.resultsTable.setHorizontalHeaderLabels(self.header_labels)
self.resultsTable.resizeColumnsToContents()
self.resultsTable.sortItems(1, QtCore.Qt.AscendingOrder)
self.resultsTable.setMouseTracking(True)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.addWidget(self.resultsTable)
self.remote_search = False
self.download_pdb = QtWidgets.QCheckBox()
self.download_pdb.setChecked(False)
self.download_pdb.setText(
"Download PDB structures for all incorporated templates")
self.layout.addWidget(self.download_pdb)
self.layout.addLayout(self.buttonLayout)
self.resultsTable.resizeColumnToContents(1)
self.resultsTable.horizontalHeader().setStretchLastSection(True)
self.resultsTable.setWordWrap(False)
self.incorporateButton.setFocus(QtCore.Qt.OtherFocusReason)
self.resultsTable.itemSelectionChanged.connect(self.selectionChanged)
self.resultsTable.cellEntered.connect(self.cellEntered)
self.selectButton.clicked.connect(self.selectTopRows)
self.closeButton.clicked.connect(self.closeDialog)
self.clearButton.clicked.connect(self.clearContents)
self.incorporateButton.clicked.connect(self.incorporateResults)
self.selecting = False
self.resize(self.resultsTable.width() + 15, self.resultsTable.height())
self.sequences = {}
self.sequence_group = None
self.has_pdb_info = False
self.pdb_info = {}
self.old_query = None
self.new_query = None
[docs] def closeDialog(self):
"""
Empties the list of selected sequences and closes the dialog.
"""
self.close()
[docs] def clearContents(self):
"""
Clears the dialog contents.
"""
self.resultsTable.clearContents()
for row in range(self.resultsTable.rowCount() - 1):
self.resultsTable.removeRow(0)
self.sequences = {}
self.resultsTable.resizeColumnsToContents()
[docs] def setSequenceGroup(self, sequence_group):
self.sequence_group = sequence_group
[docs] def setNewQuerySequence(self, old_query, new_query):
"""
Sets a replacement query sequence to be used if user accepts and
incorporates the results.
:param old_query: original query sequence in viewer
:type old_query: `Sequence`
:param new_query: modified sequence with gaps after BLAST search
:type new_query: `Sequence`
"""
self.old_query = old_query
self.new_query = new_query
[docs] def incorporateResults(self):
"""
This method prepares a list of sequences to incorporate
and closes the dialog.
"""
all_new_sequences = []
for index in self.resultsTable.selectionModel().selectedRows():
name = self.resultsTable.item(index.row(), 0).text() + ":" + \
self.resultsTable.item(index.row(),
self.header_labels.index("Sequence Name")).text()
if name in self.sequences:
new_sequence = self.sequences[name].copyForUndo(deep_copy=False)
all_new_sequences.append(new_sequence)
self.sequence_group.sequences.append(new_sequence)
if self.sequence_group:
self.sequence_group.unselectAllSequences()
if self.old_query in self.sequence_group.sequences:
# Replace query sequence with aligned one
idx = self.sequence_group.sequences.index(self.old_query)
seq = self.sequence_group.sequences[idx]
# Replace the sequence with the aligned one, while preseving
# the same reference/hash/memory location. Fix for BIOLUM-2416:
seq.__dict__.update(self.new_query.__dict__)
self.old_query = None
self.new_query = None
self.sequence_group.minimizeAlignment()
for res in self.sequence_group.reference.residues:
res.sequence = self.sequence_group.reference
for child in self.sequence_group.reference.children:
child.parent_sequence = self.sequence_group.reference
if self.viewer:
self.viewer.contents_changed = True
self.viewer.updateView()
self.hide()
if self.download_pdb.isChecked():
for seq in all_new_sequences:
seq.selected = True
if self.viewer:
self.viewer.downloadPDB(remote_query=not self.remote_search)
self.viewer.runClustal(ignore_selection=True)
if self.viewer:
self.viewer.contents_changed = True
self.viewer.updateView()
self.close()
[docs] def selectTopRows(self):
"""
Selects top rows according to the value of topInput field.
"""
self.resultsTable.setRangeSelected(
QtWidgets.QTableWidgetSelectionRange(
0, 0,
self.resultsTable.rowCount() - 1,
self.resultsTable.columnCount() - 1), False)
n_rows = int(self.topInput.text())
self.resultsTable.setRangeSelected(
QtWidgets.QTableWidgetSelectionRange(
0, 0, n_rows - 1,
self.resultsTable.columnCount() - 1), True)
[docs] def selectionChanged(self):
if self.selecting:
return
self.selecting = True
for item in self.resultsTable.selectedItems():
row = item.row()
# Select the entire row.
self.resultsTable.setRangeSelected(
QtWidgets.QTableWidgetSelectionRange(
row, 0, row,
self.resultsTable.columnCount() - 1), True)
self.selecting = False
[docs] def cellEntered(self, row, column):
"""
cellEntered signal handler for tool tip generation.
"""
if column == 14: # LIGAND column
item = self.resultsTable.item(row, column)
smiles = str(item.data(QtCore.Qt.UserRole))
if smiles:
item.setData(QtCore.Qt.ToolTipRole,
self.generate2DToolTip(smiles))
else:
item.setData(QtCore.Qt.ToolTipRole, "")
[docs] def addRow(self,
sequence,
name="",
score=0.0,
length=0,
coverage=0.0,
identity=0.0,
similarity=0.0,
homology=0.0,
resolution=-1.0,
pdb_id="",
evalue=0.0,
tooltip=None):
"""
This method adds a new row to the BLAST results table.
"""
row = self.resultsTable.rowCount()
self.resultsTable.insertRow(row)
new_item = QtWidgets.QTableWidgetItem(pdb_id)
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 0, new_item)
id = new_item.text()
new_item = QTableWidgetNumericalItem()
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
new_item.setData(QtCore.Qt.DisplayRole, evalue)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 1, new_item)
new_item = QTableWidgetNumericalItem()
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
new_item.setData(QtCore.Qt.DisplayRole, score)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 2, new_item)
new_item = QTableWidgetNumericalItem()
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
new_item.setData(QtCore.Qt.DisplayRole, length)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 3, new_item)
new_item = QTableWidgetNumericalItem()
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
new_item.setData(QtCore.Qt.DisplayRole, identity)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 4, new_item)
new_item = QTableWidgetNumericalItem()
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
new_item.setData(QtCore.Qt.DisplayRole, similarity)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 5, new_item)
new_item = QTableWidgetNumericalItem()
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
new_item.setData(QtCore.Qt.DisplayRole, homology)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 6, new_item)
new_item = QTableWidgetNumericalItem()
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
new_item.setData(QtCore.Qt.DisplayRole, coverage)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 7, new_item)
if not self.has_pdb_info:
self.initPDBInfo()
pdb_info_items = {
"TITLE:": 9,
"COMPND:": 10,
"SOURCE:": 11,
"EXPDTA:": 12,
"RESOLUTION:": 13,
"HETNAM:": 14,
"PFAM:": 15
}
if self.has_pdb_info:
#"PDB Title", "PDB Compound Name",
#"PDB Source", "Exp. Method", "Resolution",
#"PDB Heteroatoms", "PDB Family" ]
if pdb_id in list(self.pdb_info):
pdb_info = self.pdb_info[pdb_id]
for item in list(pdb_info_items):
if item in pdb_info:
new_item = QtWidgets.QTableWidgetItem(pdb_info[item])
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsEnabled)
if item == "HETNAM:":
if "HETSMILES:" in pdb_info:
new_item.setData(QtCore.Qt.UserRole,
pdb_info["HETSMILES:"])
elif tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, pdb_info_items[item],
new_item)
new_item = QtWidgets.QTableWidgetItem(name)
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
if tooltip:
new_item.setData(QtCore.Qt.ToolTipRole, tooltip)
self.resultsTable.setItem(row, 8, new_item)
id += ":" + new_item.text()
self.sequences[id] = sequence
self.topInput.validator().setTop(self.resultsTable.rowCount())
[docs] def initPDBInfo(self):
"""
Initialize PDB header info.
"""
if self.has_pdb_info:
return
psp_exec = os.getenv("PSP_EXEC")
if not psp_exec:
psp_exec = glob.glob(os.getenv("SCHRODINGER") + os.sep + "psp-v*")
if psp_exec:
psp_exec = psp_exec[0]
if psp_exec:
header_file_name = psp_exec + os.sep + ".." + os.sep + ".." + \
os.sep + "data" + os.sep + "headerinfo.dat"
try:
header_file = open(header_file_name, "r")
except:
header_file_name = psp_exec + os.sep + "data" + os.sep + \
"headerinfo.dat"
try:
header_file = open(header_file_name, "r")
except:
return False
else:
return False
try:
header_file = open(header_file_name, "r")
lines = header_file.readlines()
header_file.close()
except:
return False
self.pdb_info = {}
pdb_id = ""
for line in lines:
names = line.split(' ', 1)
if len(names) > 1:
key = names[0]
text = names[1].rstrip()
if key == "ID:":
pdb_id = text
self.pdb_info[pdb_id] = {}
elif pdb_id:
self.pdb_info[pdb_id][key] = text
self.has_pdb_info = True
return True
[docs] def updateTableGeometry(self):
self.resultsTable.resizeColumnsToContents()
self.resultsTable.resizeRowsToContents()
self.topInput.validator().setTop(self.resultsTable.rowCount())
self.topInput.setText(str(self.resultsTable.rowCount()))
[docs] def show(self):
"""
Shows the panel.
"""
QtWidgets.QDialog.show(self)
self.setCursor(QtCore.Qt.ArrowCursor)
[docs]def createBlastResultsDialog(parent=None):
"""
Creates and returns BLAST results dialog.
"""
return BlastResultsDialog(parent=parent)