Skip to content

Commit ab4dc93

Browse files
committed
add support for single session anipose
1 parent f812d12 commit ab4dc93

2 files changed

Lines changed: 61 additions & 21 deletions

File tree

packages/cheese3d/cheese3d/cli.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,17 @@ def calibrate(
155155
name: Annotated[str, typer.Argument(help="Name of project")] = ".",
156156
config_overrides: Annotated[Optional[List[str]], typer.Argument(
157157
help="Config overrides passed to Hydra (https://hydra.cc/docs/intro/)"
158+
)] = None,
159+
session: Annotated[Optional[str], typer.Option(
160+
help="Name of a specific session subfolder to process"
158161
)] = None
159162
):
160163
"""Extract camera calibration for triangulation."""
161164
path = ctx.obj["path"]
162165
configs = ctx.obj["configs"]
163166
project = _build_project(path, name, configs, config_overrides)
164167
rich.print("Calibrating ...")
165-
project.calibrate()
168+
project.calibrate(session=session)
166169
rich.print("Calibration complete :white_check_mark:")
167170

168171
@cli.command()
@@ -171,14 +174,17 @@ def track(
171174
name: Annotated[str, typer.Argument(help="Name of project")] = ".",
172175
config_overrides: Annotated[Optional[List[str]], typer.Argument(
173176
help="Config overrides passed to Hydra (https://hydra.cc/docs/intro/)"
177+
)] = None,
178+
session: Annotated[Optional[str], typer.Option(
179+
help="Name of a specific session subfolder to process"
174180
)] = None
175181
):
176182
"""Track 2D keypoints using trained pose estimation model."""
177183
path = ctx.obj["path"]
178184
configs = ctx.obj["configs"]
179185
project = _build_project(path, name, configs, config_overrides)
180186
rich.print("Tracking 2D pose ...")
181-
project.track()
187+
project.track(session=session)
182188
rich.print("Pose estimation complete :white_check_mark:")
183189

184190
@cli.command()
@@ -187,14 +193,17 @@ def triangulate(
187193
name: Annotated[str, typer.Argument(help="Name of project")] = ".",
188194
config_overrides: Annotated[Optional[List[str]], typer.Argument(
189195
help="Config overrides passed to Hydra (https://hydra.cc/docs/intro/)"
196+
)] = None,
197+
session: Annotated[Optional[str], typer.Option(
198+
help="Name of a specific session subfolder to process"
190199
)] = None
191200
):
192201
"""Triangulate 2D pose estimation result to obtain 3D keypoints and Cheese3D features."""
193202
path = ctx.obj["path"]
194203
configs = ctx.obj["configs"]
195204
project = _build_project(path, name, configs, config_overrides)
196205
rich.print("Triangulating ...")
197-
project.triangulate()
206+
project.triangulate(session=session)
198207
rich.print("Triangulation complete :white_check_mark:")
199208

200209
@cli.command()
@@ -203,6 +212,9 @@ def analyze(
203212
name: Annotated[str, typer.Argument(help="Name of project")] = ".",
204213
config_overrides: Annotated[Optional[List[str]], typer.Argument(
205214
help="Config overrides passed to Hydra (https://hydra.cc/docs/intro/)"
215+
)] = None,
216+
session: Annotated[Optional[str], typer.Option(
217+
help="Name of a specific session subfolder to process"
206218
)] = None
207219
):
208220
"""
@@ -212,9 +224,9 @@ def analyze(
212224
path = ctx.obj["path"]
213225
configs = ctx.obj["configs"]
214226
project = _build_project(path, name, configs, config_overrides)
215-
project.calibrate()
216-
project.track()
217-
project.triangulate()
227+
project.calibrate(session=session)
228+
project.track(session=session)
229+
project.triangulate(session=session)
218230

219231
@cli.command()
220232
def generate_videos(

packages/cheese3d/cheese3d/project.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -557,26 +557,54 @@ def _load_anipose_cfg(self):
557557

558558
return load_config(str(self.triangulation_path / "config.toml"))
559559

560-
def calibrate(self):
561-
from anipose.calibrate import calibrate_all
562-
calibrate_all(self._load_anipose_cfg())
563-
564-
def track(self):
565-
from anipose.pose_videos import pose_videos_all
566-
pose_videos_all(self._load_anipose_cfg())
560+
def _resolve_anipose_session(self, session: str) -> str:
561+
session_path = self.triangulation_path / session
562+
if not session_path.is_dir():
563+
available = [p.name for p in self.triangulation_path.iterdir()
564+
if p.is_dir()] if self.triangulation_path.is_dir() else []
565+
raise ValueError(
566+
f"Session folder '{session}' not found in {self.triangulation_path}. "
567+
f"Available sessions: {available}")
568+
return str(session_path.resolve())
569+
570+
def calibrate(self, session: Optional[str] = None):
571+
if session is not None:
572+
from anipose.calibrate import process_session
573+
config = self._load_anipose_cfg()
574+
process_session(config, self._resolve_anipose_session(session))
575+
else:
576+
from anipose.calibrate import calibrate_all
577+
calibrate_all(self._load_anipose_cfg())
578+
579+
def track(self, session: Optional[str] = None):
580+
if session is not None:
581+
from anipose.pose_videos import process_session
582+
config = self._load_anipose_cfg()
583+
process_session(config, self._resolve_anipose_session(session))
584+
else:
585+
from anipose.pose_videos import pose_videos_all
586+
pose_videos_all(self._load_anipose_cfg())
567587

568-
def triangulate(self):
588+
def triangulate(self, session: Optional[str] = None):
569589
# first triangulate points using Anipose
570-
from anipose.triangulate import triangulate_all
571-
triangulate_all(self._load_anipose_cfg())
590+
if session is not None:
591+
from anipose.triangulate import process_session
592+
config = self._load_anipose_cfg()
593+
process_session(config, self._resolve_anipose_session(session))
594+
sessions_to_process = [self.triangulation_path / session]
595+
else:
596+
from anipose.triangulate import triangulate_all
597+
triangulate_all(self._load_anipose_cfg())
598+
sessions_to_process = [s for s in self.triangulation_path.iterdir()
599+
if s.is_dir()]
572600
# now compute cheese3d features
573601
exclude_kps = set(kp.label for kp in _DEFAULT_KEYPOINTS) - set(kp.label for kp in self.keypoints)
574602
if len(exclude_kps) > 0:
575603
rprint("[bold red]Keypoint configuration does not match default Cheese3D keypoints. "
576604
"Some Cheese3D features may not be computed![/bold red]")
577-
for session in self.triangulation_path.iterdir():
578-
if session.is_dir():
579-
landmarks = read_3d_data(session)
605+
for session_dir in sessions_to_process:
606+
if session_dir.is_dir():
607+
landmarks = read_3d_data(session_dir)
580608
c3d_features = compute_anatomical_measurements(landmarks, exclude_kps)
581609
# write features to csv
582610
c3d_features_df = pd.DataFrame({
@@ -585,9 +613,9 @@ def triangulate(self):
585613
for k, v in features.items()
586614
})
587615
if len(c3d_features_df) == 0:
588-
rprint(f"[bold red]No features constructed for {session.name}, skipping![/bold red]")
616+
rprint(f"[bold red]No features constructed for {session_dir.name}, skipping![/bold red]")
589617
continue
590-
csv_output = (session / "cheese3d")
618+
csv_output = (session_dir / "cheese3d")
591619
csv_output.mkdir(exist_ok=True)
592620
csv_output = csv_output / "cheese3d_features.csv"
593621
if not csv_output.exists():

0 commit comments

Comments
 (0)