Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes globbing issues #2404

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c3a60eb
Adds skip_globbing to datasetinfo creation
Tomaz-Vieira Feb 26, 2021
a03103c
Adds skip_deglobbing to bash processing applet
Tomaz-Vieira Feb 26, 2021
5c97220
Removes leftover debug code
Tomaz-Vieira Mar 1, 2021
3f2b222
Adds skip-deglobbing headeless tests
Tomaz-Vieira Mar 1, 2021
eea120b
[WIP]Creates DataPath class, but it still allows non-existant paths
Tomaz-Vieira Mar 19, 2021
c0b76ce
Adds tests and fixes data_path inner path globbing
Tomaz-Vieira Mar 23, 2021
e0ef088
Adds tests for DatasetPath
Tomaz-Vieira Mar 23, 2021
5a3762e
Adds ArchiveDataPathlist_internal_paths, tests. Organizes methods
Tomaz-Vieira Mar 23, 2021
1fd954b
Fixes some type hints
Tomaz-Vieira Mar 23, 2021
287fe11
Adds ArchiveDataPath.siblings and more tests
Tomaz-Vieira Mar 24, 2021
2b7ffc1
Adds DatasetPath.common_internal_paths and tests
Tomaz-Vieira Mar 24, 2021
da2f076
Adds some type hints to pathHelpers
Tomaz-Vieira Mar 25, 2021
d8498c1
Fixes typing issues with data_path
Tomaz-Vieira Mar 25, 2021
f33c1de
ArchivePath.is_archive_path now takes Union[str,Path]
Tomaz-Vieira Mar 26, 2021
f45bfab
Renames classes. Creates PrecomputedChunksUrl. DataPath made into Dat…
Tomaz-Vieira Mar 30, 2021
d88d337
Cleans up path extensions in DataPath
Tomaz-Vieira Mar 31, 2021
a234b63
Refines DataUrl
Tomaz-Vieira Apr 12, 2021
8282fbe
Removes requirement data a DataPath be absolute
Tomaz-Vieira Apr 12, 2021
42b63ca
More refinements to DataPath
Tomaz-Vieira Apr 13, 2021
3f78e3c
DataPath.glob does not raise om empty expansion
Tomaz-Vieira Apr 16, 2021
481355f
StackPath checks for DataPath existance on __init__
Tomaz-Vieira Apr 16, 2021
5b1fe1b
StackPath.from_string expands user
Tomaz-Vieira Apr 16, 2021
9a2d073
Adds StackPath.suffixes method
Tomaz-Vieira Apr 16, 2021
22285a9
Adds (de)serialization to StackPath
Tomaz-Vieira Apr 16, 2021
5d67db7
StackPath: from_h5_data Uses StackPath.split instead of manual split
Tomaz-Vieira Apr 16, 2021
1b13903
[WIP] Uses StackPath everywhere
Tomaz-Vieira Apr 16, 2021
5eb1502
Rename StackPath to Dataset
Tomaz-Vieira Apr 21, 2021
09c3b13
Checks for repeated DataUrls in Dataset
Tomaz-Vieira Apr 21, 2021
876c03e
Adds Dataset.internal_paths()
Tomaz-Vieira Apr 21, 2021
458d3a2
Fixes Dataset.split using separator
Tomaz-Vieira Apr 21, 2021
87c378d
Rewrites stack selection to produce Datasets
Tomaz-Vieira Apr 21, 2021
4b2af19
Fixes serialization of sequence_axis and fixing of missing stacks
Tomaz-Vieira Apr 21, 2021
d0d5546
Fixes tests
Tomaz-Vieira Apr 22, 2021
64e1bb0
Removes now unused stackFileSelectionWidget.ui
Tomaz-Vieira Apr 23, 2021
3012107
Removes unused --skip-deglobbing from cmd line args
Tomaz-Vieira Apr 23, 2021
47df133
Fixes testPixelClassificationGui.py
Tomaz-Vieira Apr 23, 2021
c369eb7
Fixes legacy tests
Tomaz-Vieira Apr 23, 2021
33936bc
Handles backslashes in DataPath
Tomaz-Vieira Apr 23, 2021
5b290e9
Fixes black issue
Tomaz-Vieira Apr 23, 2021
df14245
More path fixes for windows
Tomaz-Vieira Apr 23, 2021
91fd130
Fixes N5DataPath.list_internal_paths backslashes
Tomaz-Vieira Apr 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion bin/convert_predictions_to_segmentation.py
@@ -1,4 +1,5 @@
from __future__ import print_function
from ilastik.utility.data_url import Dataset
import os
import copy
import h5py
Expand Down Expand Up @@ -40,7 +41,7 @@ def print_progress(progress_percent):
opExport.progressSignal.subscribe(print_progress)

for input_path in input_paths:
opReader.FilePath.setValue(input_path)
opReader.Dataset.setValue(Dataset.from_string(input_path, deglob=False))

input_pathcomp = PathComponents(input_path)
opExport.OutputFilenameFormat.setValue(str(input_pathcomp.externalPath))
Expand Down
3 changes: 2 additions & 1 deletion bin/convert_predictions_to_uncertainties.py
@@ -1,4 +1,5 @@
from __future__ import print_function
from ilastik.utility.data_url import Dataset
import os
import copy
import h5py
Expand All @@ -20,7 +21,7 @@ def convert_predictions_to_uncertainties(input_path, parsed_export_args):
graph = Graph()
opReader = OpInputDataReader(graph=graph)
opReader.WorkingDirectory.setValue(os.getcwd())
opReader.FilePath.setValue(input_path)
opReader.Dataset.setValue(Dataset.from_string(input_path, deglob=False))

opUncertainty = OpEnsembleMargin(graph=graph)
opUncertainty.Input.connect(opReader.Output)
Expand Down
3 changes: 2 additions & 1 deletion bin/train_headless.py
Expand Up @@ -31,6 +31,7 @@
"""
from __future__ import print_function
from builtins import range
from ilastik.utility.data_url import Dataset
import os


Expand Down Expand Up @@ -190,7 +191,7 @@ def generate_trained_project_file(
opReader = OpInputDataReader(graph=graph)
try:
opReader.WorkingDirectory.setValue(cwd)
opReader.FilePath.setValue(label_data_path)
opReader.Dataset.setValue(Dataset.from_string(label_data_path, deglob=False))

print("Reading label volume: {}".format(label_data_path))
label_volume = opReader.Output[:].wait()
Expand Down
2 changes: 1 addition & 1 deletion ilastik/applets/batchProcessing/batchProcessingGui.py
Expand Up @@ -315,7 +315,7 @@ def run_export(self):
return

# Run the export in a separate thread
lane_configs = self.parentApplet.dataSelectionApplet.create_lane_configs(role_inputs=role_inputs)
lane_configs = self.parentApplet.dataSelectionApplet.create_lane_configs(role_inputs=role_inputs, deglob=False)

export_req = Request(partial(self.parentApplet.run_export, lane_configs=lane_configs))
export_req.notify_failed(self.handle_batch_processing_failure)
Expand Down
22 changes: 14 additions & 8 deletions ilastik/applets/dataSelection/dataSelectionApplet.py
Expand Up @@ -21,6 +21,7 @@
from __future__ import division
from __future__ import absolute_import
from builtins import range
from ilastik.utility.data_url import Dataset, PrecomputedChunksUrl
import os
import sys
import glob
Expand Down Expand Up @@ -243,15 +244,14 @@ def _role_name_to_arg_name(role_name):
return role_name.lower().replace(" ", "_").replace("-", "_")

def create_dataset_info(
self, url: Union[Path, str], axistags: Optional[vigra.AxisTags] = None, sequence_axis: str = "z"
self, url: Union[Path, str], deglob: bool, axistags: Optional[vigra.AxisTags] = None, sequence_axis: str = "z"
) -> DatasetInfo:
url = str(url)
if isUrl(url):
return UrlDatasetInfo(url=url, axistags=axistags)
return UrlDatasetInfo(url=PrecomputedChunksUrl.from_string(url), axistags=axistags)
else:
return RelativeFilesystemDatasetInfo.create_or_fallback_to_absolute(
filePath=url, axistags=axistags, sequence_axis=sequence_axis
)
dataset = Dataset.split(url, deglob=deglob)
return FilesystemDatasetInfo(dataset=dataset, axistags=axistags, sequence_axis=sequence_axis)

def convert_info_to_h5(self, info: DatasetInfo) -> DatasetInfo:
tmp_path = tempfile.mktemp() + ".h5"
Expand All @@ -260,16 +260,18 @@ def convert_info_to_h5(self, info: DatasetInfo) -> DatasetInfo:
with h5py.File(tmp_path, mode="w") as tmp_h5:
logger.info(f"Converting info {info.effective_path} to h5 at {full_path}")
info.dumpToHdf5(h5_file=tmp_h5, inner_path=inner_path)
return self.create_dataset_info(url=full_path)
return self.create_dataset_info(url=full_path, deglob=False)

def create_lane_configs(
self,
*,
role_inputs: Dict[str, List[str]],
input_axes: Sequence[Optional[vigra.AxisTags]] = (),
preconvert_stacks: bool = False,
ignore_training_axistags: bool = False,
stack_along: str = "z",
) -> List[Dict[str, DatasetInfo]]:
deglob: bool,
) -> List[Dict[str, Optional[DatasetInfo]]]:
if not input_axes or not any(input_axes):
if ignore_training_axistags or self.num_lanes == 0:
input_axes = [None] * len(self.role_names)
Expand All @@ -283,7 +285,10 @@ def create_lane_configs(
rolewise_infos: Dict[str, List[DatasetInfo]] = {}
for role_name, axistags in zip(self.role_names, input_axes):
role_urls = role_inputs.get(role_name, [])
infos = [self.create_dataset_info(url, axistags=axistags, sequence_axis=stack_along) for url in role_urls]
infos = [
self.create_dataset_info(url, axistags=axistags, sequence_axis=stack_along, deglob=deglob)
for url in role_urls
]
if preconvert_stacks:
infos = [self.convert_info_to_h5(info) if info.is_stack() else info for info in infos]
rolewise_infos[role_name] = infos
Expand All @@ -309,6 +314,7 @@ def lane_configs_from_parsed_args(self, parsed_args: argparse.Namespace) -> List
preconvert_stacks=parsed_args.preconvert_stacks,
ignore_training_axistags=parsed_args.ignore_training_axistags,
stack_along=parsed_args.stack_along,
deglob=True,
)

def pushLane(self, role_infos: Dict[str, DatasetInfo]):
Expand Down
122 changes: 64 additions & 58 deletions ilastik/applets/dataSelection/dataSelectionGui.py
@@ -1,4 +1,6 @@
from __future__ import absolute_import
from ilastik.utility.data_url import PrecomputedChunksUrl
from ilastik.utility.data_url import ArchiveDataPath, Dataset

###############################################################################
# ilastik: interactive learning and segmentation toolkit
Expand All @@ -22,7 +24,7 @@
###############################################################################
# Python
import os
from pathlib import Path
from pathlib import Path, PurePosixPath
from typing import Dict, List, Set, Union, Optional
from vigra import AxisTags
import threading
Expand All @@ -46,9 +48,11 @@
from ilastik.utility import bind, log_exception
from ilastik.utility.gui import ThreadRouter, threadRouted
from ilastik.applets.layerViewer.layerViewerGui import LayerViewerGui
from ilastik.applets.dataSelection.dataSelectionApplet import DataSelectionApplet
from ilastik.applets.base.applet import DatasetConstraintError

from .opDataSelection import (
AxisTagsHint,
DatasetInfo,
RelativeFilesystemDatasetInfo,
FilesystemDatasetInfo,
Expand All @@ -57,7 +61,13 @@
)
from .dataLaneSummaryTableModel import DataLaneSummaryTableModel
from .datasetInfoEditorWidget import DatasetInfoEditorWidget
from ilastik.widgets.stackFileSelectionWidget import StackFileSelectionWidget, SubvolumeSelectionDlg
from ilastik.widgets.stackFileSelectionWidget import (
DatasetSelectionMode,
DatasetSelectionWidget,
SubvolumeSelectionDlg,
select_single_file_datasets,
create_dataset,
)
from .datasetDetailedInfoTableModel import DatasetDetailedInfoTableModel
from .datasetDetailedInfoTableView import DatasetDetailedInfoTableView
from .precomputedVolumeBrowser import PrecomputedVolumeBrowser
Expand Down Expand Up @@ -150,7 +160,7 @@ class UserCancelledError(Exception):

def __init__(
self,
parentApplet,
parentApplet: DataSelectionApplet,
dataSelectionOperator,
serializer,
instructionText,
Expand Down Expand Up @@ -184,7 +194,7 @@ def __init__(
self._initAppletDrawerUic(instructionText)

self._viewerControlWidgetStack = QStackedWidget(self)
self._default_h5n5_volumes: Dict[int, Set[str]] = {}
self._default_h5n5_volumes: Dict[int, Set[PurePosixPath]] = {}

self.__cleanup_fns.append(self.topLevelOperator.DatasetGroup.notifyRemove(bind(self._handleImageRemove)))

Expand Down Expand Up @@ -410,21 +420,40 @@ def addFiles(self, roleIndex, startingLaneNum=None):
the GUI table and the top-level operator inputs.
"""
# Launch the "Open File" dialog
paths = ImageFileDialog(self).getSelectedPaths()
self.addFileNames(paths, startingLaneNum, roleIndex)
datasets = select_single_file_datasets(
parent=self, internal_path_hints=self._get_previously_used_inner_paths(roleIndex)
)

self.addDatasets(datasets, startingLaneNum, roleIndex)

def addFileNames(self, paths: List[Path], startingLaneNum: int, roleIndex: int):
def addFileNames(self, paths: List[str], startingLaneNum: Optional[int], roleIndex: int):
# If the user didn't cancel
for path in paths or []:
datasets: List[Dataset] = []
for path in paths:
dataset = create_dataset(
parent=self, raw_file_paths=[path], internal_path_hints=self._get_previously_used_inner_paths(roleIndex)
)
if not dataset:
return
datasets.append(dataset)
self.addDatasets(datasets, startingLaneNum=startingLaneNum, roleIndex=roleIndex)

def addDatasets(self, datasets: Optional[List[Dataset]], startingLaneNum: Optional[int], roleIndex: int):
# If the user didn't cancel
axistags_hint = self.guess_axistags_for(role=roleIndex)
for dataset in datasets or []:
try:
full_path = self._get_dataset_full_path(path, roleIndex=roleIndex)
info = self.instantiate_dataset_info(url=str(full_path), role=roleIndex)
info = FilesystemDatasetInfo(dataset=dataset, axistags_hint=axistags_hint)
self.addLanes([info], roleIndex=roleIndex, startingLaneNum=startingLaneNum)
except DataSelectionGui.UserCancelledError:
pass
except Exception as ex:
log_exception(logger)
QMessageBox.critical(self, "Error loading file", str(ex))
for dataset in datasets or []:
internal_paths = dataset.internal_paths()
if internal_paths:
self._add_default_internal_path(roleIndex=roleIndex, internal_path=internal_paths[0])

def _findFirstEmptyLane(self, roleIndex):
opTop = self.topLevelOperator
Expand Down Expand Up @@ -528,56 +557,23 @@ def _determineLaneRange(self, infos: List[DatasetInfo], startingLaneNum=None):

return (startingLaneNum, endingLane)

def _add_default_inner_path(self, roleIndex: int, inner_path: str):
def _add_default_internal_path(self, roleIndex: int, internal_path: PurePosixPath):
paths = self._default_h5n5_volumes.get(roleIndex, set())
paths.add(inner_path)
paths.add(internal_path)
self._default_h5n5_volumes[roleIndex] = paths

def _get_previously_used_inner_paths(self, roleIndex: int) -> Set[str]:
def _get_previously_used_inner_paths(self, roleIndex: int) -> Set[PurePosixPath]:
previous_paths = self._default_h5n5_volumes.get(roleIndex, set())
return previous_paths.copy()

def _get_dataset_full_path(self, filePath: Path, roleIndex: int) -> Path:
if not DatasetInfo.fileHasInternalPaths(filePath):
return filePath
datasetNames = DatasetInfo.getPossibleInternalPathsFor(filePath.absolute())
if len(datasetNames) == 0:
raise RuntimeError(f"File {filePath} has no image datasets")
if len(datasetNames) == 1:
selected_dataset = datasetNames.pop()
else:
auto_inner_paths = self._get_previously_used_inner_paths(roleIndex).intersection(set(datasetNames))
if len(auto_inner_paths) == 1:
selected_dataset = auto_inner_paths.pop()
else:
# Ask the user which dataset to choose
dlg = SubvolumeSelectionDlg(datasetNames, self)
if dlg.exec_() != QDialog.Accepted:
raise DataSelectionGui.UserCancelledError()
selected_index = dlg.combo.currentIndex()
selected_dataset = str(datasetNames[selected_index])
self._add_default_inner_path(roleIndex=roleIndex, inner_path=selected_dataset)
return filePath / selected_dataset.lstrip("/")

def guess_axistags_for(self, role: Union[str, int], info: DatasetInfo) -> Optional[AxisTags]:
def guess_axistags_for(self, role: Union[str, int]) -> Optional[AxisTagsHint]:
if self.parentApplet.num_lanes == 0:
return info.axistags
return None
lane = self.parentApplet.get_lane(-1)
previous_info = lane.get_dataset_info(role)
if previous_info is None or previous_info.default_tags == previous_info.axistags:
return info.axistags
if len(previous_info.axistags) != len(info.axistags) or previous_info.shape5d.c != info.shape5d.c:
return info.axistags
return previous_info.axistags

def instantiate_dataset_info(self, url: str, role: Union[str, int], *info_args, **info_kwargs) -> DatasetInfo:
info = self.parentApplet.create_dataset_info(url=url, *info_args, **info_kwargs)
if info_kwargs.get("axistags") is not None:
return info
axistags = self.guess_axistags_for(role=role, info=info)
if axistags in (info.axistags, None):
return info
return self.parentApplet.create_dataset_info(url=url, *info_args, **info_kwargs, axistags=axistags)
return None
return AxisTagsHint(axistags=previous_info.axistags, num_channels=int(previous_info.shape5d.c))

def _checkDataFormatWarnings(self, roleIndex, startingLaneNum, endingLane):
warn_needed = False
Expand All @@ -596,19 +592,25 @@ def _checkDataFormatWarnings(self, roleIndex, startingLaneNum, endingLane):
"(For HDF5 files, be sure to enable chunking on your dataset.)",
)

def addStack(self, roleIndex, laneIndex):
def addStack(self, roleIndex: int, laneIndex):
"""
The user clicked the "Import Stack Files" button.
"""
stackDlg = StackFileSelectionWidget(self)
stackDlg = DatasetSelectionWidget(selection_mode=DatasetSelectionMode.STACK)
stackDlg.exec_()
if stackDlg.result() != QDialog.Accepted or not stackDlg.selectedFiles:
if stackDlg.result() != QDialog.Accepted:
return
stack = stackDlg.selected_datasets[0]

# FIXME: ask first if stack should be internalized to project file
# also, check prefer_2d, size/volume and presence of 'z' to determine this
url = os.path.pathsep.join(stackDlg.selectedFiles)
stack_info = self.instantiate_dataset_info(url=url, role=roleIndex, sequence_axis=stackDlg.sequence_axis)
axistags_hint = self.guess_axistags_for(role=roleIndex)
stack_info = RelativeFilesystemDatasetInfo.create_or_fallback_to_absolute(
project_file=self.get_project_file(),
dataset=stack,
sequence_axis=stackDlg.stacking_axis,
axistags_hint=axistags_hint,
)

try:
# FIXME: do this inside a Request
Expand Down Expand Up @@ -645,15 +647,19 @@ def editDatasetInfo(self, roleIndex, laneIndexes):
if editorDlg.exec_() == QDialog.Accepted:
self.applyDatasetInfos(editorDlg.edited_infos, selected_info_slots)

def addPrecomputedVolume(self, roleIndex, laneIndex):
def addPrecomputedVolume(self, roleIndex: int, laneIndex):
# add history...
history = []
browser = PrecomputedVolumeBrowser(history=history, parent=self)

if browser.exec_() == PrecomputedVolumeBrowser.Rejected:
return

info = self.instantiate_dataset_info(url=browser.selected_url, role=roleIndex)
selected_url = browser.selected_url
if selected_url is None:
return
precomputed_url = PrecomputedChunksUrl.create(selected_url)
axistags_hint = self.guess_axistags_for(role=roleIndex)
info = UrlDatasetInfo(url=precomputed_url, axistags_hint=axistags_hint)
self.addLanes([info], roleIndex=roleIndex, startingLaneNum=laneIndex)

def addDvidVolume(self, roleIndex, laneIndex):
Expand Down