|
13 | 13 | from typing import List, Dict, Optional, Any |
14 | 14 | from collections import namedtuple |
15 | 15 | from datetime import datetime |
| 16 | +from filelock import FileLock |
16 | 17 |
|
17 | 18 | from cheese3d.anatomy import compute_anatomical_measurements |
18 | 19 | from cheese3d.config import (MultiViewConfig, |
@@ -279,12 +280,13 @@ def from_cfg(cls, cfg: ProjectConfig, root: str | Path, model_import = None): |
279 | 280 | else: |
280 | 281 | ephys = None |
281 | 282 | model_cfg = maybe(model_import, cfg.model) |
282 | | - model = build_model_backend(model_cfg, |
283 | | - root=(root / cfg.name / |
284 | | - relative_path(cfg.model_root, root / cfg.name)), |
285 | | - sessions=sessions, |
286 | | - view_cfg=cfg.views, |
287 | | - keypoints=cfg.keypoints) |
| 283 | + with FileLock(root / cfg.name / "build_backend.lock"): |
| 284 | + model = build_model_backend(model_cfg, |
| 285 | + root=(root / cfg.name / |
| 286 | + relative_path(cfg.model_root, root / cfg.name)), |
| 287 | + sessions=sessions, |
| 288 | + view_cfg=cfg.views, |
| 289 | + keypoints=cfg.keypoints) |
288 | 290 | view_regex = get_group_pattern(ProjectConfig.build_regex(cfg.video_regex), "view") |
289 | 291 |
|
290 | 292 | return cls(name=cfg.name, |
@@ -410,17 +412,19 @@ def _import_labels(self): |
410 | 412 | if self.model is None: |
411 | 413 | raise RuntimeError("Cannot import labels when pose model does not exist " |
412 | 414 | "(hint: maybe you forgot to set `model.name` in the config?") |
413 | | - self._create_labels() |
414 | | - label_paths = self._label_folder_paths() |
415 | | - self.model.import_c3d_labels(label_paths) |
| 415 | + with FileLock(self.path / "labels.lock"): |
| 416 | + self._create_labels() |
| 417 | + label_paths = self._label_folder_paths() |
| 418 | + self.model.import_c3d_labels(label_paths) |
416 | 419 |
|
417 | 420 | def _export_labels(self): |
418 | 421 | if self.model is None: |
419 | 422 | raise RuntimeError("Cannot export labels when pose model does not exist " |
420 | 423 | "(hint: maybe you forgot to set `model.name` in the config?") |
421 | | - self._create_labels() |
422 | | - label_paths = self._label_folder_paths() |
423 | | - self.model.export_c3d_labels(label_paths) |
| 424 | + with FileLock(self.path / "labels.lock"): |
| 425 | + self._create_labels() |
| 426 | + label_paths = self._label_folder_paths() |
| 427 | + self.model.export_c3d_labels(label_paths) |
424 | 428 |
|
425 | 429 | def extract_frames(self, sessions: Optional[List[RecordingKey]] = None, manual = False): |
426 | 430 | self._import_labels() |
@@ -475,81 +479,82 @@ def _setup_anipose(self): |
475 | 479 | if self.model is None: |
476 | 480 | raise RuntimeError("Cannot setup triangulation when pose model does not exist " |
477 | 481 | "(hint: maybe you forgot to set `model.name` in the config?") |
478 | | - # make anipose project folder |
479 | | - self.triangulation_path.mkdir(exist_ok=True) |
480 | | - # create session subfolders |
481 | | - for recording, videos in self.sessions.items(): |
482 | | - session_path = self.triangulation_path / recording.name |
483 | | - session_path.mkdir(exist_ok=True) |
484 | | - # add raw videos |
485 | | - videos_path = session_path / "videos-raw" |
486 | | - videos_path.mkdir(exist_ok=True) |
487 | | - for video in videos.values(): |
488 | | - src = Path(video).resolve() |
489 | | - dst = videos_path / src.name |
490 | | - relpath = Path(os.path.relpath(src, videos_path.resolve())) |
491 | | - if dst.exists(): |
492 | | - os.remove(dst) |
493 | | - os.symlink(relpath, dst) |
494 | | - # add calibration |
495 | | - calibration_path = session_path / "calibration" |
496 | | - calibration_path.mkdir(exist_ok=True) |
497 | | - # add calibration files |
498 | | - cal_key = RecordingKey(recording.session, recording.name) |
499 | | - matches = [k for k in self.calibrations.keys() if cal_key.matches(k)] |
500 | | - if len(matches) == 0: |
501 | | - raise RuntimeError(f"No calibration found for {recording} when setting up triangulation") |
502 | | - for match in matches: |
503 | | - for video in self.calibrations[match].values(): |
| 482 | + with FileLock(self.path / "setup_anipose.lock"): |
| 483 | + # make anipose project folder |
| 484 | + self.triangulation_path.mkdir(exist_ok=True) |
| 485 | + # create session subfolders |
| 486 | + for recording, videos in self.sessions.items(): |
| 487 | + session_path = self.triangulation_path / recording.name |
| 488 | + session_path.mkdir(exist_ok=True) |
| 489 | + # add raw videos |
| 490 | + videos_path = session_path / "videos-raw" |
| 491 | + videos_path.mkdir(exist_ok=True) |
| 492 | + for video in videos.values(): |
504 | 493 | src = Path(video).resolve() |
505 | | - dst = calibration_path / src.name |
506 | | - relpath = Path(os.path.relpath(src, calibration_path.resolve())) |
| 494 | + dst = videos_path / src.name |
| 495 | + relpath = Path(os.path.relpath(src, videos_path.resolve())) |
507 | 496 | if dst.exists(): |
508 | 497 | os.remove(dst) |
509 | 498 | os.symlink(relpath, dst) |
510 | | - # create anipose config file |
511 | | - kp_schema = keypoints_by_group(self.keypoints) |
512 | | - for group, kps in kp_schema.items(): |
513 | | - if len(kps) > 2: |
514 | | - kp_schema[group].append(kps[0]) |
515 | | - config = { |
516 | | - "project": self.name, |
517 | | - "model_folder": os.path.relpath(self.model.project_path, os.getcwd()), |
518 | | - "nesting": 1, |
519 | | - "pipeline": {"videos-raw": "videos-raw",}, |
520 | | - "labeling": { |
521 | | - "scheme": list(kp_schema.values()), |
522 | | - "ignore": self.ignore_keypoint_labels |
523 | | - }, |
524 | | - "filter": { |
525 | | - "enabled": self.triangulation.filter2d, |
526 | | - "type": "medfilt", |
527 | | - "medfilt": 13, # length of median filter |
528 | | - "offset_threshold": 5, # offset from median filter to count as jump |
529 | | - "score_threshold": 0.8, # score below which to count as bad |
530 | | - "spline": False, # interpolate using linearly instead of cubic spline |
531 | | - }, |
532 | | - "calibration": { |
533 | | - "board_type": "charuco", |
534 | | - "board_size": [7, 7], |
535 | | - "board_marker_bits": 4, |
536 | | - "board_marker_dict_number": 50, |
537 | | - "board_marker_length": 4.5, # mm |
538 | | - "board_square_side_length": 6 # mm |
539 | | - }, |
540 | | - "triangulation": { |
541 | | - "triangulate": True, |
542 | | - "cam_regex": f"({self.view_regex})", |
543 | | - "manually_verify": False, |
544 | | - "axes": self.triangulation.axes, |
545 | | - "reference_point": self.triangulation.ref_point, |
546 | | - "optim": True, |
547 | | - "score_threshold": self.triangulation.score_threshold, |
548 | | - "scale_smooth": 0.0, |
| 499 | + # add calibration |
| 500 | + calibration_path = session_path / "calibration" |
| 501 | + calibration_path.mkdir(exist_ok=True) |
| 502 | + # add calibration files |
| 503 | + cal_key = RecordingKey(recording.session, recording.name) |
| 504 | + matches = [k for k in self.calibrations.keys() if cal_key.matches(k)] |
| 505 | + if len(matches) == 0: |
| 506 | + raise RuntimeError(f"No calibration found for {recording} when setting up triangulation") |
| 507 | + for match in matches: |
| 508 | + for video in self.calibrations[match].values(): |
| 509 | + src = Path(video).resolve() |
| 510 | + dst = calibration_path / src.name |
| 511 | + relpath = Path(os.path.relpath(src, calibration_path.resolve())) |
| 512 | + if dst.exists(): |
| 513 | + os.remove(dst) |
| 514 | + os.symlink(relpath, dst) |
| 515 | + # create anipose config file |
| 516 | + kp_schema = keypoints_by_group(self.keypoints) |
| 517 | + for group, kps in kp_schema.items(): |
| 518 | + if len(kps) > 2: |
| 519 | + kp_schema[group].append(kps[0]) |
| 520 | + config = { |
| 521 | + "project": self.name, |
| 522 | + "model_folder": os.path.relpath(self.model.project_path, os.getcwd()), |
| 523 | + "nesting": 1, |
| 524 | + "pipeline": {"videos-raw": "videos-raw",}, |
| 525 | + "labeling": { |
| 526 | + "scheme": list(kp_schema.values()), |
| 527 | + "ignore": self.ignore_keypoint_labels |
| 528 | + }, |
| 529 | + "filter": { |
| 530 | + "enabled": self.triangulation.filter2d, |
| 531 | + "type": "medfilt", |
| 532 | + "medfilt": 13, # length of median filter |
| 533 | + "offset_threshold": 5, # offset from median filter to count as jump |
| 534 | + "score_threshold": 0.8, # score below which to count as bad |
| 535 | + "spline": False, # interpolate using linearly instead of cubic spline |
| 536 | + }, |
| 537 | + "calibration": { |
| 538 | + "board_type": "charuco", |
| 539 | + "board_size": [7, 7], |
| 540 | + "board_marker_bits": 4, |
| 541 | + "board_marker_dict_number": 50, |
| 542 | + "board_marker_length": 4.5, # mm |
| 543 | + "board_square_side_length": 6 # mm |
| 544 | + }, |
| 545 | + "triangulation": { |
| 546 | + "triangulate": True, |
| 547 | + "cam_regex": f"({self.view_regex})", |
| 548 | + "manually_verify": False, |
| 549 | + "axes": self.triangulation.axes, |
| 550 | + "reference_point": self.triangulation.ref_point, |
| 551 | + "optim": True, |
| 552 | + "score_threshold": self.triangulation.score_threshold, |
| 553 | + "scale_smooth": 0.0, |
| 554 | + } |
549 | 555 | } |
550 | | - } |
551 | | - with open(self.triangulation_path / "config.toml", "w") as f: |
552 | | - toml.dump(config, f) |
| 556 | + with open(self.triangulation_path / "config.toml", "w") as f: |
| 557 | + toml.dump(config, f) |
553 | 558 |
|
554 | 559 | def _load_anipose_cfg(self): |
555 | 560 | from anipose.anipose import load_config |
|
0 commit comments