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, CropBlockDividable
from .views_dataset import ViewsDataset, ViewKey, View, BuildBallViews, BuildCameraViews, \
BuildHeadsViews, BuildCourtViews, BuildPlayersViews, BuildThumbnailViews, BuildBallViewsWithRandom
from .views_transforms import AddBallAnnotation, UndistortTransform, \
ComputeDiff, GameGammaColorTransform, GameRGBColorTransform, \
BayeringTransform, ViewRandomCropperTransform, AddCalibFactory, AddCourtFactory, AddDiffFactory, \
AddNextImageFactory, BallViewRandomCropperTransform
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", "BallViewRandomCropperTransform",
"CropBlockDividable", "BuildBallViewsWithRandom"]
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 = BallState(data.get('state', 0)) 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 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 BallViewRandomCropperTransform (*args, on_ball=True, def_min=None, def_max=None, size_min=None, size_max=None, scale_min=None, scale_max=None, **kwargs)
-
Create random crops with annotated balls visible in them. 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.
Arguments
on_ball: if True, ball is always visible on the random crop. If False, ball is visible in the random crop half of the time. Else, on_ball is the probability that the ball is kept visible. def_min, def_max: min and max definition [px/m] of the random crop. size_min, size_max: min and max size [px] of the ball in the random crop. scale_min, scale_max: image's min and max scale factor
Randomly scale, crop and rotate dataset items. The scale factor is randomly selected to keep the given keypoints of interest between
size_min
andsize_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
andsize_max
are ignored and no random scaling is applied. size_max: (int) upper bound of keypoints random size. If0
size_min
andsize_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) ifTrue
, doesn't actually crop but display debug information on image instead. regenerate: (bool) ifTrue
, items are (deep)-copied before calling_apply_transformation
. Else, transformation can occur in-place.Expand source code
class BallViewRandomCropperTransform(ViewRandomCropperTransform): """ Create random crops with annotated balls visible in them. 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. Arguments: on_ball: if True, ball is always visible on the random crop. If False, ball is visible in the random crop half of the time. Else, on_ball is the probability that the ball is kept visible. def_min, def_max: min and max definition [px/m] of the random crop. size_min, size_max: min and max size [px] of the ball in the random crop. scale_min, scale_max: image's min and max scale factor """ def __init__(self, *args, on_ball=True, 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) self.on_ball = {False: 0.5, True: 1.0, None: 0.0}.get(on_ball, on_ball) if self.debug and self.on_ball != 1.0: raise NotImplementedError("Random keypoint should be drawn in view.image (as well as it's projection on court for better visualization)") def random_ball_position(self, view): court = setdefaultattr(view, "court", Court(getattr(view, "rule_type", "FIBA"))) #court_polygon = view.calib.get_region_visible_corners_2d(court.corners, 1) top_edge = list(court.visible_edges(view.calib))[0] start = top_edge[0][0][0] stop = top_edge[1][0][0] x = np.random.beta(2, 2)*(stop-start)+start y = np.random.beta(2, 2)*court.h/2+court.h/4 z = 0 return Point3D(x,y,z) def _get_current_parameters(self, view_key, view): ball = getattr(view, "ball", None) if ball is None: warning.warn("No ball annotation found, using random ball position") # If not `on_ball` use the ball anyway half of the samples if random.random() < self.on_ball and ball is not None: keypoint = ball.center view.ball = ball else: keypoint = self.random_ball_position(view) view.ball = Ball({ 'center': keypoint, 'origin': "random", 'image': view_key.camera, 'visible': False, }) # Use ball if any, else use the random ball (it only affects the strategy to scale) if self.true_size is None: size = 1 else: point3D = ball.center if ball is not None else keypoint size = float(view.calib.compute_length2D(point3D, self.true_size)) keypoint = view.calib.project_3D_to_2D(keypoint) input_shape = view.calib.width, view.calib.height return keypoint, size, input_shape
Ancestors
Subclasses
Methods
def random_ball_position(self, view)
-
Expand source code
def random_ball_position(self, view): court = setdefaultattr(view, "court", Court(getattr(view, "rule_type", "FIBA"))) #court_polygon = view.calib.get_region_visible_corners_2d(court.corners, 1) top_edge = list(court.visible_edges(view.calib))[0] start = top_edge[0][0][0] stop = top_edge[1][0][0] x = np.random.beta(2, 2)*(stop-start)+start y = np.random.beta(2, 2)*court.h/2+court.h/4 z = 0 return Point3D(x,y,z)
Inherited members
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 (margin, *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, margin, *args, origins=["annotation"], **kwargs): super().__init__(margin, *args, **kwargs) self.origins = origins def __call__(self, instant_key: InstantKey, instant: Instant): balls = [a for a in instant.annotations + getattr(instant, "detections", []) if a.type == 'ball'] predicate = lambda a: a.origin in self.origins for idx, ball in enumerate(filter(predicate, balls)): if not instant.calibs[ball.camera].projects_in(ball.center): continue 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
Subclasses
Class variables
var margin : float
var margin_in_pixels : bool
var padding : int
class BuildBallViewsWithRandom (*args, random: float = 0, **kwargs)
-
random: a proportion of random views that are at least
margin
away from any created view.Expand source code
class BuildBallViewsWithRandom(BuildBallViews): """ random: a proportion of random views that are at least `margin` away from any created view. """ def __init__(self, *args, random: float=0, **kwargs): assert 0 <= random < 1, "random must be in [0,1[" super().__init__(*args, **kwargs) self.random = random self.record = {True: 0, False: 0} def __call__(self, instant_key: InstantKey, instant: Instant): idx = 0 boxes = {} for view_description in super().__call__(instant_key, instant): yield view_description idx = view_description.index + 1 boxes.setdefault(view_description.camera, []).append(view_description.box) self.record[True] += 1 court = instant.court while (self.record[False] + 1)/(sum(self.record.values())+1) < self.random: camera = np.random.randint(instant.num_cameras) top_edge = list(court.visible_edges(instant.calibs[camera]))[0] start = top_edge[0][0][0] stop = top_edge[1][0][0] x = np.random.beta(2, 2)*(stop-start)+start y = np.random.beta(2, 2)*court.h/2+court.h/4 z = np.random.randint(-400, 0) point3D = Point3D(x,y,z) if not instant.calibs[camera].projects_in(point3D): continue point2D = instant.calibs[camera].project_3D_to_2D(point3D) if any([box.x <= point2D.x <= box.x+box.w and box.y <= point2D.y <= box.y+box.h for box in boxes.get(camera, [])]): continue ball = Ball({ 'origin': 'random', 'center': point3D, 'image': camera, 'visible': False, 'status': BallState.NONE, 'value': 0 }) keypoints = [point3D] 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) idx += 1 self.record[False] += 1
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))
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): super().__post_init__() 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 CropBlockDividable (block_size=16)
-
Expand source code
class CropBlockDividable(Transform): def __init__(self, block_size=16): self.block_size = block_size def __call__(self, instant_key: InstantKey, instant: Instant): for k, image in instant.all_images.items(): h, w = image.shape[:2] h = h - h % self.block_size w = w - w % self.block_size instant.all_images[k] = image[:h, :w] return instant
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
- ArenaLabelFoldsDatasetSplitter
- KFoldsArenaLabelsTestingDatasetSplitter
- OfficialFoldsDatasetSplitter
- SingleArenaDatasetSplitter
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] self.court = Court(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: self.court.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) for detection in getattr(self, "detections", []): if detection.type == "ball" and draw_ball: center = tuple(int(x) for x in calib.project_3D_to_2D(detection.center).to_list()) color = [0, 255, 255] 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: self.court.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) for detection in getattr(self, "detections", []): if detection.type == "ball" and draw_ball: center = tuple(int(x) for x in calib.project_3D_to_2D(detection.center).to_list()) color = [0, 255, 255] 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, evaluation_sets_repetitions=5)
-
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, evaluation_sets_repetitions=5): self.fold_count = fold_count self.validation_pc = validation_pc self.evaluation_sets_repetitions = evaluation_sets_repetitions 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) self.testing_arena_labels = keys_lists[fold] 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) # Restore random seed random.setstate(random_state) training_keys = [k for k in remaining_keys if k not in validation_keys] r = self.evaluation_sets_repetitions 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=r), Subset(name="testing", subset_type=SubsetType.EVAL, keys=testing_keys, dataset=dataset, repetitions=r), ]
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): testing_keys, remaining_keys = [], [] for key in dataset.keys: (remaining_keys, testing_keys)[key.arena_label in self.testing_arena_labels].append(key) # Backup random seed np_random_state = np.random.get_state() np.random.seed(fold) total_length = len(remaining_keys) + len(testing_keys) validation_keys, training_keys = [], [] validation_indices = np.zeros(total_length, dtype=np.int32) # a vector of 1s for validation keys validation_indices[np.random.choice(total_length, total_length*self.validation_pc//100, replace=False)] = 1 for i, key in zip(validation_indices, remaining_keys): (training_keys, validation_keys)[i].append(key) # Restore random seed np.random.set_state(np_random_state) 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_key : InstantKey
-
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
andsize_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
andsize_max
are ignored and no random scaling is applied. size_max: (int) upper bound of keypoints random size. If0
size_min
andsize_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) ifTrue
, doesn't actually crop but display debug information on image instead. regenerate: (bool) ifTrue
, 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)