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.
- pyworkflow/apps/__init__.py +29 -0
- pyworkflow/apps/pw_manager.py +37 -0
- pyworkflow/apps/pw_plot.py +51 -0
- pyworkflow/apps/pw_project.py +113 -0
- pyworkflow/apps/pw_protocol_list.py +143 -0
- pyworkflow/apps/pw_protocol_run.py +51 -0
- pyworkflow/apps/pw_run_tests.py +267 -0
- pyworkflow/apps/pw_schedule_run.py +322 -0
- pyworkflow/apps/pw_sleep.py +37 -0
- pyworkflow/apps/pw_sync_data.py +439 -0
- pyworkflow/apps/pw_viewer.py +78 -0
- pyworkflow/constants.py +1 -1
- pyworkflow/gui/__init__.py +36 -0
- pyworkflow/gui/browser.py +760 -0
- pyworkflow/gui/canvas.py +1190 -0
- pyworkflow/gui/dialog.py +979 -0
- pyworkflow/gui/form.py +2726 -0
- pyworkflow/gui/graph.py +247 -0
- pyworkflow/gui/graph_layout.py +271 -0
- pyworkflow/gui/gui.py +566 -0
- pyworkflow/gui/matplotlib_image.py +233 -0
- pyworkflow/gui/plotter.py +247 -0
- pyworkflow/gui/project/__init__.py +25 -0
- pyworkflow/gui/project/base.py +192 -0
- pyworkflow/gui/project/constants.py +139 -0
- pyworkflow/gui/project/labels.py +205 -0
- pyworkflow/gui/project/project.py +491 -0
- pyworkflow/gui/project/searchprotocol.py +238 -0
- pyworkflow/gui/project/searchrun.py +181 -0
- pyworkflow/gui/project/steps.py +171 -0
- pyworkflow/gui/project/utils.py +332 -0
- pyworkflow/gui/project/variables.py +179 -0
- pyworkflow/gui/project/viewdata.py +472 -0
- pyworkflow/gui/project/viewprojects.py +510 -0
- pyworkflow/gui/project/viewprotocols.py +2116 -0
- pyworkflow/gui/project/viewprotocols_extra.py +562 -0
- pyworkflow/gui/text.py +771 -0
- pyworkflow/gui/tooltip.py +185 -0
- pyworkflow/gui/tree.py +684 -0
- pyworkflow/gui/widgets.py +307 -0
- pyworkflow/mapper/__init__.py +26 -0
- pyworkflow/mapper/mapper.py +222 -0
- pyworkflow/mapper/sqlite.py +1581 -0
- pyworkflow/mapper/sqlite_db.py +145 -0
- pyworkflow/project/__init__.py +31 -0
- pyworkflow/project/config.py +454 -0
- pyworkflow/project/manager.py +180 -0
- pyworkflow/project/project.py +2095 -0
- pyworkflow/project/usage.py +165 -0
- pyworkflow/protocol/__init__.py +38 -0
- pyworkflow/protocol/bibtex.py +48 -0
- pyworkflow/protocol/constants.py +87 -0
- pyworkflow/protocol/executor.py +483 -0
- pyworkflow/protocol/hosts.py +317 -0
- pyworkflow/protocol/launch.py +277 -0
- pyworkflow/protocol/package.py +42 -0
- pyworkflow/protocol/params.py +781 -0
- pyworkflow/protocol/protocol.py +2707 -0
- pyworkflow/tests/__init__.py +29 -0
- pyworkflow/tests/test_utils.py +25 -0
- pyworkflow/tests/tests.py +341 -0
- pyworkflow/utils/__init__.py +38 -0
- pyworkflow/utils/dataset.py +414 -0
- pyworkflow/utils/echo.py +104 -0
- pyworkflow/utils/graph.py +169 -0
- pyworkflow/utils/log.py +293 -0
- pyworkflow/utils/path.py +528 -0
- pyworkflow/utils/process.py +153 -0
- pyworkflow/utils/profiler.py +92 -0
- pyworkflow/utils/progressbar.py +154 -0
- pyworkflow/utils/properties.py +617 -0
- pyworkflow/utils/reflection.py +129 -0
- pyworkflow/utils/utils.py +880 -0
- pyworkflow/utils/which.py +229 -0
- pyworkflow/webservices/__init__.py +8 -0
- pyworkflow/webservices/config.py +8 -0
- pyworkflow/webservices/notifier.py +152 -0
- pyworkflow/webservices/repository.py +59 -0
- pyworkflow/webservices/workflowhub.py +74 -0
- pyworkflowtests/tests/__init__.py +0 -0
- pyworkflowtests/tests/test_canvas.py +72 -0
- pyworkflowtests/tests/test_domain.py +45 -0
- pyworkflowtests/tests/test_logs.py +74 -0
- pyworkflowtests/tests/test_mappers.py +392 -0
- pyworkflowtests/tests/test_object.py +507 -0
- pyworkflowtests/tests/test_project.py +42 -0
- pyworkflowtests/tests/test_protocol_execution.py +146 -0
- pyworkflowtests/tests/test_protocol_export.py +78 -0
- pyworkflowtests/tests/test_protocol_output.py +158 -0
- pyworkflowtests/tests/test_streaming.py +47 -0
- pyworkflowtests/tests/test_utils.py +210 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/METADATA +2 -2
- scipion_pyworkflow-3.11.1.dist-info/RECORD +161 -0
- scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/WHEEL +0 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/entry_points.txt +0 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/licenses/LICENSE.txt +0 -0
- {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
|
+
|