Module deepsport_utilities.ds.instants_dataset

Expand source code
from .instants_dataset import InstantsDataset, Instant, InstantKey, DownloadFlags, Player, Ball, BallState
from .instants_transforms import GammaCorrectionTransform
from .views_dataset import ViewsDataset, ViewKey, View, BuildBallViews, BuildCameraViews, \
    BuildHeadsViews, BuildCourtViews, BuildPlayersViews, BuildThumbnailViews
from .views_transforms import AddBallAnnotation, UndistortTransform, \
    ComputeDiff, GameGammaColorTransform, GameRGBColorTransform, \
    BayeringTransform, ViewRandomCropperTransform, AddCalibFactory, AddCourtFactory, AddDiffFactory, \
    AddNextImageFactory, BallCropperTransform
from .dataset_splitters import DeepSportDatasetSplitter, KFoldsArenaLabelsTestingDatasetSplitter, \
    TestingArenaLabelsDatasetSplitter

try:
    from .views_transforms import AddBallDistance
except ImportError:
    pass

# all but "InstantsDataset"
__all__ = ["Instant", "InstantKey", "DownloadFlags", "Player", "BallState",
"Ball", "GammaCorrectionTransform", "ViewsDataset", "ViewKey", "View",
"BuildBallViews", "BuildCameraViews", "AddBallAnnotation", "UndistortTransform",
"DeepSportDatasetSplitter", "KFoldsArenaLabelsTestingDatasetSplitter",
"TestingArenaLabelsDatasetSplitter", "BuildHeadsViews", "BuildCourtViews",
"BuildPlayersViews", "BuildThumbnailViews", "ComputeDiff",
"GameGammaColorTransform", "GameRGBColorTransform", "BayeringTransform",
"ViewRandomCropperTransform", "AddCalibFactory", "AddCourtFactory",
"AddDiffFactory", "AddNextImageFactory", "BallCropperTransform"]

Sub-modules

deepsport_utilities.ds.instants_dataset.dataset_splitters
deepsport_utilities.ds.instants_dataset.instants_dataset
deepsport_utilities.ds.instants_dataset.instants_transforms
deepsport_utilities.ds.instants_dataset.views_dataset
deepsport_utilities.ds.instants_dataset.views_transforms

Classes

class AddBallAnnotation
Expand source code
class AddBallAnnotation(Transform):
    def __call__(self, key, view):
        balls = [a for a in view.annotations if a.type == 'ball']
        assert len(balls) == 1, f"Expected one ball. received {len(balls)}."
        view.ball = balls[0]
        return view

Ancestors

class AddCalibFactory (as_dict=False)
Expand source code
class AddCalibFactory(Transform):
    def __init__(self, as_dict=False):
        self.as_dict = as_dict
    @staticmethod
    def to_basic_dict(calib):
        return {
            "K": calib.K,
            "r": cv2.Rodrigues(calib.R)[0].flatten(),
            "T": calib.T,
            "width": np.array([calib.width]),
            "height": np.array([calib.height]),
            "kc": np.array(calib.kc),
        }
    def __call__(self, view_key, view):
        if self.as_dict:
            return self.to_basic_dict(view.calib)
        return {"calib": view.calib}

Ancestors

Static methods

def to_basic_dict(calib)
Expand source code
@staticmethod
def to_basic_dict(calib):
    return {
        "K": calib.K,
        "r": cv2.Rodrigues(calib.R)[0].flatten(),
        "T": calib.T,
        "width": np.array([calib.width]),
        "height": np.array([calib.height]),
        "kc": np.array(calib.kc),
    }
class AddCourtFactory
Expand source code
class AddCourtFactory(Transform):
    def __call__(self, view_key, view):
        if not getattr(view, "court", None):
            view.court = Court()
        return {
            "court_width": np.array([view.court.w]),
            "court_height": np.array([view.court.h])
        }

Ancestors

class AddDiffFactory
Expand source code
class AddDiffFactory(Transform):
    def __call__(self, view_key, view):
        raise NotImplementedError() # code needs to be re-implemented: current implementation only adds next image
        return {"input_image2": view.all_images[1]}

Ancestors

class AddNextImageFactory
Expand source code
class AddNextImageFactory(Transform):
    def __call__(self, view_key, view):
        return {"input_image2": view.all_images[1]}

Ancestors

class Ball (data)
Expand source code
class Ball():
    state = BallState.NONE # default
    visible = None         # default
    value = None           # default
    def __init__(self, data):
        self.type = "ball"
        self.center = Point3D(*data['center'])
        self.origin = data.get('origin', "annotation")
        self.camera = data['image']
        self.visible = data.get('visible', None)
        self.state = data.get('state', BallState.NONE)
        self.value = data.get('value', None)

    def to_dict(self):
        return {
            "type": self.type,
            "origin": self.origin,
            "center": self.center.to_list(),
            "image": self.camera,
            "visible": self.visible,
            "state": self.state,
            "value": self.value
        }

    def __repr__(self):
        return "Ball(" + ",".join([
            f"origin='{self.origin}', " \
            f"center=({self.center.x:.01f}, {self.center.y:.01f}, {self.center.z:.01f})" \
        ]) + ")"

Subclasses

Class variables

var state
var value
var visible

Methods

def to_dict(self)
Expand source code
def to_dict(self):
    return {
        "type": self.type,
        "origin": self.origin,
        "center": self.center.to_list(),
        "image": self.camera,
        "visible": self.visible,
        "state": self.state,
        "value": self.value
    }
class BallCropperTransform (*args, def_min=None, def_max=None, size_min=None, size_max=None, scale_min=None, scale_max=None, **kwargs)

Create a random scaled thumbnail around view's ball. - If [min_size, max_size] range is given, scale factor is chosen s.t. ball size is in the [min_size, max_size] range. - If [def_min, def_max] range is given, scale factor is chosen s.t. image definition [px/m] is in the [def_min, def_max] range at ball location. - If [scale_min, scale_max] range is given, scale factor is chosen in that range. An error is raised if multiple options are given.

Randomly scale, crop and rotate dataset items. The scale factor is randomly selected to keep the given keypoints of interest between size_min and size_max (At each call, the current keypoint size is returned by _get_current_parameters).

Arguments

output_shape: Tuple(int, int) final shape of image-like data. size_min: (int) lower bound of keypoints random size. If 0 size_min and size_max are ignored and no random scaling is applied. size_max: (int) upper bound of keypoints random size. If 0 size_min and size_max are ignored and no random scaling is applied. max_angle: (degrees) positive and negative bounds for random rotation do_flip: (bool) tells if random flip should be applied padding: (px) amount of padding margin: (px) minimum margin between keypoints and output image border debug: (bool) if True, doesn't actually crop but display debug information on image instead. regenerate: (bool) if True, items are (deep)-copied before calling _apply_transformation. Else, transformation can occur in-place.

Expand source code
class BallCropperTransform(ViewRandomCropperTransform):
    """ Create a random scaled thumbnail around view's ball.
        - If [min_size, max_size] range is given, scale factor is chosen s.t.
          ball size is in the [min_size, max_size] range.
        - If [def_min, def_max] range is given, scale factor is chosen s.t.
          image definition [px/m] is in the [def_min, def_max] range at ball
          location.
        - If [scale_min, scale_max] range is given, scale factor is chosen in
          that range.
        An error is raised if multiple options are given.
    """
    def __init__(self, *args,
                 def_min=None, def_max=None,
                 size_min=None, size_max=None,
                 scale_min=None, scale_max=None,
                 **kwargs):
        msg = "Only one of ('size_min' and 'size_max') or ('def_min' and 'def_max') or ('scale_min' and 'scale_max') should be defined"
        if size_min is not None and size_max is not None:
            assert all([x is None for x in [def_min, def_max, scale_min, scale_max]]), msg
            super().__init__(*args, size_min=size_min, size_max=size_max, **kwargs)
            self.true_size = BALL_DIAMETER
        elif def_min is not None and def_max is not None:
            assert all([x is None for x in [size_min, size_max, scale_min, scale_max]]), msg
            super().__init__(*args, size_min=def_min, size_max=def_max, **kwargs)
            self.true_size = 100
        elif scale_min is not None and scale_max is not None:
            assert all([x is None for x in [size_min, size_max, def_min, def_max]]), msg
            super().__init__(*args, size_min=scale_min, size_max=scale_max, **kwargs)
            self.true_size = None
        else:
            raise ValueError(msg)

    def _get_current_parameters(self, view_key, view):
        keypoints = view.calib.project_3D_to_2D(view.ball.center)
        if self.true_size is None:
            size = 1
        else:
            size = float(view.calib.compute_length2D(view.ball.center, self.true_size))
        input_shape = view.calib.width, view.calib.height
        return keypoints, size, input_shape

Ancestors

Inherited members

class BallState (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class BallState(IntEnum):
    NONE = 0
    FLYING = 1
    CONSTRAINT = 2
    DRIBBLING = 3

Ancestors

  • enum.IntEnum
  • builtins.int
  • enum.Enum

Class variables

var CONSTRAINT
var DRIBBLING
var FLYING
var NONE
class BayeringTransform
Expand source code
class BayeringTransform(Transform):
    def __init__(self):
        self.R_filter = np.array([[1,0],[0,0]])
        self.G_filter = np.array([[0,1],[1,0]])
        self.B_filter = np.array([[0,0],[0,1]])
    def __call__(self, view_key, view):
        height, width, _ = view.image.shape
        R_mask = np.tile(self.R_filter, [height//2, width//2])
        G_mask = np.tile(self.G_filter, [height//2, width//2])
        B_mask = np.tile(self.B_filter, [height//2, width//2])
        mask = np.stack((R_mask, G_mask, B_mask), axis=2)
        mask = mask[np.newaxis]
        for i, image in enumerate(view.all_images):
            view.all_images[i] = np.sum(image*mask, axis=3)
        view.image = view.all_images[0]

Ancestors

class BuildBallViews (*args, origins=['annotation'], **kwargs)

Margin given in world coordinates by default, except if margin_in_pixels is True.

Expand source code
class BuildBallViews(ViewBuilder):
    def __init__(self, *args, origins=["annotation"], **kwargs):
        super().__init__(*args, **kwargs)
        self.origins = origins
    def __call__(self, instant_key: InstantKey, instant: Instant):
        balls = instant.annotations + getattr(instant, "detections", [])
        predicate = lambda a: a.type == 'ball' and a.origin in self.origins
        for idx, ball in enumerate(filter(predicate, balls)):
            keypoints = [ball.center]
            c = int(ball.camera)
            timestamp = getattr(instant, "timestamps", [instant.timestamp]*instant.num_cameras)[c] # timestamps may be different for each camera
            yield ViewDescription(c, idx, self.compute_box(keypoints, instant.calibs[c]), ball=ball, timestamp=timestamp)

Ancestors

Class variables

var margin : float
var margin_in_pixels : bool
var padding : int
class BuildCameraViews (margin: float = 0, padding: int = None, margin_in_pixels: bool = False)

Builds a view for each camera (margin parameter is useless)

Expand source code
class BuildCameraViews(ViewBuilder):
    """ Builds a view for each camera (margin parameter is useless)
    """
    def __call__(self, instant_key: InstantKey, instant:Instant):
        for c in range(instant.num_cameras):
            yield ViewDescription(c, 0, BoundingBox(0, 0, instant.calibs[c].width, instant.calibs[c].height), court_dim=instant.court_dim)

Ancestors

Class variables

var margin : float
var margin_in_pixels : bool
var padding : int
class BuildCourtViews (margin: float = 0, padding: int = None, margin_in_pixels: bool = False, height: float = 300)

Builds a view including all the court keypoints visible on each camera Note: keypoints are duplicated at 2m from the floor

Expand source code
class BuildCourtViews(ViewBuilder):
    """ Builds a view including all the court keypoints visible on each camera
        Note: keypoints are duplicated at 2m from the floor
    """
    height: float = 300
    def __call__(self, instant_key: InstantKey, instant:Instant):
        for c in range(instant.num_cameras):
            calib = instant.calibs[c]
            visible_edges = Court(instant.rule_type).visible_edges(calib)
            court_keypoints = []
            for p1, p2 in visible_edges:
                court_keypoints = court_keypoints + [p1, p1+Point3D(0,0,-self.height), p2, p2+Point3D(0,0,-self.height)]
            yield ViewDescription(c, 0, self.compute_box(court_keypoints, calib))

Ancestors

Class variables

var height : float
class BuildHeadsViews (margin: float = 0, padding: int = None, margin_in_pixels: bool = False)

Margin given in world coordinates by default, except if margin_in_pixels is True.

Expand source code
class BuildHeadsViews(ViewBuilder):
    def __call__(self, instant_key: InstantKey, instant: Instant):
        for idx, player in enumerate([a for a in instant.annotations if a.type == "player"]):
            c = int(player.camera)
            keypoints = [player.head]
            yield ViewDescription(c, idx, self.compute_box(keypoints, instant.calibs[c]), annotation=player)

Ancestors

Class variables

var margin : float
var margin_in_pixels : bool
var padding : int
class BuildPlayersViews (margin: float = 0, padding: int = None, margin_in_pixels: bool = False, min_annotations: int = 1)

Builds a view around the players visible on each camera min_annotations: minimum required number of person to use that camera

Expand source code
class BuildPlayersViews(ViewBuilder):
    """ Builds a view around the players visible on each camera
        min_annotations: minimum required number of person to use that camera
    """
    min_annotations: int = 1
    def __call__(self, instant_key: InstantKey, instant: Instant):
        for c in range(instant.num_cameras):
            annotations = [a for a in instant.annotations if a.camera == c and a.type == "player" and a.team > 0]
            if len(annotations) < self.min_annotations:
                continue

            keypoints = []
            for a in annotations:
                keypoints += [a.head, a.hips, a.foot1, a.foot2]

            yield ViewDescription(c, 0, self.compute_box(keypoints, instant.calibs[c]))

Ancestors

Class variables

var min_annotations : int
class BuildThumbnailViews (margin: float = 0, padding: int = None, margin_in_pixels: bool = False, with_annotations: bool = True, with_detections: bool = False, with_random: Tuple[int, bool] = 0, with_occlusions: bool = False)

Builds a view around each person (players, referee)

Expand source code
class BuildThumbnailViews(ViewBuilder):
    """ Builds a view around each person (players, referee)
    """
    with_annotations: bool = True
    with_detections: bool = False
    with_random: Tuple[int, bool] = 0
    with_occlusions: bool = False
    BODY_HEIGHT = 180
    threshold = 0.25
    def __post_init__(self):
        self.with_random = 10 if isinstance(self.with_random, bool) and self.with_random else self.with_random

    def check_density_map(self, density_map, box, threshold):
        if 0 in density_map[box.y_slice, box.x_slice].shape:
            return False
        if np.mean(density_map[box.y_slice, box.x_slice]) <= threshold:
            return True
        return False

    def sample_density_map(self, density_map):
        # avoid going too close to other detections
        dilation = cv2.dilate(density_map, np.ones((3,3)), iterations=10)
        indices_y, indices_x = np.where(dilation == 0)
        # random choic a position in the image
        i = np.random.randint(0, len(indices_x))
        return np.array([[indices_x[i], indices_y[i]]])

    @staticmethod
    def fill_density_map(density_map, box):
        density_map[box.y_slice, box.x_slice] += 1

    def __call__(self, instant_key: InstantKey, instant:Instant):
        # Set random seed with timestamp
        random_state = np.random.get_state()
        np.random.seed(instant_key.timestamp & 0xFFFFFFFF)

        instant.density_maps = [np.zeros(img.shape[0:2], dtype=np.uint8) for img in instant.images]

        for c in range(instant.num_cameras):
            calib = instant.calibs[c]
            density_map = instant.density_maps[c]
            index = 0

            # From annotation
            for a in [a for a in instant.annotations if a.type == "player" and calib.projects_in(a.hips) and self.with_annotations]:
                keypoints = [a.head, a.hips, a.foot1, a.foot2]
                box = self.compute_box(keypoints, calib)
                #if self.check_density_map(instant.density_maps, box, c, 1+self.threshold) or self.with_occlusions:
                yield ViewDescription(c, index, box, origin='annotation', annotation=a, density_map=density_map)
                self.fill_density_map(density_map, box)
                index = index + 1

            if self.with_detections:
                # From keemotion foregrounddetector detections
                # FIXME:
                for detection in []:#[d for d in instant.fg_detections if d.camera == c and calib.projects_in(d.feet) and self.with_detections]:
                    keypoints = [ detection.feet, detection.feet + Point3D(0, 0, -self.BODY_HEIGHT) ]
                    box = self.compute_box(keypoints, calib)
                    if self.check_density_map(density_map, box, self.threshold):
                        yield ViewDescription(c, index, box, origin='detection', detection=detection, density_map=density_map)
                        self.fill_density_map(density_map, box)
                        index = index + 1


            # From random
            if self.with_random:
                raise NotImplementedError
                # TODO: use Calib.visible_edge() to replace the function "find_court_intersection_with_camera_border"
                court_keypoints_3D = []# find_court_intersection_with_camera_border(calib, instant.rule_type)
                court_keypoints_2D = np.array([calib.project_3D_to_2D(p).to_list() for p in court_keypoints_3D])
                convex_hull = ConvexHull(court_keypoints_2D)
                points = np.array([court_keypoints_2D[i,:] for i in convex_hull.vertices]).astype(np.int32)
                court = np.ones(instant.images[c].shape[0:2], dtype=np.uint8)
                density_map[cv2.fillPoly(court, [points], 0)==1] += 1

            for _ in range(self.with_random):
                feet = calib.project_2D_to_3D(Point2D(self.sample_density_map(density_map)), Z=0)
                keypoints = [feet, feet + Point3D(0, 0, -self.BODY_HEIGHT)]
                box = self.compute_box(keypoints, calib)
                if self.check_density_map(density_map, box, self.threshold):
                    yield ViewDescription(c, index, box, origin='random', density_map=density_map)
                    self.fill_density_map(density_map, box)
                    index = index + 1

        # Restore random seed
        np.random.set_state(random_state)

Ancestors

Class variables

var BODY_HEIGHT
var threshold
var with_annotations : bool
var with_detections : bool
var with_occlusions : bool
var with_random : Tuple[int, bool]

Static methods

def fill_density_map(density_map, box)
Expand source code
@staticmethod
def fill_density_map(density_map, box):
    density_map[box.y_slice, box.x_slice] += 1

Methods

def check_density_map(self, density_map, box, threshold)
Expand source code
def check_density_map(self, density_map, box, threshold):
    if 0 in density_map[box.y_slice, box.x_slice].shape:
        return False
    if np.mean(density_map[box.y_slice, box.x_slice]) <= threshold:
        return True
    return False
def sample_density_map(self, density_map)
Expand source code
def sample_density_map(self, density_map):
    # avoid going too close to other detections
    dilation = cv2.dilate(density_map, np.ones((3,3)), iterations=10)
    indices_y, indices_x = np.where(dilation == 0)
    # random choic a position in the image
    i = np.random.randint(0, len(indices_x))
    return np.array([[indices_x[i], indices_y[i]]])
class ComputeDiff (squash=False, inplace=False)
Expand source code
class ComputeDiff(Transform):
    def __init__(self, squash=False, inplace=False):
        self.squash = squash
        self.inplace = inplace

    def __call__(self, view_key: ViewKey, view: View):
        diff = np.abs(view.image.astype(np.int32) - view.all_images[1].astype(np.int32)).astype(np.uint8)
        if self.squash:
            diff = np.mean(diff, axis=2).astype(np.uint8)
        if self.inplace:
            view.image = np.dstack((view.image, diff))
        else:
            view.diff = diff
        return view

Ancestors

class DeepSportDatasetSplitter (validation_pc: int = 15, additional_keys_usage: str = 'skip', folds: str = 'ABCDE')

DeepSportDatasetSplitter(validation_pc: int = 15, additional_keys_usage: str = 'skip', folds: str = 'ABCDE')

Expand source code
class DeepSportDatasetSplitter: # pylint: disable=too-few-public-methods
    validation_pc: int = 15
    additional_keys_usage: str = "skip"
    folds: str = "ABCDE"
    split = {
        "A": ['KS-FR-CAEN', 'KS-FR-LIMOGES', 'KS-FR-ROANNE'],
        "B": ['KS-FR-NANTES', 'KS-FR-BLOIS', 'KS-FR-FOS'],
        "C": ['KS-FR-LEMANS', 'KS-FR-MONACO', 'KS-FR-STRASBOURG'],
        "D": ['KS-FR-GRAVELINES', 'KS-FR-STCHAMOND', 'KS-FR-POITIERS'],
        "E": ['KS-FR-NANCY', 'KS-FR-BOURGEB', 'KS-FR-VICHY'],
    }
    def split_keys(self, keys, fold=0):
        assert 0 <= fold <= len(self.folds)-1, "Invalid fold index"

        testing_fold = self.folds[fold]
        testing_keys = [k for k in keys if k.arena_label in self.split[testing_fold]]

        remaining_arena_labels = [label for f in self.folds.replace(testing_fold, "") for label in self.split[f]]
        remaining_keys = [k for k in keys if k.arena_label in remaining_arena_labels]

        # Backup random seed
        random_state = random.getstate()
        random.seed(fold)

        validation_keys = random.sample(remaining_keys, len(remaining_keys)*self.validation_pc//100)
        training_keys = [k for k in remaining_keys if k not in validation_keys]

        additional_keys = [k for k in keys if k not in training_keys+validation_keys+testing_keys]

        if additional_keys:
            if self.additional_keys_usage == "testing":
                testing_keys += additional_keys
            elif self.additional_keys_usage == "training":
                training_keys += additional_keys
            elif self.additional_keys_usage == "validation":
                validation_keys += additional_keys
            elif self.additional_keys_usage in ["none", "skip"]:
                pass
            else:
                raise ValueError("They are additional arena labels that I don't know what to do with. Please tell me the 'additional_keys_usage' argument")

        # Restore random seed
        random.setstate(random_state)
        return training_keys, validation_keys, testing_keys

    def __call__(self, dataset, fold=0):
        keys = list(dataset.keys.all())
        training_keys, validation_keys, testing_keys = self.split_keys(keys, fold)
        return [
            Subset(name="training", subset_type=SubsetType.TRAIN, keys=training_keys, dataset=dataset),
            Subset(name="validation", subset_type=SubsetType.EVAL, keys=validation_keys, dataset=dataset, repetitions=1),
            Subset(name="testing", subset_type=SubsetType.EVAL, keys=testing_keys, dataset=dataset, repetitions=1),
        ]

Subclasses

Class variables

var additional_keys_usage : str
var folds : str
var split
var validation_pc : int

Methods

def split_keys(self, keys, fold=0)
Expand source code
def split_keys(self, keys, fold=0):
    assert 0 <= fold <= len(self.folds)-1, "Invalid fold index"

    testing_fold = self.folds[fold]
    testing_keys = [k for k in keys if k.arena_label in self.split[testing_fold]]

    remaining_arena_labels = [label for f in self.folds.replace(testing_fold, "") for label in self.split[f]]
    remaining_keys = [k for k in keys if k.arena_label in remaining_arena_labels]

    # Backup random seed
    random_state = random.getstate()
    random.seed(fold)

    validation_keys = random.sample(remaining_keys, len(remaining_keys)*self.validation_pc//100)
    training_keys = [k for k in remaining_keys if k not in validation_keys]

    additional_keys = [k for k in keys if k not in training_keys+validation_keys+testing_keys]

    if additional_keys:
        if self.additional_keys_usage == "testing":
            testing_keys += additional_keys
        elif self.additional_keys_usage == "training":
            training_keys += additional_keys
        elif self.additional_keys_usage == "validation":
            validation_keys += additional_keys
        elif self.additional_keys_usage in ["none", "skip"]:
            pass
        else:
            raise ValueError("They are additional arena labels that I don't know what to do with. Please tell me the 'additional_keys_usage' argument")

    # Restore random seed
    random.setstate(random_state)
    return training_keys, validation_keys, testing_keys
class DownloadFlags (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class DownloadFlags(IntFlag):
    NONE = 0
    WITH_IMAGE = 1
    WITH_CALIB_FILE = 2
    #WITH_FOREGROUND_MASK_FILE = 4 # obsolete
    WITH_HUMAN_SEGMENTATION_MASKS = 8
    WITH_FOLLOWING_IMAGE = 16
    WITH_ALL_IMAGES = 32
    ALL = -1

Ancestors

  • enum.IntFlag
  • builtins.int
  • enum.Flag
  • enum.Enum

Class variables

var ALL
var NONE
var WITH_ALL_IMAGES
var WITH_CALIB_FILE
var WITH_FOLLOWING_IMAGE
var WITH_HUMAN_SEGMENTATION_MASKS
var WITH_IMAGE
class GameGammaColorTransform (transform_dict)
Expand source code
class GameGammaColorTransform(Transform):
    def __init__(self, transform_dict):
        assert all([isinstance(k, int) for k in transform_dict.keys()])
        #29582 : [1.04, 1.02, 0.93],
        #24651 : [1.05, 1.02, 0.92],
        #30046 : [1.01, 1.01, 1.01]
        self.transform_dict = transform_dict

    def __call__(self, view_key, view):
        if view_key.instant_key.game_id in self.transform_dict.keys():
            gammas = np.array(self.transform_dict[view_key.instant_key.game_id])
            view.image = gamma_correction(view.image, gammas)
        return view

Ancestors

class GameRGBColorTransform (transform_dict)
Expand source code
class GameRGBColorTransform(Transform):
    def __init__(self, transform_dict):
        assert all([isinstance(k, int) for k in transform_dict.keys()])
        self.transform_dict = transform_dict

    def __call__(self, view_key: ViewKey, view: View):
        if view_key.instant_key.game_id in self.transform_dict.keys():
            adaptation_vector = np.array(self.transform_dict[view_key.instant_key.game_id])
            view.image = np.clip(view.image.astype(np.float32)*adaptation_vector, 0, 255).astype(np.uint8)
        return view

Ancestors

class GammaCorrectionTransform (transform_dict=None)
Expand source code
class GammaCorrectionTransform(Transform):
    def __init__(self, transform_dict=None):
        self.transform_dict = {
            29582 : [1.04, 1.02, 0.93], # Gravelines game
            24651 : [1.05, 1.02, 0.92], # Gravelines game
            69244 : [1.035, 1.025, 0.990], # Gravelines game
            59201 : [1.040, 1.030, 0.990], # Gravelines game
            30046 : [0.98, 0.98, 0.98], # Strasbourg game
            # TODO: LAPUA
            # TODO: ESPOO
            **(transform_dict if transform_dict is not None else {})
        }
        # transform_dict is a dict of game_id, gamma correction triplets
        assert all([isinstance(k, int) for k in self.transform_dict.keys()])

    def __call__(self, instant_key: InstantKey, instant: Instant):
        if instant_key.game_id in self.transform_dict.keys():
            gammas = np.array(self.transform_dict[instant_key.game_id])
            for k, image in instant.all_images.items():
                instant.all_images[k] = gamma_correction(image, gammas)
        return instant

Ancestors

class Instant (db_item, dataset_folder, download_flags)

Python object describing dataset item.

Important

Attributes that require files to be downloaded (like images) should be decorated with functools.cached_property to prevent being read before they get downloaded.

Expand source code
class Instant(GenericItem):
    def __init__(self, db_item, dataset_folder, download_flags):
        self.dataset_folder = dataset_folder
        self.download_flags = download_flags

        self.arena_label = db_item["arena_label"]
        self.num_cameras = db_item["num_cameras"]

        self.game_id = db_item["game_id"]
        self.league_id = db_item["league_id"]
        self.rule_type = db_item["rule_type"]
        self.sport = db_item["sport"]

        self.timestamp = db_item["timestamp"]
        self.offsets = db_item["offsets"]

        self.annotation_state = db_item.get("annotation_state", None)
        self.annotator_id = db_item.get("annotator_id", None)
        self.annotation_ts = db_item.get("annotation_ts", None)
        self.annotation_duration = db_item.get("annotation_duration", None)
        self.annotation_game_state = db_item.get("annotation_game_state", "standard_game")
        self.annotated_human_masks = db_item.get("annotated_human_masks", False)

        self.format =  db_item["format"]

        annotation_map = {
            "player": Player,
            "ball": Ball
        }
        self.annotations = [annotation_map[a['type']](a)for a in (db_item.get('annotations', []) or [])]

        self.image_source = db_item.get("image_source", "raw")
        self.court_dim = COURT_DIM[self.rule_type]

    def __str__(self):
        return "({}[{:5d}]@{})".format(self.arena_label, self.game_id, self.timestamp)

    def get_filekey(self, prefix, suffix):
        return os.path.join(self.arena_label, str(self.game_id), "{}{}{}".format(prefix, self.timestamp, suffix))

    @cached_property
    def calibs(self):
        return [self.__load_calib(c) for c in range(self.num_cameras)]

    @cached_property
    def all_images(self):
        all_images = {}
        for c in range(self.num_cameras):
            for idx, offset in enumerate(self.offsets):
                if     (idx == 0) \
                    or (idx == 1 and self.download_flags & DownloadFlags.WITH_FOLLOWING_IMAGE) \
                    or (self.download_flags & DownloadFlags.WITH_ALL_IMAGES):
                    try:
                        all_images[(c,offset)] = self.__load_image(c, offset)
                    except BaseException as e:
                        raise ValueError((self.offsets, self.key)) from e
        return all_images

    @property
    def images(self):
        return [img for (c, offset), img in self.all_images.items() if offset == 0]

    @cached_property
    def human_masks(self):
        assert self.download_flags & DownloadFlags.WITH_HUMAN_SEGMENTATION_MASKS, \
            "Provided flag doesn't contain 'human_masks'. Recreate your dataset with appropriate DownloadFlags"
        try:
            filenames = [os.path.join(self.dataset_folder, self.get_filekey("camcourt{}_".format(cam_idx+1), "_humans.png")) for cam_idx in range(self.num_cameras)]
            return [imageio.imread(filename) for filename in filenames] # imageio handles 16bits images while cv2 doesn't
        except FileNotFoundError:
            # If one human_masks file is missing for one camera, no human_masks will be available.
            # If file is missing because no human appears on that camera, you should upload an empty image to the bucket.
            return []

    def __load_image(self, cam_idx, offset=0):
        filename = os.path.join(self.dataset_folder, self.get_filekey("camcourt{}_".format(cam_idx+1), "_{}.png".format(offset)))
        image = cv2.imread(filename)
        if image is None:
            raise FileNotFoundError(filename)
        return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    def __load_calib(self, cam_idx):
        assert self.download_flags & DownloadFlags.WITH_CALIB_FILE, \
            "Provided flag doesn't contain calib files. Recreate your dataset with appropriate DownloadFlags"
        filename = os.path.join(self.dataset_folder, self.get_filekey("camcourt{}_".format(cam_idx+1), ".json"))
        return parse_DeepSport_calib(json.load(open(filename, 'r'))['calibration'])

    def draw(self, i=None, draw_players=True, draw_ball=True, draw_lines=False):
        if i is None:
            w, h = self.court_dim
            image = np.ones((int(h), int(w), 3), np.uint8)*255
            R = np.identity(3)
            C = Point3D(w/2, h/2, -3000)
            m = w/0.01  # pixel_size (pixels/meters) on 1 centimeter sensor
            f = 0.009  # focal (meters)
            R = np.identity(3)
            calib = Calib(width=w, height=h, T=-R@C[:3], R=R, K=np.array([[f*m,  0, w/2], [0, f*m, h/2], [0,  0,  1]]))
        else:
            image = self.images[i].copy()
            calib = self.calibs[i]

        if draw_lines:
            Court(self.rule_type).draw_lines(image, calib)
        # if fg_detections is not None:
        #     calib = self.calibs[i] if i is not None else None
        #     detections = self.fg_detections[i][fg_detections] if i is not None else \
        #                  itertools.chain(*self.fg_detections)
        #     for det in detections:
        #         v = calib.project_3D_to_2D(det.feet).to_int_tuple()
        #         cv2.circle(image, v[0:2].flatten(), 7, [255, 0, 255], -1)

        for annotation in self.annotations:
            if annotation.type == "player" and draw_players:
                head = calib.project_3D_to_2D(annotation.head).to_int_tuple()
                hips = calib.project_3D_to_2D(annotation.hips).to_int_tuple()
                foot1 = calib.project_3D_to_2D(annotation.foot1).to_int_tuple()
                foot2 = calib.project_3D_to_2D(annotation.foot2).to_int_tuple()

                if any([kp[0] < 0 or kp[1] > image.shape[1] or kp[1] < 0 or kp[1] > image.shape[0] for kp in [head, hips]]):
                    continue

                # head tip
                length = 70 # cm
                headT3D = length * Point3D(np.cos(annotation.headAngle), np.sin(annotation.headAngle), 0)
                headT = calib.project_3D_to_2D(annotation.head+headT3D).to_int_tuple()

                color = [0, 0, 0]
                color[annotation.team-1] = 255

                if i is not None:
                    cv2.line(image, hips, foot1, color, 3)
                    cv2.line(image, hips, foot2, color, 3)
                    cv2.line(image, head, hips, color, 3)
                else:
                    cv2.circle(image, head, 5, color, -1)
                cv2.line(image, head, headT, color, 3)

            elif annotation.type == "ball" and draw_ball:
                center = tuple(int(x) for x in calib.project_3D_to_2D(annotation.center).to_list())
                color = [255, 255, 0]
                cv2.circle(image, center, 5, color, -1)
        return image

    @property
    def files(self):
        for i in range(0, int(self.num_cameras)):
            if self.download_flags & DownloadFlags.WITH_IMAGE:
                for idx, offset in enumerate(self.offsets):
                    if     (self.download_flags & DownloadFlags.WITH_ALL_IMAGES) \
                        or (self.download_flags & DownloadFlags.WITH_FOLLOWING_IMAGE and idx == 1) \
                        or (idx == 0):
                        yield self.get_filekey("camcourt{}_".format(i+1), "_{}.png".format(offset))
            if self.download_flags & DownloadFlags.WITH_CALIB_FILE:
                yield self.get_filekey("camcourt{}_".format(i+1), ".json")
            if self.download_flags & DownloadFlags.WITH_HUMAN_SEGMENTATION_MASKS:
                yield self.get_filekey("camcourt{}_".format(i+1), "_humans.png")

    @property
    def key(self):
        return InstantKey(self.arena_label, self.game_id, self.timestamp)

    @property
    def db_item(self):
        db_item = {
            "format": self.format,
            "image_source": self.image_source,

            # arena relative infos
            "arena_label": self.arena_label,
            "num_cameras": self.num_cameras,

            # game relative infos
            "sport": self.sport,
            "game_id": self.game_id,
            "league_id": self.league_id,
            "rule_type": self.rule_type,

            # instant relative infos
            "timestamp": self.timestamp,
            "offsets": self.offsets,

            "annotation_state": self.annotation_state,
            "annotations": [a.to_dict() for a in self.annotations],
            "annotated_human_masks": self.annotated_human_masks
        }

        for attr in ["annotation_ts", "annotator_id", "annotation_duration", "annotation_game_state"]:
            if value := getattr(self, attr, None):
                db_item[attr] = value
        return db_item

    def to_dict(self):
        return {"db_item": self.db_item, "download_flags": self.download_flags, "dataset_folder": self.dataset_folder}

Ancestors

Instance variables

var all_images

Declares a property to be lazy (evaluated only if absent from the object) Also provides a first basic mechanism for a lazy property. Enhanced by Lazy

Expand source code
def __get__(self, instance, ownerclass=None):
    name = self.name
    value = instance.__dict__.get(name, _NOVALUE)
    if value is _NOVALUE:
        try:
            value = self.initializer(instance)
        except AttributeError as e:
            raise LazyPropertyError(name) from e
        instance.__dict__[name] = value
    return value
var calibs

Declares a property to be lazy (evaluated only if absent from the object) Also provides a first basic mechanism for a lazy property. Enhanced by Lazy

Expand source code
def __get__(self, instance, ownerclass=None):
    name = self.name
    value = instance.__dict__.get(name, _NOVALUE)
    if value is _NOVALUE:
        try:
            value = self.initializer(instance)
        except AttributeError as e:
            raise LazyPropertyError(name) from e
        instance.__dict__[name] = value
    return value
var human_masks

Declares a property to be lazy (evaluated only if absent from the object) Also provides a first basic mechanism for a lazy property. Enhanced by Lazy

Expand source code
def __get__(self, instance, ownerclass=None):
    name = self.name
    value = instance.__dict__.get(name, _NOVALUE)
    if value is _NOVALUE:
        try:
            value = self.initializer(instance)
        except AttributeError as e:
            raise LazyPropertyError(name) from e
        instance.__dict__[name] = value
    return value
var images
Expand source code
@property
def images(self):
    return [img for (c, offset), img in self.all_images.items() if offset == 0]

Methods

def draw(self, i=None, draw_players=True, draw_ball=True, draw_lines=False)
Expand source code
def draw(self, i=None, draw_players=True, draw_ball=True, draw_lines=False):
    if i is None:
        w, h = self.court_dim
        image = np.ones((int(h), int(w), 3), np.uint8)*255
        R = np.identity(3)
        C = Point3D(w/2, h/2, -3000)
        m = w/0.01  # pixel_size (pixels/meters) on 1 centimeter sensor
        f = 0.009  # focal (meters)
        R = np.identity(3)
        calib = Calib(width=w, height=h, T=-R@C[:3], R=R, K=np.array([[f*m,  0, w/2], [0, f*m, h/2], [0,  0,  1]]))
    else:
        image = self.images[i].copy()
        calib = self.calibs[i]

    if draw_lines:
        Court(self.rule_type).draw_lines(image, calib)
    # if fg_detections is not None:
    #     calib = self.calibs[i] if i is not None else None
    #     detections = self.fg_detections[i][fg_detections] if i is not None else \
    #                  itertools.chain(*self.fg_detections)
    #     for det in detections:
    #         v = calib.project_3D_to_2D(det.feet).to_int_tuple()
    #         cv2.circle(image, v[0:2].flatten(), 7, [255, 0, 255], -1)

    for annotation in self.annotations:
        if annotation.type == "player" and draw_players:
            head = calib.project_3D_to_2D(annotation.head).to_int_tuple()
            hips = calib.project_3D_to_2D(annotation.hips).to_int_tuple()
            foot1 = calib.project_3D_to_2D(annotation.foot1).to_int_tuple()
            foot2 = calib.project_3D_to_2D(annotation.foot2).to_int_tuple()

            if any([kp[0] < 0 or kp[1] > image.shape[1] or kp[1] < 0 or kp[1] > image.shape[0] for kp in [head, hips]]):
                continue

            # head tip
            length = 70 # cm
            headT3D = length * Point3D(np.cos(annotation.headAngle), np.sin(annotation.headAngle), 0)
            headT = calib.project_3D_to_2D(annotation.head+headT3D).to_int_tuple()

            color = [0, 0, 0]
            color[annotation.team-1] = 255

            if i is not None:
                cv2.line(image, hips, foot1, color, 3)
                cv2.line(image, hips, foot2, color, 3)
                cv2.line(image, head, hips, color, 3)
            else:
                cv2.circle(image, head, 5, color, -1)
            cv2.line(image, head, headT, color, 3)

        elif annotation.type == "ball" and draw_ball:
            center = tuple(int(x) for x in calib.project_3D_to_2D(annotation.center).to_list())
            color = [255, 255, 0]
            cv2.circle(image, center, 5, color, -1)
    return image
def get_filekey(self, prefix, suffix)
Expand source code
def get_filekey(self, prefix, suffix):
    return os.path.join(self.arena_label, str(self.game_id), "{}{}{}".format(prefix, self.timestamp, suffix))
def to_dict(self)
Expand source code
def to_dict(self):
    return {"db_item": self.db_item, "download_flags": self.download_flags, "dataset_folder": self.dataset_folder}

Inherited members

class InstantKey (arena_label: str, game_id: int, timestamp: int)

InstantKey(arena_label, game_id, timestamp)

Expand source code
class InstantKey(NamedTuple):
    arena_label: str
    game_id: int
    timestamp: int

Ancestors

  • builtins.tuple

Instance variables

var arena_label : str

Alias for field number 0

var game_id : int

Alias for field number 1

var timestamp : int

Alias for field number 2

class KFoldsArenaLabelsTestingDatasetSplitter (fold_count=8, validation_pc=15)

DeepSportDatasetSplitter(validation_pc: int = 15, additional_keys_usage: str = 'skip', folds: str = 'ABCDE')

Expand source code
class KFoldsArenaLabelsTestingDatasetSplitter(DeepSportDatasetSplitter):
    def __init__(self, fold_count=8, validation_pc=15):
        self.fold_count = fold_count
        self.validation_pc = validation_pc

    def __call__(self, dataset, fold=0):
        keys = list(dataset.keys.all())
        assert fold >= 0 and fold < self.fold_count

        keys_dict = count_keys_per_arena_label(keys)
        keys_lists = split_equally(keys_dict, self.fold_count)

        testing_keys = [k for k in keys if k.arena_label in keys_lists[fold]]
        remaining_keys = [k for k in keys if k not in testing_keys]

        # Backup random seed
        random_state = random.getstate()
        random.seed(fold)

        validation_keys = random.sample(remaining_keys, len(keys)*self.validation_pc//100)

        # Restore random seed
        random.setstate(random_state)

        training_keys = [k for k in remaining_keys if k not in validation_keys]

        return [
            Subset(name="training", subset_type=SubsetType.TRAIN, keys=training_keys, dataset=dataset),
            Subset(name="validation", subset_type=SubsetType.EVAL, keys=validation_keys, dataset=dataset, repetitions=5),
            Subset(name="testing", subset_type=SubsetType.EVAL, keys=testing_keys, dataset=dataset, repetitions=5),
        ]

Ancestors

Class variables

var additional_keys_usage : str
var folds : str
var validation_pc : int
class Player (data)
Expand source code
class Player():
    def __init__(self, data):
        self.type = "player"
        self.origin = data.get('origin', "annotation")
        self.team = data['team']
        self.head = Point3D(*data['head'])
        self.hips = Point3D(*data['hips'])
        self.foot1 = Point3D(*data['foot1'])
        self.foot2 = Point3D(*data['foot2'])
        self.foot1_at_the_ground = str(data["foot1_at_the_ground"]).lower() == "true"
        self.foot2_at_the_ground = str(data["foot2_at_the_ground"]).lower() == "true"
        self.headAngle = data['headOrientation']
        self.camera = data['image']
        self.hipsAngle = data.get('hipsOrientation', self.headAngle)
        self.feet = (self.foot1 + self.foot2) / 2

    def to_dict(self):
        return {
            "type": self.type,
            "origin": self.origin,
            "team": self.team,
            "head": self.head.to_list(),
            "headOrientation": self.headAngle,
            "hips": self.hips.to_list(),
            "foot1": self.foot1.to_list(),
            "foot2": self.foot2.to_list(),
            "foot1_at_the_ground": self.foot1_at_the_ground,
            "foot2_at_the_ground": self.foot2_at_the_ground,
            "image": self.camera
        }

Methods

def to_dict(self)
Expand source code
def to_dict(self):
    return {
        "type": self.type,
        "origin": self.origin,
        "team": self.team,
        "head": self.head.to_list(),
        "headOrientation": self.headAngle,
        "hips": self.hips.to_list(),
        "foot1": self.foot1.to_list(),
        "foot2": self.foot2.to_list(),
        "foot1_at_the_ground": self.foot1_at_the_ground,
        "foot2_at_the_ground": self.foot2_at_the_ground,
        "image": self.camera
    }
class TestingArenaLabelsDatasetSplitter (testing_arena_labels, validation_pc=15)
Expand source code
class TestingArenaLabelsDatasetSplitter():
    def __init__(self, testing_arena_labels, validation_pc=15):
        self.testing_arena_labels = testing_arena_labels
        self.validation_pc = validation_pc
        assert isinstance(self.testing_arena_labels, list)

    def __call__(self, dataset, fold=0):
        keys = list(dataset.keys.all())
        testing_keys = [k for k in keys if k.arena_label in self.testing_arena_labels]
        remaining_keys = [k for k in keys if k not in testing_keys]

        # Backup random seed
        random_state = random.getstate()
        random.seed(fold)

        validation_keys = random.sample(remaining_keys, len(keys)*self.validation_pc//100) if self.validation_pc else []

        # Restore random seed
        random.setstate(random_state)

        training_keys = [k for k in remaining_keys if k not in validation_keys]

        subsets = [
            Subset(name="training", subset_type=SubsetType.TRAIN, keys=training_keys, dataset=dataset),
            Subset(name="validation", subset_type=SubsetType.EVAL, keys=validation_keys, dataset=dataset, repetitions=2),
            Subset(name="testing", subset_type=SubsetType.EVAL, keys=testing_keys, dataset=dataset, repetitions=2),
        ]

        return [s for s in subsets if len(s.keys) > 0]
class UndistortTransform
Expand source code
class UndistortTransform(Transform):
    def __call__(self, key, view):
        all_images = []
        for image in view.all_images:
            all_images.append(cv2.undistort(image, view.calib.K, view.calib.kc))
        view.calib = view.calib.update(kc=np.array([0,0,0,0,0]))
        return view

Ancestors

class View (all_images, calib, annotations=None, **kwargs)
Expand source code
class View():
    def __init__(self, all_images, calib, annotations=None, **kwargs):
        self.image = all_images[0]
        self.all_images = all_images
        self.calib = calib
        self.annotations = annotations
        for k, v in kwargs.items():
            setattr(self, k, v)

    def draw(self):
        image = self.image.copy()

        coord_2D_int = lambda x: self.calib.project_3D_to_2D(x).to_int_tuple()

        for annotation in self.annotations:
            if annotation.type == "player":
                head = annotation.head
                hips = annotation.hips
                foot1 = annotation.foot1
                foot2 = annotation.foot2

                color = [0, 0, 0]
                color[annotation.team-1] = 255
                cv2.line(image, coord_2D_int(head), coord_2D_int(hips), color, 3)
                cv2.line(image, coord_2D_int(hips), coord_2D_int(foot1), color, 3)
                cv2.line(image, coord_2D_int(hips), coord_2D_int(foot2), color, 3)

            elif annotation.type == "ball":
                ball =  annotation.center
                color = [255, 255, 0]
                cv2.circle(image, coord_2D_int(ball), 5, color, -1)

        return image

Methods

def draw(self)
Expand source code
def draw(self):
    image = self.image.copy()

    coord_2D_int = lambda x: self.calib.project_3D_to_2D(x).to_int_tuple()

    for annotation in self.annotations:
        if annotation.type == "player":
            head = annotation.head
            hips = annotation.hips
            foot1 = annotation.foot1
            foot2 = annotation.foot2

            color = [0, 0, 0]
            color[annotation.team-1] = 255
            cv2.line(image, coord_2D_int(head), coord_2D_int(hips), color, 3)
            cv2.line(image, coord_2D_int(hips), coord_2D_int(foot1), color, 3)
            cv2.line(image, coord_2D_int(hips), coord_2D_int(foot2), color, 3)

        elif annotation.type == "ball":
            ball =  annotation.center
            color = [255, 255, 0]
            cv2.circle(image, coord_2D_int(ball), 5, color, -1)

    return image
class ViewKey (instant_key: InstantKey, camera: int, index: int)

ViewKey(instant_key, camera, index)

Expand source code
class ViewKey(NamedTuple):
    instant_key: InstantKey
    camera: int
    index: int

    @property
    def arena_label(self):
        return self.instant_key.arena_label

    @property
    def timestamp(self):
        return self.instant_key.timestamp

    @property
    def game_id(self):
        return self.instant_key.game_id

Ancestors

  • builtins.tuple

Instance variables

var arena_label
Expand source code
@property
def arena_label(self):
    return self.instant_key.arena_label
var camera : int

Alias for field number 1

var game_id
Expand source code
@property
def game_id(self):
    return self.instant_key.game_id
var index : int

Alias for field number 2

var instant_keyInstantKey

Alias for field number 0

var timestamp
Expand source code
@property
def timestamp(self):
    return self.instant_key.timestamp
class ViewRandomCropperTransform (output_shape, size_min, size_max, max_angle=0, do_flip=False, padding=0, margin=0, debug=False, regenerate=False)

Randomly scale, crop and rotate dataset items. The scale factor is randomly selected to keep the given keypoints of interest between size_min and size_max (At each call, the current keypoint size is returned by _get_current_parameters).

Arguments

output_shape: Tuple(int, int) final shape of image-like data. size_min: (int) lower bound of keypoints random size. If 0 size_min and size_max are ignored and no random scaling is applied. size_max: (int) upper bound of keypoints random size. If 0 size_min and size_max are ignored and no random scaling is applied. max_angle: (degrees) positive and negative bounds for random rotation do_flip: (bool) tells if random flip should be applied padding: (px) amount of padding margin: (px) minimum margin between keypoints and output image border debug: (bool) if True, doesn't actually crop but display debug information on image instead. regenerate: (bool) if True, items are (deep)-copied before calling _apply_transformation. Else, transformation can occur in-place.

Expand source code
class ViewRandomCropperTransform(RandomCropperTransform):
    def _apply_transformation(self, view, A):
        if self.debug:
            w, h = self.output_shape
            points = Point2D(np.linalg.inv(A)@Point2D([0,0,w,w],[0,h,h,0]).H)
            cv2.polylines(view.image, [points.T.astype(np.int32)], True, (255,0,0), self.linewidth)
        else:
            view.image = cv2.warpAffine(view.image, A[0:2,:], self.output_shape, flags=cv2.INTER_LINEAR)
            view.calib = view.calib.update(K=A@view.calib.K, width=self.output_shape[0], height=self.output_shape[1])

            if hasattr(view, "all_images"):
                for i in range(1, len(view.all_images)): # skip first image as it was already done
                    view.all_images[i] = cv2.warpAffine(view.all_images[i], A[0:2,:], self.output_shape, flags=cv2.INTER_LINEAR)
            if hasattr(view, "human_masks") and view.human_masks is not None:
                view.human_masks = cv2.warpAffine(view.human_masks, A[0:2,:], self.output_shape, flags=cv2.INTER_NEAREST)
        return view

Ancestors

Subclasses

Inherited members

class ViewsDataset (instants_dataset: InstantsDataset, view_builder: ViewBuilder, output_shape=None, rescale=True, crop=True)

Dataset of views built according the given ViewBuilder, extracted from the given InstantDataset.

Arguments

instants_dataset - the InstantDataset from which views are built view_builder - the ViewBuilder dictating what type of view is to be created output_shape - view aspect ratio (or exact dimension if 'rescale' is given) rescale - tells whether the view should be rescaled to output_shape size crop - tells whether the original image should be cropped or a rectangle should be drawn instead (for debug purposes)

Expand source code
class ViewsDataset(AugmentedDataset):
    """ Dataset of views built according the given ViewBuilder, extracted from the given InstantDataset.
        Arguments:
            instants_dataset   - the InstantDataset from which views are built
            view_builder       - the ViewBuilder dictating what type of view is to be created
            output_shape       - view aspect ratio (or exact dimension if 'rescale' is given)
            rescale            - tells whether the view should be rescaled to output_shape size
            crop               - tells whether the original image should be cropped or a rectangle
                                 should be drawn instead (for debug purposes)
    """
    def __init__(self, instants_dataset: InstantsDataset, view_builder: ViewBuilder, output_shape=None, rescale=True, crop=True):
        super().__init__(instants_dataset)
        self.view_builder = view_builder
        self.output_shape = output_shape
        self.rescale = rescale
        self.crop = crop

    def _crop_view(self, view_description: ViewDescription, instant: Instant, **kwargs):
        padding = self.view_builder.padding
        c = view_description.camera
        input_height, input_width, _ = instant.images[c].shape
        aspect_ratio = self.output_shape[0]/self.output_shape[1] if self.output_shape else None
        x_slice, y_slice = view_description.box.increase_box(
            max_width=input_width,
            max_height=input_height,
            aspect_ratio=aspect_ratio,
            padding=self.view_builder.padding
        )
        all_images = []

        if self.crop:
            for offset in instant.offsets:
                index = (c,offset)
                if index not in instant.all_images:
                    continue # that offset was not downloaded with the download flag of instants dataset
                image = crop_padded(instant.all_images[index], x_slice, y_slice, padding)
                if self.rescale and self.output_shape:
                    image = cv2.resize(image, self.output_shape)
                all_images.append(image)
            calib = instant.calibs[c].crop(x_slice, y_slice) # handles negative `slice.start` positions
            if self.rescale and self.output_shape:
                calib = calib.scale(*self.output_shape)
            if instant.download_flags & DownloadFlags.WITH_HUMAN_SEGMENTATION_MASKS and instant.human_masks:
                human_masks = crop_padded(instant.human_masks[c], x_slice, y_slice, padding)
                if self.rescale and self.output_shape:
                    human_masks = cv2.resize(human_masks, self.output_shape)
            else:
                human_masks = None
        else:
            for offset in instant.offsets:
                # the coordinates of the rectangle below are probably wrong see documentation of cv2.rectangle
                raise NotImplementedError("TODO: check rectangle coordinates")
                image = cv2.rectangle(instant.all_images[(c, offset)], (x_slice.start, x_slice.stop), (y_slice.start, y_slice.stop), (255,0,0), 10)
                all_images.append(image)
            calib = instant.calibs[c]
            human_masks = instant.human_masks[c]

        return View(all_images, calib, instant.annotations, rule_type=instant.rule_type, human_masks=human_masks, **kwargs)

    def augment(self, instant_key: InstantKey, instant: Instant):
        random_state = np.random.get_state()
        np.random.seed(instant_key.timestamp & 0xFFFFFFFF)

        for view_description in self.view_builder(instant_key, instant):
            view_key = ViewKey(instant_key, view_description.camera, view_description.index)
            yield view_key, self._crop_view(view_description, instant, **view_description.data)

        # required to keep the random seed random
        np.random.set_state(random_state)

Ancestors

  • mlworkflow.datasets.AugmentedDataset
  • mlworkflow.datasets.Dataset

Methods

def augment(self, instant_key: InstantKey, instant: Instant)
Expand source code
def augment(self, instant_key: InstantKey, instant: Instant):
    random_state = np.random.get_state()
    np.random.seed(instant_key.timestamp & 0xFFFFFFFF)

    for view_description in self.view_builder(instant_key, instant):
        view_key = ViewKey(instant_key, view_description.camera, view_description.index)
        yield view_key, self._crop_view(view_description, instant, **view_description.data)

    # required to keep the random seed random
    np.random.set_state(random_state)