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.
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
+