Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 7 additions & 5 deletions deeplabcut/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from __future__ import annotations

from pathlib import Path
from typing import Iterable
from typing import Iterable, Literal

import numpy as np
from ruamel.yaml import YAML
Expand Down Expand Up @@ -666,7 +666,7 @@ def analyze_videos(
in_random_order: bool = True,
destfolder: str | None = None,
batchsize: int = None,
cropping: list[int] | None = None,
cropping: list[int] | None | Literal['from_config'] = 'from_config',
TFGPUinference: bool = True,
dynamic: tuple[bool, float, int] = (False, 0.5, 10),
modelprefix: str = "",
Expand Down Expand Up @@ -740,9 +740,11 @@ def analyze_videos(
Currently not supported by the PyTorch engine.
Change batch size for inference; if given overwrites value in ``pose_cfg.yaml``.

cropping: list or None, optional, default=None
List of cropping coordinates as [x1, x2, y1, y2].
Note that the same cropping parameters will then be used for all videos.
cropping: None, list or 'from_config', default='from_config'.
- Value None is interpreted as no cropping.
- List of cropping coordinates as [x1, x2, y1, y2].
- Value 'from_config' is interpreted as read cropping params from config.yaml.
Note: when cropping, the same parameters will be used for all videos.
If different video crops are desired, run ``analyze_videos`` on individual
videos with the corresponding cropping coordinates.

Expand Down
14 changes: 6 additions & 8 deletions deeplabcut/gui/tabs/analyze_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,15 +275,13 @@ def analyze_videos(self):
num_animals_in_videos = None
assemble_with_ID_only = False

# Read cropping parameters from config
cropping = None

if self.root.cfg["cropping"] == "True":
cropping = (
self.root.cfg["x1"],
self.root.cfg["x2"],
self.root.cfg["y1"],
self.root.cfg["y2"],
)
if self.root.cfg.get("cropping", False):
cropping = [
self.root.cfg[k]
for k in ["x1", "x2", "y1", "y2"]
]

dynamic_cropping_params = (False, 0.5, 10)
try:
Expand Down
44 changes: 32 additions & 12 deletions deeplabcut/pose_estimation_pytorch/apis/videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import pickle
import time
from pathlib import Path
from typing import Any
from typing import Any, Literal

import albumentations as A
import numpy as np
Expand Down Expand Up @@ -55,6 +55,13 @@ def __init__(
context: list[dict[str, Any]] | None = None,
cropping: list[int] | None = None,
) -> None:
"""
Args:
video_path: The path to the video to iterate over.
context: A list of dictionaries containing context for each frame.
cropping: A list of 4 integers specifying the cropping coordinates
as [x1, x2, y1, y2]. Value None is interpreted as no cropping.
"""
super().__init__(str(video_path))
self._context = context
self._index = 0
Expand Down Expand Up @@ -141,6 +148,7 @@ def video_inference(
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]``.
A value of None is interpreted as no cropping.
shelf_writer: By default, data are dumped in a pickle file at the end of the
video analysis. Passing a shelf manager writes data to disk on-the-fly
using a "shelf" (a pickle-based, persistent, database-like object by
Expand Down Expand Up @@ -270,7 +278,7 @@ def analyze_videos(
calibrate: bool = False,
identity_only: bool | None = False,
overwrite: bool = False,
cropping: list[int] | None = None,
cropping: list[int] | None | Literal["from_config"] = "from_config",
save_as_df: bool = False,
show_gpu_memory: bool = False,
inference_cfg: InferenceConfig | dict | None = None,
Expand Down Expand Up @@ -394,10 +402,14 @@ def analyze_videos(
identity_only: sub-call for auto_track. If ``True`` and animal identity was
learned by the model, assembly and tracking rely exclusively on identity
prediction.
cropping: List of cropping coordinates as [x1, x2, y1, y2]. Note that the same
cropping parameters will then be used for all videos. If different video
crops are desired, run ``analyze_videos`` on individual videos with the
corresponding cropping coordinates.
cropping:
Cropping parameters to use for the video analysis. Can be either:
- Value None which is interpreted as no cropping.
- List of cropping coordinates as [x1, x2, y1, y2].
- String "from_config" to use cropping parameters from config.yaml.
Note that for cropping the same parameters will then be used for all videos.
If different video crops are desired, run ``analyze_videos`` on
individual videos with the corresponding cropping coordinates.
save_as_df: Cannot be used when `use_shelve` is True. Saves the video
predictions (before tracking results) to an H5 file containing a pandas
DataFrame. If ``save_as_csv==True`` than the full predictions will also be
Expand Down Expand Up @@ -432,12 +444,20 @@ def analyze_videos(
detector_snapshot_index,
)

if cropping is None and loader.project_cfg.get("cropping", False):
cropping = (
loader.project_cfg["x1"],
loader.project_cfg["x2"],
loader.project_cfg["y1"],
loader.project_cfg["y2"],
# Read the cropping parameters in the configuration file (only used when cropping=='from_config')
cropping_config = (
[loader.project_cfg[k] for k in ["x1", "x2", "y1", "y2"]]
if loader.project_cfg.get("cropping", False) else None
)

if cropping == "from_config":
cropping = cropping_config
logging.info(f"Using cropping parameters from config.yaml: cropping={cropping_config}")
elif cropping is None and cropping_config:
logging.warning(
"Found cropping configured in config.yaml, but analyze_videos received cropping=None. "
"To enable cropping, run analyze_videos with cropping=[x1, x2, y1, y2] or "
"cropping='from_config'. Continuing analysis without cropping..."
)

# Get general project parameters
Expand Down
31 changes: 27 additions & 4 deletions deeplabcut/pose_estimation_tensorflow/predict_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,11 @@ def analyze_videos(
batchsize: int or None, optional, default=None
Change batch size for inference; if given overwrites value in ``pose_cfg.yaml``.

cropping: list or None, optional, default=None
List of cropping coordinates as [x1, x2, y1, y2].
Note that the same cropping parameters will then be used for all videos.
cropping: None, list or 'from_config', default='from_config'.
- Value None is interpreted as no cropping.
- List of cropping coordinates as [x1, x2, y1, y2].
- Value 'from_config' is interpreted as read cropping params from config.yaml.
Note: when cropping, the same parameters will be used for all videos.
If different video crops are desired, run ``analyze_videos`` on individual
videos with the corresponding cropping coordinates.

Expand Down Expand Up @@ -484,11 +486,32 @@ def analyze_videos(
trainFraction = cfg["TrainingFraction"][trainingsetindex]
iteration = cfg["iteration"]

if cropping is not None:
if cropping is not None and cropping != 'from_config':
# FIXME: (Jaap) writing cropping parameters directly to config.yaml
# can lead to unexpected behavior.
cfg["cropping"] = True
cfg["x1"], cfg["x2"], cfg["y1"], cfg["y2"] = cropping
print("Overwriting cropping parameters:", cropping)
print("These are used for all videos, but won't be save to the cfg file.")
else:
if cropping is None:
print(
"Warning: cropping=None is not supported for tensorflow models. "
f"Defaulting to cropping='from_config'."
)
try:
enable_cropping = cfg['cropping']
cropping_coords = [cfg["x1"], cfg["x2"], cfg["y1"], cfg["y2"]]
except KeyError as e:
missing_keys = [k for k in ("x1", "x2", "y1", "y2") if k not in cfg]
raise KeyError(
"Cropping was set to 'from_config', but the following cropping keys are "
f"missing from the project configuration: {missing_keys}"
) from e
print(
f"Using crop settings from config.yaml (cropping={enable_cropping}, "
f"with coordinates {cropping_coords})"
)

modelfolder = os.path.join(
cfg["project_path"],
Expand Down
12 changes: 9 additions & 3 deletions deeplabcut/utils/make_labeled_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,9 @@ def CreateVideo(
for i in range(n_bboxes):
bbox = bboxes[i]
x, y = bbox[0], bbox[1]
x += x1
y += y1
if cropping and not displaycropped:
x += x1
y += y1
w, h = bbox[2], bbox[3]
if bbox_scores is not None and bbox_scores[i] < bboxes_pcutoff:
continue
Expand Down Expand Up @@ -1027,6 +1028,7 @@ def proc_video(
plot_bboxes=plot_bboxes,
bboxes_list=bboxes_list,
bboxes_pcutoff=bboxes_pcutoff,
cropping=cropping,
)

return True
Expand Down Expand Up @@ -1058,6 +1060,7 @@ def create_video(
bboxes_list=None,
bboxes_pcutoff=0.6,
bboxes_color: tuple | None = None,
cropping: bool | None = None,
):
if color_by not in ("bodypart", "individual"):
raise ValueError("`color_by` should be either 'bodypart' or 'individual'.")
Expand All @@ -1075,7 +1078,10 @@ def create_video(
fps=fps,
)

cropping = bbox != (0, clip.w, 0, clip.h)
# If not specified whether to crop or not,
# infer from the bounding box size if cropping is needed.
if cropping is None:
cropping = bbox != (0, clip.w, 0, clip.h)

x1, x2, y1, y2 = bbox if bbox is not None else (0, clip.w, 0, clip.h)

Expand Down