scipion-pyworkflow 3.11.0__py3-none-any.whl → 3.11.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. pyworkflow/apps/__init__.py +29 -0
  2. pyworkflow/apps/pw_manager.py +37 -0
  3. pyworkflow/apps/pw_plot.py +51 -0
  4. pyworkflow/apps/pw_project.py +113 -0
  5. pyworkflow/apps/pw_protocol_list.py +143 -0
  6. pyworkflow/apps/pw_protocol_run.py +51 -0
  7. pyworkflow/apps/pw_run_tests.py +267 -0
  8. pyworkflow/apps/pw_schedule_run.py +322 -0
  9. pyworkflow/apps/pw_sleep.py +37 -0
  10. pyworkflow/apps/pw_sync_data.py +439 -0
  11. pyworkflow/apps/pw_viewer.py +78 -0
  12. pyworkflow/constants.py +1 -1
  13. pyworkflow/gui/__init__.py +36 -0
  14. pyworkflow/gui/browser.py +760 -0
  15. pyworkflow/gui/canvas.py +1190 -0
  16. pyworkflow/gui/dialog.py +979 -0
  17. pyworkflow/gui/form.py +2726 -0
  18. pyworkflow/gui/graph.py +247 -0
  19. pyworkflow/gui/graph_layout.py +271 -0
  20. pyworkflow/gui/gui.py +566 -0
  21. pyworkflow/gui/matplotlib_image.py +233 -0
  22. pyworkflow/gui/plotter.py +247 -0
  23. pyworkflow/gui/project/__init__.py +25 -0
  24. pyworkflow/gui/project/base.py +192 -0
  25. pyworkflow/gui/project/constants.py +139 -0
  26. pyworkflow/gui/project/labels.py +205 -0
  27. pyworkflow/gui/project/project.py +491 -0
  28. pyworkflow/gui/project/searchprotocol.py +238 -0
  29. pyworkflow/gui/project/searchrun.py +181 -0
  30. pyworkflow/gui/project/steps.py +171 -0
  31. pyworkflow/gui/project/utils.py +332 -0
  32. pyworkflow/gui/project/variables.py +179 -0
  33. pyworkflow/gui/project/viewdata.py +472 -0
  34. pyworkflow/gui/project/viewprojects.py +510 -0
  35. pyworkflow/gui/project/viewprotocols.py +2116 -0
  36. pyworkflow/gui/project/viewprotocols_extra.py +562 -0
  37. pyworkflow/gui/text.py +771 -0
  38. pyworkflow/gui/tooltip.py +185 -0
  39. pyworkflow/gui/tree.py +684 -0
  40. pyworkflow/gui/widgets.py +307 -0
  41. pyworkflow/mapper/__init__.py +26 -0
  42. pyworkflow/mapper/mapper.py +222 -0
  43. pyworkflow/mapper/sqlite.py +1581 -0
  44. pyworkflow/mapper/sqlite_db.py +145 -0
  45. pyworkflow/project/__init__.py +31 -0
  46. pyworkflow/project/config.py +454 -0
  47. pyworkflow/project/manager.py +180 -0
  48. pyworkflow/project/project.py +2095 -0
  49. pyworkflow/project/usage.py +165 -0
  50. pyworkflow/protocol/__init__.py +38 -0
  51. pyworkflow/protocol/bibtex.py +48 -0
  52. pyworkflow/protocol/constants.py +87 -0
  53. pyworkflow/protocol/executor.py +483 -0
  54. pyworkflow/protocol/hosts.py +317 -0
  55. pyworkflow/protocol/launch.py +277 -0
  56. pyworkflow/protocol/package.py +42 -0
  57. pyworkflow/protocol/params.py +781 -0
  58. pyworkflow/protocol/protocol.py +2707 -0
  59. pyworkflow/tests/__init__.py +29 -0
  60. pyworkflow/tests/test_utils.py +25 -0
  61. pyworkflow/tests/tests.py +341 -0
  62. pyworkflow/utils/__init__.py +38 -0
  63. pyworkflow/utils/dataset.py +414 -0
  64. pyworkflow/utils/echo.py +104 -0
  65. pyworkflow/utils/graph.py +169 -0
  66. pyworkflow/utils/log.py +293 -0
  67. pyworkflow/utils/path.py +528 -0
  68. pyworkflow/utils/process.py +153 -0
  69. pyworkflow/utils/profiler.py +92 -0
  70. pyworkflow/utils/progressbar.py +154 -0
  71. pyworkflow/utils/properties.py +617 -0
  72. pyworkflow/utils/reflection.py +129 -0
  73. pyworkflow/utils/utils.py +880 -0
  74. pyworkflow/utils/which.py +229 -0
  75. pyworkflow/webservices/__init__.py +8 -0
  76. pyworkflow/webservices/config.py +8 -0
  77. pyworkflow/webservices/notifier.py +152 -0
  78. pyworkflow/webservices/repository.py +59 -0
  79. pyworkflow/webservices/workflowhub.py +74 -0
  80. pyworkflowtests/tests/__init__.py +0 -0
  81. pyworkflowtests/tests/test_canvas.py +72 -0
  82. pyworkflowtests/tests/test_domain.py +45 -0
  83. pyworkflowtests/tests/test_logs.py +74 -0
  84. pyworkflowtests/tests/test_mappers.py +392 -0
  85. pyworkflowtests/tests/test_object.py +507 -0
  86. pyworkflowtests/tests/test_project.py +42 -0
  87. pyworkflowtests/tests/test_protocol_execution.py +146 -0
  88. pyworkflowtests/tests/test_protocol_export.py +78 -0
  89. pyworkflowtests/tests/test_protocol_output.py +158 -0
  90. pyworkflowtests/tests/test_streaming.py +47 -0
  91. pyworkflowtests/tests/test_utils.py +210 -0
  92. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/METADATA +2 -2
  93. scipion_pyworkflow-3.11.1.dist-info/RECORD +161 -0
  94. scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
  95. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/WHEEL +0 -0
  96. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/entry_points.txt +0 -0
  97. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/licenses/LICENSE.txt +0 -0
  98. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,238 @@
1
+ # -*- coding: utf-8 -*-
2
+ # **************************************************************************
3
+ # *
4
+ # * Authors: Pablo Conesa [1]
5
+ # *
6
+ # * [1] Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
7
+ # *
8
+ # * This program is free software: you can redistribute it and/or modify
9
+ # * it under the terms of the GNU General Public License as published by
10
+ # * the Free Software Foundation, either version 3 of the License, or
11
+ # * (at your option) any later version.
12
+ # *
13
+ # * This program is distributed in the hope that it will be useful,
14
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # * GNU General Public License for more details.
17
+ # *
18
+ # * You should have received a copy of the GNU General Public License
19
+ # * along with this program. If not, see <https://www.gnu.org/licenses/>.
20
+ # *
21
+ # * All comments concerning this program package may be sent to the
22
+ # * e-mail address 'scipion@cnb.csic.es'
23
+ # *
24
+ # **************************************************************************
25
+ """ This module contains the provider and dialog to search for a protocol"""
26
+ import tkinter as tk
27
+ from pyworkflow import Config
28
+ import pyworkflow.gui as pwgui
29
+ import pyworkflow.object as pwobj
30
+ from pyworkflow.gui.dialog import SearchBaseWindow
31
+
32
+ from pyworkflow.gui.project.utils import isAFinalProtocol
33
+ from pyworkflow.gui.project.viewprotocols_extra import ProtocolTreeConfig
34
+ from pyworkflow.project.usage import getNextProtocolSuggestions
35
+ from pyworkflow.utils import Icon
36
+
37
+ UPDATED = "updated"
38
+
39
+ NEW = "new"
40
+
41
+ BETA = "beta"
42
+
43
+
44
+ class ProtocolTreeProvider(pwgui.tree.ObjectTreeProvider):
45
+ """Create the tree elements for a Protocol run"""
46
+
47
+ def __init__(self, protocol):
48
+ self.protocol = protocol
49
+ # This list is create to group the protocol parameters
50
+ # in the tree display
51
+ self.status = pwobj.List(objName='_status')
52
+ self.params = pwobj.List(objName='_params')
53
+ self.statusList = ['status', 'initTime', 'endTime', 'error',
54
+ 'interactive', 'mode']
55
+
56
+ objList = [] if protocol is None else [protocol]
57
+ pwgui.tree.ObjectTreeProvider.__init__(self, objList)
58
+
59
+
60
+
61
+ class SearchProtocolWindow(SearchBaseWindow):
62
+
63
+ columnConfig = {
64
+ '#0': ('Status', {'width': 50, 'minwidth': 50, 'stretch': tk.NO}, 5),
65
+ # Heading, tree column kwargs, casting for sorting
66
+ 'protocol': ('Protocol', {'width': 300, 'stretch': tk.FALSE}, 10),
67
+ 'score': ('Score/Freq.', {'width': 50, 'stretch': tk.FALSE}, 5, int),
68
+ 'streaming': ('Streamified', {'width': 100, 'stretch': tk.FALSE}, 5),
69
+ 'installed': ('Installation', {'width': 110, 'stretch': tk.FALSE}, 5),
70
+ 'help': ('Help', {'minwidth': 300, 'stretch': tk.YES}, 5),
71
+ }
72
+
73
+ def __init__(self, parentWindow, position=None, selectionGetter=None):
74
+
75
+ posStr = "" if position is None else " at (%s,%s)" % position
76
+ self.position = position
77
+ self.selectionGetter = selectionGetter
78
+ self.selectedProtocol = None
79
+ self._infoLbl = None # Label to show information
80
+ super().__init__(parentWindow,
81
+ title="Add a protocol" + posStr)
82
+
83
+ self.root.bind("<FocusIn>", self._onWindowFocusIn)
84
+
85
+ def _onWindowFocusIn(self, event):
86
+ """
87
+ To refresh the selected protocol in the graph upon window activation.
88
+ :param event: event information
89
+ :return: Nothing
90
+ """
91
+ if event.widget == self.root and self.selectionGetter:
92
+ self.selectedProtocol = self.selectionGetter()
93
+ if self._isSuggestionActive():
94
+ self._onSearchClick()
95
+ def _isSuggestionActive(self):
96
+ """
97
+ :return: Returns true if current mode is suggestion mode.
98
+ """
99
+ return self._searchVar.get().lower().strip() ==""
100
+
101
+ def _createSearchBox(self, content):
102
+ frame = super()._createSearchBox(content)
103
+
104
+ btn = pwgui.widgets.IconButton(frame, "Suggest",
105
+ tooltip="Suggestions for active protocol based on usage.",
106
+ imagePath=Icon.LIGHTBULB,
107
+ command=self.showSuggestions)
108
+ btn.grid(row=0, column=3, sticky='nw')
109
+
110
+ self.lbl = tk.StringVar()
111
+ lbl = tk.Label(frame, text="", bg=Config.SCIPION_BG_COLOR, textvariable=self.lbl, font=self.font)
112
+ lbl.grid(row=0, column=4, sticky='news')
113
+
114
+ def _createResultsTree(self, frame, show, columns):
115
+ # This code is where the callback (on double click) is defined.
116
+ return self.master.getViewWidget()._createProtocolsTree(frame, show=show, columns=columns, position=self.position)
117
+
118
+ def showSuggestions(self, e=None):
119
+ self._searchVar.set("")
120
+ self._onSearchClick()
121
+ def _onSearchClick(self, e=None):
122
+
123
+ self._resultsTree.clear()
124
+
125
+ protList = self.scoreProtocols()
126
+
127
+ # Sort by weight
128
+ protList.sort(reverse=True, key=lambda x: x[8])
129
+
130
+ self._addProtocolToTree(protList)
131
+
132
+ def scoreProtocols(self):
133
+
134
+ if self._isSuggestionActive():
135
+ return self.addSuggestions()
136
+
137
+ keyword = self._searchVar.get().lower().strip()
138
+ self.lbl.set('Showing text search matches for "%s"' % keyword)
139
+
140
+ emProtocolsDict = Config.getDomain().getProtocols()
141
+ protList = []
142
+
143
+ for key, prot in emProtocolsDict.items():
144
+ if isAFinalProtocol(prot, key):
145
+ label = prot.getClassLabel().lower()
146
+ line = (key, label,
147
+ "installed" if prot.isInstalled() else "missing installation",
148
+ prot.getHelpText().strip().replace('\r', '').replace('\n', '').lower(),
149
+ "streamified" if prot.worksInStreaming() else "static",
150
+ BETA if prot.isBeta() else "",
151
+ NEW if prot.isNewDev() else "",
152
+ UPDATED if prot.isUpdated() else "")
153
+
154
+ line = self._addSearchWeight(line, keyword)
155
+ # something was found: weight > 0
156
+ if line[8] != 0:
157
+ protList.append(line)
158
+
159
+ return protList
160
+
161
+ def addSuggestions(self):
162
+
163
+ if self.selectedProtocol is None:
164
+ self.lbl.set("Showing suggestions for a first protocol")
165
+ protName =str(None)
166
+ else:
167
+ protName = self.selectedProtocol.getClassName()
168
+ self.lbl.set("Usage suggestions for selected protocol: %s" % self.selectedProtocol.getClassLabel())
169
+
170
+ protList = []
171
+ suggestions = getNextProtocolSuggestions(protName)
172
+ for suggestion in suggestions:
173
+ #Fields comming from the site:
174
+ # https://scipion.i2pc.es/report_protocols/api/v2/nextprotocol/suggestion/None/
175
+ # 'next_protocol__name', 'count', 'next_protocol__friendlyName', 'next_protocol__package', 'next_protocol__description'
176
+ nextProtName, count, name, package, descr = suggestion
177
+ streamstate = "unknown"
178
+ installed = "Missing. Available in %s plugin." % package
179
+ protClass = Config.getDomain().getProtocols().get(nextProtName, None)
180
+
181
+ # Get accurate values from existing installations
182
+ if protClass is not None:
183
+ name = protClass.getClassLabel().lower()
184
+ descr = protClass.getHelpText().strip().replace('\r', '').replace('\n', '').lower()
185
+ streamstate = "streamified" if protClass.worksInStreaming() else "static"
186
+ installed = "installed" if protClass.isInstalled() else "missing installation"
187
+
188
+ line = (nextProtName, name,
189
+ installed,
190
+ descr,
191
+ streamstate,
192
+ "",
193
+ "",
194
+ "",
195
+ count)
196
+
197
+ protList.append(line)
198
+
199
+ return protList
200
+
201
+ @staticmethod
202
+ def _addSearchWeight(line2Search, searchtext):
203
+ # Adds a weight value for the search
204
+ weight = 0
205
+
206
+ # prioritize findings in label
207
+ if searchtext in line2Search[1]:
208
+ weight += 10
209
+
210
+ for value in line2Search[2:]:
211
+ weight += 5 if searchtext in value else 0
212
+
213
+ if " " in searchtext:
214
+ for word in searchtext.split():
215
+ if word in line2Search[1]:
216
+ weight += 3
217
+
218
+ for value in line2Search[2:]:
219
+ weight += 1 if word in value else 0
220
+
221
+ return line2Search + (weight,)
222
+
223
+ def _addProtocolToTree(self, protList):
224
+ """ Adds the items in protList to the tree
225
+
226
+ :param protList: List of tuples with all the values/columns used in search and shown in the tree"""
227
+
228
+ for key, label, installed, help, streamified, beta, new, updated, weight in protList:
229
+ tag = ProtocolTreeConfig.getProtocolTag(installed == 'installed',
230
+ beta == BETA,
231
+ new == NEW,
232
+ updated == UPDATED)
233
+
234
+ self._resultsTree.insert(
235
+ '', 'end', key, text="", tags=tag,
236
+ values=(label, weight, streamified, installed, help))
237
+
238
+
@@ -0,0 +1,181 @@
1
+ # -*- coding: utf-8 -*-
2
+ # **************************************************************************
3
+ # *
4
+ # * Authors: Pablo Conesa [1]
5
+ # *
6
+ # * [1] Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
7
+ # *
8
+ # * This program is free software: you can redistribute it and/or modify
9
+ # * it under the terms of the GNU General Public License as published by
10
+ # * the Free Software Foundation, either version 3 of the License, or
11
+ # * (at your option) any later version.
12
+ # *
13
+ # * This program is distributed in the hope that it will be useful,
14
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # * GNU General Public License for more details.
17
+ # *
18
+ # * You should have received a copy of the GNU General Public License
19
+ # * along with this program. If not, see <https://www.gnu.org/licenses/>.
20
+ # *
21
+ # * All comments concerning this program package may be sent to the
22
+ # * e-mail address 'scipion@cnb.csic.es'
23
+ # *
24
+ # **************************************************************************
25
+ """ This modules hosts code provider and window to search for a run"""
26
+
27
+ import tkinter as tk
28
+ from pyworkflow import Config
29
+ import pyworkflow.protocol as pwprot
30
+
31
+ from pyworkflow.gui import SearchBaseWindow
32
+ from pyworkflow.gui.project.constants import *
33
+ from pyworkflow.gui.tree import ProjectRunsTreeProvider
34
+
35
+ class RunsTreeProvider(ProjectRunsTreeProvider):
36
+ """Provide runs info to populate tree inside viewprotocols. Is more advanced
37
+ than ProjectRunsTreeProvider extended, extended with right click actions."""
38
+
39
+ def __init__(self, project, actionFunc):
40
+ super().__init__(project)
41
+ self.actionFunc = actionFunc # ProtocolsView._runActionClicked
42
+ self._selection = project.getSettings().runSelection
43
+
44
+ def getActionsFromSelection(self):
45
+ """ Return the list of options available for selection. """
46
+ n = len(self._selection)
47
+ single = n == 1
48
+ anyselected = n > 0
49
+ if n:
50
+ prot = self.project.getProtocol(self._selection[0], fromRuns=True)
51
+ status = prot.getStatus()
52
+ nodeInfo = self.project.getSettings().getNodeById(prot.getObjId())
53
+ expanded = nodeInfo.isExpanded() if nodeInfo else True
54
+ else:
55
+ status = None
56
+
57
+ stoppable = status in [pwprot.STATUS_RUNNING, pwprot.STATUS_SCHEDULED,
58
+ pwprot.STATUS_LAUNCHED]
59
+
60
+ # This list defines the order the icons are shown
61
+ return [(ACTION_NEW, True),
62
+ (ACTION_EDIT, single and status and expanded),
63
+ (ACTION_BROWSE, single and status and expanded),
64
+ (ACTION_RENAME, single and status and expanded),
65
+ (ACTION_LABELS, True),
66
+
67
+ (ACTION_DUPLICATE, status and expanded),
68
+ (ACTION_COPY, status and expanded),
69
+ (ACTION_PASTE, status and expanded),
70
+ (ACTION_DELETE, status != pwprot.STATUS_RUNNING and status and expanded),
71
+
72
+ (ACTION_SELECT_FROM, anyselected),
73
+ (ACTION_SELECT_TO, anyselected),
74
+ (ACTION_COLLAPSE, single and status and expanded),
75
+ (ACTION_EXPAND, single and status and not expanded),
76
+
77
+ (ACTION_STOP, stoppable and single),
78
+ (ACTION_STOP_WORKFLOW, single),
79
+ (ACTION_RESTART_WORKFLOW, single),
80
+ (ACTION_CONTINUE_WORKFLOW, single),
81
+ (ACTION_RESET_WORKFLOW, single),
82
+
83
+ (ACTION_EXPORT, anyselected),
84
+ (ACTION_EXPORT_UPLOAD, anyselected),
85
+
86
+ (ACTION_STEPS, single and Config.debugOn() and status and expanded),
87
+ (ACTION_DB, single and Config.debugOn() and status and expanded),
88
+ ]
89
+
90
+ def getObjectActions(self, obj, withEvent=False):
91
+ """ Get actions available to perform.
92
+
93
+ This method is called in 2 cases:
94
+ 1.- Right-click on the tree (list of runs)
95
+ 2.- Right-click on the canvas. When called from the canvas we need the event to get the click's position.
96
+
97
+ :param obj: optional, if passed, actions on the object. otherwise generic actions
98
+ :param withEvent: pass True if callback has to have the event as a parameter (call from canvas but not from tree)
99
+
100
+ """
101
+
102
+ def addAction(actionLabel):
103
+ if actionLabel:
104
+ text = actionLabel
105
+ action = actionLabel
106
+ if withEvent:
107
+ callback=lambda e: self.actionFunc(action, e)
108
+ else:
109
+ callback=lambda: self.actionFunc(action)
110
+
111
+ actionLabel = (text, callback,
112
+ ActionIcons.get(action, None),
113
+ ActionShortCuts.get(action,None))
114
+ return actionLabel
115
+
116
+ actions = [addAction(a)
117
+ for a, cond in self.getActionsFromSelection() if cond]
118
+
119
+ if obj is not None and hasattr(obj, 'getActions'):
120
+ for text, action in obj.getActions():
121
+ actions.append((text, action, None))
122
+
123
+ return actions
124
+
125
+
126
+ class SearchRunWindow(SearchBaseWindow):
127
+
128
+ columnConfig = {
129
+ '#0': (ProjectRunsTreeProvider.ID_COLUMN, {'width': 100, 'stretch': tk.NO}, 10),
130
+ ProjectRunsTreeProvider.RUN_COLUMN: (ProjectRunsTreeProvider.RUN_COLUMN, {'width': 300, 'stretch': tk.TRUE}, 10),
131
+ ProjectRunsTreeProvider.STATE_COLUMN: (ProjectRunsTreeProvider.STATE_COLUMN, {'width': 150, 'stretch': tk.FALSE}, 5),
132
+ ProjectRunsTreeProvider.TIME_COLUMN: (ProjectRunsTreeProvider.TIME_COLUMN, {'width': 200, 'stretch': tk.FALSE}, 5),
133
+ 'Comment': ('Comment', {'width': 300, 'stretch': tk.FALSE}, 5),
134
+ 'Expanded': ('Expanded', {'width': 150, 'stretch': tk.FALSE}, 5),
135
+ }
136
+
137
+ def __init__(self, parentWindow, runsGraph, **kwargs):
138
+
139
+
140
+ super().__init__(parentWindow,
141
+ title="Locate a protocol in the graph",
142
+ **kwargs)
143
+ self.runsGraph = runsGraph
144
+
145
+ def _onSearchClick(self, e=None):
146
+
147
+ self._resultsTree.clear()
148
+ keyword = self._searchVar.get().lower().strip()
149
+
150
+ weightIndex = len(self.columnConfig)
151
+ nodes = self.runsGraph.getNodes()
152
+ protList = []
153
+
154
+ for node in nodes:
155
+ if node.run is not None:
156
+ run = node.run
157
+ key = run.getObjId()
158
+ label = run.getRunName()
159
+ status = run.getStatusMessage(),
160
+ time = run.getObjCreation()
161
+ comment = run.getObjComment()
162
+ expanded = "expanded" if getattr(node, 'expanded', False) else "collapsed"
163
+ line = (key, label, status, time, comment, expanded)
164
+
165
+ line = self.addSearchWeight(line, keyword)
166
+ # something was found: weight > 0
167
+ if line[weightIndex] != 0:
168
+ # Add the run
169
+ protList.append(line)
170
+
171
+ # Sort by weight
172
+ protList.sort(reverse=True, key=lambda x: x[weightIndex])
173
+
174
+ for line in protList:
175
+
176
+ self._resultsTree.insert(
177
+ '', 'end', line[0], text=line[0],
178
+ values=line[1:])
179
+
180
+
181
+
@@ -0,0 +1,171 @@
1
+ # -*- coding: utf-8 -*-
2
+ # **************************************************************************
3
+ # *
4
+ # * Authors: Pablo Conesa [1]
5
+ # *
6
+ # * [1] Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
7
+ # *
8
+ # * This program is free software: you can redistribute it and/or modify
9
+ # * it under the terms of the GNU General Public License as published by
10
+ # * the Free Software Foundation, either version 3 of the License, or
11
+ # * (at your option) any later version.
12
+ # *
13
+ # * This program is distributed in the hope that it will be useful,
14
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # * GNU General Public License for more details.
17
+ # *
18
+ # * You should have received a copy of the GNU General Public License
19
+ # * along with this program. If not, see <https://www.gnu.org/licenses/>.
20
+ # *
21
+ # * All comments concerning this program package may be sent to the
22
+ # * e-mail address 'scipion@cnb.csic.es'
23
+ # *
24
+ # **************************************************************************
25
+ """ This modules hosts gui code to visualize steps"""
26
+ import json
27
+ import tkinter as tk
28
+
29
+ import pyworkflow.protocol as pwprot
30
+ import pyworkflow.gui as pwgui
31
+ import pyworkflow.utils as pwutils
32
+
33
+ from pyworkflow import TK
34
+ from pyworkflow.utils import Icon
35
+
36
+
37
+ class StepsTreeProvider(pwgui.tree.TreeProvider):
38
+ """Create the tree elements for a Protocol run"""
39
+
40
+ def __init__(self, stepsList):
41
+ for i, s in enumerate(stepsList):
42
+ if not s._index:
43
+ s._index = i + 1
44
+
45
+ self._stepsList = stepsList
46
+ self.getColumns = lambda: [('Index', 50), ('Step', 200), ('Status', 150),
47
+ ('Time', 150), ('Class', 100)]
48
+ self._parentDict = {}
49
+
50
+ def getObjects(self):
51
+ return self._stepsList
52
+
53
+ @staticmethod
54
+ def getObjectInfo(obj):
55
+ info = {'key': obj._index,
56
+ 'values': (str(obj), obj.getStatus(), pwutils.prettyDelta(obj.getElapsedTime()),
57
+ obj.getClassName())}
58
+ return info
59
+
60
+ @staticmethod
61
+ def getObjectPreview(obj: pwprot.Step):
62
+
63
+ args = json.loads(obj.argsStr.get())
64
+ msg = "*Prerequisites*: %s \n" % str(obj._prerequisites)
65
+
66
+ msg += ("*Arguments*:\n")
67
+ for arg in args:
68
+ msg += " %s\n" % arg
69
+
70
+ msg += "*Needs GPU*: %s" % obj.needsGPU()
71
+
72
+ if hasattr(obj, 'resultFiles'):
73
+ results = json.loads(obj.resultFiles.get())
74
+ if len(results):
75
+ msg += "\n*Result files:* " + '\n '.join(results)
76
+
77
+ return None, msg
78
+
79
+ class StepsWindow(pwgui.browser.BrowserWindow):
80
+ def __init__(self, title, parentWindow, protocol, **args):
81
+ self._protocol = protocol
82
+ provider = StepsTreeProvider(protocol.loadSteps())
83
+ pwgui.browser.BrowserWindow.__init__(self, title, parentWindow,
84
+ weight=False, **args)
85
+ # Create buttons toolbar
86
+ self.root.columnconfigure(0, weight=1)
87
+ self.root.rowconfigure(1, weight=1)
88
+
89
+ self.fillToolBar()
90
+
91
+ # Create and set browser
92
+ browser = pwgui.browser.ObjectBrowser(self.root, provider,
93
+ showPreviewTop=False)
94
+ self.setBrowser(browser, row=1, column=0)
95
+
96
+ def fillToolBar(self):
97
+ # Tool bar
98
+ toolbar = tk.Frame(self.root)
99
+ toolbar.grid(row=0, column=0, sticky='nw', padx=5, pady=5)
100
+
101
+ # Tree button
102
+ btn = tk.Label(toolbar, text="Tree",
103
+ image=self.getImage(Icon.CODE_BRANCH),
104
+ compound=tk.LEFT, cursor='hand2')
105
+ btn.bind(TK.LEFT_CLICK, self._showTree)
106
+ btn.grid(row=0, column=0, sticky='nw')
107
+
108
+ # Reset status
109
+ btn = tk.Label(toolbar, text="Reset",
110
+ image=self.getImage(Icon.BROOM),
111
+ compound=tk.LEFT, cursor='hand2')
112
+ btn.bind('<Button-1>', self._resetStep)
113
+ btn.grid(row=0, column=1, sticky='nw')
114
+
115
+ # Finish status
116
+ btn = tk.Label(toolbar, text="Finish",
117
+ image=self.getImage(Icon.CHECKED),
118
+ compound=tk.LEFT, cursor='hand2')
119
+ btn.bind('<Button-1>', self._finishStep)
120
+ btn.grid(row=0, column=2, sticky='nw')
121
+
122
+ def _setStepStatus(self, status):
123
+
124
+ item = self.browser._lastSelected
125
+ if item is not None:
126
+ objId = item.getObjId()
127
+ self._protocol._updateSteps(lambda step: step.setStatus(status), where="id='%s'" % objId)
128
+ item.setStatus(status)
129
+ self.browser.tree.update()
130
+ def _resetStep(self, e=None):
131
+ self._setStepStatus(pwprot.STATUS_NEW)
132
+
133
+ def _finishStep(self, e=None):
134
+ self._setStepStatus(pwprot.STATUS_FINISHED)
135
+
136
+ # noinspection PyUnusedLocal
137
+ def _showTree(self, e=None):
138
+ g = self._protocol.getStepsGraph()
139
+ w = pwgui.Window("Protocol steps", self, minsize=(800, 600))
140
+ root = w.root
141
+ canvas = pwgui.Canvas(root, width=600, height=500,
142
+ tooltipCallback=self._stepTooltip,)
143
+ canvas.grid(row=0, column=0, sticky='nsew')
144
+ canvas.drawGraph(g, pwgui.LevelTreeLayout())
145
+ w.show()
146
+
147
+ def _stepTooltip(self, tw, item):
148
+ """ Create the contents of the tooltip to be displayed
149
+ for the given step.
150
+ Params:
151
+ tw: a tk.TopLevel instance (ToolTipWindow)
152
+ item: the selected step.
153
+ """
154
+
155
+ if not hasattr(item.node, 'step'):
156
+ return
157
+
158
+ step = item.node.step
159
+
160
+ tm = str(step.funcName)
161
+
162
+ if not hasattr(tw, 'tooltipText'):
163
+ frame = tk.Frame(tw)
164
+ frame.grid(row=0, column=0)
165
+ tw.tooltipText = pwgui.dialog.createMessageBody(
166
+ frame, tm, None, textPad=0, textBg=pwutils.Color.ALT_COLOR_2)
167
+ tw.tooltipText.config(bd=1, relief=tk.RAISED)
168
+ else:
169
+ pwgui.dialog.fillMessageText(tw.tooltipText, tm)
170
+
171
+