Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Videolab: Minimum Viable Class #231

Closed
wants to merge 14 commits into from
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ pytorch = ["torchvision>=0.12.0"]
azure = ["adlfs>=2022.2.0"] # latest compatible with Python 3.7
gcs = ["gcsfs>=2022.1.0"] # latest compatible with Python 3.7
s3 = ["s3fs>=2023.1.0"] # latest compatible with Python 3.7
video = ["av>=10.0.0"]

all = ["cleanvision[huggingface,pytorch,azure,gcs,s3]"]
all = ["cleanvision[huggingface,pytorch,azure,gcs,s3,video]"]

[project.urls]
"Source" = "https://github.com/cleanlab/cleanvision"
"Bug Tracker" = "https://github.com/cleanlab/cleanvision/issues"
"Documentation" = "https://cleanvision.readthedocs.io/"

376 changes: 376 additions & 0 deletions src/cleanvision/videolab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,376 @@
"""Videolab is an extension of Imagelab for finding issues in a video dataset."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amazing stuff!

Could you comment:

A minimal example script of how to use this new Videolab and what the outputs look like?

A comment with comprehensive list of limitations of this code?
Eg:

  • What video file-type requirements are there.
  • What happens if some videos are really long some really short.
  • any efficiency bottlenecks where the code feels slow
  • edge cases where the results might not be good or code might crash.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! That will super useful while our team is reviewing the current code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great callout, ya replacing the FrameSampler will certainly need to be improved before we publicly announce this module to users. But I don't think it is critical to this PR being merged and could be addressed in a future follow-up PR.

Thanks for the comprehensive benchmarking/profiling, very helpful! Our team will get to the code review as soon as we're able to

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job, if you need me to optimise this, let me know, there are some avenues to explore here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lafemmephile Do you want to make one of your notebooks here:
https://github.com/lafemmephile/videolab/tree/master/notebooks

the official quickstart tutorial for Videolab?

Here's a GH issue detailing what we need for our quick-start tutorial:
#239

Should be a simple PR of one of your existing notebooks, which you can make separately from the Videolab source code PR in parallel, if you're interested!

from __future__ import annotations

Check warning on line 2 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L2

Added line #L2 was not covered by tests

import pickle
from copy import deepcopy
from importlib import import_module
from pathlib import Path
from typing import Any, Dict, Generator, Iterator, List, Optional, Type, TypeVar, Union

Check warning on line 8 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L4-L8

Added lines #L4 - L8 were not covered by tests

import pandas as pd
from PIL.Image import Image
from tqdm.auto import tqdm

Check warning on line 12 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L10-L12

Added lines #L10 - L12 were not covered by tests

from cleanvision.dataset.base_dataset import Dataset
from cleanvision.imagelab import Imagelab
from cleanvision.utils.utils import get_is_issue_colname

Check warning on line 16 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L14-L16

Added lines #L14 - L16 were not covered by tests

OBJECT_FILENAME = "videolab.pkl"
ISSUES_FILENAME = "frame_issues.csv"
ISSUE_SUMMARY_FILENAME = "frame_issue_summary.csv"
VIDEO_FILE_EXTENSIONS = ["*.mp4", "*.avi", "*.mkv", "*.mov", "*.webm"]

Check warning on line 21 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L18-L21

Added lines #L18 - L21 were not covered by tests

__all__ = ["Videolab"]
TVideolab = TypeVar("TVideolab", bound="Videolab")

Check warning on line 24 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L23-L24

Added lines #L23 - L24 were not covered by tests


class VideoDataset(Dataset):

Check warning on line 27 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L27

Added line #L27 was not covered by tests
"""Wrapper class to handle video datasets."""

def __init__(

Check warning on line 30 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L30

Added line #L30 was not covered by tests
self,
data_folder: Optional[str] = None,
filepaths: Optional[List[str]] = None,
) -> None:
"""Determine video dataset source and populate index."""
# check if data folder is given
if data_folder:

Check warning on line 37 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L37

Added line #L37 was not covered by tests
# get filepaths from video dataset directory
self._filepaths = [
str(path) for path in self.__get_filepaths(Path(data_folder))
]

else:
# store user supplied video file paths
assert filepaths is not None
self._filepaths = filepaths

Check warning on line 46 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L46

Added line #L46 was not covered by tests

# create index
self._set_index()

Check warning on line 49 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L49

Added line #L49 was not covered by tests

def __len__(self) -> int:

Check warning on line 51 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L51

Added line #L51 was not covered by tests
"""Get video dataset file count."""
return len(self.index)

Check warning on line 53 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L53

Added line #L53 was not covered by tests

def __iter__(self) -> Iterator[Union[int, str]]:

Check warning on line 55 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L55

Added line #L55 was not covered by tests
"""Defining the iteration behavior."""
return iter(self.index)

Check warning on line 57 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L57

Added line #L57 was not covered by tests

def _set_index(self) -> None:

Check warning on line 59 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L59

Added line #L59 was not covered by tests
"""Create internal storage for filepaths."""
self.index = [path for path in self._filepaths]

def __get_filepaths(self, dataset_path: Path) -> Generator[Path, None, None]:

Check warning on line 63 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L63

Added line #L63 was not covered by tests
"""Scan file system for video files and grab their file paths."""
# notify user
print(f"Reading videos from {dataset_path}")

Check warning on line 66 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L66

Added line #L66 was not covered by tests

# iterate over video file extensions
for ext in VIDEO_FILE_EXTENSIONS:
# loop through video paths matching ext
yield from dataset_path.glob(f"**/{ext}")

Check warning on line 71 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L71

Added line #L71 was not covered by tests


class FrameSampler:

Check warning on line 74 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L74

Added line #L74 was not covered by tests
"""Simplest frame sampling strategy."""

def __init__(self, k: int) -> None:

Check warning on line 77 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L77

Added line #L77 was not covered by tests
"""Store frame sample interval k and import PyAV."""
# storing frame sampling interval
self.k = k

Check warning on line 80 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L80

Added line #L80 was not covered by tests

# attempting to import PyAV
try:
self.av = import_module("av")

Check warning on line 84 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L83-L84

Added lines #L83 - L84 were not covered by tests
except ImportError as error:
raise ImportError(
"Cannot import package `av`. "
"Please install it via `pip install av` and then try again."
) from error

def _create_frame_sample_sub_dir(self, output_dir: Path, idx: int) -> Path:

Check warning on line 91 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L91

Added line #L91 was not covered by tests
"""Create a unique sub direcotry for storing frame samples from a video file."""
# create new sub directory from video_dataset index
sub_dir = output_dir / str(idx)
sub_dir.mkdir(parents=True)

Check warning on line 95 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L94-L95

Added lines #L94 - L95 were not covered by tests

# return path to new sub dir
return sub_dir

Check warning on line 98 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L98

Added line #L98 was not covered by tests

def sample(self, video_dataset: VideoDataset, output_dir: Path) -> None:

Check warning on line 100 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L100

Added line #L100 was not covered by tests
"""Loop through frames and store every k-th frame."""
# notify of sampling
print(f"Sampling frames at every {self.k} frames ...")

Check warning on line 103 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L103

Added line #L103 was not covered by tests

# iterate over video files in video data directory
for idx, video_file in enumerate(tqdm(video_dataset)):
# create frame samples sub directory
sample_sub_dir = self._create_frame_sample_sub_dir(output_dir, idx)

Check warning on line 108 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L108

Added line #L108 was not covered by tests

# open video file for streaming
with self.av.open(str(video_file)) as container:
# get video stream
stream = container.streams.video[0]

Check warning on line 113 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L113

Added line #L113 was not covered by tests

# iterate frames
for frame_indx, frame in enumerate(container.decode(stream)):
# check for k-th frame
if not frame_indx % self.k:
# get PIL image
frame_pil: Image = frame.to_image()

Check warning on line 120 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L120

Added line #L120 was not covered by tests

# use frame timestamp as image file name
image_file_name = str(frame.time) + ".jpg"

Check warning on line 123 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L123

Added line #L123 was not covered by tests

# save to output dir
frame_pil.save(sample_sub_dir / image_file_name)

Check warning on line 126 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L126

Added line #L126 was not covered by tests


class Videolab:

Check warning on line 129 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L129

Added line #L129 was not covered by tests
"""A single class to find all types of issues in video datasets."""

def __init__(

Check warning on line 132 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L132

Added line #L132 was not covered by tests
self,
video_dir: Optional[str] = None,
video_filepaths: Optional[List[str]] = None,
) -> None:
"""Create Path object from video directory string."""
# store video dataset
self.video_dataset: VideoDataset = VideoDataset(video_dir, video_filepaths)

Check warning on line 139 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L139

Added line #L139 was not covered by tests

def _sample_frames(self, samples_dir: Path, sample_interval: int) -> None:

Check warning on line 141 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L141

Added line #L141 was not covered by tests
"""Get sample frames."""
# setup frame sampler
frame_sampler = FrameSampler(sample_interval)

Check warning on line 144 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L144

Added line #L144 was not covered by tests

# sample frames from target video data directory
frame_sampler.sample(self.video_dataset, samples_dir)

Check warning on line 147 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L147

Added line #L147 was not covered by tests

@staticmethod

Check warning on line 149 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L149

Added line #L149 was not covered by tests
def _parent_dir_frame_samples_dict(
frame_issues: pd.DataFrame,
) -> Dict[str, List[str]]:
"""Creates dictionary of parent directory and frame samples."""
# set dict
cluster_frame_samples: Dict[str, List[str]] = {}

Check warning on line 155 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L155

Added line #L155 was not covered by tests

# looper over index
for img_path in frame_issues.index:
# get frame sample parent
sample_dir = Path(img_path).parents[0]

Check warning on line 160 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L160

Added line #L160 was not covered by tests

# get key
key = str(sample_dir)

Check warning on line 163 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L163

Added line #L163 was not covered by tests

# check if key exists
if key in cluster_frame_samples:
# update
cluster_frame_samples[key].append(img_path)

Check warning on line 168 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L168

Added line #L168 was not covered by tests

else:
# create new entry
cluster_frame_samples[key] = [img_path]

Check warning on line 172 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L172

Added line #L172 was not covered by tests

# get cluster dict
return cluster_frame_samples

Check warning on line 175 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L175

Added line #L175 was not covered by tests

def _aggregate_issues(self, frame_issues: pd.DataFrame) -> pd.DataFrame:

Check warning on line 177 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L177

Added line #L177 was not covered by tests
"""Aggregate Imagelab issues into a single frame for each video."""
# convert booleans to floats
pure_float_issues = frame_issues * 1

Check warning on line 180 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L180

Added line #L180 was not covered by tests

# store new aggregate_issues
aggregate_issues = []

Check warning on line 183 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L183

Added line #L183 was not covered by tests

# loop over clusters
for _, indexes in self._parent_dir_frame_samples_dict(frame_issues).items():
# get all frame issues for sample_dir subset
frame_issues = pure_float_issues.loc[indexes]

Check warning on line 188 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L188

Added line #L188 was not covered by tests

# calculate new index
new_index = indexes[int(len(indexes) / 2)]

Check warning on line 191 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L191

Added line #L191 was not covered by tests

# create aggregated scores df
aggregate_issues.append(

Check warning on line 194 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L194

Added line #L194 was not covered by tests
pd.DataFrame(frame_issues.mean().to_dict(), index=[new_index])
)

# finally create a new DataFrame of all aggregate results
agg_df = pd.concat(aggregate_issues)

Check warning on line 199 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L199

Added line #L199 was not covered by tests

# create lists of columns
issue_columns = [
get_is_issue_colname(issue) for issue in self.imagelab._issue_types
]

# convert float represent average booleans back to booleans
agg_df[issue_columns] = agg_df[issue_columns].astype(bool)

Check warning on line 207 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L207

Added line #L207 was not covered by tests

# return the aggregated dataframe
return agg_df

Check warning on line 210 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L210

Added line #L210 was not covered by tests

def _aggregate_summary(self, aggregate_issues: pd.DataFrame) -> pd.DataFrame:

Check warning on line 212 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L212

Added line #L212 was not covered by tests
"""Create issues summary for aggregate issues."""
# setup issue summary storage
summary_dict = {}

Check warning on line 215 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L215

Added line #L215 was not covered by tests

# loop over issue type
for issue_type in self.imagelab._issue_types:
# add individual type summaries
summary_dict[issue_type] = {

Check warning on line 220 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L220

Added line #L220 was not covered by tests
"num_images": aggregate_issues[get_is_issue_colname(issue_type)].sum()
}

# reshape summary dataframe
agg_summary = pd.DataFrame.from_dict(summary_dict, orient="index")
agg_summary = agg_summary.reset_index()
agg_summary = agg_summary.rename(columns={"index": "issue_type"})
agg_summary = agg_summary.astype({"num_images": int, "issue_type": str})

Check warning on line 228 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L225-L228

Added lines #L225 - L228 were not covered by tests

# return aggregate summary
return agg_summary

Check warning on line 231 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L231

Added line #L231 was not covered by tests

@staticmethod

Check warning on line 233 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L233

Added line #L233 was not covered by tests
def list_default_issue_types() -> List[str]:
"""Returns list of the default issue types."""
return [
issue
for issue in Imagelab.list_default_issue_types()
if "duplicates" not in issue
]

@staticmethod

Check warning on line 242 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L242

Added line #L242 was not covered by tests
def list_possible_issue_types() -> List[str]:
"""Returns list of all possible issue types including custom issues."""
return Imagelab.list_possible_issue_types()

Check warning on line 245 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L245

Added line #L245 was not covered by tests

def find_issues(

Check warning on line 247 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L247

Added line #L247 was not covered by tests
self,
frame_samples_dir: str,
frame_samples_interval: int,
issue_types: Optional[Dict[str, Any]] = None,
n_jobs: Optional[int] = None,
verbose: bool = True,
) -> None:
"""Sample frames before calling find_issues and aggregating."""
# create sample frames
self._sample_frames(Path(frame_samples_dir), frame_samples_interval)

Check warning on line 257 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L257

Added line #L257 was not covered by tests

# get imagelab instance
self.imagelab = Imagelab(frame_samples_dir)

Check warning on line 260 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L260

Added line #L260 was not covered by tests

# set default issue types
setattr(

Check warning on line 263 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L263

Added line #L263 was not covered by tests
self.imagelab, "list_default_issue_types", self.list_default_issue_types
)

# use imagelab to find issues in frames
self.imagelab.find_issues(issue_types, n_jobs, verbose)

Check warning on line 268 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L268

Added line #L268 was not covered by tests

# get frame issues
self.frame_issues = self.imagelab.issues
self.frame_issue_summary = self.imagelab.issue_summary

Check warning on line 272 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L271-L272

Added lines #L271 - L272 were not covered by tests

# update aggregate issues/summary
self.imagelab.issues = self._aggregate_issues(self.frame_issues)
self.imagelab.issue_summary = self._aggregate_summary(self.imagelab.issues)

Check warning on line 276 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L275-L276

Added lines #L275 - L276 were not covered by tests

def report(

Check warning on line 278 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L278

Added line #L278 was not covered by tests
self,
issue_types: Optional[List[str]] = None,
max_prevalence: Optional[float] = None,
num_images: Optional[int] = None,
verbosity: int = 1,
print_summary: bool = True,
show_id: bool = False,
) -> None:
"""Prints summary of the aggregate issues found in your dataset."""
# report on video frame samples
self.imagelab.report(

Check warning on line 289 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L289

Added line #L289 was not covered by tests
issue_types,
max_prevalence,
num_images,
verbosity,
print_summary,
show_id,
)

def get_stats(self) -> Any:

Check warning on line 298 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L298

Added line #L298 was not covered by tests
"""Returns dict of statistics computed from video frames."""
return self.imagelab.info["statistics"]

Check warning on line 300 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L300

Added line #L300 was not covered by tests

def save(self, path: str, force: bool = False) -> None:

Check warning on line 302 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L302

Added line #L302 was not covered by tests
"""Saves this Videolab instance."""
# get pathlib Path object
root_save_path = Path(path)

Check warning on line 305 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L305

Added line #L305 was not covered by tests

# check if videolab root save path exists
if not root_save_path.exists():
# create otherwise
root_save_path.mkdir(parents=True, exist_ok=True)

Check warning on line 310 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L310

Added line #L310 was not covered by tests
else:
if not force:

Check warning on line 312 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L312

Added line #L312 was not covered by tests
raise FileExistsError("Please specify a new path or set force=True")
print(

Check warning on line 314 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L314

Added line #L314 was not covered by tests
"WARNING: Existing files will be overwritten "
f"by newly saved files at: {root_save_path}"
)

# create specific imagelab sub directory
imagelab_sub_dir = str(root_save_path / "imagelab")

Check warning on line 320 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L320

Added line #L320 was not covered by tests

# now save imagelab to subdir
self.imagelab.save(imagelab_sub_dir, force)

Check warning on line 323 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L323

Added line #L323 was not covered by tests

# save aggregate dataframes
self.frame_issues.to_csv(root_save_path / ISSUES_FILENAME)
self.frame_issue_summary.to_csv(root_save_path / ISSUE_SUMMARY_FILENAME)

Check warning on line 327 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L326-L327

Added lines #L326 - L327 were not covered by tests

# copy videolab object
videolab_copy = deepcopy(self)

Check warning on line 330 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L330

Added line #L330 was not covered by tests

# clear out dataframes
videolab_copy.frame_issues = None
videolab_copy.frame_issue_summary = None

Check warning on line 334 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L333-L334

Added lines #L333 - L334 were not covered by tests

# Save the imagelab object to disk.
with open(root_save_path / OBJECT_FILENAME, "wb") as f:
pickle.dump(videolab_copy, f)

Check warning on line 338 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L338

Added line #L338 was not covered by tests

print(f"Saved Videolab to folder: {root_save_path}")
print(

Check warning on line 341 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L340-L341

Added lines #L340 - L341 were not covered by tests
"The data path and dataset must be not be changed to maintain consistent "
"state when loading this Videolab"
)

@classmethod

Check warning on line 346 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L346

Added line #L346 was not covered by tests
def load(cls: Type[TVideolab], path: str) -> Videolab:
"""Loads Videolab from given path."""
# get pathlib Path object
root_save_path = Path(path)

Check warning on line 350 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L350

Added line #L350 was not covered by tests

# check if path exists
if not root_save_path.exists():

Check warning on line 353 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L353

Added line #L353 was not covered by tests
raise ValueError(f"No folder found at specified path: {path}")

with open(root_save_path / OBJECT_FILENAME, "rb") as f:
videolab: Videolab = pickle.load(f)

Check warning on line 357 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L357

Added line #L357 was not covered by tests

# Load the issues from disk.
videolab.frame_issues = pd.read_csv(

Check warning on line 360 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L360

Added line #L360 was not covered by tests
root_save_path / ISSUES_FILENAME, index_col=0
)
videolab.frame_issue_summary = pd.read_csv(

Check warning on line 363 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L363

Added line #L363 was not covered by tests
root_save_path / ISSUE_SUMMARY_FILENAME, index_col=0
)

# create specific imagelab sub directory
imagelab_sub_dir = str(root_save_path / "imagelab")

Check warning on line 368 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L368

Added line #L368 was not covered by tests

# store imagelab object
videolab.imagelab = Imagelab.load(imagelab_sub_dir)

Check warning on line 371 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L371

Added line #L371 was not covered by tests

# notify user
print("Successfully loaded Videolab")

return videolab

Check warning on line 376 in src/cleanvision/videolab.py

View check run for this annotation

Codecov / codecov/patch

src/cleanvision/videolab.py#L376

Added line #L376 was not covered by tests