deepsport_utilities.transforms API documentation

# Module `deepsport_utilities.transforms`

## Classes

`class DataExtractorTransform (*factories)`
  Expand source code

      class DataExtractorTransform(Transform):
          def __init__(self, *factories):
              self.factories = list(factories)
          def __call__(self, key, item):
              if not item:
                  return None
              data = {}
              for factory in self.factories:
                  if factory is None:
                      continue
                  try:
                      data.update(**factory(key, item))
                  except BaseException as e:
                      print(factory, "failed", e)
                      raise
              return data

  ### Ancestors

  - Transform
`class DeclutterItems (drop)`
  Expand source code

      class DeclutterItems(Transform):
          """ Drops attributes from dataset items. Attributes to drop are given by the
              'drop' argument.
          """
          def __init__(self, drop):
              self.drop = drop
          def __call__(self, key, item):
              for name in self.drop:
                  delattr(item, name)
              return item

  Drops attributes from dataset items. Attributes to drop are given by the 'drop' argument.

  ### Ancestors

  - Transform
`class DoNothing`
  Expand source code

      class DoNothing(Transform):
          def __call__(self, key, item):
              return item

  ### Ancestors

  - Transform
`class IncompatibleCropException (*args, **kwargs)`
  Expand source code

      class IncompatibleCropException(ValueError):
          pass

  Inappropriate argument value (of correct type).

  ### Ancestors

  - builtins.ValueError
  - builtins.Exception
  - builtins.BaseException
`class JPEGCompressionTransform (key_names, q_range=(30, 60))`
  Expand source code

      class JPEGCompressionTransform(Transform):
          def __init__(self, key_names, q_range=(30,60)):
              self.key_names = key_names
              self.q_range = q_range
              assert len(q_range) == 2 and q_range[0] < q_range[1] and q_range[0] > 0 and q_range[1] <= 100

          def __call__(self, key, data):
              if data is None:
                  return None
              q = random.randint(*self.q_range)
              for k in data:
                  if k in self.key_names:
                      data[k] = jpegBlur(data[k], q)
              return data

  ### Ancestors

  - Transform
`class RandomCropperTransform (output_shape,
size_min,
size_max,
max_angle=0,
do_flip=False,
padding=0,
margin=0,
debug=False,
regenerate=False)`
  Expand source code

      class RandomCropperTransform(Transform, metaclass=abc.ABCMeta):
          linewidth = 4
          random_size = np.random.uniform
          def __init__(self, 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: (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.
              """
              self.output_shape = output_shape
              self.size_min = size_min
              self.size_max = size_max
              self.max_angle = max_angle
              self.do_flip = do_flip
              assert not self.do_flip, "There seem to be a bug in the flip"
              self.padding = padding
              self.margin = margin
              self.debug = debug
              self.regenerate = regenerate

          def compute(self, input_shape, keypoints, actual_size, size_min=None, size_max=None):
              if size_min is not None and self.size_min*self.size_max == 0:
                  # updated size range after failing to create a crop at given scale
                  raise IncompatibleCropException("Impossible to crop image without changing the scale")

              size_min = size_min or self.size_min
              size_max = size_max or self.size_max

              if size_min > size_max:
                  raise IncompatibleCropException("Impossible to crop image with object in the given size range")
              target_size = self.random_size(size_min, size_max) if size_min and size_max else actual_size
              ratio = target_size/actual_size
              tmp_width, tmp_height = [int(x/ratio) for x in self.output_shape]
              if tmp_width == 0 or tmp_height == 0:
                  raise IncompatibleCropException("Impossible to crop image with object in the given size range")
              input_width, input_height = input_shape

              # If target size makes the output image bigger than input image, try with a lower size
              if tmp_width >= input_width + 2*self.padding or tmp_height >= input_height + 2*self.padding:
                  if not size_min or not size_max:
                      raise IncompatibleCropException("Impossible to crop image with object in the given size range")
                  return self.compute(input_shape, keypoints, actual_size, size_min=target_size*1.1, size_max=size_max)

              margin = self.margin / ratio # margin expressed in pixels in the output image
              if keypoints is not None:
                  # Restrict keypoints in input image limits
                  max_keypoints_x = min(int(np.max(keypoints.x))+margin, input_width)
                  min_keypoints_x = max(0, int(np.min(keypoints.x))-margin)
                  max_keypoints_y = min(int(np.max(keypoints.y))+margin, input_height)
                  min_keypoints_y = max(0, int(np.min(keypoints.y))-margin)

                  # Compute offsets to fit input image
                  x_offset_min = max(-self.padding, max_keypoints_x - tmp_width)
                  x_offset_max = min(min_keypoints_x, input_width + self.padding)
                  y_offset_min = max(-self.padding, max_keypoints_y - tmp_height)
                  y_offset_max = min(min_keypoints_y, input_height + self.padding)
              else:
                  x_offset_min = -self.padding
                  x_offset_max = input_width - tmp_width + self.padding
                  y_offset_min = -self.padding
                  y_offset_max = input_height - tmp_height + self.padding

              x_offset_max = int(np.ceil(x_offset_max))
              x_offset_min = int(np.floor(x_offset_min))
              y_offset_max = int(np.ceil(y_offset_max))
              y_offset_min = int(np.floor(y_offset_min))

              # If target size makes it incompatible with input image shape and keypoints positions, try with a higher size
              if x_offset_max < x_offset_min or y_offset_max < y_offset_min:
                  return self.compute(input_shape, keypoints, actual_size, size_min=size_min, size_max=target_size*0.9)

              x_offset = np.random.randint(x_offset_min, x_offset_max) if x_offset_min != x_offset_max else x_offset_max
              y_offset = np.random.randint(y_offset_min, y_offset_max) if y_offset_min != y_offset_max else y_offset_max

              x_slice = slice(x_offset, x_offset+tmp_width, None)
              y_slice = slice(y_offset, y_offset+tmp_height, None)

              angle = self.max_angle*(2*np.random.beta(2, 2)-1)

              return angle, x_slice, y_slice

          @abc.abstractmethod
          def _get_current_parameters(self, key, item):
              raise NotImplementedError(
                  "This method should return (keypoints, actual_size, shape) corresponding to the " \
                  "current keypoints and size of object of interest in the image, as well as the " \
                  "current image shape (width, height)")

          @abc.abstractmethod
          def _apply_transformation(self, item, A):
              raise NotImplementedError(
                  "This method should return the final transformed item, based on the original" \
                  "item and the affine transformation matrix")

          def __call__(self, key, item):
              if item is None:
                  return None
              parameters = self._get_current_parameters(key, item)
              if parameters is None:
                  return None
              keypoints, actual_size, input_shape = parameters
              try:
                  angle, x_slice, y_slice = self.compute(input_shape, keypoints, actual_size)
                  flip = self.do_flip and bool(np.random.randint(0,2))
              except IncompatibleCropException:
                  return None

              A = parameters_to_affine_transform(angle, x_slice, y_slice, self.output_shape, flip)
              if self.regenerate:
                  item = copy.deepcopy(item)
              return self._apply_transformation(item, A)

  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

  - Transform

  ### Subclasses

  - ViewRandomCropperTransform

  ### Class variables

  `var linewidth`

  ### Static methods

  `def random_size(low=0.0, high=1.0, size=None)`

    uniform(low=0.0, high=1.0, size=None)

    Draw samples from a uniform distribution.

    Samples are uniformly distributed over the half-open interval `[low, high)` (includes low, but excludes high). In other words, any value within the given interval is equally likely to be drawn by `uniform`.

    Note

    New code should use the `~numpy.random.Generator.uniform` method of a `~numpy.random.Generator` instance instead; please see the :ref:`random-quick-start`.

    ## Parameters

    `low` :`float` or `array_like` of `floats`, optional
      Lower boundary of the output interval. All values generated will be greater than or equal to low. The default value is 0.
    `high` :`float` or `array_like` of `floats`
      Upper boundary of the output interval. All values generated will be less than or equal to high. The high limit may be included in the returned array of floats due to floating-point rounding in the equation `low + (high-low) * random_sample()`. The default value is 1.0.
    `size` :`int` or `tuple` of `ints`, optional
      Output shape. If the given shape is, e.g., `(m, n, k)`, then `m * n * k` samples are drawn. If size is `None` (default), a single value is returned if `low` and `high` are both scalars. Otherwise, `np.broadcast(low, high).size` samples are drawn.

    ## Returns

    `out` :`ndarray` or `scalar`
      Drawn samples from the parameterized uniform distribution.

    ## See Also

    `randint`
      Discrete uniform distribution, yielding integers.
    `random_integers`
      Discrete uniform distribution over the closed interval `[low, high]`.
    `random_sample`
      Floats uniformly distributed over `[0, 1)`.
    `random`
      Alias for `random_sample`.
    `rand`
      Convenience function that accepts dimensions as input, e.g., `rand(2,2)` would generate a 2-by-2 array of floats, uniformly distributed over `[0, 1)`.
    `random.Generator.uniform`
      which should be used for new code.

    ## Notes

    The probability density function of the uniform distribution is

    [ ]

    anywhere within the interval `[a, b)`, and zero elsewhere.

    When `high` == `low`, values of `low` will be returned. If `high` < `low`, the results are officially undefined and may eventually raise an error, i.e. do not rely on this function to behave when passed arguments satisfying that inequality condition. The `high` limit may be included in the returned array of floats due to floating-point rounding in the equation `low + (high-low) * random_sample()`. For example:

        >>> x = np.float32(5*0.99999999)
        >>> x
        np.float32(5.0)

    ## Examples

    Draw samples from the distribution:

        >>> s = np.random.uniform(-1,0,1000)

    All values are within the given interval:

        >>> np.all(s >= -1)
        True
        >>> np.all(s < 0)
        True

    Display the histogram of the samples, along with the probability density function:

        >>> import matplotlib.pyplot as plt
        >>> count, bins, ignored = plt.hist(s, 15, density=True)
        >>> plt.plot(bins, np.ones_like(bins), linewidth=2, color='r')
        >>> plt.show()

  ### Methods

  `def compute(self, input_shape, keypoints, actual_size, size_min=None, size_max=None)`
    Expand source code

        def compute(self, input_shape, keypoints, actual_size, size_min=None, size_max=None):
            if size_min is not None and self.size_min*self.size_max == 0:
                # updated size range after failing to create a crop at given scale
                raise IncompatibleCropException("Impossible to crop image without changing the scale")

            size_min = size_min or self.size_min
            size_max = size_max or self.size_max

            if size_min > size_max:
                raise IncompatibleCropException("Impossible to crop image with object in the given size range")
            target_size = self.random_size(size_min, size_max) if size_min and size_max else actual_size
            ratio = target_size/actual_size
            tmp_width, tmp_height = [int(x/ratio) for x in self.output_shape]
            if tmp_width == 0 or tmp_height == 0:
                raise IncompatibleCropException("Impossible to crop image with object in the given size range")
            input_width, input_height = input_shape

            # If target size makes the output image bigger than input image, try with a lower size
            if tmp_width >= input_width + 2*self.padding or tmp_height >= input_height + 2*self.padding:
                if not size_min or not size_max:
                    raise IncompatibleCropException("Impossible to crop image with object in the given size range")
                return self.compute(input_shape, keypoints, actual_size, size_min=target_size*1.1, size_max=size_max)

            margin = self.margin / ratio # margin expressed in pixels in the output image
            if keypoints is not None:
                # Restrict keypoints in input image limits
                max_keypoints_x = min(int(np.max(keypoints.x))+margin, input_width)
                min_keypoints_x = max(0, int(np.min(keypoints.x))-margin)
                max_keypoints_y = min(int(np.max(keypoints.y))+margin, input_height)
                min_keypoints_y = max(0, int(np.min(keypoints.y))-margin)

                # Compute offsets to fit input image
                x_offset_min = max(-self.padding, max_keypoints_x - tmp_width)
                x_offset_max = min(min_keypoints_x, input_width + self.padding)
                y_offset_min = max(-self.padding, max_keypoints_y - tmp_height)
                y_offset_max = min(min_keypoints_y, input_height + self.padding)
            else:
                x_offset_min = -self.padding
                x_offset_max = input_width - tmp_width + self.padding
                y_offset_min = -self.padding
                y_offset_max = input_height - tmp_height + self.padding

            x_offset_max = int(np.ceil(x_offset_max))
            x_offset_min = int(np.floor(x_offset_min))
            y_offset_max = int(np.ceil(y_offset_max))
            y_offset_min = int(np.floor(y_offset_min))

            # If target size makes it incompatible with input image shape and keypoints positions, try with a higher size
            if x_offset_max < x_offset_min or y_offset_max < y_offset_min:
                return self.compute(input_shape, keypoints, actual_size, size_min=size_min, size_max=target_size*0.9)

            x_offset = np.random.randint(x_offset_min, x_offset_max) if x_offset_min != x_offset_max else x_offset_max
            y_offset = np.random.randint(y_offset_min, y_offset_max) if y_offset_min != y_offset_max else y_offset_max

            x_slice = slice(x_offset, x_offset+tmp_width, None)
            y_slice = slice(y_offset, y_offset+tmp_height, None)

            angle = self.max_angle*(2*np.random.beta(2, 2)-1)

            return angle, x_slice, y_slice

`class Transform`
  Expand source code

      class Transform(metaclass=abc.ABCMeta):
          def __lt__(self, other):
              return self.__repr__() < other.__repr__()

          def __gt__(self, other):
              return self.__repr__() > other.__repr__()

          @abc.abstractmethod
          def __call__(self, key, item):
              pass

          def __repr__(self):
              config = getattr(self, "config", {k:v for k, v in self.__dict__.items() if not k.startswith("_")})
              attributes = ",".join("{}={}".format(k, v) for k,v in config.items())
              return "{}({})".format(self.__class__.__name__, attributes)

  ### Subclasses

  - CropBlockDividable
  - EnhanceColorTransform
  - GammaCorrectionTransform
  - ProjectBall
  - RemoveAnnotationMetadata
  - RemoveGroundTruth
  - AddBallAnnotation
  - AddBallFactory
  - AddBallHeightFactory
  - AddBallPositionFactory
  - AddBallPresenceFactory
  - AddBallSegmentationTargetViewFactory
  - AddBallSizeFactory
  - AddBallStateFactory
  - AddBallVisibilityFactory
  - AddCalibFactory
  - AddCourtFactory
  - AddDiffFactory
  - AddHumansSegmentationTargetViewFactory
  - AddImageFactory
  - AddNextImageFactory
  - BayeringTransform
  - ComputeDiff
  - GameGammaColorTransform
  - GameRGBColorTransform
  - UndistortTransform
  - DataExtractorTransform
  - DeclutterItems
  - DoNothing
  - JPEGCompressionTransform
  - RandomCropperTransform