class JupyterConsole(BaseExtension): preferences_ui = 'extensions.JupyterConsole.preferences' @BaseExtension.as_thread(wait=500) def event_startup(self): from jupyter_tabwidget import ConsoleTabWidget self.set_busy(True) self._jupyter_console = ConsoleTabWidget(self.main_window) self._dock_widget = QDockWidget(u'Console', self.main_window) self._dock_widget.setObjectName(u'JupyterConsole') self._dock_widget.setWidget(self._jupyter_console) self._dock_widget.closeEvent = self._on_close_event self.main_window.addDockWidget( Qt.BottomDockWidgetArea, self._dock_widget ) self._set_visible(cfg.jupyter_visible) self._shortcut_focus = QShortcut( QKeySequence(cfg.jupyter_focus_shortcut), self.main_window, self._focus, context=Qt.ApplicationShortcut ) self.set_busy(False) def fire(self, event, **kwdict): if event != 'startup': oslogger.debug('ignoring events until after startup') return JupyterConsole.fire = BaseExtension.fire self.fire(event, **kwdict) def activate(self): if not hasattr(self, '_jupyter_console'): oslogger.debug('ignoring activate until after startup') return self._set_visible(not cfg.jupyter_visible) def event_run_experiment(self, fullscreen): oslogger.debug(u'capturing stdout') self._jupyter_console.current.capture_stdout() def event_end_experiment(self, ret_val): self._jupyter_console.current.release_stdout() self._jupyter_console.current.show_prompt() oslogger.debug(u'releasing stdout') def event_jupyter_start_kernel(self, kernel): self._jupyter_console.add(kernel=kernel) def event_jupyter_run_file(self, path, debug=False): self._set_visible(True) if not os.path.isfile(path): return self._jupyter_console.current.change_dir(os.path.dirname(path)) if debug: self._jupyter_console.current.run_debug( path, breakpoints=self.extension_manager.provide( 'pyqode_breakpoints' ) ) else: self._jupyter_console.current.run_file(path) def event_jupyter_change_dir(self, path): self._jupyter_console.current.change_dir(path) def event_jupyter_run_code(self, code, editor=None): self._set_visible(True) self._jupyter_console.current.execute(code) def event_jupyter_run_silent(self, code): self._jupyter_console.current.execute(code) def event_jupyter_run_system_command(self, cmd): self._jupyter_console.current.run_system_command(cmd) def event_jupyter_write(self, msg): try: self._jupyter_console.current.write(msg) except AttributeError: oslogger.error(safe_decode(msg)) def event_jupyter_focus(self): self._jupyter_console.current.focus() def event_jupyter_show_prompt(self): self._jupyter_console.current.show_prompt() def event_jupyter_restart(self): self._jupyter_console.current.restart() def event_jupyter_interrupt(self): self._jupyter_console.current.interrupt() def event_set_workspace_globals(self, global_dict): self._jupyter_console.current.set_workspace_globals(global_dict) def provide_jupyter_workspace_name(self): try: return self._jupyter_console.current.name except AttributeError: return None def provide_workspace_kernel(self): try: return self._jupyter_console.current._kernel except AttributeError: return None def provide_workspace_language(self): try: return self._jupyter_console.current.language except AttributeError: return None def provide_workspace_logging_commands(self): from jupyter_tabwidget.constants import LOGGING_LEVEL_CMD try: kernel = self._jupyter_console.current.language except AttributeError: return None return LOGGING_LEVEL_CMD.get(kernel, None) def provide_jupyter_workspace_globals(self): return self.get_workspace_globals() def provide_jupyter_list_workspace_globals(self): return self.list_workspace_globals() def provide_jupyter_workspace_variable(self, name): return self._jupyter_console.current.get_workspace_variable(name) def provide_jupyter_kernel_running(self): try: return self._jupyter_console.current.running() except AttributeError: return False def provide_jupyter_check_syntax(self, code): return self._jupyter_console.current.check_syntax(code) def get_workspace_globals(self): try: return self._jupyter_console.current.get_workspace_globals() except AttributeError: return {u'no reply': None} def list_workspace_globals(self): try: return self._jupyter_console.current.list_workspace_globals() except Exception as e: print(e) return [] def event_close(self): if not hasattr(self, '_jupyter_console'): oslogger.debug('ignoring close all') return self._jupyter_console.close_all() def _set_visible(self, visible): cfg.jupyter_visible = visible self.set_checked(visible) if visible: self._dock_widget.show() self._jupyter_console.current.focus() else: self._dock_widget.hide() def _focus(self): self._set_visible(True) self._jupyter_console.current.focus() def _on_close_event(self, e): self._set_visible(False)
class BaseMainWindow(QMainWindow): """ Base for main windows of subprograms :ivar settings: store state of application. initial value is obtained from :py:attr:`.settings_class` :ivar files_num: maximal number of files accepted by drag and rop event :param config_folder: path to directory in which application save state. If `settings` parameter is note then settings object is created with passing this path to :py:attr:`.settings_class`. If this parameter and `settings` are None then constructor fail with :py:exc:`ValueError`. :param title: Window default title :param settings: object to store application state :param signal_fun: function which need to be called when window shown. """ show_signal = Signal() """Signal emitted when window has shown. Used to hide Launcher.""" @classmethod def get_setting_class(cls) -> Type[BaseSettings]: """Get constructor for :py:attr:`settings`""" return BaseSettings def __init__( self, config_folder: Union[str, Path, None] = None, title="PartSeg", settings: Optional[BaseSettings] = None, load_dict: Optional[Register] = None, signal_fun=None, ): if settings is None: if config_folder is None: raise ValueError("wrong config folder") if not os.path.exists(config_folder): import_config() settings: BaseSettings = self.get_setting_class()(config_folder) errors = settings.load() if errors: errors_message = QMessageBox() errors_message.setText("There are errors during start") errors_message.setInformativeText( "During load saved state some of data could not be load properly\n" "The files has prepared backup copies in " " state directory (Help > State directory)") errors_message.setStandardButtons(QMessageBox.Ok) text = "\n".join("File: " + x[0] + "\n" + str(x[1]) for x in errors) errors_message.setDetailedText(text) errors_message.exec_() super().__init__() if signal_fun is not None: self.show_signal.connect(signal_fun) self.settings = settings self._load_dict = load_dict self.viewer_list: List[Viewer] = [] self.files_num = 1 self.setAcceptDrops(True) self.setWindowTitle(title) self.title_base = title app = QApplication.instance() if app is not None: app.setStyleSheet(settings.style_sheet) self.settings.theme_changed.connect(self.change_theme) self.channel_info = "" self.multiple_files = None self.settings.request_load_files.connect(self.read_drop) self.recent_file_menu = QMenu("Open recent") self._refresh_recent() self.settings.connect_(FILE_HISTORY, self._refresh_recent) self.settings.napari_settings.appearance.events.theme.connect( self.change_theme) self.settings.set_parent(self) self.console = None self.console_dock = QDockWidget("console", self) self.console_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.BottomDockWidgetArea) # self.console_dock.setWidget(self.console) self.console_dock.hide() self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) def _toggle_console(self): if self.console is None: self.console = QtConsole(self) self.console_dock.setWidget(self.console) self.console_dock.setVisible(not self.console_dock.isVisible()) def _refresh_recent(self): self.recent_file_menu.clear() for name_list, method in self.settings.get_last_files(): action = self.recent_file_menu.addAction( f"{name_list[0]}, {method}") action.setData((name_list, method)) action.triggered.connect(self._load_recent) def _load_recent(self): sender: QAction = self.sender() data = sender.data() try: method: LoadBase = self._load_dict[data[1]] dial = ExecuteFunctionDialog( method.load, [data[0]], exception_hook=load_data_exception_hook) if dial.exec_(): result = dial.get_result() self.main_menu.set_data(result) self.settings.add_last_files(data[0], method.get_name()) self.settings.set(OPEN_DIRECTORY, os.path.dirname(data[0][0])) self.settings.set(OPEN_FILE, data[0][0]) self.settings.set(OPEN_FILE_FILTER, data[1]) except KeyError: self.read_drop(data[0]) def toggle_multiple_files(self): self.settings.set("multiple_files_widget", not self.settings.get("multiple_files_widget")) def get_colormaps(self) -> List[Optional[colormap.Colormap]]: channel_num = self.settings.image.channels if not self.channel_info: return [None for _ in range(channel_num)] colormaps_name = [ self.settings.get_channel_colormap_name(self.channel_info, i) for i in range(channel_num) ] return [ self.settings.colormap_dict[name][0] for name in colormaps_name ] def napari_viewer_show(self): viewer = Viewer(title="Additional output", settings=self.settings, partseg_viewer_name=self.channel_info) viewer.theme = self.settings.theme_name viewer.create_initial_layers(image=True, roi=True, additional_layers=False, points=True) self.viewer_list.append(viewer) viewer.window.qt_viewer.destroyed.connect( lambda x: self.close_viewer(viewer)) def additional_layers_show(self, with_channels=False): if not self.settings.additional_layers: QMessageBox().information( self, "No data", "Last executed algoritm does not provide additional data") return viewer = Viewer(title="Additional output", settings=self.settings, partseg_viewer_name=self.channel_info) viewer.theme = self.settings.theme_name viewer.create_initial_layers(image=with_channels, roi=False, additional_layers=True, points=False) self.viewer_list.append(viewer) viewer.window.qt_viewer.destroyed.connect( lambda x: self.close_viewer(viewer)) def close_viewer(self, obj): for i, el in enumerate(self.viewer_list): if el == obj: self.viewer_list.pop(i) break # @ensure_main_thread def change_theme(self, event): style_sheet = self.settings.style_sheet app = QApplication.instance() if app is not None: app.setStyleSheet(style_sheet) self.setStyleSheet(style_sheet) def showEvent(self, a0: QShowEvent): self.show_signal.emit() def dragEnterEvent(self, event: QDragEnterEvent): # pylint: disable=R0201 if event.mimeData().hasUrls(): event.acceptProposedAction() def read_drop(self, paths: List[str]): """Function to process loading files by drag and drop.""" self._read_drop(paths, self._load_dict) def _read_drop(self, paths, load_dict): ext_set = {os.path.splitext(x)[1].lower() for x in paths} def exception_hook(exception): if isinstance(exception, OSError): QMessageBox().warning( self, "IO Error", "Disc operation error: " + ", ".join(exception.args), QMessageBox.Ok) for load_class in load_dict.values(): if load_class.partial( ) or load_class.number_of_files() != len(paths): continue if ext_set.issubset(load_class.get_extensions()): dial = ExecuteFunctionDialog(load_class.load, [paths], exception_hook=exception_hook) if dial.exec_(): result = dial.get_result() self.main_menu.set_data(result) self.settings.add_last_files(paths, load_class.get_name()) return QMessageBox.information( self, "No method", "No methods for load files: " + ",".join(paths)) def dropEvent(self, event: QDropEvent): """ Support for load files by drag and drop. At beginning it check number of files and if it greater than :py:attr:`.files_num` it refuse loading. Otherwise it call :py:meth:`.read_drop` method and this method should be overwritten in sub classes """ if not all(x.isLocalFile() for x in event.mimeData().urls()): QMessageBox().warning( self, "Load error", "Not all files are locally. Cannot load data.", QMessageBox.Ok) paths = [x.toLocalFile() for x in event.mimeData().urls()] if self.files_num != -1 and len(paths) > self.files_num: QMessageBox.information( self, "To many files", "currently support only drag and drop one file") return self.read_drop(paths) def show_settings_directory(self): DirectoryDialog( self.settings.json_folder_path, "Path to place where PartSeg store the data between runs").exec_() @staticmethod def show_about_dialog(): """Show about dialog.""" AboutDialog().exec_() @staticmethod def get_project_info(file_path, image): raise NotADirectoryError() def image_adjust_exec(self): dial = ImageAdjustmentDialog(self.settings.image) if dial.exec_(): algorithm = dial.result_val.algorithm dial2 = ExecuteFunctionDialog(algorithm.transform, [], { "image": self.settings.image, "arguments": dial.result_val.values }) if dial2.exec_(): result: Image = dial2.get_result() self.settings.set_project_info( self.get_project_info(result.file_path, result)) def closeEvent(self, event: QCloseEvent): for el in self.viewer_list: el.close() del el self.settings.napari_settings.appearance.events.theme.disconnect( self.change_theme) self.settings.dump() super().closeEvent(event) def screenshot(self, viewer: ImageView): def _screenshot(): data = viewer.viewer_widget.screenshot() dial = PSaveDialog( SaveScreenshot, settings=self.settings, system_widget=False, path="io.save_screenshot", file_mode=PSaveDialog.AnyFile, ) if not dial.exec_(): return res = dial.get_result() res.save_class.save(res.save_destination, data, res.parameters) return _screenshot def image_read(self): folder_name, file_name = os.path.split(self.settings.image_path) self.setWindowTitle( f"{self.title_base}: {os.path.join(os.path.basename(folder_name), file_name)}" ) self.statusBar().showMessage(self.settings.image_path) def deleteLater(self) -> None: self.settings.napari_settings.appearance.events.theme.disconnect( self.change_theme) super().deleteLater()
class WorkspaceExplorer(BaseExtension): @BaseExtension.as_thread(wait=2500) def event_startup(self): dm = DataMatrix(length=0) dm.initializing = -1 self._workspace_cache = {} self._label_kernel_not_supported = QLabel('<b>' + _('Kernel not supported') + '</b>') self._label_kernel_not_supported.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self._qdm = WorkspaceMatrix(dm, read_only=True) self._qdm.setFont(QFont(cfg.pyqode_font_name, cfg.pyqode_font_size)) self._qdm.cell_double_clicked.connect(self._inspect_variable) self._dock_widget = QDockWidget(self.main_window) self._dock_widget.setWidget(self._qdm) self._dock_widget.closeEvent = self._on_close_event self._dock_widget.setWindowTitle(_(u'Workspace')) self._dock_widget.setObjectName('WorkspaceExplorer') self._dock_widget.visibilityChanged.connect( self._on_visibility_changed) self.main_window.addDockWidget(Qt.RightDockWidgetArea, self._dock_widget) self._set_visible(cfg.workspace_visible) def _inspect_variable(self, row, column): if self.extension_manager.provide('jupyter_kernel_running'): self.extension_manager.fire( 'notify', message=_(u'Cannot inspect variables in running kernel')) return if not row: return self.extension_manager.fire( 'data_viewer_inspect', name=self._qdm.dm.name[row - 1], workspace=self.extension_manager.provide('jupyter_workspace_name')) def activate(self): if not hasattr(self, '_qdm'): oslogger.info('ignoring activate until after startup') return self._set_visible(not cfg.workspace_visible) def _on_visibility_changed(self, visible): if not visible: return self._update( self.extension_manager.provide('jupyter_workspace_name'), lambda: self.extension_manager.provide('jupyter_workspace_globals')) def _on_close_event(self, e): self._set_visible(False) def _update(self, name, workspace_func): if (not hasattr(self, '_dock_widget') or not self._dock_widget.isVisible()): return self._dock_widget.setWidget(self._qdm) self._dock_widget.setWindowTitle(_(u'Workspace ({})').format(name)) workspace = workspace_func() self._workspace_cache[name] = workspace # If the workspace didn't reply, we try again in a second if workspace is None or workspace.get(u'no reply', False) is None: QTimer.singleShot(1000, lambda: self._update(name, workspace_func)) return # If the current kernel doesn't expose its workspace, indicate this if workspace.get(u'not supported', False) is None: dm = DataMatrix(length=0) dm.kernel_not_supported = -1 self._dock_widget.setWidget(self._label_kernel_not_supported) # Create a DataMatrix that exposes the workspace else: dm = DataMatrix(length=len(workspace)) dm.sorted = False dm.name = '' dm.value = '' dm.shape = '' dm.type = '' for row, (var, data) in zip(dm, workspace.items()): if data is None: oslogger.warning(u'invalid workspace data: {}'.format(var)) continue value, type_, shape = data row.value = value row.name = var if shape is not None: row.shape = repr(shape) row.type = type_ self._qdm.dm = dm self._qdm.refresh() def _set_visible(self, visible): cfg.workspace_visible = visible self.set_checked(visible) if visible: self._dock_widget.show() else: self._dock_widget.hide() def event_workspace_update(self, name, workspace_func): self._update(name, workspace_func) def event_workspace_restart(self, name, workspace_func): self._update(name, workspace_func) def event_workspace_switch(self, name, workspace_func): # When a kernel is running (which includes being a debugging session) # it doesn't respond to silent requests for the workspace. Therefore # we use cached copy of the last update. if self.extension_manager.provide('jupyter_kernel_running'): self._update(name, lambda: self._workspace_cache.get(name, {})) return self._update(name, workspace_func) def event_workspace_new(self, name, workspace_func): self._update(name, workspace_func)
class WorkspaceExplorer(BaseExtension): @BaseExtension.as_thread(wait=2500) def event_startup(self): dm = DataMatrix(length=0) dm.initializing = -1 self._qdm = WorkspaceMatrix(dm, read_only=True) self._qdm.cell_double_clicked.connect(self._inspect_variable) self._dock_widget = QDockWidget(self.main_window) self._dock_widget.setWidget(self._qdm) self._dock_widget.closeEvent = self._on_close_event self._dock_widget.setWindowTitle(_(u'Workspace')) self._dock_widget.setObjectName('WorkspaceExplorer') self._dock_widget.visibilityChanged.connect( self._on_visibility_changed) self.main_window.addDockWidget(Qt.RightDockWidgetArea, self._dock_widget) self._set_visible(cfg.workspace_visible) def _inspect_variable(self, row, column): if not row: return name = self._qdm.dm.name[row - 1] self.extension_manager.fire( 'data_viewer_inspect', name=name, workspace=self.extension_manager.provide('jupyter_workspace_name')) def activate(self): if not hasattr(self, '_qdm'): oslogger.info('ignoring activate until after startup') return self._set_visible(not cfg.workspace_visible) def _on_visibility_changed(self, visible): if not visible: return self._update( self.extension_manager.provide('jupyter_workspace_name'), lambda: self.extension_manager.provide('jupyter_workspace_globals')) def _on_close_event(self, e): self._set_visible(False) def _update(self, name, workspace_func): if (not hasattr(self, '_dock_widget') or not self._dock_widget.isVisible()): return self._dock_widget.setWindowTitle(_(u'Workspace ({})').format(name)) workspace = workspace_func() # If the workspace didn't reply, we try again in a second if workspace is None or workspace.get(u'no reply', False) is None: QTimer.singleShot(1000, lambda: self._update(name, workspace_func)) return # If the current kernel doesn't expose its workspace, indicate this if workspace.get(u'not supported', False) is None: dm = DataMatrix(length=0) dm.kernel_not_supported = -1 # Create a DataMatrix that exposes the workspace else: dm = DataMatrix(length=len(workspace)) dm.sorted = False dm.name = '' dm.value = '' dm.shape = '' dm.type = '' for row, (var, data) in zip(dm, workspace.items()): if data is None: oslogger.warning(u'invalid workspace data: {}'.format(var)) continue value, type_, shape = data row.value = value row.name = var if shape is not None: row.shape = repr(shape) row.type = type_ self._qdm.dm = dm self._qdm.refresh() def _set_visible(self, visible): cfg.workspace_visible = visible self.set_checked(visible) if visible: self._dock_widget.show() else: self._dock_widget.hide() def event_workspace_update(self, name, workspace_func): self._update(name, workspace_func) def event_workspace_restart(self, name, workspace_func): self._update(name, workspace_func) def event_workspace_switch(self, name, workspace_func): self._update(name, workspace_func) def event_workspace_new(self, name, workspace_func): self._update(name, workspace_func)