diff --git a/deeplabcut/pose_estimation_pytorch/README.md b/deeplabcut/pose_estimation_pytorch/README.md index d8d95da7ba..831c618ca3 100644 --- a/deeplabcut/pose_estimation_pytorch/README.md +++ b/deeplabcut/pose_estimation_pytorch/README.md @@ -64,20 +64,17 @@ disk. ```python from pathlib import Path -from deeplabcut.pose_estimation_pytorch.config import ( - make_pytorch_pose_config, - write_config, -) +import deeplabcut.pose_estimation_pytorch as dlc_torch project_cfg = { "Task": "mice", ... } # the configuration for your DLC project pose_config_path = Path("/path/to/my/config/pytorch_cfg.yaml") -model_cfg = make_pytorch_pose_config( +model_cfg = dlc_torch.config.make_pytorch_pose_config( project_config=project_cfg, pose_config_path=pose_config_path, net_type="hrnet_w32", top_down=True, + save=True, ) -write_config(pose_config_path, model_cfg) ``` #### Adding Models @@ -238,9 +235,9 @@ dataset creation and test/train splitting. The `DLCLoader` class is used to load labeled data for a specific shuffle. ```python3 -from deeplabcut.pose_estimation_pytorch.data import DLCLoader +import deeplabcut.pose_estimation_pytorch as dlc_torch -loader = DLCLoader( +loader = dlc_torch.DLCLoader( config="/path/to/my/project/config.yaml", trainset_index=0, shuffle=1, @@ -265,23 +262,20 @@ images and keypoints to a tensor dataset for training and evaluation. You can ge an instance of training/test dataset with your `DLCLoader`: ```python3 -from deeplabcut.pose_estimation_pytorch.data import ( - build_transforms, - DLCLoader, -) +import deeplabcut.pose_estimation_pytorch as dlc_torch -loader = DLCLoader( +loader = dlc_torch.DLCLoader( config="/path/to/my/project/config.yaml", trainset_index=0, shuffle=1, ) train_dataset = loader.create_dataset( - transform=build_transforms(loader.model_cfg["data"]["train"]), + transform=dlc_torch.build_transforms(loader.model_cfg["data"]["train"]), mode="train", task=loader.pose_task, ) valid_dataset = loader.create_dataset( - transform=build_transforms(loader.model_cfg["data"]["train"]), + transform=dlc_torch.build_transforms(loader.model_cfg["data"]["train"]), mode="test", task=loader.pose_task, ) @@ -320,15 +314,7 @@ configuration file (as described in [#model_configuration_files]). ```python3 from pathlib import Path -from deeplabcut.pose_estimation_pytorch.config import ( - make_pytorch_pose_config, - write_config, -) -from deeplabcut.pose_estimation_pytorch.data import ( - build_transforms, - COCOLoader, -) -from deeplabcut.pose_estimation_pytorch.task import Task +import deeplabcut.pose_estimation_pytorch as dlc_torch # Specify project paths project_root = Path("/path/to/my/COCOProject") @@ -336,42 +322,40 @@ train_json_filename = "train.json" test_json_filename = "test.json" # Parse information about the project -train_dict = COCOLoader.load_json(project_root, filename=train_json_filename) -max_num_individuals, bodyparts = COCOLoader.get_project_parameters(train_dict) +train_dict = dlc_torch.COCOLoader.load_json(project_root, filename=train_json_filename) +max_num_individuals, bodyparts = dlc_torch.COCOLoader.get_project_parameters(train_dict) # Generate a configuration file for your PyTorch model # In this case, it's for a Top-Down HRNet_w32 experiment_path = project_root / "experiments" / "hrnet_w32" model_cfg_path = experiment_path / "train" / "pytorch_cfg.yaml" -model_cfg = make_pytorch_pose_config( - project_config={ - "project_path": str(project_root.resolve()), - "multianimalproject": max_num_individuals > 1, - "bodyparts": bodyparts, - "multianimalbodyparts": bodyparts, - "uniquebodyparts": [], - "individuals": [f"idv{i}" for i in range(max_num_individuals)], - }, +model_cfg = dlc_torch.config.make_pytorch_pose_config( + project_config=dlc_torch.config.make_basic_project_config( + dataset_path=str(project_root.resolve()), + bodyparts=bodyparts, + max_individuals=max_num_individuals, + multi_animal=True, + ), pose_config_path=experiment_path, net_type="hrnet_w32", top_down=True, + save=True, ) -write_config(config_path=model_cfg_path, config=model_cfg) # Create the loader for the COCO dataset -loader = COCOLoader( +loader = dlc_torch.COCOLoader( project_root=project_root, model_config_path="/path/to/my/project/experiments/pytorch_config.yaml", train_json_filename=train_json_filename, test_json_filename=test_json_filename, ) train_dataset = loader.create_dataset( - transform=build_transforms(loader.model_cfg["data"]["train"]), + transform=dlc_torch.build_transforms(loader.model_cfg["data"]["train"]), mode="train", task=loader.pose_task, ) valid_dataset = loader.create_dataset( - transform=build_transforms(loader.model_cfg["data"]["train"]), + transform=dlc_torch.build_transforms(loader.model_cfg["data"]["train"]), mode="test", task=loader.pose_task, ) @@ -389,16 +373,15 @@ pretrained weights, and either train them or run inference with them. ```python from pathlib import Path +import deeplabcut.pose_estimation_pytorch as dlc_torch from deeplabcut.pose_estimation_pytorch.apis.train import train -from deeplabcut.pose_estimation_pytorch.data import COCOLoader -from deeplabcut.pose_estimation_pytorch.task import Task # Specify project paths project_root = Path("/path/to/my/COCOProject") train_json_filename = "train.json" test_json_filename = "test.json" -loader = COCOLoader( +loader = dlc_torch.COCOLoader( project_root=project_root, model_config_path="/path/to/my/project/experiments/pytorch_config.yaml", train_json_filename=train_json_filename, @@ -407,7 +390,7 @@ loader = COCOLoader( train( loader=loader, run_config=loader.model_cfg, - task=Task(loader.model_cfg["method"]), + task=dlc_torch.Task(loader.model_cfg["method"]), device="cuda:2", logger_config=dict( type="WandbLogger", @@ -421,7 +404,7 @@ train( ### Running Video Analysis outside a DeepLabCut Project DeepLabCut provides high-level APIs (via the GUI or the python package) to analyze your -data. The usage of this API assumes the existance of a DLC project (with `config.yaml` +data. The usage of this API assumes the existence of a DLC project (with `config.yaml` file, etc.). Sometimes it might be more convenient to just run a model on your data via a low-level @@ -431,10 +414,7 @@ example below: ```python from pathlib import Path -from deeplabcut.pose_estimation_pytorch import Task -from deeplabcut.pose_estimation_pytorch.apis.analyze_videos import video_inference -from deeplabcut.pose_estimation_pytorch.config import read_config_as_dict -from deeplabcut.pose_estimation_pytorch.apis.utils import get_inference_runners +import deeplabcut.pose_estimation_pytorch as dlc_torch train_dir = Path("/Users/Jaylen/my-dlc-models/train") pytorch_config_path = train_dir / "pytorch_config.yaml" @@ -450,21 +430,26 @@ batch_size = 16 detector_batch_size = 8 # read model configuration -model_cfg = read_config_as_dict(pytorch_config_path) - -pose_task = Task(model_cfg["method"]) -pose_runner, detector_runner = get_inference_runners( +model_cfg = dlc_torch.config.read_config_as_dict(pytorch_config_path) +pose_task = dlc_torch.Task(model_cfg["method"]) +pose_runner = dlc_torch.get_pose_inference_runner( model_config=model_cfg, snapshot_path=snapshot_path, max_individuals=max_num_animals, batch_size=batch_size, - detector_batch_size=detector_batch_size, - detector_path=detector_snapshot_path, ) -predictions = video_inference( +detector_runner = None +if pose_task == dlc_torch.Task.TOP_DOWN: + detector_runner = dlc_torch.get_detector_inference_runner( + model_config=model_cfg, + snapshot_path=detector_snapshot_path, + max_individuals=max_num_animals, + batch_size=detector_batch_size, + ) + +predictions = dlc_torch.video_inference( video=video_path, - task=pose_task, pose_runner=pose_runner, detector_runner=detector_runner, ) @@ -486,13 +471,11 @@ You can easily do so by writing a bit of custom code, as shown in the example be from pathlib import Path import numpy as np -from deeplabcut.pose_estimation_pytorch import get_inference_runners -from deeplabcut.pose_estimation_pytorch.apis import VideoIterator -from deeplabcut.pose_estimation_pytorch.config import read_config_as_dict +import deeplabcut.pose_estimation_pytorch as dlc_torch from tqdm import tqdm # create an iterator for your video -video = VideoIterator("/Users/Jayson/my-cool-video.mp4") +video = dlc_torch.VideoIterator("/Users/Jayson/my-cool-video.mp4") # dummy bboxes - you can load yours from a file or in another way # the bboxes should be in `xywh` format, i.e. (x_top_left, y_top_left, width, height) @@ -512,8 +495,8 @@ video.set_context(bounding_boxes) max_individuals = np.max([len(context["bboxes"]) for context in bounding_boxes]) # run inference! -model_cfg = read_config_as_dict("/Users/Jayson/pytorch_config.yaml") -pose_runner, _ = get_inference_runners( +model_cfg = dlc_torch.config.read_config_as_dict("/Users/Jayson/pytorch_config.yaml") +pose_runner = dlc_torch.get_pose_inference_runner( model_config=model_cfg, snapshot_path=Path("/Users/Jayson/model-snapshot.pt"), max_individuals=max_individuals, diff --git a/deeplabcut/pose_estimation_pytorch/__init__.py b/deeplabcut/pose_estimation_pytorch/__init__.py index 56a16d2b8d..d8582096af 100644 --- a/deeplabcut/pose_estimation_pytorch/__init__.py +++ b/deeplabcut/pose_estimation_pytorch/__init__.py @@ -8,31 +8,53 @@ # # Licensed under GNU Lesser General Public License v3.0 # +import deeplabcut.pose_estimation_pytorch.config as config from deeplabcut.pose_estimation_pytorch.apis import ( + analyze_image_folder, analyze_images, analyze_videos, + build_predictions_dataframe, + create_labeled_images, convert_detections2tracklets, - get_inference_runners, + evaluate, evaluate_network, extract_maps, extract_save_all_maps, + get_detector_inference_runner, + get_pose_inference_runner, + predict, + superanimal_analyze_images, + train, train_network, + video_inference, + VideoIterator, + visualize_predictions, ) from deeplabcut.pose_estimation_pytorch.config import ( available_detectors, available_models, ) -from deeplabcut.pose_estimation_pytorch.data.base import Loader -from deeplabcut.pose_estimation_pytorch.data.cocoloader import COCOLoader -from deeplabcut.pose_estimation_pytorch.data.dataset import ( +from deeplabcut.pose_estimation_pytorch.data import ( + build_transforms, + COCOLoader, + COLLATE_FUNCTIONS, + DLCLoader, + Loader, PoseDataset, PoseDatasetParameters, ) -from deeplabcut.pose_estimation_pytorch.data.dlcloader import DLCLoader -from deeplabcut.pose_estimation_pytorch.runners.base import ( +from deeplabcut.pose_estimation_pytorch.runners import ( + build_inference_runner, + build_training_runner, + DetectorInferenceRunner, + DetectorTrainingRunner, get_load_weights_only, + InferenceRunner, + PoseInferenceRunner, + PoseTrainingRunner, set_load_weights_only, + TorchSnapshotManager, + TrainingRunner, ) -from deeplabcut.pose_estimation_pytorch.runners.snapshots import TorchSnapshotManager from deeplabcut.pose_estimation_pytorch.task import Task from deeplabcut.pose_estimation_pytorch.utils import fix_seeds diff --git a/deeplabcut/pose_estimation_pytorch/apis/__init__.py b/deeplabcut/pose_estimation_pytorch/apis/__init__.py index 76043e6d37..a6e886c678 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/__init__.py +++ b/deeplabcut/pose_estimation_pytorch/apis/__init__.py @@ -12,6 +12,7 @@ from deeplabcut.pose_estimation_pytorch.apis.analyze_images import ( analyze_image_folder, analyze_images, + analyze_image_folder, superanimal_analyze_images, ) from deeplabcut.pose_estimation_pytorch.apis.analyze_videos import ( @@ -23,8 +24,10 @@ convert_detections2tracklets, ) from deeplabcut.pose_estimation_pytorch.apis.evaluate import ( + predict, evaluate, evaluate_network, + visualize_predictions, ) from deeplabcut.pose_estimation_pytorch.apis.export import export_model from deeplabcut.pose_estimation_pytorch.apis.train import ( @@ -37,6 +40,12 @@ get_pose_inference_runner, ) from deeplabcut.pose_estimation_pytorch.apis.visualization import ( + create_labeled_images, extract_maps, extract_save_all_maps, ) +from deeplabcut.pose_estimation_pytorch.apis.utils import ( + build_predictions_dataframe, + get_detector_inference_runner, + get_pose_inference_runner, +) diff --git a/deeplabcut/pose_estimation_pytorch/apis/analyze_images.py b/deeplabcut/pose_estimation_pytorch/apis/analyze_images.py index 7db94c2af2..a23f572470 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/analyze_images.py +++ b/deeplabcut/pose_estimation_pytorch/apis/analyze_images.py @@ -29,9 +29,10 @@ from deeplabcut.core.engine import Engine from deeplabcut.modelzoo.utils import get_superanimal_colormaps from deeplabcut.pose_estimation_pytorch.apis.utils import ( + get_detector_inference_runner, build_predictions_dataframe, - get_inference_runners, get_model_snapshots, + get_pose_inference_runner, get_scorer_name, get_scorer_uid, parse_snapshot_index_for_analysis, @@ -313,6 +314,7 @@ def analyze_images( images = list(predictions.keys()) output_dir = Path(images[0]).parent.resolve() print(f"Setting output directory to {output_dir}") + output_dir = Path(output_dir) output_dir.mkdir(exist_ok=True) @@ -425,26 +427,17 @@ def analyze_image_folder( f" Please specify the `detector_path` parameter." ) - bodyparts = model_cfg["metadata"]["bodyparts"] - unique_bodyparts = model_cfg["metadata"]["unique_bodyparts"] - individuals = model_cfg["metadata"]["individuals"] if max_individuals is None: - max_individuals = len(individuals) + max_individuals = len(model_cfg["metadata"]["individuals"]) if device is None: device = resolve_device(model_cfg) - pose_runner, detector_runner = get_inference_runners( + pose_runner = get_pose_inference_runner( model_config=model_cfg, snapshot_path=snapshot_path, - max_individuals=max_individuals, - num_bodyparts=len(bodyparts), - num_unique_bodyparts=len(unique_bodyparts), device=device, - with_identity=False, - transform=None, - detector_path=detector_path, - detector_transform=None, + max_individuals=max_individuals, ) image_suffixes = ".png", ".jpg", ".jpeg" @@ -453,8 +446,14 @@ def analyze_image_folder( image_paths = parse_images_and_image_folders(images, image_suffixes) pose_inputs = image_paths - if detector_runner is not None: + if detector_path is not None: logging.info(f"Running object detection with {detector_path}") + detector_runner = get_detector_inference_runner( + model_config=model_cfg, + snapshot_path=detector_path, + device=device, + max_individuals=max_individuals, + ) detector_image_paths = image_paths if progress_bar: diff --git a/deeplabcut/pose_estimation_pytorch/apis/analyze_videos.py b/deeplabcut/pose_estimation_pytorch/apis/analyze_videos.py index f4ecc22856..6de6bd7520 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/analyze_videos.py +++ b/deeplabcut/pose_estimation_pytorch/apis/analyze_videos.py @@ -88,7 +88,6 @@ def __next__(self) -> np.ndarray | tuple[str, dict[str, Any]]: def video_inference( video: str | Path | VideoIterator, - task: Task, pose_runner: InferenceRunner, detector_runner: InferenceRunner | None = None, cropping: list[int] | None = None, @@ -99,10 +98,11 @@ def video_inference( Args: video: The video to analyze - task: The pose task to run (bottom-up or top-down) pose_runner: The pose runner to run inference with - detector_runner: When ``task==Task.TOP_DOWN``, the detector runner to obtain - bounding boxes for the video. + detector_runner: When the pose model is a top-down model, a detector runner can + be given to obtain bounding boxes for the video. If the pose model is a + top-down model and no detector_runner is given, the bounding boxes must + already be set in the VideoIterator (see examples). cropping: Optionally, video inference can be run on a cropped version of the video. To do so, pass a list containing 4 elements to specify which area of the video should be analyzed: ``[xmin, xmax, ymin, ymax]``. @@ -119,6 +119,41 @@ def video_inference( Predictions for each frame in the video. If a shelf_manager is given, this list will be empty and the predictions will exclusively be stored in the file written by the shelf. + + Examples: + Bottom-up video analysis: + >>> import deeplabcut.pose_estimation_pytorch as pep + >>> model_cfg = pep.config.read_config_as_dict("pytorch_config.yaml") + >>> runner = pep.get_pose_inference_runner(model_cfg, "snapshot.pt") + >>> video_predictions = pep.video_inference("video.mp4", runner) + >>> + + Top-down video analysis: + >>> import deeplabcut.pose_estimation_pytorch as pep + >>> model_cfg = pep.config.read_config_as_dict("pytorch_config.yaml") + >>> runner = pep.get_pose_inference_runner(model_cfg, "snapshot.pt") + >>> d_runner = pep.get_pose_inference_runner(model_cfg, "snapshot-detector.pt") + >>> video_predictions = pep.video_inference("video.mp4", runner, d_runner) + >>> + + Top-Down pose estimation with pre-computed bounding boxes: + >>> import numpy as np + >>> import deeplabcut.pose_estimation_pytorch as pep + >>> + >>> video_iterator = pep.VideoIterator("video.mp4") + >>> video_iterator.set_context([ + >>> { # frame 1 context + >>> "bboxes": np.array([[12, 17, 4, 5]]), # format (x0, y0, w, h) + >>> }, + >>> { # frame 1 context + >>> "bboxes": np.array([[12, 17, 4, 5], [18, 92, 54, 32]]), + >>> }, + >>> ... + >>> ]) + >>> model_cfg = pep.config.read_config_as_dict("pytorch_config.yaml") + >>> runner = pep.get_pose_inference_runner(model_cfg, "snapshot.pt") + >>> video_predictions = pep.video_inference(video_iterator, runner) + >>> """ if not isinstance(video, VideoIterator): video = VideoIterator(str(video), cropping=cropping) @@ -134,11 +169,7 @@ def video_inference( f" resolution: w={vid_w}, h={vid_h}\n" ) - if task == Task.TOP_DOWN: - # Get bounding boxes for context - if detector_runner is None: - raise ValueError("Must use a detector for top-down video analysis") - + if detector_runner is not None: print(f"Running detector with batch size {detector_runner.batch_size}") bbox_predictions = detector_runner.inference(images=tqdm(video)) video.set_context(bbox_predictions) @@ -407,7 +438,6 @@ def analyze_videos( predictions = video_inference( video=video_iterator, pose_runner=pose_runner, - task=pose_task, detector_runner=detector_runner, cropping=cropping, shelf_writer=shelf_writer, diff --git a/deeplabcut/pose_estimation_pytorch/apis/evaluate.py b/deeplabcut/pose_estimation_pytorch/apis/evaluate.py index 79d213d892..cc8fe913c0 100755 --- a/deeplabcut/pose_estimation_pytorch/apis/evaluate.py +++ b/deeplabcut/pose_estimation_pytorch/apis/evaluate.py @@ -79,7 +79,10 @@ def predict( context = bbox_predictions else: ground_truth_bboxes = loader.ground_truth_bboxes(mode=mode) - context = [{"bboxes": ground_truth_bboxes[image]} for image in image_paths] + context = [ + {"bboxes": ground_truth_bboxes[image]["bboxes"]} + for image in image_paths + ] images_with_context = image_paths if context is not None: diff --git a/deeplabcut/pose_estimation_pytorch/apis/train.py b/deeplabcut/pose_estimation_pytorch/apis/train.py index 41269d4924..a3f249c910 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/train.py +++ b/deeplabcut/pose_estimation_pytorch/apis/train.py @@ -358,6 +358,7 @@ def train_network( logger_config=loader.model_cfg.get("logger"), snapshot_path=snapshot_path, max_snapshots_to_keep=max_snapshots_to_keep, + load_head_weights=load_head_weights, ) destroy_file_logging() diff --git a/deeplabcut/pose_estimation_pytorch/apis/visualization.py b/deeplabcut/pose_estimation_pytorch/apis/visualization.py index 0a790a2307..b923b2040b 100644 --- a/deeplabcut/pose_estimation_pytorch/apis/visualization.py +++ b/deeplabcut/pose_estimation_pytorch/apis/visualization.py @@ -550,7 +550,9 @@ def _get_context( bboxes_train = loader.ground_truth_bboxes(mode="train") bboxes_test = loader.ground_truth_bboxes(mode="test") bboxes = {**bboxes_train, **bboxes_test} - return [dict(bboxes=bboxes[str(img_path)]) for img_path in image_paths] + return [ + dict(bboxes=bboxes[str(img_path)]["bboxes"]) for img_path in image_paths + ] detector_runner = utils.get_detector_inference_runner( model_config=loader.model_cfg, diff --git a/deeplabcut/pose_estimation_pytorch/config/__init__.py b/deeplabcut/pose_estimation_pytorch/config/__init__.py index 2cf1468e63..3e62f52750 100644 --- a/deeplabcut/pose_estimation_pytorch/config/__init__.py +++ b/deeplabcut/pose_estimation_pytorch/config/__init__.py @@ -9,6 +9,7 @@ # Licensed under GNU Lesser General Public License v3.0 # from deeplabcut.pose_estimation_pytorch.config.make_pose_config import ( + make_basic_project_config, make_pytorch_pose_config, make_pytorch_test_config, ) diff --git a/deeplabcut/pose_estimation_pytorch/config/make_pose_config.py b/deeplabcut/pose_estimation_pytorch/config/make_pose_config.py index 65cdff2d1d..25aa6b9050 100644 --- a/deeplabcut/pose_estimation_pytorch/config/make_pose_config.py +++ b/deeplabcut/pose_estimation_pytorch/config/make_pose_config.py @@ -217,6 +217,75 @@ def make_pytorch_test_config( return test_config +def make_basic_project_config( + dataset_path: Path | str, + bodyparts: list[str], + max_individuals: int, + multi_animal: bool = True, +) -> dict: + """Creates a basic configuration dict that can be used to create model configs. + + This should be used to create the `project_config` given to + `make_pytorch_pose_config` for non-DeepLabCut projects (e.g. when creating a + configuration file for a model that will be trained on a COCO dataset). + + Args: + dataset_path: The path to the dataset for which the config will be created. + bodyparts: The bodyparts labeled for individuals in the dataset. + max_individuals: The maximum number of individuals to detect in a single image. + multi_animal: Whether multiple animals can be present in an image. + + Returns: + The created project configuration dict that can be given to + `make_pytorch_pose_config`. + + Examples: + Creating a `pytorch_config` for a ResNet50 backbone with a part-affinity head ( + as multi_animal=True and top_down=False) + + >>> import deeplabcut.pose_estimation_pytorch as pep + >>> project_config = pep.config.make_basic_project_config( + >>> dataset_path="/path/coco", + >>> bodyparts=["nose", "left_eye", "right_eye"], + >>> max_individuals=12, + >>> multi_animal=True, + >>> ) + >>> model_config = pep.config.make_pytorch_pose_config( + >>> project_config=project_config, + >>> pose_config_path="/path/coco/models/resnet50/pytorch_config.yaml", + >>> net_type="resnet_50", + >>> top_down=False, + >>> save=True, + >>> ) + + Creating a `pytorch_config` for a ResNet50 backbone with a simple heatmap head + (as the project is single-animal): + + >>> import deeplabcut.pose_estimation_pytorch as pep + >>> project_config = pep.config.make_basic_project_config( + >>> dataset_path="/path/coco", + >>> bodyparts=["nose", "left_eye", "right_eye"], + >>> max_individuals=1, + >>> multi_animal=False, + >>> ) + >>> model_config = pep.config.make_pytorch_pose_config( + >>> project_config=project_config, + >>> pose_config_path="/path/coco/models/resnet50/pytorch_config.yaml", + >>> net_type="resnet_50", + >>> top_down=False, + >>> save=True, + >>> ) + """ + return dict( + project_path=str(dataset_path), + multianimalproject=multi_animal, + bodyparts=bodyparts, + multianimalbodyparts=bodyparts, + uniquebodyparts=[], + individuals=[f"individual{i:03d}" for i in range(max_individuals)], + ) + + def add_metadata( project_config: dict, config: dict, pose_config_path: str | Path ) -> dict: diff --git a/deeplabcut/pose_estimation_pytorch/data/__init__.py b/deeplabcut/pose_estimation_pytorch/data/__init__.py index fc42676461..e4ae41d5b1 100644 --- a/deeplabcut/pose_estimation_pytorch/data/__init__.py +++ b/deeplabcut/pose_estimation_pytorch/data/__init__.py @@ -10,6 +10,7 @@ # from deeplabcut.pose_estimation_pytorch.data.base import Loader from deeplabcut.pose_estimation_pytorch.data.cocoloader import COCOLoader +from deeplabcut.pose_estimation_pytorch.data.collate import COLLATE_FUNCTIONS from deeplabcut.pose_estimation_pytorch.data.dlcloader import DLCLoader from deeplabcut.pose_estimation_pytorch.data.dataset import ( PoseDatasetParameters, diff --git a/deeplabcut/pose_estimation_pytorch/data/base.py b/deeplabcut/pose_estimation_pytorch/data/base.py index 705df4a1ad..e854a88120 100644 --- a/deeplabcut/pose_estimation_pytorch/data/base.py +++ b/deeplabcut/pose_estimation_pytorch/data/base.py @@ -149,7 +149,7 @@ def ground_truth_keypoints( return ground_truth_dict - def ground_truth_bboxes(self, mode: str = "train") -> dict[str, np.ndarray]: + def ground_truth_bboxes(self, mode: str = "train") -> dict[str, dict]: """Creates a dictionary containing the ground truth bounding boxes Args: @@ -158,7 +158,14 @@ def ground_truth_bboxes(self, mode: str = "train") -> dict[str, np.ndarray]: Returns: A dict mapping image paths to the ground truth annotations for the mode in the format: - {'image': bboxes with shape (num_individuals, xywh)} + { + 'path/to/image000.png': { + "width": (int) the width of the image, in pixels + "height": (int) the height of the image, in pixels + "bboxes": (np.ndarray) bboxes with shape (num_individuals, xywh) + }, + 'path/to/image000.png': {...}, + } """ if mode not in self._loaded_data: self._loaded_data[mode] = self.load_data(mode) @@ -176,7 +183,12 @@ def ground_truth_bboxes(self, mode: str = "train") -> dict[str, np.ndarray]: bboxes = np.zeros((0, 4)) else: bboxes = _compute_crop_bounds(np.stack(bboxes, axis=0), img_shape) - ground_truth_dict[image_path] = bboxes + + ground_truth_dict[image_path] = dict( + width=image["width"], + height=image["height"], + bboxes=bboxes, + ) return ground_truth_dict diff --git a/deeplabcut/pose_estimation_pytorch/modelzoo/inference.py b/deeplabcut/pose_estimation_pytorch/modelzoo/inference.py index 361e1d5a6e..aa0869dc3b 100644 --- a/deeplabcut/pose_estimation_pytorch/modelzoo/inference.py +++ b/deeplabcut/pose_estimation_pytorch/modelzoo/inference.py @@ -108,7 +108,6 @@ def _video_inference_superanimal( detector_batch_size=detector_batch_size, detector_path=detector_snapshot_path, ) - pose_task = Task(model_cfg.get("method", "BU")) results = {} if isinstance(video_paths, str): @@ -138,7 +137,6 @@ def _video_inference_superanimal( video = VideoIterator(video_path, cropping=cropping) predictions = video_inference( video, - task=pose_task, pose_runner=pose_runner, detector_runner=detector_runner, ) diff --git a/tests/pose_estimation_pytorch/config/test_make_pose_config.py b/tests/pose_estimation_pytorch/config/test_make_pose_config.py index aaa1f6566b..4208d96ab0 100644 --- a/tests/pose_estimation_pytorch/config/test_make_pose_config.py +++ b/tests/pose_estimation_pytorch/config/test_make_pose_config.py @@ -11,7 +11,11 @@ """Tests the pre-processors""" import pytest -from deeplabcut.pose_estimation_pytorch.config.make_pose_config import make_pytorch_pose_config +import deeplabcut.utils.auxiliaryfunctions as af +from deeplabcut.pose_estimation_pytorch.config.make_pose_config import ( + make_basic_project_config, + make_pytorch_pose_config, +) from deeplabcut.pose_estimation_pytorch.config.utils import pretty_print, update_config @@ -417,3 +421,25 @@ def _make_project_config( project_config["bodyparts"] = bodyparts return project_config + + +@pytest.mark.parametrize("bodyparts", [["nose"], ["nose", "ear", "eye"]]) +@pytest.mark.parametrize("max_idv", [1, 12, 20]) +@pytest.mark.parametrize("multi", [True, False]) +def test_make_basic_project_config(bodyparts: list[str], max_idv: int, multi: bool): + if not multi and max_idv > 1: + return + + project_config = make_basic_project_config( + dataset_path="path/dataset", + bodyparts=bodyparts, + max_individuals=max_idv, + multi_animal=multi, + ) + + bpts = af.get_bodyparts(project_config) + assert bodyparts == bpts + + individuals = project_config["individuals"] + assert len(individuals) == max_idv + assert len(set(individuals)) == max_idv