Source code for schrodinger.ui.sequencealignment.sequence_viewer

"""
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.


[docs]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. """
[docs] 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
[docs] 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()
[docs] 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
[docs] 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)
[docs] 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.width(' ') + 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()
[docs] 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()
[docs] 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()
[docs] 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
[docs] 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()
[docs] def fillGaps(self): """ Fill a selected region with gaps. """ self.sequence_group.fillGaps() self.contents_changed = True self.updateView()
[docs] 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()
[docs] 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()
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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()
[docs] def lockGaps(self): """ Locks gaps. """ self.sequence_group.lockGaps() self.update()
[docs] def unlockGaps(self): """ Unlocks gaps. """ self.sequence_group.unlockGaps() self.update()
[docs] 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)
[docs] def selectStructureBlocks(self): """ Selects blocks that have structure. """ self.sequence_group.selectStructureBlocks() self.selection_changed = True self.updateView(generate_rows=False, update_colors=False)
[docs] def selectIdentities(self): """ Selects identical residues in columns. """ self.sequence_group.selectIdentities() self.selection_changed = True self.updateView(generate_rows=False, update_colors=False)
[docs] 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
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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()
[docs] def expandAllSequences(self): """ Expands all sequences. """ self.sequence_group.showAllChildren() self.updateView()
[docs] def collapseAllSequences(self): """ Collapses all sequences. """ self.sequence_group.hideAllChildren() self.updateView()
[docs] 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()
[docs] 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()
[docs] def toggleHistory(self): """ Toggles changes tracking feature. """ self.undo_stack.storeStateGroupDeep(self.sequence_group, label="Track Changes") self.sequence_group.toggleHistory() self.updateView()
[docs] def resetHistory(self): """ Toggles changes tracking feature. """ self.undo_stack.storeStateGroupDeep(self.sequence_group, label="Reset History") self.sequence_group.resetHistory() self.updateView()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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
[docs] 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)
[docs] 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()
[docs] def getMode(self): """ Gets current sequence viewer operational mode. :rtype: int :return: Current sequence viewer mode. """ return self.mode
[docs] 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
[docs] 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
[docs] 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)
[docs] 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()
[docs] 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
[docs] 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)
[docs] def undo(self): """ Undoes the last operation. """ status = self.undo_stack.undo(self) self.contents_changed = True self.updateView() return status
[docs] def redo(self): """ Redoes previously undone operation. """ status = self.undo_stack.redo(self) self.contents_changed = True self.updateView() return status
[docs] def runSSP(self): """ Runs secondary structure prediction and incorporates the results. """ self.runPredictors(["sspro"])
[docs] 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()
[docs] 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
[docs] 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)
[docs] 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_()
[docs] 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
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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)
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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
[docs] 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
[docs] 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()
[docs] def weightColorsSettings(self): """ Enables weighting by colors. """ dialog = WeightColorsDialog(self, self.sequence_group) dialog.exec_()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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)
[docs] 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
[docs] 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()
[docs] 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()
[docs] 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
[docs] def displayMessage(self, message): """ Displays a message on a status bar. """ if self.message_status_bar: self.message_status_bar.setText(message)
[docs] 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)
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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()
[docs] 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
[docs] def clearConstraints(self): """ Clears pairwise alignment constraints. """ self.sequence_group.clearConstraints() self.updateView()
[docs] 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()
[docs] 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()
[docs] def clearQueryConstraints(self): """ Toggles a query sequence constraints. """ self.sequence_group.clearConstraints() self.updateView()
[docs] 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()
[docs] 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_()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] def computeSequenceProfile(self): """ Computes internal sequence profile and updates self. """ self.sequence_group.calculateProfile() self.sequence_group.updateVariableSequences() self.updateView()
[docs] def alignmentSettingsDialog(self): """ Opens alignment settings dialog. """ self.alignment_settings_dialog.group = self.sequence_group self.alignment_settings_dialog.exec_()
[docs] 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
[docs] def showJobLog(self): """ Displays a job log window. """ dialog = createJobProgressDialog("Job Log", parent=self) dialog.setButtonText("Close") dialog.exec_()
[docs] def showJobSettings(self): """ Displays a job settings dialog. """ result = createJobSettingsDialog(self) if result: self.job_settings = result
[docs] def buildModel(self): """ Builds 3D model of reference sequence. """ showPrimeSettingsDialog(self, self.sequence_group, prime.prime_run, self.job_progress_dialog)
[docs] def alignStructures(self): """ Aligns available structures. """ jobs.run_align_structures(self.sequence_group, self.job_progress_dialog, viewer=self)
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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
[docs] 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()
[docs] 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
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] 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()
[docs] def colorSequenceNames(self, color): self.sequence_group.colorSequenceNames(color) self.update()
[docs] 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()
[docs] 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()
[docs] 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
[docs] 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
[docs] 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()
[docs] 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()
[docs] 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)
[docs] def passSelectionToMaestro(self): tmp = self.auto_synchronize self.auto_synchronize = True maestro_helpers.maestroSelectResiduesInWorkspace(self) self.auto_synchronize = tmp
[docs] 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")
[docs] 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
[docs] def resizeEvent(self, event): """ Resize event handler. Update font face and size here. """ self.updateFontSize(self.font_size)
[docs] 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
[docs] 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)
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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
[docs] 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
[docs] 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
[docs] 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.")
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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()
[docs] 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()
[docs] def removeUserAnnotations(self): self.sequence_group.user_annotations = [] self.updateView()
[docs] 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])
[docs] 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()
[docs] def getStructureAlignment(self): maestro_helpers.maestroGetStructureAlignment(self, self.sequence_group) self.contents_changed = True self.updateView()
[docs] 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_()
[docs] 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()
[docs] 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()
[docs] 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_()