Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
6,535 changes: 2,886 additions & 3,649 deletions pixi.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ motile_tracker = { path = "./motile_tracker"}
higra = ">=0.6.12,<0.7"
numba = { version = ">=0.57.0,<0.61", channel = "numba" }
llvmlite = { version = ">=0.44.0,<0.45", channel = "numba" }
ilpy = ">=0.4.0,<0.5"
ilpy = ">=0.5.1,<0.6"
pyscipopt = "*" # needed for ilpy on mac
# napari-ome-zarr = ">=0.6.1,<0.7"
zarr = ">=3.0.0,<4.0.0" # zarr v3 support, ultrack now compatible
psycopg2-binary = ">=2.9.6"
pyqt = ">=5.15.9,<6"
numpy = "<2.2"
pre-commit = ">=4.1.0,<5"
dask = ">=2025.2.0,<2026"
pyscipopt = "*" #for ilpy (on mac)

[tool.pixi.feature.test.dependencies]
pytest = "*"
Expand Down
95 changes: 95 additions & 0 deletions scripts/convert_geff_to_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
Script to convert GEFF files to Ultrack SQLite databases for the cellmotv1 tracking challenge.

For each experiment folder:
- Runs: trackedit convert geff-to-db <experiment>.geff -o data.db
- Writes: metadata.toml with shape = [ 100, 64, 256, 256,]

Usage:
python convert_geff_to_db.py # test mode: processes one experiment, output to Desktop
python convert_geff_to_db.py --all # processes all experiments, output next to geff/zarr
"""

import subprocess
import sys
from pathlib import Path

BASE_DIR = Path(
"/hpc/projects/group.royer/people/thibaut.goldsborough/tracking-challenge/cellmotv1"
)

# Used only in test mode (no write access to BASE_DIR)
TEST_OUTPUT_DIR = Path("/home/teun.huijben/Desktop/tracking-challenge/cellmotv1")

SHAPE = [100, 64, 256, 256]

METADATA_CONTENT = f"shape = {SHAPE}\n"


def process_experiment(experiment_dir: Path, out_dir: Path) -> bool:
geff_files = list(experiment_dir.glob("*.geff"))
if not geff_files:
print(f" [SKIP] No .geff file found in {experiment_dir}")
return False

geff_path = geff_files[0]
out_dir.mkdir(parents=True, exist_ok=True)
db_output = out_dir / "data.db"
metadata_path = out_dir / "metadata.toml"

print(f" Converting: {geff_path.name}")
result = subprocess.run(
["trackedit", "convert", "geff-to-db", str(geff_path), "-o", str(db_output)],
capture_output=True,
text=True,
)

if result.returncode != 0:
print(f" [ERROR] Conversion failed:\n{result.stderr}")
return False

print(f" Written: {db_output}")

metadata_path.write_text(METADATA_CONTENT)
print(f" Written: {metadata_path}")

return True


def main():
run_all = "--all" in sys.argv

date_folders = sorted(BASE_DIR.iterdir())

if run_all:
experiments = [
exp
for date_folder in date_folders
for exp in sorted(date_folder.iterdir())
if exp.is_dir()
]
print(f"Processing all {len(experiments)} experiments...\n")
pairs = [(exp, exp) for exp in experiments]
else:
# Test mode: first experiment of the first folder, write to Desktop
first_folder = date_folders[0]
exp = sorted(first_folder.iterdir())[0]
experiments = [exp]
relative = exp.relative_to(BASE_DIR)
out = TEST_OUTPUT_DIR / relative
pairs = [(exp, out)]
print("TEST MODE: processing 1 experiment")
print(f" Input: {exp}")
print(f" Output: {out}\n")

success = 0
for exp, out in pairs:
print(f"[{exp.parent.name}/{exp.name}]")
if process_experiment(exp, out):
success += 1

print(f"\nDone: {success}/{len(experiments)} experiments converted successfully.")


if __name__ == "__main__":
main()
70 changes: 70 additions & 0 deletions scripts/script_instanseg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import sys
import warnings
from pathlib import Path

import numpy as np

from trackedit.run import run_trackedit

# Databases saved with numpy>2 need np._core.numeric, which is not available in numpy<2, hence the following hack
sys.modules["numpy._core.numeric"] = np.core.numeric

warnings.filterwarnings("ignore", category=FutureWarning, message=".*qt_viewer.*")

# **********INPUTS*********
# path to the working directory that contains the database file AND metadata.toml:
working_directory = Path(
"/hpc/projects/group.royer/people/teun.huijben/data/Thibault/4th_exp//masks_on_geff/"
)
# name of the database file to start from, or "latest" to start from the latest version, defaults to "data.db"
db_filename_start = "latest"
# maximum number of frames display, defaults to None (use all frames)
tmax = 100
# (Z),Y,X, defaults to (1, 1, 1)
scale = (1.625, 0.40625, 0.40625)
# overwrite existing database/changelog, defaults to False (not used when db_filename_start is "latest")
allow_overwrite = False

# OPTIONAL: imaging data
imaging_zarr_file = (
"/hpc/projects/group.royer/people/teun.huijben/data/Thibault/4th_exp/first_fov.zarr"
)
imaging_channel = "0"
imaging_layer_names = ["dense", "sparse"]

# OPTIONAL: annotation mapping (default is neuromast cell types)
# annotation_mapping = {
# 1: {"name": "hair", "color": [0.0, 1.0, 0.0, 1.0]}, # green
# 2: {"name": "support", "color": [1.0, 0.1, 0.6, 1.0]}, # pink
# 3: {"name": "mantle", "color": [0.0, 0.0, 0.9, 1.0]}, # blue
# }
annotation_mapping = None

# OPTIONAL: InstanSeg model for interactive cell segmentation
# Enable this to add cells via InstanSeg inference instead of spherical masks
flag_allow_adding_instanseg_cell = True
instanseg_model_path = (
"/hpc/projects/group.royer/people/teun.huijben/data/Thibault/model_96.pt"
)
instanseg_device = None # 'cuda', 'cpu', or None for auto-detect
# *************************

if __name__ == "__main__":
run_trackedit(
working_directory=working_directory,
db_filename=db_filename_start,
tmax=tmax,
scale=scale,
allow_overwrite=allow_overwrite,
imaging_zarr_file=imaging_zarr_file,
imaging_channel=imaging_channel,
imaging_layer_names=imaging_layer_names,
annotation_mapping=annotation_mapping,
flag_allow_adding_spherical_cell=True,
adding_spherical_cell_radius=10,
flag_remove_red_flags_at_edge=True,
remove_red_flags_at_edge_threshold=10,
flag_allow_adding_instanseg_cell=flag_allow_adding_instanseg_cell,
instanseg_model_path=instanseg_model_path,
instanseg_device=instanseg_device,
)
29 changes: 21 additions & 8 deletions scripts/script_neuromast.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,25 @@
# **********INPUTS*********
# path to the working directory that contains the database file AND metadata.toml:
working_directory = Path(
"/hpc/projects/group.royer/people/teun.huijben/data/Akila/trackedit_example_data/"
"/hpc/projects/group.royer/people/teun.huijben/data/Akila/trackedit_example_data_v2/"
)
# name of the database file to start from, or "latest" to start from the latest version, defaults to "data.db"
db_filename_start = "latest"
# maximum number of frames display, defaults to None (use all frames)
tmax = 600
# (Z),Y,X, defaults to (1, 1, 1)
scale = (2.31, 1, 1)
scale = (0.25, 0.108, 0.108) # microns
# overwrite existing database/changelog, defaults to False (not used when db_filename_start is "latest")
allow_overwrite = False

# OPTIONAL: imaging data
# imaging_zarr_file = (
# "/hpc/....../deconvolved.zarr"
# )
# imaging_channel = "0/4/0/0"
imaging_zarr_file = None
imaging_channel = None
imaging_zarr_file = (
"/hpc/projects/group.royer/people/teun.huijben/data/Akila/"
"trackedit_example_data_v2/deconvolved_and_registered_corrected_cropped850.zarr"
)
imaging_channel = "0/3/0/0"
# imaging_zarr_file = None
# imaging_channel = None

# OPTIONAL: annotation mapping (default is neuromast cell types)
# annotation_mapping = {
Expand All @@ -40,6 +41,15 @@
# 3: {"name": "mantle", "color": [0.0, 0.0, 0.9, 1.0]}, # blue
# }
annotation_mapping = None

# OPTIONAL: InstanSeg model for interactive cell segmentation
# Enable this to add cells via InstanSeg inference instead of spherical masks
flag_allow_adding_instanseg_cell = True
instanseg_model_path = (
"/hpc/projects/group.royer/people/teun.huijben/data/Thibault/"
"neuromast_model/instanseg_27527750.pt"
)
instanseg_device = None # 'cuda', 'cpu', or None for auto-detect
# *************************

if __name__ == "__main__":
Expand All @@ -52,4 +62,7 @@
imaging_zarr_file=imaging_zarr_file,
imaging_channel=imaging_channel,
annotation_mapping=annotation_mapping,
flag_allow_adding_instanseg_cell=flag_allow_adding_instanseg_cell,
instanseg_model_path=instanseg_model_path,
instanseg_device=instanseg_device,
)
12 changes: 12 additions & 0 deletions trackedit/DatabaseHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ def db_to_df(
NodeDB.z,
NodeDB.y,
NodeDB.x,
NodeDB.area,
NodeDB.generic,
),
)
Expand Down Expand Up @@ -714,7 +715,17 @@ def find_all_red_flags(self) -> pd.DataFrame:
df, self.db_path_new
)

# Trajectory changes (jumps and direction changes)
# rfs_trajectory = find_trajectory_changes(df, self.scale)

# Area/volume changes
# rfs_area = find_area_changes(df)

# Combine all red flag detection results
# result_df = combine_red_flags(
# rfs_starts_and_ends, rfs_overlap, rfs_trajectory, rfs_area
# )

result_df = combine_red_flags(rfs_starts_and_ends, rfs_overlap)

# ToDo: make option to filter redflags in the first two timepoints
Expand All @@ -729,6 +740,7 @@ def find_all_red_flags(self) -> pd.DataFrame:
data_shape=self.data_shape_full[1:],
edge_threshold=self.remove_red_flags_at_edge_threshold,
ndim=self.ndim,
scale=self.scale,
)

return result_df
Expand Down
Loading
Loading