def getGLContext(**kwargs): """Create and return a GL context object for on- or off-screen OpenGL rendering. If a context object has already been created, it is returned. Otherwise, one is created and returned. See the :class:`GLContext` class for details on the arguments. .. warning:: Use the ``ready`` argument to :meth:`GLContext.__init__`, and don't call :func:`bootstrap` until it has been called! """ import sys thismod = sys.modules[__name__] # A context has already been created if hasattr(thismod, '_glContext'): # If a callback was provided, # make sure it gets called. callback = kwargs.pop('ready', None) if callback is not None: idle.idle(callback) return thismod._glContext thismod._glContext = GLContext(**kwargs) return thismod._glContext
def _test_screenshot(panel, overlayList, displayCtx, stype, imgfile): import matplotlib.image as mplimg import fsleyes.actions.screenshot as screenshot import fsleyes.views.orthopanel as orthopanel if isinstance(panel, orthopanel.OrthoPanel): panel.sceneOpts.showCursor = False panel.sceneOpts.showLabels = False img = fslimage.Image(op.join(datadir, imgfile)) overlayList.append(img) with tempdir(): fname = 'test_screenshot_{}.png'.format(stype) realYield(100) idle.idle(screenshot.screenshot, panel, fname) realYield() bfname = op.join(datadir, 'test_screenshot_{}.png'.format(stype)) screenshot = mplimg.imread(fname) benchmark = mplimg.imread(bfname) result, diff = compare_images(screenshot, benchmark, 50) print('Comparing {} with {}: {}'.format(fname, bfname, diff)) assert result
def __volumeChanged(self, *a): """Called when the :attr:`.NiftiOpts.volume` property changes. Selects the corresponding row in the :class:`.WidgetGrid`. """ # Only change the row if we are # currently visible, otherwise # this will screw up the focus. if not self.IsShown(): return grid = self.__grid opts = self.displayCtx.getOpts(self.__overlay) log.debug('Overlay volume changed ({}) - updating ' 'selected component'.format(opts.volume)) # The setOverlay method updates the grid size on # the idle loop when the selected overlay changes, # so we have to update the selection on idle too, # otherwise the following sequence of events: # # 1. Overlay change (asynchronously schedules 3) # 2. Volume change (directly calls SetSelection on # wrongly-sized grid) # 3. Grid refresh # # may raise an error idle.idle(grid.SetSelection, opts.volume, -1)
def _navModeChar(self, ev, canvas, key): """Handles key presses in ``nav`` mode. Arrow key presses in ``nav`` mode update the :attr:`.DisplayContext.location`. Arrow keys map to the horizontal/vertical axes, and -/+ keys map to the depth axis of the canvas which was the target of the event. """ if len(self.overlayList) == 0: return False try: ch = chr(key) except Exception: ch = None dirs = [0, 0, 0] copts = canvas.opts if key == wx.WXK_LEFT: dirs[copts.xax] = -1 elif key == wx.WXK_RIGHT: dirs[copts.xax] = 1 elif key == wx.WXK_UP: dirs[copts.yax] = 1 elif key == wx.WXK_DOWN: dirs[copts.yax] = -1 elif ch in ('+', '='): dirs[copts.zax] = 1 elif ch in ('-', '_'): dirs[copts.zax] = -1 else: return False def update(): self.displayCtx.location.xyz = self.__offsetLocation(*dirs) # See comment in _zoomModeMouseWheel about timeout idle.idle(update, timeout=0.1) return True
def _zoomModeMouseWheel(self, ev, canvas, wheel, mousePos=None, canvasPos=None): """Handles mouse wheel events in ``zoom`` mode. Zooms in/out of the canvas by updating the :attr:`.SceneOpts.zoom` property. """ if wheel > 0: wheel = 50 elif wheel < 0: wheel = -50 else: return False opts = self.viewPanel.sceneOpts # see comment in OrthoViewProfile._zoomModeMouseWheel # about timeout def update(): opts.zoom += wheel idle.idle(update, timeout=0.1) return True
def addLabel(startIdx): # If the user closes this panel while the # label list is being created, wx will # complain when we try to append things # to a widget that has been destroyed. try: for labelIdx in range(startIdx, min(startIdx + blockSize, nlabels)): # A new request to re-create the list has # been made - cancel this creation chain. if self.__labelListCreateKey != myCreateKey: return label = lut[labelIdx] widget = LabelWidget(self, lut, label) self.__labelList.Append(str(label.value), clientData=label.value, extraWidget=widget) if labelIdx == nlabels - 1: status.update('Lookup table label list created.') self.Enable() self.__labelList.Enable() else: idle.idle(addLabel, labelIdx + 1) except RuntimeError: pass
def __textureUpdateLoop(self): """This method is called via the :func:`.idle.idle` function. It loops through all :class:`.RenderTexture` instances, and refreshes any that have been marked as *dirty*. Each call to this method causes one ``RenderTexture`` to be refreshed. After a ``RenderTexture`` has been refreshed, if there are dirty more ``RenderTexture`` instances, this method re-schedules itself to be called again via :func:`.idle.idle`. """ if len(self.__updateQueue) == 0 or len(self.__textures) == 0: return idx = self.__updateQueue.pop(0) if self.__textureDirty[idx]: tex = self.__textures[idx] log.debug('Refreshing texture slice {} (zax {})'.format( idx, self.__zax)) self.__refreshTexture(tex, idx) if len(self.__updateQueue) > 0: idle.idle(self.__textureUpdateLoop)
def __init__(self, globj): """Create a ``RenderTextureStack``. An update listener is registered on the ``GLObject``, so that the textures can be refreshed whenever it changes. :arg globj: The :class:`.GLObject` instance. """ self.name = '{}_{}_{}'.format( type(self).__name__, type(globj).__name__, id(self)) self.__globj = globj self.__maxNumTextures = 256 self.__maxWidth = 1024 self.__maxHeight = 1024 self.__defaultNumTextures = 64 self.__defaultWidth = 256 self.__defaultHeight = 256 self.__textureDirty = [] self.__textures = [] self.__lastDrawnTexture = None self.__updateQueue = [] idle.idle(self.__textureUpdateLoop) log.debug('{}.init ({})'.format(type(self).__name__, id(self)))
def test_idle(): called = [False] def task(arg, kwarg1=None): called[0] = arg == 1 and kwarg1 == 2 def errtask(arg, kwarg1=None): raise Exception('Task which was supposed to crash crashed!') assert idle.idleLoop.callRate > 0 # Run directly _run_without_wx(idle.idle, task, 1, kwarg1=2, name='direct') assert called[0] called[0] = False # Run on wx idle loop _run_with_wx(idle.idle, task, 1, kwarg1=2) assert called[0] # Run a crashing task directly with pytest.raises(Exception): idle.idle(errtask, 1, kwarg1=2) # Run a crashing task on idle loop - error should not propagate _run_with_wx(idle.idle, errtask, 1, kwarg1=2)
def __refreshAllTextures(self, *a): """Marks all :class:`.RenderTexture` instances as *dirty*, so that they will be refreshed by the :meth:`.__textureUpdateLoop`. """ if self.__lastDrawnTexture is not None: lastIdx = self.__lastDrawnTexture else: lastIdx = len(self.__textures) // 2 aboveIdxs = list(range(lastIdx, len(self.__textures))) belowIdxs = list(range(lastIdx - 1, -1, -1)) idxs = [0] * len(self.__textures) for i in range(len(self.__textures)): if len(aboveIdxs) > 0 and len(belowIdxs) > 0: if i % 2: idxs[i] = belowIdxs.pop(0) else: idxs[i] = aboveIdxs.pop(0) elif len(aboveIdxs) > 0: idxs[i] = aboveIdxs.pop(0) else: idxs[i] = belowIdxs.pop(0) self.__textureDirty = [True] * len(self.__textures) self.__updateQueue = idxs idle.idle(self.__textureUpdateLoop)
def calcCorr(): correlations = self.calculateCorrelation(xyz, data) # The correlation overlay is updated/ # created on the main thread. def update(): try: # A correlation overlay already # exists for the source overlay # - update its data if corrOvl is not None: corrOvl[:] = correlations # The correlation overlay hasn't # been created yet - create a # new overlay with the correlation # values. else: self.__createCorrelateOverlay(ovl, correlations) finally: fslstatus.clearStatus() self.__correlateFlag.clear() idle.idle(update)
def _zoomModeMouseWheel(self, ev, canvas, wheel, mousePos=None, canvasPos=None): """Handles mouse wheel events in ``zoom`` mode. Mouse wheel motion in zoom mode increases/decreases the zoom level of the target canvas. """ if wheel > 0: wheel = 50 elif wheel < 0: wheel = -50 copts = canvas.opts minzoom = copts.getAttribute('zoom', 'minval') maxzoom = copts.getAttribute('zoom', 'maxval') # Over SSH/X11, mouse wheel events seem to get queued, # and continue to get processed after the user has # stopped spinning the mouse wheel, which is super # frustrating. So we do the update asynchronously, and # set a time out to drop the event, and prevent the # horribleness from happening. def update(): newZoom = np.clip(copts.zoom + wheel, minzoom, maxzoom) copts.zoom = newZoom idle.idle(update, timeout=0.1) return True
def _viewModeMouseWheel(self, ev, canvas, wheel, mousePos=None, canvasPos=None): """Handles mouse wheel events in ``view`` mode. Updates the :attr:.LightBoxCanvasOpts.topRow` property, thus scrolling through the slices displayed on the canvas. """ # When we scroll up, we move to lower slices, # so a positive scroll direction corresponds # to negative slice direction, and vice versa. if wheel > 0: wheel = -1 elif wheel < 0: wheel = 1 else: return False opts = self.__canvas.opts # See comment in OrthoViewProfile._zoomModeMouseWheel # about timeout def update(): opts.topRow += wheel idle.idle(update, timeout=0.1) return True
def _sliceModeMouseWheel(self, ev, canvas, wheel, mousePos=None, canvasPos=None): """Handles mouse wheel movement in ``slice`` mode. Mouse wheel movement on a canvas changes the depth location displayed on that canvas. """ if len(self.overlayList) == 0: return False dirs = [0, 0, 0] copts = canvas.opts if wheel > 0: dirs[copts.zax] = 1 elif wheel < 0: dirs[copts.zax] = -1 pos = self.__offsetLocation(*dirs) def update(): self.displayCtx.location[copts.zax] = pos[copts.zax] # See comment in _zoomModeMouseWheel about timeout idle.idle(update, timeout=0.1) return True
def __eventloop(self): """Event loop used for the IPython kernel. Calls :meth:`__kernelDispatch`, then schedules a future call to ``__eventloop`` via :func:`.idle.idle` loop. """ self.__kernelDispatch() idle.idle(self.__eventloop, after=self.__kernel._poll_interval)
def test_idle_alwaysQueue4(): # Test scheduling the task when # wx is not present - the task # should just be executed immediately called = [False] def task(): called[0] = True import fsl.utils.platform with mock.patch.dict('sys.modules', {'wx': None}): # idle uses the platform module to # determine whether a GUI is available, # so we have to reload it reload_module(fsl.utils.platform) idle.idle(task, alwaysQueue=True) with pytest.raises(ImportError): import wx reload_module(fsl.utils.platform) assert called[0]
def _test_synchronous(): called = [False] def task(): called[0] = True def test_async(): called[0] = False idle.idle(task) assert not called[0] _wait_for_idle_loop_to_clear() assert called[0] oldval = idle.idleLoop.neverQueue try: idle.idleLoop.neverQueue = False test_async() with idle.idleLoop.synchronous(): called[0] = False idle.idle(task) assert called[0] test_async() finally: idle.idleLoop.neverQueue = oldval
def setOverlay(self, overlay, volLabels, refreshGrid=True): """Sets the :class:`.Image` to display component labels for. The :class:`.WidgetGrid` is re-populated to display the component-label mappings contained in the :class:`.VolumeLabels` instance associated with the overlay. :arg refreshGrid: If ``True`` (the default), the ``WidgetGrid`` displaying component labels is refreshed. This flag is used internally (see :meth:`__overlayTypeChanged`). """ self.__deregisterCurrentOverlay() self.__grid.ClearGrid() if not (isinstance(overlay, fslimage.Image) and len(overlay.shape) == 4): self.__grid.Refresh() return log.debug('Registering new overlay: {}'.format(overlay)) self.__overlay = overlay self.__volLabels = volLabels display = self.displayCtx.getDisplay(overlay) opts = display.opts volLabels.register( self.name, self.__labelsChanged) opts .addListener('volume', self.name, self.__volumeChanged) display .addListener('overlayType', self.name, self.__overlayTypeChanged) # We refresh the component grid on idle, in # case multiple calls to setOverlay are made # in quick succession - only the most recent # request will be executed. def doRefreshGrid(): # The overlay might have been cleared # by the time this function gets called if self.__overlay is None: self.__grid.Refresh() return ncomps = self.__volLabels.numComponents() self.__grid.SetGridSize(ncomps, 2, growCols=[1]) self.__grid.SetColLabel(0, strings.labels[self, 'componentColumn']) self.__grid.SetColLabel(1, strings.labels[self, 'labelColumn']) self.__recreateTags() self.__volumeChanged() if refreshGrid: idle.idle(doRefreshGrid, name='{}_doRefreshGrid'.format(self.name), skipIfQueued=True)
def queuetask(): print('Queuetask running') idle.idle(task1, after=0.01, name=name) idle.idle(task2, after=0.01, name=name, dropIfQueued=True) print('Queuetask finished')
def notify(self, *args, **kwargs): """Notify all registered listeners of this ``Notifier``. The documented arguments must be passed as keyword arguments. :arg topic: The topic on which to notify. Default listeners are always notified, regardless of the specified topic. :arg value: A value passed through to the registered listener functions. If not provided, listeners will be passed a value of ``None``. All other arguments passed to this method are ignored. .. note:: Listeners registered with ``runOnIdle=True`` are called via :func:`idle.idle`. Other listeners are called directly. See :meth:`register`. """ topic = kwargs.get('topic', None) value = kwargs.get('value', None) listeners = self.__getListeners(topic) if len(listeners) == 0: return if log.getEffectiveLevel() <= logging.DEBUG: stack = inspect.stack() frame = stack[1] srcMod = '...{}'.format(frame[1][-20:]) srcLine = frame[2] log.debug('{}: Notifying {} listeners (topic: {}) [{}:{}]'.format( type(self).__name__, len(listeners), topic, srcMod, srcLine)) for listener in listeners: callback = listener.callback name = listener.name # The callback, or the owner of the # callback function may have been # gc'd - remove it if this is the case. if callback is None: log.debug('Listener {} has been gc\'d - ' 'removing from list'.format(name)) self.__listeners[listener.topic].pop(name) elif not listener.enabled: continue elif listener.runOnIdle: idle.idle(callback, self, topic, value) else: callback( self, topic, value)
def __onCancel(self, ev=None): """Called when the *Cancel* button is pushed. Resets the :attr:`.NiftiOpts.displayXform` attribute of the overlay being transformed, and then calls :meth:`.OrthoPanel.toggleEditTransformPanel` to close this panel. """ self.__resetAllOverlays() idle.idle(self.__ortho.toggleEditTransformPanel)
def updateColourBarTexture(self, *a): """Called whenever the colour bar texture needs to be updated. """ def update(): self.__genColourBarTexture() self.Refresh() name = '{}_updateColourBarTexture'.format(id(self)) idle.idle(update, name=name, skipIfQueued=True)
def waitUntilIdle(): called = [False] def flag(): called[0] = True idle.idle(flag) while not called[0]: realYield(50)
def __destroyTextures(self): """Destroys all :class:`.RenderTexture` instances. This is performed asynchronously, via the ``idle.idle`` function. """ texes = self.__textures self.__textures = [] for tex in texes: idle.idle(tex.destroy)
def setOverlay(self, overlay, volLabels): """Set the :class:`.Image` shown on this ``LabelGrid``. A listener is registered with its :class:`.VolumeLabels`, and its component-label mappings displayed on the :class:`.WidgetGrid`. """ self.__deregisterCurrentOverlay() self.__grid.ClearGrid() self.__labelTags.clear() if not (isinstance(overlay, fslimage.Image) and len(overlay.shape) == 4): self.__grid.Refresh() return log.debug('Registering new overlay: {}'.format(overlay)) self.__overlay = overlay self.__volLabels = volLabels volLabels.register(self.name, self.__labelsChanged) # We refresh the label grid on idle, in # case multiple calls to setOverlay are # made in quick succession - only the most # recent request will be executed. def createGrid(): # The grid is initialised with length 0. # Rows for each label are are added in # the __createTags method, which is called # here, and as needed when the LUT or # MelodicClassification for the currently # selected overlay change. self.__grid.SetGridSize(0, 2, [1]) self.__grid.ShowRowLabels(False) self.__grid.ShowColLabels(True) self.__grid.SetColLabel(0, strings.labels[self, 'labelColumn']) self.__grid.SetColLabel(1, strings.labels[self, 'componentColumn']) # The overlay might have been cleared # by the time this function gets called if self.__overlay is None: self.__grid.Refresh() return self.__createTags() self.refreshTags() self.__grid.Refresh() idle.idle(createGrid, name='{}_createGrid'.format(self.name), skipIfQueued=True)
def __kernelDispatch(self): """Event loop used for the IPython kernel. Submits the kernel function to the :func:`.idle.idle` loop, and schedules another call to this method on the ``zmq`` event loop. This means that, while the ``zmq`` loop runs in its own thread, the IPython kernel is executed on the main thread. """ idle.idle(self.__kernel.do_one_iteration) self.__ioloop.call_later(self.__kernel._poll_interval, self.__kernelDispatch)
def __init__(self, parent, overlayList, displayCtx, frame, msg, warnCondition, changeTo): """Create a ``DisplaySpaceWarning``. :arg parent: ``wx`` parent object :arg overlayList: The :class:`.OverlayList` :arg displayCtx: The :class:`.DisplayContext` :arg frame: The :class:`.FSLeyesFrame` :arg msg: Message to display :arg warnCondition: One of ``'world'``, ``'overlay'``, or ``'not overlay'`` :arg changeTo: One of ``'world'`` or ``'overlay'`` """ fslpanel.FSLeyesPanel.__init__(self, parent, overlayList, displayCtx, frame) self.__warnCondition = warnCondition self.__changeTo = changeTo self.__dsWarning = wx.StaticText(self) self.__changeDS = wx.Button(self) self.__dsWarning.SetLabel(msg) self.__changeDS.SetLabel(strings.labels[self, 'changeDS']) self.__dsWarning.SetForegroundColour((192, 0, 0, 255)) self.__sizer = wx.BoxSizer(wx.HORIZONTAL) self.__realSizer = wx.BoxSizer(wx.HORIZONTAL) self.__realSizer.Add((1, 1), flag=wx.EXPAND) self.__realSizer.Add(self.__dsWarning) self.__realSizer.Add((10, 1)) self.__realSizer.Add(self.__changeDS, flag=wx.ALIGN_CENTRE_VERTICAL) self.__realSizer.Add((1, 1), flag=wx.EXPAND) self.__sizer.Add(self.__realSizer, flag=wx.EXPAND) self.SetSizer(self.__sizer) self.__changeDS.Bind(wx.EVT_BUTTON, self.__onChangeDS) displayCtx.addListener('displaySpace', self.name, self.__displaySpaceChanged) displayCtx.addListener('selectedOverlay', self.name, self.__displaySpaceChanged) overlayList.addListener('overlays', self.name, self.__displaySpaceChanged) idle.idle(self.__displaySpaceChanged)
def __onPaint(self, ev): """Called on ``wx.EVT_PAINT`` events. Schedules :meth:`Refresh` to be called on the idle loop. """ def doRefresh(): if fwidgets.isalive(self): self.Refresh() # GL canvases do need to be refreshed # on EVT_PAINT events. If they are not, # the canvas will be corrupted. if not self.__freezeDraw: idle.idle(doRefresh)
def _wait_for_idle_loop_to_clear(): if fslplatform.haveGui: import wx idleDone = [False] def busywait(): idleDone[0] = True idle.idle(busywait) while not idleDone[0]: wx.GetApp().Yield()
def __onCancel(self, ev=None): """Called when the Cancel button is pushed. Calls :meth:`.OrthoPanel.toggleCropMode` - this will result in this ``CropImagePanel`` being destroyed. This method is also called programmatically from the :meth:`__onCrop` method after the image is cropped. """ # Do asynchronously, because we don't want # this CropImagePanel being destroyed from # its own event handler. idle.idle(self.__ortho.toggleCropMode)