Skip to content
6,089 changes: 3,078 additions & 3,011 deletions pixi.lock

Large diffs are not rendered by default.

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,
)
14 changes: 13 additions & 1 deletion trackedit/DatabaseHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
combine_red_flags,
filter_red_flags_at_edge,
find_all_starts_and_ends,
find_area_changes,
find_overlapping_cells,
find_trajectory_changes,
)
from trackedit.utils.utils import (
annotations_to_zarr,
Expand Down Expand Up @@ -605,6 +607,7 @@ def db_to_df(
NodeDB.z,
NodeDB.y,
NodeDB.x,
NodeDB.area,
NodeDB.generic,
),
)
Expand Down Expand Up @@ -714,8 +717,16 @@ 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)
result_df = combine_red_flags(
rfs_starts_and_ends, rfs_overlap, rfs_trajectory, rfs_area
)

# ToDo: make option to filter redflags in the first two timepoints
# (useful for neuromast with suboptimal beginning)
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