Source code for simba.labelling.extract_labelled_frames
__author__ = "Simon Nilsson; sronilsson@gmail.com"
import os.path
from typing import Dict, List, Optional, Union
try:
from typing import Literal
except:
from typing_extensions import Literal
import cv2
from simba.mixins.config_reader import ConfigReader
from simba.mixins.plotting_mixin import PlottingMixin
from simba.utils.checks import (check_file_exist_and_readable, check_float,
check_if_df_field_is_boolean, check_str,
check_that_column_exist, check_valid_boolean,
check_valid_lst)
from simba.utils.data import detect_bouts
from simba.utils.printing import stdout_information, stdout_success
from simba.utils.read_write import (check_valid_dataframe, create_directory,
find_video_of_file, get_fn_ext,
get_video_meta_data, read_df,
read_frm_of_video)
from simba.utils.warnings import FrameRangeWarning
IMG_FORMATS = ('png', 'webp', 'jpg')
VIDEO_FORMATS = ('mp4', 'avi', 'webm')
[docs]class AnnotationFrameExtractor(ConfigReader):
"""
Extract frames annotated as behavior-present and save them as image files.
:param Union[str, os.PathLike] config_path: Path to the SimBA ``project_config.ini`` file.
:param List[Union[str, os.PathLike]] data_paths: Annotation file paths to read labels from.
:param List[str] clfs: Names of classifiers to extract behavior-present images for.
:param Optional[Union[float, int]] img_downsample_factor: Optional image downsampling factor. If ``None`` or ``1``, no downsampling is applied.
:param Optional[Literal['png', 'webp', 'jpg']] img_format: Output image format.
:param Optional[bool] img_greyscale: If ``True``, save images in grayscale.
:example:
>>> extractor = AnnotationFrameExtractor(
... config_path='project_folder/project_config.ini',
... data_paths=['project_folder/csv/targets_inserted/video_1.csv'],
... clfs=['Sniffing', 'Attack'],
... img_downsample_factor=2,
... img_format='png',
... img_greyscale=False
... )
>>> extractor.run()
"""
def __init__(self,
config_path: Union[str, os.PathLike],
data_paths: List[Union[str, os.PathLike]],
clfs: List[str],
img_downsample_factor: Optional[Union[float, int]] = None,
img_format: Literal['png', 'webp', 'jpg'] = 'png',
img_greyscale: Optional[bool] = False):
ConfigReader.__init__(self, config_path=config_path, read_video_info=False)
check_valid_lst(data=clfs, valid_dtypes=(str,), valid_values=self.clf_names, min_len=1, source='classifiers')
check_valid_lst(data=data_paths, valid_dtypes=(str,), min_len=1, source='data_paths')
for i in data_paths: check_file_exist_and_readable(file_path=i)
if img_downsample_factor is not None: check_float(name=f'{self.__class__.__name__} img_downsample_factor', value=img_downsample_factor, min_value=1.0)
if img_format is not None: check_str(name=f'{self.__class__.__name__} img_format', value=img_format, options=IMG_FORMATS)
if img_greyscale is not None: check_valid_boolean(value=img_greyscale, source=f'{self.__class__.__name__} img_greyscale', raise_error=True)
self.img_downsample_factor, self.img_format = img_downsample_factor, img_format
self.img_greyscale, self.data_paths, self.clfs = img_greyscale, data_paths, clfs
self.video_lk = {}
for file_cnt, file_path in enumerate(self.data_paths):
_, video_name, _ = get_fn_ext(filepath=file_path)
video_path = find_video_of_file(video_dir=self.video_dir, filename=video_name, raise_error=True)
self.video_lk[video_name] = video_path
def run(self):
create_directory(paths=os.path.join(self.annotated_frm_dir, 'images'), overwrite=False, verbose=False)
for file_cnt, data_path in enumerate(self.data_paths):
video_name = get_fn_ext(filepath=data_path)[1]
data_df = read_df(file_path=data_path, file_type=self.file_type)
check_valid_dataframe(df=data_df, source=f'{self.__class__.__name__} {data_path}', required_fields= self.clfs)
for field in self.clfs: check_if_df_field_is_boolean(df=data_df, field=field, df_name=data_path)
video_path = self.video_lk[video_name]
_ = get_video_meta_data(video_path=video_path)
cap, size = cv2.VideoCapture(video_path), None
for clf in self.clfs:
clf_annot_idx = list(data_df.index[data_df[clf] == 1])
if len(clf_annot_idx) == 0:
FrameRangeWarning(msg=f'No annotations found for classifier {clf} in video {video_name}. Skipping the classifier from file {data_path} ....', source=self.__class__.__name__)
continue
for frm_cnt, frm in enumerate(clf_annot_idx):
img = read_frm_of_video(video_path=cap, frame_index=frm, size=size, greyscale=self.img_greyscale)
img_save_path = os.path.join(self.annotated_frm_dir, 'images', f'{video_name}_{clf}_{frm_cnt}.{self.img_format}')
cv2.imwrite(img_save_path, img)
stdout_information(f"Saved {clf} annotated image for classifier {clf} at location {img_save_path} ({str(frm_cnt)}/{str(len(clf_annot_idx))}), video: {file_cnt+1}/{len(self.data_paths)}, ({video_name}) ...")
self.timer.stop_timer()
stdout_success(msg=f"Annotated frames saved in {self.annotated_frm_dir} directory", elapsed_time=self.timer.elapsed_time_str)
# test = AnnotationFrameExtractor(config_path=r'E:\troubleshooting\mitra\project_folder\project_config.ini',
# clfs=['grooming'],
# img_downsample_factor=4,
# data_paths=[r"E:\troubleshooting\mitra\project_folder\csv\targets_inserted\grooming\502_MA141_Gi_Saline_0517.csv"],
# img_greyscale=True)
# test.run()