diff --git a/volumina/widgets/hdf5ExportFileOptionsWidget.py b/volumina/widgets/hierarchicalFileExportOptionsWidget.py similarity index 84% rename from volumina/widgets/hdf5ExportFileOptionsWidget.py rename to volumina/widgets/hierarchicalFileExportOptionsWidget.py index 483fe4df4..b7fb5846c 100644 --- a/volumina/widgets/hdf5ExportFileOptionsWidget.py +++ b/volumina/widgets/hierarchicalFileExportOptionsWidget.py @@ -1,7 +1,7 @@ ############################################################################### # volumina: volume slicing and editing library # -# Copyright (C) 2011-2018, the ilastik developers +# Copyright (C) 2011-2024, the ilastik developers # # # This program is free software; you can redistribute it and/or @@ -20,26 +20,35 @@ # http://ilastik.org/license/ ############################################################################### import os +from typing import Tuple from PyQt5 import uic from PyQt5.QtCore import pyqtSignal, Qt, QEvent from PyQt5.QtWidgets import QWidget, QFileDialog -class Hdf5ExportFileOptionsWidget(QWidget): +class HierarchicalFileExportOptionsWidget(QWidget): pathValidityChange = pyqtSignal(bool) - def __init__(self, parent): - super(Hdf5ExportFileOptionsWidget, self).__init__(parent) + def __init__(self, parent, file_extensions: Tuple[str, ...], extension_description: str): + super().__init__(parent) uic.loadUi(os.path.splitext(__file__)[0] + ".ui", self) + self.file_extensions = file_extensions + self.default_extension = file_extensions[0] + self.extension_description = extension_description self.settings_are_valid = True # We need to watch the textEdited signal because Qt has a bug that causes the OK button # to receive it's click event BEFORE the LineEdit receives its FocusOut event. # (That is, we can't just watch for FocusOut events and disable the button before the click.) - self.datasetEdit.textEdited.connect(lambda: self._handleTextEdited(self.datasetEdit)) self.filepathEdit.textEdited.connect(lambda: self._handleTextEdited(self.filepathEdit)) + if self.default_extension == ".zarr": + self.datasetLabel.setVisible(False) + self.datasetEdit.setVisible(False) + self.datasetEdit.setEnabled(False) + else: + self.datasetEdit.textEdited.connect(lambda: self._handleTextEdited(self.datasetEdit)) def initSlots(self, filepathSlot, datasetNameSlot, fullPathOutputSlot): self._filepathSlot = filepathSlot @@ -51,7 +60,7 @@ def initSlots(self, filepathSlot, datasetNameSlot, fullPathOutputSlot): self.datasetEdit.installEventFilter(self) def showEvent(self, event): - super(Hdf5ExportFileOptionsWidget, self).showEvent(event) + super().showEvent(event) self.updateFromSlots() def eventFilter(self, watched, event): @@ -95,8 +104,8 @@ def updateFromSlots(self): if self._filepathSlot.ready(): file_path = self._filepathSlot.value file_path, ext = os.path.splitext(file_path) - if ext != ".h5" and ext != ".hdf5": - file_path += ".h5" + if ext not in self.file_extensions: + file_path += self.default_extension else: file_path += ext self.filepathEdit.setText(file_path) @@ -115,9 +124,9 @@ def _browseForFilepath(self): else: starting_dir = os.path.expanduser("~") - dlg = QFileDialog(self, "Export Location", starting_dir, "HDF5 Files (*.h5 *.hdf5)") + dlg = QFileDialog(self, "Export Location", starting_dir, self.extension_description) - dlg.setDefaultSuffix("h5") + dlg.setDefaultSuffix(self.default_extension.lstrip(".")) dlg.setAcceptMode(QFileDialog.AcceptSave) if not dlg.exec_(): return @@ -148,7 +157,7 @@ def propagateDirty(self, *args): op = OpMock(graph=Graph()) app = QApplication([]) - w = Hdf5ExportFileOptionsWidget(None) + w = HierarchicalFileExportOptionsWidget(None, (".h5",), "H5 Files (*.h5)") w.initSlots(op.Filepath, op.DatasetName, op.FullPath) w.show() app.exec_() diff --git a/volumina/widgets/hdf5ExportFileOptionsWidget.ui b/volumina/widgets/hierarchicalFileExportOptionsWidget.ui similarity index 97% rename from volumina/widgets/hdf5ExportFileOptionsWidget.ui rename to volumina/widgets/hierarchicalFileExportOptionsWidget.ui index 561d5b1de..eee8c5276 100644 --- a/volumina/widgets/hdf5ExportFileOptionsWidget.ui +++ b/volumina/widgets/hierarchicalFileExportOptionsWidget.ui @@ -31,7 +31,7 @@ - + Dataset: diff --git a/volumina/widgets/multiformatSlotExportFileOptionsWidget.py b/volumina/widgets/multiformatSlotExportFileOptionsWidget.py index d5b8acf5e..af2b938bb 100644 --- a/volumina/widgets/multiformatSlotExportFileOptionsWidget.py +++ b/volumina/widgets/multiformatSlotExportFileOptionsWidget.py @@ -31,8 +31,7 @@ from PyQt5.QtWidgets import QWidget from .singleFileExportOptionsWidget import SingleFileExportOptionsWidget -from .hdf5ExportFileOptionsWidget import Hdf5ExportFileOptionsWidget -from .n5ExportFileOptionsWidget import N5ExportFileOptionsWidget +from .hierarchicalFileExportOptionsWidget import HierarchicalFileExportOptionsWidget from .stackExportFileOptionsWidget import StackExportFileOptionsWidget try: @@ -73,7 +72,7 @@ def initExportOp(self, opDataExport): # HDF5 for fmt in ("compressed hdf5", "hdf5"): - hdf5OptionsWidget = Hdf5ExportFileOptionsWidget(self) + hdf5OptionsWidget = HierarchicalFileExportOptionsWidget(self, (".h5", ".hdf5"), "HDF5 files (*.h5 *.hdf5)") hdf5OptionsWidget.initSlots( opDataExport.OutputFilenameFormat, opDataExport.OutputInternalPath, opDataExport.ExportPath ) @@ -82,13 +81,21 @@ def initExportOp(self, opDataExport): # N5 for fmt in ("compressed n5", "n5"): - n5OptionsWidget = N5ExportFileOptionsWidget(self) + n5OptionsWidget = HierarchicalFileExportOptionsWidget(self, (".n5",), "N5 files (*.n5)") n5OptionsWidget.initSlots( opDataExport.OutputFilenameFormat, opDataExport.OutputInternalPath, opDataExport.ExportPath ) n5OptionsWidget.pathValidityChange.connect(self._handlePathValidityChange) self._format_option_editors[fmt] = n5OptionsWidget + # Zarr + zarrOptionsWidget = HierarchicalFileExportOptionsWidget(self, (".zarr",), "Zarr files (*.zarr)") + zarrOptionsWidget.initSlots( + opDataExport.OutputFilenameFormat, opDataExport.OutputInternalPath, opDataExport.ExportPath + ) + zarrOptionsWidget.pathValidityChange.connect(self._handlePathValidityChange) + self._format_option_editors["single-scale OME-Zarr"] = zarrOptionsWidget + # Numpy npyOptionsWidget = SingleFileExportOptionsWidget(self, "npy", "numpy files (*.npy)") npyOptionsWidget.initSlots(opDataExport.OutputFilenameFormat, opDataExport.ExportPath) diff --git a/volumina/widgets/n5ExportFileOptionsWidget.py b/volumina/widgets/n5ExportFileOptionsWidget.py deleted file mode 100644 index 739327aad..000000000 --- a/volumina/widgets/n5ExportFileOptionsWidget.py +++ /dev/null @@ -1,159 +0,0 @@ -############################################################################### -# volumina: volume slicing and editing library -# -# Copyright (C) 2011-2018, the ilastik developers -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the Lesser GNU General Public License -# as published by the Free Software Foundation; either version 2.1 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# See the files LICENSE.lgpl2 and LICENSE.lgpl3 for full text of the -# GNU Lesser General Public License version 2.1 and 3 respectively. -# This information is also available on the ilastik web site at: -# http://ilastik.org/license/ -############################################################################### -import os - -from PyQt5 import uic -from PyQt5.QtCore import pyqtSignal, Qt, QEvent -from PyQt5.QtWidgets import QWidget, QFileDialog - - -class N5ExportFileOptionsWidget(QWidget): - pathValidityChange = pyqtSignal(bool) - - def __init__(self, parent): - super(N5ExportFileOptionsWidget, self).__init__(parent) - uic.loadUi(os.path.splitext(__file__)[0] + ".ui", self) - - self.settings_are_valid = True - - # We need to watch the textEdited signal because Qt has a bug that causes the OK button - # to receive it's click event BEFORE the LineEdit receives its FocusOut event. - # (That is, we can't just watch for FocusOut events and disable the button before the click.) - self.datasetEdit.textEdited.connect(lambda: self._handleTextEdited(self.datasetEdit)) - self.filepathEdit.textEdited.connect(lambda: self._handleTextEdited(self.filepathEdit)) - - def initSlots(self, filepathSlot, datasetNameSlot, fullPathOutputSlot): - self._filepathSlot = filepathSlot - self._datasetNameSlot = datasetNameSlot - self._fullPathOutputSlot = fullPathOutputSlot - self.fileSelectButton.clicked.connect(self._browseForFilepath) - - self.filepathEdit.installEventFilter(self) - self.datasetEdit.installEventFilter(self) - - def showEvent(self, event): - super(N5ExportFileOptionsWidget, self).showEvent(event) - self.updateFromSlots() - - def eventFilter(self, watched, event): - """ - Apply the new path/dataset if the user presses 'enter' - or clicks outside the path/dataset edit box. - """ - if event.type() == QEvent.FocusOut or ( - event.type() == QEvent.KeyPress and (event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return) - ): - if watched == self.datasetEdit: - self._applyDataset() - if watched == self.filepathEdit: - self._applyFilepath() - return False - - def _applyDataset(self): - was_valid = self.settings_are_valid - datasetName = self.datasetEdit.text() - self._datasetNameSlot.setValue(str(datasetName)) - self.settings_are_valid = str(datasetName) != "" - if self.settings_are_valid != was_valid: - self.pathValidityChange.emit(self.settings_are_valid) - - def _applyFilepath(self): - filepath = self.filepathEdit.text() - self._filepathSlot.setValue(filepath) - # TODO: Check for valid path format and signal validity - - def _handleTextEdited(self, watched): - if watched == self.datasetEdit: - self._applyDataset() - if watched == self.filepathEdit: - self._applyFilepath() - - def updateFromSlots(self): - was_valid = self.settings_are_valid - if self._datasetNameSlot.ready(): - dataset_name = self._datasetNameSlot.value - self.datasetEdit.setText(dataset_name) - self.path_is_valid = dataset_name != "" - - if self._filepathSlot.ready(): - file_path = self._filepathSlot.value - file_path, ext = os.path.splitext(file_path) - if ext != ".n5": - file_path += ".n5" - else: - file_path += ext - self.filepathEdit.setText(file_path) - - # Re-configure the file slot in case we changed the extension - self._filepathSlot.setValue(file_path) - - if was_valid != self.path_is_valid: - self.pathValidityChange.emit(self.settings_are_valid) - - def _browseForFilepath(self): - from lazyflow.utility import PathComponents - - if self._fullPathOutputSlot.ready(): - starting_dir = PathComponents(self._fullPathOutputSlot.value).externalDirectory - else: - starting_dir = os.path.expanduser("~") - - dlg = QFileDialog(self, "Export Location", starting_dir, "N5 Files (*.n5)") - - dlg.setDefaultSuffix("n5") - dlg.setAcceptMode(QFileDialog.AcceptSave) - if not dlg.exec_(): - return - - exportPath = dlg.selectedFiles()[0] - self.filepathEdit.setText(exportPath) - self._filepathSlot.setValue(exportPath) - - -if __name__ == "__main__": - from PyQt5.QtWidgets import QApplication - from lazyflow.graph import Graph, Operator, InputSlot - - class OpMock(Operator): - Filepath = InputSlot(value="~/something.n5") - DatasetName = InputSlot(value="volume/data") - FullPath = InputSlot(value="~/") - - def setupOutputs(self): - pass - - def execute(self, *args): - pass - - def propagateDirty(self, *args): - pass - - op = OpMock(graph=Graph()) - - app = QApplication([]) - w = N5ExportFileOptionsWidget(None) - w.initSlots(op.Filepath, op.DatasetName, op.FullPath) - w.show() - app.exec_() - - print("Selected Filepath: {}".format(op.Filepath.value)) - print("Selected Dataset: {}".format(op.DatasetName.value)) diff --git a/volumina/widgets/n5ExportFileOptionsWidget.ui b/volumina/widgets/n5ExportFileOptionsWidget.ui deleted file mode 100644 index 561d5b1de..000000000 --- a/volumina/widgets/n5ExportFileOptionsWidget.ui +++ /dev/null @@ -1,67 +0,0 @@ - - - Form - - - - 0 - 0 - 622 - 92 - - - - - 0 - 0 - - - - Form - - - - - - File: - - - - - - - - - - Dataset: - - - - - - - - - - Qt::Vertical - - - - 20 - 219 - - - - - - - - Select... - - - - - - - -