auto-options-python 0.1.4__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.
- auto_options_python-0.1.4.dist-info/METADATA +58 -0
- auto_options_python-0.1.4.dist-info/RECORD +19 -0
- auto_options_python-0.1.4.dist-info/WHEEL +5 -0
- auto_options_python-0.1.4.dist-info/licenses/LICENSE +21 -0
- auto_options_python-0.1.4.dist-info/top_level.txt +2 -0
- autooptions/__init__.py +16 -0
- autooptions/_tests/__init__.py +0 -0
- autooptions/_tests/test_array_util.py +24 -0
- autooptions/_tests/test_napari_util.py +146 -0
- autooptions/_tests/test_options.py +172 -0
- autooptions/_tests/test_qtutil.py +245 -0
- autooptions/_tests/test_widget.py +132 -0
- autooptions/_version.py +24 -0
- autooptions/array_util.py +39 -0
- autooptions/napari_util.py +133 -0
- autooptions/options.py +311 -0
- autooptions/qtutil.py +270 -0
- autooptions/widget.py +370 -0
- scratch/options.py +36 -0
autooptions/options.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import appdirs
|
|
3
|
+
import json
|
|
4
|
+
from copy import copy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Options:
|
|
9
|
+
""" Options are a list of parameters that are intended to be passed to an operation. Options have
|
|
10
|
+
a name, a type and a value.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __init__(self, applicationName, optionsName):
|
|
15
|
+
"""
|
|
16
|
+
Construct an empty Options object for an operation within an application.
|
|
17
|
+
|
|
18
|
+
The options will be saved into a file optionsName in the subfolder applicationName of the user-data-folder.
|
|
19
|
+
Spaces in the names will be replaced by underscores in the folder and filename.
|
|
20
|
+
|
|
21
|
+
:param applicationName: The name of the application
|
|
22
|
+
:param optionsName: The name of the options
|
|
23
|
+
"""
|
|
24
|
+
self.optionsName = optionsName
|
|
25
|
+
self.applicationName = applicationName
|
|
26
|
+
self.items = {}
|
|
27
|
+
self.defaultItems = None
|
|
28
|
+
appName = self.applicationName.replace(' ', '_')
|
|
29
|
+
self.dataFolder = appdirs.user_data_dir(appName)
|
|
30
|
+
os.makedirs(self.dataFolder, exist_ok=True)
|
|
31
|
+
optName = self.optionsName.replace(' ', '_')
|
|
32
|
+
self.optionsPath = os.path.join(self.dataFolder, optName + "_options.json")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def setDefaultValues(self, defaultItems):
|
|
36
|
+
"""
|
|
37
|
+
Set the default values for all options in the options object. Currently unused. The idea is that it might be
|
|
38
|
+
useful to have default values, other than in the code, to which the option can be reset.
|
|
39
|
+
|
|
40
|
+
:param defaultItems: A dictionary of default values for all options in the options object.
|
|
41
|
+
"""
|
|
42
|
+
self.defaultItems = defaultItems
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def getItems(self):
|
|
46
|
+
"""
|
|
47
|
+
Return the items stored in the options object.
|
|
48
|
+
|
|
49
|
+
:return: A dictionary of all options in the options object.
|
|
50
|
+
"""
|
|
51
|
+
if not self.items and self.defaultItems:
|
|
52
|
+
self.items = copy(self.defaultItems)
|
|
53
|
+
return self.items
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def save(self):
|
|
57
|
+
"""
|
|
58
|
+
Save the options as a JSON file.
|
|
59
|
+
"""
|
|
60
|
+
with open(self.optionsPath, 'w') as f:
|
|
61
|
+
json.dump(self.getItems(), f)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load(self):
|
|
65
|
+
"""
|
|
66
|
+
Load the options as a JSON file.
|
|
67
|
+
"""
|
|
68
|
+
if not os.path.exists(self.optionsPath):
|
|
69
|
+
self.save()
|
|
70
|
+
with open(self.optionsPath) as f:
|
|
71
|
+
items = json.load(f)
|
|
72
|
+
for key, value in items.items():
|
|
73
|
+
if value['transient']:
|
|
74
|
+
continue
|
|
75
|
+
self.items[key] = value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get(self, name):
|
|
79
|
+
"""
|
|
80
|
+
Answer the option with the given name.
|
|
81
|
+
|
|
82
|
+
:param name: The name of an option
|
|
83
|
+
:return: Answers the option with the given name as a dictionary
|
|
84
|
+
"""
|
|
85
|
+
return self.items[name]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def value(self, name):
|
|
89
|
+
"""
|
|
90
|
+
Answer the value of the option with the given name.
|
|
91
|
+
|
|
92
|
+
:param name: The name of an option
|
|
93
|
+
:return: The value of the option with the given name. The type of the result depends on the type of the option.
|
|
94
|
+
"""
|
|
95
|
+
return self.get(name)['value']
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def set(self, name, option):
|
|
99
|
+
"""
|
|
100
|
+
Set the option in options under the given name.
|
|
101
|
+
|
|
102
|
+
:param name: The name of an option
|
|
103
|
+
:param option: The new option
|
|
104
|
+
"""
|
|
105
|
+
self.items[name] = option
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def setValue(self, name, value):
|
|
109
|
+
"""
|
|
110
|
+
Set the value of the option with the given name to value
|
|
111
|
+
:param name: The name of an option
|
|
112
|
+
:param value: The new value of the option
|
|
113
|
+
"""
|
|
114
|
+
self.get(name)['value'] = value
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def getCallbackName(cls, callback):
|
|
119
|
+
callbackName = None
|
|
120
|
+
if callback:
|
|
121
|
+
callbackName = callback.__name__
|
|
122
|
+
return callbackName
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def addImage(self, name='image', value=None, transient=True, position=None, callback=None):
|
|
126
|
+
"""
|
|
127
|
+
Add an option that represents the selection of an image. Image options are often transient.
|
|
128
|
+
|
|
129
|
+
:param name: The name of the image option
|
|
130
|
+
:param value: The value of the image option
|
|
131
|
+
:param transient: Whether the image option is transient. Transient options are not saved and reloaded.
|
|
132
|
+
:param position: The position of the image option within the options (Could be used when dictionaries are not
|
|
133
|
+
ordered). If none is given, the next free position is used.
|
|
134
|
+
:param callback: The callback function, that is called when the value of the option is changed.
|
|
135
|
+
"""
|
|
136
|
+
self.set(name, self.getBaseOption(value, transient, position, callback) | {
|
|
137
|
+
'type': 'image'})
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def addLabels(self, name='labels', value=None, transient=True, position=None, callback=None):
|
|
141
|
+
"""
|
|
142
|
+
Add an option that represents the selection of a labels image. Labels options are often transient.
|
|
143
|
+
|
|
144
|
+
:param name: The name of the labels option
|
|
145
|
+
:param value: The value of the labels option
|
|
146
|
+
:param transient: Whether the labels option is transient. Transient options are not saved and reloaded.
|
|
147
|
+
:param position: The position of the labels option within the options (Could be used when dictionaries are not
|
|
148
|
+
ordered). If none is given, the next free position is used.
|
|
149
|
+
:param callback: The callback function, that is called when the value of the option is changed.
|
|
150
|
+
"""
|
|
151
|
+
self.set(name, self.getBaseOption(value, transient, position, callback) | {
|
|
152
|
+
'type': 'labels'})
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def addFFT(self, name='fft', value=None, transient=True, position=None, callback=None):
|
|
156
|
+
"""
|
|
157
|
+
Add an option that represents the selection of an FFT image. Image options are often transient. The FFT
|
|
158
|
+
is not a standard image, since only the amplitude information is in the image, while the phase information is
|
|
159
|
+
kept in the metadata.
|
|
160
|
+
|
|
161
|
+
:param name: The name of the option
|
|
162
|
+
:param value: The name of the fft layer
|
|
163
|
+
:param transient: Whether the fft option is transient. Transient options are not saved and reloaded.
|
|
164
|
+
:param position: The position of the fft option within the options. (Could be used when dictionaries are not
|
|
165
|
+
ordered). If none is given, the next free position is used.
|
|
166
|
+
:param callback: A callback function, that is called when the selected fft layer changes
|
|
167
|
+
"""
|
|
168
|
+
self.set(name, self.getBaseOption(value, transient, position, callback) | {
|
|
169
|
+
'type': 'fft'})
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def addPoints(self, name='points', value=None, transient=True, position=None, callback=None):
|
|
173
|
+
"""
|
|
174
|
+
Add an option that represents the selection of a points layer. Points options are often transient.
|
|
175
|
+
|
|
176
|
+
:param name: The name of the points option
|
|
177
|
+
:param value: The value of the points option
|
|
178
|
+
:param transient: Whether the points option is transient. Transient options are not saved and reloaded.
|
|
179
|
+
:param position: The position of the points option within the options (Could be used when dictionaries are not
|
|
180
|
+
ordered). If none is given, the next free position is used.
|
|
181
|
+
:param callback: The callback function, that is called when the value of the option is changed.
|
|
182
|
+
"""
|
|
183
|
+
self.set(name, self.getBaseOption(value, transient, position, callback) | {
|
|
184
|
+
'type': 'points'})
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def addInt(self, name, value=1, transient=False, position=None, widget="input", callback=None):
|
|
188
|
+
"""
|
|
189
|
+
An option that represents an integer value.
|
|
190
|
+
|
|
191
|
+
:param name: The name of the option
|
|
192
|
+
:param value: The integer value
|
|
193
|
+
:param transient: Whether the option is transient. Transient options are not saved and reloaded.
|
|
194
|
+
:param position: The position of the option within the options. (Could be used when dictionaries are not
|
|
195
|
+
ordered). If none is given, the next free position is used.
|
|
196
|
+
:param widget: Currently only the input-widget, which means entering the number into an input field, is
|
|
197
|
+
implemented. However, an integer could also be entered in a different way, for example using
|
|
198
|
+
a slider.
|
|
199
|
+
:param callback: A callback function, that is called when the value is changed via the graphical interface.
|
|
200
|
+
"""
|
|
201
|
+
self.set(name, self.getBaseOption(value, transient, position, callback) | {
|
|
202
|
+
'type': 'int',
|
|
203
|
+
'widget': widget})
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def addFloat(self, name, value=0.0, transient=False, position=None, widget="input", callback=None):
|
|
207
|
+
"""
|
|
208
|
+
An option that represents a float value.
|
|
209
|
+
|
|
210
|
+
:param name: The name of the option
|
|
211
|
+
:param value: The float value
|
|
212
|
+
:param transient: Whether the option is transient. Transient options are not saved and reloaded.
|
|
213
|
+
:param position: The position of the option within the options. (Could be used when dictionaries are not
|
|
214
|
+
ordered). If none is given, the next free position is used.
|
|
215
|
+
:param widget: Currently only the input-widget, which means entering the number into an input field, is
|
|
216
|
+
implemented. However, a float could also be entered in a different way, for example using
|
|
217
|
+
a slider.
|
|
218
|
+
:param callback: A callback function, that is called when the value is changed via the graphical interface.
|
|
219
|
+
"""
|
|
220
|
+
self.set(name, self.getBaseOption(value, transient, position, callback) | {
|
|
221
|
+
'type': 'float',
|
|
222
|
+
'widget': widget})
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def addChoice(self, name, value=None, choices=None, transient=False, position=None, callback=None):
|
|
226
|
+
"""
|
|
227
|
+
An option that represents a choice in a list of given values.
|
|
228
|
+
|
|
229
|
+
:param name: The name of the option
|
|
230
|
+
:param value: The text of the selected choice
|
|
231
|
+
:param choices: A collection of possible values
|
|
232
|
+
:param transient: Whether the option is transient. Transient options are not saved and reloaded.
|
|
233
|
+
:param position: The position of the option within the options. (Could be used when dictionaries are not
|
|
234
|
+
ordered). If none is given, the next free position is used.
|
|
235
|
+
:param callback: A callback function, that is called whenever the selected item is changed, whether via the gui
|
|
236
|
+
or programmatically. The implementer must take care to avoid endless loops.
|
|
237
|
+
"""
|
|
238
|
+
if not choices:
|
|
239
|
+
choices = []
|
|
240
|
+
self.set(name, self.getBaseOption(value, transient, position, callback) | {
|
|
241
|
+
'type': 'choice',
|
|
242
|
+
'choices': choices})
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def addStr(self, name, value="", transient=False, position=None, callback=None):
|
|
246
|
+
"""
|
|
247
|
+
An option that represents a textual value.
|
|
248
|
+
|
|
249
|
+
:param name: The name of the option
|
|
250
|
+
:param value: The text
|
|
251
|
+
:param transient: Whether the option is transient. Transient options are not saved and reloaded.
|
|
252
|
+
:param position: The position of the option within the options. (Could be used when dictionaries are not
|
|
253
|
+
ordered). If none is given, the next free position is used.
|
|
254
|
+
:param callback: A callback function, that is called when the value is changed via the graphical interface.
|
|
255
|
+
"""
|
|
256
|
+
self.set(name,
|
|
257
|
+
self.getBaseOption(value, transient, position, callback) | {
|
|
258
|
+
'type': 'str',
|
|
259
|
+
'widget': "input"})
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def addBool(self, name, value=False, transient=False, position=None, callback=None):
|
|
263
|
+
"""
|
|
264
|
+
An option that represents a binary choice.
|
|
265
|
+
|
|
266
|
+
:param name: The name of the option
|
|
267
|
+
:param value: The boolean value, True or False
|
|
268
|
+
:param transient: Whether the option is transient. Transient options are not saved and reloaded.
|
|
269
|
+
:param position: The position of the option within the options. (Could be used when dictionaries are not
|
|
270
|
+
ordered). If none is given, the next free position is used.
|
|
271
|
+
:param callback: A callback function, that is called whenever the selected item is changed, whether via the gui
|
|
272
|
+
or programmatically. The implementer must take care to avoid endless loops.
|
|
273
|
+
"""
|
|
274
|
+
self.set(name,
|
|
275
|
+
self.getBaseOption(value, transient, position, callback) | {
|
|
276
|
+
'type': 'bool',
|
|
277
|
+
'widget': "checkbox"})
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def getBaseOption(self, value, transient, position, callback):
|
|
281
|
+
"""
|
|
282
|
+
A helper method to set the parts of an option that a common to all kinds of options.
|
|
283
|
+
|
|
284
|
+
:param value: The value of the option
|
|
285
|
+
:param transient: Whether the option is transient. Transient options are not saved and reloaded.
|
|
286
|
+
:param position: The position of the option within the options. (Could be used when dictionaries are not
|
|
287
|
+
ordered). If none is given, the next free position is used.
|
|
288
|
+
:param callback: A callback function, that is called when the value of the option changes.
|
|
289
|
+
"""
|
|
290
|
+
option = {'value': value,
|
|
291
|
+
'transient': transient,
|
|
292
|
+
'position': self._getPosition(position),
|
|
293
|
+
'callback': self.getCallbackName(callback)}
|
|
294
|
+
return option
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _getPosition(self, position):
|
|
298
|
+
"""
|
|
299
|
+
If the position is None, the next free position is returned. The next free position is the
|
|
300
|
+
maximal position plus one. If position is not None, the given position is returned.
|
|
301
|
+
|
|
302
|
+
:param position: None or a non-negative integer
|
|
303
|
+
:return: A non-negative integer, which is either the given position or the next free position
|
|
304
|
+
"""
|
|
305
|
+
if not position:
|
|
306
|
+
positions = [option["position"] for option in self.items.values()]
|
|
307
|
+
if positions:
|
|
308
|
+
position = max([option["position"] for option in self.items.values()]) + 1
|
|
309
|
+
else:
|
|
310
|
+
position = 1
|
|
311
|
+
return position
|
autooptions/qtutil.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
import pyperclip
|
|
3
|
+
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
from qtpy.QtWidgets import QHBoxLayout, QCheckBox
|
|
6
|
+
from qtpy.QtCore import Qt
|
|
7
|
+
from qtpy.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout
|
|
8
|
+
from qtpy.QtWidgets import QLabel, QLineEdit, QComboBox, QTableWidget, QTableWidgetItem, QAction
|
|
9
|
+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
10
|
+
from napari.utils import notifications
|
|
11
|
+
from autooptions.array_util import ArrayUtil
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import napari
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WidgetTool:
|
|
18
|
+
"""
|
|
19
|
+
Utility methods for working with qt-widgets.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def getLineInput(parent, labelText, defaultValue, fieldWidth, callback):
|
|
24
|
+
"""Returns a label displaying the given text and an input field
|
|
25
|
+
with the given default value.
|
|
26
|
+
|
|
27
|
+
:param parent: The parent widget of the label and the input field
|
|
28
|
+
:param labelText: The text of the label
|
|
29
|
+
:param defaultValue: The value initailly displayed in the input field
|
|
30
|
+
:param fieldWidth: The width of the input field
|
|
31
|
+
:param callback: A callback function with a parameter text. The function
|
|
32
|
+
is called with the new text when the content of the
|
|
33
|
+
input field changes
|
|
34
|
+
:return: A tupel of the label and the input field
|
|
35
|
+
:rtype: (QLabel, QLineEdit)
|
|
36
|
+
"""
|
|
37
|
+
label = QLabel(parent)
|
|
38
|
+
label.setText(labelText)
|
|
39
|
+
inputWidget = QLineEdit(parent)
|
|
40
|
+
inputWidget.setText(str(defaultValue))
|
|
41
|
+
if callback:
|
|
42
|
+
inputWidget.textEdited.connect(callback)
|
|
43
|
+
inputWidget.setMaximumWidth(fieldWidth)
|
|
44
|
+
return label, inputWidget
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def getComboInput(parent, labelText, values, callback=None):
|
|
49
|
+
"""Returns a label displaying the given text and a combo-box
|
|
50
|
+
with the given values.
|
|
51
|
+
|
|
52
|
+
:param parent: The parent widget of the label and the input field
|
|
53
|
+
:param labelText: The text of the label
|
|
54
|
+
:param values: The values in the list of the combo-box
|
|
55
|
+
:param callback: A callback function that is called with the new text when the selected text changes.
|
|
56
|
+
:return: A tupel of the label and the input field
|
|
57
|
+
:rtype: (QLabel, QComboBox)
|
|
58
|
+
"""
|
|
59
|
+
label = QLabel(parent)
|
|
60
|
+
label.setText(labelText)
|
|
61
|
+
inputCombo = QComboBox(parent)
|
|
62
|
+
inputCombo.addItems(values)
|
|
63
|
+
if callback:
|
|
64
|
+
inputCombo.currentTextChanged.connect(callback)
|
|
65
|
+
return label, inputCombo
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def replaceItemsInComboBox(comboBox, newItems):
|
|
70
|
+
"""Replace the items in the combo-box with newItems
|
|
71
|
+
|
|
72
|
+
:param comboBox: The combo-box in which the items will be replaced
|
|
73
|
+
:param newItems: The new items that will replace the current items
|
|
74
|
+
in the combo-box.
|
|
75
|
+
"""
|
|
76
|
+
selectedText = comboBox.currentText()
|
|
77
|
+
comboBox.clear()
|
|
78
|
+
comboBox.addItems(newItems)
|
|
79
|
+
index = -1
|
|
80
|
+
try:
|
|
81
|
+
index = newItems.index(selectedText)
|
|
82
|
+
except ValueError:
|
|
83
|
+
index = -1
|
|
84
|
+
if index > -1:
|
|
85
|
+
comboBox.setCurrentIndex(index)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def getCheckbox(parent, labelText, defaultValue, fieldWidth, callback):
|
|
90
|
+
"""Answers a label and a checkbox checked or unchecked depeding on the default value.
|
|
91
|
+
|
|
92
|
+
:param parent: The parent widget of the label and the input field
|
|
93
|
+
:param labelText: The text of the label
|
|
94
|
+
:param defaultValue: The boolean default value
|
|
95
|
+
:param fieldWidth: The maximum width of the checkbox
|
|
96
|
+
:param callback: A callback function
|
|
97
|
+
:return: A tupel of a label and a checkbox
|
|
98
|
+
"""
|
|
99
|
+
label = QLabel(parent)
|
|
100
|
+
label.setText(labelText)
|
|
101
|
+
cb = QCheckBox(parent)
|
|
102
|
+
cb.setChecked(defaultValue)
|
|
103
|
+
if callback:
|
|
104
|
+
cb.stateChanged.connect(callback)
|
|
105
|
+
cb.setMaximumWidth(fieldWidth)
|
|
106
|
+
return label, cb
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TableView(QTableWidget):
|
|
111
|
+
""" A table that allows to copy the selected cells to the system-clipboard.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, data, *args):
|
|
115
|
+
"""Create a new table from data.
|
|
116
|
+
|
|
117
|
+
:param data: A dictionary with the column names as keys and the data
|
|
118
|
+
in the columns as lists.
|
|
119
|
+
"""
|
|
120
|
+
rows = 0
|
|
121
|
+
columns = 0
|
|
122
|
+
if data:
|
|
123
|
+
columns = len(data)
|
|
124
|
+
rows = len(list(data.values())[0])
|
|
125
|
+
QTableWidget.__init__(self, rows, columns, *args)
|
|
126
|
+
self.data = data
|
|
127
|
+
if data:
|
|
128
|
+
self.__setData()
|
|
129
|
+
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
|
130
|
+
copyAction = QAction("Copy\tCtrl+C", self)
|
|
131
|
+
copyAction.triggered.connect(self.copyDataToClipboard)
|
|
132
|
+
self.addAction(copyAction)
|
|
133
|
+
self.resetAction = QAction("Reset", self)
|
|
134
|
+
self.addAction(self.resetAction)
|
|
135
|
+
self.deleteAction = QAction("Delete", self)
|
|
136
|
+
self.addAction(self.deleteAction)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def setData(self, table):
|
|
140
|
+
"""Clear the table and replace the data in the table with the input table"""
|
|
141
|
+
self.data = table
|
|
142
|
+
self.resetView()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def resetView(self):
|
|
146
|
+
"""Resets the view to the data of the table view"""
|
|
147
|
+
self.clear()
|
|
148
|
+
self.__setData()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def __setData(self):
|
|
152
|
+
horizontalHeaders = []
|
|
153
|
+
for n, key in enumerate(self.data.keys()):
|
|
154
|
+
horizontalHeaders.append(key)
|
|
155
|
+
for m, item in enumerate(self.data[key]):
|
|
156
|
+
newItem = QTableWidgetItem(str(item))
|
|
157
|
+
newItem.setTextAlignment(Qt.AlignRight)
|
|
158
|
+
self.setItem(m, n, newItem)
|
|
159
|
+
self.setHorizontalHeaderLabels(horizontalHeaders)
|
|
160
|
+
self.resizeColumnsToContents()
|
|
161
|
+
self.resizeRowsToContents()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def keyPressEvent(self, event):
|
|
165
|
+
"""Copy the selected table data to the system clipboard if the key-event
|
|
166
|
+
is ctrl+C.
|
|
167
|
+
|
|
168
|
+
:param event: The received key-pressed event.
|
|
169
|
+
"""
|
|
170
|
+
super().keyPressEvent(event)
|
|
171
|
+
if event.key() == Qt.Key_C and (event.modifiers() & Qt.ControlModifier):
|
|
172
|
+
self.copyDataToClipboard()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def copyDataToClipboard(self):
|
|
176
|
+
""" Copy the data in the selected table-cells into the system clipboard.
|
|
177
|
+
"""
|
|
178
|
+
notifications.show_info("copying data to clipboard")
|
|
179
|
+
tableDataAsText = self.getSelectedDataAsString()
|
|
180
|
+
pyperclip.copy(tableDataAsText)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def getSelectedDataAsString(self):
|
|
184
|
+
""" Get the data in the selected cells as a string. Columns are
|
|
185
|
+
separated by tabs and lines by newlines".
|
|
186
|
+
"""
|
|
187
|
+
copied_cells = self.selectedIndexes()
|
|
188
|
+
if len(copied_cells) == 0:
|
|
189
|
+
return ""
|
|
190
|
+
labels = [self.horizontalHeaderItem(id).text() for id in range(0, self.columnCount())]
|
|
191
|
+
data = [['' for i in range(self.columnCount())] for j in range(self.rowCount())]
|
|
192
|
+
for cell in copied_cells:
|
|
193
|
+
data[cell.row()][cell.column()] = cell.data()
|
|
194
|
+
table = np.array(data)
|
|
195
|
+
table, columnIndices, _ = ArrayUtil.stripZeroRowsAndColumns(table, zero='')
|
|
196
|
+
lines = ''
|
|
197
|
+
for row in table:
|
|
198
|
+
lines = lines + "\t".join([str(elem) for elem in row]) + "\n"
|
|
199
|
+
lines = lines[:-1]
|
|
200
|
+
remainingHeadings = [labels[index] for index in columnIndices]
|
|
201
|
+
result = "\t".join(remainingHeadings) + "\n" + lines
|
|
202
|
+
return result
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class PlotWidget(QWidget):
|
|
207
|
+
"""
|
|
208
|
+
A widget that contains a pyplot plot.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def __init__(self, viewer: "napari.viewer.Viewer"):
|
|
212
|
+
"""Create a new empty plot widget.
|
|
213
|
+
|
|
214
|
+
param viewer: The napari viewer in which the widget will be displayed
|
|
215
|
+
"""
|
|
216
|
+
super().__init__()
|
|
217
|
+
self.figure = plt.figure()
|
|
218
|
+
self.ax = self.figure.add_subplot(111)
|
|
219
|
+
self.canvas = FigureCanvas(self.figure)
|
|
220
|
+
self.viewer = viewer
|
|
221
|
+
self.createLayout()
|
|
222
|
+
self.formatStrings = []
|
|
223
|
+
self.title = "Plot"
|
|
224
|
+
self.X = []
|
|
225
|
+
self.Y = []
|
|
226
|
+
self.area = 'left'
|
|
227
|
+
self.tabify = True
|
|
228
|
+
self.xLabel = "x"
|
|
229
|
+
self.yLabel = "y"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def createLayout(self):
|
|
233
|
+
"""Create the layout of the widget.
|
|
234
|
+
"""
|
|
235
|
+
mainLayout = QVBoxLayout()
|
|
236
|
+
canvasLayout = QHBoxLayout()
|
|
237
|
+
canvasLayout.addWidget(self.canvas)
|
|
238
|
+
mainLayout.addLayout(canvasLayout)
|
|
239
|
+
self.setLayout(mainLayout)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def addData(self, X, Y, formatString=None):
|
|
243
|
+
"""Add the data and the format string.
|
|
244
|
+
"""
|
|
245
|
+
self.X.append(X)
|
|
246
|
+
self.Y.append(Y)
|
|
247
|
+
if formatString:
|
|
248
|
+
self.formatStrings.append(formatString)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def clear(self):
|
|
252
|
+
"""Remove everything from the plot.
|
|
253
|
+
"""
|
|
254
|
+
self.figure.clear()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def display(self):
|
|
258
|
+
"""Display the plot as a dock widget in napari.
|
|
259
|
+
"""
|
|
260
|
+
self.ax.set_xlabel(self.xLabel)
|
|
261
|
+
self.ax.set_ylabel(self.yLabel)
|
|
262
|
+
if self.formatStrings:
|
|
263
|
+
for x, y, plotFormat in zip(self.X, self.Y, self.formatStrings):
|
|
264
|
+
self.ax.plot(x, y, plotFormat)
|
|
265
|
+
else:
|
|
266
|
+
for x, y in zip(self.X, self.Y):
|
|
267
|
+
self.ax.plot(x, y)
|
|
268
|
+
self.canvas.draw()
|
|
269
|
+
self.viewer.window.add_dock_widget(self, area=self.area, name=self.title, tabify=self.tabify)
|
|
270
|
+
|