Skip to content

Commit 05766bd

Browse files
committed
rename variables and add test
1 parent fe2a0d8 commit 05766bd

7 files changed

+373
-146
lines changed

tests/quadStatusBar_test.py

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
###############################################################################
2+
# volumina: volume slicing and editing library
3+
#
4+
# Copyright (C) 2011-2019, the ilastik developers
5+
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the Lesser GNU General Public License
9+
# as published by the Free Software Foundation; either version 2.1
10+
# of the License, or (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 Lesser General Public License for more details.
16+
#
17+
# See the files LICENSE.lgpl2 and LICENSE.lgpl3 for full text of the
18+
# GNU Lesser General Public License version 2.1 and 3 respectively.
19+
# This information is also available on the ilastik web site at:
20+
# http://ilastik.org/license/
21+
###############################################################################
22+
import pytest
23+
from PyQt5.QtWidgets import QMainWindow
24+
from PyQt5.QtCore import QPointF, Qt
25+
from PyQt5.QtGui import QColor
26+
27+
import numpy as np
28+
import vigra
29+
from ilastik.applets.layerViewer.layerViewerGui import LayerViewerGui
30+
from lazyflow.operators.opReorderAxes import OpReorderAxes
31+
from volumina.volumeEditor import VolumeEditor
32+
from volumina.volumeEditorWidget import VolumeEditorWidget
33+
from volumina.layerstack import LayerStackModel
34+
from volumina.pixelpipeline.datasources import LazyflowSource
35+
from volumina.layer import AlphaModulatedLayer
36+
from lazyflow.graph import Operator, InputSlot, OutputSlot, Graph
37+
38+
39+
class MainWindow(QMainWindow):
40+
def __init__(self):
41+
super().__init__()
42+
self.volumeEditorWidget = VolumeEditorWidget(parent=self)
43+
44+
45+
class OpTestImgaeSlots(Operator):
46+
"""test operator, containing 3-dim test data"""
47+
48+
GrayscaleImageIn = InputSlot()
49+
Label1ImageIn = InputSlot()
50+
Label2ImageIn = InputSlot()
51+
52+
GrayscaleImageOut = OutputSlot()
53+
Label1ImageOut = OutputSlot()
54+
Label2ImageOut = OutputSlot()
55+
56+
def __init__(self, *args, **kwargs):
57+
super().__init__(*args, **kwargs)
58+
59+
width, height, depth = 300, 200, 40
60+
61+
# create 2-dimensional images
62+
grayscaleImageSource = np.random.randint(0, 255, (depth, height, width, 1))
63+
label1ImageSource = np.zeros((depth, height, width, 3), dtype=np.int32)
64+
label2ImageSource = np.zeros((depth, height, width, 3), dtype=np.int32)
65+
66+
for z, set1 in enumerate(grayscaleImageSource[:, :, :, 0]):
67+
for y, set2 in enumerate(set1):
68+
for x, set3 in enumerate(set2):
69+
if z in range(5, 20) and y in range(20, 30) and x in range(80, 140):
70+
label1ImageSource[z, y, x, :] = [255, 255, 255]
71+
if z in range(25, 37) and y in range(100, 150) and x in range(10, 60):
72+
label2ImageSource[z, y, x, :] = [255, 255, 255]
73+
74+
self.GrayscaleImageIn.setValue(grayscaleImageSource, notify=False, check_changed=False)
75+
self.Label1ImageIn.setValue(label1ImageSource, notify=False, check_changed=False)
76+
self.Label2ImageIn.setValue(label2ImageSource, notify=False, check_changed=False)
77+
78+
self.GrayscaleImageIn.meta.axistags = vigra.defaultAxistags("tzyxc"[5 - len(self.GrayscaleImageIn.meta.shape):])
79+
self.Label1ImageIn.meta.axistags = vigra.defaultAxistags("tzyxc"[5 - len(self.Label1ImageIn.meta.shape):])
80+
self.Label2ImageIn.meta.axistags = vigra.defaultAxistags("tzyxc"[5 - len(self.Label2ImageIn.meta.shape):])
81+
82+
self.GrayscaleImageOut.connect(self.GrayscaleImageIn)
83+
self.Label1ImageOut.connect(self.Label1ImageIn)
84+
self.Label2ImageOut.connect(self.Label2ImageIn)
85+
86+
87+
class TestSpinBoxImageView(object):
88+
89+
def updateAllTiles(self, imageScenes):
90+
for scene in imageScenes:
91+
scene.joinRenderingAllTiles()
92+
93+
@pytest.fixture(autouse=True)
94+
def setupClass(self, qtbot):
95+
96+
self.qtbot = qtbot
97+
self.main = MainWindow()
98+
self.layerStack = LayerStackModel()
99+
100+
g = Graph()
101+
self.op = OpTestImgaeSlots(graph=g)
102+
103+
self.grayscaleLayer = LayerViewerGui._create_grayscale_layer_from_slot(self.op.GrayscaleImageOut, 1)
104+
self.labelLayer1 = AlphaModulatedLayer(LazyflowSource(self.op.Label1ImageOut), tintColor=QColor(Qt.cyan),
105+
range=(0, 255), normalize=(0, 255))
106+
self.labelLayer2 = AlphaModulatedLayer(LazyflowSource(self.op.Label2ImageOut), tintColor=QColor(Qt.yellow),
107+
range=(0, 255), normalize=(0, 255))
108+
109+
self.labelLayer1.name = "Segmentation (Label 1)"
110+
self.labelLayer2.name = "Segmentation (Label 2)"
111+
112+
self.layerStack.append(self.grayscaleLayer)
113+
self.layerStack.append(self.labelLayer1)
114+
self.layerStack.append(self.labelLayer2)
115+
116+
activeOutSlot = self.op.GrayscaleImageOut # take any out slot here
117+
if activeOutSlot.ready() and activeOutSlot.meta.axistags is not None:
118+
# Use an OpReorderAxes adapter to transpose the shape for us.
119+
op5 = OpReorderAxes(graph=g)
120+
op5.Input.connect(activeOutSlot)
121+
op5.AxisOrder.setValue('txyzc')
122+
shape = op5.Output.meta.shape
123+
124+
# We just needed the op to determine the transposed shape.
125+
# Disconnect it so it can be garbage collected.
126+
op5.Input.disconnect()
127+
op5.cleanUp()
128+
129+
self.editor = VolumeEditor(self.layerStack, self.main)
130+
self.editorWidget = self.main.volumeEditorWidget
131+
self.editorWidget.init(self.editor)
132+
133+
self.editor.dataShape = shape
134+
135+
# Find the xyz midpoint
136+
midpos5d = [x // 2 for x in shape]
137+
# center viewer there
138+
# set xyz position
139+
midpos3d = midpos5d[1:4]
140+
self.editor.posModel.slicingPos = midpos3d
141+
self.editor.navCtrl.panSlicingViews(midpos3d, [0, 1, 2])
142+
for i in range(3):
143+
self.editor.navCtrl.changeSliceAbsolute(midpos3d[i], i)
144+
145+
self.main.setCentralWidget(self.editorWidget)
146+
self.main.show()
147+
self.qtbot.addWidget(self.main)
148+
149+
def testAddingAndRemovingPosVal(self):
150+
assert 0 == len(self.editorWidget.quadViewStatusBar.layerValueWidgets)
151+
152+
for layer in self.layerStack:
153+
if not layer.showPosValue:
154+
layer.showPosValue = True
155+
if not layer.visible:
156+
layer.visible = True
157+
158+
for layer in self.layerStack:
159+
assert layer in self.editorWidget.quadViewStatusBar.layerValueWidgets
160+
161+
self.layerStack[0].showPosValue = False
162+
assert self.layerStack[0] not in self.editorWidget.quadViewStatusBar.layerValueWidgets
163+
self.layerStack[2].showPosValue = False
164+
assert self.layerStack[2] not in self.editorWidget.quadViewStatusBar.layerValueWidgets
165+
self.layerStack[1].showPosValue = False
166+
assert self.layerStack[1] not in self.editorWidget.quadViewStatusBar.layerValueWidgets
167+
self.layerStack[2].showPosValue = True
168+
assert self.layerStack[2] in self.editorWidget.quadViewStatusBar.layerValueWidgets
169+
170+
def testLayerPositionValueStrings(self):
171+
for layer in self.layerStack:
172+
if not layer.showPosValue:
173+
layer.showPosValue = True
174+
if not layer.visible:
175+
layer.visible = True
176+
177+
x, y, z = 90, 25, 10
178+
179+
posVal = 255-int(self.op.GrayscaleImageIn.value[z, y, x, 0])
180+
grayValidationStrings = ["Gray:" + str(posVal), "Gray:" + str(posVal+1), "Gray:" + str(posVal-1)]
181+
label1ValidationString = "Label 1"
182+
label2ValidationString = "Label 2"
183+
184+
signal = self.editor.posModel.cursorPositionChanged
185+
with self.qtbot.waitSignal(signal, timeout=1000):
186+
self.editor.navCtrl.changeSliceAbsolute(z, 2)
187+
188+
# After change of crosshair positions tiles are marked dirty.
189+
self.updateAllTiles(self.editor.imageScenes) # Wait for all tiles being refreshed
190+
191+
signals = [self.editorWidget.quadViewStatusBar.layerValueWidgets[self.grayscaleLayer].textChanged,
192+
self.editorWidget.quadViewStatusBar.layerValueWidgets[self.labelLayer1].textChanged]
193+
with self.qtbot.waitSignals(signals, timeout=1000):
194+
self.editor.navCtrl.positionDataCursor(QPointF(x, y), 2)
195+
196+
self.updateAllTiles(self.editor.imageScenes) # Wait for all tiles being refreshed
197+
198+
assert self.editorWidget.quadViewStatusBar.layerValueWidgets[
199+
self.grayscaleLayer].text() in grayValidationStrings
200+
assert self.editorWidget.quadViewStatusBar.layerValueWidgets[self.labelLayer1].text() == label1ValidationString
201+
202+
x, y, z = 39, 130, 30
203+
204+
posVal = 255-int(self.op.GrayscaleImageIn.value[z, y, x, 0])
205+
grayValidationStrings = ["Gray:" + str(posVal), "Gray:" + str(posVal+1), "Gray:" + str(posVal-1)]
206+
207+
with self.qtbot.waitSignal(signal, timeout=1000):
208+
self.editor.navCtrl.changeSliceAbsolute(y, 1)
209+
210+
self.updateAllTiles(self.editor.imageScenes) # Wait for all tiles being refreshed
211+
212+
with self.qtbot.waitSignals(signals, timeout=1000):
213+
self.editor.navCtrl.positionDataCursor(QPointF(x, z), 1)
214+
215+
self.updateAllTiles(self.editor.imageScenes) # Wait for all tiles being refreshed
216+
217+
assert self.editorWidget.quadViewStatusBar.layerValueWidgets[
218+
self.grayscaleLayer].text() in grayValidationStrings
219+
assert self.editorWidget.quadViewStatusBar.layerValueWidgets[self.labelLayer2].text() == label2ValidationString
220+

volumina/layer.py

+27-20
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class Layer(QObject):
6060
somethingChanged signals is emitted."""
6161
changed = pyqtSignal()
6262

63-
showValueChanged = pyqtSignal(object, bool)
63+
showPosValueChanged = pyqtSignal(object, bool)
6464
visibleChanged = pyqtSignal(bool)
6565
opacityChanged = pyqtSignal(float)
6666
nameChanged = pyqtSignal(object) # sends a python str object, not unicode!
@@ -82,13 +82,13 @@ def visible(self, value):
8282
self.visibleChanged.emit(value)
8383

8484
@property
85-
def showValue(self):
86-
return self._showValue
85+
def showPosValue(self):
86+
return self._showPosValue
8787

88-
@showValue.setter
89-
def showValue(self, value):
90-
self._showValue = value
91-
self.showValueChanged.emit(self, value)
88+
@showPosValue.setter
89+
def showPosValue(self, value):
90+
self._showPosValue = value
91+
self.showPosValueChanged.emit(self, value)
9292

9393
def toggleVisible(self):
9494
"""Convenience function."""
@@ -175,17 +175,17 @@ def toolTip(self):
175175
def setToolTip(self, tip):
176176
self._toolTip = tip
177177

178-
def setValueWidget(self, value):
178+
def getPosInfo(self, value):
179179
"""
180180
This function needs to be overwritten by every layer.
181-
This function is called by QuadStatusBar.setMouseCoords. It is expected to return a tuple of information for
182-
the position widgets, showing current pixelvalues of respective layer.
181+
It is called by QuadStatusBar.setLayerPosIfos and is expected to return a tuple of information for
182+
the position widgets, showing current pixelvalues at cursor position of respective layer.
183183
:param val: layer value at current cursor position
184184
:return: ((String)text, (QColor)foregroundcolor, (QColor)backgroundcolor) for respective widget
185185
"""
186186
if value is not None:
187-
return self.name + str(value), QColor(255, 255, 255), QColor(0, 0, 0)
188-
return None, QColor(255, 255, 255), QColor(0, 0, 0)
187+
return self.name + str(value), QColor(0, 0, 0), QColor(255, 255, 255)
188+
return None, QColor(0, 0, 0), QColor(255, 255, 255)
189189

190190
def isDifferentEnough(self, other_layer):
191191
"""This ugly function is here to support the updateAllLayers function in the layerViewerGui in ilastik"""
@@ -201,7 +201,7 @@ def __init__(self, datasources, direct=False):
201201
super(Layer, self).__init__()
202202
self._name = u"Unnamed Layer"
203203
self._visible = True
204-
self._showValue = False
204+
self._showPosValue = False
205205
self._opacity = 1.0
206206
self._datasources = datasources
207207
self._layerId = None
@@ -429,10 +429,16 @@ def isDifferentEnough(self, other_layer):
429429
return True
430430
return self._window_leveling != other_layer._window_leveling
431431

432-
def setValueWidget(self, value):
432+
def getPosInfo(self, value):
433+
"""overwrites Layer.getPosInfo"""
433434
if value is not None:
434-
return 'Gray:' + str(value.black()), QColor(value.black(), value.black(), value.black()), value
435-
return None, QColor(255, 255, 255), QColor(0, 0, 0)
435+
gray_val = 255
436+
if value.value() in range(int(gray_val/2-10), int(gray_val/2+10)):
437+
gray_val = value.black() + 20 * numpy.sign(value.black() - value.value())
438+
else:
439+
gray_val = value.black()
440+
return 'Gray:' + str(value.black()), QColor(gray_val, gray_val, gray_val), value
441+
return None, QColor(0, 0, 0), QColor(255, 255, 255)
436442

437443
def __init__(self, datasource, range=None, normalize=None, direct=False, window_leveling=False):
438444
assert isinstance(datasource, SourceABC)
@@ -458,15 +464,16 @@ def tintColor(self, c):
458464
self._tintColor = c
459465
self.tintColorChanged.emit()
460466

461-
def setValueWidget(self, value):
467+
def getPosInfo(self, value):
468+
"""overwrites Layer.getPosInfo"""
462469
if value is None:
463-
return None, QColor(255, 255, 255), QColor(0, 0, 0)
470+
return None, QColor(0, 0, 0), QColor(255, 255, 255)
464471
elif "Segmentation (Label " in self.name:
465472
if value.getRgb() == (0,0,0,0):
466-
return None, QColor(255, 255, 255), QColor(0, 0, 0)
473+
return None, QColor(0, 0, 0), QColor(255, 255, 255)
467474
return self.name[self.name.find("(") + 1:self.name.find(")")], QColor(255, 255, 255), self.tintColor
468475
else:
469-
return self.name + str(value.getRgb()), QColor(255, 255, 255), QColor(0, 0, 0)
476+
return self.name + str(value.getRgb()), QColor(0, 0, 0), QColor(255, 255, 255)
470477

471478
def __init__(self, datasource, tintColor=QColor(255, 0, 0), range=(0, 255), normalize=None):
472479
assert isinstance(datasource, SourceABC)

volumina/patchAccessor.py

+13-21
Original file line numberDiff line numberDiff line change
@@ -94,36 +94,28 @@ def patchRectF(self, blockNum, overlap=0):
9494
return QRectF(QPointF(startx, starty), QPointF(endx, endy))
9595

9696
def getPatchesForRect(self, startx, starty, endx, endy):
97-
"""returns patchnumbers for patches that are intersecting with the normalized rectangle defined by upper left
98-
corner (staŕtx/y) and lower right corner (endx/y)
9997
"""
100-
if endy < 0 or endx < 0:
101-
# allowing no inverted start/end -positions, there should be no negative end-positions
102-
return []
98+
Looks up patches for specified block
99+
x in [startx, endx)
100+
y in [starty, endy)
101+
:returns: list of patch ids
102+
"""
103+
assert startx < endx
104+
assert starty < endy
105+
103106
sx = int(numpy.floor(1.0 * startx / self._blockSize))
104107
ex = int(numpy.ceil(1.0 * endx / self._blockSize))
105108
sy = int(numpy.floor(1.0 * starty / self._blockSize))
106109
ey = int(numpy.ceil(1.0 * endy / self._blockSize))
107110

108-
# Clip to rect to upper bounds
111+
# Clip to rect bounds
112+
sx = max(sx, 0)
113+
sy = max(sy, 0)
109114
ex = min(ex, self._cX)
110115
ey = min(ey, self._cY)
111116

112-
# return an index also, when start and end are equal
113-
if sx == ex:
114-
if ex >= self._cX:
115-
sx = sx - 1
116-
else:
117-
ex = ex + 1
118-
if sy == ey:
119-
if ey >= self._cY:
120-
sy = sy - 1
121-
else:
122-
ey = ey + 1
123-
124-
# Clip to rect to lower bounds
125-
sx = max(sx, 0)
126-
sy = max(sy, 0)
117+
sx = max(min(sx, ex - 1), 0)
118+
sy = max(min(sy, ey - 1), 0)
127119

128120
nums = []
129121
for y in range(sy, ey):

volumina/quadsplitter.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,8 @@ def addStatusBar(self, bar):
270270
def setGrayScaleToQuadStatusBar(self, gray):
271271
self.quadViewStatusBar.setGrayScale(gray)
272272

273-
def setMouseCoordsToQuadStatusBar(self, x, y, z):
274-
self.quadViewStatusBar.setMouseCoords(x, y, z)
273+
def setLayerPosIfosToQuadStatusBar(self, x, y, z):
274+
self.quadViewStatusBar.setMousePosInfos(x, y, z)
275275

276276
def ensureMaximized(self, axis):
277277
"""

0 commit comments

Comments
 (0)