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,233 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
|
4
|
+
# *
|
5
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
6
|
+
# *
|
7
|
+
# * This program is free software; you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation; either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program; if not, write to the Free Software
|
19
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
20
|
+
# * 02111-1307 USA
|
21
|
+
# *
|
22
|
+
# * All comments concerning this program package may be sent to the
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
24
|
+
# *
|
25
|
+
# **************************************************************************
|
26
|
+
"""
|
27
|
+
Several Image tools using Matplotlib.
|
28
|
+
"""
|
29
|
+
|
30
|
+
import tkinter as tk
|
31
|
+
import matplotlib
|
32
|
+
import numpy as np
|
33
|
+
import pyworkflow as pw
|
34
|
+
from pyworkflow import TK_GRAY_DEFAULT
|
35
|
+
|
36
|
+
try:
|
37
|
+
matplotlib.use('TkAgg')
|
38
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
39
|
+
import matplotlib.pyplot as plt
|
40
|
+
except ImportError:
|
41
|
+
plt = None
|
42
|
+
|
43
|
+
from matplotlib.figure import Figure
|
44
|
+
import matplotlib.cm as cm
|
45
|
+
from matplotlib.patches import Wedge
|
46
|
+
import copy
|
47
|
+
|
48
|
+
|
49
|
+
class FigureFrame(tk.Frame):
|
50
|
+
""" Create a Tk Frame that will contains a
|
51
|
+
Matplotlib Figure.
|
52
|
+
**kwargs arguments will be passed to Figure constructor.
|
53
|
+
Valid options are:
|
54
|
+
figsize = (xdim, ydim)
|
55
|
+
dpi = value
|
56
|
+
frameon = (True|False)
|
57
|
+
"""
|
58
|
+
def __init__(self, parent, **kwargs):
|
59
|
+
tk.Frame.__init__(self, parent)
|
60
|
+
self.figure = Figure(**kwargs)
|
61
|
+
self.canvas = FigureCanvasTkAgg(self.figure, master=self)
|
62
|
+
self.canvas.get_tk_widget().grid(column=0, row=0)
|
63
|
+
|
64
|
+
def getFigure(self):
|
65
|
+
return self.figure
|
66
|
+
|
67
|
+
def getCanvas(self):
|
68
|
+
return self.canvas
|
69
|
+
|
70
|
+
|
71
|
+
class Preview(tk.Frame):
|
72
|
+
# def __init__(self, parent, dim, dpi=36, label=None):
|
73
|
+
def __init__(self, parent, dim, dpi=36, label=None, col=0, row=0, listenersDict=None):
|
74
|
+
tk.Frame.__init__(self, parent)
|
75
|
+
self.dim = dim
|
76
|
+
self.bg = np.zeros((int(dim), int(dim)), float)
|
77
|
+
ddim = dim/dpi
|
78
|
+
self.figure = Figure(figsize=(ddim, ddim), dpi=dpi, frameon=False)
|
79
|
+
self.canvas = FigureCanvasTkAgg(self.figure, master=self)
|
80
|
+
self.canvas.get_tk_widget().grid(column=0, row=0) # , sticky=(N, W, E, S))
|
81
|
+
self.canvas.get_tk_widget().config(bg=TK_GRAY_DEFAULT)
|
82
|
+
if label:
|
83
|
+
self.label = tk.Label(self, text=label)
|
84
|
+
self.label.grid(column=0, row=1)
|
85
|
+
self._createAxes()
|
86
|
+
|
87
|
+
if listenersDict is not None:
|
88
|
+
for bindingKey, callback in listenersDict.items():
|
89
|
+
self.canvas.get_tk_widget().bind(bindingKey, callback)
|
90
|
+
|
91
|
+
def setLabel(self, text):
|
92
|
+
self.label.config(text=text)
|
93
|
+
|
94
|
+
def setWindowTitle(self, title):
|
95
|
+
""" Set window title"""
|
96
|
+
self.canvas.set_window_title(title)
|
97
|
+
|
98
|
+
def _createAxes(self):
|
99
|
+
""" Should be implemented in subclasses. """
|
100
|
+
pass
|
101
|
+
|
102
|
+
def _update(self, *args):
|
103
|
+
""" Should be implemented in subclasses. """
|
104
|
+
pass
|
105
|
+
|
106
|
+
def clear(self):
|
107
|
+
self._update(self.bg)
|
108
|
+
|
109
|
+
def updateData(self, Z):
|
110
|
+
self.clear()
|
111
|
+
self._update(Z)
|
112
|
+
|
113
|
+
|
114
|
+
class ImagePreview(Preview):
|
115
|
+
def __init__(self, parent, dim, dpi=36, label=None, col=0, listenersDict=None):
|
116
|
+
Preview.__init__(self, parent, dim, dpi, label, col, listenersDict=listenersDict)
|
117
|
+
|
118
|
+
def _createAxes(self):
|
119
|
+
ax = self.figure.add_axes([0, 0, 1, 1], frameon=False)
|
120
|
+
self.figureimg = ax.imshow(self.bg, cmap=cm.gray) # , extent=[-h, h, -h, h])
|
121
|
+
ax.set_axis_off()
|
122
|
+
self.ax = ax
|
123
|
+
|
124
|
+
def _update(self, Z, *args):
|
125
|
+
# Adjust the Z shape according to the current data structure.
|
126
|
+
if Z.ndim == 4 and Z.shape[1] == 1:
|
127
|
+
# Assuming that the second dimension is redundant
|
128
|
+
ZAdjusted = Z[:, 0, :, :]
|
129
|
+
else:
|
130
|
+
ZAdjusted = Z
|
131
|
+
|
132
|
+
# If ZAdjusted is a grayscale image (it has only one layer)
|
133
|
+
if ZAdjusted.ndim == 3 and ZAdjusted.shape[0] == 1:
|
134
|
+
ZAdjusted = ZAdjusted[0, :, :]
|
135
|
+
|
136
|
+
# Checks if ZAdjusted has a form compatible with set_data
|
137
|
+
if ZAdjusted.ndim == 2 or (ZAdjusted.ndim == 3 and (ZAdjusted.shape[2] == 3 or ZAdjusted.shape[2] == 4)):
|
138
|
+
self.figureimg.set_data(ZAdjusted)
|
139
|
+
self.figureimg.autoscale()
|
140
|
+
self.figureimg.set(extent=[0, ZAdjusted.shape[1], 0, ZAdjusted.shape[0]])
|
141
|
+
self.canvas.draw()
|
142
|
+
|
143
|
+
|
144
|
+
class PsdPreview(Preview):
|
145
|
+
def __init__(self, master, dim, lf, hf, dpi=72, label="PSD", listenersDict=None):
|
146
|
+
Preview.__init__(self, master, dim, dpi, label, listenersDict=listenersDict)
|
147
|
+
self.lf = lf
|
148
|
+
self.hf = hf
|
149
|
+
if self.ring:
|
150
|
+
self.createRing()
|
151
|
+
else:
|
152
|
+
self.canvas.draw()
|
153
|
+
|
154
|
+
def _createAxes(self):
|
155
|
+
ax = self.figure.add_axes([0, 0, 1, 1], frameon=False)
|
156
|
+
h = 0.5
|
157
|
+
ax.set_xlim(-h, h)
|
158
|
+
ax.set_ylim(-h, h)
|
159
|
+
ax.tick_params(axis="x", direction="in", pad=-15)
|
160
|
+
ax.tick_params(axis="y", direction="in", pad=-22)
|
161
|
+
ax.grid(True)
|
162
|
+
self.ring = None
|
163
|
+
self.img = ax.imshow(self.bg, cmap=cm.gray, extent=[-h, h, -h, h])
|
164
|
+
self.ax = ax
|
165
|
+
|
166
|
+
def createRing(self):
|
167
|
+
radius = float(self.hf)
|
168
|
+
width = radius - float(self.lf)
|
169
|
+
styleArgs = getWedgeExtraParams(self.dim / 2)
|
170
|
+
self.ring = Wedge((0, 0), radius, 0, 360, width=width, **styleArgs) # Full ring
|
171
|
+
self.ax.add_patch(self.ring)
|
172
|
+
self.canvas.draw()
|
173
|
+
|
174
|
+
def updateFreq(self, lf, hf):
|
175
|
+
self.lf = lf
|
176
|
+
self.hf = hf
|
177
|
+
if self.ring:
|
178
|
+
self.ring.remove()
|
179
|
+
self.ring = None
|
180
|
+
if self.hf:
|
181
|
+
self.createRing()
|
182
|
+
|
183
|
+
def _update(self, Z):
|
184
|
+
if self.ring:
|
185
|
+
self.ring.remove()
|
186
|
+
self.ring = None
|
187
|
+
if self.hf:
|
188
|
+
self.createRing()
|
189
|
+
self.img.set_data(Z)
|
190
|
+
self.img.autoscale()
|
191
|
+
self.canvas.draw()
|
192
|
+
|
193
|
+
|
194
|
+
class MaskPreview(ImagePreview):
|
195
|
+
def __init__(self, parent, dim, dpi=36, label=None, col=0, listenersDict=None):
|
196
|
+
ImagePreview.__init__(self, parent, dim, dpi, label, col, listenersDict)
|
197
|
+
self.ring = None
|
198
|
+
|
199
|
+
def updateMask(self, outerRadius, innerRadius=0):
|
200
|
+
if self.ring is not None:
|
201
|
+
self.ring.remove()
|
202
|
+
center = self.dim / 2
|
203
|
+
width = outerRadius - innerRadius
|
204
|
+
styleArgs = getWedgeExtraParams(center)
|
205
|
+
self.ring = Wedge((center, center), outerRadius, 0, 360, width=width, **styleArgs) # Full ring
|
206
|
+
self.ax.add_patch(self.ring)
|
207
|
+
self.canvas.draw()
|
208
|
+
|
209
|
+
|
210
|
+
def getPngData(filename):
|
211
|
+
import matplotlib.image as mpimg
|
212
|
+
return mpimg.imread(filename)
|
213
|
+
|
214
|
+
|
215
|
+
def createBgImage(dim):
|
216
|
+
return np.ones((dim, dim, 3))
|
217
|
+
|
218
|
+
|
219
|
+
def getWedgeExtraParams(maxRad):
|
220
|
+
color = pw.Config.getWizardMaskColor()
|
221
|
+
# Face color
|
222
|
+
fcolor = copy.deepcopy(color) # Normalized RGB
|
223
|
+
fca = 0.15 # alpha
|
224
|
+
fcolor.append(fca)
|
225
|
+
# Edge color
|
226
|
+
ecolor = copy.deepcopy(color)
|
227
|
+
eca = 1
|
228
|
+
ecolor.append(eca)
|
229
|
+
return {'linewidth': 0.02 * maxRad, # 2% of the max radius
|
230
|
+
'facecolor': fcolor,
|
231
|
+
'edgecolor': ecolor,
|
232
|
+
'linestyle': '-'}
|
233
|
+
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
|
4
|
+
# *
|
5
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
6
|
+
# *
|
7
|
+
# * This program is free software; you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation; either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program; if not, write to the Free Software
|
19
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
20
|
+
# * 02111-1307 USA
|
21
|
+
# *
|
22
|
+
# * All comments concerning this program package may be sent to the
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
24
|
+
# *
|
25
|
+
# **************************************************************************
|
26
|
+
"""
|
27
|
+
This module implement the classes to create plots on xmipp.
|
28
|
+
"""
|
29
|
+
|
30
|
+
try:
|
31
|
+
import matplotlib.pyplot as plt
|
32
|
+
except ImportError:
|
33
|
+
plt = None
|
34
|
+
|
35
|
+
from pyworkflow.viewer import View
|
36
|
+
|
37
|
+
figureCounter = 0
|
38
|
+
|
39
|
+
|
40
|
+
class Plotter(View):
|
41
|
+
""" Create different types of plots using the matplotlib library. """
|
42
|
+
backend = None
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def setBackend(cls, value):
|
46
|
+
""" Possible values are:
|
47
|
+
- TkAgg for Tkinter
|
48
|
+
- Agg for non-interactive plots.
|
49
|
+
"""
|
50
|
+
plt.switch_backend(value)
|
51
|
+
cls.backend = value
|
52
|
+
|
53
|
+
def __init__(self, x=1, y=1, mainTitle="",
|
54
|
+
figsize=None, dpi=100, windowTitle="",
|
55
|
+
fontsize=8, **kwargs):
|
56
|
+
""" This Plotter class has some utilities to create a Matplotlib figure
|
57
|
+
and add some plots to it.
|
58
|
+
Params:
|
59
|
+
x, y: number of rows and columns of the grid for plots.
|
60
|
+
mainTitle: figure main title.
|
61
|
+
figsize: the size of the figure, if None, it will be guessed from x and y
|
62
|
+
dpi: resolution, 100 by default.
|
63
|
+
windowTitle: title for the whole windows.
|
64
|
+
"""
|
65
|
+
figure = kwargs.get('figure', None)
|
66
|
+
|
67
|
+
if figure == 'active':
|
68
|
+
figure = plt.gcf()
|
69
|
+
|
70
|
+
if self.backend is None:
|
71
|
+
Plotter.setBackend('Agg')
|
72
|
+
|
73
|
+
plt.style.use(kwargs.get('style', 'default'))
|
74
|
+
|
75
|
+
if figure is None:
|
76
|
+
self.tightLayoutOn = True
|
77
|
+
|
78
|
+
if figsize is None: # Set some defaults values
|
79
|
+
if x == 1 and y == 1:
|
80
|
+
figsize = (6, 5)
|
81
|
+
elif x == 1 and y == 2:
|
82
|
+
figsize = (4, 6)
|
83
|
+
elif x == 2 and y == 1:
|
84
|
+
figsize = (6, 4)
|
85
|
+
else:
|
86
|
+
figsize = (8, 6)
|
87
|
+
|
88
|
+
# Create grid
|
89
|
+
# import matplotlib.gridspec as gridspec
|
90
|
+
# self.grid = gridspec.GridSpec(x, y)#, height_ratios=[7,4])
|
91
|
+
# self.grid.update(left=0.15, right=0.95, hspace=0.25, wspace=0.4)#, top=0.8, bottom=0.2)
|
92
|
+
global figureCounter
|
93
|
+
figureCounter += 1
|
94
|
+
self.figure = plt.figure(figureCounter, figsize=figsize, dpi=dpi)
|
95
|
+
# from matplotlib.figure import Figure
|
96
|
+
# self.figure = Figure(figsize=figsize, dpi=dpi)
|
97
|
+
if mainTitle:
|
98
|
+
self.figure.suptitle(mainTitle, fontsize=fontsize + 4)
|
99
|
+
if windowTitle:
|
100
|
+
self.figure.canvas.manager.set_window_title(windowTitle)
|
101
|
+
self.plot_count = 0
|
102
|
+
self.last_subplot = None
|
103
|
+
self.plot_yformat = '%1.2e'
|
104
|
+
else:
|
105
|
+
self.figure = figure
|
106
|
+
self.tightLayoutOn = False
|
107
|
+
self.plot_count = 0
|
108
|
+
|
109
|
+
self.fontsize = fontsize
|
110
|
+
self.plot_title_fontsize = fontsize + 4
|
111
|
+
self.plot_axis_fontsize = fontsize + 2
|
112
|
+
self.plot_text_fontsize = fontsize
|
113
|
+
self.gridx = x
|
114
|
+
self.gridy = y
|
115
|
+
|
116
|
+
def activate(self):
|
117
|
+
""" Activate this figure. """
|
118
|
+
plt.figure(self.figure.number)
|
119
|
+
|
120
|
+
def getCanvas(self):
|
121
|
+
return self.figure.canvas
|
122
|
+
|
123
|
+
def getFigure(self):
|
124
|
+
return self.figure
|
125
|
+
|
126
|
+
def showLegend(self, labels, loc='best'):
|
127
|
+
leg = self.last_subplot.legend(tuple(labels), loc=loc)
|
128
|
+
for t in leg.get_texts():
|
129
|
+
t.set_fontsize(self.plot_axis_fontsize) # the legend text fontsize
|
130
|
+
|
131
|
+
def legend(self, loc='best', **kwargs):
|
132
|
+
self.last_subplot.legend(loc=loc, **kwargs)
|
133
|
+
|
134
|
+
def createSubPlot(self, title, xlabel, ylabel, xpos=None, ypos=None,
|
135
|
+
yformat=False, projection='rectilinear', subtitle=None
|
136
|
+
):
|
137
|
+
"""
|
138
|
+
Create a subplot in the figure.
|
139
|
+
You should provide plot title, and x and y axis labels.
|
140
|
+
yformat True specified the use of global self.plot_yformat
|
141
|
+
Possible values for projection are:
|
142
|
+
'aitoff', 'hammer', 'lambert', 'mollweide', 'polar', 'rectilinear'
|
143
|
+
|
144
|
+
"""
|
145
|
+
if xpos is None:
|
146
|
+
self.plot_count += 1
|
147
|
+
pos = self.plot_count
|
148
|
+
else:
|
149
|
+
pos = xpos + (ypos - 1) * self.gridx
|
150
|
+
a = self.figure.add_subplot(self.gridx, self.gridy, pos, projection=projection)
|
151
|
+
# a.get_label().set_fontsize(12)
|
152
|
+
a.set_title(title, fontsize=self.plot_title_fontsize)
|
153
|
+
|
154
|
+
if subtitle:
|
155
|
+
self.figure.text(0.5, 0.015, subtitle, horizontalalignment="center")
|
156
|
+
|
157
|
+
def setTicksFont(labels):
|
158
|
+
for label in labels:
|
159
|
+
label.set_fontsize(self.plot_text_fontsize) # Set fontsize
|
160
|
+
|
161
|
+
if xlabel is not None:
|
162
|
+
# Axis setup
|
163
|
+
a.set_xlabel(xlabel, fontsize=self.plot_axis_fontsize)
|
164
|
+
a.xaxis.get_label().set_fontsize(self.plot_axis_fontsize)
|
165
|
+
setTicksFont(a.xaxis.get_ticklabels())
|
166
|
+
|
167
|
+
if ylabel is not None:
|
168
|
+
a.set_ylabel(ylabel, fontsize=self.plot_axis_fontsize)
|
169
|
+
|
170
|
+
if yformat:
|
171
|
+
import matplotlib.ticker as ticker
|
172
|
+
formatter = ticker.FormatStrFormatter(self.plot_yformat)
|
173
|
+
a.yaxis.set_major_formatter(formatter)
|
174
|
+
a.yaxis.get_label().set_fontsize(self.plot_axis_fontsize)
|
175
|
+
setTicksFont(a.yaxis.get_ticklabels())
|
176
|
+
|
177
|
+
if xlabel is None and ylabel is None:
|
178
|
+
a.axis('off')
|
179
|
+
|
180
|
+
self.last_subplot = a
|
181
|
+
self.plot = a.plot
|
182
|
+
self.hist = a.hist
|
183
|
+
self.scatterP = a.scatter
|
184
|
+
self.bar = a.bar
|
185
|
+
return a
|
186
|
+
|
187
|
+
def getLastSubPlot(self):
|
188
|
+
return self.last_subplot
|
189
|
+
|
190
|
+
def createCanvas(self):
|
191
|
+
a = self.figure.add_subplot(111, axisbg='g')
|
192
|
+
a.set_axis_off()
|
193
|
+
self.figure.set_facecolor('white')
|
194
|
+
return a
|
195
|
+
|
196
|
+
def getColorBar(self, plot):
|
197
|
+
self.tightLayoutOn = False
|
198
|
+
cax = self.figure.add_axes([0.9, 0.1, 0.03, 0.8])
|
199
|
+
cbar = self.figure.colorbar(plot, cax=cax)
|
200
|
+
cbar.set_ticks(cbar.get_ticks())
|
201
|
+
cbar.ax.invert_yaxis()
|
202
|
+
|
203
|
+
def tightLayout(self):
|
204
|
+
if self.tightLayoutOn:
|
205
|
+
self.activate()
|
206
|
+
plt.tight_layout()
|
207
|
+
|
208
|
+
def show(self, interactive=True, block=False):
|
209
|
+
self.tightLayout()
|
210
|
+
plt.show(block=block)
|
211
|
+
|
212
|
+
def draw(self):
|
213
|
+
self.tightLayout()
|
214
|
+
self.getCanvas().draw()
|
215
|
+
|
216
|
+
def clear(self):
|
217
|
+
self.getFigure().clear()
|
218
|
+
self.plot_count = 0
|
219
|
+
self.last_subplot = None
|
220
|
+
|
221
|
+
def savefig(self, *args, **kwargs):
|
222
|
+
self.tightLayout()
|
223
|
+
self.figure.savefig(*args, **kwargs)
|
224
|
+
|
225
|
+
def isClosed(self):
|
226
|
+
""" Return true if the figure have been closed. """
|
227
|
+
return not plt.fignum_exists(self.figure.number)
|
228
|
+
|
229
|
+
def close(self):
|
230
|
+
""" Close current Plotter figure. """
|
231
|
+
plt.close(self.figure)
|
232
|
+
|
233
|
+
|
234
|
+
def getHexColorList(numberOfColors, colorName='jet'):
|
235
|
+
""" Returns a list of hexColor """
|
236
|
+
from matplotlib import cm, colors
|
237
|
+
|
238
|
+
colorsList = []
|
239
|
+
colorMap = cm.get_cmap(colorName)
|
240
|
+
ratio = colorMap.N / numberOfColors
|
241
|
+
for index in range(numberOfColors):
|
242
|
+
colorPosition = int(round((index * ratio)))
|
243
|
+
rgb = colorMap(colorPosition)[:3]
|
244
|
+
rgbColor = colors.rgb2hex(rgb)
|
245
|
+
colorsList.append(rgbColor)
|
246
|
+
|
247
|
+
return colorsList
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
|
4
|
+
# *
|
5
|
+
# * [1] SciLifeLab, Stockholm University
|
6
|
+
# *
|
7
|
+
# * This program is free software: you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
# *
|
20
|
+
# * All comments concerning this program package may be sent to the
|
21
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
22
|
+
# *
|
23
|
+
# **************************************************************************
|
24
|
+
|
25
|
+
from .project import ProjectManagerWindow, ProjectWindow
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
|
4
|
+
# *
|
5
|
+
# * [1] SciLifeLab, Stockholm University
|
6
|
+
# *
|
7
|
+
# * This program is free software: you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
# *
|
20
|
+
# * All comments concerning this program package may be sent to the
|
21
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
22
|
+
# *
|
23
|
+
# **************************************************************************
|
24
|
+
|
25
|
+
|
26
|
+
import os
|
27
|
+
import webbrowser
|
28
|
+
import tkinter as tk
|
29
|
+
|
30
|
+
import pyworkflow as pw
|
31
|
+
from pyworkflow.gui import Window, Message, Color, getBigFont, defineStyle, ToolTip
|
32
|
+
from pyworkflow.gui.widgets import GradientFrame
|
33
|
+
from pyworkflow.utils.properties import Icon
|
34
|
+
from pyworkflow.gui.project.variables import VariablesDialog
|
35
|
+
|
36
|
+
from .viewprojects import ProjectsView
|
37
|
+
from .viewprotocols import ProtocolsView
|
38
|
+
from .viewdata import ProjectDataView
|
39
|
+
|
40
|
+
VIEW_PROJECTS = Message.VIEW_PROJECTS
|
41
|
+
VIEW_PROTOCOLS = Message.VIEW_PROTOCOLS
|
42
|
+
VIEW_DATA = Message.VIEW_DATA
|
43
|
+
VIEW_LIST = [VIEW_PROTOCOLS, VIEW_DATA]
|
44
|
+
|
45
|
+
|
46
|
+
class ProjectBaseWindow(Window):
|
47
|
+
""" Base windows for Project and Manager GUIs.
|
48
|
+
It extends from Window and add some layout functions (header and footer)
|
49
|
+
"""
|
50
|
+
def __init__(self, title, masterWindow=None, weight=True,
|
51
|
+
minsize=(900, 500), icon=Icon.SCIPION_ICON, **kwargs):
|
52
|
+
Window.__init__(self, title, masterWindow, weight=weight,
|
53
|
+
icon=icon, minsize=minsize, enableQueue=True, **kwargs)
|
54
|
+
|
55
|
+
content = tk.Frame(self.root)
|
56
|
+
content.columnconfigure(0, weight=1)
|
57
|
+
content.rowconfigure(1, weight=1)
|
58
|
+
content.grid(row=0, column=0, sticky='news')
|
59
|
+
self.content = content
|
60
|
+
|
61
|
+
defineStyle()
|
62
|
+
|
63
|
+
if getattr(self, 'menuCfg', None):
|
64
|
+
Window.createMainMenu(self, self.menuCfg)
|
65
|
+
|
66
|
+
self.header = self.createHeaderFrame(content)
|
67
|
+
self.header.grid(row=0, column=0, sticky='new')
|
68
|
+
|
69
|
+
self.footer = tk.Frame(content, bg=pw.Config.SCIPION_BG_COLOR)
|
70
|
+
self.footer.grid(row=1, column=0, sticky='news')
|
71
|
+
|
72
|
+
self.view, self.viewWidget = None, None
|
73
|
+
|
74
|
+
self.viewFuncs = {VIEW_PROJECTS: ProjectsView,
|
75
|
+
VIEW_PROTOCOLS: ProtocolsView,
|
76
|
+
VIEW_DATA: ProjectDataView,
|
77
|
+
}
|
78
|
+
|
79
|
+
def createHeaderFrame(self, parent):
|
80
|
+
|
81
|
+
""" Create the Header frame at the top of the windows.
|
82
|
+
It has (from left to right):
|
83
|
+
- Main application Logo
|
84
|
+
- Project Name
|
85
|
+
- View selection combobox
|
86
|
+
"""
|
87
|
+
header = tk.Frame(parent, bg=pw.Config.SCIPION_BG_COLOR)
|
88
|
+
header.columnconfigure(1, weight=1)
|
89
|
+
header.columnconfigure(2, weight=1)
|
90
|
+
# Create the SCIPION logo label
|
91
|
+
logoImg = self.getImage(Icon.SCIPION_LOGO_SMALL)
|
92
|
+
logoLabel = tk.Label(header, image=logoImg,
|
93
|
+
borderwidth=0, anchor='nw', bg=pw.Config.SCIPION_BG_COLOR)
|
94
|
+
logoLabel.grid(row=0, column=0, sticky='nw', padx=(5, 0), pady=5)
|
95
|
+
version = "%s - %s (core)" % (os.environ.get('SCIPION_VERSION', ""), pw.LAST_VERSION)
|
96
|
+
|
97
|
+
versionLabel = tk.Label(header, text=version,
|
98
|
+
bg=pw.Config.SCIPION_BG_COLOR)
|
99
|
+
versionLabel.grid(row=0, column=1, sticky='sw', pady=20)
|
100
|
+
|
101
|
+
self.customizeheader(header)
|
102
|
+
|
103
|
+
# Create gradient
|
104
|
+
GradientFrame(header, height=8, borderwidth=0).grid(row=1, column=0,
|
105
|
+
columnspan=3,
|
106
|
+
sticky='new')
|
107
|
+
return header
|
108
|
+
|
109
|
+
def customizeheader(self, headerFrame):
|
110
|
+
""" Implement in children classes to customize it: e.g.: Project name in project window"""
|
111
|
+
pass
|
112
|
+
|
113
|
+
def addViewList(self, header):
|
114
|
+
"""Create the view selection frame (Protocols|Data) in the header.
|
115
|
+
"""
|
116
|
+
# This function is called from createHeaderFrame() in ProjectWindow
|
117
|
+
viewFrame = tk.Frame(header, bg=pw.Config.SCIPION_BG_COLOR)
|
118
|
+
viewFrame.grid(row=0, column=2, sticky='se', padx=5, pady=10)
|
119
|
+
|
120
|
+
def addLink(elementText):
|
121
|
+
btn = tk.Label(viewFrame, text=elementText, cursor='hand2',
|
122
|
+
fg=pw.Config.SCIPION_MAIN_COLOR, bg=pw.Config.SCIPION_BG_COLOR)
|
123
|
+
btn.bind('<Button-1>', lambda e: self._viewComboSelected(elementText))
|
124
|
+
return btn
|
125
|
+
|
126
|
+
def addTube():
|
127
|
+
return tk.Label(viewFrame, text="|", fg=pw.Config.SCIPION_MAIN_COLOR, bg=pw.Config.SCIPION_BG_COLOR, padx=5)
|
128
|
+
|
129
|
+
for i, elementText in enumerate(VIEW_LIST):
|
130
|
+
btn = addLink(elementText)
|
131
|
+
btn.grid(row=0, column=i*2)
|
132
|
+
|
133
|
+
if i < len(VIEW_LIST)-1:
|
134
|
+
tube = addTube()
|
135
|
+
tube.grid(row=0, column=(i*2)+1)
|
136
|
+
|
137
|
+
def _viewComboSelected(self, elementText):
|
138
|
+
if elementText != self.view:
|
139
|
+
self.switchView(elementText)
|
140
|
+
|
141
|
+
def switchView(self, newView):
|
142
|
+
# Destroy the previous view if exists:
|
143
|
+
if self.viewWidget:
|
144
|
+
self.viewWidget.grid_forget()
|
145
|
+
self.viewWidget.destroy()
|
146
|
+
# Create the new view
|
147
|
+
self.viewWidget = self.viewFuncs[newView](self.footer, self)
|
148
|
+
# Grid in the second row (1)
|
149
|
+
self.viewWidget.grid(row=0, column=0, columnspan=10, sticky='news')
|
150
|
+
self.footer.rowconfigure(0, weight=1)
|
151
|
+
self.footer.columnconfigure(0, weight=1)
|
152
|
+
self.view = newView
|
153
|
+
|
154
|
+
def getViewWidget(self):
|
155
|
+
return self.viewWidget
|
156
|
+
#
|
157
|
+
# The next functions are callbacks from the menu options.
|
158
|
+
# See how it is done in pyworkflow/gui/gui.py:Window._addMenuChilds()
|
159
|
+
|
160
|
+
def onExit(self):
|
161
|
+
# Project -> Exit
|
162
|
+
self._onClosing()
|
163
|
+
|
164
|
+
def onOnlineHelp(self):
|
165
|
+
# Help -> Online help
|
166
|
+
webbrowser.open_new("https://scipion-em.github.io/docs/")
|
167
|
+
|
168
|
+
def onAbout(self):
|
169
|
+
# Help -> About
|
170
|
+
self.showInfo(
|
171
|
+
"[[http://scipion.i2pc.es/][Scipion]] is an image processing "
|
172
|
+
"framework to obtain 3D models of macromolecular complexes using "
|
173
|
+
"Electron Microscopy.\n\n"
|
174
|
+
"It integrates several software packages with a unified interface. "
|
175
|
+
"This way you can combine them in a single workflow, while all the "
|
176
|
+
"formats and conversions are taken care of automatically.\n\n"
|
177
|
+
"*Scipion* is developed by a multidisciplinary group of engineers, "
|
178
|
+
"physicists, mathematicians, biologists and computer scientists. "
|
179
|
+
"It is produced mainly by people at the "
|
180
|
+
"[[http://biocomputingunit.es//][CNB Biocomputing Unit]], "
|
181
|
+
"[[https://www2.mrc-lmb.cam.ac.uk/][MRC LMB]], "
|
182
|
+
"but with many contributions across the globe.")
|
183
|
+
|
184
|
+
def onContactSupport(self):
|
185
|
+
# Help -> Contact support
|
186
|
+
email = pw.Config.SCIPION_SUPPORT_EMAIL
|
187
|
+
self.showInfo("Please, do contact us at [[mailto:%s][%s]] for any "
|
188
|
+
"feedback, bug, idea, anything that will make Scipion "
|
189
|
+
"better.""" % (email, email))
|
190
|
+
|
191
|
+
def onConfiguration(self):
|
192
|
+
VariablesDialog(self.root)
|