forked from SainsburyWellcomeCentre/lasagna
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lasagna_axis.py
257 lines (199 loc) · 10.7 KB
/
lasagna_axis.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
"""
this file describes a class that handles the axis behavior for the lasagna viewer
"""
import lasagna_helperFunctions as lasHelp
import pyqtgraph as pg
import ingredients
class projection2D():
def __init__(self, thisPlotWidget, lasagna, axisName='', minMax=(0,1500), axisRatio=1, axisToPlot=0):
"""
thisPlotWidget - the PlotWidget to which we will add the axes
minMax - the minimum and maximum values of the plotted image.
axisRatio - the voxel size ratio along the x and y axes. 1 means square voxels.
axisToPlot - the dimension along which we are slicing the data.
"""
#Create properties
self.axisToPlot = axisToPlot #the axis in 3D space that this view correponds to
self.axisName = axisName
#We can link this projection to two others
self.linkedXprojection=None
self.linkedYprojection=None
print("Creating axis at " + str(thisPlotWidget.objectName()))
self.view = thisPlotWidget #This should target the axes to a particular plot widget
if lasHelp.readPreference('hideZoomResetButtonOnImageAxes')==True:
self.view.hideButtons()
if lasHelp.readPreference('hideAxes')==True:
self.view.hideAxis('left')
self.view.hideAxis('bottom')
self.view.setAspectLocked(True,axisRatio)
#Loop through the ingredients list and add them to the ViewBox
self.lasagna = lasagna
self.items=[] #a list of added plot items TODO: check if we really need this
self.addItemsToPlotWidget(self.lasagna.ingredientList)
#The currently plotted slice
self.currentSlice=None
#Link the progressLayer signal to a slot that will move through image layers as the wheel is turned
self.view.getViewBox().progressLayer.connect(self.wheel_layer_slot)
def addItemToPlotWidget(self,ingredient):
"""
Adds an ingredient to the PlotWidget as an item (i.e. the ingredient manages the process of
producing an item using information in the ingredient properties.
"""
verbose=False
_thisItem = ( getattr(pg,ingredient.pgObject)(**ingredient.pgObjectConstructionArgs) )
_thisItem.objectName = ingredient.objectName
if verbose:
print("\nlasagna_axis.addItemToPlotWidget adds item " + ingredient.objectName + " as: " + str(_thisItem))
self.view.addItem(_thisItem)
self.items.append(_thisItem)
def removeItemFromPlotWidget(self,item):
"""
Removes an item from the PlotWidget and also from the list of items.
This function is used for when want to delete or wipe an item from the list
because it is no longer needed. Use hideIngredient if you want to temporarily
make something invisible
"item" is either a string defining an objectName or the object itself
"""
items=list(self.view.items())
nItemsBefore = len(items) #to determine if an item was removed
if isinstance(item,str):
removed=False
for thisItem in items:
if hasattr(thisItem,'objectName') and thisItem.objectName==item:
self.view.removeItem(thisItem)
removed=True
if removed==False:
print("lasagna_axis.removeItemFromPlotWidget failed to remove item defined by string " + item)
else: #it should be an image item
self.view.removeItem(item)
#Optionally return True of False depending on whether the removal was successful
nItemsAfter = len(list(self.view.items()))
if nItemsAfter<nItemsBefore:
return True
elif nItemsAfter==nItemsBefore:
return False
else:
print('** removeItemFromPlotWidget: %d items before removal and %d after removal **' % (nItemsBefore,nItemsAfter))
return False
print("%d items after remove call" % len(list(self.view.items())))
def addItemsToPlotWidget(self,ingredients):
"""
Add all ingredients in list to the PlotWidget as items
"""
if len(ingredients)==0:
return
[self.addItemToPlotWidget(thisIngredient) for thisIngredient in ingredients]
def removeAllItemsFromPlotWidget(self,items):
"""
Remove all items (i.e. delete them) from the PlotWidget
items is a list of strings or plot items
"""
if len(items)==0:
return
[self.removeItemFromPlotWidget(thisItem) for thisItem in items]
def listNamedItemsInPlotWidget(self):
"""
Print a list of all named items actually *added* in the PlotWidget
"""
n=1
for thisItem in list(self.view.items()):
if hasattr(thisItem,'objectName') and isinstance(thisItem.objectName,str):
print("object %s: %s" % (n,thisItem.objectName))
n=n+1
def getPlotItemByName(self,objName):
"""
returns the first plot item in the list bearing the objectName 'objName'
because of the way we generally add objects, there *should* never be
multiple objects with the same name
"""
for thisItem in list(self.view.items()):
if hasattr(thisItem,'objectName') and isinstance(thisItem.objectName,str):
if thisItem.objectName == objName:
return thisItem
def getPlotItemByType(self,itemType):
"""
returns all plot items of the defined type.
itemType should be a string that defines a pyqtgraph item type.
Examples include: ImageItem, ViewBox, PlotItem, AxisItem and LabelItem
"""
itemList = []
for thisItem in list(self.view.items()):
if thisItem.__module__.endswith(itemType):
itemList.append(thisItem)
return itemList
def hideItem(self,item):
"""
Hides an item from the PlotWidget. If you want to delete an item
outright then use removeItemFromPlotWidget.
"""
print("NEED TO WRITE lasagna.axis.hideItem()")
return
def updatePlotItems_2D(self, ingredientsList, sliceToPlot=None, resetToMiddleLayer=False):
"""
Update all plot items on axis, redrawing so everything associated with a specified
slice (sliceToPlot) is shown. This is done based upon a list of ingredients
"""
verbose=False
# loop through all plot items searching for imagestack items (these need to be plotted first)
for thisIngredient in ingredientsList:
if isinstance(thisIngredient, ingredients.imagestack.imagestack):
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
#TODO: AXIS need some way of linking the ingredient to the plot item but keeping in mind
# that this same object needs to be plotted in different axes, each of which has its own
# plot items. So I can't assign a single plot item to the ingredient. Options?
# a list of items and axes in the ingredient? I don't like that.
#Got to the middle of the stack
if sliceToPlot is None or resetToMiddleLayer:
stacks = self.lasagna.returnIngredientByType('imagestack')
numSlices = []
[numSlices.append(thisStack.data(self.axisToPlot).shape[0]) for thisStack in stacks]
numSlices = max(numSlices)
sliceToPlot=numSlices//2
self.currentSlice = sliceToPlot
if verbose:
print("lasagna_axis.updatePlotItems_2D - plotting ingredient " + thisIngredient.objectName)
thisIngredient.plotIngredient(
pyqtObject=lasHelp.findPyQtGraphObjectNameInPlotWidget(self.view,thisIngredient.objectName,verbose=verbose),
axisToPlot=self.axisToPlot,
sliceToPlot=self.currentSlice
)
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# the image is now displayed
# loop through all plot items searching for non-image items (these need to be overlaid on top of the image)
for thisIngredient in ingredientsList:
if isinstance(thisIngredient, ingredients.imagestack.imagestack)==False:
if verbose:
print("lasagna_axis.updatePlotItems_2D - plotting ingredient " + thisIngredient.objectName)
thisIngredient.plotIngredient(pyqtObject=lasHelp.findPyQtGraphObjectNameInPlotWidget(self.view,thisIngredient.objectName,verbose=verbose),
axisToPlot=self.axisToPlot,
sliceToPlot=self.currentSlice
)
def updateDisplayedSlices_2D(self, ingredients, slicesToPlot):
"""
Update the image planes shown in each of the axes
ingredients - lasagna.ingredients
slicesToPlot - a tuple of length 2 that defines which slices to plot for the Y and X linked axes
"""
#self.updatePlotItems_2D(ingredients) # TODO: Not have this here. This should be set when the mouse enters the axis and then not changed.
# Like this it doesn't work if we are to change the displayed slice in the current axis using the mouse wheel.
self.linkedYprojection.updatePlotItems_2D(ingredients,slicesToPlot[0])
self.linkedXprojection.updatePlotItems_2D(ingredients,slicesToPlot[1])
def getMousePositionInCurrentView(self, pos):
#TODO: figure out what pos is and where best to put it. Then can integrate this call into updateDisplayedSlices
#TODO: Consider not returning X or Y values that fall outside of the image space.
mousePoint = self.view.getPlotItem().vb.mapSceneToView(pos)
X = int(mousePoint.x())
Y = int(mousePoint.y())
return (X,Y)
def resetAxes(self):
"""
Set the X and Y limits of the axis to nicely frame the data
"""
self.view.autoRange()
#------------------------------------------------------
#slots
def wheel_layer_slot(self):
"""
Handle the wheel action that allows the user to move through stack layers
"""
self.updatePlotItems_2D(self.lasagna.ingredientList,sliceToPlot=round(self.currentSlice + self.view.getViewBox().progressBy)) #round creates an int that supresses a warning in p3