def installLookupTable(key): """Attempts to install/save a previously registered lookup table into the ``[settingsbase]/luts`` directory. """ # keyerror if not registered lut = _luts[key] destFile = op.join('luts', '{}.lut'.format(key)) destFile = fslsettings.filePath(destFile) destDir = op.dirname(destFile) log.debug('Installing lookup table {} to {}'.format(key, destFile)) if not op.exists(destDir): os.makedirs(destDir) lut.mapObj.save(destFile) # Update user-added settings lutNames = fslsettings.read('fsleyes.luts', OrderedDict()) lutNames[key] = lut.name fslsettings.write('fsleyes.luts', lutNames) lut.mapFile = destFile lut.installed = True
def realOnLoad(*a): if saveDir and len(paths) > 0: fslsettings.write('loadSaveOverlayDir', op.dirname(paths[-1])) if onLoad is not None: onLoad(pathIdxs, overlays)
def fslDirWarning(parent): """Checks to see if the ``$FSLDIR`` environment variable is set, or if a FSL installation directory has been saved previously. If not, displays a warning via a :class:`.FSLDirDialog`. :arg parent: A ``wx`` parent object. """ if fslplatform.fsldir is not None: return import fsl.utils.settings as fslsettings # Check settings before # prompting the user fsldir = fslsettings.read('fsldir') if fsldir is not None: fslplatform.fsldir = fsldir return from fsleyes_widgets.dialog import FSLDirDialog dlg = FSLDirDialog(parent, 'FSLeyes', fslplatform.os == 'Darwin') if dlg.ShowModal() == wx.ID_OK: fsldir = dlg.GetFSLDir() log.debug('Setting $FSLDIR to {} (specified ' 'by user)'.format(fsldir)) fslplatform.fsldir = fsldir fslsettings.write('fsldir', fsldir)
def test_initialise(): # Assuming that initialise() # has not yet been called assert settings.read('nothing') is None assert settings.read('nothing', 'default') == 'default' settings.write('nothing', 'nothing') settings.delete('nothing') assert settings.readFile('nothing') is None settings.writeFile('nothing', 'nothing') settings.deleteFile('nothing') assert settings.filePath() is None assert settings.readAll() == {} assert settings.listFiles() == [] settings.clear() with tests.testdir() as testdir: settings.initialise(cfgid='test', cfgdir=testdir, writeOnExit=False) assert settings.settings.configID == 'test' assert settings.settings.configDir == testdir settings.write('setting', 'value') assert settings.read('setting') == 'value' assert settings.read('nothing') is None
def __onOk(self, ev): """Called when the *Ok* button is pushed. Prompts the user to select a directory, and then downloads the files. """ files = self.__panel.GetSelectedFiles() if len(files) == 0: self.EndModal(wx.ID_OK) destdir = fslsettings.read('fsleyes.xnat.downloaddir', os.getcwd()) dlg = wx.DirDialog(self, strings.labels[self, 'choosedir'], defaultPath=destdir) if dlg.ShowModal() != wx.ID_OK: return destdir = dlg.GetPath() for f in files: dest = self.__panel.DownloadFile(f, op.join(destdir, f.id)) if dest is not None: self.__paths.append(dest) fslsettings.write('fsleyes.xnat.downloaddir', destdir) self.__panel.EndSession() self.EndModal(wx.ID_OK)
def installColourMap(key): """Attempts to install a previously registered colourmap into the ``[settingsbase]/colourmaps/`` directory. """ # keyerror if not registered cmap = _cmaps[key] # TODO I think the colors attribute is only # available on ListedColormap instances, # so if you ever start using different # mpl types, you might need to revisit # this. data = cmap.mapObj.colors destFile = op.join('colourmaps', '{}.cmap'.format(key)) log.debug('Installing colour map {} to {}'.format(key, destFile)) # Numpy under python 3 will break if # we give it a file opened with mode='wt'. with fslsettings.writeFile(destFile, mode='b') as f: np.savetxt(f, data, '%0.6f') # Update user-added settings cmapNames = fslsettings.read('fsleyes.colourmaps', OrderedDict()) cmapNames[key] = cmap.name fslsettings.write('fsleyes.colourmaps', cmapNames) cmap.installed = True
def setFSLDIR(self, *args, **kwargs): """Opens a directory dialog allowing the user to select a value for ``$FSLDIR``. """ import wx from fsl.utils.platform import platform import fsl.utils.settings as settings fsldir = platform.fsldir if fsldir is None: fsldir = os.getcwd() msg = strings.titles[self, 'setFSLDIR'] dlg = wx.DirDialog(self, message=msg, defaultPath=fsldir, style=wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST) if dlg.ShowModal() != wx.ID_OK: return fsldir = dlg.GetPath() platform.fsldir = fsldir settings.write('fsldir', fsldir)
def __doExport(self): import wx # Ask the user if they want to save the # x axis values as the first column dlg = wx.MessageDialog( wx.GetTopLevelWindows()[0], message=strings.messages[self, 'saveXColumn'], caption=strings.titles[ self, 'saveXColumn'], style=(wx.ICON_QUESTION | wx.YES_NO | wx.CANCEL | wx.YES_DEFAULT)) result = dlg.ShowModal() if result == wx.ID_CANCEL: return savex = result == wx.ID_YES # Ask the user where they want to save the data msg = strings.messages[self, 'selectFile'] fromDir = fslsettings.read('loadSaveOverlayDir', os.getcwd()) dlg = wx.FileDialog(wx.GetApp().GetTopWindow(), message=msg, defaultDir=fromDir, defaultFile='dataseries.txt', style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() != wx.ID_OK: return filePath = dlg.GetPath() dss = self.__plotPanel.getDrawnDataSeries() xs = [ds[1] for ds in dss] ys = [ds[2] for ds in dss] dss = [ds[0] for ds in dss] # Create some x data, unified # across all data series xdata = np.unique(np.concatenate(xs)) # Linearly interpolate each data series # according to the merged x data ydata = [np.interp(xdata, x, y, left=np.nan, right=np.nan) for x, y in zip(xs, ys)] # Turn it all into one big # array and save it out if savex: data = np.vstack([xdata] + ydata) else: data = np.vstack( ydata) np.savetxt(filePath, data.T, fmt='% 0.8f') fslsettings.write('loadSaveOverlayDir', filePath)
def __doAction(self, script=None): """Called when this :class:`.Action` is invoked. If the ``script`` argument is ``None``, the user is prompted to select a script file. The script is then compiled and executed. """ import wx if script is None: lastDir = fslsettings.read('runScriptLastDir') if lastDir is None: lastDir = os.getcwd() msg = strings.messages[self, 'runScript'] # Ask the user what script # they want to run dlg = wx.FileDialog(self.__frame, message=msg, defaultDir=lastDir, wildcard='*.py', style=wx.FD_OPEN) if dlg.ShowModal() != wx.ID_OK: return script = dlg.GetPath() # Save the script directory for the # next time the user is prompted fslsettings.write('runScriptLastDir', op.dirname(script)) # Run the script, show an # error if it crashes try: runScript(self.__frame, self.__overlayList, self.__displayCtx, script) except Exception as e: log.warning('Script ({}) could not be executed: {}'.format( script, str(e)), exc_info=True) msg = strings.messages[self, 'crash'].format( script, str(e)) wx.MessageDialog(self.__frame, message=msg, style=wx.OK | wx.ICON_ERROR).ShowModal() return
def _addToPerspectivesList(persp): """Adds the given perspective name to the list of saved perspectives. """ persp = persp.strip() perspectives = getAllPerspectives() if persp not in perspectives: perspectives.append(persp) log.debug('Updating stored perspective list: {}'.format(perspectives)) fslsettings.write('fsleyes.perspectives', perspectives)
def _addToLayoutList(layout): """Adds the given layout name to the list of saved layouts. """ layout = layout.strip() layouts = getAllLayouts() if layout not in layouts: layouts.append(layout) log.debug('Updating stored layout list: {}'.format(layout)) fslsettings.write('fsleyes.layouts', layouts)
def __onLoadLut(self, ev): """Called when the user presses the *Load LUT* button. Does the following: - Prompts the user to select a LUT file with a ``wx.FileDialog`` - Prompts the user to enter a name for the LUT via the :func:`promptForLutName` function. - Creates and registers a new :class:`.LookupTable` instance, initialising it with the selected file. - Updates this ``LookupTablePanel`` via the :meth:`__updateLutChoices` and :meth:`__setLut` methods. """ parent = wx.GetApp().GetTopWindow() loadDir = fslsettings.read('fsleyes.loadlutdir', os.getcwd()) # Prompt the user to select a lut file fileDlg = wx.FileDialog(parent, defaultDir=loadDir, message=strings.titles[self, 'loadLut'], style=wx.FD_OPEN) if fileDlg.ShowModal() != wx.ID_OK: return lutFile = fileDlg.GetPath() lutDir = op.dirname(lutFile) lutName = op.splitext(op.basename(lutFile))[0] # Prompt the user to enter a name lutKey, lutName = promptForLutName(lutName) if lutKey is None: return # Register the lut errTitle = strings.titles[self, 'loadError'] errMsg = strings.messages[self, 'loadError'].format(lutFile) with status.reportIfError(errTitle, errMsg): lut = fslcmaps.registerLookupTable(lutFile, self.overlayList, self.displayCtx, key=lutKey, name=lutName) # Save the directory for next time fslsettings.write('fsleyes.loadlutdir', lutDir) # Select the lut in the panel self.__updateLutChoices() self.__setLut(lut)
def _removeFromPerspectivesList(persp): """Removes the given perspective name from the list of saved perspectives. """ perspectives = getAllPerspectives() try: perspectives.remove(persp) except ValueError: return log.debug('Updating stored perspective list: {}'.format(perspectives)) fslsettings.write('fsleyes.perspectives', perspectives)
def _removeFromLayoutList(layout): """Removes the given layout name from the list of saved layouts. """ layouts = getAllLayouts() try: layouts.remove(layout) except ValueError: return log.debug('Updating stored layout list: {}'.format(layouts)) fslsettings.write('fsleyes.layouts', layouts)
def __openBrowser(self): """Opens a :class:`XNATBrowser`, then adds any files that the user selected to the :class:`.OverlayList`. """ hosts = fslsettings.read('fsleyes.xnat.hosts', []) accounts = fslsettings.read('fsleyes.xnat.accounts', {}) dlg = XNATBrowser(self.__frame, hosts, accounts) dlg.Layout() dlg.Fit() dlg.SetSize((-1, 400)) dlg.CentreOnParent() if dlg.ShowModal() != wx.ID_OK: return paths = dlg.GetPaths() hosts = dlg.GetHosts() accounts = dlg.GetAccounts() # No files downloaded if len(paths) == 0: return # Save successful hosts/credentials fslsettings.write('fsleyes.xnat.hosts', hosts) fslsettings.write('fsleyes.xnat.accounts', accounts) def onLoad(paths, overlays): if len(overlays) == 0: return self.__overlayList.extend(overlays) self.__displayCtx.selectedOverlay = \ self.__displayCtx.overlayOrder[-1] if self.__displayCtx.autoDisplay: for overlay in overlays: autodisplay.autoDisplay(overlay, self.__overlayList, self.__displayCtx) loadoverlay.loadOverlays(paths, onLoad=onLoad, saveDir=False, inmem=self.__displayCtx.loadInMemory)
def __loadPlugin(self, *args, **kwargs): """Prompts the user to select a plugin file, asks them whether they would like to install it permanently, and then passes it to either :func:`.loadPlugin` or :func:`.installPlugin`. """ lastDir = fslsettings.read('loadPluginLastDir') if lastDir is None: lastDir = os.getcwd() msg = strings.messages[self, 'loadPlugin'] dlg = wx.FileDialog(self.__frame, message=msg, defaultDir=lastDir, wildcard='*.py', style=wx.FD_OPEN) if dlg.ShowModal() != wx.ID_OK: return fname = dlg.GetPath() fslsettings.write('loadPluginLastDir', op.dirname(fname)) dlg = wx.MessageDialog(self.__frame, caption=strings.titles[self, 'installPlugin'], message=strings.messages[self, 'installPlugin'], style=wx.YES_NO | wx.CANCEL) result = dlg.ShowModal() if result == wx.ID_YES: etitle = strings.titles[self, 'installError'] emsg = strings.messages[self, 'installError'] func = plugins.installPlugin elif result == wx.ID_NO: etitle = strings.titles[self, 'loadError'] emsg = strings.messages[self, 'loadError'] func = plugins.loadPlugin else: return with status.reportIfError(title=etitle, msg=emsg, raiseError=False): func(fname) self.__frame.refreshViewMenu() self.__frame.refreshToolsMenu() self.__frame.refreshSettingsMenu()
def saveLayout(frame, name): """Serialises the layout of the given :class:`.FSLeyesFrame` and saves it as a layout with the given name. """ if name in BUILT_IN_LAYOUTS.keys(): raise ValueError('A built-in layout named "{}" ' 'already exists'.format(name)) log.debug('Saving current layout with name {}'.format(name)) layout = serialiseLayout(frame) fslsettings.write('fsleyes.layouts.{}'.format(name), layout) _addToLayoutList(name) log.debug('Serialised layout:\n{}'.format(layout))
def savePerspective(frame, name): """Serialises the layout of the given :class:`.FSLeyesFrame` and saves it as a perspective with the given name. """ if name in BUILT_IN_PERSPECTIVES.keys(): raise ValueError('A built-in perspective named "{}" ' 'already exists'.format(name)) log.debug('Saving current perspective with name {}'.format(name)) persp = serialisePerspective(frame) fslsettings.write('fsleyes.perspectives.{}'.format(name), persp) _addToPerspectivesList(name) log.debug('Serialised perspective:\n{}'.format(persp))
def __saveKnownAtlases(self): """Saves the IDs and paths of all atlases which are currently in the registry. The atlases are saved via the :mod:`.settings` module. """ if self.__atlasDescs is None: return atlases = [] for desc in self.__atlasDescs: atlases.append((desc.atlasID, desc.specPath)) atlases = ['{}={}'.format(name, path) for name, path in atlases] atlases = op.pathsep.join(atlases) fslsettings.write('fsl.data.atlases', atlases)
def recordPath(self, path): """Adds the given ``path`` to the recent files list. """ recent = self.listRecentPaths() if path in recent: return recent.append(path) if len(recent) > 10: recent = recent[-10:] recent = op.pathsep.join(recent) fslsettings.write('fsleyes.recentFiles', recent) self.notify()
def postLoad(completed): # Did the user cancel the progress dialog? if not completed: return # Did an error occur in the load step above? if isinstance(images[0], Exception): errTitle = strings.titles['loadDicom.loadError'] errMsg = strings.messages['loadDicom.loadError'] status.reportError(errTitle, errMsg, images[0]) return fslsettings.write('loadSaveOverlayDir', op.dirname(dcmdir.rstrip(op.sep))) if callback is not None: callback(images)
def _onLoadDir(self, ev=None): """Called when the user pushes the *load data directory* button. Prompts the user to select a directory, then calls the :meth:`__loadTree` method. """ msg = strings.messages[self, 'loadDir'] fromDir = fslsettings.read('loadSaveOverlayDir') dlg = wx.DirDialog(wx.GetApp().GetTopWindow(), message=msg, defaultPath=fromDir, style=wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST) if dlg.ShowModal() != wx.ID_OK: return dirname = dlg.GetPath() treename = self._getTreeChoice() fslsettings.write('loadSaveOverlayDir', dirname) self._loadTree(treename, dirname)
def __onLoadButton(self, ev): """Called when the *Load labels* button is pushed. Prompts the user to select a label file to load, then does the following: 1. If the selected label file refers to the currently selected melodic_IC overlay, the labels are applied to the overlay. 2. If the selected label file refers to a different melodic_IC overlay, the user is asked whether they want to load the different melodic_IC file (the default), or whether they want the labels applied to the existing overlay. 3. If the selected label file does not refer to any overlay (it only contains the bad component list), the user is asked whether they want the labels applied to the current melodic_IC overlay. If the number of labels in the file is less than the number of melodic_IC components, the remaining components are labelled as unknown. If the number of labels in the file is greater than the number of melodic_IC components, an error is shown, and nothing is done. """ # The aim of the code beneath the # applyLabels function is to load # a set of component labels, and # to figure out which overlay # they should be added to. # When it has done this, it calls # applyLabels, which applies the # loaded labels to the overlay. def applyLabels(labelFile, overlay, allLabels, newOverlay): # labelFile: Path to the loaded label file # overlay: Overlay to apply them to # allLabels: Loaded labels (list of (component, [label]) tuples) # newOverlay: True if the selected overlay has changed, False # otherwise lut = self.__lut volLabels = self.overlayList.getData(overlay, 'VolumeLabels') ncomps = volLabels.numComponents() nlabels = len(allLabels) # Error: number of labels in the # file is greater than the number # of components in the overlay. if ncomps < nlabels: msg = strings.messages[self, 'wrongNComps'].format( labelFile, overlay.dataSource) title = strings.titles[ self, 'loadError'] wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK) return # Number of labels in the file is # less than number of components # in the overlay - we pad the # labels with 'Unknown' elif ncomps > nlabels: for i in range(nlabels, ncomps): allLabels.append(['Unknown']) # Disable notification while applying # labels so the component/label grids # don't confuse themselves. with volLabels.skip(self.__componentGrid.name), \ volLabels.skip(self.__labelGrid .name): volLabels.clear() for comp, lbls in enumerate(allLabels): for lbl in lbls: volLabels.addLabel(comp, lbl) # Make sure a colour in the melodic # lookup table exists for all labels for label in volLabels.getAllLabels(): label = volLabels.getDisplayLabel(label) lutLabel = lut.getByName(label) if lutLabel is None: log.debug('New melodic classification ' 'label: {}'.format(label)) lut.new(label, colour=fslcm.randomBrightColour()) # New overlay was loaded if newOverlay: # Make sure the new image is selected. with props.skip(self.displayCtx, 'selectedOverlay', self.name): self.displayCtx.selectOverlay(overlay) self.__componentGrid.setOverlay(overlay) self.__labelGrid .setOverlay(overlay) # Labels were applied to # already selected overlay. else: self.__componentGrid.refreshTags() self.__labelGrid .refreshTags() # If the current overlay is a compatible # Image, the open file dialog starting # point will be its directory. overlay = self.__overlay if overlay is not None and overlay.dataSource is not None: loadDir = op.dirname(overlay.dataSource) # Otherwise it will be the most # recent overlay load directory. else: loadDir = fslsettings.read('loadSaveOverlayDir', os.getcwd()) # Ask the user to select a label file dlg = wx.FileDialog( self, message=strings.titles[self, 'loadDialog'], defaultDir=loadDir, style=wx.FD_OPEN) # User cancelled the dialog if dlg.ShowModal() != wx.ID_OK: return # Load the specified label file filename = dlg.GetPath() emsg = strings.messages[self, 'loadError'].format(filename) etitle = strings.titles[ self, 'loadError'] try: with status.reportIfError(msg=emsg, title=etitle): melDir, allLabels = fixlabels.loadLabelFile(filename) except Exception: return # Ok we've got the labels, now # we need to figure out which # overlay to add them to. # If the label file does not refer # to a Melodic directory, and the # current overlay is a compatible # image, apply the labels to the # image. if overlay is not None and melDir is None: applyLabels(filename, overlay, allLabels, False) return # If the label file refers to a # Melodic directory, and the # current overlay is a compatible # image. if overlay is not None and melDir is not None: if isinstance(overlay, fslmelimage.MelodicImage): overlayDir = overlay.getMelodicDir() elif overlay.dataSource is not None: overlayDir = op.dirname(overlay.dataSource) else: overlayDir = 'none' # And both the current overlay and # the label file refer to the same # directory, then we apply the # labels to the curent overlay. if op.abspath(melDir) == op.abspath(overlayDir): applyLabels(filename, overlay, allLabels, False) return # Otherwise, if the overlay and the # label file refer to different # directories... # Ask the user whether they want to load # the image specified in the label file, # or apply the labels to the currently # selected image. dlg = wx.MessageDialog( self, strings.messages[self, 'diffMelDir'].format( melDir, overlayDir), style=wx.ICON_QUESTION | wx.YES_NO | wx.CANCEL) dlg.SetYesNoLabels( strings.messages[self, 'diffMelDir.labels'], strings.messages[self, 'diffMelDir.overlay']) response = dlg.ShowModal() # User cancelled the dialog if response == wx.ID_CANCEL: return # User chose to load the melodic # image specified in the label # file. We'll carry on with this # processing below. elif response == wx.ID_YES: pass # Apply the labels to the current # overlay, even though they are # from different analyses. else: applyLabels(filename, overlay, allLabels, False) return # If we've reached this far, we are # going to attempt to identify the # image associated with the label # file, load that image, and then # apply the labels. # The label file does not # specify a melodic directory if melDir is None: msg = strings.messages[self, 'noMelDir'].format(filename) title = strings.titles[ self, 'loadError'] wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK) return # Try loading the melodic_IC image # specified in the label file. try: overlay = fslmelimage.MelodicImage(melDir) log.debug('Adding {} to overlay list'.format(overlay)) with props.skip(self.overlayList, 'overlays', self.name),\ props.skip(self.displayCtx, 'selectedOverlay', self.name): self.overlayList.append(overlay) if self.displayCtx.autoDisplay: autodisplay.autoDisplay(overlay, self.overlayList, self.displayCtx) fslsettings.write('loadSaveOverlayDir', op.abspath(melDir)) except Exception as e: e = str(e) msg = strings.messages[self, 'loadError'].format(filename, e) title = strings.titles[ self, 'loadError'] log.debug('Error loading classification file ' '({}), ({})'.format(filename, e), exc_info=True) wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK) # Apply the loaded labels # to the loaded overlay. applyLabels(filename, overlay, allLabels, True)
def __copyOverlay(self): """Creates a copy of the currently selected overlay, and inserts it into the :class:`.OverlayList`. """ import wx overlay = self.__displayCtx.getSelectedOverlay() if overlay is None: return # TODO support for other overlay types if type(overlay) != fslimage.Image: raise RuntimeError('Currently, only {} instances can be ' 'copied'.format(fslimage.Image.__name__)) display = self.__displayCtx.getDisplay(overlay) # We ask the user questions three: # - Copy data, or create an empty (a.k.a. mask) image? # - Copy display settings? # - For 4D, copy 4D, or just the current 3D volume? # # Here we build a list of # questions and initial states. options = [] states = [] createMaskSetting = 'fsleyes.actions.copyoverlay.createMask' copyDisplaySetting = 'fsleyes.actions.copyoverlay.copyDisplay' copy4DSetting = 'fsleyes.actions.copyoverlay.copy4D' createMask = fslsettings.read(createMaskSetting, False) copyDisplay = fslsettings.read(copyDisplaySetting, False) copy4D = fslsettings.read(copy4DSetting, False) is4D = len(overlay.shape) > 3 and overlay.shape[3] > 1 options.append(strings.messages['actions.copyoverlay.createMask']) states.append(createMask) options.append(strings.messages['actions.copyoverlay.copyDisplay']) states.append(copyDisplay) if is4D: options.append(strings.messages['actions.copyoverlay.copy4D']) states.append(copy4D) # Ask the user what they want to do dlg = fsldlg.CheckBoxMessageDialog(self.__frame, title=strings.actions[self], message='Copy {}'.format( display.name), cbMessages=options, cbStates=states, yesText='OK', cancelText='Cancel', focus='yes') if dlg.ShowModal() != wx.ID_YES: return createMask = dlg.CheckBoxState(0) copyDisplay = dlg.CheckBoxState(1) if is4D: copy4D = dlg.CheckBoxState(2) fslsettings.write(createMaskSetting, createMask) fslsettings.write(copyDisplaySetting, copyDisplay) if is4D: fslsettings.write(copy4DSetting, copy4D) copyImage(self.__overlayList, self.__displayCtx, overlay, createMask=createMask, copy4D=copy4D, copyDisplay=copyDisplay)
def initialise(splash, namespace, callback): """Called by :func:`main`. Bootstraps/Initialises various parts of *FSLeyes*. The ``callback`` function is asynchronously called when the initialisation is complete. :arg splash: The :class:`.FSLeyesSplash` screen. :arg namespace: The ``argparse.Namespace`` object containing parsed command line arguments. :arg callback: Function which is called when initialisation is done. """ import fsl.utils.settings as fslsettings import fsleyes_props as props import fsleyes.gl as fslgl props.initGUI() # The save/load directory defaults # to the current working directory. curDir = op.normpath(os.getcwd()) # But if we are running as a frozen application, check to # see if FSLeyes has been started by the system (e.g. # double-clicking instead of being called from the CLI). # # If so, we set the save/load directory # to the user's home directory instead. if fwidgets.frozen(): fsleyesDir = op.dirname(__file__) # If we're a frozen OSX application, # we need to adjust the FSLeyes dir # (which will be: # [install_dir]/FSLeyes.app/Contents/MacOS/fsleyes/), # # Because the cwd will default to: # [install_dir]/ if fslplatform.os == 'Darwin': fsleyesDir = op.normpath( op.join(fsleyesDir, '..', '..', '..', '..')) # Similar adjustment for linux elif fslplatform.os == 'Linux': fsleyesDir = op.normpath(op.join(fsleyesDir, '..')) if curDir == fsleyesDir: curDir = op.expanduser('~') fslsettings.write('loadSaveOverlayDir', curDir) # Initialise silly things if namespace.bumMode: import fsleyes.icons as icons icons.BUM_MODE = True # Set notebook server port fslsettings.write('fsleyes.notebook.port', namespace.notebookPort) # This is called by fsleyes.gl.getGLContext # when the GL context is ready to be used. def realCallback(): fslgl.bootstrap(namespace.glversion) callback() try: # Force the creation of a wx.glcanvas.GLContext object, # and initialise OpenGL version-specific module loads. fslgl.getGLContext(ready=realCallback) except Exception: log.error('Unable to initialise OpenGL!', exc_info=True) splash.Destroy() sys.exit(1)
def __loadColourMap(self): """This method does the following: 1. Prompts the user to select a colour map file 2. Prompts the user to name the new colour map. 3. Registers the colour map with the :mod:`.colourmaps` module. 4. Asks the user if they want the colour map installed, and installs it if they do. """ import wx app = wx.GetApp() # Get the most recent colour map # directory if there is one loadDir = fslsettings.read('fsleyes.loadcolourmapdir', os.getcwd()) # prompt the user to choose a colour map file dlg = wx.FileDialog(app.GetTopWindow(), defaultDir=loadDir, message=strings.messages[self, 'loadcmap'], style=wx.FD_OPEN) if dlg.ShowModal() != wx.ID_OK: return # prompt the user to choose a name for the colour # map (using the filename prefix as the default) cmapFile = dlg.GetPath() cmapDir = op.dirname(cmapFile) cmapName = op.splitext(op.basename(cmapFile))[0] cmapNameMsg = strings.messages[self, 'namecmap'] cmapNameTitle = strings.titles[self, 'namecmap'] while True: dlg = wx.TextEntryDialog(app.GetTopWindow(), cmapNameMsg, cmapNameTitle, cmapName) if dlg.ShowModal() != wx.ID_OK: return cmapName = dlg.GetValue() cmapKey = fslcmap.makeValidMapKey(cmapName) # a colour map with the specified name already exists if fslcmap.isColourMapRegistered(cmapKey): cmapNameMsg = strings.messages[self, 'alreadyinstalled'] continue break # register the selected colour map file fslcmap.registerColourMap(cmapFile, self.overlayList, self.displayCtx, key=cmapKey, name=cmapName) # Save the directory for next time fslsettings.write('fsleyes.loadcolourmapdir', cmapDir) # ask the user if they want to install # the colour map for future use dlg = wx.MessageDialog(app.GetTopWindow(), caption=strings.titles[self, 'installcmap'], message=strings.messages[self, 'installcmap'], style=wx.YES_NO) if dlg.ShowModal() != wx.ID_YES: return # install the colour map etitle = strings.titles[self, 'installerror'] emsg = strings.messages[self, 'installerror'] with status.reportIfError(title=etitle, msg=emsg, raiseError=False): fslcmap.installColourMap(cmapKey)
def saveOverlay(overlay, display=None): """Saves the currently selected overlay (only if it is a :class:`.Image`), by a call to :meth:`.Image.save`. If a ``display`` is provided, the :attr:`.Display.name` may be updated to match the new overlay file name. :arg overlay: The :class:`.Image` overlay to save :arg display: The :class:`.Display` instance associated with the overlay. """ import wx # TODO support for other overlay types if not isinstance(overlay, fslimage.Image): raise RuntimeError('Non-volumetric types not supported yet') # If this image has been loaded from a file, # ask the user whether they want to overwrite # that file, or save the image to a new file. # if overlay.dataSource is not None: # If the data source is not nifti (e.g. # mgz), we are not going to overwrite it, # so we don't ask. if fslimage.looksLikeImage(overlay.dataSource): msg = strings.messages['SaveOverlayAction.overwrite'].format( overlay.dataSource) title = strings.titles['SaveOverlayAction.overwrite'].format( overlay.dataSource) dlg = wx.MessageDialog(wx.GetTopLevelWindows()[0], message=msg, caption=title, style=(wx.ICON_WARNING | wx.YES_NO | wx.CANCEL | wx.NO_DEFAULT)) dlg.SetYesNoCancelLabels( strings.labels['SaveOverlayAction.overwrite'], strings.labels['SaveOverlayAction.saveNew'], strings.labels['SaveOverlayAction.cancel']) response = dlg.ShowModal() # Cancel == cancel the save # Yes == overwrite the existing file # No == save to a new file (prompt the user for the file name) if response == wx.ID_CANCEL: return if response == wx.ID_YES: doSave(overlay) return fromDir = op.dirname(overlay.dataSource) filename = fslimage.removeExt(op.basename(overlay.dataSource)) filename = '{}_copy'.format(filename) else: fromDir = fslsettings.read('loadSaveOverlayDir', os.getcwd()) if display is not None: filename = display.name else: filename = overlay.name filename = filename.replace('/', '_') filename = filename.replace('\\', '_') # Ask the user where they # want to save the image msg = strings.titles['SaveOverlayAction.saveFile'] dlg = wx.FileDialog(wx.GetApp().GetTopWindow(), message=msg, defaultDir=fromDir, defaultFile=filename, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() != wx.ID_OK: return # Make sure that the user chose a supported # extension. If not, use the default extension. savePath = dlg.GetPath() prefix, suffix = fslimage.splitExt(savePath) if suffix == '': savePath = '{}{}'.format(prefix, fslimage.defaultExt()) oldPath = overlay.dataSource saveDir = op.dirname(savePath) if doSave(overlay, savePath): # Cache the save directory for next time. fslsettings.write('loadSaveOverlayDir', saveDir) # If image was in memory, or its old # name equalled the old datasource # base name, update its name. if oldPath is None or \ fslimage.removeExt(op.basename(oldPath)) == overlay.name: overlay.name = fslimage.removeExt(op.basename(savePath)) if display is not None: display.name = overlay.name
def __doImport(self): import wx frame = wx.GetApp().GetTopWindow() # Ask the user where to get the data msg = strings.messages[self, 'selectFile'] fromDir = fslsettings.read('loadSaveOverlayDir', os.getcwd()) dlg = wx.FileDialog(frame, message=msg, defaultDir=fromDir, style=wx.FD_OPEN) if dlg.ShowModal() != wx.ID_OK: return filePath = dlg.GetPath() fileName = op.basename(filePath) # Load the file, show an # error if it fails try: # Assuming that the data series # to plot are stored as columns data = np.loadtxt(filePath, dtype=np.float).T # Make sure the data is 2D, to # make code below easier and # happier. if len(data.shape) == 1: data = data.reshape((1, -1)) except Exception as e: title = strings.titles[self, 'error'] msg = strings.messages[self, 'error'].format( filePath, '{}: {}'.format(type(e).__name__, str(e))) wx.MessageBox(msg, title, wx.ICON_ERROR | wx.OK) return fslsettings.write('loadSaveOverlayDir', filePath) # Ask the user the x axis scaling factor. # If the currently selected overlay is # Nifti and 4D, default to its pixdim[3] overlay = self.__displayCtx.getSelectedOverlay() if overlay is not None and \ isinstance(overlay, fslimage.Nifti) and \ len(overlay.shape) == 4 and \ self.__plotPanel.usePixdim: xscale = overlay.pixdim[3] else: xscale = 1 title = strings.titles[self, 'selectXScale'] msg = strings.messages[self, 'selectXScale'] # If the user pushes 'Ok', the entered value # is used as a fixed X axis interval. Otherwise, # it is assumed that the first column in the # file is the x axis data. dlg = numdlg.NumberDialog(frame, title=title, message=msg, initial=xscale, minValue=1e-5, cancelText=strings.labels[self, 'firstColumnIsX']) firstColumnIsX = dlg.ShowModal() != wx.ID_OK xscale = dlg.GetValue() # Add the data series series = [] if firstColumnIsX: xdata = data[0, :] ydata = data[1:, :] else: xdata = np.arange(0, data.shape[1] * xscale, xscale, dtype=np.float) ydata = data for i, ydata in enumerate(ydata): x = np.array(xdata) y = np.array(ydata) fin = np.isfinite(x) & np.isfinite(y) x = x[fin] y = y[fin] ds = plotting.DataSeries(None, self.__overlayList, self.__displayCtx, self.__plotPanel) ds.setData(x, y) # If we recognise the file name, # we can give it a useful label. label = strings.plotLabels.get('{}.{}'.format(fileName, i), '{} [{}]'.format(fileName, i)) ds.label = label ds.lineWidth = 1 ds.colour = fslcm.randomDarkColour() series.append(ds) self.__plotPanel.dataSeries.extend(series)
def __doScreenshot(self): """Capture a screenshot. Prompts the user to select a file to save the screenshot to, and then calls the :func:`screenshot` function. """ lastDirSetting = 'fsleyes.actions.screenshot.lastDir' # Ask the user where they want # the screenshot to be saved fromDir = fslsettings.read(lastDirSetting, os.getcwd()) # We can get a list of supported output # types via a matplotlib figure object fig = plt.figure() fmts = fig.canvas.get_supported_filetypes() # Default to png if # it is available if 'png' in fmts: fmts = [('png', fmts['png'])] + \ [(k, v) for k, v in fmts.items() if k != 'png'] else: fmts = list(fmts.items()) wildcard = [ '[*.{}] {}|*.{}'.format(fmt, desc, fmt) for fmt, desc in fmts ] wildcard = '|'.join(wildcard) filename = 'screenshot' dlg = wx.FileDialog(self.__panel, message=strings.messages[self, 'screenshot'], wildcard=wildcard, defaultDir=fromDir, defaultFile=filename, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() != wx.ID_OK: return filename = dlg.GetPath() # Make the dialog go away before # the screenshot gets taken dlg.Close() dlg.Destroy() wx.GetApp().Yield() # Show an error if the screenshot # function raises an error doScreenshot = status.reportErrorDecorator( strings.titles[self, 'error'], strings.messages[self, 'error'])(screenshot) # We do the screenshot asynchronously, # to make sure it is performed on # the main thread, during idle time idle.idle(doScreenshot, self.__panel, filename) status.update(strings.messages[self, 'pleaseWait'].format(filename)) fslsettings.write(lastDirSetting, op.dirname(filename))
def __copyOverlay(self): """Creates a copy of the currently selected overlay, and inserts it into the :class:`.OverlayList`. """ import wx overlay = self.displayCtx.getSelectedOverlay() if overlay is None: return # TODO support for other overlay types if type(overlay) != fslimage.Image: raise RuntimeError('Currently, only {} instances can be ' 'copied'.format(fslimage.Image.__name__)) display = self.displayCtx.getDisplay(overlay) # We ask the user questions four: # - Copy data, or create an empty (a.k.a. mask) image? # - Copy display settings? # - For 4D, copy 4D, or just the current 3D volume? # - For complex/RGB(A), copy as single channel, or multi-channel? # # Here we build a list of # questions and initial states. options = [] states = [] createMaskSetting = 'fsleyes.actions.copyoverlay.createMask' copyDisplaySetting = 'fsleyes.actions.copyoverlay.copyDisplay' copy4DSetting = 'fsleyes.actions.copyoverlay.copy4D' copyMultiSetting = 'fsleyes.actions.copyoverlay.copyMulti' createMask = fslsettings.read(createMaskSetting, False) copyDisplay = fslsettings.read(copyDisplaySetting, False) copy4D = fslsettings.read(copy4DSetting, False) copyMulti = fslsettings.read(copy4DSetting, True) is4D = len(overlay.shape) > 3 and overlay.shape[3] > 1 isMulti = overlay.iscomplex or overlay.nvals > 1 options.append(strings.messages['actions.copyoverlay.createMask']) states.append(createMask) options.append(strings.messages['actions.copyoverlay.copyDisplay']) states.append(copyDisplay) if is4D: options.append(strings.messages['actions.copyoverlay.copy4D']) states.append(copy4D) if isMulti: options.append(strings.messages['actions.copyoverlay.copyMulti']) states.append(copyMulti) # Ask the user what they want to do dlg = fsldlg.CheckBoxMessageDialog(self.__frame, title=strings.actions[self], message='Copy {}'.format( display.name), cbMessages=options, cbStates=states, yesText='OK', cancelText='Cancel', focus='yes') if dlg.ShowModal() != wx.ID_YES: return createMask = dlg.CheckBoxState(0) copyDisplay = dlg.CheckBoxState(1) if is4D: copy4D = dlg.CheckBoxState(2) if isMulti: copyMulti = dlg.CheckBoxState(3 if is4D else 2) fslsettings.write(createMaskSetting, createMask) fslsettings.write(copyDisplaySetting, copyDisplay) if is4D: fslsettings.write(copy4DSetting, copy4D) if isMulti: fslsettings.write(copyMultiSetting, copyMulti) # If the user de-selected copy all channels, # ask them which channel they want to copy channel = None if isMulti and (not copyMulti): if overlay.iscomplex: choices = ['real', 'imag'] else: choices = ['R', 'G', 'B', 'A'][:overlay.nvals] labels = [strings.choices[self, 'component'][c] for c in choices] title = strings.titles['actions.copyoverlay.component'] msg = strings.messages['actions.copyoverlay.component'] dlg = wx.SingleChoiceDialog(self.__frame, msg, title, choices=labels) if dlg.ShowModal() != wx.ID_OK: return channel = choices[dlg.GetSelection()] copyImage(self.overlayList, self.displayCtx, overlay, createMask=createMask, copy4D=copy4D, channel=channel, copyDisplay=copyDisplay)