def __onApply(self, ev): """Called when the *Apply* button is pushed. Sets the ``voxToWorldMat`` attribute of the :class:`.Image` instance being transformed. """ overlay = self.__overlay if overlay is None: return if self.__extraXform is None: v2wXform = overlay.voxToWorldMat else: v2wXform = self.__extraXform newXform = self.__getCurrentXform() opts = self.displayCtx.getOpts(overlay) xform = transform.concat(newXform, v2wXform) with props.suppress(opts, 'displayXform'): opts.displayXform = np.eye(4) overlay.voxToWorldMat = xform # Reset the interface, and clear any # cached transform for this overlay self.__deregisterOverlay() self.__cachedXforms.pop(overlay, None) self.__selectedOverlayChanged()
def __dataRangeChanged(self, *args, **kwargs): """Called when the :attr:`dataRange` property changes, and also by the :meth:`__initProperties` and :meth:`__volumeChanged` methods. """ finData = self.__finiteData nzData = self.__nonZeroData self.__clippedFiniteData = finData[(finData >= self.dataRange.xlo) & (finData < self.dataRange.xhi)] self.__clippedNonZeroData = nzData[(nzData >= self.dataRange.xlo) & (nzData < self.dataRange.xhi)] with props.suppress(self, 'showOverlayRange', notify=True): dlo, dhi = self.dataRange.x dist = (dhi - dlo) / 10000.0 needsInit = np.all(np.isclose(self.showOverlayRange.x, [0, 0])) self.showOverlayRange.xmin = dlo - dist self.showOverlayRange.xmax = dhi + dist if needsInit or not self.showOverlay: self.showOverlayRange.xlo = dlo self.showOverlayRange.xhi = dhi else: self.showOverlayRange.xlo = max(dlo, self.showOverlayRange.xlo) self.showOverlayRange.xhi = min(dhi, self.showOverlayRange.xhi) self.__histPropsChanged()
def _cropModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos): """Called on left mouse drags. Updates the :attr:`cropBox` boudary which was clicked on (see :meth:`_cropModeLeftMouseDown`), so it follows the mouse location. """ if self.__overlay is None or self.__dragAxis is None: return box = self.cropBox axis = self.__dragAxis limit = self.__dragLimit oppLimit = 1 - limit vox = self.__getVoxel(self.__overlay, canvasPos) newval = vox[axis] oppval = box.getLimit(axis, oppLimit) if limit == 0 and newval >= oppval: newval = oppval - 1 elif limit == 1 and newval <= oppval: newval = oppval + 1 with props.suppress(self, 'cropBox', notify=True): self.cropBox.setLimit(axis, limit, newval) self.displayCtx.location = canvasPos
def append(self, item, overlayType=None): with props.suppress(self, 'overlays', notify=True): self.overlays.append(item) if overlayType is not None: self.__initOverlayType[item] = overlayType
def insert(self, index, item, overlayType=None): with props.suppress(self, 'overlays', notify=True): self.overlays.insert(index, item) if overlayType is not None: self.__initOverlayType[item] = overlayType
def insert(self, index, item, **initProps): """Insert a new overlay into the overlay list. Any initial :class:`.Display`/:class:`.DisplayOpts` property values may be passed in as keyword arguments. """ with props.suppress(self, 'overlays', notify=True): self.overlays.insert(index, item) self.__initProps[item] = initProps
def extend(self, iterable, overlayTypes=None): with props.suppress(self, 'overlays', notify=True): result = self.overlays.extend(iterable) if overlayTypes is not None: for overlay, overlayType in overlayTypes.items(): self.__initOverlayType[overlay] = overlayType return result
def __updateAxisLimits(self): """Called by the ``panzoom`` ``MouseDrag`` event handlers. Makes sure that the :attr:`.PlotPanel.limits` property is up to date. """ xlims = list(self.__axis.get_xlim()) ylims = list(self.__axis.get_ylim()) with props.suppress(self.viewPanel, 'limits'): self.viewPanel.limits.x = xlims self.viewPanel.limits.y = ylims
def draw(self, *a): """Overrides :meth:`.PlotPanel.draw`. Draws some :class:`.PowerSpectrumSeries` using the :meth:`.PlotPanel.drawDataSeries` method. """ if not self or self.destroyed: return pss = self.getDataSeriesToPlot() for ps in pss: with props.suppress(ps, 'label'): ps.label = ps.makeLabel() self.drawDataSeries(extraSeries=pss) self.drawArtists()
def realOnLoad(atlas): # label image if labelIdx is None: overlayType = 'label' data = atlas[:] else: # regional label image if summary: labelVal = atlasDesc.find(index=labelIdx).value overlayType = 'mask' data = np.zeros(atlas.shape, dtype=np.uint16) data[atlas[:] == labelVal] = labelVal # regional probability image else: overlayType = 'volume' data = atlas[:, :, :, labelIdx] overlay = fslimage.Image(data, header=atlas.header, name=overlayName) with props.suppress(self.overlayList, 'overlays', self.name): self.overlayList.append(overlay, overlayType=overlayType) self.__overlayPanel.setOverlayState(atlasDesc, labelIdx, summary, True) self.__enabledOverlays[overlayName] = (overlay, atlasID, labelIdx, summary) log.debug('Added overlay {}'.format(overlayName)) display = self.displayCtx.getDisplay(overlay) display.overlayType = overlayType opts = display.opts if overlayType == 'mask': opts.colour = np.random.random(3) elif overlayType == 'volume': opts.cmap = 'hot' if onLoad is not None: onLoad()
def extend(self, iterable, **initProps): """Add new overlays to the overlay list. Any initial :class:`.Display`/:class:`.DisplayOpts` property values may be passed in as keyword arguments, where the argument name is the property name, and the argument value is a dict of ``{overlay : value}`` mappings. """ with props.suppress(self, 'overlays', notify=True): result = self.overlays.extend(iterable) for propName, overlayProps in initProps.items(): for overlay, val in overlayProps.items(): self.__initProps[overlay][propName] = val return result
def __onVisButton(self, ev): """Called when the *visibility* button is pushed. Toggles the overlay visibility. """ if self.__propagateSelect: self.__displayCtx.selectOverlay(self.__overlay) idx = self.__listBox.IndexOf(self.__overlay) enabled = self.__visibility.GetValue() with props.suppress(self.__display, 'enabled', self.__name): self.__display.enabled = enabled if enabled: fgColour = ListItemWidget.enabledFG else: fgColour = ListItemWidget.disabledFG self.__listBox.SetItemForegroundColour(idx, fgColour)
def draw(self, *a): """Overrides :meth:`.PlotPanel.draw`. Passes some :class:`.TimeSeries` instances to the :meth:`.PlotPanel.drawDataSeries` method. """ if not self or self.destroyed(): return tss = self.getDataSeriesToPlot() # Include all of the extra model series # for all FEATTimeSeries instances newTss = [] for ts in tss: if isinstance(ts, plotting.FEATTimeSeries): mtss = ts.getModelTimeSeries() newTss += mtss # If the FEATTimeSeries is disabled, # disable the associated model time # series. for mts in mtss: mts.enabled = ts.enabled else: newTss.append(ts) tss = newTss for ts in tss: # Changing the label might trigger # another call to this method, as # the PlotPanel might have a listener # registered on it. Hence the suppress with props.suppress(ts, 'label'): ts.label = ts.makeLabel() xlabel, ylabel = self.__generateDefaultLabels(tss) self.drawDataSeries(extraSeries=tss, xlabel=xlabel, ylabel=ylabel) self.drawArtists()
def onDataRangeChange(self): """Overrides :meth:`HistogramSeries.onDataRangeChange`. Makes sure that the :attr:`showOverlayRange` limits are synced to the :attr:`HistogramSeries.dataRange`. """ with props.suppress(self, 'showOverlayRange', notify=True): dlo, dhi = self.dataRange.x dist = (dhi - dlo) / 10000.0 needsInit = np.all(np.isclose(self.showOverlayRange.x, [0, 0])) self.showOverlayRange.xmin = dlo - dist self.showOverlayRange.xmax = dhi + dist if needsInit or not self.showOverlay: self.showOverlayRange.xlo = dlo self.showOverlayRange.xhi = dhi else: self.showOverlayRange.xlo = max(dlo, self.showOverlayRange.xlo) self.showOverlayRange.xhi = min(dhi, self.showOverlayRange.xhi)
def realOnLoad(atlas): initprops = {} # label image if labelIdx is None: overlay = fslimage.Image(atlas) initprops['overlayType'] = 'label' else: # regional label image if summary: overlay = atlas.get(index=labelIdx, binary=False) initprops['overlayType'] = 'mask' initprops['colour'] = np.random.random(3) # regional statistic/probability image else: overlay = atlas.get(index=labelIdx) initprops['overlayType'] = 'volume' initprops['cmap'] = 'hot' initprops['displayRange'] = (atlasDesc.lower, atlasDesc.upper) initprops['clippingRange'] = (atlasDesc.lower, atlasDesc.upper) overlay.name = overlayName with props.suppress(self.overlayList, 'overlays', self.name): self.overlayList.append(overlay, **initprops) self.__overlayPanel.setOverlayState(atlasDesc, labelIdx, summary, True) self.__enabledOverlays[overlayName] = (overlay, atlasID, labelIdx, summary) log.debug('Added overlay {}'.format(overlayName)) if onLoad is not None: onLoad()
def draw(self, *a): """Overrides :meth:`.PlotPanel.draw`. Passes some :class:`.DataSeries` instances to the :meth:`.PlotPanel.drawDataSeries` method. """ if not self or self.destroyed: return tss = self.getDataSeriesToPlot() for ts in tss: # Changing the label might trigger # another call to this method, as # the PlotPanel might have a listener # registered on it. Hence the suppress with props.suppress(ts, 'label'): ts.label = ts.makeLabel() xlabel, ylabel = self.__generateDefaultLabels(tss) self.drawDataSeries(extraSeries=tss, xlabel=xlabel, ylabel=ylabel) self.drawArtists()
def __selectedOverlayChanged(self, *a): """Called when the :attr:`.DisplayContext.selectedOverlay` changes. If the overlay is a :class:`.Image` instance, it is set as the :attr:`.DisplayContext.displaySpace` reference, and the :attr:`cropBox` is configured to be relative to the newly selected overlay. """ overlay = self.displayCtx.getSelectedOverlay() if overlay is self.__overlay: return self.__deregisterOverlay() enabled = isinstance(overlay, fslimage.Image) self.__xrect.enabled = enabled self.__yrect.enabled = enabled self.__zrect.enabled = enabled if not enabled: return self.__registerOverlay(overlay) shape = overlay.shape[:3] crop = self.__cachedCrops.get(overlay, None) if crop is None: crop = [0, shape[0], 0, shape[1], 0, shape[2]] with props.suppress(self, 'cropBox', notify=True): self.cropBox.xmin = 0 self.cropBox.ymin = 0 self.cropBox.zmin = 0 self.cropBox.xmax = shape[0] self.cropBox.ymax = shape[1] self.cropBox.zmax = shape[2] self.cropBox = crop
def __updateBounds(self, *a): """Called when the overlay list changes, or when any overlay display transform is changed. Updates the :attr:`bounds` property so that it is big enough to contain all of the overlays (as defined by their :attr:`.DisplayOpts.bounds` properties). """ if len(self.__overlayList) == 0: minBounds = [0.0, 0.0, 0.0] maxBounds = [0.0, 0.0, 0.0] else: minBounds = 3 * [sys.float_info.max] maxBounds = 3 * [-sys.float_info.max] for ovl in self.__overlayList: display = self.__displays[ovl] opts = display.opts lo = opts.bounds.getLo() hi = opts.bounds.getHi() for ax in range(3): if lo[ax] < minBounds[ax]: minBounds[ax] = lo[ax] if hi[ax] > maxBounds[ax]: maxBounds[ax] = hi[ax] self.bounds[:] = [ minBounds[0], maxBounds[0], minBounds[1], maxBounds[1], minBounds[2], maxBounds[2] ] # Update the constraints on the location # property to be aligned with the new bounds with props.suppress(self, 'location'): self.location.setLimits(0, self.bounds.xlo, self.bounds.xhi) self.location.setLimits(1, self.bounds.ylo, self.bounds.yhi) self.location.setLimits(2, self.bounds.zlo, self.bounds.zhi)
def draw(self, *a): """Overrides :meth:`.PlotPanel.draw`. Passes some :class:`.HistogramSeries` instances to the :meth:`.PlotPanel.drawDataSeries` method. """ if not self or self.destroyed(): return hss = self.getDataSeriesToPlot() for hs in hss: with props.suppress(hs, 'label'): hs.label = self.displayCtx.getDisplay(hs.overlay).name if self.smooth or self.plotType == 'centre': self.drawDataSeries(hss) # use a step plot when plotting bin edges else: self.drawDataSeries(hss, drawstyle='steps-pre') self.drawArtists()
def __updateWidgets(self): """Called by the :meth:`__selectedOverlayChanged` and :meth:`__displayOptsChanged` methods. Enables/disables the voxel/world location and volume controls depending on the currently selected overlay (or reference image). """ overlay = self.__registeredOverlay opts = self.__registeredOpts if overlay is not None: refImage = opts.referenceImage else: refImage = None haveRef = refImage is not None self.__voxelX .Enable(haveRef) self.__voxelY .Enable(haveRef) self.__voxelZ .Enable(haveRef) self.__voxelLabel .Enable(haveRef) ###################### # World location label ###################### label = strings.labels[self, 'worldLocation'] if haveRef: label += strings.anatomy[refImage, 'space', refImage.getXFormCode()] else: label += strings.labels[ self, 'worldLocation', 'unknown'] self.__worldLabel.SetLabel(label) #################################### # Voxel/world location widget limits #################################### # Figure out the limits for the # voxel/world location widgets if haveRef: opts = self.displayCtx.getOpts(refImage) v2w = opts.getTransform('voxel', 'world') shape = refImage.shape[:3] vlo = [0, 0, 0] vhi = np.array(shape) - 1 wlo, whi = transform.axisBounds(shape, v2w) wstep = refImage.pixdim[:3] else: vlo = [0, 0, 0] vhi = [0, 0, 0] wbounds = self.displayCtx.bounds[:] wlo = wbounds[0::2] whi = wbounds[1::2] wstep = [1, 1, 1] log.debug('Setting voxelLocation limits: {} - {}'.format(vlo, vhi)) log.debug('Setting worldLocation limits: {} - {}'.format(wlo, whi)) # Update the voxel and world location limits, # but don't trigger a listener callback, as # this would change the display location. widgets = [self.__worldX, self.__worldY, self.__worldZ] with props.suppress(self, 'worldLocation'), \ props.suppress(self, 'voxelLocation'): for i in range(3): self.voxelLocation.setLimits(i, vlo[i], vhi[i]) self.worldLocation.setLimits(i, wlo[i], whi[i]) widgets[i].SetIncrement(wstep[i])
def _cropModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos): """Called on mouse down events. Calculates the nearest crop box boundary to the mouse click, adjusts the boundary accordingly, and saves the boundary/axis information for subsequent drag events (see :meth:`_cropModeLeftMouseDrag`). """ copts = canvas.opts overlay = self.__overlay if overlay is None: return # What canvas was the click on? if copts.zax == 0: hax, vax = 1, 2 elif copts.zax == 1: hax, vax = 0, 2 elif copts.zax == 2: hax, vax = 0, 1 # Figure out the distances from # the mouse click to each crop # box boundary on the clicked # canvas vox = self.__getVoxel(overlay, canvasPos) hlo, hhi = self.cropBox.getLo(hax), self.cropBox.getHi(hax) vlo, vhi = self.cropBox.getLo(vax), self.cropBox.getHi(vax) # We compare the click voxel # coords with each of the x/y # lo/hi crop box boundaries boundaries = np.array([[hlo, vox[vax]], [hhi, vox[vax]], [vox[hax], vlo], [vox[hax], vhi]]) # In case the voxel is out of bounds, # make sure that the crop box boundary # coordinates are actually in the crop # box (or on an edge). boundaries[:, 0] = np.clip(boundaries[:, 0], hlo, hhi) boundaries[:, 1] = np.clip(boundaries[:, 1], vlo, vhi) # As the display space is set to # this overlay, the display coordinate # system is equivalent to the scaled # voxel coordinate system of the # overlay. So we can just multiply the # 2D voxel coordinates by the # corresponding pixdims to get the # distances in the display coordinate # system. pixdim = overlay.pixdim[:3] scVox = [vox[hax] * pixdim[hax], vox[vax] * pixdim[vax]] boundaries[:, 0] = boundaries[:, 0] * pixdim[hax] boundaries[:, 1] = boundaries[:, 1] * pixdim[vax] # Calculate distance from click to # crop boundaries, and figure out # the screen axis (x/y) and limit # (lo/hi) to be dragged. dists = (boundaries - scVox)**2 dists = np.sqrt(np.sum(dists, axis=1)) axis, limit = np.unravel_index(np.argmin(dists), (2, 2)) voxAxis = [hax, vax][axis] axis = int(axis) limit = int(limit) # Save these for the mouse drag handler self.__dragAxis = voxAxis self.__dragLimit = limit # Update the crop box and location with props.suppress(self, 'cropBox', notify=True): self.cropBox.setLimit(voxAxis, limit, vox[voxAxis]) self.displayCtx.location = canvasPos