deepsport_utilities.ds.instants_dataset API documentation

# 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

  - Transform
`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

  - Transform

  ### 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

  - Transform
`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

  - Transform
`class AddNextImageFactory`
  Expand source code

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

  ### Ancestors

  - Transform
`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

  - BallAnnotation

  ### 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

  - ViewRandomCropperTransform
  - RandomCropperTransform
  - Transform

  ### Inherited members

  - `ViewRandomCropperTransform`:
    - `random_size`
`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

  - Transform
`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

  - ViewBuilder

  ### 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

  - ViewBuilder

  ### 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

  - ViewBuilder

  ### 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

  - ViewBuilder

  ### 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

  - ViewBuilder

  ### 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

  - ViewBuilder

  ### 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

  - Transform
`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

  - Transform
`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

  - Transform
`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

  - Transform
`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

  - GenericItem

  ### 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

  - `GenericItem`:
    - `db_item`
    - `files`
    - `key`
`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

  - DeepSportDatasetSplitter

  ### 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

  - Transform
`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` 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

  - RandomCropperTransform
  - Transform

  ### Subclasses

  - BallCropperTransform
  - CleverViewRandomCropperTransform
  - NaiveViewRandomCropperTransform
  - PlayerViewRandomCropperTransform

  ### Inherited members

  - `RandomCropperTransform`:
    - `random_size`
`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)