Skip to content

Module wtracker.dataset.sample_extractor

View Source
import numpy as np

from typing import Collection

from wtracker.dataset.box_calculator import BoxCalculator

from wtracker.utils.bbox_utils import BoxUtils

from wtracker.utils.io_utils import FrameSaver

class SampleExtractor:

    """

    A class that extracts samples from frames based on specified parameters.

    Each sample is a cropped image around a bounding box which was detected in the frame.

    The bounding boxes are calculated using the BoxCalculator class.

    This class is used to create image datasets for training object detection models.

    Args:

        bbox_calculator (BoxCalculator): An instance of the BoxCalculator class.

    """

    def __init__(self, bbox_calculator: BoxCalculator):

        self._bbox_calculator = bbox_calculator

        self._frame_reader = bbox_calculator._frame_reader

    def move_bboxes_into_bounds(self, bboxes: np.ndarray, frame_size: tuple[int, int]) -> np.ndarray:

        """

        Moves the bounding boxes into the bounds of the frame.

        Args:

            bboxes (np.ndarray): The bounding boxes to be moved.

            frame_size (tuple[int, int]): The size of the frame in the format (w, h).

        Returns:

            np.ndarray: The updated bounding boxes.

        Raises:

            ValueError: If exists a bounding box which cannot be moved into the provided bounds without resizing it.

        """

        max_w, max_h = frame_size

        x, y, w, h = BoxUtils.unpack(bboxes)

        x[x < 0] = 0

        mask = (x + w) > max_w

        x[mask] = max_w - w[mask]

        y[y < 0] = 0

        mask = (y + h) > max_h

        y[mask] = max_h - h[mask]

        if np.any(x < 0) or np.any(y < 0):

            raise ValueError()

        if np.any(x + w > frame_size[0]) or np.any(y + h > frame_size[1]):

            raise ValueError()

        return BoxUtils.pack(x, y, w, h)

    def create_specified_samples(

        self,

        frame_indices: Collection[int],

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates specified samples based on the given frame indices.

        Args:

            frame_indices (Collection[int]): The indices of the frames to extract samples from.

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk.

        """

        bboxes = self._bbox_calculator.calc_specified_boxes(

            frame_indices=frame_indices,

            num_workers=num_workers,

            chunk_size=chunk_size,

        )

        x, y, w, h = BoxUtils.unpack(bboxes)

        x -= np.random.randint(0, target_size[0] - w + 1)

        y -= np.random.randint(0, target_size[1] - h + 1)

        w = np.full_like(x, target_size[0])

        h = np.full_like(x, target_size[1])

        bboxes = BoxUtils.pack(x, y, w, h)

        frame_size = tuple(reversed(self._frame_reader.frame_size))  # (h, w) -> (w, h)

        bboxes = self.move_bboxes_into_bounds(bboxes, frame_size)

        with FrameSaver(self._frame_reader, root_path=save_folder, desc="Saving samples", unit="fr") as saver:

            for i, bbox in enumerate(bboxes):

                saver.schedule_save(i, bbox, name_format.format(i))

    def create_samples(

        self,

        count: int,

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates random samples based on a specified count.

        Args:

            count (int): The number of samples to create.

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk sent to each worker.

        """

        length = len(self._frame_reader)

        count = min(length, count)

        frame_indices = np.random.choice(length, size=count, replace=False)

        self.create_specified_samples(frame_indices, target_size, save_folder, name_format, num_workers, chunk_size)

    def create_all_samples(

        self,

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates samples for all frames.

        Args:

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk.

        """

        frame_indices = range(0, len(self._frame_reader))

        self.create_specified_samples(frame_indices, target_size, save_folder, name_format, num_workers, chunk_size)

Classes

SampleExtractor

class SampleExtractor(
    bbox_calculator: wtracker.dataset.box_calculator.BoxCalculator
)

A class that extracts samples from frames based on specified parameters.

Each sample is a cropped image around a bounding box which was detected in the frame. The bounding boxes are calculated using the BoxCalculator class. This class is used to create image datasets for training object detection models.

Attributes

Name Type Description Default
bbox_calculator BoxCalculator An instance of the BoxCalculator class. None
View Source
class SampleExtractor:

    """

    A class that extracts samples from frames based on specified parameters.

    Each sample is a cropped image around a bounding box which was detected in the frame.

    The bounding boxes are calculated using the BoxCalculator class.

    This class is used to create image datasets for training object detection models.

    Args:

        bbox_calculator (BoxCalculator): An instance of the BoxCalculator class.

    """

    def __init__(self, bbox_calculator: BoxCalculator):

        self._bbox_calculator = bbox_calculator

        self._frame_reader = bbox_calculator._frame_reader

    def move_bboxes_into_bounds(self, bboxes: np.ndarray, frame_size: tuple[int, int]) -> np.ndarray:

        """

        Moves the bounding boxes into the bounds of the frame.

        Args:

            bboxes (np.ndarray): The bounding boxes to be moved.

            frame_size (tuple[int, int]): The size of the frame in the format (w, h).

        Returns:

            np.ndarray: The updated bounding boxes.

        Raises:

            ValueError: If exists a bounding box which cannot be moved into the provided bounds without resizing it.

        """

        max_w, max_h = frame_size

        x, y, w, h = BoxUtils.unpack(bboxes)

        x[x < 0] = 0

        mask = (x + w) > max_w

        x[mask] = max_w - w[mask]

        y[y < 0] = 0

        mask = (y + h) > max_h

        y[mask] = max_h - h[mask]

        if np.any(x < 0) or np.any(y < 0):

            raise ValueError()

        if np.any(x + w > frame_size[0]) or np.any(y + h > frame_size[1]):

            raise ValueError()

        return BoxUtils.pack(x, y, w, h)

    def create_specified_samples(

        self,

        frame_indices: Collection[int],

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates specified samples based on the given frame indices.

        Args:

            frame_indices (Collection[int]): The indices of the frames to extract samples from.

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk.

        """

        bboxes = self._bbox_calculator.calc_specified_boxes(

            frame_indices=frame_indices,

            num_workers=num_workers,

            chunk_size=chunk_size,

        )

        x, y, w, h = BoxUtils.unpack(bboxes)

        x -= np.random.randint(0, target_size[0] - w + 1)

        y -= np.random.randint(0, target_size[1] - h + 1)

        w = np.full_like(x, target_size[0])

        h = np.full_like(x, target_size[1])

        bboxes = BoxUtils.pack(x, y, w, h)

        frame_size = tuple(reversed(self._frame_reader.frame_size))  # (h, w) -> (w, h)

        bboxes = self.move_bboxes_into_bounds(bboxes, frame_size)

        with FrameSaver(self._frame_reader, root_path=save_folder, desc="Saving samples", unit="fr") as saver:

            for i, bbox in enumerate(bboxes):

                saver.schedule_save(i, bbox, name_format.format(i))

    def create_samples(

        self,

        count: int,

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates random samples based on a specified count.

        Args:

            count (int): The number of samples to create.

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk sent to each worker.

        """

        length = len(self._frame_reader)

        count = min(length, count)

        frame_indices = np.random.choice(length, size=count, replace=False)

        self.create_specified_samples(frame_indices, target_size, save_folder, name_format, num_workers, chunk_size)

    def create_all_samples(

        self,

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates samples for all frames.

        Args:

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk.

        """

        frame_indices = range(0, len(self._frame_reader))

        self.create_specified_samples(frame_indices, target_size, save_folder, name_format, num_workers, chunk_size)

Methods

create_all_samples

def create_all_samples(
    self,
    target_size: tuple[int, int],
    save_folder: str,
    name_format: str = 'img_{:09d}.png',
    num_workers: int = None,
    chunk_size: int = 50
)

Creates samples for all frames.

Parameters:

Name Type Description Default
target_size tuple[int, int] The target size of the samples in the format (w, h). None
save_folder str The folder path to save the samples. None
name_format str The format of the sample names. None
num_workers int The number of workers to use for parallel processing.
If None, the number of workers is determined automatically.
None
chunk_size int The size of each processing chunk. None
View Source
    def create_all_samples(

        self,

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates samples for all frames.

        Args:

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk.

        """

        frame_indices = range(0, len(self._frame_reader))

        self.create_specified_samples(frame_indices, target_size, save_folder, name_format, num_workers, chunk_size)

create_samples

def create_samples(
    self,
    count: int,
    target_size: tuple[int, int],
    save_folder: str,
    name_format: str = 'img_{:09d}.png',
    num_workers: int = None,
    chunk_size: int = 50
)

Creates random samples based on a specified count.

Parameters:

Name Type Description Default
count int The number of samples to create. None
target_size tuple[int, int] The target size of the samples in the format (w, h). None
save_folder str The folder path to save the samples. None
name_format str The format of the sample names. None
num_workers int The number of workers to use for parallel processing.
If None, the number of workers is determined automatically.
None
chunk_size int The size of each processing chunk sent to each worker. None
View Source
    def create_samples(

        self,

        count: int,

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates random samples based on a specified count.

        Args:

            count (int): The number of samples to create.

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk sent to each worker.

        """

        length = len(self._frame_reader)

        count = min(length, count)

        frame_indices = np.random.choice(length, size=count, replace=False)

        self.create_specified_samples(frame_indices, target_size, save_folder, name_format, num_workers, chunk_size)

create_specified_samples

def create_specified_samples(
    self,
    frame_indices: Collection[int],
    target_size: tuple[int, int],
    save_folder: str,
    name_format: str = 'img_{:09d}.png',
    num_workers: int = None,
    chunk_size: int = 50
)

Creates specified samples based on the given frame indices.

Parameters:

Name Type Description Default
frame_indices Collection[int] The indices of the frames to extract samples from. None
target_size tuple[int, int] The target size of the samples in the format (w, h). None
save_folder str The folder path to save the samples. None
name_format str The format of the sample names. None
num_workers int The number of workers to use for parallel processing.
If None, the number of workers is determined automatically.
None
chunk_size int The size of each processing chunk. None
View Source
    def create_specified_samples(

        self,

        frame_indices: Collection[int],

        target_size: tuple[int, int],

        save_folder: str,

        name_format: str = "img_{:09d}.png",

        num_workers: int = None,

        chunk_size: int = 50,

    ):

        """

        Creates specified samples based on the given frame indices.

        Args:

            frame_indices (Collection[int]): The indices of the frames to extract samples from.

            target_size (tuple[int, int]): The target size of the samples in the format (w, h).

            save_folder (str): The folder path to save the samples.

            name_format (str, optional): The format of the sample names.

            num_workers (int, optional): The number of workers to use for parallel processing.

                If None, the number of workers is determined automatically.

            chunk_size (int, optional): The size of each processing chunk.

        """

        bboxes = self._bbox_calculator.calc_specified_boxes(

            frame_indices=frame_indices,

            num_workers=num_workers,

            chunk_size=chunk_size,

        )

        x, y, w, h = BoxUtils.unpack(bboxes)

        x -= np.random.randint(0, target_size[0] - w + 1)

        y -= np.random.randint(0, target_size[1] - h + 1)

        w = np.full_like(x, target_size[0])

        h = np.full_like(x, target_size[1])

        bboxes = BoxUtils.pack(x, y, w, h)

        frame_size = tuple(reversed(self._frame_reader.frame_size))  # (h, w) -> (w, h)

        bboxes = self.move_bboxes_into_bounds(bboxes, frame_size)

        with FrameSaver(self._frame_reader, root_path=save_folder, desc="Saving samples", unit="fr") as saver:

            for i, bbox in enumerate(bboxes):

                saver.schedule_save(i, bbox, name_format.format(i))

move_bboxes_into_bounds

def move_bboxes_into_bounds(
    self,
    bboxes: numpy.ndarray,
    frame_size: tuple[int, int]
) -> numpy.ndarray

Moves the bounding boxes into the bounds of the frame.

Parameters:

Name Type Description Default
bboxes np.ndarray The bounding boxes to be moved. None
frame_size tuple[int, int] The size of the frame in the format (w, h). None

Returns:

Type Description
np.ndarray The updated bounding boxes.

Raises:

Type Description
ValueError If exists a bounding box which cannot be moved into the provided bounds without resizing it.
View Source
    def move_bboxes_into_bounds(self, bboxes: np.ndarray, frame_size: tuple[int, int]) -> np.ndarray:

        """

        Moves the bounding boxes into the bounds of the frame.

        Args:

            bboxes (np.ndarray): The bounding boxes to be moved.

            frame_size (tuple[int, int]): The size of the frame in the format (w, h).

        Returns:

            np.ndarray: The updated bounding boxes.

        Raises:

            ValueError: If exists a bounding box which cannot be moved into the provided bounds without resizing it.

        """

        max_w, max_h = frame_size

        x, y, w, h = BoxUtils.unpack(bboxes)

        x[x < 0] = 0

        mask = (x + w) > max_w

        x[mask] = max_w - w[mask]

        y[y < 0] = 0

        mask = (y + h) > max_h

        y[mask] = max_h - h[mask]

        if np.any(x < 0) or np.any(y < 0):

            raise ValueError()

        if np.any(x + w > frame_size[0]) or np.any(y + h > frame_size[1]):

            raise ValueError()

        return BoxUtils.pack(x, y, w, h)