Ejemplo n.º 1
0
class BlackledgeModuleFrame(Frame):
    """ Frame for handling MODULE calculation.
  
  Note that the frame uses (or creates) an NmrCalcStore named 'BLACKLEDGE_MODULE'
  and linked to the current NmrProject.
  """
    def __init__(self,
                 parent,
                 project,
                 closeButton=False,
                 tempFiles=False,
                 *args,
                 **kw):

        ###########################################################################
        # INIT VARIABLES

        self.parent = parent
        self.project = project

        try:
            self.nmrProject = (project.currentNmrProject or
                               project.newNmrProject(name='BLACKLEDGE_MODULE'))
        except:
            print '&&& Running MODULE popup from outside CCPN Analysis - debug only - no NmrCalc'
            self.nmrProject = None

        if self.nmrProject:
            self.calcStore = project.findFirstNmrCalcStore(name=MODULE, nmrProject=self.nmrProject) or \
                             project.newNmrCalcStore(name=MODULE, nmrProject=self.nmrProject)

        else:
            self.calcStore = None

        self.run = None

        self.inputStructure = None
        self.inputRdcConstraintList = None
        self.inputDistanceConstraintList = [None]
        self.inputUserDescriptionText = None

        # path to the module executable
        modPath = subprocess.Popen(
            ['which', 'module'],
            stdout=subprocess.PIPE).communicate()[0].strip()
        self.moduleExePath = modPath or '    NB. MODULE executable not found    '

        self.waiting = False

        # for debug this could be False
        if tempFiles:
            self.useTempFiles = True
        else:
            self.useTempFiles = False

        # create temp files for MODULE
        if self.useTempFiles:
            self.moduleTempDir = tempfile.mkdtemp(prefix='MODULE-')
        else:
            self.moduleTempDir = os.getcwd()

        #djo35# self.calcStore = self.resetCalcStore(name='BLACKLEDGE_MODULE')

        # END INIT OF VARIABLES
        ###########################################################################

        ###########################################################################
        # START GUI CODE

        Frame.__init__(self, parent, *args, **kw)

        self.expandGrid(0, 0)

        ## Single Frame
        # frame = Frame(self, grid=(0,0))

        # or with Tabs?
        options = ['Launch', 'Output', 'Runs']
        tabbedFrame = TabbedFrame(self, options=options, grid=(0, 0))
        frameA, frameB, frameC = tabbedFrame.frames
        self.tabbedFrame = tabbedFrame

        frameA.expandGrid(14, 2)

        row = 0
        div = LabelDivider(frameA,
                           text='MODULE Setup',
                           grid=(row, 0),
                           gridSpan=(1, 4))

        row += 1
        # allow the user to choose MODULE if either the one in PATH is incorrect or not found
        button = Button(frameA, text='Select MODULE executable:',bd=1, \
                                command=self.selectExecutable,  grid=(row,0), sticky="ew")
        self.moduleExeEntry = Entry(frameA, text=self.moduleExePath, grid=(row,1), gridSpan=(1,3), \
                                width=32, sticky="ew", bd=1)
        self.moduleExePath = self.moduleExeEntry.get()

        # separator "MODULE input"
        row += 1
        div = LabelDivider(frameA,
                           text='MODULE input',
                           grid=(row, 0),
                           gridSpan=(1, 5))

        row += 1
        label = Label(frameA, text='Structure:', grid=(row, 1))
        self.inputStructurePulldown = PulldownList(frameA, self.changeInputStructure, \
                                                        grid=(row,2))
        # self.constraintsFileEntry.bind('<Leave>', self.updateEntryParams)

        row += 1
        label = Label(frameA, text='RDC constraints:', grid=(row, 1))
        self.inputRdcConstraintsPulldown = PulldownList(frameA, self.changeInputRdcConstraintList, \
                                                        grid=(row,2))
        #self.constraintsFileEntry.bind('<Leave>', self.updateEntryParams)

        row += 1
        label = Label(frameA, text='(Optional input)', grid=(row, 0))
        label = Label(frameA, text='Distance constraints:', \
                                                        grid=(row,1))
        self.inputDistanceConstraintsPulldown = PulldownList(frameA, self.changeInputDistanceConstraintList, \
                                                        grid=(row,2))
        #self.constraintsFileEntry.bind('<Leave>', self.updateEntryParams)

        row += 1

        subFrameDepth = 4
        subframe = LabelFrame(frameA, text='MODULE User Notes (store notes about how MODULE was run here)', \
                                                        grid=(row,0), gridSpan=(1,4))
        subframe.expandGrid(subFrameDepth, 0)

        self.moduleUserText = ScrolledText(subframe)
        self.moduleUserText.grid(row=subFrameDepth,
                                 column=0,
                                 columnspan=4,
                                 sticky='nsew')

        # View Results
        row += subFrameDepth

        # row += 1
        # div = LabelDivider(frameA, text='MODULE launch', grid=(row,0), gridSpan=(1,4))

        row += 1
        button = Button(frameA, text='Run MODULE', bd=1, command=self.executeModule, \
                                                        grid=(row,0), gridSpan=(1,4), sticky="ew", bg=MODULE_GREEN)
        # grid=(row,0), gridSpan=(1,2), sticky="ew", bg=MODULE_GREEN)

        ###########################################################################
        # Frame B (tab 2) Ouput
        frameB.expandGrid(4, 1)
        row = 0

        subFrameDepth = 6
        subframe = LabelFrame(frameB, text='MODULE Output', \
                                                        grid=(row,0), gridSpan=(1,5))
        #subframe.grid_columnconfigure(2, weight=1)
        subframe.expandGrid(subFrameDepth, 0)

        self.moduleOutputText = ScrolledText(subframe)
        self.moduleOutputText.setState(state=Tkinter.DISABLED)
        self.moduleOutputText.grid(row=subFrameDepth,
                                   column=0,
                                   columnspan=4,
                                   sticky='nsew')

        # separator "MODULE input"
        row += 1
        div = LabelDivider(frameB,
                           text='MODULE RDC Back Values',
                           grid=(row, 0),
                           gridSpan=(1, 5))

        row += 1
        button = Button(frameB, text='Import MODULE Back Values file', bd=1, command=self.importModuleBackValues, \
                                                        grid=(row,0), gridSpan=(1,4), sticky="ew", bg=MODULE_BLUE)
        # grid=(row,0), gridSpan=(2,4), sticky="ew", bg=MODULE_BLUE)

        row += 1
        self.rdcOutputTable = None
        frameB.grid_rowconfigure(row, weight=1)
        headings = ('#', 'Resonances', 'Value', 'Back Value', 'Diff.', 'Error')

        editWidgets = [None, None, None, None, None, None]
        editGetCallbacks = [None, None, None, None, None, None]
        editSetCallbacks = [None, None, None, None, None, None]

        self.rdcOutputTable = ScrolledMatrix(frameB,
                                             headingList=headings,
                                             multiSelect=False,
                                             editWidgets=editWidgets,
                                             editGetCallbacks=editGetCallbacks,
                                             editSetCallbacks=editSetCallbacks,
                                             initialRows=4)

        self.rdcOutputTable.grid(row=row,
                                 column=0,
                                 columnspan=4,
                                 sticky='nsew')

        row += 1
        button = Button(frameB, text='Import MODULE Structure', bd=1, command=self.importModuleStructure, \
                                                        grid=(row,0), gridSpan=(1,4), sticky="ew", bg=MODULE_BLUE)
        # grid=(row,0), gridSpan=(2,4), sticky="ew", bg=MODULE_BLUE)

        ###########################################################################
        # Frame C (tab 3) NMR Calc display bits
        frameC.expandGrid(4, 1)
        row = 0

        div = LabelDivider(frameC,
                           text='Stored MODULE Runs',
                           grid=(row, 0),
                           gridSpan=(1, 5))

        # NmrCalc Run scrolled matrix
        row += 1
        self.runTable = None
        frameC.grid_rowconfigure(row, weight=1)
        headings = ('Run ID', 'notes', 'Status')

        # self.editRunNotes = DataEntry.askString('Run Notes', 'Edit notes about Run', tipText='Notes about Run', parent=self)
        # editWidgets       = [None, self.editRunNotes, None]

        editWidgets = [None, None, None]
        editGetCallbacks = [None, None, None]
        editSetCallbacks = [None, None, None]

        self.runTable = ScrolledMatrix(frameC,
                                       headingList=headings,
                                       multiSelect=False,
                                       editWidgets=editWidgets,
                                       editGetCallbacks=editGetCallbacks,
                                       editSetCallbacks=editSetCallbacks,
                                       initialRows=4)

        self.runTable.grid(row=row, column=0, columnspan=4, sticky='nsew')

        row += 4
        tipTexts = ['Load Selected Run', 'Delete Selected Run']
        texts = ['Load Selected Run', 'Delete Selected']
        commands = [self.loadRun, self.deleteRun]
        colours = [MODULE_GREEN, MODULE_RED]
        self.runButtons = ButtonList(frameC,
                                     texts=texts,
                                     tipTexts=tipTexts,
                                     commands=commands,
                                     grid=(row, 0),
                                     gridSpan=(1, 4))
        self.runButtons.buttons[0].config(bg=MODULE_GREEN)
        self.runButtons.buttons[1].config(bg=MODULE_RED)

        ###########################################################################
        # Keep GUI up to date

        self.updateAfter()
        self.administerNotifiers(self.parent.registerNotify)

    # END GUI CODE
    ###########################################################################

    ###########################################################################
    # FUNCTIONS CALLED FROM GUI

    ################
    # OS interaction
    def selectExecutable(self):
        """ Choose the MODULE executable appropriate for your system. """

        popup = FileSelectPopup(self, show_file=True)

        executable = popup.getFile()
        if executable:
            self.moduleExeEntry.set(executable)

        popup.destroy()

    def executeModule(self):
        """ Execute MODULE with all given parameters 
        Yeah it's a big function, bite me :-P
    """

        # check that all params have been set
        if not os.path.isfile(self.moduleExePath):
            popup = showWarning('MODULE',
                                'Cannot find MODULE executable.',
                                parent=self)
            return

        if self.inputStructure is None:
            popup = showWarning(
                'MODULE',
                'MODULE cannot run with out a structure for input.',
                parent=self)
            return

        if self.inputRdcConstraintList is None:
            popup = showWarning(
                'MODULE',
                'MODULE cannot run with out an RDC constraint list for input.',
                parent=self)
            return

        # write temp PDB file
        strucTempFile = tempfile.NamedTemporaryFile(mode='w',
                                                    suffix='.pdb',
                                                    dir=self.moduleTempDir,
                                                    delete=False)
        strucTempFile.close()
        makePdbFromStructure(strucTempFile.name,
                             self.inputStructure.structureEnsemble,
                             model=self.inputStructure)

        # write temp RDC file
        rdcTempFile = tempfile.NamedTemporaryFile(mode='w',
                                                  suffix='.tab',
                                                  dir=self.moduleTempDir,
                                                  delete=False)

        moduleIo.writeConstraintList(rdcTempFile, self.inputRdcConstraintList)
        rdcTempFile.close()

        # command to be executed (to start MODULE)
        moduleCommands = [
            self.moduleExePath, strucTempFile.name, rdcTempFile.name
        ]

        # if user has set Distance constraint list then fill add this (user optional)
        if self.inputDistanceConstraintList:
            distanceTempFile = tempfile.NamedTemporaryFile(
                mode='w', suffix='.tab', dir=self.moduleTempDir, delete=False)
            moduleIo.writeConstraintList(distanceTempFile,
                                         self.inputDistanceConstraintList)
            distanceTempFile.close()
            moduleCommands.append(distanceTempFile.name)

        # execute MODULE
        moduleOutput = subprocess.Popen(
            moduleCommands, stdout=subprocess.PIPE).communicate()[0]

        # clean out the blank lines
        for line in moduleOutput:
            if line == '':
                moduleOutput.pop(line)

        # send the output to our screen
        self.moduleOutputText.setState(state=Tkinter.NORMAL)
        self.moduleOutputText.setText(text=moduleOutput)
        self.moduleOutputText.setState(state=Tkinter.DISABLED)

        # clean up temp files (delete has been set to False)
        for tempFile in [strucTempFile.name, rdcTempFile.name]:
            if os.path.isfile(tempFile):
                os.unlink(tempFile)
        if self.inputDistanceConstraintList:
            if os.path.isfile(distanceTempFile.name):
                os.unlink(distanceTempFile.name)

        # create new run and store inputs
        self.newRun()
        self.updateRunInputs()

    ############################################################################
    # Outputs:

    ############################
    # Import MODULE PDB

    def findModuleExportPdbFile(self):
        """ Find the MODULE PDB export file, typically called temp%s.pdb 
      unless user has renamed it.
    """
        modulePdbFileGood = False

        def yes():
            modulePdbFileGood = True

        def cancel():
            modulePdbFileGood = False

        # MODULE writes pdb files to CWD no matter where you or it is
        possibleFiles = glob.glob(os.path.join(os.getcwd(), 'temp*'))
        # debug
        # possibleFiles = glob.glob( os.path.join( os.getcwd(), 'module', 'temp*' ) )

        if len(possibleFiles) == 1:

            if os.path.isfile(possibleFiles[0]):

                texts = ['Yes', 'No, choose another PDB file', 'Cancel']
                objects = [yes, self.selectModulePdbExport, cancel]
                func = showMulti('MODULE', 'Is this the PDB strcture you exported from MODULE?\n%s' % possibleFiles[0], \
                          texts=texts, objects=objects, parent=self)
                func()

                if modulePdbFileGood == True:
                    modulePdbFile = possibleFiles[0]
                else:
                    return None

            else:
                modulePdbFile = self.selectModulePdbExport()

        elif len(possibleFiles) > 1:

            texts = ['%s' % fileName for fileName in possibleFiles]
            objects = ['%s' % fileName for fileName in possibleFiles]
            modulePdbFile = showMulti('MODULE', 'Multiple possible MODULE export\nfiles found, please select one:' % possibleFiles, \
                      texts=texts, objects=objects, parent=self)

            if os.path.isfile(modulePdbFile):
                modulePdbFile = modulePdbFile

        else:
            modulePdbFile = self.selectModulePdbExport()

        return modulePdbFile

    def selectModulePdbExport(self):
        """ Choose the PDB file that was exported from MODULE. """

        file_types = [
            FileType("PDB structure files", ["*.pdb"]),
            FileType("All files", ["*"])
        ]
        popup = FileSelectPopup(self,
                                file_types,
                                dismiss_text='Cancel',
                                show_file=True)

        chosenPdbFile = popup.getFile()
        if os.path.isfile(chosenPdbFile):
            modulePdbFile = chosenPdbFile
        else:
            warnPopup = showWarning('MODULE',
                                    'File %s not found.' % chosenPdbFile,
                                    parent=self)
            return None

        popup.destroy()

        return modulePdbFile

    def importModuleStructure(self):
        """ Find MODULE structure file and import into Analysis (or attempt to, this will probably
      barf as MODULE pdb files are not good)
    """

        modulePdbFile = self.findModuleExportPdbFile()
        if not modulePdbFile: return

        # this stores the re-hacked MODULE pdb file and then removes it on close
        tempPdb = tempfile.NamedTemporaryFile(mode='w',
                                              suffix='.pdb',
                                              dir=self.moduleTempDir,
                                              delete=False)
        tempPdb.close()

        # if file found then add to project
        if os.path.isfile(modulePdbFile):
            moduleIo.BlackledgeToPdbConverter(modulePdbFile, tempPdb)
            structure = getStructureFromFile(
                self.inputStructure.parent.molSystem, tempPdb)
            os.unlink(tempPdb)

    ############################
    # Import MODULE Back Values

    def findModuleExportBackValuesFile(self):
        """ Find the Back Values file that the User hopefully exported from MODULE """
        def yes():
            moduleBvFileGood = True

        def cancel():
            moduleBvFileGood = False

        # back value files are helpfully appended '*.back'
        possibleFiles = glob.glob(os.path.join(os.getcwd(), '*.back'))
        possibleFiles += glob.glob(os.path.join(self.moduleTempDir, '*.back'))

        if len(possibleFiles) == 1:

            if os.path.isfile(possibleFiles[0]):

                texts = [
                    'Yes', 'No, choose another Back Values file', 'Cancel'
                ]
                objects = [yes, self.selectModuleBvExport, cancel]
                func = showMulti('MODULE', 'Is this the Back Values file that you exported from MODULE?\n%s' % possibleFiles[0], \
                          texts=texts, objects=objects, parent=self)
                func()

                if moduleBvFileGood == True:
                    moduleBackValueFile = possibleFiles[0]
                else:
                    return None

            else:
                moduleBackValueFile = self.selectModuleBvExport()

        elif len(possibleFiles) > 1:

            texts = ['%s' % fileName for fileName in possibleFiles]
            objects = ['%s' % fileName for fileName in possibleFiles]
            chosenBvFile = showMulti('MODULE', 'Multiple possible MODULE Back Value\nfiles found, please select one:' % possibleFiles, \
                      texts=texts, objects=objects, parent=self)

            if os.path.isfile(modulePdbFile):
                moduleBackValueFile = chosenBvFile

        else:
            moduleBackValueFile = self.selectModuleBvExport()

        return moduleBackValueFile

    def selectModuleBvExport(self):
        """ Choose the Back Value file that was exported from MODULE. """

        file_types = [
            FileType("Back Value files", ["*.back"]),
            FileType("All files", ["*"])
        ]
        popup = FileSelectPopup(self,
                                file_types,
                                dismiss_text='Cancel',
                                show_file=True)

        chosenBvFile = popup.getFile()
        if os.path.isfile(chosenBvFile):
            moduleBackValueFile = chosenBvFile
        else:
            warnPopup = showWarning('MODULE',
                                    'File %s not found.' % chosenBvFile,
                                    parent=self)
            return None

        popup.destroy()

        return moduleBackValueFile

    def importModuleBackValues(self):
        """ Attempt to find the Back Values file and then import it ... """

        # find the Back Values file or ask user
        backValFile = self.findModuleExportBackValuesFile()
        if not backValFile: return

        # create a new RDC list
        if self.project:
            nmrConstraintStore = self.project.newNmrConstraintStore(
                nmrProject=self.project.findFirstNmrProject())

        if self.inputStructure:
            chain = self.inputStructure.parent.molSystem.findFirstChain()
        else:
            chain = None

        if nmrConstraintStore and chain:

            rdcList = moduleIo.getBackValuesListFromFile(
                backValFile, chain, nmrConstraintStore)

            # rdcRawData = moduleIo.getRawBackValuesFromFile( backValFile )
            #
            # for run in runs:
            #   runText.append( [run.getSerial(), run.getDetails(), run.getStatus()] )

            self.rdcOutputTable.update(objectList=None, textMatrix=None)

    # end OS interaction
    ####################

    ##################################
    # get, set and update menu options

    def changeInputRun(self, run):
        self.run = run

    def changeInputStructure(self, structure):
        self.inputStructure = structure

    def changeInputRdcConstraintList(self, constraintList):
        self.inputRdcConstraintList = constraintList

    def changeInputDistanceConstraintList(self, constraintList):
        self.inputDistanceConstraintList = constraintList

    def changeInputConstraintList(self, constraintList, className):
        if className == 'RdcConstraintList':
            self.changeInputRdcConstraintList(constraintList)
        elif className == 'DistanceConstraintList':
            self.changeInputDistanceConstraintList(constraintList)

    def getInputConstraintList(self, className):
        if className == 'RdcConstraintList':
            return self.inputRdcConstraintList
        elif className == 'DistanceConstraintList':
            return self.inputDistanceConstraintList

    # end get and set
    ##################################

    ####################
    # NMR Calc runs etc.

    def loadRun(self):
        pass

    def deleteRun(self):
        pass

    def changeRun(self):
        pass

    def newRun(self):

        if self.calcStore:
            self.run = self.calcStore.newRun(status='provisional')

    def updateRunInputs(self, event=None):

        self.inputUserDescriptionText = self.moduleUserText.getText() or None

        # store input parameters in run
        if self.run:

            # set the path to the MODULE executable used
            setRunTextParameter(self.run, MODULE_EXE, self.moduleExePath)

            # set the input structure sent to MODULE
            sEnsemble = self.inputStructure.StructureEnsemble
            self.run.newStructureEnsembleData(sEnsemble.ensembleId,
                                              sEnsemble.molSystem.code,
                                              name=STRUCTURE_DATA,
                                              ioRole=INPUT)

            # set the input RDC constraint list
            rdcConst = self.inputRdcConstraintList
            self.run.newConstraintStoreData(rdcConst.nmrConstraintStore.serial,
                                            name=RDC_CONSTRAINT_DATA,
                                            ioRole=INPUT)

            # set the input RDC constraint list (optional)
            distConst = self.inputDistanceConstraintList
            if distConst:
                self.run.newConstraintStoreData(
                    distConst.nmrConstraintStore.serial,
                    name=DIST_CONSTRAINT_DATA,
                    ioRole=INPUT)

            # set the input user data (if any)
            setRunTextParameter(self.run, USER_DESCRIPTION_DATA,
                                self.inputUserDescriptionText)

    def editRunNotes(self):
        pass

    # end NMR calc stuff
    ####################

    ######################
    # update and noitfiers

    def administerNotifiers(self, notifyFunc):

        for func in ['__init__', 'delete']:
            for clazz in ['RdcConstraintList', 'DistanceConstraintList']:
                notifyFunc(self.updateAfter, 'ccp.nmr.NmrConstraint.' + clazz,
                           func)

            notifyFunc(self.updateAfter, 'ccp.nmr.NmrCalc.Run', func)

    def destroy(self):

        self.administerNotifiers(self.parent.unregisterNotify)
        Frame.destroy(self)

    def updateAll(self):

        # tab A
        self.updateModuleExecutablePath()
        self.updateInputStructure()
        self.updateInputRdcConstraintList()
        self.updateInputDistanceConstraintList()

        # tab C
        self.updateInputRuns()

        self.waiting = False

    def updateAfter(self, obj=None):

        if self.waiting:
            return
        else:
            self.waiting = True
            self.after_idle(self.updateAll)

    def updateInputRuns(self):

        if self.calcStore:
            runs = [
                r for r in self.calcStore.sortedRuns()
                if r.status == PROVISIONAL
            ]

        if not runs:
            self.newRun()
            runs = [self.run]

        runText = []

        for run in runs:
            runText.append(
                [run.getSerial(),
                 run.getDetails(),
                 run.getStatus()])

        self.runTable.update(objectList=runs, textMatrix=runText)

    def updateModuleExecutablePath(self):
        self.moduleExePath = self.moduleExeEntry.get()

    def updateInputStructure(self):

        index = None
        # names       = ['<None>']
        # structures  = [None]
        names = []
        structures = []

        for molSystem in self.project.sortedMolSystems():
            for ensemble in molSystem.sortedStructureEnsembles():
                for structure in ensemble.sortedModels():
                    structures.append(structure)

        if structures:

            for i, model in enumerate(structures):
                if model is None: continue
                ee = model.structureEnsemble
                name = '%s:%d:%d' % (ee.molSystem.code, ee.ensembleId,
                                     model.serial)
                names.append(name)

            if self.inputStructure not in structures:
                self.changeInputStructure(structures[0])

            index = structures.index(self.inputStructure)

        else:
            self.inputStructure = None

        self.inputStructurePulldown.setup(names, structures, index or -1)

    def updateInputConstraintList(self, className, obj=None):

        index = None
        # if DistanceConstraintList then None must be an option
        # names           = ['<None>']
        # constraintLists = [None]

        if className == 'DistanceConstraintList':
            names = ['<None>']
            constraintLists = [None]
        else:
            names = []
            constraintLists = []

        for nmrConstraintStore in self.parent.project.sortedNmrConstraintStores(
        ):
            for constraintList in nmrConstraintStore.sortedConstraintLists():
                if not className or constraintList.className == className:
                    constraintLists.append(constraintList)

        if constraintLists:

            for constList in constraintLists:
                if constList is None: continue
                store = constList.nmrConstraintStore
                name = '%d:%d' % (store.serial, constList.serial)
                names.append(name)

            if self.getInputConstraintList(className) not in constraintLists:
                self.changeInputConstraintList(constraintLists[0], className)

            index = constraintLists.index(
                self.getInputConstraintList(className))

        else:
            self.changeInputConstraintList(None, className)

        if className == 'RdcConstraintList':
            self.inputRdcConstraintsPulldown.setup(names, constraintLists,
                                                   index or -1)
        elif className == 'DistanceConstraintList':
            self.inputDistanceConstraintsPulldown.setup(
                names, constraintLists, index or -1)

    def updateInputRdcConstraintList(self):
        self.updateInputConstraintList('RdcConstraintList')

    def updateInputDistanceConstraintList(self):
        self.updateInputConstraintList('DistanceConstraintList')