vkit.mechanism.distortion_policy.geometric.mls

  1# Copyright 2022 vkit-x Administrator. All Rights Reserved.
  2#
  3# This project (vkit-x/vkit) is dual-licensed under commercial and SSPL licenses.
  4#
  5# The commercial license gives you the full rights to create and distribute software
  6# on your own terms without any SSPL license obligations. For more information,
  7# please see the "LICENSE_COMMERCIAL.txt" file.
  8#
  9# This project is also available under Server Side Public License (SSPL).
 10# The SSPL licensing is ideal for use cases such as open source projects with
 11# SSPL distribution, student/academic purposes, hobby projects, internal research
 12# projects without external distribution, or other projects where all SSPL
 13# obligations can be met. For more information, please see the "LICENSE_SSPL.txt" file.
 14from typing import Tuple, List
 15
 16import attrs
 17from numpy.random import Generator as RandomGenerator
 18
 19from vkit.element import Point, PointList
 20from vkit.mechanism import distortion
 21from ..type import DistortionConfigGenerator, DistortionPolicyFactory
 22from ..opt import sample_float, SampleFloatMode, generate_grid_size
 23
 24
 25@attrs.define
 26class SimilarityMlsConfigGeneratorConfig:
 27    num_segments_min: int = 2
 28    num_segments_max: int = 4
 29    step_min: int = 10
 30    radius_max_ratio_min: float = 0.025
 31    radius_max_ratio_max: float = 0.125
 32    grid_size_min: int = 15
 33    grid_size_ratio: float = 0.01
 34
 35
 36class SimilarityMlsConfigGenerator(
 37    DistortionConfigGenerator[
 38        SimilarityMlsConfigGeneratorConfig,
 39        distortion.SimilarityMlsConfig,
 40    ]
 41):  # yapf: disable
 42
 43    @classmethod
 44    def generate_coord(cls, length: int, step: int, rng: RandomGenerator):
 45        end = length - 1
 46        if end % step == 0:
 47            steps = [step] * (end // step)
 48        else:
 49            steps = [step] * (end // step - 1)
 50            steps.append(step + end % step)
 51        assert sum(steps) == end
 52
 53        rng.shuffle(steps)
 54        coord: List[int] = [0]
 55        for step in steps:
 56            pos = coord[-1] + step
 57            coord.append(pos)
 58        return coord
 59
 60    def __call__(self, shape: Tuple[int, int], rng: RandomGenerator):
 61        # Generate control points.
 62        short_side_length = min(shape)
 63        num_segments = rng.integers(self.config.num_segments_min, self.config.num_segments_max + 1)
 64        step = (short_side_length - 1) // num_segments
 65        if step < self.config.step_min:
 66            # Downgrade to corners if the gap is too small.
 67            step = short_side_length - 1
 68
 69        height, width = shape
 70        # NOTE:
 71        # 1. Corners are always included.
 72        # 2. Distance of any two points >= step.
 73        coord_y = self.generate_coord(height, step, rng)
 74        coord_x = self.generate_coord(width, step, rng)
 75        src_handle_points = PointList()
 76        for y in coord_y:
 77            for x in coord_x:
 78                src_handle_points.append(Point.create(y=y, x=x))
 79
 80        # Generate deformed points.
 81        assert self.config.radius_max_ratio_max < 0.5
 82        radius_max_ratio = sample_float(
 83            level=self.level,
 84            value_min=self.config.radius_max_ratio_min,
 85            value_max=self.config.radius_max_ratio_max,
 86            prob_reciprocal=None,
 87            rng=rng,
 88            mode=SampleFloatMode.QUAD,
 89        )
 90        radius = int(radius_max_ratio * step)
 91        dst_handle_points = PointList()
 92        for point in src_handle_points:
 93            delta_y = rng.integers(-radius, radius + 1)
 94            delta_x = rng.integers(-radius, radius + 1)
 95            dst_handle_points.append(Point.create(
 96                y=point.y + delta_y,
 97                x=point.x + delta_x,
 98            ))
 99
100        # Generate grid size.
101        grid_size = generate_grid_size(
102            self.config.grid_size_min,
103            self.config.grid_size_ratio,
104            shape,
105        )
106
107        return distortion.SimilarityMlsConfig(
108            src_handle_points=src_handle_points.to_point_tuple(),
109            dst_handle_points=dst_handle_points.to_point_tuple(),
110            grid_size=grid_size,
111        )
112
113
114similarity_mls_policy_factory = DistortionPolicyFactory(
115    distortion.similarity_mls,
116    SimilarityMlsConfigGenerator,
117)
class SimilarityMlsConfigGeneratorConfig:
27class SimilarityMlsConfigGeneratorConfig:
28    num_segments_min: int = 2
29    num_segments_max: int = 4
30    step_min: int = 10
31    radius_max_ratio_min: float = 0.025
32    radius_max_ratio_max: float = 0.125
33    grid_size_min: int = 15
34    grid_size_ratio: float = 0.01
SimilarityMlsConfigGeneratorConfig( num_segments_min: int = 2, num_segments_max: int = 4, step_min: int = 10, radius_max_ratio_min: float = 0.025, radius_max_ratio_max: float = 0.125, grid_size_min: int = 15, grid_size_ratio: float = 0.01)
2def __init__(self, num_segments_min=attr_dict['num_segments_min'].default, num_segments_max=attr_dict['num_segments_max'].default, step_min=attr_dict['step_min'].default, radius_max_ratio_min=attr_dict['radius_max_ratio_min'].default, radius_max_ratio_max=attr_dict['radius_max_ratio_max'].default, grid_size_min=attr_dict['grid_size_min'].default, grid_size_ratio=attr_dict['grid_size_ratio'].default):
3    self.num_segments_min = num_segments_min
4    self.num_segments_max = num_segments_max
5    self.step_min = step_min
6    self.radius_max_ratio_min = radius_max_ratio_min
7    self.radius_max_ratio_max = radius_max_ratio_max
8    self.grid_size_min = grid_size_min
9    self.grid_size_ratio = grid_size_ratio

Method generated by attrs for class SimilarityMlsConfigGeneratorConfig.

 37class SimilarityMlsConfigGenerator(
 38    DistortionConfigGenerator[
 39        SimilarityMlsConfigGeneratorConfig,
 40        distortion.SimilarityMlsConfig,
 41    ]
 42):  # yapf: disable
 43
 44    @classmethod
 45    def generate_coord(cls, length: int, step: int, rng: RandomGenerator):
 46        end = length - 1
 47        if end % step == 0:
 48            steps = [step] * (end // step)
 49        else:
 50            steps = [step] * (end // step - 1)
 51            steps.append(step + end % step)
 52        assert sum(steps) == end
 53
 54        rng.shuffle(steps)
 55        coord: List[int] = [0]
 56        for step in steps:
 57            pos = coord[-1] + step
 58            coord.append(pos)
 59        return coord
 60
 61    def __call__(self, shape: Tuple[int, int], rng: RandomGenerator):
 62        # Generate control points.
 63        short_side_length = min(shape)
 64        num_segments = rng.integers(self.config.num_segments_min, self.config.num_segments_max + 1)
 65        step = (short_side_length - 1) // num_segments
 66        if step < self.config.step_min:
 67            # Downgrade to corners if the gap is too small.
 68            step = short_side_length - 1
 69
 70        height, width = shape
 71        # NOTE:
 72        # 1. Corners are always included.
 73        # 2. Distance of any two points >= step.
 74        coord_y = self.generate_coord(height, step, rng)
 75        coord_x = self.generate_coord(width, step, rng)
 76        src_handle_points = PointList()
 77        for y in coord_y:
 78            for x in coord_x:
 79                src_handle_points.append(Point.create(y=y, x=x))
 80
 81        # Generate deformed points.
 82        assert self.config.radius_max_ratio_max < 0.5
 83        radius_max_ratio = sample_float(
 84            level=self.level,
 85            value_min=self.config.radius_max_ratio_min,
 86            value_max=self.config.radius_max_ratio_max,
 87            prob_reciprocal=None,
 88            rng=rng,
 89            mode=SampleFloatMode.QUAD,
 90        )
 91        radius = int(radius_max_ratio * step)
 92        dst_handle_points = PointList()
 93        for point in src_handle_points:
 94            delta_y = rng.integers(-radius, radius + 1)
 95            delta_x = rng.integers(-radius, radius + 1)
 96            dst_handle_points.append(Point.create(
 97                y=point.y + delta_y,
 98                x=point.x + delta_x,
 99            ))
100
101        # Generate grid size.
102        grid_size = generate_grid_size(
103            self.config.grid_size_min,
104            self.config.grid_size_ratio,
105            shape,
106        )
107
108        return distortion.SimilarityMlsConfig(
109            src_handle_points=src_handle_points.to_point_tuple(),
110            dst_handle_points=dst_handle_points.to_point_tuple(),
111            grid_size=grid_size,
112        )

Abstract base class for generic types.

A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::

class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.

This class can then be used as follows::

def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default

@classmethod
def generate_coord(cls, length: int, step: int, rng: numpy.random._generator.Generator):
44    @classmethod
45    def generate_coord(cls, length: int, step: int, rng: RandomGenerator):
46        end = length - 1
47        if end % step == 0:
48            steps = [step] * (end // step)
49        else:
50            steps = [step] * (end // step - 1)
51            steps.append(step + end % step)
52        assert sum(steps) == end
53
54        rng.shuffle(steps)
55        coord: List[int] = [0]
56        for step in steps:
57            pos = coord[-1] + step
58            coord.append(pos)
59        return coord