import os
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from .maestro import maestroCalculateMinimumDistance
from .maestro import maestroGetLigandAnnotations
ANALYZE_DIALOG = None
[docs]def analyzeBindingSiteDialog(parent=None):
global ANALYZE_DIALOG
if ANALYZE_DIALOG is None:
ANALYZE_DIALOG = AnalyzeBindingSiteDialog(parent)
return ANALYZE_DIALOG
[docs]class AnalyzeBindingSiteDialog(QtWidgets.QDialog):
"""
This class implements simple pattern editor dialog.
"""
[docs] def __init__(self, parent):
# Initialize base class.
QtWidgets.QDialog.__init__(self, parent)
self.setWindowTitle("Analyze Binding Site")
self.okButton = QtWidgets.QPushButton("Analyze")
self.cancelButton = QtWidgets.QPushButton("Close")
self.exportButton = QtWidgets.QPushButton(
"Export results to CSV file...")
self.viewer = parent
self.group = None
self.current_sequence = None
self.spacer = QtWidgets.QWidget()
self.spacer.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Preferred)
self.min_range = QtWidgets.QSpinBox()
self.min_range.setValue(3)
self.min_range.setMinimum(0)
self.min_range.setMaximum(100)
self.min_range.setEnabled(True)
self.min_label = QtWidgets.QLabel("Scan from:")
self.min_label.setEnabled(True)
self.max_range = QtWidgets.QSpinBox()
self.max_range.setValue(10)
self.max_range.setMinimum(0)
self.max_range.setMaximum(100)
self.max_range.setEnabled(True)
self.max_label = QtWidgets.QLabel("to:")
self.max_label.setEnabled(True)
self.angstroms_label = QtWidgets.QLabel("angstroms")
self.settingsLayout = QtWidgets.QHBoxLayout()
self.settingsLayout.addWidget(self.min_label)
self.settingsLayout.addWidget(self.min_range)
self.settingsLayout.addWidget(self.max_label)
self.settingsLayout.addWidget(self.max_range)
self.settingsLayout.addWidget(self.angstroms_label)
self.settingsLayout.addStretch()
self.buttonLayout = QtWidgets.QHBoxLayout()
self.buttonLayout.addWidget(self.exportButton)
self.buttonLayout.addWidget(self.spacer)
self.buttonLayout.addWidget(self.okButton)
self.lowButtonLayout = QtWidgets.QHBoxLayout()
self.lowButtonLayout.addStretch()
self.lowButtonLayout.addWidget(self.cancelButton)
self.resultsTable = QtWidgets.QTableWidget(0, 4)
self.table_font = self.resultsTable.font()
self.table_font.setPointSize(10)
self.resultsTable.setFont(self.table_font)
self.tableHeader = [
"Distance (angstroms)", "Identity %", "Similarity %", "Homology %"
]
self.resultsTable.setHorizontalHeaderLabels(self.tableHeader)
self.resultsTable.resizeColumnsToContents()
self.resultsTable.sortItems(1, QtCore.Qt.AscendingOrder)
self.layout = QtWidgets.QVBoxLayout(self)
self.ligand_selector_layout = QtWidgets.QHBoxLayout()
self.layout.addLayout(self.ligand_selector_layout)
self.seq_label = QtWidgets.QLabel("Sequence:")
self.ligand_selector_layout.addWidget(self.seq_label)
self.seq_choice = QtWidgets.QComboBox(self)
self.seq_choice.currentIndexChanged.connect(self.seqCB)
self.ligand_selector_layout.addWidget(self.seq_choice)
self.lig_label = QtWidgets.QLabel("Ligand:")
self.ligand_selector_layout.addWidget(self.lig_label)
self.ligand_choice = QtWidgets.QComboBox(self)
self.ligand_choice.currentIndexChanged.connect(self.ligCB)
self.ligand_selector_layout.addWidget(self.ligand_choice)
self.ligand_selector_layout.addStretch()
self.tab_view = QtWidgets.QTabWidget(self)
self.tab_view.currentChanged.connect(self.currentChangedCB)
self.layout.addWidget(self.tab_view)
self.layout.addLayout(self.lowButtonLayout)
self.conservation_page = QtWidgets.QWidget()
self.conservation_page_layout = QtWidgets.QVBoxLayout(
self.conservation_page)
self.conservation_page.setLayout(self.conservation_page_layout)
self.value_layout = QtWidgets.QHBoxLayout()
self.value_layout.addWidget(
QtWidgets.QLabel(
"Sequence identity threshold in selected columns:"))
self.value_layout.addStretch()
self.value_box = QtWidgets.QLineEdit("100 %")
self.value_box.setReadOnly(True)
self.value_box.setAlignment(QtCore.Qt.AlignRight)
self.value_box.setMaxLength(5)
self.value_box.setFixedWidth(60)
self.value_layout.addWidget(self.value_box)
self.conservation_page_layout.addLayout(self.value_layout)
self.similarity_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal, self)
self.similarity_slider.setTickInterval(5)
self.similarity_slider.setMinimum(0)
self.similarity_slider.setMaximum(100)
self.similarity_slider.setValue(100)
self.similarity_slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.similarity_slider.valueChanged.connect(self.similaritySliderCB)
self.conservation_page_layout.addWidget(self.similarity_slider)
self.range_layout = QtWidgets.QHBoxLayout()
self.range_layout.addWidget(QtWidgets.QLabel("0 %"))
self.range_layout.addStretch()
self.range_layout.addWidget(QtWidgets.QLabel("100 %"))
self.conservation_page_layout.addLayout(self.range_layout)
self.conservation_page_layout.addStretch()
self.cons_button_layout = QtWidgets.QHBoxLayout()
self.button_remove_similar = QtWidgets.QPushButton(
"Remove Similar Sequences")
self.button_remove_similar.clicked.connect(
self.removeSimilarSequencesCB)
self.button_remove_divergent = QtWidgets.QPushButton(
"Remove Divergent Sequences")
self.button_remove_divergent.clicked.connect(
self.removeDivergentSequencesCB)
self.cons_button_layout.addWidget(self.button_remove_similar)
self.cons_button_layout.addWidget(self.button_remove_divergent)
self.conservation_page_layout.addLayout(self.cons_button_layout)
self.distance_page = QtWidgets.QWidget()
self.distance_page_layout = QtWidgets.QVBoxLayout(self.distance_page)
self.distance_page.setLayout(self.distance_page_layout)
self.tab_view.addTab(self.conservation_page, "Conservation Analysis")
self.tab_view.addTab(self.distance_page, "Distance Analysis")
self.distance_page_layout.addWidget(self.resultsTable)
self.distance_page_layout.addSpacing(5)
self.distance_page_layout.addLayout(self.settingsLayout)
self.distance_page_layout.addSpacing(5)
self.distance_page_layout.addLayout(self.buttonLayout)
self.resultsTable.horizontalHeader().setStretchLastSection(True)
self.resultsTable.setWordWrap(False)
self.cancelButton.clicked.connect(self.cancelClicked)
self.okButton.clicked.connect(self.okClicked)
self.exportButton.clicked.connect(self.exportClicked)
self.resize(self.resultsTable.width() + 15, self.resultsTable.height())
self.save_dialog = None
self.sequence = None
self.seq_list = []
self.ligand_list = []
self.similarity_value = 100
[docs] def currentChangedCB(self, index):
"""
Current tab changed callback.
"""
self.updateLigand()
[docs] def ligCB(self, index):
"""
Ligand combo box callback.
"""
if not self.ligand_list or index >= len(self.ligand_list):
return
if self.viewer and self.ligand_list:
self.viewer.contents_changed = True
self.viewer.updateView()
self.group.unselectAll()
self.viewer.selectLigandContacts(ligand=self.ligand_list[index])
self.group.expandSelection()
self.similaritySliderCB(self.similarity_value)
[docs] def seqCB(self, index):
"""
Sequence combo box callback.
"""
if not self.seq_list or index >= len(self.seq_list):
return
self.current_sequence = self.seq_list[index]
self.updateLigand()
self.similaritySliderCB(self.similarity_value)
[docs] def removeSimilarSequencesCB(self):
"""
Removes selected sequences. This function assumes similar sequences are
selected in the group, so it deletes the selected sequences in the group
and updates the dialog.
"""
if not self.group or not self.group.reference:
return
self.group.reference.selected = False
if self.viewer:
self.viewer.deleteSelectedSequences()
self.update()
[docs] def removeDivergentSequencesCB(self):
"""
Removes de-selected sequences. This function assumes similar sequences
are selected in the group, so it inverts the selection, deletes the
selected sequences in the group and updates the dialog.
"""
if not self.group or not self.group.reference:
return
self.group.invertSequenceSelection()
self.group.reference.selected = False
if self.viewer:
self.viewer.deleteSelectedSequences()
self.update()
[docs] def similaritySliderCB(self, value):
if not self.group:
return
self.value_box.setText(str(value) + " %")
self.similarity_value = value
self.group.unselectAllSequences()
self.group.selectRedundantSequences(value,
reference=self.current_sequence,
columns=True)
if self.viewer:
self.viewer.updateView()
[docs] def setSequence(self, sequence_group, sequence):
self.sequence = sequence
self.group = sequence_group
[docs] def updateLigand(self):
"""
Updates ligand list in the dialog. Called when either
group contents changes or current sequence changes.
"""
if not self.group or not self.current_sequence:
return
self.group.unselectAllSequences()
self.group.unselectAll()
self.current_sequence.selected = True
ligand_list = maestroGetLigandAnnotations(self.current_sequence)
self.ligand_list = ligand_list
self.ligand_choice.clear()
self.ligand_choice.setCurrentIndex(0)
if not ligand_list:
return
for lig in ligand_list:
self.ligand_choice.addItem(lig.short_name)
if self.viewer and ligand_list:
self.viewer.contents_changed = True
self.viewer.updateView()
self.viewer.selectLigandContacts(ligand=ligand_list[0])
self.group.expandSelection()
[docs] def update(self):
"""
View update event.
"""
self.seq_list = []
self.ligand_list = []
self.seq_choice.clear()
self.seq_choice.setCurrentIndex(0)
if not self.group:
return
for seq in self.group.sequences:
if seq.isValidProtein():
self.seq_choice.addItem(seq.short_name + "_" + seq.chain_id)
self.seq_list.append(seq)
if not self.seq_list:
return
self.current_sequence = self.seq_list[0]
self.updateLigand()
self.similaritySliderCB(self.similarity_value)
[docs] def okClicked(self):
"""
OK button callback
"""
if not self.sequence:
return
distances = maestroCalculateMinimumDistance(self.sequence)
self.resultsTable.clearContents()
for row in range(self.resultsTable.rowCount()):
self.resultsTable.removeRow(0)
min = self.min_range.value()
max = self.max_range.value()
initial = [False] * len(self.sequence.residues)
for idx, res in enumerate(self.sequence.residues):
initial[idx] = res.selected
for test in range(min, max + 1):
for idx, res in enumerate(self.sequence.residues):
if distances[idx] <= test * test:
res.selected = True
self.group.expandSelectionRef()
id = 0.0
sim = 0.0
hom = 0.0
cnt = 0
for seq in self.group.sequences:
if seq != self.group.reference and seq.isValidProtein():
id += seq.calcIdentity(self.sequence,
self.group.consider_gaps, True)
sim += self.group.reference.calcSimilarity(
seq, self.group.consider_gaps, True)
hom += self.group.reference.calcHomology(
seq, self.group.consider_gaps, True)
cnt += 1
if cnt == 0:
break
cnt = cnt * 0.01 # Convert to percents
id /= cnt
sim /= cnt
hom /= cnt
row = self.resultsTable.rowCount()
self.resultsTable.insertRow(row)
for col in range(4):
new_item = QTableWidgetNumericalItem()
new_item.setFont(self.table_font)
new_item.setFlags(QtCore.Qt.ItemIsEnabled)
if col == 0:
new_item.setData(QtCore.Qt.DisplayRole, test)
elif col == 1:
new_item.setData(QtCore.Qt.DisplayRole, id)
elif col == 2:
new_item.setData(QtCore.Qt.DisplayRole, sim)
elif col == 3:
new_item.setData(QtCore.Qt.DisplayRole, hom)
self.resultsTable.setItem(row, col, new_item)
self.group.deselectAll()
for idx, res in enumerate(self.sequence.residues):
res.selected = initial[idx]
self.resultsTable.resizeColumnsToContents()
self.resultsTable.resizeColumnToContents(4)
[docs] def cancelClicked(self):
"""
Cancel button callback
"""
self.hide()
[docs] def exportClicked(self):
"""
Export button callback
"""
if self.resultsTable.rowCount() < 1:
return False
if not self.save_dialog:
self.save_dialog = QtWidgets.QFileDialog()
self.save_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
self.save_dialog.setOption(
QtWidgets.QFileDialog.DontUseNativeDialog)
self.save_dialog.setWindowTitle("Export Results")
self.save_dialog.setViewMode(QtWidgets.QFileDialog.Detail)
self.save_dialog.setNameFilters(["CSV File (*.csv)"])
self.save_dialog.exec_()
if self.save_dialog.result():
file_names = self.save_dialog.selectedFiles()
if len(file_names) < 1:
return False
file_name = file_names[0]
if len(file_name) == 0:
return False
name, ext = os.path.splitext(str(file_name))
if (not ext) or (ext != ".csv"):
file_name += ".csv"
outfile = open(file_name, "w")
outfile.write("Distance,Identity,Similarity,Homology\n")
for row in range(self.resultsTable.rowCount()):
line = ""
for col in range(self.resultsTable.columnCount()):
if col > 0:
line += ","
line += self.resultsTable.item(row, col).text()
line += "\n"
outfile.write(line)
outfile.close()
[docs] def show(self):
"""
Show and update the dialog.
"""
self.update()
QtWidgets.QDialog.show(self)