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.
@@ -0,0 +1,245 @@
1
+ import pyperclip
2
+ from qtpy.QtWidgets import QWidget
3
+ from qtpy.QtGui import QKeyEvent
4
+ from qtpy.QtCore import QEvent
5
+ from qtpy.QtCore import Qt
6
+ from autooptions.qtutil import WidgetTool
7
+ from autooptions.qtutil import TableView
8
+ from autooptions.qtutil import PlotWidget
9
+
10
+
11
+
12
+ COUNTER_TEXT_CHANGED = 0
13
+
14
+
15
+
16
+ class ParentWidget(QWidget):
17
+
18
+
19
+ def __init__(self):
20
+ super().__init__()
21
+ self.setWindowTitle("Test")
22
+
23
+
24
+
25
+ def handleTextChanged(text):
26
+ global COUNTER_TEXT_CHANGED
27
+ COUNTER_TEXT_CHANGED = COUNTER_TEXT_CHANGED + 1
28
+
29
+
30
+ def testGetLineInput(make_napari_viewer_proxy):
31
+ make_napari_viewer_proxy()
32
+ parent = ParentWidget()
33
+ label, inputWidget = WidgetTool.getLineInput(parent,
34
+ "your guess",
35
+ 10,
36
+ 50,
37
+ handleTextChanged)
38
+ assert label in parent.children()
39
+ assert inputWidget in parent.children()
40
+ assert label.parent() == parent
41
+ assert inputWidget.parent() == parent
42
+ assert label.text() == "your guess"
43
+ counter = COUNTER_TEXT_CHANGED
44
+ assert inputWidget.text() == str(10)
45
+ inputWidget.setText("20")
46
+ assert COUNTER_TEXT_CHANGED == counter # Only editing in the interface sends the signal,
47
+ # not programmatically changing the value
48
+ assert inputWidget.maximumWidth() == 50
49
+
50
+
51
+ def testGetLineInputNoCallback(make_napari_viewer_proxy):
52
+ make_napari_viewer_proxy()
53
+ parent = ParentWidget()
54
+ label, inputWidget = WidgetTool.getLineInput(parent,
55
+ "your guess",
56
+ 10,
57
+ 50,
58
+ None)
59
+ assert label in parent.children()
60
+ assert inputWidget in parent.children()
61
+ assert label.parent() == parent
62
+ assert inputWidget.parent() == parent
63
+ assert label.text() == "your guess"
64
+ counter = COUNTER_TEXT_CHANGED
65
+ assert inputWidget.text() == str(10)
66
+ inputWidget.setText("20")
67
+ assert inputWidget.maximumWidth() == 50
68
+ assert COUNTER_TEXT_CHANGED == counter
69
+
70
+
71
+ def testGetComboBox(make_napari_viewer_proxy, mocker):
72
+ make_napari_viewer_proxy()
73
+ parent = ParentWidget()
74
+ callback = mocker.stub(name='onSelectedFruitChanged')
75
+ label, comboWidget = WidgetTool.getComboInput(parent,
76
+ "fruits",
77
+ ["apple", "orange", "banana"],
78
+ callback=callback)
79
+ assert label in parent.children()
80
+ assert comboWidget in parent.children()
81
+ assert label.parent() == parent
82
+ assert comboWidget.parent() == parent
83
+ assert label.text() == "fruits"
84
+ allItems = [comboWidget.itemText(i) for i in range(comboWidget.count())]
85
+ assert allItems == ["apple", "orange", "banana"]
86
+ assert comboWidget.currentText() == "apple"
87
+ comboWidget.setCurrentText("banana")
88
+ assert comboWidget.currentText() == "banana"
89
+ callback.assert_called_once_with("banana")
90
+
91
+
92
+ def testReplaceItemsInComboBox(make_napari_viewer_proxy, mocker):
93
+ make_napari_viewer_proxy()
94
+ parent = ParentWidget()
95
+ label, comboWidget = WidgetTool.getComboInput(parent,
96
+ "fruits",
97
+ ["apple", "orange", "banana"])
98
+ WidgetTool.replaceItemsInComboBox(comboWidget, ["lemon", "ananas"])
99
+ allItems = [comboWidget.itemText(i) for i in range(comboWidget.count())]
100
+ assert allItems == ["lemon", "ananas"]
101
+ assert comboWidget.currentText() == "lemon"
102
+ comboWidget.setCurrentIndex(1)
103
+ WidgetTool.replaceItemsInComboBox(comboWidget, ["apple", "ananas"])
104
+ allItems = [comboWidget.itemText(i) for i in range(comboWidget.count())]
105
+ assert allItems == ["apple", "ananas"]
106
+ assert comboWidget.currentText() == "ananas"
107
+
108
+
109
+ def testGetCheckBox(make_napari_viewer_proxy, mocker):
110
+ make_napari_viewer_proxy()
111
+ parent = ParentWidget()
112
+ callback = mocker.stub(name='onRemoveBackgroundChanged')
113
+ label, checkbox = WidgetTool.getCheckbox(parent,
114
+ "remove background",
115
+ True,
116
+ 50,
117
+ callback)
118
+ assert label in parent.children()
119
+ assert checkbox in parent.children()
120
+ assert checkbox.parent() == parent
121
+ assert label.parent() == parent
122
+ assert label.text() == "remove background"
123
+ assert checkbox.isChecked()
124
+ assert checkbox.maximumWidth() == 50
125
+ checkbox.setChecked(False)
126
+ callback.assert_called_once_with(False)
127
+ label2, checkbox2 = WidgetTool.getCheckbox(parent,
128
+ "smooth",
129
+ False,
130
+ 50,
131
+ None)
132
+ assert not checkbox2.isChecked()
133
+
134
+
135
+
136
+ class TestTableView:
137
+
138
+
139
+ def testConstructor(self, make_napari_viewer_proxy):
140
+ make_napari_viewer_proxy()
141
+ table = {"area": [23.87, 65.28, 12.98], "mean": [19992, 2233, 24553]}
142
+ tableView = TableView(table)
143
+ assert tableView.data == table
144
+ tableView2 = TableView(None)
145
+ assert not tableView2.data
146
+
147
+
148
+ def testSetData(self, make_napari_viewer_proxy):
149
+ make_napari_viewer_proxy()
150
+ table = {"area": [23.87, 65.28, 12.98], "mean": [19992, 2233, 24553]}
151
+ table2 = {"fruits": ["apple", "orange", "banana"], "animals": ["lion", "tiger"]}
152
+ tableView = TableView(table)
153
+ tableView.setData(table2)
154
+ assert tableView.data == table2
155
+
156
+
157
+ def testResetView(self, make_napari_viewer_proxy):
158
+ make_napari_viewer_proxy()
159
+ table = {"area": [23.87, 65.28, 12.98], "mean": [19992, 2233, 24553]}
160
+ tableView = TableView(table)
161
+ tableView.resetView()
162
+ assert tableView.data == table
163
+
164
+
165
+ def testKeyPressEvent(self, make_napari_viewer_proxy):
166
+ pyperclip.copy("")
167
+ make_napari_viewer_proxy()
168
+ table = {"area": [23.87, 65.28, 12.98], "mean": [19992, 2233, 24553]}
169
+ tableView = TableView(table)
170
+ tableView.selectAll()
171
+ event = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_C, Qt.KeyboardModifier.ControlModifier, "copy")
172
+ tableView.keyPressEvent(event)
173
+ text = pyperclip.paste()
174
+ assert "23.87" in text
175
+ assert "24553" in text
176
+
177
+
178
+ def testKeyPressEventNoCopy(self, make_napari_viewer_proxy):
179
+ pyperclip.copy("")
180
+ make_napari_viewer_proxy()
181
+ table = {"area": [23.87, 65.28, 12.98], "mean": [19992, 2233, 24553]}
182
+ tableView = TableView(table)
183
+ tableView.selectAll()
184
+ event = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_C, Qt.KeyboardModifier.ShiftModifier, "copy")
185
+ tableView.keyPressEvent(event)
186
+ text = pyperclip.paste()
187
+ assert text == ""
188
+
189
+
190
+ def testCopyDataToClipboard(self, make_napari_viewer_proxy):
191
+ pyperclip.copy("")
192
+ make_napari_viewer_proxy()
193
+ table = {"area": [23.87, 65.28, 12.98], "mean": [19992, 2233, 24553]}
194
+ tableView = TableView(table)
195
+ tableView.copyDataToClipboard()
196
+ text = pyperclip.paste()
197
+ assert text == ""
198
+ tableView.selectAll()
199
+ tableView.copyDataToClipboard()
200
+ text = pyperclip.paste()
201
+ assert "23.87" in text
202
+ assert "24553" in text
203
+
204
+
205
+ class TestPlotWidget:
206
+
207
+
208
+ def testConstructor(self, make_napari_viewer_proxy):
209
+ viewer = make_napari_viewer_proxy()
210
+ plot = PlotWidget(viewer)
211
+ assert plot.figure
212
+ assert plot.X == []
213
+ assert plot.Y == []
214
+
215
+
216
+ def testAddData(self, make_napari_viewer_proxy):
217
+ viewer = make_napari_viewer_proxy()
218
+ plot = PlotWidget(viewer)
219
+ plot.addData([1, 2, 3], [1, 4, 9], "r+")
220
+ assert [1, 2, 3] in plot.X
221
+ assert [1, 4, 9] in plot.Y
222
+ assert "r+" in plot.formatStrings
223
+
224
+
225
+ def testClearData(self, make_napari_viewer_proxy):
226
+ viewer = make_napari_viewer_proxy()
227
+ plot = PlotWidget(viewer)
228
+ plot.addData([1, 2, 3], [1, 4, 9], "r+")
229
+ plot.clear()
230
+ assert True
231
+
232
+
233
+ def testDisplay(self, make_napari_viewer_proxy):
234
+ viewer = make_napari_viewer_proxy()
235
+ plot = PlotWidget(viewer)
236
+ plot.addData([1, 2, 3], [1, 4, 9], "r+")
237
+ plot.display()
238
+ assert plot.ax.get_xlabel() == plot.xLabel
239
+ assert plot.ax.get_ylabel() == plot.yLabel
240
+ plot2 = PlotWidget(viewer)
241
+ plot2.addData([1, 2, 3], [1, 4, 9])
242
+ plot2.display()
243
+ assert plot2.ax.get_xlabel() == plot2.xLabel
244
+ assert plot2.ax.get_ylabel() == plot2.yLabel
245
+
@@ -0,0 +1,132 @@
1
+ import pytest
2
+ import os
3
+ import numpy as np
4
+ from autooptions.widget import OptionsWidget
5
+ from autooptions.options import Options
6
+
7
+ class FakeEvent:
8
+ modifiers = {}
9
+
10
+
11
+ class TestOptionsWidget:
12
+
13
+
14
+
15
+ def onSomethingHappened(self, value):
16
+ self.lastValue = value
17
+
18
+
19
+ @pytest.fixture()
20
+ def options(self):
21
+ options = Options("Autooptions Test", "Widget Test")
22
+ options.addImage('image', value=None, transient=True)
23
+ options.addLabels('labels', value='labels_layer', transient=True)
24
+ options.addPoints('points', value='points_layer', transient=True)
25
+ options.addFFT('fft', value=None, transient=True)
26
+ options.addStr("group", value="lps")
27
+ options.addInt('size xy', value=3)
28
+ options.addInt('size z', value=1)
29
+ options.addChoice('footprint', choices=["none", "cube", "ball", "octahedron"], callback=self.onSomethingHappened)
30
+ options.addFloat('sigma', value=1.34)
31
+ options.addBool('do it', value=True)
32
+ yield options
33
+
34
+
35
+ def testConstructor(self, options, make_napari_viewer_proxy):
36
+ viewer = make_napari_viewer_proxy()
37
+ widget = OptionsWidget(viewer, options, self)
38
+ assert widget.options is options
39
+ assert widget.viewer is viewer
40
+ assert widget.client is self
41
+
42
+
43
+ def testAddApplyButton(self, options, make_napari_viewer_proxy, mocker):
44
+ viewer = make_napari_viewer_proxy()
45
+ widget = OptionsWidget(viewer, options, self)
46
+ assert widget.getApplyButton() is None
47
+ callback = mocker.stub(name='onApplyButtonPressed')
48
+ widget.addApplyButton(callback)
49
+ assert not widget.getApplyButton() is None
50
+ assert callback.call_count == 0
51
+
52
+
53
+ def testAddOKButton(self, options, make_napari_viewer_proxy, mocker):
54
+ viewer = make_napari_viewer_proxy()
55
+ widget = OptionsWidget(viewer, options, self)
56
+ assert widget.getOKButton() is None
57
+ callback = mocker.stub(name='onOKButtonPressed')
58
+ widget.addOKButton(callback)
59
+ assert not widget.getOKButton() is None
60
+ assert callback.call_count == 0
61
+
62
+
63
+ def testAddCancelButton(self, options, make_napari_viewer_proxy, mocker):
64
+ viewer = make_napari_viewer_proxy()
65
+ widget = OptionsWidget(viewer, options, self)
66
+ assert widget.getCancelButton() is None
67
+ callback = mocker.stub(name='onCancelButtonPressed')
68
+ widget.addCancelButton(callback)
69
+ assert not widget.getCancelButton() is None
70
+ assert callback.call_count == 0
71
+
72
+
73
+ def test_OnApplyButtonClicked(self, options, make_napari_viewer_proxy, mocker):
74
+ viewer = make_napari_viewer_proxy()
75
+ widget = OptionsWidget(viewer, options, self)
76
+ callback = mocker.stub(name='self.apply')
77
+ widget.addApplyButton(callback)
78
+ options.setValue('group', 'xps')
79
+ if os.path.exists(options.optionsPath):
80
+ os.remove(options.optionsPath)
81
+ widget._onApplyButtonClicked()
82
+ assert os.path.exists(options.optionsPath)
83
+ assert options.value("group") == "lps"
84
+
85
+
86
+ def test_OnOKButtonClicked(self, options, make_napari_viewer_proxy, mocker):
87
+ viewer = make_napari_viewer_proxy()
88
+ widget = OptionsWidget(viewer, options, self)
89
+ callback = mocker.stub(name='self.ok')
90
+ widget.addOKButton(callback)
91
+ viewer.window.add_dock_widget(widget, name=options.optionsName)
92
+ options.setValue('group', 'xps')
93
+ if os.path.exists(options.optionsPath):
94
+ os.remove(options.optionsPath)
95
+ widget._onOKButtonClicked()
96
+ assert os.path.exists(options.optionsPath)
97
+ assert options.value("group") == "lps"
98
+
99
+
100
+ def test_OnCancelButtonClicked(self, options, make_napari_viewer_proxy, mocker):
101
+ viewer = make_napari_viewer_proxy()
102
+ widget = OptionsWidget(viewer, options, self)
103
+ callback = mocker.stub(name='self.cancel')
104
+ widget.addCancelButton(callback)
105
+ viewer.window.add_dock_widget(widget, name=options.optionsName)
106
+ options.setValue('group', 'xps')
107
+ if os.path.exists(options.optionsPath):
108
+ os.remove(options.optionsPath)
109
+ widget._onCancelButtonClicked()
110
+ assert not os.path.exists(options.optionsPath)
111
+ assert options.value("group") == "xps"
112
+
113
+
114
+ def test_OnLayerAddedOrRemoved(self, options, make_napari_viewer_proxy):
115
+ viewer = make_napari_viewer_proxy()
116
+ widget = OptionsWidget(viewer, options, self)
117
+ viewer.window.add_dock_widget(widget, name=options.optionsName)
118
+ data = np.random.rand(12, 12)
119
+ viewer.add_image(data=data, name='data')
120
+ assert True
121
+
122
+
123
+ def testGetImageLayer(self, options, make_napari_viewer_proxy):
124
+ viewer = make_napari_viewer_proxy()
125
+ widget = OptionsWidget(viewer, options, self)
126
+ viewer.window.add_dock_widget(widget, name=options.optionsName)
127
+ data = np.random.rand(12, 12)
128
+ viewer.add_image(data=data, name='labels_layer')
129
+ layer = widget.getImageLayer("labels")
130
+ assert layer is not None
131
+ assert layer.name == "labels_layer"
132
+ assert layer.data.shape == (12, 12)
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.4'
22
+ __version_tuple__ = version_tuple = (0, 1, 4)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,39 @@
1
+ import numpy as np
2
+
3
+
4
+
5
+ class ArrayUtil:
6
+ """A class to provide utils that do common operations on arrays.
7
+ """
8
+
9
+
10
+ @staticmethod
11
+ def stripZeroRowsAndColumns(data, zero=0):
12
+ """Return an array with all-zero rows and columns removed.
13
+
14
+ Returns a stripped array, with all rows and columns, in which each
15
+ element is zero, removed. Instead of rows and comumns with all zero
16
+ elements, rows and columns containing another number, string or
17
+ object at each position can be removed from the array.
18
+
19
+ :param data: A table from which empty rows and columns will be stripped
20
+ :type data: numpy.ndarray
21
+ :param zero: The element for which rows and columns will be removed
22
+ :return: A 3-tupel with
23
+
24
+ * the input array data with all-zero rows and columns removed
25
+ * A 1D array of the indices of the columns that are not all zero
26
+ in the input array
27
+ * A 1D array of the indices of the rows that are not all zero
28
+ in the input array
29
+
30
+ :rtype: (numpy.ndarray, numpy.ndarray, numpy.ndarray)
31
+ """
32
+ rowIndices = np.where(~np.all(data == zero, axis=1))[0]
33
+ stripped = data[~np.all(data == zero, axis=1)]
34
+ stripped = np.array(list(zip(*stripped)))
35
+ columnIndices = np.where(~np.all(stripped == zero, axis=1))[0]
36
+ stripped = stripped[~np.all(stripped == zero, axis=1)]
37
+ stripped = np.array(list(zip(*stripped)))
38
+ return stripped, columnIndices, rowIndices
39
+
@@ -0,0 +1,133 @@
1
+ from napari.layers.labels.labels import Labels
2
+ from napari.layers.points.points import Points
3
+ from napari.layers.image.image import Image
4
+
5
+
6
+
7
+ class NapariUtil:
8
+ """ Utility methods for the napari image viewer.
9
+ """
10
+
11
+ def __init__(self, viewer):
12
+ """ Constructor.
13
+
14
+ :param viewer: the napari viewer
15
+ :type viewer: napari.viewer.Viewer
16
+ """
17
+ self.viewer = viewer
18
+
19
+
20
+ def getImageLayers(self):
21
+ """ Return all image layers
22
+
23
+ :return: A list of the image layers in the viewer
24
+ :rtype: [napari.layers.image.Image.Image]
25
+ """
26
+ return self.getLayersOfType(Image)
27
+
28
+
29
+ def getFFTLayers(self):
30
+ """ Return all fft layers
31
+
32
+ :return: A list of the fft layers in the viewer
33
+ :rtype: [napari.layers.image.Image.Image]
34
+ """
35
+ imageLayers = self.getImageLayers()
36
+ imageLayers = [self.getLayerWithName(name) for name in imageLayers]
37
+ fftLayers = [layer.name for layer in imageLayers if 'fft' in layer.metadata.keys()]
38
+ return fftLayers
39
+
40
+
41
+ def getLabelLayers(self):
42
+ """ Return all label layers
43
+
44
+ :return: A list of the label layers in the viewer
45
+ :rtype: [napari.layers.labels.labels.Labels]
46
+ """
47
+ return self.getLayersOfType(Labels)
48
+
49
+
50
+ def getPointsLayers(self):
51
+ """ Return all point layers
52
+
53
+ :return: A list of the point layers in the viewer
54
+ :rtype: [napari.layers.points.points.Points]
55
+ """
56
+ return self.getLayersOfType(Points)
57
+
58
+
59
+ def getLayersOfType(self, layerType):
60
+ """ Return all layers of type layerType in the viewer
61
+
62
+ :param layerType: A napari layer type like Labels or Points.
63
+ :return: A list of the layers with the given type
64
+ """
65
+ layers = [layer.name for layer in self.viewer.layers if isinstance(layer, layerType)]
66
+ return layers
67
+
68
+
69
+ def getDataOfLayerWithName(self, name):
70
+ """ Return the data of the layer with the given name.
71
+
72
+ :param name: The name of the layer
73
+ :type name: str
74
+ :return: The layer with the given name if it exists and None otherwise
75
+ """
76
+ layer = self.getLayerWithName(name)
77
+ if layer:
78
+ return layer.data
79
+ else:
80
+ return None
81
+
82
+
83
+ def getLayerWithName(self, name):
84
+ """ Answer the layer with the given name, if it exists and None otherwise.
85
+
86
+ :param name: The name of a layer
87
+ :return: The layer from napari's layer list that has the given name
88
+ """
89
+ for layer in self.viewer.layers:
90
+ if layer.name == name:
91
+ return layer
92
+ return None
93
+
94
+
95
+ def getDataAndScaleOfLayerWithName(self, name):
96
+ """ Answer the data, the scale and the unit of the layer with the given name.
97
+
98
+ :param name: The name of a layer
99
+ :return: A tupel with the data, the scale and the unit of the layer with the given name.
100
+ The unit is the unit of the first dimension. The unit is supposed to be the same for all dimensions.
101
+ """
102
+ layer = self.getLayerWithName(name)
103
+ return layer.data, layer.scale, str(layer.units[0])
104
+
105
+
106
+ @staticmethod
107
+ def getOriginalPath(layer):
108
+ """ Answer the source path of the layer if it represents an image opened from the filesystem and the
109
+ original path from the metadata if it is a derived image. If it is neither an image opened from a file nor
110
+ derived from one (for example an image programmatically created) answer None. For the metadata information to
111
+ be present, the image operations must set it.
112
+
113
+ :param layer: The input layer
114
+ :return: The path of the image in the filesystem if it is known
115
+ """
116
+ if 'original_path' in layer.metadata.keys():
117
+ return layer.metadata['original_path']
118
+ if layer.source.path:
119
+ return layer.source.path
120
+ return None
121
+
122
+
123
+ @staticmethod
124
+ def copyOriginalPath(srcLayer, destLayer):
125
+ """Copy the orignal path from one layer to another. This should be used by operations that create images derived
126
+ from an input image.
127
+
128
+ :param srcLayer: The layer from which the original path is copied.
129
+ :param destLayer: The layer to which the original path is copied.
130
+ """
131
+ path = NapariUtil.getOriginalPath(srcLayer)
132
+ destLayer.metadata['original_path'] = path
133
+