deepsport_utilities.ds.instants_dataset API documentation

# Module `deepsport_utilities.ds.instants_dataset`

## 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 = 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": int(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": int(self.state),
                "value": self.value
            }

`class BallState (value, names=None, *, module=None, qualname=None, type=None, start=1)`
  Expand source code

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

  An enumeration.

  ### 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,
rectify=True,
**kwargs)`
  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,
                       rectify=True, **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.rectify = rectify
              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 _apply_transformation(self, view, A):
              if not self.rectify:
                  return super()._apply_transformation(view, A)

              # rectify eccentricity caused by projection on a plane
              X = compute_shear_rectification_matrix(view.calib, view.__shear_center)
              return super()._apply_transformation(view, A@X)

          def _get_current_parameters(self, view_key, view):
              ball = getattr(view, "ball", None)

              # 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)[0])

              keypoint = view.calib.project_3D_to_2D(keypoint)
              input_shape = view.calib.width, view.calib.height
              view.__shear_center = keypoint
              return keypoint, size, input_shape

  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` 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: (int) positive and negative bounds for random rotation (in degrees) do_flip: (bool) tells if random flip should be applied padding: (int) amount of padding (in pixels) in input image margin: (int) minimum margin (in pixels) 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.

  ### Ancestors

  - ViewRandomCropperTransform
  - RandomCropperTransform
  - Transform

  ### Subclasses

  - CleverViewRandomCropperTransform

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

  - `ViewRandomCropperTransform`:
    - `random_size`
`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 (margin, *args, origins=['annotation'], **kwargs)`
  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']
              # 'ballseg.*' matches 'ballseg' and 'ballseg+ballsize'
              predicate = lambda a: any(re.fullmatch(origin, a.origin) for 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)

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

  ### Ancestors

  - ViewBuilder

  ### Subclasses

  - BuildBallViewsWithRandom
`class BuildBallViewsWithRandom (*args, random: float = 0, **kwargs)`
  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 compute_box(self, point3D_list: list, calib: Calib):
              if self.margin == np.inf:
                  return BoundingBox(0, 0, calib.width, calib.height)
              return super().compute_box(point3D_list, calib)
          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

  random: a proportion of random views that are at least `margin` away from any created view.

  ### Ancestors

  - BuildBallViews
  - ViewBuilder

  ### Methods

  `def compute_box(self,
  point3D_list: list,
  calib: Calib)`
    Expand source code

        def compute_box(self, point3D_list: list, calib: Calib):
            if self.margin == np.inf:
                return BoundingBox(0, 0, calib.width, calib.height)
            return super().compute_box(point3D_list, calib)

`class BuildCameraViews (margin: float = 0, padding: int = None, margin_in_pixels: bool = False)`
  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))

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

  ### Ancestors

  - ViewBuilder
`class BuildCourtViews (margin: float = 0,
padding: int = None,
margin_in_pixels: bool = False,
height: float = 300)`
  Expand source code

      @dataclass
      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))

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

  ### Ancestors

  - ViewBuilder

  ### Class variables

  `var height : float`
`class BuildHeadsViews (margin: float = 0, padding: int = None, margin_in_pixels: bool = False)`
  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)

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

  ### Ancestors

  - ViewBuilder
`class BuildPlayersViews (margin: float = 0,
padding: int = None,
margin_in_pixels: bool = False,
min_annotations: int = 1)`
  Expand source code

      @dataclass
      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]))

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

  ### 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)`
  Expand source code

      @dataclass
      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.court)
                      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)

  Builds a view around each person (players, referee)

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

  - Transform
`class DeepSportDatasetSplitter (validation_pc: int = 15,
additional_keys_usage: str = 'skip',
folds: str = 'ABCDEF',
repetitions: dict = None)`
  Expand source code

      @dataclasses.dataclass
      class DeepSportDatasetSplitter:
          validation_pc: int = 15
          additional_keys_usage: str = "skip"
          folds: str = "ABCDEF"
          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'],
              # F is a fictive fold for which all keys are used for training
          }
          repetitions: dict = None
          def __post_init__(self):
              self.repetitions = self.repetitions or {}

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

              testing_fold = self.folds[fold_index]
              self.testing_arena_labels = set(self.split.get(testing_fold, []))
              remaining_arena_labels = [label for f in self.folds.replace(testing_fold, "") for label in self.split.get(f, [])]
              testing_keys = []
              remaining_keys = defaultdict(list)
              additional_keys = []
              additional_arena_labels = set()
              for key in keys:
                  if key.arena_label in remaining_arena_labels:
                      remaining_keys[key.instant_key].append(key)
                  elif key.arena_label in self.testing_arena_labels:
                      testing_keys.append(key)
                  else:
                      additional_keys.append(key)
                      additional_arena_labels.add(key.arena_label)

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

              total_length = len(remaining_keys)
              validation_keys, training_keys = [], []
              indices = np.zeros(total_length, dtype=np.int32) # a vector of 1s for validation keys
              indices[np.random.choice(total_length, total_length*self.validation_pc//100, replace=False)] = 1
              for i, instant_key in zip(indices, remaining_keys):
                  (training_keys, validation_keys)[i].extend(remaining_keys[instant_key])

              # Restore random seed
              random.setstate(random_state)
              testing2_keys = None
              if additional_keys:
                  if self.additional_keys_usage == "testing":
                      testing_keys += additional_keys
                      self.testing_arena_labels = self.testing_arena_labels.union(additional_arena_labels)
                  elif self.additional_keys_usage == "training":
                      print(f"INFO: adding {len(additional_keys)} keys to the training set")
                      training_keys += additional_keys
                  elif self.additional_keys_usage == "validation":
                      validation_keys += additional_keys
                  elif self.additional_keys_usage in ["none", "skip"]:
                      pass
                  elif self.additional_keys_usage == "testing2":
                      testing2_keys = additional_keys
                  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")

              self.testing_arena_labels = list(self.testing_arena_labels)

              return training_keys, validation_keys, testing_keys, testing2_keys

          def __call__(self, dataset, fold=0):
              training_keys, validation_keys, testing_keys, testing2_keys = self.split_keys(dataset.keys, fold)
              subsets = [
                  Subset(name="training", stage=Stage.TRAIN, keys=training_keys, dataset=dataset),
                  Subset(name="validation", stage=Stage.EVAL, keys=validation_keys, dataset=dataset, repetitions=self.repetitions.get('validation', 1)),
                  Subset(name="testing", stage=Stage.EVAL, keys=testing_keys, dataset=dataset, repetitions=self.repetitions.get('testing', 1)),
              ]
              if testing2_keys:
                  subsets.append(Subset(name="testing2", stage=Stage.EVAL, keys=testing2_keys, dataset=dataset, repetitions=1))

              return subsets

  DeepSportDatasetSplitter(validation_pc: int = 15, additional_keys_usage: str = 'skip', folds: str = 'ABCDEF', repetitions: dict = None)

  ### Subclasses

  - ArenaLabelFoldsDatasetSplitter
  - KFoldsArenaLabelsTestingDatasetSplitter
  - OfficialFoldsDatasetSplitter
  - SingleArenaDatasetSplitter

  ### Class variables

  `var additional_keys_usage : str`
  `var folds : str`
  `var repetitions : dict`
  `var split`
  `var validation_pc : int`

  ### Methods

  `def split_keys(self, keys, fold=0)`
    Expand source code

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

            testing_fold = self.folds[fold_index]
            self.testing_arena_labels = set(self.split.get(testing_fold, []))
            remaining_arena_labels = [label for f in self.folds.replace(testing_fold, "") for label in self.split.get(f, [])]
            testing_keys = []
            remaining_keys = defaultdict(list)
            additional_keys = []
            additional_arena_labels = set()
            for key in keys:
                if key.arena_label in remaining_arena_labels:
                    remaining_keys[key.instant_key].append(key)
                elif key.arena_label in self.testing_arena_labels:
                    testing_keys.append(key)
                else:
                    additional_keys.append(key)
                    additional_arena_labels.add(key.arena_label)

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

            total_length = len(remaining_keys)
            validation_keys, training_keys = [], []
            indices = np.zeros(total_length, dtype=np.int32) # a vector of 1s for validation keys
            indices[np.random.choice(total_length, total_length*self.validation_pc//100, replace=False)] = 1
            for i, instant_key in zip(indices, remaining_keys):
                (training_keys, validation_keys)[i].extend(remaining_keys[instant_key])

            # Restore random seed
            random.setstate(random_state)
            testing2_keys = None
            if additional_keys:
                if self.additional_keys_usage == "testing":
                    testing_keys += additional_keys
                    self.testing_arena_labels = self.testing_arena_labels.union(additional_arena_labels)
                elif self.additional_keys_usage == "training":
                    print(f"INFO: adding {len(additional_keys)} keys to the training set")
                    training_keys += additional_keys
                elif self.additional_keys_usage == "validation":
                    validation_keys += additional_keys
                elif self.additional_keys_usage in ["none", "skip"]:
                    pass
                elif self.additional_keys_usage == "testing2":
                    testing2_keys = additional_keys
                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")

            self.testing_arena_labels = list(self.testing_arena_labels)

            return training_keys, validation_keys, testing_keys, testing2_keys

`class DownloadFlags (value, names=None, *, module=None, qualname=None, type=None, start=1)`
  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

  An enumeration.

  ### 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)`
  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 = Court(self.rule_type)
              self.timestamps = [self.timestamp for _ in range(self.num_cameras)]

          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):
              _ = self.calibs # triggers calib loading
              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]

          @property
          def ball(self):
              balls = [a for a in self.annotations if a.type == 'ball']
              if len(balls) > 1:
                  raise ValueError("Too many annotated balls")
              elif len(balls) == 1:
                  return balls[0]
              return None

          @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.w, self.court.h
                  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}

  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.

  ### Ancestors

  - GenericItem

  ### Instance variables

  `var all_images`
    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

    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

  `prop ball`
    Expand source code

        @property
        def ball(self):
            balls = [a for a in self.annotations if a.type == 'ball']
            if len(balls) > 1:
                raise ValueError("Too many annotated balls")
            elif len(balls) == 1:
                return balls[0]
            return None

  `var calibs`
    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

    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

  `var human_masks`
    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

    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

  `prop 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.w, self.court.h
                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

  - `GenericItem`:
    - `db_item`
    - `files`
    - `key`
`class InstantKey (arena_label: str, game_id: int, timestamp: int)`
  Expand source code

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

  InstantKey(arena_label, game_id, timestamp)

  ### Ancestors

  - builtins.tuple

  ### Instance variables

  `var arena_label : str`
    Expand source code

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

    Alias for field number 0

  `var game_id : int`
    Expand source code

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

    Alias for field number 1

  `var timestamp : int`
    Expand source code

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

    Alias for field number 2

`class KFoldsArenaLabelsTestingDatasetSplitter (fold_count=8, validation_pc=15, evaluation_sets_repetitions=5)`
  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", stage=Stage.TRAIN, keys=training_keys, dataset=dataset),
                  Subset(name="validation", stage=Stage.EVAL, keys=validation_keys, dataset=dataset, repetitions=r),
                  Subset(name="testing", stage=Stage.EVAL, keys=testing_keys, dataset=dataset, repetitions=r),
              ]

  DeepSportDatasetSplitter(validation_pc: int = 15, additional_keys_usage: str = 'skip', folds: str = 'ABCDEF', repetitions: dict = None)

  ### Ancestors

  - DeepSportDatasetSplitter
`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, repetitions=1)`
  Expand source code

      class TestingArenaLabelsDatasetSplitter():
          def __init__(self, testing_arena_labels, validation_pc=15, repetitions=1):
              self.testing_arena_labels = testing_arena_labels
              self.validation_pc = validation_pc
              self.repetitions = repetitions
              assert isinstance(self.testing_arena_labels, (list, tuple))

          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)
              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", stage=Stage.TRAIN, keys=training_keys, dataset=dataset),
                  Subset(name="validation", stage=Stage.EVAL, keys=validation_keys, dataset=dataset, repetitions=self.repetitions),
                  Subset(name="testing", stage=Stage.EVAL, keys=testing_keys, dataset=dataset, repetitions=self.repetitions),
              ]
              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)`
  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

  ViewKey(instant_key, camera, index)

  ### Ancestors

  - builtins.tuple

  ### Instance variables

  `prop arena_label`
    Expand source code

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

  `var camera : int`
    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

    Alias for field number 1

  `prop game_id`
    Expand source code

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

  `var index : int`
    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

    Alias for field number 2

  `var instant_key : InstantKey`
    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

    Alias for field number 0

  `prop 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)`
  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

  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: (int) positive and negative bounds for random rotation (in degrees) do_flip: (bool) tells if random flip should be applied padding: (int) amount of padding (in pixels) in input image margin: (int) minimum margin (in pixels) 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.

  ### Ancestors

  - RandomCropperTransform
  - Transform

  ### Subclasses

  - BallViewRandomCropperTransform
  - NaiveViewRandomCropperTransform
  - PlayerViewRandomCropperTransform

  ### Inherited members

  - `RandomCropperTransform`:
    - `random_size`
`class ViewsDataset (instants_dataset: InstantsDataset,
view_builder: ViewBuilder,
output_shape=None,
rescale=True,
crop=True,
debug=False)`
  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, debug=False):
              super().__init__(instants_dataset)
              self.view_builder = view_builder
              self.output_shape = output_shape
              self.rescale = rescale
              self.crop = crop
              self.debug = debug

          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
              )

              if self.crop:
                  all_images = []
                  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)
                      height, width, _ = image.shape
                      if self.rescale and self.output_shape and (width, height) != 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:
                  if self.debug:
                      all_images = []
                      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)
                  else:
                      all_images = instant.all_images
                  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)

  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)

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