Source code for impy.viewer.widgets.explorer

from __future__ import annotations
from qtpy.QtWidgets import (QWidget, QFileSystemModel, QTreeView, QMenu, QAction, QGridLayout, QLineEdit, QPushButton,
                            QFileDialog, QHBoxLayout, QLabel)
from qtpy.QtCore import Qt, QModelIndex
import os
import datetime
import napari
try:
    import pyperclip
except ImportError:
    pass

from .table import read_csv
from .textedit import read_txt
from ..utils import viewer_imread, add_labeledarray
from ...core import imread

[docs]class Explorer(QWidget): """ A Read-only explorer widget. Capable of filter, set working directory, copy path and open file in the viewer. By default QTreeView supports real time update on file change. """ def __init__(self, viewer:"napari.Viewer", path:str=""): super().__init__(viewer.window._qt_window) self.viewer = viewer self.setLayout(QGridLayout()) self._add_change_root() self._add_filetree(path) self._add_filter_line() def _add_change_root(self): self.root_button = QPushButton(self) self.root_button.setText("Change root directory") @self.root_button.clicked.connect def _(): dlg = QFileDialog() dlg.setFileMode(QFileDialog.DirectoryOnly) dlg.setHistory([self.tree.rootpath]) dirname = dlg.getExistingDirectory(self, caption="Select root ...", directory=self.tree.rootpath) if dirname: self.tree.rootpath = dirname self.tree._set_file_model(dirname) napari.utils.history.update_open_history(dirname) return None self.layout().addWidget(self.root_button) return None def _add_filetree(self, path:str=""): """ Add tree view of files with root directory set to ``path``. Parameters ---------- path : str, default is "" Path of the root directory. If not found, current directory will be used. """ path = os.getcwd() if not os.path.exists(path) else path self.tree = FileTree(self, path) self.layout().addWidget(self.tree) return None def _add_filter_line(self): """ Add line edit widget which filters file tree by file names. """ wid = QWidget(self) wid.setLayout(QHBoxLayout()) # add label label = QLabel(self) label.setText("Filter file name: ") self.line = QLineEdit(self) self.line.setToolTip("Filter by names split by comma. e.g. '*.tif, *csv'.") @self.line.editingFinished.connect def _(): self.tree.set_filter(self.line.text()) wid.layout().addWidget(label) wid.layout().addWidget(self.line) self.layout().addWidget(wid)
[docs]class FileTree(QTreeView): def __init__(self, parent, path:str): super().__init__(parent=parent) self.viewer = parent.viewer self.rootpath = path self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.rightClickContextMenu) self.setUniformRowHeights(True) self.header().hide() # Set QFileSystemModel self.file_system = FileSystemModel(self) self.file_system.setReadOnly(True) self.file_system.setNameFilterDisables(False) self._set_file_model(path) # hide columns except for name for i in range(1, self.file_system.columnCount()): self.hideColumn(i) self.doubleClicked.connect(self.onDoubleClicked) self.show() def _set_file_model(self, path:str): self.file_system.setRootPath(path) self.setModel(self.file_system) self.setRootIndex(self.file_system.index(path)) return None
[docs] def rightClickContextMenu(self, point): menu = QMenu(self) open_file = QAction("Open", self) open_file.setShortcut(Qt.Key_Return) @open_file.triggered.connect def _(): index = self.indexAt(point) self.open_path_at(index) copy_path = QAction("Copy path", self) copy_path.setShortcut("Ctrl+C") @copy_path.triggered.connect def _(): index = self.indexAt(point) self.copy_path_at(index) menu.addAction(open_file) menu.addAction(copy_path) menu.exec_(self.mapToGlobal(point)) return None
[docs] def open_path_at(self, index:QModelIndex): path = self.file_system.filePath(index) if os.path.isdir(path): img = imread(os.path.join(path, "*.tif")) add_labeledarray(self.viewer, img) return None _, ext = os.path.splitext(path) if ext in (".tif", ".tiff", ".mrc", ".rec", ".png", ".jpg"): viewer_imread(self.viewer, path) elif ext in (".csv", ".dat"): read_csv(self.viewer, path) elif ext in (".txt",): read_txt(self.viewer, path) return None
[docs] def copy_path_at(self, index:QModelIndex): """ Copy the absolute path of the file at index. Double quotations are included. """ path = self.file_system.filePath(index) pyperclip.copy('"' + path + '"') return None
[docs] def onDoubleClicked(self, index:QModelIndex): path = self.file_system.filePath(index) if os.path.isfile(path): self.open_path_at(index) else: return None
[docs] def set_filter(self, names:str|list[str]): """ Apply filter with comma separated string or list of string as an input. """ if isinstance(names, str): if names == "": names = "*" names = names.split(",") names = [s.strip() for s in names] self.file_system.setNameFilters(names) return None
[docs] def keyPressEvent(self, event): if event.key() == Qt.Key_Return: index = self.selected index is None or self.onDoubleClicked(index) elif event.key() == Qt.Key_C and event.modifiers() == Qt.ControlModifier: index = self.selected index is None or self.copy_path_at(index) else: return super().keyPressEvent(event)
@property def selected(self) -> QModelIndex: inds = self.selectionModel().selectedIndexes() if len(inds) > 0: index = inds[0] else: index = None return index
[docs]class FileSystemModel(QFileSystemModel): """ File system model with tooltips. """
[docs] def data(self, index:QModelIndex, role:int=Qt.DisplayRole): if role == Qt.ToolTipRole: path = self.filePath(index) name = os.path.basename(path) stat = os.stat(path) if os.path.isdir(path): size = "" elif stat.st_size < 10**3: size = f"size: {stat.st_size:.1f} B\n" elif stat.st_size < 10**6: size = f"size: {stat.st_size/10**3:.1f} KB\n" elif stat.st_size < 10**9: size = f"size: {stat.st_size/10**6:.1f} MB\n" else: size = f"size: {stat.st_size/10**9:.1f} GB\n" info = f"name: {name}\n" \ f"{size}" \ f"last accessed: {get_time_stamp(stat.st_atime)}\n" \ f"last modified: {get_time_stamp(stat.st_mtime)}" return info else: return super().data(index, role)
[docs]def get_time_stamp(epoch): t = str(datetime.datetime.fromtimestamp(epoch)) return t.split(".")[0]