Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/motile_tracker/import_export/menus/import_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,65 @@ def _ensure_area_enabled(self) -> None:
recompute = "area" not in self.tracks.graph.node_attr_keys()
self.tracks.enable_features(["area"], recompute=recompute)

def _maybe_convert_legacy_masks(self, geff_dir: Path) -> Path | None:
"""Offer to convert masks stored in an older, memory-heavy dtype.

Geff files written by older versions of tracksdata store segmentation
masks as integers (e.g. ``uint64``) rather than ``bool``, using ~8x more
memory when read. If such masks are detected, warn the user and offer a
lossless conversion. To keep the original (possibly shared) file
untouched, the conversion is written to a ``*_bool.geff`` copy next to
it, and that copy is loaded instead.

Returns:
Path | None: the geff directory to import (the original, or the
converted copy), or None if the user cancelled.
"""
try:
from tracksdata.io import convert_geff_mask_to_bool, geff_mask_dtype
except ImportError:
return geff_dir # older tracksdata without the utility; import as before

try:
dtype = geff_mask_dtype(geff_dir)
except Exception: # noqa: BLE001
return geff_dir # detection failed; don't block the import

if dtype is None or dtype == "bool":
return geff_dir

answer = QMessageBox.question(
self,
"Old mask format detected",
f"This geff stores segmentation masks as '{dtype}', an older format that "
"uses about 8x more memory when loaded.\n\n"
"Convert them to boolean now? A converted copy will be written next to the "
"original, which is left untouched.",
QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
QMessageBox.Yes,
)
if answer == QMessageBox.Cancel:
return None
if answer == QMessageBox.No:
return geff_dir # load the original as-is

name = geff_dir.name
out_name = (
f"{name[:-5]}_bool.geff" if name.endswith(".geff") else f"{name}_bool"
)
out_path = geff_dir.parent / out_name

QApplication.setOverrideCursor(Qt.WaitCursor)
try:
if not out_path.exists():
convert_geff_mask_to_bool(geff_dir, output_path=out_path)
except Exception as e: # noqa: BLE001
QMessageBox.critical(self, "Error", f"Failed to convert masks: {e}")
return None
finally:
QApplication.restoreOverrideCursor()
return out_path

def _finish(self) -> None:
"""Tries to read the csv/geff file and optional segmentation image and apply the
attribute to column mapping to construct a Tracks object"""
Expand All @@ -339,6 +398,10 @@ def _finish(self) -> None:
group_path = Path(self.import_widget.root.path) # e.g. 'tracks'
geff_dir = store_path / group_path

geff_dir = self._maybe_convert_legacy_masks(geff_dir)
if geff_dir is None:
return

self.name = self.import_widget.dir_name
scale = self.scale_widget.get_scale() if self.seg else None

Expand Down
Loading