"""
This class implements a sequence viewer widget.
The sequence viewer widget is a QSplitter object that includes three
child widgets: tree area, name area and sequence area.
Copyright Schrodinger, LLC. All rights reserved.
"""
# Contributors: Piotr Rotkiewicz
import os
import pickle
from past.utils import old_div
from schrodinger import get_maestro
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtPrintSupport
from schrodinger.Qt import QtWidgets
# These are common dialogs, they can also work in text mode
from . import align
from . import constants
from . import dialogs
from . import fileio
from . import jobs
from . import maestro as maestro_helpers
from . import predictors
from . import prime
from . import sequence
from . import sequence_group
# Import external dialogs and panels
from .align_gui import AlignmentSettingsDialog
from .analyze_gui import analyzeBindingSiteDialog
from .associate_gui import AssociateEntryPanel
from .blast_gui import createBlastResultsDialog
from .blast_gui import createBlastSettingsDialog
from .compare_sequences_gui import CompareSequencesDialog
from .contact_map import ContactMap
from .jobs_gui import createJobProgressDialog
from .jobs_gui import createJobSettingsDialog
from .name_area import NameArea
from .prime_gui import primeValidateBuildMode
from .prime_gui import setPrimeSequenceGroup
from .prime_gui import showPrimeSettingsDialog
from .prime_gui import updatePrimeQueryList
from .sequence_area import SequenceArea
from .sequence_viewer_gui import RemoteQueryDialog
from .sequence_viewer_gui import RemoveRedundancyDialog
from .sequence_viewer_gui import RenumberResiduesDialog
from .sequence_viewer_gui import SequenceEditorDialog
from .sequence_viewer_gui import WeightColorsDialog
from .tree_area import TreeArea
from .undo import UndoStack
maestro = get_maestro()
try:
from schrodinger.application.prime.packages.antibody import \
SeqType as PspSeqType
except:
PspSeqType = None
# The SequenceViewer is a container for the name area, sequence area
# and tree area.
class SequenceViewer(QtWidgets.QSplitter):
"""
The sequence viewer widget displays and manipulates sequences and
associated data. The widget is a QSplitter that includes tree coupled
areas: sequence area, name area and tree area.
"""
def __init__(self, parent=None):
QtWidgets.QSplitter.__init__(self, parent) # Initialize base class.
self.setFrameStyle(QtWidgets.QFrame.Panel |
QtWidgets.QFrame.Sunken) #of widget.
self.sequence_group = sequence_group.SequenceGroup()
self.sequence_group_list = [] #in current project.
#: Create tree area widget
self.tree_area = TreeArea()
self.tree_area.setViewer(self) #Tree area's parent viewer.
self.addWidget(self.tree_area)
# Create name area widget.
self.name_area = NameArea()
self.name_area.setViewer(self) #Name area's parent viewer.
self.addWidget(self.name_area)
# Create sequence area widget.
self.sequence_area = SequenceArea()
self.sequence_area.setViewer(self) #Sequence area's parent viewer.
self.addWidget(self.sequence_area)
# Initially, tree area not visible (resized to 0).
self.setSizes([0, 150, 400]) #Of children widgets.
self.rows = []
self.background_color = QtGui.QColor(QtCore.Qt.white)
self.inv_background_color = QtGui.QColor(QtCore.Qt.black) #inverted
self.font_size = 12
self.font_width = 6
self.font_height = 12
self.font_xheight = 5
self.zoom_factor = 1.0 #horizontal zoom factor.
self.cell_width = 12
self.wrapped = False #Wrapping mode.
self.wrapped_width = None #Max width of wrapped area.
self.chain_name_only = False #sequence name format.
self.left_column = 0 #position, used in unwrapped mode.
self.top_row = 0 #position, used in wrapped mode.
self.max_columns = 0
self.separator_scale = 100 #of row percentage. (0 disables separator)
self.crop_image = False #crop output image to minimize margins.
self.mode = constants.MODE_GRAB_AND_DRAG #Current sequence viewer operating mode.
self.has_tooltips = True #Tool tips enabled.
# If this flag is enabled, the sequence right to mouse cursor position
# will be locked during slide or grab-and-drag operations.
self.lock_downstream = True #lock downstream sequences.
self.sequence_group.color_mode = constants.COLOR_SIDECHAIN_CHEMISTRY
self.auto_color = False #switches to white for dark background.
self.margin = 3 #size
self.has_ruler = True
self.average_columns = False #avg colors in columns
self.weight_colors = False #by alignment strength
self.weight_colors_by_identity = False #by sequence identity
self.weight_colors_by_difference = False #by sequence difference
#: Pad alignment with gaps. If enabled, all sequences will be padded
#: with gaps so that they all have identical lengths.
self.padded = True
self.use_mouse_across = False #across rows
self.name_popup_menu = None
self.tree_popup_menu = None
self.sequence_popup_menu = None
self.mutate = False #as type
#: State stack for undo/redo.
self.undo_stack = UndoStack()
#: Display percentange identity on a right side of the panel.
self.display_identity = False
self.display_similarity = False
self.display_homology = False
self.display_score = False
#: Update flags
self.contents_changed = False
self.alignment_changed = False
self.selection_changed = False
self.display_dots = False #If True, identities display as dots.
# Fonts
self.original_font = None
self.header_font = None
self.ruler_font = None
self.bold_font = None
self.italic_font = None
# Displays
self.display_boundaries = False
self.group_annotations = False
self.min_weight_identity = 20.0
self.max_weight_identity = 100.0
self.statistics_status_bar = None
self.message_status_bar = None
self.use_colors = True
self.set_constraints = False
self.set_query_constraints = False
# Syncing
self.auto_synchronize = True #with Maestro.
self.auto_profile = True #automatically calculate internal seq profile.
self.build_mode = False
self.last_index = 0
# Use Courier.
# @note: this is not working properly on Ubuntu.
font = QtGui.QFont("Courier", self.font_size)
font.setStyleHint(QtGui.QFont.Courier)
if not font.exactMatch():
# Workaround for Windows.
font = QtGui.QFont("Courier New", self.font_size)
if not font.exactMatch():
# Workaround for Ubuntu.
font = QtGui.QFont("Courier 10 Pitch", self.font_size)
self.setFont(font)
# Use Courier.
# @note: this is not working properly on Ubuntu.
self.bold_font = QtGui.QFont("Courier", self.font_size)
self.bold_font.setStyleHint(QtGui.QFont.Courier)
if not self.bold_font.exactMatch():
# Workaround for Windows.
self.bold_font = QtGui.QFont("Courier New", self.font_size)
if not self.bold_font.exactMatch():
# Workaround for Ubuntu.
self.bold_font = QtGui.QFont("Courier 10 Pitch", self.font_size)
self.bold_font.setBold(True)
# Use Courier.
# @note: this is not working properly on Ubuntu.
self.italic_font = QtGui.QFont("Courier", self.font_size)
self.italic_font.setStyleHint(QtGui.QFont.Courier)
if not self.italic_font.exactMatch():
# Workaround for Windows.
self.italic_font = QtGui.QFont("Courier New", self.font_size)
if not self.italic_font.exactMatch():
# Workaround for Ubuntu.
self.italic_font = QtGui.QFont("Courier 10 Pitch", self.font_size)
self.italic_font.setItalic(True)
self.job_progress_dialog = createJobProgressDialog("Job Progress",
parent=self)
self.remote_query_dialog = RemoteQueryDialog(self)
self.alignment_settings_dialog = AlignmentSettingsDialog(self)
self.always_ask_action = None
self.query_tabs = None
self.has_header_row = False
self.last_project_path = "" #Path to currently open project.
self.save_state = False #current state to Maestro proj when MSV closes.
dialogs.set_parent_widget(self)
self.updateFontSize(self.font_size)
self.command_dict = None
#: Callbacks
self.cb_residue_selection_changed = None
self.cb_sequence_selection_changed = None
self.cb_contents_changed = None
self.maestro_busy = False
self.blast_settings_dialog = None
self.blast_results_dialog = None
self.use_maestro_entry_title = True #as seq name
self.incorporate_scratch_entry = False
self.update_annotations_menu = False #parent's annotations menu
self.feedback_label = None #QLabel widget
self.auto_resize = False #name area on mouse over
self.associate_dialog = None #Entry dialog
self.hide_empty_lines = False
#: Global job settings
self.job_settings = {
'command_line': ['-HOST', 'localhost'],
'keep_files': False,
'temporary_dir': True
}
self.ready_to_save = True #Set after updateView, reset by proj export.
self.compare_sequences_dialog = None
def setPadded(self, padded):
"""
Toggles alignment padding.
:type padded: bool
:param padded: If True, enable padded mode, False - disable it.
"""
self.padded = padded
self.updateView()
def setMouseAcross(self, enabled):
"""
Toggles use mouse across rows mode.
:type enabled: bool
:param enabled: if True, enable "mouse across rows" mode, otherwise
disable it
"""
self.use_mouse_across = enabled
def setBackgroundColor(self, color=(255, 255, 255)):
"""
Sets a background color.
:type color: (int, int, int)
:param color: Background color RGB tuple.
"""
r, g, b = color
self.background_color = QtGui.QColor(r, g, b)
self.sequence_group.background_color = color
self.inv_background_color = QtGui.QColor(QtCore.Qt.black)
if old_div((r + g + b), 3) < 127:
self.inv_background_color = QtGui.QColor(QtCore.Qt.white)
self.sequence_group.inv_background_color = (
self.inv_background_color.red(), self.inv_background_color.green(),
self.inv_background_color.blue())
self.sequence_group.updateVariableSequences()
self.sequence_area.cacheSSAImages()
self.updateView(update_colors=True)
def updateFontSize(self, size=None):
"""
Updates sequence viewer font size in all child widgets. Recomputes
all font size dependent variables.
:type size: int
:param size: new font size
"""
if size is None:
size = self.font_size
else:
self.font_size = size
# Use Courier.
# @note: this is not working properly on Ubuntu.
font = QtGui.QFont("Courier", self.font_size)
font.setStyleHint(QtGui.QFont.Courier)
if not font.exactMatch():
# Workaround for Windows.
font = QtGui.QFont("Courier New", self.font_size)
if not font.exactMatch():
# Workaround for Ubuntu.
font = QtGui.QFont("Courier 10 Pitch", self.font_size)
font.setPointSize(size)
self.setFont(font)
# Use Courier.
# @note: this is not working properly on Ubuntu.
self.bold_font = QtGui.QFont("Courier", self.font_size)
self.bold_font.setStyleHint(QtGui.QFont.Courier)
if not self.bold_font.exactMatch():
# Workaround for Windows.
self.bold_font = QtGui.QFont("Courier New", self.font_size)
if not self.bold_font.exactMatch():
# Workaround for Ubuntu.
self.bold_font = QtGui.QFont("Courier 10 Pitch", self.font_size)
self.bold_font.setBold(True)
font.setStyleHint(QtGui.QFont.Courier)
font_metrics = QtGui.QFontMetrics(font)
self.bold_font.setPointSize(size)
# Calculate font width.
self.font_width = font_metrics.horizontalAdvance(' ') + 1
self.font_height = font_metrics.overlinePos() + \
font_metrics.underlinePos()
self.font_vertical_offset = font_metrics.underlinePos() - \
self.font_height - 2
self.font_xheight = 0.75 * font_metrics.xHeight()
self.cell_width = self.zoom_factor * self.font_width
if self.cell_width < 1.0:
self.cell_width = 1.0
self.cell_width = int(self.cell_width)
height = self.height() - self.margin
if self.has_header_row:
height -= 20
self.max_columns = self.maxColumns()
self.setFont(font)
# Set new font for all children,
self.name_area.setFont(font)
self.sequence_area.setFont(font)
self.tree_area.setFont(font)
# Remember the original font size with default charactr spacing.
self.original_font = QtGui.QFont(self.font())
self.original_font.setLetterSpacing(QtGui.QFont.AbsoluteSpacing, 0.0)
self.font().setLetterSpacing(QtGui.QFont.AbsoluteSpacing, 1.0)
self.header_font = QtGui.QFont("Arial")
self.header_font.setPointSize(12)
# This is an ugly hack to have the font actually rebuilt. Otherwise,
# Qt will not honor the letter spacing settings.
self.original_font.setPointSize(self.original_font.pointSize() - 1)
# Create a slightly smaller font for the ruler row.
self.ruler_font = QtGui.QFont(self.font())
size = self.ruler_font.pointSize() - 3
if size < 8:
size = 8
self.ruler_font.setPointSize(size)
self.sequence_area.cacheSSAImages()
self.updateView()
def setWrapped(self, wrapped):
"""
This method toggles sequence wrapping mode.
:type wrapped: bool
:param wrapped: if True, enable wrapped mode, otherwise - disable it
"""
self.wrapped = wrapped
if not self.wrapped:
self.sequence_area.horizontal_scroll_bar.show()
self.sequence_area.horizontal_scroll_bar.setValue(0)
self.sequence_area.vertical_scroll_bar.setValue(0)
self.left_column = 0
self.top_row = 0
else:
self.sequence_area.horizontal_scroll_bar.hide()
self.sequence_area.vertical_scroll_bar.setValue(0)
self.top_row = 0
self.updateView()
def hideSelectedSequences(self):
"""
Hides all selected sequences.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Hide Selected Sequences")
self.sequence_group.hideSelected()
self.contents_changed = True
self.updateView()
def deleteSelectedSequences(self):
"""
Deletes all selected sequences.
:rtype: bool
:return: True if all sequences were removed
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Delete Selected Sequences")
self.excludeSelected()
result = self.sequence_group.deleteSelected()
self.sequence_area.vertical_scroll_bar.setValue(0)
self.contents_changed = True
if not self.sequence_group.sequences:
self.sequence_group.user_annotations = []
self.updateView()
return result
def showAllSequences(self):
"""
Shows all sequences.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Show All Sequences")
self.sequence_group.showAll()
self.contents_changed = True
self.updateView()
def fillGaps(self):
"""
Fill a selected region with gaps.
"""
self.sequence_group.fillGaps()
self.contents_changed = True
self.updateView()
def removeGaps(self):
"""
Removes all gaps.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Remove Gaps")
self.sequence_group.removeAllGaps()
self.alignment_changed = True
self.updateView()
def deleteSelectedResidues(self):
"""
Deletes all selected residues.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Delete Selected Residues")
if self.mutate:
asl = maestro_helpers.maestroGetSelectedResiduesASL(self)
if asl:
try:
maestro.command("delete " + asl)
except:
pass
self.sequence_group.deleteSelectedResidues()
self.selection_changed = True
self.updateView()
def invertSelection(self):
"""
Inverts residue selection.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Invert Selection")
self.sequence_group.invertSelection()
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def deselectAll(self):
"""
Deselects all residues.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Deselect All")
self.sequence_group.deselectAll()
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def selectAll(self):
"""
Selects all residues.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Select All")
self.sequence_group.selectAll()
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def minimizeAlignment(self):
"""
Removes all gaps.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Remove Empty Columns")
self.sequence_group.minimizeAlignment()
self.contents_changed = True
self.updateView()
def lockGaps(self):
"""
Locks gaps.
"""
self.sequence_group.lockGaps()
self.update()
def unlockGaps(self):
"""
Unlocks gaps.
"""
self.sequence_group.unlockGaps()
self.update()
def selectAlignedBlocks(self):
"""
Selects aligned blocks (the sequence regions without gaps).
"""
self.sequence_group.selectAlignedBlocks()
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def selectStructureBlocks(self):
"""
Selects blocks that have structure.
"""
self.sequence_group.selectStructureBlocks()
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def selectIdentities(self):
"""
Selects identical residues in columns.
"""
self.sequence_group.selectIdentities()
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def setHasTooltips(self, has_tooltips):
"""
Toggles mouse hover tooltips.
:type has_tooltips: bool
:param has_tooltips: if True, enable tooltips, if False -disable them
"""
self.has_tooltips = has_tooltips
def setHasHeaderRow(self, has_header_row):
"""
Toggles header row.
:type has_tooltips: bool
:param has_tooltips: If True, enable the header row,
if False - disable it
"""
self.has_header_row = has_header_row
self.update()
def setHasRuler(self, has_ruler):
"""
Toggles the ruler.
:type has_ruler: bool
:param has_ruler: if True - enable the ruler, otherwise - disable it.
"""
self.has_ruler = has_ruler
self.updateView()
def deleteAnnotations(self):
"""
Deletes all annotations.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Delete Annotations")
self.sequence_group.deleteAnnotations()
self.contents_changed = True
self.updateView()
def deletePredictions(self):
"""
Deletes all predictions.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Delete Predictions")
self.sequence_group.deletePredictions()
self.contents_changed = True
self.updateView()
def loadFile(self,
file_name,
merge=False,
replace=False,
to_maestro=False,
translate=False,
new_list=None,
maestro_include=True):
"""
Loads a sequence file and merges the read sequences with current
sequence viewer contents.
:note: This is a default behavior. To replace current contents with
new sequences, clear the sequence viewer contents and then load
the file.
:type file_name: str
:param file_name: name of the file to be read.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Load File")
if maestro and to_maestro:
name, ext = os.path.splitext(str(file_name))
from_maestro = (ext == ".mae") or (ext == ".maegz") \
or (file_name[-7:] == ".mae.gz")
if from_maestro or \
(to_maestro and ((ext == ".pdb") or (ext == ".ent"))):
if from_maestro:
tmp_status = self.auto_synchronize
self.auto_synchronize = False
current_entries = maestro_helpers.maestroGetListOfEntryIDs()
wsreplace = maestro.get_command_option(
"entryimport", "wsreplace")
wsinclude = maestro.get_command_option(
"entryimport", "wsinclude")
format = maestro.get_command_option("entryimport", "format")
if maestro_include:
maestro.command("entryimport "
" wsreplace=false format=maestro " +
" \"" + str(file_name) + "\"")
else:
maestro.command("entryimport "
" wsreplace=false wsinclude=none "
"format=maestro " + " \"" +
str(file_name) + "\"")
# Revert to original Maestro command settings.
maestro.command("entryimport" + " wsreplace=" + wsreplace +
" wsinclude=" + wsinclude + " format=" +
format)
self.auto_synchronize = True
maestro_helpers.maestroIncorporateEntries(
self.sequence_group,
what="all",
ignore=current_entries,
include=to_maestro,
align_func=jobs.pdb_align_structure_to_sequence,
use_title=self.use_maestro_entry_title,
viewer=self)
self.auto_synchronize = tmp_status
result = True
if new_list is not None:
for seq in self.sequence_group.sequences:
if seq.maestro_entry_id and \
seq.maestro_entry_id not in current_entries:
new_list.append(seq)
else:
result = jobs.incorporatePDB(
self.sequence_group,
file_name,
maestro_include=maestro_include,
new_list=new_list,
viewer=self)
self.contents_changed = True
self.updateView()
return result
tmp_group = sequence_group.SequenceGroup()
result = fileio.load_file(
tmp_group,
file_name,
align_func=jobs.pdb_align_structure_to_sequence)
if result:
new_sequence_list = tmp_group.sequences
if replace:
new_sequence_list = []
for seq in tmp_group.sequences:
found = False
for original in self.sequence_group.sequences:
if original.gaplessLength() == seq.gaplessLength() and\
original.compare(seq) == 1.0:
found = True
seq.residues = original.propagateGaps(
seq, parent_sequence=seq)
seq.propagateGapsToChildren()
self.sequence_group.sequences[
self.sequence_group.sequences.index(
original)] = seq
break
if not found:
new_sequence_list.append(seq)
if translate:
for seq in new_sequence_list:
if seq.isDNA():
seq.translateDNA()
self.sequence_group.sequences += new_sequence_list
if new_list is not None:
new_list += new_sequence_list
if merge:
last_sequence = None
for seq in self.sequence_group.sequences:
if seq.type == constants.SEQ_AMINO_ACIDS:
last_sequence = seq
if last_sequence and self.sequence_group.reference:
align.align(self.sequence_group.reference,
last_sequence,
scoring_matrix=constants.BLOSUM62,
gap_open_penalty=-10,
gap_extend_penalty=-1,
merge=True,
sequence_group=self.sequence_group)
self.contents_changed = True
self.alignment_changed = True
self.updateView()
return result
def saveFile(self,
file_name,
save_annotations=False,
selected_only=False,
save_similarity=False,
format="FASTA"):
"""
Saves sequences to a file.
:type file_name: str
:param file_name: name of the output file
:rtype: bool
:return: True if file successfully saved, False otherwise.
"""
try:
name, ext = os.path.splitext(file_name)
except:
ext = ""
if format.startswith("FAS"):
if not ext:
file_name += ".fasta"
return fileio.save_fasta_file(self.sequence_group,
file_name,
save_annotations=save_annotations,
save_similarity=save_similarity,
selected_only=selected_only)
elif format.startswith("T"):
if not ext:
file_name += ".txt"
return fileio.save_fasta_file(self.sequence_group,
file_name,
save_annotations=save_annotations,
selected_only=selected_only,
save_similarity=save_similarity,
as_text=True)
def setColorMode(self, color_mode):
"""
Colors sequences using a specified color mode.
:type color_mode: int
:param color_mode: Color mode used to color the sequences.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Change Color Mode")
self.sequence_group.color_mode = color_mode
self.sequence_group.colorSequences(self.sequence_group.color_mode)
self.updateView(update_colors=True)
def colorSequences(self, color=None):
"""
Sets arbitrary color to the sequences.
"""
self.sequence_group.color_mode = constants.COLOR_CUSTOM
if color:
self.sequence_group.custom_color = color
self.sequence_group.colorSequences(self.sequence_group.color_mode,
color=color)
self.updateView(update_colors=True)
def propagateColors(self):
"""
Propagates colors to Maestro workspace.
"""
for seq in self.sequence_group.sequences:
if seq.isValidProtein() and seq.from_maestro:
maestro_helpers.propagateColorsToMaestro(self, seq)
def clearSequences(self):
"""
Removes all sequences and updates the viewer.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="New Project")
self.sequence_group.clear()
self.contents_changed = True
self.updateView()
def expandAllSequences(self):
"""
Expands all sequences.
"""
self.sequence_group.showAllChildren()
self.updateView()
def collapseAllSequences(self):
"""
Collapses all sequences.
"""
self.sequence_group.hideAllChildren()
self.updateView()
def addConsensus(self, toggle=False, update=True):
"""
Adds a consensus annotation sequence.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Add Consensus Sequence")
self.sequence_group.addConsensusSequence(toggle=toggle)
if update:
self.contents_changed = True
self.updateView()
def addSymbols(self, toggle=False, update=True):
"""
Adds a symbols sequence.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Add Consensus Symbols")
self.sequence_group.addConsensusSymbols(toggle=toggle)
if update:
self.contents_changed = True
self.updateView()
def toggleHistory(self):
"""
Toggles changes tracking feature.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Track Changes")
self.sequence_group.toggleHistory()
self.updateView()
def resetHistory(self):
"""
Toggles changes tracking feature.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Reset History")
self.sequence_group.resetHistory()
self.updateView()
def addMeanHydrophobicity(self, toggle=False, update=True):
"""
Adds a mean hydrophobicity annotation.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Add Mean Hydrophobicity")
self.sequence_group.addMeanHydrophobicity(toggle=toggle)
if update:
self.contents_changed = True
self.updateView()
def addMeanPI(self, toggle=False, update=True):
"""
Adds a mean isoelectric point annotation.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Add Mean PI")
self.sequence_group.addMeanPI(toggle=toggle)
if update:
self.contents_changed = True
self.updateView()
def addSequenceLogo(self, toggle=False, update=True):
"""
Adds a sequence logo annotation.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Add Sequence Logo")
self.sequence_group.addSequenceLogo(toggle=toggle)
if update:
self.contents_changed = True
self.updateView()
def zoomIn(self):
"""
Increases zoom factor and updates sequence viewer contents.
"""
if self.zoom_factor < 1.0:
self.zoom_factor += 0.2
else:
self.zoom_factor += 1.0
if self.zoom_factor > 4.0:
self.zoom_factor = 4.0
self.updateFontSize(self.font().pointSize())
# self.updateView()
def zoomOut(self):
"""
Decreases zoom factor and updates sequence viewer contents.
"""
if self.zoom_factor > 1.0:
self.zoom_factor -= 1.0
else:
self.zoom_factor -= 0.2
if self.zoom_factor <= 0.2:
self.zoom_factor = 0.2
self.updateFontSize(self.font().pointSize())
# self.updateView()
def addAnnotation(self, annotation_type, remove=False):
"""
Adds a new annotation sequence to selected sequences or to all
sequences if no sequence is selected.
:type annotation_type: int
:param annotation_type: Type of the annotation sequence.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Add Annotation")
self.sequence_group.addAnnotation(annotation_type, remove=remove)
self.contents_changed = True
self.updateView()
def addAllColorBlocks(self):
"""
Adds all available color blocks annotations to selected or all
sequences in the sequence group.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Add All Color Blocks")
for ann in constants.COLOR_BLOCKS:
self.sequence_group.addAnnotation(ann)
self.contents_changed = True
self.updateView()
def removeAllColorBlocks(self):
"""
Removes all color blocks annotations from selected or all
sequences in the sequence group.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Remove All Color Blocks")
for ann in constants.COLOR_BLOCKS:
self.sequence_group.addAnnotation(ann, remove=True)
self.contents_changed = True
self.updateView()
def updateView(self,
generate_rows=True,
update_colors=False,
repaint=True,
immediately=False):
"""
Updates the sequence viewer, re-generates profile and re-colors
the sequences. This should be called every time sequence group
contents changes.
:note: This method may take a long time to execute if there are many
sequences in the group. Consider making profile generation optional.
:type generate_rows: bool
:param generate_rows: Optional parameter. If False, the method will
not re-generate rows (default=True).
:type.update: bool
:param.update: Optional parameter. If False, the method will not
update the viewer contents (default=True).
"""
if self.sequence_group:
if self.has_ruler:
self.sequence_group.addRuler()
else:
self.sequence_group.removeRuler()
if (self.alignment_changed or self.contents_changed or
(self.selection_changed
and self.sequence_group.identity_in_columns)) and \
self.auto_profile:
self.sequence_group.removeTerminalGaps()
if self.padded:
self.sequence_group.padAlignment()
self.sequence_group.calculateProfile()
self.sequence_group.updateVariableSequences()
update_colors = True
if update_colors:
self.sequence_group.colorSequences()
if self.average_columns:
self.sequence_group.colorAverageColumnColors()
if self.weight_colors:
self.sequence_group.colorWeightByAlignmentStrength(
self.min_weight_identity, self.max_weight_identity)
if self.weight_colors_by_identity:
self.sequence_group.colorByIdentity()
elif self.weight_colors_by_difference:
self.sequence_group.colorByDifference()
if generate_rows:
self.generateRows()
if repaint:
self.update()
if immediately:
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
if self.contents_changed:
self.ready_to_save = True
primeValidateBuildMode()
updatePrimeQueryList(refresh_ligands=True)
self.updateAnnotationsMenu()
if self.cb_contents_changed:
self.cb_contents_changed()
if self.selection_changed:
self.ready_to_save = True
maestro_helpers.maestroSelectResiduesInWorkspace(self)
if self.cb_residue_selection_changed:
self.cb_residue_selection_changed()
if self.alignment_changed:
self.ready_to_save = True
self.contents_changed = False
self.alignment_changed = False
self.selection_changed = False
def updateStatusBar(self):
"""
Updates bottom status bar.
"""
total, selected, maestro, hidden = self.sequence_group.statistics()
ss = "s"
if total == 1:
ss = ""
status = str(total) + " sequence" + ss + " total, " + \
str(selected) + " selected, " + \
str(maestro) + " from Maestro, " + \
str(hidden) + " hidden. "
if self.sequence_group.reference:
status += "Query sequence: " + \
self.sequence_group.reference.short_name
else:
status += "Query sequence: None"
if self.statistics_status_bar:
self.statistics_status_bar.setText(status)
def setMode(self, mode):
"""
Sets sequence viewer operation mode.
:note: See constants.py for more details on what modes are available.
:type mode: int
:param mode: Sequence viewer mode.
"""
self.mode = mode
self.sequence_area.cursor_enabled = False
self.update()
def getMode(self):
"""
Gets current sequence viewer operational mode.
:rtype: int
:return: Current sequence viewer mode.
"""
return self.mode
def auxBorderWidth(self):
"""
Calculates a width of sequence area border that includes auxiliary
information (sequence similarity, borders, etc).
"""
aux_right_column_width = 0
aux_left_column_width = 0
if self.display_identity or \
self.display_similarity or \
self.display_homology:
aux_right_column_width = 4 * self.font_width
elif self.display_score:
aux_right_column_width = 5 * self.font_width
if self.display_boundaries:
aux_right_column_width += 7 * self.font_width
aux_left_column_width = 5 * self.font_width
return aux_right_column_width + aux_left_column_width
def maxColumns(self,
custom_width=None,
custom_height=None,
calculate_width=False):
"""
Calculates a maximum number of sequence columns that can fit
into the viewer.
:rtype: int or tuple
:return: maximum number of columns or max number of columns
and calculated width when calculate_width == True
"""
aux_right_column_width = 0
aux_left_column_width = 0
if self.display_identity or self.display_similarity or \
self.display_homology:
aux_right_column_width = 4 * self.font_width
elif self.display_score:
aux_right_column_width = 5 * self.font_width
if self.display_boundaries:
aux_right_column_width += 7 * self.font_width
aux_left_column_width = 5 * self.font_width
if custom_width is None:
custom_width = self.sequence_area.width()
if self.wrapped_width:
custom_width = self.wrapped_width * self.cell_width \
+ aux_left_column_width + aux_right_column_width
max_columns = old_div((custom_width - 2 * self.margin - 15 -
aux_left_column_width - aux_right_column_width),
self.cell_width)
if max_columns < 1:
max_columns = 1
if self.wrapped_width:
max_columns = self.wrapped_width
if calculate_width:
return (max_columns, custom_width)
else:
return max_columns
def generateRows(self,
use_max_length=False,
custom_width=None,
custom_height=None):
"""
Generate rows that can be directly displayed by the sequence area
widget. This method is relatively fast, because it doesn't generate
actual sequence chunks, but rather calculates pointers.
"""
self.rows = []
actual_width = 0
actual_height = 0
max_length = self.maxColumns(custom_width=custom_width,
custom_height=custom_height)
if self.wrapped_width:
max_length, custom_width = self.maxColumns(
custom_width=custom_width,
custom_height=custom_height,
calculate_width=True)
else:
max_length = self.maxColumns(custom_width=custom_width,
custom_height=custom_height)
annotation_types = [
[constants.SEQ_SECONDARY, None],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_HELIX_PROPENSITY],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_STRAND_PROPENSITY],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_TURN_PROPENSITY],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_HELIX_TERMINATORS],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_STERIC_GROUP],
[
constants.SEQ_ANNOTATION,
constants.ANNOTATION_SIDECHAIN_CHEMISTRY
], [constants.SEQ_ANNOTATION, constants.ANNOTATION_IDENTITY],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_SIMILARITY],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_BFACTOR],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_HYDROPHOBICITY],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_PI],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_SSP],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_ACC],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_DIS],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_DOM],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_CCB],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_STRUCTURE],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_PFAM],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_RESNUM],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_LIGAND],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_SSBOND],
[constants.SEQ_ANNOTATION, constants.ANNOTATION_CUSTOM]
]
empty = None
if self.wrapped:
# Wrapped mode.
actual_width = self.sequence_area.width()
if custom_width is not None:
actual_width = custom_width
if self.sequence_group:
start = 0
length = 0
has_sequences = True
while has_sequences:
has_sequences = False
for seq in self.sequence_group.sequences:
if seq.residues and seq.visible and \
start < len(seq.residues):
has_sequences = True
if has_sequences:
last_hidden = False
for seq in self.sequence_group.sequences:
if self.hide_empty_lines and \
seq.isValidProtein() and \
start > seq.unpaddedLength():
continue
parent_seq = seq
child_index = 0
parent_row = 0
while seq:
if seq.visible:
length = max_length
if start + length > len(seq.residues):
length = len(seq.residues) - start
if length < 0:
length = 0
height = seq.height
if seq.type == constants.SEQ_RULER:
height = 2
if len(seq.residues) == 0:
length = max_length
row = (seq, start, start + length, height)
if seq.annotation_type == constants.ANNOTATION_RESNUM:
self.rows.insert(parent_row, row)
else:
self.rows.append(row)
actual_height += height * self.font_height
seq.last_hidden = last_hidden
last_hidden = False
else:
last_hidden = True
if seq == parent_seq:
parent_row = len(self.rows) - 1
if not self.group_annotations or \
seq.type != constants.SEQ_AMINO_ACIDS:
seq = None
if parent_seq.visible and \
not parent_seq.collapsed and \
len(parent_seq.children) > 0:
while child_index < \
len(parent_seq.children):
seq = parent_seq.children[
child_index]
child_index += 1
if seq.visible:
break
seq = None
else:
seq = None
# Sequence group.
if self.group_annotations:
empty = sequence.Sequence()
empty.type = constants.SEQ_EMPTY
self.rows.append((empty, start, start + length, 1))
actual_height += self.font_height
for sequence_type, annotation_type in annotation_types:
n_child = 0
for seq in self.sequence_group.sequences:
for child in seq.children:
if child.type == sequence_type and \
child.annotation_type == annotation_type:
n_child += 1
if child.visible:
length = max_length
if start + length > len(
child.residues):
length = len(
child.residues) - start
if length < 0:
length = 0
height = child.height
row = (child, start,
start + length, height)
self.rows.append(row)
actual_height += height * \
self.font_height
if n_child > 0:
empty = sequence.Sequence()
empty.type = constants.SEQ_EMPTY
self.rows.append(
(empty, start, start + length, 1))
actual_height += self.font_height
else:
empty = sequence.Sequence()
empty.type = constants.SEQ_EMPTY
self.rows.append((empty, start, start + length, 1))
actual_height += self.font_height
if empty:
empty.type = constants.SEQ_SEPARATOR
start = start + max_length
else:
# Unwrapped mode.
start = self.left_column
length = 0
max_sequence_length = 0
if use_max_length:
max_length = self.sequence_group.findMaxLength()
if self.sequence_group:
last_hidden = False
for seq in self.sequence_group.sequences:
parent_seq = seq
child_index = 0
parent_row = 0
while seq:
if seq.visible and \
seq.type != constants.SEQ_SEPARATOR:
sequence_length = len(seq.residues)
if sequence_length == 0 and self.sequence_group.profile:
sequence_length = len(
self.sequence_group.profile.residues)
if sequence_length > max_sequence_length:
max_sequence_length = sequence_length
length = max_length
if start + length > sequence_length:
length = sequence_length - start
if length < 0:
length = 0
height = seq.height
if seq.type == constants.SEQ_RULER:
height = 2
length = max_length
row = (seq, start, start + length, height)
if seq.annotation_type == constants.ANNOTATION_RESNUM:
self.rows.insert(parent_row, row)
else:
self.rows.append(row)
actual_height += height * self.font_height
seq.last_hidden = False
last_hidden = False
elif not seq.visible:
last_hidden = True
if seq == parent_seq:
parent_row = len(self.rows) - 1
if not self.group_annotations or \
seq.type != constants.SEQ_AMINO_ACIDS:
seq = None
if parent_seq.visible and \
not parent_seq.collapsed and \
len(parent_seq.children) > 0:
while child_index < len(parent_seq.children):
seq = parent_seq.children[child_index]
child_index += 1
if seq.visible:
break
seq = None
else:
seq = None
# Sequence group.
if self.group_annotations:
empty = sequence.Sequence()
empty.type = constants.SEQ_EMPTY
self.rows.append((empty, start, start + length, 1))
actual_height += self.font_height
for sequence_type, annotation_type in annotation_types:
n_child = 0
for seq in self.sequence_group.sequences:
for child in seq.children:
if child.type == sequence_type and \
child.annotation_type == annotation_type:
n_child += 1
if child.visible:
sequence_length = len(child.residues)
if sequence_length > max_sequence_length:
max_sequence_length = sequence_length
length = max_length
if start + length > sequence_length:
length = sequence_length - start
if length < 0:
length = 0
height = child.height
row = (child, start, start + length,
height)
self.rows.append(row)
actual_height += height * \
self.font_height
if n_child > 0:
empty = sequence.Sequence()
empty.type = constants.SEQ_EMPTY
self.rows.append((empty, start, start + length, 1))
actual_height += self.font_height
self.sequence_area.horizontal_scroll_bar.setRange(
0, max_sequence_length - max_length + 5)
actual_width = max_length * self.cell_width + self.auxBorderWidth()
# Make the bottom separator empty.
if empty:
empty.type = constants.SEQ_EMPTY
self.sequence_area.vertical_scroll_bar.setRange(0, len(self.rows))
actual_height += self.font_height
return (actual_width, actual_height)
def runClustal(self, ignore_selection=False):
"""
Runs Clustal alignment and updates self.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Align Sequences")
ss_constraints = not self.alignment_settings_dialog.allowSSGaps()
jobs.run_clustal(self,
self.sequence_group,
ss_constraints=ss_constraints,
progress_dialog=self.job_progress_dialog,
global_alignment=False,
ignore_selection=ignore_selection,
viewer=self)
self.sequence_group.setReference(self.sequence_group.reference)
self.alignment_changed = True
self.updateView()
def setPopupMenus(self, name_menu=None, sequence_menu=None, tree_menu=None):
"""
Sets popup menu for name area widget.
:type menu: QPopupMenu
:param menu: Name area popup menu.
"""
self.name_popup_menu = name_menu
self.sequence_popup_menu = sequence_menu
self.tree_popup_menu = tree_menu
def findPattern(self, pattern):
"""
Finds a specified PROSITE-like pattern in the sequences.
:type pattern: str
:param pattern: Pattern to find in the sequence group.
"""
self.sequence_group.findPattern(pattern)
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def undo(self):
"""
Undoes the last operation.
"""
status = self.undo_stack.undo(self)
self.contents_changed = True
self.updateView()
return status
def redo(self):
"""
Redoes previously undone operation.
"""
status = self.undo_stack.redo(self)
self.contents_changed = True
self.updateView()
return status
def runSSP(self):
"""
Runs secondary structure prediction and incorporates the results.
"""
self.runPredictors(["sspro"])
def runPfam(self):
"""
Runs Pfam simulation and incorporates the results.
"""
self.undo_stack.storeStateGroup(self.sequence_group, label="Run Pfam")
jobs.run_pfam(self.sequence_group,
self.job_progress_dialog,
job_settings=self.job_settings)
self.updateView()
def runBlast(self, failed_callback=None, ok_callback=None):
"""
Takes a `schrodinger.ui.sequencealignment.sequence.Sequence` and runs a
Blast simulation to determine the best matches. The status callbacks can
be set.
This method will return None if the run dialog was cancelled, 'failed' if
the job failed and 'ok' if the job succeeded.
:param ref_sequence: The sequence to run the BLAST search against
:type ref_sequence: schrodinger.ui.sequencealignment.sequence.Sequence
:param failed_callback: The callback to call when the job fails
:type failed_callback: callable
:param failed_callback: The callback to call when the job succeeds
:type failed_callback: callable
See also `failedBlastCallback` and `successBlastCallback`.
"""
if not failed_callback:
failed_callback = self.blastFailedCallback
if not ok_callback:
ok_callback = self.blastOkCallback
self.undo_stack.storeStateGroup(self.sequence_group, label="Run Blast")
if not self.blast_settings_dialog:
self.blast_settings_dialog = createBlastSettingsDialog(
self, execute=False)
result = self.blast_settings_dialog.exec()
if not result:
return
settings = self.blast_settings_dialog.getAllSettings()
if not self.blast_results_dialog:
self.blast_results_dialog = createBlastResultsDialog(self)
self.blast_results_dialog.hide()
status = jobs.blast_run(self.sequence_group,
settings,
progress_dialog=self.job_progress_dialog,
results_dialog=self.blast_results_dialog,
remote_query_dialog=self.remote_query_dialog,
job_settings=self.job_settings)
if status == 'failed':
failed_callback(self.blast_settings_dialog,
self.blast_results_dialog, ok_callback)
elif status == 'ok':
ok_callback(self.blast_results_dialog)
if self.cb_contents_changed:
self.cb_contents_changed()
return status
def blastFailedCallback(self, dialog, results_dialog, ok_callback):
"""
The default callback to call when a BLAST search job fails. This
will retry the job if it was a locally run job and try to use the
a remote job to succeed.
"""
if dialog.remote_button.isChecked() == False:
question = dialogs.question_dialog(
"Local BLAST search has failed",
"Would you like to run a remote BLAST job?",
buttons=["yes", "no"])
if question == "yes":
# Run remote BLAST search
dialog.remote_button.setChecked(True)
settings = dialog.getAllSettings()
status = jobs.blast_run(
self.sequence_group,
settings,
progress_dialog=self.job_progress_dialog,
results_dialog=results_dialog,
remote_query_dialog=self.remote_query_dialog)
self.job_progress_dialog.hide()
if status == "ok":
ok_callback(results_dialog)
else:
title = 'BLAST Search Error'
msg = 'The BLAST search failed.'
dialogs.error_dialog(title, msg)
def blastOkCallback(self, results_dialog):
"""
The default callback to call when a BLAST search job succeeds. This
will show the BLAST results panel which allows users to choose which
sequences to import into the sequence viewer, with the best 10
highlighted.
"""
results_dialog.show()
results_dialog.updateTableGeometry()
results_dialog.setTopInput(10)
results_dialog.raise_()
results_dialog.exec()
def fetchSequence(self,
ids,
replace=None,
progress=None,
maestro_include=False,
maestro_incorporate=True,
remote_query=True):
"""
Fetches sequences from online repositories based on entry ID.
This method attempts to automatically recognize the repository
by looking on the specified entry ID format.
:type entry_id: str
:param entry_id: Entry ID in the online database.
:type replace: Sequence
:param replace: Sequence to be replaced by a matching PDB sequence.
:rtype: string/bool
:return: On success "ok" or True, on error "error", "cancelled" or "invalid"
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Fetch Sequence")
result = False
ids = ids.replace(',', ' ')
query_dialog = None
if remote_query and self.remote_query_dialog:
query_dialog = self.remote_query_dialog
for entry_id in ids.split(' '):
if len(entry_id) >= 4 and \
len(entry_id) <= 5 and \
entry_id[0] >= '0' and \
entry_id[0] <= '9':
tmp_group = sequence_group.SequenceGroup()
result = jobs.fetch_pdb(
tmp_group,
entry_id,
maestro_include=maestro_include,
progress_dialog=self.job_progress_dialog,
progress=progress,
maestro_incorporate=maestro_incorporate,
remote_query_dialog=query_dialog,
viewer=self)
if result == "ok" and tmp_group.sequences:
if replace:
replace_index = self.sequence_group.sequences.index(
replace)
self.sequence_group.sequences.remove(replace)
else:
replace_index = len(self.sequence_group.sequences)
for index, pdb in enumerate(tmp_group.sequences):
self.sequence_group.sequences.insert(
replace_index + index, pdb)
if replace:
replace.propagateGaps(pdb,
parent_sequence=pdb,
replace=True)
pdb.selected = replace.selected
pdb.propagateGapsToChildren()
else:
result = jobs.fetch_entrez(
self.sequence_group,
entry_id,
progress_dialog=self.job_progress_dialog,
remote_query_dialog=query_dialog)
# FIXME result here be True or False, while result returned by
# jobs.fetch_pdb() is a string.
if self.job_progress_dialog:
self.job_progress_dialog.hide()
self.contents_changed = True
self.updateView()
return result
def moveUp(self):
"""
Moves selected sequences one level up.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Move Sequences Up")
self.sequence_group.moveUp()
self.sequence_group.setReference(self.sequence_group.reference)
self.alignment_changed = True
self.updateView()
def moveDown(self):
"""
Moves selected sequences one level down.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Move Sequences Down")
self.sequence_group.moveDown()
self.sequence_group.setReference(self.sequence_group.reference)
self.alignment_changed = True
self.updateView()
def moveTop(self):
"""
Moves selected sequences to the top of the group.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Move Sequences Top")
self.sequence_group.moveTop()
self.sequence_group.setReference(self.sequence_group.reference)
self.alignment_changed = True
self.updateView()
def moveBottom(self):
"""
Moves selected sequences to the bottom of the group.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Move Sequences Bottom")
self.sequence_group.moveBottom()
self.sequence_group.setReference(self.sequence_group.reference)
self.alignment_changed = True
self.updateView()
def synchronizeWithMaestro(self):
"""
Synchronizes sequence viewer contents with Maestro workspace.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Synchronize With Maestro")
maestro_helpers.maestroSynchronize(self.sequence_group)
maestro_helpers.synchronizePropertiesWithMaestro(self,
colors=True,
selection=True)
self.updateView()
def duplicateSequences(self):
"""
Duplicates selected sequences.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Duplicate Sequences")
self.sequence_group.duplicateSelectedSequences()
if self.cb_contents_changed:
self.cb_contents_changed()
self.contents_changed = True
self.updateView()
def setUndoRedoActions(self, undo_action, redo_action):
"""
Sets Qt undo/redo actions, so that the undo/redo mechanism can change
the corresponding menu items appropriately.
:type undo_action: QAction
:param undo_action: Qt action for undo operation.
:type redo_action: QAction
:param redo_action: Qt action for redo operation.
"""
self.undo_stack.setActions(undo_action, redo_action)
def setDisplayIdentity(self, value):
"""
Turns on and off displaing sequence identity information.
:type value: bool
:param value: Display identity value.
"""
self.display_identity = value
self.updateView()
def setDisplaySimilarity(self, value):
"""
Turns on and off displaing sequence similarity information.
:type value: bool
:param value: Display similarity value.
"""
self.display_similarity = value
self.updateView()
def setDisplayHomology(self, value):
"""
Turns on and off displaing sequence homology information.
:type value: bool
:param value: Display homology value.
"""
self.display_homology = value
self.updateView()
def setDisplayScore(self, value):
"""
Turns on and off displaing sequence score information.
:type value: bool
:param value: display score value.
"""
self.display_score = value
self.updateView()
def setBoundaries(self, value):
"""
Turns on and off displaying sequence boundaries on the alignment.
:type value: bool
:param value: if True, display boundaries
"""
self.display_boundaries = value
self.updateView()
def saveProject(self, file_name, auto_save=False):
"""
Saves current project to an external file.
"""
if auto_save and not self.ready_to_save:
return
try:
output_file = open(file_name, "wb")
for group in self.sequence_group_list:
pickle.dump(group.sequences, output_file, 2)
pickle.dump(group.profile, output_file, 2)
pickle.dump(group.tree, output_file, 2)
pickle.dump(group.name, output_file, 2)
pickle.dump(group.color_mode, output_file, 2)
pickle.dump(group.custom_color, output_file, 2)
pickle.dump(group.background_color, output_file, 2)
pickle.dump(group.inv_background_color, output_file, 2)
pickle.dump(group.user_annotations, output_file, 2)
name = output_file.name
output_file.close()
except:
raise
name = None
self.ready_to_save = False
return name
def loadProject(self, file_name):
"""
Loads a MSV project from an external file.
"""
result = True
try:
input_file = open(file_name, "rb")
except:
return False
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Load Project")
try:
input_file.seek(0, 2)
file_size = input_file.tell()
input_file.seek(0, 0)
group_list = []
pos = 0
while pos < file_size:
try:
group = sequence_group.SequenceGroup()
group.sequences = pickle.load(input_file)
group.profile = pickle.load(input_file)
group.tree = pickle.load(input_file)
group.name = pickle.load(input_file)
group.color_mode = pickle.load(input_file)
group.custom_color = pickle.load(input_file)
group.background_color = pickle.load(input_file)
group.inv_background_color = pickle.load(input_file)
group.user_annotations = pickle.load(input_file)
group.updateReference()
group_list.append(group)
pos = input_file.tell()
except:
if group:
group_list.append(group)
break
input_file.close()
if group_list:
for index, group in enumerate(group_list):
if not group.name:
group.name = "Query %d" % (index + 1)
group.repair()
self.sequence_group_list = group_list
self.sequence_group = group_list[0]
self.makeTabsFromGroupList()
except:
raise
result = False
self.contents_changed = True
self.updateView()
maestro_helpers.maestroSynchronize(self.sequence_group)
return result
def removeRedundancy(self):
"""
Removes redundant sequences.
"""
dialog = RemoveRedundancyDialog(self, self.sequence_group)
dialog.exec()
if dialog.result():
self.undo_stack.storeStateGroupDeep(
self.sequence_group, label="Remove Redundant Sequences")
self.excludeSelected()
self.sequence_group.deleteSelected()
self.contents_changed = True
self.updateView()
def weightColorsSettings(self):
"""
Enables weighting by colors.
"""
dialog = WeightColorsDialog(self, self.sequence_group)
dialog.exec()
def pairwiseAlignment(self):
"""
Performs a pariwise alignment using dynamic programming
(Smith-Waterman algorithm).
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Align Sequences")
if self.sequence_group.reference not in self.sequence_group.sequences or \
self.sequence_group.reference.type != constants.SEQ_AMINO_ACIDS or \
not self.sequence_group.reference.isValidProtein():
QtWidgets.QMessageBox.critical(
self, self.tr("Cannot Align Sequences"),
self.tr("At least two sequences must be present " +
"for pairwise alignment."), QtWidgets.QMessageBox.Ok)
return False
target_sequence = None
for seq in self.sequence_group.sequences:
if seq != self.sequence_group.reference and \
seq.isValidProtein():
target_sequence = seq
break
if target_sequence:
constraint_list = None
for seq in self.sequence_group.sequences:
if seq.type == constants.SEQ_CONSTRAINTS:
constraint_list = seq.constraint_list
break
not_selected = not self.sequence_group.hasSelectedSequences()
for seq in self.sequence_group.sequences:
if seq.isValidProtein() and \
seq != self.sequence_group.reference:
if (not_selected and seq == target_sequence) or \
seq.selected:
go_penalty, ge_penalty = \
self.alignment_settings_dialog.gapPenalties()
ss_constraints = \
not self.alignment_settings_dialog.allowSSGaps()
align.align(self.sequence_group.reference,
seq,
gap_open_penalty=go_penalty,
gap_extend_penalty=ge_penalty,
scoring_matrix=constants.SIMILARITY_MATRIX,
constraints=constraint_list,
merge=False,
ss_constraints=ss_constraints)
self.updateView()
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 100)
if maestro:
maestro.process_pending_events()
self.sequence_group.minimizeAlignment()
else:
QtWidgets.QMessageBox.critical(
self, self.tr("Cannot Align Sequences"),
self.tr("At least two sequences must be present " +
"for pairwise alignment."), QtWidgets.QMessageBox.Ok)
return False
self.alignment_changed = True
self.updateView()
def alignByResidueNumbers(self):
"""
Performs a sequence alignment using residue numbers as IDs.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Align by Residue Numbers")
self.sequence_group.alignByResidueNumbers()
self.alignment_changed = True
self.updateView()
def alignMerge(self):
"""
Performs a pariwise alignment using dynamic programming
(Smith-Waterman algorithm). Sequentially merges the aligned sequences
with the existing alignment.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Align and Merge Sequences")
if self.sequence_group.reference not in self.sequence_group.sequences or \
self.sequence_group.reference.type != constants.SEQ_AMINO_ACIDS or \
not self.sequence_group.hasSelectedSequences(exclude_reference=True):
QtWidgets.QMessageBox.critical(
self, self.tr("Cannot Align Sequences"),
self.tr(
"At least one non-reference sequence must be selected " +
"for pairwise alignment merging."),
QtWidgets.QMessageBox.Ok)
return False
target_sequence = None
for seq in self.sequence_group.sequences:
if seq != self.sequence_group.reference and \
seq.type == constants.SEQ_AMINO_ACIDS:
target_sequence = seq
break
if target_sequence:
constraint_list = None
for seq in self.sequence_group.sequences:
if seq.type == constants.SEQ_CONSTRAINTS:
constraint_list = seq.constraint_list
break
for seq in self.sequence_group.sequences:
if seq.type == constants.SEQ_AMINO_ACIDS and \
seq != self.sequence_group.reference:
if seq.selected:
go_penalty, ge_penalty = \
self.alignment_settings_dialog.gapPenalties()
ss_constraints = \
not self.alignment_settings_dialog.allowSSGaps()
align.align(self.sequence_group.reference,
seq,
gap_open_penalty=go_penalty,
gap_extend_penalty=ge_penalty,
scoring_matrix=constants.SIMILARITY_MATRIX,
constraints=constraint_list,
merge=True,
merge_selected=False,
ss_constraints=ss_constraints,
sequence_group=self.sequence_group,
last_to_merge=seq)
self.updateView()
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 100)
if maestro:
maestro.process_pending_events()
self.sequence_group.minimizeAlignment()
else:
QtWidgets.QMessageBox.critical(
self, self.tr("Cannot Align Sequences"),
self.tr("At least two sequences must" +
"be present for pairwise alignment."),
QtWidgets.QMessageBox.Ok)
return False
self.alignment_changed = True
self.updateView()
def editSequence(self):
"""
Edits a selected sequence.
"""
any_selected = self.sequence_group.hasSelectedSequences()
# Just edit the first selected sequence.
for seq in self.sequence_group.sequences:
if seq.type == constants.SEQ_AMINO_ACIDS and \
(seq.selected or not any_selected):
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Edit Sequence")
dialog = SequenceEditorDialog(self, seq, self.sequence_group)
dialog.setModal(False)
dialog.show()
dialog.exec()
self.updateView()
return
def createSequence(self):
"""
Creates a new sequence and adds it to the sequence group.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Create New Sequence")
seq = sequence.Sequence()
seq.name = "new_sequence"
dialog = SequenceEditorDialog(self, seq, self.sequence_group)
dialog.setModal(False)
dialog.button_add.setText("Add")
dialog.button_replace.setVisible(False)
dialog.show()
dialog.exec()
self.contents_changed = True
self.updateView()
def pasteFasta(self):
"""
Pastes an alignment in text FASTA format into the MSV window.
"""
dialog = SequenceEditorDialog(self,
None,
self.sequence_group,
fasta_editor=True)
dialog.setModal(False)
dialog.sequence_label.setText("Alignment in FASTA format:")
dialog.button_add.setText("Add")
dialog.button_replace.setVisible(False)
dialog.show()
dialog.exec()
self.contents_changed = True
self.updateView()
def setAsReference(self):
"""
Sets a selected sequence as a reference.
"""
if not self.sequence_group.hasSelectedSequences():
return
self.contents_changed = True
self.sequence_group.setReference()
self.alignment_changed = True
self.updateView()
def selectAllSequences(self):
"""
Selects all sequences in the group.
"""
self.sequence_group.selectAllSequences()
self.name_area.update()
if self.cb_sequence_selection_changed:
self.cb_sequence_selection_changed()
def unselectAllSequences(self):
"""
Deselects all sequences in the group.
"""
self.sequence_group.unselectAllSequences()
self.name_area.update()
if self.cb_sequence_selection_changed:
self.cb_sequence_selection_changed()
def invertSequenceSelection(self):
"""
Inverts current sequence selection range.
"""
self.sequence_group.invertSequenceSelection()
self.name_area.update()
if self.cb_sequence_selection_changed:
self.cb_sequence_selection_changed()
def findPrevious(self):
"""
Scrolls to a previous occurence of a selected pattern.
"""
if self.wrapped:
found = None
for index in range(self.top_row - 1, 0, -1):
seq, start, end, height = self.rows[index]
if not seq.parent_sequence and \
start < len(seq.residues) and \
end < len(seq.residues):
for pos in range(start, end):
if seq.residues[pos].selected:
found = index
break
if found:
self.sequence_area.vertical_scroll_bar.setValue(found)
break
else:
mid = self.left_column
found = None
for pos in range(mid - 1, 0, -1):
for seq in self.sequence_group.sequences:
if pos < seq.length():
if seq.residues[pos].selected:
found = pos
break
if found:
break
if found:
self.sequence_area.horizontal_scroll_bar.setValue(found)
def findNext(self):
"""
Scrolls to a next occurence of a selected pattern.
"""
if self.wrapped:
found = None
for index in range(self.top_row + 1, len(self.rows)):
seq, start, end, height = self.rows[index]
if not seq.parent_sequence and \
start < len(seq.residues) and \
end < len(seq.residues):
for pos in range(start, end):
if seq.residues[pos].selected:
found = index
break
if found:
self.sequence_area.vertical_scroll_bar.setValue(found)
break
else:
mid = self.left_column
max = self.sequence_group.findMaxLength()
found = None
for pos in range(mid + 1, max):
for seq in self.sequence_group.sequences:
if pos < seq.length():
if seq.residues[pos].selected:
found = pos
break
if found:
self.sequence_area.horizontal_scroll_bar.setValue(found)
break
def anchorSelection(self):
"""
Freezes the selected part of the alignment so it is not possible
to move residues outside of the restricted part.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Anchor Unselected Residues")
self.sequence_group.anchorSelection()
self.updateView()
def clearAnchors(self):
"""
Clears the rectricted part of the alignment.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Clear Anchors")
self.sequence_group.clearAnchors()
self.updateView()
def saveImage(self,
file_name,
save_all=True,
format="PNG",
custom_width=None,
custom_height=None):
"""
Saves current view to a PNG image file.
"""
height = self.sequence_area.height()
pane_sizes = self.sizes()
tree_width = pane_sizes[0]
name_width = pane_sizes[1]
multiple = 1
_font_size = self.font_size
self.updateFontSize(multiple * self.font_size)
seq_area_width = self.sequence_area.width()
seq_area_height = self.sequence_area.height()
aux_right_column_width = 0
aux_left_column_width = 0
if self.display_identity or \
self.display_similarity or \
self.display_homology:
aux_right_column_width = 4 * self.font_width
elif self.display_score:
aux_right_column_width = 5 * self.font_width
if self.display_boundaries:
aux_right_column_width += 15 * self.font_width
aux_left_column_width = 5 * self.font_width
if self.wrapped_width:
custom_width = (self.wrapped_width + 3) * self.font_width \
+ aux_right_column_width + aux_left_column_width
if custom_width is not None:
name_width = self.name_area.getMaxWidth()
custom_width -= name_width
seq_area_width = custom_width
if custom_height is not None:
seq_area_height = custom_height
if (custom_width is not None and
custom_width <= 0) or (custom_height is not None and
custom_height <= 0):
custom_width = None
custom_height = None
if save_all:
sequence_width, sequence_height = self.generateRows(
use_max_length=True,
custom_width=custom_width,
custom_height=custom_height)
# Save the entire alignment.
height = sequence_height
width = tree_width + \
name_width + \
sequence_width
else:
sequence_width, sequence_height = self.generateRows(
use_max_length=False,
custom_width=custom_width,
custom_height=custom_height)
# Save only the visible part of the alignment.
height = seq_area_height
width = tree_width + \
name_width + \
seq_area_width
if format == "AUTO":
if file_name.endswith(".png"):
format = "PNG"
elif file_name.endswith(".pdf"):
format = "PDF"
else:
format = "PNG"
if format == "PNG":
image = QtGui.QImage(multiple * width, multiple * height,
QtGui.QImage.Format_RGB32)
if image:
painter = QtGui.QPainter(image)
painter.setFont(self.original_font)
painter.fillRect(image.rect(), self.background_color)
painter.setClipping(True)
painter.setClipRect(0, 0, multiple * tree_width,
multiple * sequence_height)
self.tree_area.paintTreeArea(painter, None, clip=False)
painter.translate(tree_width, 0)
painter.setClipRect(0, 0, multiple * name_width,
multiple * sequence_height)
self.name_area.paintNameArea(painter,
None,
clip=False,
custom_width=name_width,
custom_height=custom_height)
painter.translate(name_width, 0)
painter.setClipRect(0, 0, multiple * sequence_width,
multiple * sequence_height)
self.sequence_area.paintSequenceArea(
painter,
None,
clip=False,
custom_width=custom_width,
custom_height=custom_height)
painter.end()
if self.crop_image:
# Determine image extends
bgpix = image.pixel(0, 0)
top = 0
bottom = image.height() - 1
left = 0
right = image.width() - 1
found = False
for y in range(image.height()):
for x in range(image.width()):
if image.pixel(x, y) != bgpix:
top = y
found = True
break
if found:
break
found = False
for y in reversed(list(range(image.height()))):
for x in range(image.width()):
if image.pixel(x, y) != bgpix:
bottom = y
found = True
break
if found:
break
found = False
for x in range(image.width()):
for y in range(image.height()):
if image.pixel(x, y) != bgpix:
left = x
found = True
break
if found:
break
found = False
for x in reversed(list(range(image.width()))):
for y in range(image.height()):
if image.pixel(x, y) != bgpix:
right = x
found = True
break
if found:
break
image = image.copy(left, top, right - left, bottom - top)
image.save(file_name, "PNG")
elif format == "PDF":
try:
printer = QtPrintSupport.QPrinter(
QtPrintSupport.QPrinter.ScreenResolution)
except:
printer = None
if printer:
printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat)
printer.setOutputFileName(file_name)
printer.setFontEmbeddingEnabled(True)
printer.setFullPage(False)
printer.setPaperSize(QtCore.QSizeF(width, height),
QtPrintSupport.QPrinter.DevicePixel)
printer.setPageMargins(0, 0, 0, 0,
QtPrintSupport.QPrinter.DevicePixel)
painter = QtGui.QPainter(printer)
painter.setFont(self.original_font)
painter.setClipping(True)
painter.setClipRect(0, 0, tree_width, sequence_height)
self.tree_area.paintTreeArea(painter, None, clip=False)
painter.translate(tree_width, 0)
painter.setClipRect(0, 0, name_width, sequence_height)
self.name_area.paintNameArea(painter,
None,
clip=False,
custom_width=name_width,
custom_height=custom_height)
painter.translate(name_width, 0)
painter.setClipRect(0, 0, sequence_width, sequence_height)
painter.setClipping(False)
self.sequence_area.paintSequenceArea(
painter,
None,
clip=False,
custom_width=custom_width,
custom_height=custom_height)
painter.end()
# Regenerate original layout
self.generateRows()
self.updateFontSize(_font_size)
if format == "PNG" and image:
return (image.width(), image.height())
else:
return None
def displayMessage(self, message):
"""
Displays a message on a status bar.
"""
if self.message_status_bar:
self.message_status_bar.setText(message)
def setConsiderGaps(self, value):
"""
Sets value of consider gaps flag. If set to True, gaps will be
included in calculation of local sequence similarity measures.
:type value: bool
:param value: Should we consider gaps for sequence identity
calculations.
"""
self.sequence_group.setConsiderGaps(value)
def getConsiderGaps(self):
"""
Gets value of consider gaps flag.
:rtype value: bool
:return: Should we consider gaps for sequence identity calculations.
"""
return self.sequence_group.consider_gaps
def expandSelection(self):
"""
Expands selection to include entire columns.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Expand Selection")
self.sequence_group.expandSelection()
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def expandSelectionRef(self):
"""
Expands selection from reference sequence to include entire columns.
"""
self.undo_stack.storeStateGroupDeep(
self.sequence_group, label="Expand Selection from Reference")
self.sequence_group.expandSelectionRef()
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def setUseColors(self, use_colors):
"""
Sets use colors flag.
:type use_colors: bool
:param use_colors: If True, colors will be used to draw the sequences.
"""
self.use_colors = use_colors
self.updateView()
def toggleConstraints(self):
"""
Toggles a pairwise alignment constraints.
"""
if not self.set_constraints:
result = self.sequence_group.addConstraint(None, None, None, None)
if not result:
dialogs.error_dialog("Error", "Could not create constraints.")
return False
else:
self.sequence_group.removeConstraints()
self.set_constraints = not self.set_constraints
self.updateView()
return self.set_constraints
def clearConstraints(self):
"""
Clears pairwise alignment constraints.
"""
self.sequence_group.clearConstraints()
self.updateView()
def enableQueryConstraints(self):
"""
Toggles a query sequence constraints.
"""
self.set_query_constraints = True
if self.set_query_constraints:
self.sequence_group.addConstraint(None,
None,
None,
None,
for_prime=True)
self.updateView()
def disableQueryConstraints(self, remove_if_empty=True):
"""
Toggles a query sequence constraints.
"""
self.set_query_constraints = False
if remove_if_empty and \
not self.sequence_group.hasConstraints():
self.sequence_group.removeConstraints()
self.updateView()
def clearQueryConstraints(self):
"""
Toggles a query sequence constraints.
"""
self.sequence_group.clearConstraints()
self.updateView()
def setIdentityInColumns(self, value):
"""
Sets "Calculate identity in columns" setting.
"""
self.sequence_group.identity_in_columns = value
self.sequence_group.calculateProfile()
self.sequence_group.updateVariableSequences()
self.updateView()
def showBlastResults(self):
"""
Displays BLAST results dialog.
"""
if not self.blast_results_dialog:
self.blast_results_dialog = createBlastResultsDialog()
self.blast_results_dialog.updateTableGeometry()
self.blast_results_dialog.exec()
def addGlobalAnnotations(self):
"""
Add global annotations action callback.
"""
self.addConsensus(update=False)
self.addSymbols(update=False)
self.addMeanHydrophobicity(update=False)
self.addMeanPI(update=False)
self.addSequenceLogo(update=False)
self.contents_changed = True
self.updateView()
def removeGlobalAnnotations(self):
"""
Removes global annotations.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Delete Global Annotations")
self.sequence_group.deleteGlobalAnnotations()
self.contents_changed = True
self.updateView()
def hideColumns(self, unselected=False):
"""
Hides selected columns.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Hide Columns")
self.sequence_group.hideColumns(unselected=unselected)
self.contents_changed = True
self.updateView()
def showColumns(self):
"""
Shows all hidden columns.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Show Columns")
self.sequence_group.showAllResidues()
self.contents_changed = True
self.updateView()
def computeSequenceProfile(self):
"""
Computes internal sequence profile and updates self.
"""
self.sequence_group.calculateProfile()
self.sequence_group.updateVariableSequences()
self.updateView()
def alignmentSettingsDialog(self):
"""
Opens alignment settings dialog.
"""
self.alignment_settings_dialog.group = self.sequence_group
self.alignment_settings_dialog.exec()
def downloadPDB(self,
maestro_include=True,
maestro_incorporate=True,
remote_query=True):
"""
Downloads a corresponding PDB structure.
:return: True if the download was successful, otherwise False.
:rtype: boolean
"""
self.maestro_busy = True
result = True
sequences = self.sequence_group.sequences[:]
any_selected = self.sequence_group.hasSelectedSequences()
total = 0
for seq in sequences:
if seq.type == constants.SEQ_AMINO_ACIDS and \
(seq.selected or not any_selected):
pdb_id = seq.getPDBId()
if len(pdb_id) >= 3:
total += 1
if total:
count = 0
self.job_progress_dialog.updateProgress(0.0)
for seq in sequences:
if seq.type == constants.SEQ_AMINO_ACIDS and \
(seq.selected or not any_selected):
pdb_id = seq.getPDBId()
if len(pdb_id) >= 3:
progress = old_div(float(count), float(total))
count += 1
if self.fetchSequence(
pdb_id,
replace=seq,
progress=progress,
maestro_include=maestro_include,
maestro_incorporate=maestro_incorporate,
remote_query=remote_query) == "cancelled":
result = False
break
self.maestro_busy = False
self.job_progress_dialog.hide()
self.contents_changed = True
self.updateView()
return result
def showJobLog(self):
"""
Displays a job log window.
"""
dialog = createJobProgressDialog("Job Log", parent=self)
dialog.setButtonText("Close")
dialog.exec()
def showJobSettings(self):
"""
Displays a job settings dialog.
"""
result = createJobSettingsDialog(self)
if result:
self.job_settings = result
def buildModel(self):
"""
Builds 3D model of reference sequence.
"""
showPrimeSettingsDialog(self, self.sequence_group, prime.prime_run,
self.job_progress_dialog)
def alignStructures(self):
"""
Aligns available structures.
"""
jobs.run_align_structures(self.sequence_group,
self.job_progress_dialog,
viewer=self)
def markResidues(self, rgb):
"""
Marks selected residues using a specified RGB color.
:type rgb: (int, int, int)
:param rgb: RGB color tuple.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Mark Selected Residues")
self.sequence_group.markResidues(rgb)
self.sequence_group.unselectAll()
self.update()
def clearMarkedResidues(self):
"""
Clears residues that are marked by custom color.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Clear Marked Residues")
self.sequence_group.clearMarkedResidues()
self.sequence_group.unselectAll()
self.update()
def cropSelectedResidues(self):
"""
Crops residues in a selected area.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Crop Selected Residues")
self.sequence_group.cropSelectedResidues()
self.updateView()
def displayLigands(self):
"""
Displays Maestro ligand interaction fingerprints.
"""
none_selected = not self.sequence_group.hasSelectedSequences()
for seq in self.sequence_group.sequences:
if seq.isValidProtein() and (none_selected or seq.selected):
maestro_helpers.maestroGetLigandAnnotations(seq)
self.contents_changed = True
self.updateView()
def newSet(self, name=None):
"""
Creates a new query set
"""
self.sequence_group = sequence_group.SequenceGroup()
self.last_index += 1
set_name = "Query %d" % (self.last_index)
if name:
self.sequence_group.name = name
else:
self.sequence_group.name = set_name
self.sequence_group_list.append(self.sequence_group)
self.updateView()
return set_name
def clearSet(self):
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Clear All")
name = self.sequence_group.name
if self.sequence_group in self.sequence_group_list:
index = self.sequence_group_list.index(self.sequence_group)
else:
index = 0
self.sequence_group = sequence_group.SequenceGroup()
self.sequence_group.name = name
self.sequence_group_list[index] = self.sequence_group
self.updateView()
def changeQuery(self, index):
if index in range(len(self.sequence_group_list)):
group = self.sequence_group_list[index]
self.sequence_group = group
setPrimeSequenceGroup(group)
self.updateView()
return True
return False
def excludeSelected(self):
if not maestro:
return
tmp_synchronize = self.auto_synchronize
self.auto_synchronize = False
ids = set()
for seq in self.sequence_group.sequences:
if (not seq.selected) and seq.from_maestro:
ids.add(seq.maestro_entry_id)
for seq in self.sequence_group.sequences:
if seq.selected and seq.from_maestro and \
seq.maestro_entry_id not in ids and \
seq.maestro_entry_id != "Scratch":
maestro.command("entrywsexclude entry \"" +
str(seq.maestro_entry_id) + "\"")
self.auto_synchronize = tmp_synchronize
def excludeQueryEntries(self):
if not maestro:
return
tmp_synchronize = self.auto_synchronize
self.auto_synchronize = False
for seq in self.sequence_group.sequences:
if seq.from_maestro:
maestro.command("entrywsexclude entry \"" +
str(seq.maestro_entry_id) + "\"")
self.auto_synchronize = tmp_synchronize
def includeQueryEntries(self):
if not maestro:
return
tmp_synchronize = self.auto_synchronize
self.auto_synchronize = False
for seq in self.sequence_group.sequences:
if seq.from_maestro and seq.maestro_included:
maestro.command("entrywsinclude entry \"" +
str(seq.maestro_entry_id) + "\"")
maestro.command("fit all")
self.auto_synchronize = tmp_synchronize
maestro_helpers.maestroSynchronize(self.sequence_group)
def deleteSet(self, index):
"""
Remove a sequence viewer tab.
"""
if index in range(len(self.sequence_group_list)):
group = self.sequence_group_list[index]
self.sequence_group_list.remove(group)
if index == len(self.sequence_group_list):
index -= 1
self.sequence_group = self.sequence_group_list[index]
group = self.sequence_group
self.contents_changed = True
self.updateView()
return True
return False
def emphasizeArea(self, area):
if area == 0:
self.name_area.emphasized = False
self.sequence_area.emphasized = False
elif area == 1:
self.name_area.emphasized = True
self.sequence_area.emphasized = False
elif area == 2:
self.name_area.emphasized = False
self.sequence_area.emphasized = True
self.update()
def colorSequenceNames(self, color):
self.sequence_group.colorSequenceNames(color)
self.update()
def incorporateIncludedEntries(self, incorporate_scratch_entry=None):
"""
Incorporates included entries into the SV.
"""
if incorporate_scratch_entry is not None:
scratch_entry = incorporate_scratch_entry
else:
scratch_entry = self.incorporate_scratch_entry
maestro_helpers.maestroIncorporateEntries(
self.sequence_group,
incorporate_scratch_entry=scratch_entry,
align_func=jobs.pdb_align_structure_to_sequence,
use_title=self.use_maestro_entry_title,
viewer=self)
self.contents_changed = True
self.updateView()
def incorporateSelectedEntries(self):
"""
Incorporates selected entries into the SV.
"""
maestro_helpers.maestroIncorporateEntries(
self.sequence_group,
what="selected",
incorporate_scratch_entry=self.incorporate_scratch_entry,
align_func=jobs.pdb_align_structure_to_sequence,
use_title=self.use_maestro_entry_title,
viewer=self)
self.contents_changed = True
self.updateView()
def colorEntrySurface(self):
any_selected = self.sequence_group.hasSelectedSequences()
for seq in self.sequence_group.sequences:
if seq.from_maestro and (seq.selected or not any_selected):
if not maestro_helpers.maestroColorEntrySurface(self, seq):
dialogs.error_dialog(
"Error",
"Could not color surface for selected sequence(s).")
return
def runPredictors(self, predictor_list):
"""
Runs a specified residue-level property predictors.
"""
self.undo_stack.storeStateGroup(self.sequence_group,
label="Run Prediction")
for pred in predictor_list:
if not predictors.has_predictor(pred):
dialogs.error_dialog(
"Predictor not available",
pred + " is not available. Do you have Prime installed?")
return False
any_selected = self.sequence_group.hasSelectedSequences()
for seq in self.sequence_group.sequences:
if seq.selected or not any_selected:
results = predictors.run(
seq,
predictor_list,
progress_dialog=self.job_progress_dialog,
remote_query_dialog=self.remote_query_dialog)
if results == "cancelled":
break
elif results != "failed":
for pred_seq in results:
pred_seq.parent_sequence = seq
seq.children.append(pred_seq)
seq.propagateGapsToChildren()
if hasattr(seq, "beta_map") and \
"betapro" in predictor_list:
beta_map = ContactMap(
self,
seq.short_name + " Beta Strand Contacts Prediction")
length = seq.gaplessLength()
beta_map.setMap(seq.beta_map, length, length)
self.updateView()
return True
def renameSequence(self):
"""
Renames a selected sequence.
"""
for seq in self.sequence_group.sequences:
if seq.selected:
name, ok = dialogs.string_input_dialog(
"Rename Sequence", "Enter new sequence name",
seq.short_name)
if ok and name:
seq.short_name = str(name)
self.sequence_group.unselectAllSequences()
self.updateView()
def copySequences(self, group):
"""
Copies all or selected sequences from group to self.sequence_group.
"""
self.sequence_group.copySequences(group)
self.contents_changed = True
self.updateView()
def makeTabsFromGroupList(self):
"""
Create named query tabs based on self sequence group list.
"""
if not self.query_tabs:
return
for index in range(self.query_tabs.count()):
self.query_tabs.removeTab(0)
for group in self.sequence_group_list:
self.query_tabs.addTab(group.name)
def passSelectionToMaestro(self):
tmp = self.auto_synchronize
self.auto_synchronize = True
maestro_helpers.maestroSelectResiduesInWorkspace(self)
self.auto_synchronize = tmp
def closeEvent(self, event):
"""
Called on window close request.
Will attempt to save MSV state in current Maestro project directory.
"""
if not self.save_state:
return
project_path = maestro_helpers.maestroGetProjectPath()
if project_path:
self.saveProject(project_path + "project.msv")
def showEvent(self, event):
"""
Called on window show request.
Will attempt to restore MSV state from current Maestro project directory.
"""
if not self.save_state:
return
# At this point, we know we are running in standalone MSV inside
# Maestro
project_path = maestro_helpers.maestroGetProjectPath()
if project_path and not self.last_project_path:
if self.loadProject(project_path + "project.msv"):
self.last_project_path = project_path
else:
project_path = maestro_helpers.maestroGetProjectPath(old=True)
if self.loadProject(project_path + "project.msv"):
self.last_project_path = project_path
def resizeEvent(self, event):
"""
Resize event handler.
Update font face and size here.
"""
self.updateFontSize(self.font_size)
def createCommandDict(self):
"""
Creates an external command dictionary.
"""
if self.command_dict:
return
self.command_dict = {}
self.command_dict["update"] = self.update
self.command_dict["update_view"] = self.updateView
self.command_dict["update_font_size"] = self.updateFontSize
self.command_dict[
"hide_selected_sequences"] = self.hideSelectedSequences
self.command_dict[
"delete_selected_sequences"] = self.deleteSelectedSequences
self.command_dict["show_all_sequences"] = self.showAllSequences
self.command_dict["fill_gaps"] = self.fillGaps
self.command_dict["remove_gaps"] = self.removeGaps
self.command_dict[
"delete_selected_residues"] = self.deleteSelectedResidues
self.command_dict["invert_selection"] = self.invertSelection
self.command_dict["expand_selection"] = self.expandSelection
self.command_dict["deselect_all"] = self.deselectAll
self.command_dict["select_all"] = self.selectAll
self.command_dict["remove_empty_columns"] = self.minimizeAlignment
self.command_dict["select_aligned_blocks"] = self.selectAlignedBlocks
self.command_dict[
"select_structure_blocks"] = self.selectStructureBlocks
self.command_dict["select_identities"] = self.selectIdentities
self.command_dict["delete_annotations"] = self.deleteAnnotations
self.command_dict["delete_predictions"] = self.deletePredictions
self.command_dict["propagate_colors"] = self.propagateColors
self.command_dict["delete_all"] = self.clearSequences
self.command_dict["add_consensus"] = self.addConsensus
self.command_dict["multiple_sequence_alignment"] = self.runClustal
self.command_dict["run_ssp"] = self.runSSP
self.command_dict["run_pfam"] = self.runPfam
self.command_dict["run_blast"] = self.runBlast
self.command_dict["synchronize"] = self.synchronizeWithMaestro
self.command_dict["pairwise_alignment"] = self.pairwiseAlignment
self.command_dict["select_all_sequences"] = self.selectAllSequences
self.command_dict["deselect_all_sequences"] = self.unselectAllSequences
self.command_dict[
"invert_sequence_selection"] = self.invertSequenceSelection
self.command_dict["remove_redundant_sequences"] = self.removeRedundancy
self.command_dict["quit"] = quit
self.command_dict["align_structures"] = self.alignStructures
self.command_dict["collapse_all"] = self.collapseAllSequences
self.command_dict["expand_all"] = self.expandAllSequences
self.command_dict["sequence_logo"] = self.addSequenceLogo
self.command_dict["send_to_knime"] = self.parent().parent().backToKnime
def colorToRGB(self, color):
"""
Converts a color name string to RGB tuple.
"""
colors = {
'white': (255, 255, 255),
'black': (0, 0, 0),
'red': (255, 0, 0),
'green': (0, 255, 0),
'blue': (0, 0, 255),
'yellow': (255, 255, 0),
'magenta': (255, 0, 255),
'cyan': (0, 255, 255),
'gray': (127, 127, 127),
'orange': (255, 127, 0),
'teal': (0, 127, 255)
}
if color in colors:
return colors[color]
return (255, 255, 255)
def selectSequencesByName(self, name_list):
for seq in self.sequence_group.sequences:
if seq.short_name in name_list \
or seq.name in name_list:
seq.selected = True
def executeCommandFile(self, cmd_file_name):
"""
Executes an external command file.
"""
try:
cmd_file = open(cmd_file_name, "r")
commands = cmd_file.readlines()
cmd_file.close()
except:
print("Cannot execute command file ", cmd_file_name)
return False
progress_dialog = self.job_progress_dialog
self.job_progress_dialog = None
self.createCommandDict()
for cmd in commands:
if cmd[0] == '#':
continue
cmd_list = cmd.split()
if not cmd_list:
continue
if cmd_list[0] in list(self.command_dict):
command = self.command_dict[cmd_list[0]]
command()
elif cmd_list[0] == "load_file":
arg_list = ''.join(cmd_list[1:]).split(',')
if not arg_list:
continue
inp_name = arg_list[0]
if inp_name.startswith('"') and inp_name.endswith('"'):
inp_name = inp_name[1:-1]
self.loadFile(inp_name)
elif cmd_list[0] == "save_image":
arg_list = ''.join(cmd_list[1:]).split(',')
if len(arg_list) < 3:
continue
out_name = arg_list[0]
if out_name.startswith('"') and out_name.endswith('"'):
out_name = out_name[1:-1]
try:
out_width = int(arg_list[1])
out_height = int(arg_list[2])
except:
out_width = None
out_height = None
self.saveImage(out_name,
format="AUTO",
custom_width=out_width,
custom_height=out_height)
elif cmd_list[0] == "annotate":
arg_list = ''.join(cmd_list[1:]).split(',')
ann_type = arg_list[0]
if ann_type not in constants.ANNOTATION_NAMES_DICT:
continue
if len(arg_list) > 1:
self.selectSequencesByName(arg_list[1:])
self.addAnnotation(constants.ANNOTATION_NAMES_DICT[ann_type])
elif cmd_list[0] == "color":
ann_type = cmd_list[1]
if cmd_list[1] not in constants.COLOR_NAMES_DICT:
continue
self.setColorMode(constants.COLOR_NAMES_DICT[cmd_list[1]])
elif cmd_list[0] == "background":
arg_list = ''.join(cmd_list[1:]).split(',')
color = self.colorToRGB(arg_list[0])
self.setBackgroundColor(color)
elif cmd_list[0] == "color_name":
arg_list = ''.join(cmd_list[1:]).split(',')
color = self.colorToRGB(arg_list[0])
if len(arg_list) > 1:
self.selectSequencesByName(arg_list[1:])
self.colorSequenceNames(color)
elif cmd_list[0] == "select_columns":
arg = ''.join(cmd_list[1:])
res_range = [
c for c in arg if c in [
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", ",",
"-"
]
]
res_list = res_range.split(',')
for res in res_list:
try:
col = int(res)
self.sequence_group.selectColumns(col - 1,
col - 1,
update_viewer=False)
res = None
except:
pass
if res and '-' in res:
col = res.split('-')
if len(col) > 1:
try:
col0 = int(col[0])
col1 = int(col[1])
self.sequence_group.selectColumns(
col0 - 1, col1 - 1, update_viewer=False)
except:
pass
elif cmd_list[0] == "mark_residues":
arg = ''.join(cmd_list[1:])
color = self.colorToRGB(arg)
self.markResidues(color)
elif cmd_list[0][:3] == "set":
set_list = ''.join(cmd_list[1:]).split('=')
if len(set_list) < 2:
continue
toggle = False
if set_list[1].lower() == "true":
toggle = True
if not hasattr(self, set_list[0]):
continue
if set_list[0] == "separator_scale":
toggle = int(set_list[1])
if set_list[0] == "wrapped_width":
toggle = int(set_list[1])
if set_list[0] == "font_size":
toggle = int(set_list[1])
setattr(self, set_list[0], toggle)
if set_list[0] == "font_size":
self.updateFontSize()
self.update()
self.job_progress_dialog = progress_dialog
return True
def setCallback(self, callback, event_type="residue_selection_changed"):
"""
Sets a sequence viewer callback.
:type event_type: string
:param event_type: Type of callback event.
:param callback: Callback function to be called.
"""
if event_type == "residue_selection_changed":
self.cb_residue_selection_changed = callback
elif event_type == "sequence_selection_changed":
self.cb_sequence_selection_changed = callback
elif event_type == "contents_changed":
self.cb_contents_changed = callback
def getContents(self, all_sequences=False):
"""
Returns contents of the current sequence group (tab).
:type all_sequences: boolean
:param all_sequences: If False (default), return only protein sequences.
If True, return all sequences including ruler,
annotations, spacers, etc.
:rtype: list of tuples
:return: Returns a list of following tuples: (sequence_index,
sequence_short_name, sequence_full_name, maestro_entry_id,
maestro_chain_name, sequence_string)
"""
out_list = []
for index, seq in enumerate(self.sequence_group.sequences):
if all_sequences or seq.isValidProtein():
out_list.append(
(index, seq.short_name, seq.name, seq.maestro_entry_id,
seq.maestro_chain_name, seq.text()))
return out_list
def selectColumns(self, column_list):
"""
Selects specified columns in the viewer.
First, the function deselects all contents, then selects
columns specified by the provided list of alignment indices.
:type column_list: list of integers
:param column_list: List of colums to select.
"""
self.sequence_group.unselectAll()
for index in column_list:
self.sequence_group.selectColumns(index, index, update_viewer=False)
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def selectResidues(self, sequence_list):
"""
Selects specified residues in the viewer.
The function deselects all contents, then selects residues
specified by the alignment indices for each sequence.
:type sequence_list: list of tuples
:param sequence_list: Each tuple includes (sequence_index,
list_of_alignment_indices)
"""
self.sequence_group.unselectAll()
for seq_index, residue_list in sequence_list:
if seq_index >= 0 and seq_index < len(
self.sequence_group.sequences):
seq = self.sequence_group.sequences[seq_index]
for res_index in residue_list:
if res_index >= 0 and res_index < seq.length():
seq.residues[res_index].selected = True
self.selection_changed = True
self.updateView(generate_rows=False, update_colors=False)
def getGlobalAnnotations(
self,
annotation_types=["all"], # noqa: M511
ignore_query=False,
ignore_gaps=False):
"""
Returns global annotations for calculate for each position
of the alignment.
:type annotation_types: list of strings
:param annotation_types: List of global annotation types
to be calculated. The following types are allowed:
"all" (default value): All available annotations.
"variability_percentage": sequence variability (normalized
Shannon entropy) calculated for each alignment position.
"variability_count": Number of different residue types for
each alignment position.
"group_conservation": Classification based on pre-defined conservation
groups: 'strong' or 'weak' conservations.
The following numerical values are possible:
3: identity
2: strong conservation
1: weak conservation
0: no conservation
-1: gap in query
"query_match_percentage": Percentage of sequences that match
the corresponding residue of the query sequence.
"sasa": Solvent-accessible surface area. It is not used by default and
it has to be explicitly specified.
"sasa_percentage": Percentage of solvent-accessible surface area.
It is not used by default and it has to be explicitly specified.
:type ignore_query: boolean
:param ignore query: Determines if query (parent) sequence should be
included in calculations.
:type ignore_gaps: boolean
:param ignore_gaps: Determines if gaps in query sequence should be
included in calculations. If False, only values calculated for ungapped
positions will be returned.
:rtype: dictionary of lists of floats
:return: Returns a dictionary with annotation names as keys and lists of
numbers including calculated annotations for each alignment position.
"""
# Make sure the annotations are current
self.sequence_group.calculateProfile(ignore_query=ignore_query)
known_annotations = [
"variability_percentage", "variability_count", "group_conservation",
"query_match_percentage"
]
annotation_dict = {}
if "sasa" in annotation_types:
annotation_types.append("sasa")
if "all" in annotation_types:
annotation_types += known_annotations
for annotation in annotation_types:
if annotation == "variability_percentage":
ann = []
for index, r in enumerate(self.sequence_group.profile.residues):
if ignore_gaps and \
(index < len(self.sequence_group.reference)) and \
self.sequence_group.reference.residues[index].is_gap:
continue
ann.append(100.0 * r.entropy)
annotation_dict[annotation] = ann
elif annotation == "variability_count":
ann = []
for index, r in enumerate(self.sequence_group.profile.residues):
if ignore_gaps and \
(index < len(self.sequence_group.reference)) and \
self.sequence_group.reference.residues[index].is_gap:
continue
ann.append(len(r.composition))
annotation_dict[annotation] = ann
elif annotation == "group_conservation":
has_symbols = False
for seq in self.sequence_group.sequences:
if seq.type == constants.SEQ_SYMBOLS:
has_symbols = True
symbols = self.sequence_group.addConsensusSymbols()
ann = []
if symbols:
self.sequence_group.updateSymbols(symbols)
for index, r in enumerate(symbols.residues):
if ignore_gaps and \
(index < len(self.sequence_group.reference)) and \
self.sequence_group.reference.residues[index].is_gap:
continue
if r.code == '*':
ann.append(3)
elif r.code == ':':
ann.append(2)
elif r.code == '.':
ann.append(1)
else:
ann.append(0)
if not has_symbols:
self.sequence_group.sequences.remove(symbols)
annotation_dict[annotation] = ann
elif annotation == "query_match_percentage":
ann = []
for index, r in enumerate(self.sequence_group.profile.residues):
if ignore_gaps and \
(index < len(self.sequence_group.reference)) and \
self.sequence_group.reference.residues[index].is_gap:
continue
ann.append(100.0 * r.query_fraction)
annotation_dict[annotation] = ann
elif annotation == "sasa":
sasa_list = self.getSASA(
sequences=[self.sequence_group.reference],
ignore_gaps=ignore_gaps)
if sasa_list and sasa_list[0]:
sequence_index, list_residue_sasa = sasa_list[0]
annotation_dict[annotation] = list_residue_sasa
elif annotation == "sasa_percentage":
sasa_list = self.getSASA(
sequences=[self.sequence_group.reference],
ignore_gaps=ignore_gaps,
percentage=True)
if sasa_list and sasa_list[0]:
sequence_index, list_residue_sasa = sasa_list[0]
annotation_dict[annotation] = list_residue_sasa
elif annotation == "variations":
ann = []
for index, r in enumerate(self.sequence_group.profile.residues):
if ignore_gaps and \
(index < len(self.sequence_group.reference)) and \
self.sequence_group.reference.residues[index].is_gap:
continue
ann.append(list(r.composition))
annotation_dict[annotation] = ann
# Recalculate profile using default settings
self.sequence_group.calculateProfile()
return annotation_dict
def getSASA(
self,
sequences=[], # noqa: M511
selected_only=False,
ignore_gaps=False,
normalize=True,
percentage=False):
"""
Calculates residue sequence accessible surface area for each sequence.
The calculated area is not normalized.
:type sequences: list of Sequences
:param: List of sequences to calculate SASA. When multiple sequences
come from a single entry, the calculation will be optimized (performed
once per entry).
:type: ignore_gaps
:param: If True the gaps will not be included in the output list.
:type normalize: bool
:param normalize: Should we normalize the SASA area by area of amino acid
in default conformation.
:type percentage: bool
:param percentage: If True return percentage SASA instead of absolute
values.
:rtype: list of tuples
:return: Each tuple includes (sequence_index, list_residue_sasa)
If SASA could not be calculated (e.g. no structural information),
the list_residue_sasa is empty.
"""
maestro_helpers.maestroCalculateSASA(self.sequence_group,
sequences=sequences,
selected_only=selected_only,
normalize=normalize,
percentage=percentage)
group = sequences if sequences else self.sequence_group.sequences
out_list = []
for index, seq in enumerate(group):
sasa_list = []
out_list.append((index, sasa_list))
if selected_only and not seq.selected:
continue
if not seq.isValidProtein():
continue
for res in seq.residues:
if ignore_gaps and res.is_gap:
continue
sasa_list.append(res.area)
return out_list
def assignAntibodyScheme(self,
scheme='Chothia',
display_annotation=True,
annotation_color=(255, 0, 0),
remove=False,
renumber_entry=True,
renumber=True,
annotate=True,
select=False):
"""
Assigns a specified antibody numbering scheme.
:param scheme: Numbering scheme type. 'Chothia', 'EnhancedChothia'
'IMGT', 'AHo' or 'Kabat' are valid choices. Please refer to psp.antibody docs
for more details.
:param display_annotation: Displays a custom annotation for
the assigned loops (default: True)
:param annotation_color: R,G,B color uses for annotation
(default: red)
:type remove: bool
:param remove: If True, instead of creating new annotations,
the function removes any existing CDR annotations and quits.
:type renumber: bool
:param renumber: When True, the function will assign new residue
numbers according to the selected scheme.
:type renumber_entry: bool
:param renumber_entry: When True the function will renumber
corresponding Maestro entry.
:type annotate: bool
:param annotate: When True, the function will create new CDR
annotations (note: the 'remove' paarameter will override this).
:type select: bool
:param select: When True, the function will select residues
belonging to the CDRs.
:rtype: int
:return: Number of successfully re-numbered sequences.
"""
if not PspSeqType:
return 0
if select:
self.sequence_group.unselectAll()
ok = 0
for seq in self.sequence_group.sequences:
if seq.isValidProtein():
if remove:
for child in seq.children:
if child.annotation_type == constants.ANNOTATION_CUSTOM and \
child.name == "CDR":
seq.children.remove(child)
continue
seq_text = seq.toString()
seq_type = PspSeqType(seq_text, scheme=scheme)
resid_list = seq_type.resid_list
cdr_index = seq_type.cdr_index
cdr_label = seq_type.cdr_label
region_list = []
if seq_type and cdr_index:
for index, pos in enumerate(cdr_index):
start, end = pos
region = (start, end, cdr_label[index],
annotation_color)
region_list.append(region)
if annotate:
self.sequence_group.addCustomAnnotation(
sequence=seq,
title="CDR",
name="CDR",
region_list=region_list)
if select:
self.sequence_group.selectRegions(seq, region_list)
self.selection_changed = True
residues = seq.gaplessResidues()
if renumber and resid_list and \
len(resid_list) <= len(residues):
for index, resid in enumerate(resid_list):
if resid[-1] < '0' or resid[-1] > '9':
residues[index].num = int(resid[1:-1])
residues[index].icode = resid[-1]
else:
residues[index].num = int(resid[1:])
residues[index].icode = ' '
# Renumber the remanining part of the sequence
if len(residues) > index:
num = residues[index].num + 1
for res in residues[index + 1:]:
res.num = num
res.icode = ' '
num += 1
ok += 1
if renumber_entry:
maestro_helpers.renumberMaestroEntry(self, seq)
self.contents_changed = True
self.updateView()
return ok
def analyzeBindingSite(self):
"""
Performs binding site analysis.
"""
dialog = analyzeBindingSiteDialog(self)
dialog.setSequence(None, None)
# if self.sequence_group.reference and
# self.sequence_group.reference.from_maestro:
dialog.setSequence(self.sequence_group, self.sequence_group.reference)
dialog.show()
# else:
# dialog.error_dialog("Analyze Binding Site",
# "No structure available in the sequence viewer.")
def maestroWorkspaceChanged(self, what_changed):
"""
This function is invoked whenever Maestro colors change. It updates colors
and selection state of the sequences associated with Maestro.
"""
if self.maestro_busy:
return
if not self.auto_synchronize:
return
if not self.isVisible():
return
if what_changed == "color":
maestro_helpers.synchronizePropertiesWithMaestro(self, colors=True)
elif what_changed == "selection":
maestro_helpers.synchronizePropertiesWithMaestro(self,
selection=True)
elif what_changed == "geometry" or \
what_changed == "representation" or \
what_changed == "coordinates" or \
what_changed == "unknown":
pass
else:
# Everything changed, update project (slow!)
self.maestroProjectOpen()
maestro_helpers.maestroSynchronize(self.sequence_group)
self.repaint()
def maestroProjectChanged(self):
"""
This method is invoked whenever Maestro project changes.
"""
if self.maestro_busy:
return
if not self.auto_synchronize:
return
if not self.isVisible():
return
maestro_helpers.maestroSynchronize(self.sequence_group)
# Update MSV window.
self.updateView()
def maestroProjectClose(self):
"""
This function is invoked whenever Maestro project is about
to be closed.
"""
if self.maestro_busy or not self.save_state:
return
project_path = maestro_helpers.maestroGetProjectPath()
if project_path:
self.last_project_path = ""
self.saveProject(project_path + "project.msv")
self.sequence_group.removeMaestroSequences()
self.contents_changed = True
self.updateView()
def maestroProjectOpen(self):
"""
This function is invoked whenever Maestro project is opened.
"""
if self.maestro_busy:
return
project_path = maestro_helpers.maestroGetProjectPath()
if project_path and not self.last_project_path:
if self.loadProject(project_path + "project.msv"):
self.last_project_path = project_path
else:
project_path = maestro_helpers.maestroGetProjectPath(old=True)
if self.loadProject(project_path + "project.msv"):
self.last_project_path = project_path
def initMaestro(self):
"""
Initializes Maestro callbacks.
"""
if maestro_helpers.hasMaestro():
if not maestro.is_function_registered('workspace_changed',
self.maestroWorkspaceChanged):
maestro.workspace_changed_function_add(
self.maestroWorkspaceChanged)
if not maestro.is_function_registered('project_update',
self.maestroProjectChanged):
maestro.project_update_callback_add(self.maestroProjectChanged)
if not maestro.is_function_registered('project_close',
self.maestroProjectClose):
maestro.project_close_callback_add(self.maestroProjectClose)
if not maestro.is_function_registered('command',
self.maestroCommandCallback):
maestro.command_callback_add(self.maestroCommandCallback)
def removeMaestroCallbacks(self):
"""
Removes registered Maestro callbacks.
"""
if maestro_helpers.hasMaestro():
if maestro.is_function_registered('workspace_changed',
self.maestroWorkspaceChanged):
maestro.workspace_changed_function_remove(
self.maestroWorkspaceChanged)
if maestro.is_function_registered('project_update',
self.maestroProjectChanged):
maestro.project_update_callback_remove(
self.maestroProjectChanged)
if maestro.is_function_registered('project_close',
self.maestroProjectClose):
maestro.project_close_callback_remove(self.maestroProjectClose)
if maestro.is_function_registered('command',
self.maestroCommandCallback):
maestro.command_callback_remove(self.maestroCommandCallback)
def maestroCommandCallback(self, command):
"""
Called when Maestro executes a command.
"""
if (command and command.startswith("projectcopy") and
self.ready_to_save and self.save_state):
project_path = maestro_helpers.maestroGetProjectPath()
if project_path:
self.saveProject(project_path + "project.msv", auto_save=True)
def translate(self):
"""
Translates DNA / RNA to amino acids.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Translate Sequence")
for seq in self.sequence_group.sequences:
if seq.selected:
if seq.isDNA():
seq.translateDNA()
elif seq.isRNA():
seq.translateRNA()
self.contents_changed = True
self.updateView()
def addUserAnnotation(self, annotation_type="region"):
if annotation_type == "custom_sequence":
self.sequence_group.addCustomSequence()
self.contents_changed = True
self.updateView()
return
if not self.sequence_group.hasSelectedResidues():
return
# Determine selected region boundaries
start = end = -1
for col in range(self.sequence_group.findMaxLength() + 1):
if self.sequence_group.isColumnSelected(col, weak=True):
if start < 0:
start = col
else:
if start >= 0:
end = col - 1
if start >= 0 and end >= 0:
if annotation_type == "region":
self.sequence_group.user_annotations.append(
(annotation_type, start, end, "", (255, 127, 0)))
elif annotation_type == "rectangle":
first = last = None
for seq in self.sequence_group.sequences:
if start < seq.length():
if seq.residues[start].selected:
first = seq
break
for seq in reversed(self.sequence_group.sequences):
if end < seq.length():
if seq.residues[end].selected:
last = seq
break
self.sequence_group.user_annotations.append(
(annotation_type, (first, start), (last, end), "",
(240, 0, 0)))
start = end = -1
self.updateView()
def removeUserAnnotations(self):
self.sequence_group.user_annotations = []
self.updateView()
def updateAnnotationsMenu(self):
if not self.update_annotations_menu:
return
if self.parent() and self.parent().parent():
if hasattr(self.parent().parent(), "annotationsMenu"):
ann_states = {}
for ann in constants.LOCAL_ANNOTATIONS:
ann_states[ann] = False
ann_states['secondary'] = False
has_cdr = False
has_consensus = False
has_mean_hydro = False
has_mean_pi = False
has_logo = False
has_symbols = False
gparent = self.parent().parent()
for seq in self.sequence_group.sequences:
if seq.type == constants.SEQ_CONSENSUS:
has_consensus = True
elif seq.type == constants.SEQ_LOGO:
has_logo = True
elif seq.type == constants.SEQ_SYMBOLS:
has_symbols = True
if seq.annotation_type == constants.ANNOTATION_MEAN_HYDRO:
has_mean_hydro = True
if seq.annotation_type == constants.ANNOTATION_MEAN_PI:
has_mean_pi = True
for child in seq.children:
if child.type == constants.SEQ_SECONDARY:
ann_states['secondary'] = True
else:
ann_states[child.annotation_type] = True
if child.annotation_type == constants.ANNOTATION_CUSTOM and \
child.name == "CDR":
has_cdr = True
gparent.addSSAAct.setChecked(ann_states['secondary'])
gparent.addResnumAct.setChecked(
ann_states[constants.ANNOTATION_RESNUM])
gparent.addBFactorAct.setChecked(
ann_states[constants.ANNOTATION_BFACTOR])
gparent.addHydrophobicityAct.setChecked(
ann_states[constants.ANNOTATION_HYDROPHOBICITY])
gparent.addPIAct.setChecked(ann_states[constants.ANNOTATION_PI])
gparent.addSSBondAct.setChecked(
ann_states[constants.ANNOTATION_SSBOND])
gparent.sidechainChemistryAct.setChecked(
ann_states[constants.ANNOTATION_SIDECHAIN_CHEMISTRY])
gparent.helixPropensityAct.setChecked(
ann_states[constants.ANNOTATION_HELIX_PROPENSITY])
gparent.strandPropensityAct.setChecked(
ann_states[constants.ANNOTATION_STRAND_PROPENSITY])
gparent.turnPropensityAct.setChecked(
ann_states[constants.ANNOTATION_TURN_PROPENSITY])
gparent.stericGroupAct.setChecked(
ann_states[constants.ANNOTATION_STERIC_GROUP])
gparent.exposureTendencyAct.setChecked(
ann_states[constants.ANNOTATION_EXPOSURE_TENDENCY])
gparent.helixTerminatorsAct.setChecked(
ann_states[constants.ANNOTATION_HELIX_TERMINATORS])
gparent.renumberToAntibodyAct.setChecked(has_cdr)
gparent.addSequenceLogoAct.setChecked(has_logo)
gparent.addConsensusAct.setChecked(has_consensus)
gparent.addSymbolsAct.setChecked(has_symbols)
gparent.addMeanHydrophobicityAct.setChecked(has_mean_hydro)
gparent.addMeanPIAct.setChecked(has_mean_pi)
gparent.showLigandsAct.setChecked(
ann_states[constants.ANNOTATION_LIGAND])
def renumberResidues(self):
"""
Executes 'Renumber Residues' command.
"""
self.undo_stack.storeStateGroupDeep(self.sequence_group,
label="Renumber Residues")
dialog = RenumberResiduesDialog(self, self.sequence_group)
dialog.show()
dialog.exec()
self.updateView()
def getStructureAlignment(self):
maestro_helpers.maestroGetStructureAlignment(self, self.sequence_group)
self.contents_changed = True
self.updateView()
def associateMaestroEntries(self):
"""
Displays 'Associate Maestro Entries' dialog.
"""
if not self.associate_dialog:
self.associate_dialog = AssociateEntryPanel(self)
self.associate_dialog.show()
self.associate_dialog.raise_()
def selectLigandContacts(self, ligand=None):
"""
Selects residues in ligand proximity in selected ligand sequence.
"""
if ligand:
parent = ligand.parent_sequence
for idx, res in enumerate(ligand.residues):
if res.value > 0.0:
parent.residues[idx].selected = True
else:
for seq in self.sequence_group.sequences:
for child in seq.children:
if child.selected and \
child.annotation_type == constants.ANNOTATION_LIGAND:
for idx, res in enumerate(child.residues):
if res.value > 0.0:
seq.residues[idx].selected = True
self.selection_changed = True
self.updateView()
def incorporateStructure(self, st):
"""
Incorporates a structure passed as a Structure object.
"""
maestro_helpers.maestroIncorporateEntries(
self.sequence_group,
ct=st,
use_title=self.use_maestro_entry_title,
viewer=self)
self.contents_changed = True
self.updateView()
def showCompareSequencesDialog(self):
"""
Create an instance of 'Compare sequences' dialog (if necessary)
and open the dialog.
"""
if not self.compare_sequences_dialog:
self.compare_sequences_dialog = CompareSequencesDialog(self)
self.compare_sequences_dialog.show()
self.compare_sequences_dialog.raise_()