''' Title : mtsslTrilaterate Version : 1.2 Author : Dinar Abdullin Date : March 2016 E-mail : abdullin@pc.uni-bonn.de Short description: mtsslTrilaterate is a PyMOL plugin, which provides a set of mathematical tools to trilaterate paramagnetic spin centers in biomolecules using spin labels and ESR distance constraints ''' import os import sys import threading import string import math import numpy from numpy import * from numpy.linalg import * from pymol import * from pymol import stored from pymol.cgo import * import wx import wx.grid import wx.lib.mixins.listctrl as listmix from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub class mtsslTrilaterate(wx.Frame): '''GUI of mtsslTrilaterate''' def __init__(self, parent, ID, title): wx.Frame.__init__(self, parent, ID, title, wx.DefaultPosition, wx.Size(700, 525)) self.VariablesInitialization() pub.subscribe(self.OnImportParam, 'import.param') self.UserInterface() self.Centre() self.Show() def VariablesInitialization(self): '''Initialize variables''' # System parameters self.maxNumOfLabels = 10 self.curLabel = 0 self.inputAccepted = False # Data arrays self.labelNames = [None for col in range(self.maxNumOfLabels)] for index in range(self.maxNumOfLabels): self.labelNames[index] = "Label_" + str(index + 1) self.labelCoordMean = [[None for col in range(3)] for row in range(self.maxNumOfLabels)] self.labelCoordStd = [[None for col in range(3)] for row in range(self.maxNumOfLabels)] self.distMean = [None for col in range(self.maxNumOfLabels)] self.distStd = [None for col in range(self.maxNumOfLabels)] self.numOfLabels = 0 self.targetCoordMean = [[None for col in range(3)]] self.targetCoordStd = [[None for col in range(3)]] self.chiSquare = None self.numOfIter = None # Calculation parameters (Preferences menu) self.coordFactor = 1.0 self.distFactor = 10.0 self.calcMode = 0 self.maxNumOfIter = 10000 self.minChiSquare = 0.000001 self.lambdaFirst = 0.001 self.lambdaStep = 10 self.ellipsoidColor = [1.00, 0.43, 0.00] # orange self.spheresColor = [0.00, 1.00, 1.00] # blue self.spheresTransparency = 0.3 self.ellipsoidTransparency = 0.0 self.confidenceLevel = 1 def OnImportParam(self, data): '''Import the parameters from the preferences menu''' self.calcMode = data[0] self.maxNumOfIter = data[1] self.minChiSquare = data[2] self.lambdaFirst = data[3] self.lambdaStep = data[4] self.coordFactor = data[5] self.distFactor = data[6] self.spheresColor = data[7] self.spheresTransparency = data[8] self.ellipsoidColor = data[9] self.ellipsoidTransparency = data[10] self.confidenceLevel = data[11] def UserInterface(self): '''Make GUI''' panel = wx.Panel(self, -1) panel.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL)) # MAIN SIZER sizer = wx.GridBagSizer(hgap=1, vgap=1) sizer.AddGrowableCol(1) panel.SetSizer(sizer) # INPUT BOX inputBox = wx.StaticBox(panel, label='') sizerI1 = wx.StaticBoxSizer(inputBox, wx.VERTICAL) sizer.Add(sizerI1, pos=(0, 0), flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.BOTTOM, border=5) # Header headerInput = wx.StaticText(panel, label="INPUT") headerInput.SetForegroundColour(wx.Color(112, 5, 0)) headerInput.SetFont(wx.Font(11, wx.DEFAULT, wx.NORMAL, wx.BOLD)) sizerI1.Add(headerInput, flag=wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=10) # Input data table self.table = wx.grid.Grid(panel, -1, size=(-1, -1)) rowsNum = self.maxNumOfLabels + 2 self.table.CreateGrid(rowsNum, 6) # Hide initial column labels and row labels colLabels = ['' for col in range(6)] rowLabels = ['' for row in range(rowsNum)] for col in range(len(colLabels)): self.table.SetColLabelValue(col, colLabels[col]) for row in range(len(rowLabels)): self.table.SetRowLabelValue(row, rowLabels[row]) self.table.SetRowLabelSize(1) self.table.SetColLabelSize(1) # Create new column labels and row labels self.table.SetCellSize(0, 0, 2, 1) self.table.SetCellValue(0, 0, 'Label name') self.table.SetCellSize(0, 1, 1, 3) self.table.SetCellValue(0, 1, 'Label coordinates ' + u'(\u00c5)') self.table.SetCellValue(1, 1, 'x') self.table.SetCellValue(1, 2, 'y') self.table.SetCellValue(1, 3, 'z') self.table.SetCellSize(0, 4, 1, 2) self.table.SetCellValue(0, 4, 'Distance ' + u'(\u00c5)') self.table.SetCellValue(1, 4, 'mean') self.table.SetCellValue(1, 5, 'std') # Do not edit column labels and row labels for col in range(6): for row in range(2): self.table.SetReadOnly(row, col, isReadOnly=True) # Hide white borders panelcolour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) self.table.SetDefaultCellBackgroundColour(panelcolour) for row in range(2, rowsNum): for col in range(6): self.table.SetCellBackgroundColour(row, col, wx.WHITE) self.table.Refresh() # Set default text format self.table.SetDefaultCellAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.table.SetLabelFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL)) # Set column and row size self.table.SetColSize(0, 100) for col in range(1, 6): self.table.SetColSize(col, 55) for row in range(0, rowsNum): self.table.SetRowSize(row, 22) # Preset names of labels for row in range(2, rowsNum): self.table.SetCellValue(row, 0, self.labelNames[row - 2]) # Table commands self.table.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnInputTableCellSelect) self.table.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.OnInputTableCellSelect) self.table.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnInputTableCellChange) # Attach to sizer sizerI1.Add(self.table, proportion=1, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=10) # Load list with labels sizerI11 = wx.BoxSizer(wx.HORIZONTAL) sizerI1.Add(sizerI11, proportion=0, flag=wx.LEFT | wx.RIGHT, border=10) importCoordFromPymol = wx.Button(panel, label='Import from PyMOL') importCoordFromPymol.Bind(wx.EVT_BUTTON, self.OnImportCoordFromPymol) sizerI11.Add(importCoordFromPymol, flag=wx.RIGHT, border=5) # Load coordinates from data file loadCoordFromFile = wx.Button(panel, label='Load coordinates') loadCoordFromFile.Bind(wx.EVT_BUTTON, self.OnLoadCoordFromFile) sizerI11.Add(loadCoordFromFile, flag=wx.RIGHT, border=30) # Load distances from data file loadDistFromFile = wx.Button(panel, label='Load distances') loadDistFromFile.Bind(wx.EVT_BUTTON, self.OnLoadDistFromFile) sizerI11.Add(loadDistFromFile) # Import from PyMOL sizerI12 = wx.BoxSizer(wx.HORIZONTAL) sizerI1.Add(sizerI12, flag=wx.ALL, border=10) sizerI12.Add(wx.StaticText(panel, label="List of labels:"), flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5) self.labelList = wx.ComboBox(panel, choices=[], size=[200, 20], style=wx.CB_READONLY) sizerI12.Add(self.labelList, flag=wx.RIGHT, border=5) self.loadFromPymol = wx.Button(panel, label='Load') self.loadFromPymol.Bind(wx.EVT_BUTTON, self.OnLoadFromPymol) sizerI12.Add(self.loadFromPymol) self.labelList.Disable() self.loadFromPymol.Disable() # General commands sizerI13 = wx.BoxSizer(wx.HORIZONTAL) acceptInput = wx.Button(panel, label='Accept') acceptInput.Bind(wx.EVT_BUTTON, self.OnAcceptInput) sizerI13.Add(acceptInput) clearInput = wx.Button(panel, label='Clear All') clearInput.Bind(wx.EVT_BUTTON, self.OnClearInput) sizerI13.Add(clearInput, flag=wx.LEFT, border=10) sizerI1.Add(sizerI13, proportion=0, flag=wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, border=10) # OUTPUT BOX outputBox = wx.StaticBox(panel, label='') sizerO1 = wx.StaticBoxSizer(outputBox, wx.VERTICAL) sizer.Add(sizerO1, pos=(0, 1), flag=wx.EXPAND | wx.ALL, border=5) # Header headerOutput = wx.StaticText(panel, label='OUTPUT') headerOutput.SetForegroundColour(wx.Color(112, 5, 0)) headerOutput.SetFont(wx.Font(11, wx.DEFAULT, wx.NORMAL, wx.BOLD)) sizerO1.Add(headerOutput, flag=wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, border=10) # Computation copmute = wx.Button(panel, label='Compute') copmute.Bind(wx.EVT_BUTTON, self.OnCompute) sizerO1.Add(copmute, flag=wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, border=14) # Otput data table self.table1 = wx.grid.Grid(panel, -1, size=(-1, -1)) self.table1.CreateGrid(4, 4) # Hide initial column labels and row labels colLabels = ['' for col in range(4)] rowLabels = ['' for row in range(4)] for col in range(len(colLabels)): self.table1.SetColLabelValue(col, colLabels[col]) for row in range(len(rowLabels)): self.table1.SetRowLabelValue(row, rowLabels[row]) self.table1.SetRowLabelSize(1) self.table1.SetColLabelSize(1) # Create new column labels and row labels self.table1.SetCellSize(0, 0, 2, 1) self.table1.SetCellSize(0, 1, 1, 3) self.table1.SetCellValue(0, 1, 'Target coordinates ' + u'(\u00c5)') self.table1.SetCellValue(1, 1, 'x') self.table1.SetCellValue(1, 2, 'y') self.table1.SetCellValue(1, 3, 'z') self.table1.SetCellValue(2, 0, 'mean') self.table1.SetCellValue(3, 0, 'std') # Do not edit column labels and row labels for col in range(4): for row in range(4): self.table1.SetReadOnly(row, col, isReadOnly=True) # Hide white borders panelcolour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) self.table1.SetDefaultCellBackgroundColour(panelcolour) for row in range(2, 4): for col in range(1, 4): self.table1.SetCellBackgroundColour(row, col, wx.WHITE) self.table1.Refresh() # Set default text format self.table1.SetDefaultCellAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.table1.SetLabelFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL)) # Set column and row size for col in range(4): self.table1.SetColSize(col, 55) for row in range(4): self.table1.SetRowSize(row, 22) # Attach to sizer sizerO1.Add(self.table1, flag=wx.ALL, border=10) # Statistics sizerO11 = wx.GridBagSizer(hgap=5, vgap=5) sizerO1.Add(sizerO11, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=10) sizerO11.Add(wx.StaticText(panel, label='Chi-square value:'), pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL) sizerO11.Add(wx.StaticText(panel, label='Number of iterations:'), pos=(1, 0), flag=wx.ALIGN_CENTER_VERTICAL) self.chiSquareBox = wx.TextCtrl(panel, size=(80, 20), style=wx.TE_CENTRE) sizerO11.Add(self.chiSquareBox, pos=(0, 1)) self.numOfIterBox = wx.TextCtrl(panel, size=(80, 20), style=wx.TE_CENTRE) sizerO11.Add(self.numOfIterBox, pos=(1, 1)) # General commands sizerO12 = wx.BoxSizer(wx.HORIZONTAL) sizerO1.Add(sizerO12, flag=wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=10) exportToPymol = wx.Button(panel, label='Export to PyMOL') exportToPymol.Bind(wx.EVT_BUTTON, self.OnExportToPymol) sizerO12.Add(exportToPymol) clearOutput = wx.Button(panel, label='Clear') clearOutput.Bind(wx.EVT_BUTTON, self.OnClearOutput) sizerO12.Add(clearOutput, flag=wx.LEFT, border=10) # MENU BAR menubar = wx.MenuBar() fileMenu = wx.Menu() openItem = wx.MenuItem(fileMenu, 11, '&Open\tCtrl+O') self.Bind(wx.EVT_MENU, self.OnOpen, id=11) fileMenu.AppendItem(openItem) saveItem = wx.MenuItem(fileMenu, 12, '&Save As ...\tCtrl+S') self.Bind(wx.EVT_MENU, self.OnSave, id=12) fileMenu.AppendItem(saveItem) closeItem = wx.MenuItem(fileMenu, 13, '&Close\tCtrl+C') self.Bind(wx.EVT_MENU, self.OnClose, id=13) fileMenu.AppendItem(closeItem) fileMenu.AppendSeparator() prefItem = wx.MenuItem(fileMenu, 14, '&Preferences\tCtrl+P') self.Bind(wx.EVT_MENU, self.OnOpenPref, id=14) fileMenu.AppendItem(prefItem) fileMenu.AppendSeparator() exitItem = wx.MenuItem(fileMenu, 15, '&Exit\tAlt+F4') self.Bind(wx.EVT_MENU, self.OnExit, id=15) fileMenu.AppendItem(exitItem) pymolMenu = wx.Menu() loadItem = wx.MenuItem(pymolMenu, 21, '&Load structure to PyMOL') self.Bind(wx.EVT_MENU, self.OnLoad, id=21) pymolMenu.AppendItem(loadItem) removeItem = wx.MenuItem(pymolMenu, 22, '&Remove structure from PyMOL') self.Bind(wx.EVT_MENU, self.OnRemove, id=22) pymolMenu.AppendItem(removeItem) mtsslWizardItem = wx.MenuItem(pymolMenu, 23, '&Run MtsslWizard') self.Bind(wx.EVT_MENU, self.OnRunMtsslWizard, id=23) pymolMenu.AppendItem(mtsslWizardItem) helpMenu = wx.Menu() aboutItem = wx.MenuItem(helpMenu, 31, '&About\tCtrl+A') self.Bind(wx.EVT_MENU, self.OnAbout, id=31) helpMenu.AppendItem(aboutItem) helpMenu.AppendSeparator() helpItem = wx.MenuItem(helpMenu, 32, '&Help\tF1') #self.Bind(wx.EVT_MENU, self.OnHelp, id=32) helpMenu.AppendItem(helpItem) menubar.Append(fileMenu, '&File') menubar.Append(pymolMenu, '&PyMOL') menubar.Append(helpMenu, '&Help') self.SetMenuBar(menubar) # STATUS BAR self.statusbar = self.CreateStatusBar() pub.subscribe(self.OnStatusUpdate, 'statusbar.update') def OnStatusUpdate(self, status): self.statusbar.SetStatusText(status,0) def OnInputTableCellSelect(self, event): '''Read the number of the clicked cell''' curRow = event.GetRow() if (curRow >= 2): # Highlight selected label self.table.SetCellBackgroundColour((self.curLabel + 2), 0, wx.WHITE) self.table.SetCellBackgroundColour(curRow, 0, wx.Colour(255, 168, 0)) self.table.Refresh() # Set number of current label self.curLabel = curRow - 2 event.Skip() def OnInputTableCellChange(self, event): '''Save the data entered into the table as input arrays''' curRow = event.GetRow() curCol = event.GetCol() if (curCol == 0): self.labelNames[self.curLabel] = self.table.GetCellValue(curRow, curCol) elif (curCol == 1): try: self.labelCoordMean[self.curLabel][0] = float(self.table.GetCellValue(curRow, curCol)) self.statusbar.SetStatusText('Enter the coordinates and distances. Click the Accept button after all input data has been entered.') except: self.labelCoordMean[self.curLabel][0] = None self.statusbar.SetStatusText('Incorrect input data!') elif (curCol == 2): try: self.labelCoordMean[self.curLabel][1] = float(self.table.GetCellValue(curRow, curCol)) self.statusbar.SetStatusText('Enter the coordinates and distances. Click the Accept button after all input data has been entered.') except: self.labelCoordMean[self.curLabel][1] = None self.statusbar.SetStatusText('Incorrect input data!') elif (curCol == 3): try: self.labelCoordMean[self.curLabel][2] = float(self.table.GetCellValue(curRow, curCol)) self.statusbar.SetStatusText('Enter the coordinates and distances. Click the Accept button after all input data has been entered.') except: self.labelCoordMean[self.curLabel][2] = None self.statusbar.SetStatusText('Incorrect input data!') elif (curCol == 4): try: self.distMean[self.curLabel] = float(self.table.GetCellValue(curRow, curCol)) self.statusbar.SetStatusText('Enter the coordinates and distances. Click the Accept button after all input data has been entered.') except: self.distMean[self.curLabel] = None self.statusbar.SetStatusText('Incorrect input data!') elif (curCol == 5): try: self.distStd[self.curLabel] = float(self.table.GetCellValue(curRow, curCol)) self.statusbar.SetStatusText('Enter the coordinates and distances. Click the Accept button after all input data has been entered.') except: self.distStd[self.curLabel] = None self.statusbar.SetStatusText('Incorrect input data!') # Update table self.UpdateInputTable() # Input data doesn't accepted self.inputAccepted = False event.Skip() def OnLoadCoordFromFile(self, event): '''Load the labels' coordinates from a data file''' dlg = wx.FileDialog(self, message="Choose a file", defaultDir=os.getcwd(), defaultFile="", wildcard="Text Files (.txt, .dat)|*.txt;*.dat", style=wx.OPEN | wx.CHANGE_DIR) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() dataFile = open(path, 'r') # Load the array of coordinates coord = [] for (i, line) in enumerate(dataFile): coordLine = line.split() coord += [[float(coordLine[0]) * self.coordFactor, float(coordLine[1]) * self.coordFactor, float(coordLine[2]) * self.coordFactor]] # Calculate average coordinates and their standard deviations self.labelCoordMean[self.curLabel], self.labelCoordStd[self.curLabel] = AverageCoordCalc(coord) # Update table self.UpdateInputTable() # Input data doesn't accepted self.inputAccepted = False # Statusbar message self.statusbar.SetStatusText('The coordinates of the label ' + str(self.curLabel + 1) + ' are imported from data file!') event.Skip() def OnLoadDistFromFile(self, event): '''Load the distance from a data file''' dlg = wx.FileDialog(self, message="Choose a file", defaultDir=os.getcwd(), defaultFile="", wildcard="Text Files (.txt, .dat)|*.txt;*.dat", style=wx.OPEN | wx.CHANGE_DIR) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() dataFile = open(path, 'r') # Load a distance distribution dist = [] density = [] for (i, line) in enumerate(dataFile): distrib = line.split() dist += [float(distrib[0]) * self.distFactor] density += [float(distrib[1])] # Calculate distance and its standard deviation self.distMean[self.curLabel], self.distStd[self.curLabel] = AverageDistCalc(dist, density) # Update table self.UpdateInputTable() # Input data doesn't accepted self.inputAccepted = False # Statusbar message self.statusbar.SetStatusText('The distance between the target and the label ' + str(self.curLabel + 1) + ' is imported from a data file!') event.Skip() def UpdateInputTable(self): '''Fill the table with input data''' for i in range(self.maxNumOfLabels): self.table.SetCellValue(i + 2, 0, self.labelNames[i]) if not (self.labelCoordMean[i][0] == None): self.table.SetCellValue(i + 2, 1, '{0:.2f}'.format(self.labelCoordMean[i][0])) else: self.table.SetCellValue(i + 2, 1, '') if not (self.labelCoordMean[i][1] == None): self.table.SetCellValue(i + 2, 2, '{0:.2f}'.format(self.labelCoordMean[i][1])) else: self.table.SetCellValue(i + 2, 2, '') if not (self.labelCoordMean[i][2] == None): self.table.SetCellValue(i + 2, 3, '{0:.2f}'.format(self.labelCoordMean[i][2])) else: self.table.SetCellValue(i + 2, 3, '') if not (self.distMean[i] == None): self.table.SetCellValue(i + 2, 4, '{0:.2f}'.format(self.distMean[i])) else: self.table.SetCellValue(i + 2, 4, '') if not (self.distStd[i] == None): self.table.SetCellValue(i + 2, 5, '{0:.2f}'.format(self.distStd[i])) else: self.table.SetCellValue(i + 2, 5, '') # Highlight complete sets of data if not ((self.labelCoordMean[i][0] == None) | (self.labelCoordMean[i][1] == None) | (self.labelCoordMean[i][2] == None) | (self.distMean[i] == None) | (self.distStd[i] == None)): for j in range(1, 6): self.table.SetCellBackgroundColour(i + 2, j, wx.Colour(127, 255, 0)) else: for j in range(1, 6): self.table.SetCellBackgroundColour(i + 2, j, wx.WHITE) def OnAcceptInput(self, event): '''Appropriation of the entered input data''' # Number of defined labels N = 0 # Rearrange input data arrays & calculate number of defined labels for i in range(self.maxNumOfLabels): if not ((self.labelCoordMean[i][0] == None) | (self.labelCoordMean[i][1] == None) | (self.labelCoordMean[i][2] == None) | (self.distMean[i] == None) | (self.distStd[i] == None)): # Rearrange input data arrays tempLabelName = self.labelNames[N] tempLabelCoordMean = self.labelCoordMean[N] tempLabelCoordStd = self.labelCoordMean[N] tempDistMean = self.distMean[N] tempDistStd = self.distStd[N] self.labelNames[N] = self.labelNames[i] self.labelCoordMean[N] = self.labelCoordMean[i] self.labelCoordStd[N] = self.labelCoordStd[i] self.distMean[N] = self.distMean[i] self.distStd[N] = self.distStd[i] self.labelNames[i] = tempLabelName self.labelCoordMean[i] = tempLabelCoordMean self.labelCoordStd[i] = tempLabelCoordStd self.distMean[i] = tempDistMean self.distStd[i] = tempDistStd N += 1 # Update table self.UpdateInputTable() # Check in the number of defined labels if (N >= 4): # Input data does accepted self.inputAccepted = True self.numOfLabels = N # Statusbar message self.statusbar.SetStatusText('Input data is prepared!') else: # Input data doesn't accepted self.inputAccepted = False self.numOfLabels = N # Statusbar message self.statusbar.SetStatusText('The number of defined labels should exceed 3!') event.Skip() def OnClearInput(self, event): '''Clear all input data''' # Clear input arrays self.labelNames = [None for col in range(self.maxNumOfLabels)] for index in range(self.maxNumOfLabels): self.labelNames[index] = "Label_" + str(index + 1) self.labelCoordMean = [[None for col in range(3)] for row in range(self.maxNumOfLabels)] self.labelCoordStd = [[None for col in range(3)] for row in range(self.maxNumOfLabels)] self.distMean = [None for col in range(self.maxNumOfLabels)] self.distStd = [None for col in range(self.maxNumOfLabels)] # Update table self.UpdateInputTable() # Input data doesn't accepted self.inputAccepted = False event.Skip() def OnCompute(self, event): '''Compute the target coordinates''' if (self.inputAccepted == False): # Statusbar message self.statusbar.SetStatusText('Correct input data is required!') else: # Linear least squares, Singular value decomposition self.targetCoordMean, self.targetCoordStd, self.chiSquare = SingularValueDecomposition(self.numOfLabels, self.labelCoordMean, self.distMean, self.distStd) self.numOfIter = None # Statusbar message self.statusbar.SetStatusText('Target coordinates are calculated!') # Fill in the otput fields self.table1.SetCellValue(2, 1, '{0:.1f}'.format(self.targetCoordMean[0][0])) self.table1.SetCellValue(2, 2, '{0:.1f}'.format(self.targetCoordMean[0][1])) self.table1.SetCellValue(2, 3, '{0:.1f}'.format(self.targetCoordMean[0][2])) self.table1.SetCellValue(3, 1, '{0:.1f}'.format(self.targetCoordStd[0][0])) self.table1.SetCellValue(3, 2, '{0:.1f}'.format(self.targetCoordStd[0][1])) self.table1.SetCellValue(3, 3, '{0:.1f}'.format(self.targetCoordStd[0][2])) self.chiSquareBox.SetValue('{0:.3f}'.format(self.chiSquare)) self.numOfIterBox.SetValue('1') if self.calcMode == 0: self.targetCoordMean, self.targetCoordStd, self.chiSquare, self.numOfIter = InverseHessian(self.numOfLabels, self.labelCoordMean, self.distMean, self.distStd, self.targetCoordMean, self.minChiSquare, self.maxNumOfIter) elif self.calcMode == 1: self.targetCoordMean, self.targetCoordStd, self.chiSquare, self.numOfIter = LevenbergMarquardt(self.numOfLabels, self.labelCoordMean, self.distMean, self.distStd, self.targetCoordMean, self.minChiSquare, self.maxNumOfIter, self.lambdaFirst, self.lambdaStep) if self.numOfIter == self.maxNumOfIter: self.statusbar.SetStatusText('Cannot find the solution for the given set of input data! (Try to increase number of iterations.)') else: self.statusbar.SetStatusText('Target coordinates are calculated!') # Fill in output fields self.table1.SetCellValue(2, 1, '{0:.1f}'.format(self.targetCoordMean[0][0])) self.table1.SetCellValue(2, 2, '{0:.1f}'.format(self.targetCoordMean[0][1])) self.table1.SetCellValue(2, 3, '{0:.1f}'.format(self.targetCoordMean[0][2])) self.table1.SetCellValue(3, 1, '{0:.1f}'.format(self.targetCoordStd[0][0])) self.table1.SetCellValue(3, 2, '{0:.1f}'.format(self.targetCoordStd[0][1])) self.table1.SetCellValue(3, 3, '{0:.1f}'.format(self.targetCoordStd[0][2])) self.chiSquareBox.SetValue('{0:.3f}'.format(self.chiSquare)) self.numOfIterBox.SetValue('{0:d}'.format(self.numOfIter)) event.Skip() def OnClearOutput(self, event): '''Clear all output data ''' self.targetCoordMean = [[None for col in range(3)]] self.targetCoordStd = [[None for col in range(3)]] self.chiSquare = None self.numOfIter = None self.table1.SetCellValue(2, 1, '') self.table1.SetCellValue(2, 2, '') self.table1.SetCellValue(2, 3, '') self.table1.SetCellValue(3, 1, '') self.table1.SetCellValue(3, 2, '') self.table1.SetCellValue(3, 3, '') self.chiSquareBox.SetValue('') self.numOfIterBox.SetValue('') # Remove figures from PyMOL try: cmd.delete('trilateration') cmd.delete('target') except: pass event.Skip() def OnImportCoordFromPymol(self, event): '''Import the list of spin labels from the running PyMOL session''' self.labelList.Enable() self.loadFromPymol.Enable() # Load a dictionary with labels and their coordinates from PyMOL items = cmd.get_object_list("*_label") # Write names of labels to the combobox list self.labelList.Clear() for key in sorted(items): self.labelList.Append(key) # Statusbar message pub.sendMessage('statusbar.update', status='Choose the label and push the Load button...') event.Skip() def OnLoadFromPymol(self, event): '''Import the names and the coordinates of the spin labels from PyMOL''' # Read the name of the label s = self.labelList.GetStringSelection() # Determine the coordinates of epr-active center of the spin label coord = [] cmd.iterate_state(0, s, 'coord.append([x,y,z])', space=locals(), atomic=0) # Set the name of spin label and its average coordinates self.labelNames[self.curLabel] = s self.labelCoordMean[self.curLabel] = coord[0] self.labelCoordStd[self.curLabel] = [0.0, 0.0, 0.0] # Update table self.UpdateInputTable() # Statusbar message self.statusbar.SetStatusText('The coordinates of the label ' + s + ' are imported from PyMOL!') # Input data doesn't accepted self.inputAccepted = False event.Skip() def OnExportToPymol(self, event): '''Create a plot in PyMOL''' if (self.inputAccepted == False): # Plot spheres PlotSpheres(self.numOfLabels, self.labelCoordMean, self.distMean, self.spheresColor, self.spheresTransparency) else: # Plot spheres PlotSpheres(self.numOfLabels, self.labelCoordMean, self.distMean, self.spheresColor, self.spheresTransparency) # Plot ellipsoid try: PlotEllipsoid(self.targetCoordMean[0][0], self.targetCoordMean[0][1], self.targetCoordMean[0][2], self.targetCoordStd[0][0] * self.confidenceLevel, self.targetCoordStd[0][1] * self.confidenceLevel, self.targetCoordStd[0][2] * self.confidenceLevel, self.ellipsoidColor, self.ellipsoidTransparency) except: pass event.Skip() def OnSave(self, event): '''Save a current session of the program''' if (self.inputAccepted == False): self.statusbar.SetStatusText('Data set is not complete!') else: dialog = wx.FileDialog(None, message="Save file as ...", defaultDir=os.getcwd(), defaultFile="", wildcard="*.dat", style=wx.SAVE | wx.OVERWRITE_PROMPT) if dialog.ShowModal() == wx.ID_OK: path = dialog.GetPath() # Content of a data file title = "*** Trilateration program data file ***" object = "* Protein name:" numOfLabels = "* Number of labels:" labels = '''* Label parameters: col 1: Name col 2,3,4: (x,y,z) coordinates col 5: Label-target distance col 6: Standard deviation of label-target distance''' settings = '''* Calculation settings: line 1: Inverse-Hessian method vs Levenberg-Marquardt method, 1 = ON and 0 = OFF line 2: Maximal number of iterations line 3: Minimal chi-square value line 4: First lambda value line 5: Lambda increment''' target = '''* Target parameters: line 1: Name line 2: (x,y,z) coordinates line 3: Standard deviations of (x,y,z) coordinates line 4: Chi-square value line 5: Number of iterations''' # Write data to file dataFile = open(path, 'w') dataFile.write(title + '\n\n') dataFile.write(object + '\n') dataFile.write('name' + '\n') dataFile.write(numOfLabels + '\n') dataFile.write('{0:d}'.format(self.numOfLabels) + '\n') dataFile.write(labels + '\n') for i in range(self.numOfLabels): dataFile.write('{0:16s}'.format(self.labelNames[i]) + ' ') dataFile.write('{0:8.2f}'.format(self.labelCoordMean[i][0]) + ' ') dataFile.write('{0:8.2f}'.format(self.labelCoordMean[i][1]) + ' ') dataFile.write('{0:8.2f}'.format(self.labelCoordMean[i][2]) + ' ') dataFile.write('{0:8.2f}'.format(self.distMean[i]) + ' ') dataFile.write('{0:8.2f}'.format(self.distStd[i]) + '\n') dataFile.write(settings + '\n') if (self.calcMode == 0): dataFile.write('1 0' + '\n') else: dataFile.write('0 1' + '\n') dataFile.write(str(self.maxNumOfIter) + '\n') dataFile.write(str(self.minChiSquare) + '\n') dataFile.write(str(self.lambdaFirst) + '\n') dataFile.write(str(self.lambdaStep) + '\n') dataFile.write(target + '\n') dataFile.write('name' + '\n') dataFile.write('{0:8.2f}'.format(self.targetCoordMean[0][0]) + ' ') dataFile.write('{0:8.2f}'.format(self.targetCoordMean[0][1]) + ' ') dataFile.write('{0:8.2f}'.format(self.targetCoordMean[0][2]) + '\n') dataFile.write('{0:8.2f}'.format(self.targetCoordStd[0][0]) + ' ') dataFile.write('{0:8.2f}'.format(self.targetCoordStd[0][1]) + ' ') dataFile.write('{0:8.2f}'.format(self.targetCoordStd[0][2]) + '\n') dataFile.write('{0:.3f}'.format(self.chiSquare) + '\n') dataFile.write('{0:d}'.format(self.numOfIter)) dataFile.close() # Statusbar message self.statusbar.SetStatusText('The current session is saved!') dialog.Destroy() def OnOpen(self, event): '''Open the earlier saved session''' dlg = wx.FileDialog(self, message="Choose a file", defaultDir=os.getcwd(), defaultFile="", wildcard="*.dat", style=wx.OPEN | wx.CHANGE_DIR) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() dataFile = open(path, 'r') len = 8 # lenght of numerical symbols in the file for (i, line) in enumerate(dataFile): if (i == 5): self.numOfLabels = int(line) for j in range(self.numOfLabels): if (i == (11 + j)): self.labelNames[j] = line[0:2 * len].replace(' ', '') self.labelCoordMean[j][0] = float(line[2 * len + 1:3 * len + 1].replace(' ', '')) self.labelCoordMean[j][1] = float(line[3 * len + 2:4 * len + 2].replace(' ', '')) self.labelCoordMean[j][2] = float(line[4 * len + 3:5 * len + 3].replace(' ', '')) self.distMean[j] = float(line[5 * len + 4:6 * len + 4].replace(' ', '')) self.distStd[j] = float(line[6 * len + 5:7 * len + 5].replace(' ', '')) if (i == (17 + self.numOfLabels)): if (line[0] == '1'): self.calcMode = 0 else: self.calcMode = 1 if (i == (18 + self.numOfLabels)): self.maxNumOfIter = int(line.replace(' ', '')) if (i == (19 + self.numOfLabels)): self.minChiSquare = float(line.replace(' ', '')) if (i == (20 + self.numOfLabels)): self.lambdaFirst = float(line.replace(' ', '')) if (i == (21 + self.numOfLabels)): self.lambdaStep = float(line.replace(' ', '')) if (i == (29 + self.numOfLabels)): self.targetCoordMean[0][0] = float(line[0:len].replace(' ', '')) self.targetCoordMean[0][1] = float(line[len + 1:2 * len + 1].replace(' ', '')) self.targetCoordMean[0][2] = float(line[2 * len + 2:3 * len + 2].replace(' ', '')) self.table1.SetCellValue(2, 1, '{0:.1f}'.format(self.targetCoordMean[0][0])) self.table1.SetCellValue(2, 2, '{0:.1f}'.format(self.targetCoordMean[0][1])) self.table1.SetCellValue(2, 3, '{0:.1f}'.format(self.targetCoordMean[0][2])) if (i == (30 + self.numOfLabels)): self.targetCoordStd[0][0] = float(line[0:len].replace(' ', '')) self.targetCoordStd[0][1] = float(line[len + 1:2 * len + 1].replace(' ', '')) self.targetCoordStd[0][2] = float(line[2 * len + 2:3 * len + 2].replace(' ', '')) self.table1.SetCellValue(3, 1, '{0:.1f}'.format(self.targetCoordStd[0][0])) self.table1.SetCellValue(3, 2, '{0:.1f}'.format(self.targetCoordStd[0][1])) self.table1.SetCellValue(3, 3, '{0:.1f}'.format(self.targetCoordStd[0][2])) if (i == (31 + self.numOfLabels)): self.chiSquare = float(line.replace(' ', '')) self.chiSquareBox.SetValue(line.replace(' ', '')) if (i == (32 + self.numOfLabels)): self.numOfIter = float(line.replace(' ', '')) self.numOfIterBox.SetValue(line.replace(' ', '')) # Update table self.UpdateInputTable() self.inputAccepted = True # Statusbar message self.statusbar.SetStatusText('The selected session is started!') # Remove figures from PyMOL try: cmd.delete('trilateration') cmd.delete('target') except: pass else: # Statusbar message self.statusbar.SetStatusText('Nothing was selected!') dlg.Destroy() def OnClose(self, event): '''Close the current session''' # Clear input arrays self.labelNames = [None for col in range(self.maxNumOfLabels)] for index in range(self.maxNumOfLabels): self.labelNames[index] = "Label_" + str(index + 1) self.labelCoordMean = [[None for col in range(3)] for row in range(self.maxNumOfLabels)] self.labelCoordStd = [[None for col in range(3)] for row in range(self.maxNumOfLabels)] self.distMean = [None for col in range(self.maxNumOfLabels)] self.distStd = [None for col in range(self.maxNumOfLabels)] # Update table self.UpdateInputTable() # Input data doesn't accepted self.inputAccepted = False # Clear output arrays self.targetCoordMean = [[None for col in range(3)]] self.targetCoordStd = [[None for col in range(3)]] self.chiSquare = None self.numOfIter = None # Clear output text fields self.table1.SetCellValue(2, 1, '') self.table1.SetCellValue(2, 2, '') self.table1.SetCellValue(2, 3, '') self.table1.SetCellValue(3, 1, '') self.table1.SetCellValue(3, 2, '') self.table1.SetCellValue(3, 3, '') self.chiSquareBox.SetValue('') self.numOfIterBox.SetValue('') # Remove figures from PyMOL try: cmd.delete('trilateration') cmd.delete('target') except: pass event.Skip() def OnOpenPref(self, event): '''Open the preferences menu''' prefFrame = Preferences(self, self.coordFactor, self.distFactor, self.calcMode, self.maxNumOfIter, self.minChiSquare, self.lambdaFirst, self.lambdaStep, self.spheresColor, self.spheresTransparency, self.ellipsoidColor, self.ellipsoidTransparency, self.confidenceLevel) prefFrame.Show() def OnExit(self, event): '''Close the program''' self.Close() def OnLoad(self, event): '''Load a new structure into PyMOL''' dialog = wx.FileDialog(None, message='Choise the PyMOL file', style=wx.OPEN) if dialog.ShowModal() == wx.ID_OK: pdbfile = dialog.GetPath() cmd.load(pdbfile) # make PyMOL view nicer cmd.remove("solvent") cmd.show("cartoon") # Statusbar message self.statusbar.SetStatusText('A new structure is loaded into PyMOL!') else: # Statusbar message self.statusbar.SetStatusText('Nothing was selected!') dialog.Destroy() def OnRemove(self, event): '''Remove a current structure from PyMOL''' cmd.delete("all") # Statusbar message self.statusbar.SetStatusText('The previous structure is removed from PyMOL!') def OnRunMtsslWizard(self, event): '''Run MtsslWizard''' try: from .mtsslWizard import MtsslWizard except ImportError: try: from mtsslWizard import MtsslWizard except ImportError: print('could not import mtsslWizard') return try: wiz = MtsslWizard() cmd.set_wizard(wiz) except: pass def OnAbout(self, event): '''Show the program's info''' info = wx.AboutDialogInfo() info.SetName('mtsslTrilaterate') info.SetVersion('1.2') description = """ mtsslTrilaterate is a PyMOL plugin which provides a set of mathematical tools to trilaterate paramagnetic spin centers in biomolecules using spin labels and ESR distance constraints.""" info.SetDescription(description) info.AddDeveloper("Dinar Abdullin, abdullin@pc.uni-bonn.de") info.SetCopyright('(C) 2012-2016') licence = """This program can be distributed under GNU General Public License.""" info.SetLicence(licence) wx.AboutBox(info) class ListCtrlLeft(wx.ListCtrl): '''The content of the preferences menu''' def __init__(self, parent, id): wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_NO_HEADER | wx.LC_SINGLE_SEL) self.parent = parent self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelect) self.InsertColumn(0, '') titles = ['Otput data', 'Calculations', 'Input data'] for i in titles: self.InsertStringItem(0, i) def OnSize(self, event): size = self.parent.GetSize() self.SetColumnWidth(0, size.x - 5) event.Skip() def OnSelect(self, event): window = self.parent.GetGrandParent().FindWindowByName('ListControlOnRight') index = event.GetIndex() window.LoadData(index) class ListCtrlRight(wx.grid.Grid): '''The parameters of the preferences menu''' def __init__(self, parent, id, coordFactor, distFactor, calcMode, maxNumOfIter, minChiSquare, lambdaFirst, lambdaStep, spheresColor, spheresTransparency, ellipsoidColor, ellipsoidTransparency, confidenceLevel): wx.grid.Grid.__init__(self, parent, id) # Create hollow grid self.CreateGrid(0, 2) self.SetRowLabelSize(0) self.SetColLabelSize(0) self.parent = parent size = self.parent.GetSize() self.SetColSize(0, size.x - 100) self.SetColSize(1, 95) # self.EnableGridLines(False) self.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnTableCellChange) # Initialize parameters self.OnParamInit(coordFactor, distFactor, calcMode, maxNumOfIter, minChiSquare, lambdaFirst, lambdaStep, spheresColor, spheresTransparency, ellipsoidColor, ellipsoidTransparency, confidenceLevel) def OnParamInit(self, coordFactor, distFactor, calcMode, maxNumOfIter, minChiSquare, lambdaFirst, lambdaStep, spheresColor, spheresTransparency, ellipsoidColor, ellipsoidTransparency, confidenceLevel): colorLine1 = '' colorLine2 = '' for i in range(3): colorLine1 += str(int(spheresColor[i] * 255.0)) + ' ' colorLine2 += str(int(ellipsoidColor[i] * 255.0)) + ' ' self.param = { 'input': [['Multiply coordinates loaded from data files by factor of', coordFactor], ['Multiply distances loaded from data files by factor of', distFactor]], 'calc': [['Inverse-Hessian method (0) vs Lavenberg-Marquardt method (1)', calcMode], ['Maximal number of iterations', maxNumOfIter], ['Minimal chi-square value', minChiSquare], ['Initial lambda (for Lavenberg-Marquardt method only)', lambdaFirst], ['Lambda increment (for Lavenberg-Marquardt method only)', lambdaStep]], 'output': [['Color of object (RGB)', colorLine1], ['Transparency of object', spheresTransparency], ['Color of object (RGB)', colorLine2], ['Transparency of object', ellipsoidTransparency], ['Confidence level in sigma', confidenceLevel]] } def LoadData(self, index): # Clear last grid self.ClearGrid() oldRows = self.GetNumberRows() if (oldRows > 0): self.DeleteRows(0, oldRows, True) # Select type of parameters self.index = index if self.index == 0: data = self.param['input'] elif self.index == 1: data = self.param['calc'] elif self.index == 2: data = self.param['output'] # Create a new grid newRows = len(data) self.AppendRows(newRows, True) self.SetColLabelValue(0, "Name") self.SetColLabelValue(1, "Value") self.SetLabelFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL)) self.SetRowLabelSize(0) self.SetColLabelSize(20) size = self.parent.GetSize() self.SetColSize(0, size.x - 105) self.SetColSize(1, 100) # Fill in a new grid for i in range(len(data)): self.SetCellValue(i, 0, data[i][0]) self.SetCellValue(i, 1, str(data[i][1])) self.SetReadOnly(i, 0, isReadOnly=True) def OnTableCellChange(self, event): row = event.GetRow() if self.index == 0: if row == 0: self.param['input'][0][1] = float(self.GetCellValue(row, 1)) elif row == 1: self.param['input'][1][1] = float(self.GetCellValue(row, 1)) if self.index == 1: if row == 0: self.param['calc'][0][1] = int(self.GetCellValue(row, 1)) elif row == 1: self.param['calc'][1][1] = int(self.GetCellValue(row, 1)) elif row == 2: self.param['calc'][2][1] = float(self.GetCellValue(row, 1)) elif row == 3: self.param['calc'][3][1] = float(self.GetCellValue(row, 1)) elif row == 4: self.param['calc'][4][1] = float(self.GetCellValue(row, 1)) if self.index == 2: if row == 0: self.param['output'][0][1] = self.GetCellValue(row, 1) elif row == 1: self.param['output'][1][1] = float(self.GetCellValue(row, 1)) elif row == 2: self.param['output'][2][1] = self.GetCellValue(row, 1) elif row == 3: self.param['output'][3][1] = float(self.GetCellValue(row, 1)) elif row == 4: self.param['output'][4][1] = int(self.GetCellValue(row, 1)) event.Skip() class Preferences(wx.Frame): '''GUI of the preferences menu''' def __init__(self, parent, coordFactor, distFactor, calcMode, maxNumOfIter, minChiSquare, lambdaFirst, lambdaStep, spheresColor, spheresTransparency, ellipsoidColor, ellipsoidTransparency, confidenceLevel): wx.Frame.__init__(self, parent, -1, 'Preferences', size=(600, 400)) panelcolour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) self.SetBackgroundColour(panelcolour) mbox = wx.BoxSizer(wx.VERTICAL) self.SetSizer(mbox) splitter = wx.SplitterWindow(self, -1, style=wx.SP_LIVE_UPDATE | wx.SP_NOBORDER) mbox.Add(splitter, 1, wx.EXPAND) accept = wx.Button(self, label='OK') mbox.Add(accept, 0, flag=wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, border=5) accept.Bind(wx.EVT_BUTTON, self.OnReloadParam) panel1 = wx.Panel(splitter, -1) vbox1 = wx.BoxSizer(wx.VERTICAL) panel1.SetSizer(vbox1) panel11 = wx.Panel(panel1, -1, size=(-1, 40), style=wx.NO_BORDER) panel11.SetBackgroundColour('#53728c') st1 = wx.StaticText(panel11, -1, 'Bloks', (5, 5)) st1.SetForegroundColour('WHITE') panel12 = wx.Panel(panel1, -1, style=wx.BORDER_SUNKEN) panel12.SetBackgroundColour('WHITE') vbox11 = wx.BoxSizer(wx.VERTICAL) panel12.SetSizer(vbox11) list1 = ListCtrlLeft(panel12, -1) vbox11.Add(list1, 1, wx.EXPAND) vbox1.Add(panel11, 0, wx.EXPAND) vbox1.Add(panel12, 1, wx.EXPAND) panel2 = wx.Panel(splitter, -1) vbox2 = wx.BoxSizer(wx.VERTICAL) panel2.SetSizer(vbox2) panel21 = wx.Panel(panel2, -1, size=(-1, 40), style=wx.NO_BORDER) panel21.SetBackgroundColour('#53728c') st2 = wx.StaticText(panel21, -1, 'Parameters', (5, 5)) st2.SetForegroundColour('WHITE') panel22 = wx.Panel(panel2, -1, style=wx.BORDER_SUNKEN) vbox22 = wx.BoxSizer(wx.VERTICAL) panel22.SetSizer(vbox22) self.list2 = ListCtrlRight(panel22, -1, coordFactor, distFactor, calcMode, maxNumOfIter, minChiSquare, lambdaFirst, lambdaStep, spheresColor, spheresTransparency, ellipsoidColor, ellipsoidTransparency, confidenceLevel) self.list2.SetName('ListControlOnRight') vbox22.Add(self.list2, 1, wx.EXPAND) vbox2.Add(panel21, 0, wx.EXPAND) vbox2.Add(panel22, 1, wx.EXPAND) splitter.SplitVertically(panel1, panel2, 150) self.Centre() self.Show(True) def OnReloadParam(self, event): calcMode = self.list2.param['calc'][0][1] maxNumOfIter = self.list2.param['calc'][1][1] minChiSquare = self.list2.param['calc'][2][1] lambdaFirst = self.list2.param['calc'][3][1] lambdaStep = self.list2.param['calc'][4][1] coordFactor = self.list2.param['input'][0][1] distFactor = self.list2.param['input'][1][1] spheresColor = [] line = self.list2.param['output'][0][1].split() for i in range(3): spheresColor += [float(line[i]) / 255.0] spheresTransparency = self.list2.param['output'][1][1] ellipsoidColor = [] line = self.list2.param['output'][2][1].split() for i in range(3): ellipsoidColor += [float(line[i]) / 255.0] ellipsoidTransparency = self.list2.param['output'][3][1] confidenceLevel = self.list2.param['output'][4][1] msg = [calcMode, maxNumOfIter, minChiSquare, lambdaFirst, lambdaStep, coordFactor, distFactor, spheresColor, spheresTransparency, ellipsoidColor, ellipsoidTransparency, confidenceLevel] pub.sendMessage('import.param', data = msg) self.Close() class PlotSpheres: '''Plots spheres in PyMOL''' def __init__(self, num, centers, radii, color, transparency): obj = [] cmd.set("cgo_sphere_quality", 4) for i in range(num): r, g, b = color obj.extend([ALPHA, 1 - transparency]) obj.extend([COLOR, r, g, b]) obj.extend([SPHERE, float(centers[i][0]), float(centers[i][1]), float(centers[i][2]), float(radii[i])]) cmd.load_cgo(obj, 'trilateration') class PlotEllipsoid: '''Plots an ellipsoid in PyMOL''' def __init__(self, x, y, z, rx, ry, rz, color, transparency): cmd.load_cgo(self.makeEllipsoid(x, y, z, rx, ry, rz, color, transparency), 'target') def makeEllipsoid(self, x, y, z, a1, a2, a3, color, transparency): return self.makeSuperQuadricEllipsoid(x, y, z, a1, a2, a3, 1.0, 1.0, -pi / 2, pi / 2, -pi, pi, 50, 50, color, transparency) def makeSuperQuadricEllipsoid(self, x, y, z, a1, a2, a3, n, event, u1, u2, v1, v2, u_segs, v_segs, color, transparency): r, g, b = color dU = (u2 - u1) / u_segs dV = (v2 - v1) / v_segs obj = [BEGIN, TRIANGLES] U = u1 for Y in range(0, u_segs): V = v1 for X in range(0, v_segs): x1, y1, z1, n1x, n1y, n1z = self.sqEllipsoid(x, y, z, a1, a2, a3, U, V, n, event) x2, y2, z2, n2x, n2y, n2z = self.sqEllipsoid(x, y, z, a1, a2, a3, U + dU, V, n, event) x3, y3, z3, n3x, n3y, n3z = self.sqEllipsoid(x, y, z, a1, a2, a3, U + dU, V + dV, n, event) x4, y4, z4, n4x, n4y, n4z = self.sqEllipsoid(x, y, z, a1, a2, a3, U, V + dV, n, event) obj.extend([COLOR, r, g, b, ALPHA, 1 - transparency, NORMAL, n1x, n1y, n1z, VERTEX, x1, y1, z1]) obj.extend([COLOR, r, g, b, ALPHA, 1 - transparency, NORMAL, n2x, n2y, n2z, VERTEX, x2, y2, z2]) obj.extend([COLOR, r, g, b, ALPHA, 1 - transparency, NORMAL, n4x, n4y, n4z, VERTEX, x4, y4, z4]) obj.extend([COLOR, r, g, b, ALPHA, 1 - transparency, NORMAL, n2x, n2y, n2z, VERTEX, x2, y2, z2]) obj.extend([COLOR, r, g, b, ALPHA, 1 - transparency, NORMAL, n3x, n3y, n3z, VERTEX, x3, y3, z3]) obj.extend([COLOR, r, g, b, ALPHA, 1 - transparency, NORMAL, n4x, n4y, n4z, VERTEX, x4, y4, z4]) V += dV U += dU obj.append(END) return obj def sqEllipsoid(self, x, y, z, a1, a2, a3, u, v, n, event): x = a1 * self.sqC(u, n) * self.sqC(v, event) + x y = a2 * self.sqC(u, n) * self.sqS(v, event) + y z = a3 * self.sqS(u, n) + z nx = self.sqC(u, 2 - n) * self.sqC(v, 2 - event) / a1 ny = self.sqC(u, 2 - n) * self.sqS(v, 2 - event) / a2 nz = self.sqS(u, 2 - n) / a3 return x, y, z, nx, ny, nz def signOfFloat(self, f): if f < 0: return -1 if f > 0: return 1 return 0 def sqC(self, v, n): return self.signOfFloat(cos(v)) * pow(fabs(cos(v)), n) def sqS(self, v, n): return self.signOfFloat(sin(v)) * pow(fabs(sin(v)), n) ######################## # ADDITIONAL FUNCTIONS # ######################## def AverageCoordCalc(coord): '''Calculates the average coordinates and the standard deviations for a set of coordinates''' # Average coordinates coordMean = numpy.mean(coord, axis=0) # Standard deviations coordStd = numpy.std(coord, axis=0) return coordMean, coordStd def AverageDistCalc(dist, density): '''Calculates the average distance and the standard deviation for a distance distribution''' # Average distance distAve = dist[numpy.argmax(density)] # Standard deviation numerator = 0 demominator = 0 for i in range(len(dist)): numerator += density[i] * (dist[i] - distAve) ** 2 demominator += density[i] distStd = sqrt(numerator / demominator) return distAve, distStd def SquaredRadiusVector(vector1, vector2): '''Calculate a square of a radius-vector''' srv = 0 for i in range(3): srv += (vector1[i] - vector2[i]) ** 2 return srv def ChiSquareCalc(numOfLabels, labelCoord, targetCoordMean, distMean, distStd): '''Calculate Chi-Squared''' chiSquare = 0 for i in range(numOfLabels): f = sqrt(SquaredRadiusVector(targetCoordMean[0], labelCoord[i])) - distMean[i] chiSquare += (1 / distStd[i] ** 2) * f ** 2 return chiSquare def SingularValueDecomposition(numOfLabels, labelCoord, distMean, distStd): '''Optimization via Singular Value Decomposition algorithm''' # Compute A and b coefficients A = zeros((numOfLabels - 1, 3)) b = zeros((numOfLabels - 1, 1)) for i in range(1, numOfLabels): for j in range(3): A[i - 1][j] = (labelCoord[i][j] - labelCoord[0][j]) / distStd[i] b[i - 1] = 0.5 * (distMean[0] ** 2 - distMean[i] ** 2 + SquaredRadiusVector(labelCoord[i], labelCoord[0])) / distStd[i] # SVD decomposition U, S, Vh = linalg.svd(A, full_matrices=0) D = linalg.inv(diag(S)) # Compute the target coordinates and their standard deviations targetCoordMean = zeros((1, 3)) targetCoordMean = dot(dot(dot(Vh.T, D), U.T), b).T for j in range(3): targetCoordMean[0][j] += labelCoord[0][j] targetCoordStd = zeros((1, 3)) for j in range(3): for i in range(size(A, axis=1)): targetCoordStd[0][j] += (Vh.T[j][i] / S[i]) ** 2 targetCoordStd[0][j] = sqrt(targetCoordStd[0][j]) # Compute the Chi-Square value chiSquare = ChiSquareCalc(numOfLabels, labelCoord, targetCoordMean, distMean, distStd) return targetCoordMean, targetCoordStd, chiSquare def InverseHessian(numOfLabels, labelCoord, distMean, distStd, targetCoordInitial, chiSquareMin, iterMax): '''Optimization via Inverse-Hessian algorithm''' # Use previously determined values of target coordinates as initial data targetCoordMean = targetCoordInitial # Calculate current Chi-Square value chiSquare = ChiSquareCalc(numOfLabels, labelCoord, targetCoordMean, distMean, distStd) # Start iterations iter = 0 while (chiSquare > chiSquareMin) and (iter < iterMax): # Number of iteration iter += 1 # Save previous Chi-Square value chiSquare_temp = chiSquare # Calculate the first order and the second order (main part of it) derivatives d1f = zeros((3, 1)) d2f = zeros((3, 3)) for i in range(numOfLabels): f = sqrt(SquaredRadiusVector(targetCoordMean[0], labelCoord[i])) - distMean[i] for j in range(3): d1f[j] += (2 / distStd[i] ** 2) * (targetCoordMean[0][j] - labelCoord[i][j]) * f / (f + distMean[i]) for k in range(3): d2f[j][k] += (2 / distStd[i] ** 2) * (targetCoordMean[0][j] - labelCoord[i][j]) * (targetCoordMean[0][k] - labelCoord[i][k]) / (f + distMean[i]) ** 2 # Calculate the target coordinates targetCoordMean -= dot(inv(d2f), d1f).T # Calculate new Chi-Square value chiSquare = ChiSquareCalc(numOfLabels, labelCoord, targetCoordMean, distMean, distStd) # Check in changes in Chi-Square value if (chiSquare < chiSquare_temp) and ((chiSquare_temp - chiSquare) / chiSquare_temp < 0.001): # HIDDEN PARAMETER!!! break # Calculate the standard deviation of target coordinates targetCoordStd = zeros((1, 3)) d2f = zeros((3, 3)) for i in range(numOfLabels): f = sqrt(SquaredRadiusVector(targetCoordMean[0], labelCoord[i])) - distMean[i] for j in range(3): for k in range(3): d2f[j][k] += (2 / distStd[i] ** 2) * (targetCoordMean[0][j] - labelCoord[i][j]) * (targetCoordMean[0][k] - labelCoord[i][k]) / (f + distMean[i]) ** 2 correlationMatrix = inv(d2f) for j in range(3): targetCoordStd[0][j] = sqrt(abs(correlationMatrix[j][j])) return targetCoordMean, targetCoordStd, chiSquare, iter def LevenbergMarquardt(numOfLabels, labelCoord, distMean, distStd, targetCoordInitial, chiSquareMin, iterMax, lambdaFirst, lambdaStep): '''Optimization via Levenberg-Marquardt algorithm''' # Use previously determined values of target coordinates as initial data targetCoordMean = targetCoordInitial # Calculate current Chi-Square value chiSquare = ChiSquareCalc(numOfLabels, labelCoord, targetCoordMean, distMean, distStd) # Start iterations lambdaCurrent = lambdaFirst iter = 0 while (chiSquare > chiSquareMin) and (iter < iterMax): # Number of iteration iter += 1 # Save previous Chi-Square value and target coordinates chiSquare_temp = chiSquare targetCoord_temp = targetCoordMean # Calculate the first order and the second order (main part of it) derivatives d1f = zeros((3, 1)) d2f = zeros((3, 3)) for i in range(numOfLabels): f = sqrt(SquaredRadiusVector(targetCoordMean[0], labelCoord[i])) - distMean[i] for j in range(3): d1f[j] += (2 / distStd[i] ** 2) * (targetCoordMean[0][j] - labelCoord[i][j]) * f / (f + distMean[i]) for k in range(3): d2f[j][k] += (2 / distStd[i] ** 2) * (targetCoordMean[0][j] - labelCoord[i][j]) * (targetCoordMean[0][k] - labelCoord[i][k]) / (f + distMean[i]) ** 2 # Modify the second order derivatives matrix for j in range(3): d2f[j][j] *= (1 + lambdaCurrent) # Calculate the target coordinates targetCoordMean -= dot(inv(d2f), d1f).T # Calculate new Chi-Square value chiSquare = ChiSquareCalc(numOfLabels, labelCoord, targetCoordMean, distMean, distStd) # Check in changes in Chi-Square value if (chiSquare < chiSquare_temp): if ((chiSquare_temp - chiSquare) / chiSquare_temp < 0.001): # HIDDEN PARAMETER!!! break lambdaCurrent /= lambdaStep else: lambdaCurrent *= lambdaStep targetCoordMean = targetCoord_temp # Calculate the standard deviation of target coordinates targetCoordStd = zeros((1, 3)) d2f = zeros((3, 3)) for i in range(numOfLabels): f = sqrt(SquaredRadiusVector(targetCoordMean[0], labelCoord[i])) - distMean[i] for j in range(3): for k in range(3): d2f[j][k] += (2 / distStd[i] ** 2) * (targetCoordMean[0][j] - labelCoord[i][j]) * (targetCoordMean[0][k] - labelCoord[i][k]) / (f + distMean[i]) ** 2 correlationMatrix = inv(d2f) for j in range(3): targetCoordStd[0][j] = sqrt(abs(correlationMatrix[j][j])) return targetCoordMean, targetCoordStd, chiSquare, iter ################### # Launch in PyMOL # ################### class MainApp(wx.App): def OnInit(self): frame = mtsslTrilaterate(None, -1, "mtsslTrilaterate") frame.Show(True) self.SetTopWindow(frame) return True def run(): app = MainApp(0) app.MainLoop() def start(): t = threading.Thread(target=run, args=()) t.setDaemon(1), t.start() def __init__(self): self.menuBar.addmenuitem('Plugin', 'command', 'mtsslTrilaterate', label='mtsslTrilaterate', command=lambda s=self: start())