Source code for pyworkflow.viewer

# **************************************************************************
# *
# * Authors:     J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
# *
# * [1] SciLifeLab, Stockholm University
# *
# * This program is free software: you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation, either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program.  If not, see <https://www.gnu.org/licenses/>.
# *
# *  All comments concerning this program package may be sent to the
# *  e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************


import os

import pyworkflow.protocol as pwprot
from pyworkflow.utils import KEYSYM
from subprocess import call

DESKTOP_TKINTER = 'tkinter'
WEB_DJANGO = 'django'


[docs]class View(object): """ Represents a visualization result for some object or file. Views can be plots, table views, chimera scripts, commands or messages. """
[docs] def show(self): """ This method should be overwritten to implement how this particular view will be displayed in desktop. """ pass
[docs] def toUrl(self): """ If the view have web implementation, this method should be implemented to build the url with parameters that will be used to respond. """ pass
[docs]class CommandView(View): """ View for calling an external command. """ def __init__(self, cmd, **kwargs): View.__init__(self) self._cmd = cmd self._env = kwargs.get('env', None) self._cwd = kwargs.get('cwd', None)
[docs] def show(self): call(self._cmd, shell=True, env=self._env, cwd=self._cwd)
MSG_INFO = 0 MSG_WARN = 1 MSG_ERROR = 2 MSG_DICT = {MSG_INFO: 'showInfo', MSG_WARN: 'showWarning', MSG_ERROR: 'showError'}
[docs]class MessageView(View): """ View for some message. """ def __init__(self, msg, title='', msgType=MSG_INFO, tkParent=None, **kwargs): View.__init__(self) self._msg = msg self._title = title self._msgType = msgType self._tkParent = tkParent
[docs] def show(self): import pyworkflow.gui.dialog as dialog func = getattr(dialog, MSG_DICT[self._msgType]) func(self._title, self._msg, self._tkParent)
[docs] def getMessage(self): return self._msg
[docs]class TextView(View): """ View for display some text file. """ def __init__(self, filelist, title='', tkParent=None, **kwargs): View.__init__(self) self._filelist = filelist if title: self._title = title else: self._title = filelist[0] self._tkParent = tkParent
[docs] def getFileList(self): return self._filelist
[docs] def show(self): from pyworkflow.gui.text import showTextFileViewer showTextFileViewer(self._title, self._filelist, self._tkParent)
# ---------------- Viewers ----------------------------------------
[docs]class Viewer(object): """ A Viewer will provide several Views to visualize the data associated to data objects or protocol. The _targets class property should contain a list of string with the class names that this viewer is able to visualize. For example: _targets = ['Image', 'SetOfImages'] """ _targets = [] _environments = [DESKTOP_TKINTER] _name = None def __init__(self, tmpPath='./Tmp', **args): self._tmpPath = tmpPath self._project = args.get('project') if self._project is None: raise Exception('Can not initialize a Viewer with None project.') self.protocol = args.get('protocol', None) self.formWindow = args.get('parent', None) self._keyPressed = args.get('keyPressed', None) self._tkRoot = self.formWindow.root if self.formWindow else None
[docs] def getKeyPressed(self): return self._keyPressed
[docs] def shiftPressed(self): return self._keyPressed==KEYSYM.SHIFT
[docs] def controlPressed(self): return self._keyPressed == KEYSYM.CONTROL
[docs] def getTkRoot(self): return self._tkRoot
def _getTmpPath(self, *paths): return os.path.join(self._tmpPath, *paths)
[docs] def visualize(self, obj, **kwargs): """ Display each of the views, by default the implementation is for desktop. """ for view in self._visualize(obj, **kwargs): view.show()
def _visualize(self, obj, **kwargs): """ This method should make the necessary conversions and return the list of Views that will be used to visualize the object """ return [] # FIXME: REMOVE THIS METHOD AFTER RE-FACTORING
[docs] def getView(self): """ This method should return the string value of the view in web that will respond to this viewer. This method only should be implemented in those viewers that have WEB_DJANGO environment defined. """ return None
[docs] def getProject(self): return self._project
[docs] def setProject(self, project): self._project = project
[docs] def getParent(self): """ Get the Tk parent widget. """ return self.formWindow
[docs] def infoMessage(self, msg, title='',): """ Build a message View of type INFO. """ return MessageView(msg, title, msgType=MSG_INFO, tkParent=self._tkRoot)
[docs] def errorMessage(self, msg, title=''): """ Build a message View of type INFO. """ return MessageView(msg, title, msgType=MSG_ERROR, tkParent=self._tkRoot)
[docs] def errorList(self, errors, views, title='Visualization errors'): """ Convert an error list in a single Error message. """ if errors: views.append(self.errorMessage('\n'.join(errors), title))
[docs] def warnMessage(self, msg, title=''): """ Build a message View of type INFO. """ return MessageView(msg, title, msgType=MSG_WARN, tkParent=self._tkRoot)
[docs] def textView(self, filelist, title=''): return TextView(filelist, title, tkParent=self.formWindow)
[docs] def tkWindow(self, windowClass, **kwargs): kwargs['masterWindow'] = self.formWindow return windowClass(**kwargs)
[docs] def getProtocolId(self): if not hasattr(self, 'protocol'): raise Exception("self.protocol is not defined for this Viewer.") return self.protocol.strId()
[docs] @classmethod def getName(cls): if cls._name is None: return cls.__name__ return cls._name
[docs]class ProtocolViewer(Viewer, pwprot.Protocol): """ Special kind of viewer that have a Form to organize better complex visualization associated with protocol results. If should provide a mapping between form params and the corresponding functions that will return the corresponding Views. """ def __init__(self, **kwargs): # Here we are going to intercept the original _defineParams function # and replace by an empty one, this is to postpone the definition of # params until the protocol is set and then self.protocol can be used # for a more dynamic definition object.__setattr__(self, '_defineParamsBackup', self._defineParams) object.__setattr__(self, '_defineParams', self._defineParamsEmpty) pwprot.Protocol.__init__(self, **kwargs) Viewer.__init__(self, **kwargs) self.allowHeader.set(False) # This flag will be used to display a plot or return the plotter self.showPlot = True self._tkRoot = None self.formWindow = None self.setWorkingDir(self.getProject().getTmpPath())
[docs] def getWindow(self): return self.formWindow
[docs] def getTkRoot(self): return self._tkRoot
def _defineParamsEmpty(self, form): """ Just do nothing and postpone the real definition. """ pass
[docs] def setProtocol(self, protocol): """ Set the protocol instance to the viewer and call the definition of the parameters. """ self.protocol = protocol self._defineParamsBackup(self._definition) self._createVarsFromDefinition()
[docs] def visualize(self, obj, **args): """Open the Protocol GUI Form given a Protocol instance""" from pyworkflow.gui.form import FormWindow self.setProtocol(obj) self.windows = args.get('windows', None) self.formWindow = FormWindow("Protocol Viewer: " + self.getClassName(), self, self._viewAll, self.windows, visualizeDict=self.__getVisualizeWrapperDict(), visualizeMode=True) self.formWindow.visualizeMode = True self.showInfo = self.formWindow.showInfo self.showError = self.formWindow.showError self._tkRoot = self.formWindow.root self.formWindow.show(center=True)
def _visualizeParam(self, paramName=None): """ Call handler to get viewers and visualize each one. """ errors = self.validate() if errors: errorMsg = '\n'.join(errors) self.showError(errorMsg, "Validation errors") else: views = self._getVisualizeDict()[paramName](paramName) if views: for v in views: v.show() def __getVisualizeWrapperDict(self): """ Replace the True attributes handler by the generic one. """ d = {} for k in self._getVisualizeDict(): d[k] = self._visualizeParam return d def _getVisualizeDict(self): """ Create the visualization dict for view individual params. """ return {} def _viewAll(self, *args): """ Visualize all data give the parameters. """ for k, v in self._getVisualizeDict().items(): if self.getAttributeValue(k, False): v(k) def _citations(self): return self.protocol._citations()
[docs] def validateInstallation(self): return
# TODO deprecate this method, it's duplicate of one from pwutils.utils def _getListFromRangeString(self, rangeStr): """ Create a list of integer from a string with range definitions Examples: "1,5-8,10" -> [1,5,6,7,8,10] "2,6,9-11" -> [2,6,9,10,11] "2 5, 6-8" -> [2,5,6,7,8] """ elements = rangeStr.split(',') values = [] for e in elements: if '-' in e: limits = e.split('-') values += range(int(limits[0]), int(limits[1])+1) else: # If values are separated by comma also split values += map(int, e.split()) return values