Skip to content

dataclass

Module diagram

classDiagram
  class dataclass {
  }
  class box {
  }
  class segment {
  }

data.dataclass

커스텀 데이터 클래스 구현과 관련 메서드를 포함한 모듈입니다.

box

boxes_datas module-attribute

boxes_datas = {'xyxy': [[1, 2, 4, 6], [22, 28, 36, 62], [13, 39, 14, 78], [42, 24, 81, 46]], 'xywh': [[1, 2, 3, 4], [22, 28, 14, 34], [13, 39, 1, 39], [42, 24, 39, 22]], 'ccwh': [[2.5, 4, 3, 4], [29, 45, 14, 34], [13.5, 58.5, 1, 39], [61.5, 35, 39, 22]]}

boxes module-attribute

boxes = test_init_box(box_data, coordinate)

NumpyBoxes

Bases: ndarray

coordinate instance-attribute
coordinate: str
__new__
__new__(input_array, coordinate: str, dtype=None) -> NumpyBoxes
Source code in SaigeToolkit/data/dataclass/box.py
def __new__(cls, input_array, coordinate: str, dtype=None) -> NumpyBoxes:
    obj = np.array(input_array).view(cls)

    cls._check_boxes(obj)

    if dtype is not None:
        obj = obj.astype(dtype)
    obj.coordinate = coordinate
    return obj
__array_finalize__
__array_finalize__(obj)
Source code in SaigeToolkit/data/dataclass/box.py
def __array_finalize__(self, obj):
    if obj is None:
        return
    self.coordinate = getattr(obj, "coordinate", None)
__array_function__
__array_function__(func, types, args, kwargs)
Source code in SaigeToolkit/data/dataclass/box.py
def __array_function__(self, func, types, args, kwargs):
    def unwrap(e):
        return np.asarray(e) if isinstance(e, NumpyBoxes) else e

    def wrap(e, coordinate):
        return NumpyBoxes(e, coordinate) if isinstance(e, np.ndarray) else e

    def get_coordinate(args, kwargs):
        flat_args, _ = tree_flatten(args)
        flat_kwargs, _ = tree_flatten(kwargs)
        coordinates = [e.coordinate for e in flat_args + flat_kwargs if isinstance(e, NumpyBoxes)]

        assert len(set(coordinates)) == 1
        coordinate = coordinates[0]

        return coordinate

    kwargs = kwargs or {}
    ret = func(*tree_map(unwrap, args), **tree_map(unwrap, kwargs))
    coordinate = get_coordinate(args, kwargs)
    wrap_with_coordinate = functools.partial(wrap, coordinate=coordinate)
    ret = tree_map(wrap_with_coordinate, ret)

    return ret
convert_coordinate
convert_coordinate(coordinate: str) -> NumpyBoxes
Source code in SaigeToolkit/data/dataclass/box.py
def convert_coordinate(self, coordinate: str) -> NumpyBoxes:
    new_boxes: NumpyBoxes = convert_coordinate(self, self.coordinate, coordinate)
    new_boxes.coordinate = coordinate
    return new_boxes
to_numpy
to_numpy() -> ndarray
Source code in SaigeToolkit/data/dataclass/box.py
def to_numpy(self) -> np.ndarray:
    return np.array(self)
to_tensor
to_tensor() -> Tensor
Source code in SaigeToolkit/data/dataclass/box.py
def to_tensor(self) -> torch.Tensor:
    return torch.from_numpy(self.to_numpy())
_check_boxes staticmethod
_check_boxes(boxes: ndarray)
Source code in SaigeToolkit/data/dataclass/box.py
@staticmethod
def _check_boxes(boxes: np.ndarray):
    # 숫자가 아닌 값이 들어오는 경우
    if not np.issubdtype(boxes.dtype, np.number):
        raise BoxValueError

    # box position이 음수가 들어오는 경우
    if np.any(boxes < 0):
        raise BoxValueError

convert_coordinate

convert_coordinate(boxes: Union[ndarray, Tensor], source_coordinate: str, target_coordinate: str) -> Union[ndarray, Tensor]
Source code in SaigeToolkit/data/dataclass/box.py
def convert_coordinate(
    boxes: Union[np.ndarray, torch.Tensor],  # shape: (num_bboxes, 4), dtype: float
    source_coordinate: str,  # coordinate type of input boxes ["xyxy", "xywh", "ccwh"]
    target_coordinate: str,  # coordinate type want to convert ["xyxy", "xywh", "ccwh"]
) -> Union[np.ndarray, torch.Tensor]:
    def _to_xywh(boxes: NumpyBoxes, coordinate: str) -> NumpyBoxes:
        """some coordinate -> (left, top, width, height)"""
        if coordinate == "xywh":
            pass
        elif coordinate == "xyxy":
            boxes[:, [2, 3]] = boxes[:, [2, 3]] - boxes[:, [0, 1]]
        elif coordinate == "ccwh":
            boxes[:, [0, 1]] = boxes[:, [0, 1]] - (boxes[:, [2, 3]] / 2)
        else:
            raise NotImplementedError

        return boxes

    def _from_xywh(boxes: NumpyBoxes, coordinate: str) -> NumpyBoxes:
        """(left, top, width, height) -> some coordinate"""
        if coordinate == "xywh":
            pass
        elif coordinate == "xyxy":
            boxes[:, [2, 3]] = boxes[:, [2, 3]] + boxes[:, [0, 1]]
        elif coordinate == "ccwh":
            boxes[:, [0, 1]] = boxes[:, [0, 1]] + (boxes[:, [2, 3]] / 2)
        else:
            raise NotImplementedError

        return boxes

    if isinstance(boxes, np.ndarray):
        new_boxes = boxes.copy()
    elif isinstance(boxes, torch.Tensor):
        new_boxes = boxes.clone()

    if source_coordinate != target_coordinate:  # if you want to change the coordinate
        new_boxes = _to_xywh(new_boxes, source_coordinate)  # source coordinate -> xywh
        new_boxes = _from_xywh(new_boxes, target_coordinate)  # xywh -> target coordinate

    return new_boxes

test_init_box

test_init_box(box_data, coordinate) -> NumpyBoxes
Source code in SaigeToolkit/data/dataclass/box.py
def test_init_box(box_data, coordinate) -> NumpyBoxes:
    boxes = NumpyBoxes(box_data, coordinate)

    assert boxes.coordinate == coordinate
    assert boxes.dtype == np.float32
    assert boxes.tolist() == box_data

    return boxes

test_base_function

test_base_function(boxes: NumpyBoxes)
Source code in SaigeToolkit/data/dataclass/box.py
def test_base_function(boxes: NumpyBoxes):
    boxes = boxes + 3
    boxes = boxes * 3
    boxes = boxes - 3
    boxes = boxes / 3
    boxes = np.concatenate([boxes] * 4, axis=0)
    boxes = np.split(boxes, 4, axis=0)[0]
    boxes = boxes.astype(np.int32)

    assert boxes.dtype == np.int32

test_convert_coordinate

test_convert_coordinate(boxes: NumpyBoxes, coordinate, answer)
Source code in SaigeToolkit/data/dataclass/box.py
def test_convert_coordinate(boxes: NumpyBoxes, coordinate, answer):
    boxes = boxes.convert_coordinate(coordinate)

    assert boxes.coordinate == coordinate
    assert boxes.dtype == np.float32
    assert boxes.tolist() == answer

test_to_other_data

test_to_other_data(boxes: NumpyBoxes, answer)
Source code in SaigeToolkit/data/dataclass/box.py
def test_to_other_data(boxes: NumpyBoxes, answer):
    new_boxes = boxes.to_tensor()

    assert new_boxes.tolist() == answer

segment

BITMAP_PIXEL_VALUE module-attribute

BITMAP_PIXEL_VALUE = 1

Bitmap module-attribute

Bitmap = ndarray

Contour module-attribute

Contour = ndarray

Contours module-attribute

Contours = Sequence[Contour]

contours_area_methods module-attribute

contours_area_methods = {'exact': compute_contours_area_exact, 'fast': compute_contours_area_fast, 'fast_plus': compute_contours_area_fast_plus, 'fast_plusplus': compute_contours_area_fast_plusplus}

SegmentBox dataclass

SegmentBox(left: int, top: int, width: int, height: int, right: int, bottom: int)
left instance-attribute
left: int
top instance-attribute
top: int
width instance-attribute
width: int
height instance-attribute
height: int
right instance-attribute
right: int
bottom instance-attribute
bottom: int
from_xywh classmethod
from_xywh(left, top, width, height) -> SegmentBox
Source code in SaigeToolkit/data/dataclass/segment.py
@classmethod
def from_xywh(cls, left, top, width, height) -> SegmentBox:
    right = left + width
    bottom = top + height
    return cls(left, top, width, height, right, bottom)
from_xyxy classmethod
from_xyxy(left, top, right, bottom) -> SegmentBox
Source code in SaigeToolkit/data/dataclass/segment.py
@classmethod
def from_xyxy(cls, left, top, right, bottom) -> SegmentBox:
    width = right - left
    height = bottom - top
    return cls(left, top, width, height, right, bottom)

Segment

Segment(bounding_box: Optional[SegmentBox] = None, bitmap: Optional[ndarray] = None, contours: Optional[Contours] = None, class_index: Optional[int] = None)

Segment 타입을 정의합니다. (= SegmentedObject) Segmentation 라벨링 혹은 모델의 예측 결과로 나오는 연결된 픽셀 덩어리이며, 1개의 outer polygon과 여러개의 inner polygon (도넛 형태인 경우) 으로 구성된 오브젝트입니다.

해당 오브젝트를 표현하는 방식은 2가지가 존재하며, segment를 이용해 어떤 연산을 수행하는가에 따라 다른 표현 방식이 필요합니다. 1. bounding_box & bitmap: SegmentBox & np.ndarray 2. contours: Sequence[np.ndarray]

Segment 오브젝트를 생성하기 위해서는 1가지 표현의 데이터만 필요하고, 다른 표현이 필요한 연산의 경우 lazy 한 방식으로 해당 표현을 계산합니다.

Segment 오브젝트 생성 시 bounding_box, bitmap 과 contours가 모두 주어진 경우, 서로 일치하는지 확인하지 않습니다.

Segment 오브젝트 생성 시 bounding_box, bitmap 표현이 주어진 경우 모든 픽셀이 연결되어 있는지 확인하지 않습니다.

Parameters:

  • bounding_box (Optional[SegmentBox], default: None ) –

    description. Defaults to None.

  • bitmap (Optional[Bitmap], default: None ) –

    description. Defaults to None.

  • contours (Optional[Contours], default: None ) –

    description. Defaults to None.

  • class_index (Optional[int], default: None ) –

    description. Defaults to None.

Source code in SaigeToolkit/data/dataclass/segment.py
def __init__(
    self,
    bounding_box: Optional[SegmentBox] = None,
    bitmap: Optional[np.ndarray] = None,
    contours: Optional[Contours] = None,
    class_index: Optional[int] = None,
) -> None:
    if contours is None:
        assert (
            bitmap is not None and bounding_box is not None
        ), "Either `bitmap` + `bounding_box` or `contours` must be given"
    self._bounding_box = bounding_box
    self._bitmap = bitmap
    self._contours = contours
    self.class_index = class_index
_bounding_box instance-attribute
_bounding_box = bounding_box
_bitmap instance-attribute
_bitmap = bitmap
_contours instance-attribute
_contours = contours
class_index instance-attribute
class_index = class_index
contours property
contours: Contours
bounding_box property
bounding_box: SegmentBox
bitmap property
bitmap: Bitmap
from_xyhw_and_bitmap classmethod
from_xyhw_and_bitmap(bounding_box: SegmentBox, bitmap: Bitmap, class_index: Optional[int] = None, contours: Optional[Contours] = None, **ignore) -> Segment
Source code in SaigeToolkit/data/dataclass/segment.py
@classmethod
def from_xyhw_and_bitmap(
    cls,
    bounding_box: SegmentBox,
    bitmap: Bitmap,
    class_index: Optional[int] = None,
    contours: Optional[Contours] = None,
    **ignore,
) -> Segment:
    bounding_box = SegmentBox.from_xywh(*bounding_box)
    return cls(bounding_box=bounding_box, bitmap=bitmap, class_index=class_index, contours=contours)
from_any classmethod
from_any(segment: Union[Segment, Dict]) -> Segment
Source code in SaigeToolkit/data/dataclass/segment.py
@classmethod
def from_any(cls, segment: Union[Segment, Dict]) -> Segment:
    if isinstance(segment, cls):
        seg_object = segment
    elif "bitmap" in segment:
        seg_object = cls.from_xyhw_and_bitmap(**segment)
    elif "contours" in segment:
        seg_object = cls(
            contours=segment.get("contours"),
            class_index=segment.get("class_index", None),
        )
    else:
        raise NotImplementedError
    return seg_object

get_bounding_box_from_contours

get_bounding_box_from_contours(contours: Contours) -> SegmentBox

contours의 bounding box를 구합니다.

Source code in SaigeToolkit/data/dataclass/segment.py
def get_bounding_box_from_contours(contours: Contours) -> SegmentBox:
    """contours의 bounding box를 구합니다."""
    left, top, width, height = cv2.boundingRect(contours[0])
    return SegmentBox.from_xywh(left, top, width, height)

convert_contours_to_box_and_bitmap

convert_contours_to_box_and_bitmap(contours: Contours, bounding_box: Optional[SegmentBox] = None) -> Tuple[SegmentBox, Bitmap]

contours를 bounding_box 와 bitmap representation으로 변환합니다

Source code in SaigeToolkit/data/dataclass/segment.py
def convert_contours_to_box_and_bitmap(
    contours: Contours,
    bounding_box: Optional[SegmentBox] = None,
) -> Tuple[SegmentBox, Bitmap]:
    """contours를 bounding_box 와 bitmap representation으로 변환합니다"""
    if bounding_box is None:
        bounding_box = get_bounding_box_from_contours(contours)
    bitmap = cv2.drawContours(
        np.zeros((bounding_box.height, bounding_box.width), dtype=np.uint8),
        contours=contours,
        contourIdx=-1,
        color=BITMAP_PIXEL_VALUE,
        thickness=-1,
        offset=[-bounding_box.left, -bounding_box.top],
    )
    return bounding_box, bitmap

convert_box_and_bitmap_to_contours

convert_box_and_bitmap_to_contours(bitmap: Bitmap, bounding_box: Optional[SegmentBox] = None) -> Contours

bounding_box 와 bitmap을 contours representation으로 변환합니다

Source code in SaigeToolkit/data/dataclass/segment.py
def convert_box_and_bitmap_to_contours(
    bitmap: Bitmap,
    bounding_box: Optional[SegmentBox] = None,
) -> Contours:
    """bounding_box 와 bitmap을 contours representation으로 변환합니다"""
    polygons, hierarchy = cv2.findContours(bitmap, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    if not polygons:
        raise SegmentValueError
    parents = hierarchy[0, :, -1]
    outers = np.where(parents == -1)[0]
    if len(outers) != 1:
        raise SegmentValueError
    outer_idx = outers[0]
    contours = [polygons[outer_idx]]
    for idx_inner in np.where(parents == outer_idx)[0]:
        contours.append(polygons[idx_inner])
    if bounding_box is not None:
        contours = [contour + (bounding_box.left, bounding_box.top) for contour in contours]
    return contours

compute_box_intersection

compute_box_intersection(box1: SegmentBox, box2: SegmentBox) -> Optional[SegmentBox]

intersecting box를 계산합니다. 겹치지 않는 경우 None

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_box_intersection(box1: SegmentBox, box2: SegmentBox) -> Optional[SegmentBox]:
    """intersecting box를 계산합니다. 겹치지 않는 경우 None"""
    left = max(box1.left, box2.left)
    top = max(box1.top, box2.top)
    right = min(box1.right, box2.right)
    bottom = min(box1.bottom, box2.bottom)
    intersection = SegmentBox.from_xyxy(left, top, right, bottom)
    if intersection.width <= 0 or intersection.height <= 0:
        return None
    else:
        return intersection

to_segments

to_segments(segments: List[Union[Segment, Dict]]) -> List[Segment]

List[Union[Segment, Dict]]를을 List[Segment]들로 변환합니다

Source code in SaigeToolkit/data/dataclass/segment.py
def to_segments(segments: List[Union[Segment, Dict]]) -> List[Segment]:
    """List[Union[Segment, Dict]]를을 List[Segment]들로 변환합니다"""
    return [Segment.from_any(segment) for segment in segments]

merge_segments

merge_segments(segments: List[Segment]) -> Segment

여러 Segment들을 하나의 Segment로 합칩니다. (union)

Source code in SaigeToolkit/data/dataclass/segment.py
def merge_segments(segments: List[Segment]) -> Segment:
    """여러 Segment들을 하나의 Segment로 합칩니다. (union)"""
    if not segments:
        raise ValueError
    left = min(segment.bounding_box.left for segment in segments)
    top = min(segment.bounding_box.top for segment in segments)
    right = max(segment.bounding_box.right for segment in segments)
    bottom = max(segment.bounding_box.bottom for segment in segments)
    box = SegmentBox.from_xyxy(left, top, right, bottom)
    bitmap = np.zeros((box.height, box.width), dtype=np.uint8)
    for segment in segments:
        offset_x = segment.bounding_box.left - left
        offset_y = segment.bounding_box.top - top
        bitmap[
            offset_y : offset_y + segment.bounding_box.height,
            offset_x : offset_x + segment.bounding_box.width,
        ][segment.bitmap > 0] = BITMAP_PIXEL_VALUE
    return Segment(bounding_box=box, bitmap=bitmap, class_index=segments[0].class_index)

compute_contours_area_exact

compute_contours_area_exact(contours: Contours) -> int

bounding box 크기의 이미지에 contour를 그린 뒤 픽셀 개수 카운트

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_contours_area_exact(contours: Contours) -> int:
    """bounding box 크기의 이미지에 contour를 그린 뒤 픽셀 개수 카운트"""
    _, bitmap = convert_contours_to_box_and_bitmap(contours=contours)
    return int(np.sum(bitmap) / BITMAP_PIXEL_VALUE)

compute_contours_area_fast

compute_contours_area_fast(contours: Contours) -> int

cv2.contourArea() 로 contour 면적 계산: contour 테두리를 0.5 픽셀 제외하고 계산되는 듯. average error: 17.08%

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_contours_area_fast(contours: Contours) -> int:
    """cv2.contourArea() 로 contour 면적 계산: contour 테두리를 0.5 픽셀 제외하고 계산되는 듯.
    average error: 17.08%
    """
    outer = contours[0]
    area = cv2.contourArea(outer)
    for inner in contours[1:]:
        area -= cv2.contourArea(inner)
    return max(int(area), 1)

compute_contours_area_fast_plus

compute_contours_area_fast_plus(contours: Contours) -> int

cv2.contourArea() 로 contour 면적 계산, outer의 경우 cv2.arcLength() 더해줌 average error: 1.50%

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_contours_area_fast_plus(contours: Contours) -> int:
    """cv2.contourArea() 로 contour 면적 계산, outer의 경우 cv2.arcLength() 더해줌
    average error: 1.50%

    """
    outer = contours[0]
    area = cv2.contourArea(outer) + 0.5 * cv2.arcLength(outer, closed=True)
    for inner in contours[1:]:
        area -= cv2.contourArea(inner)
    return max(int(area), 1)

compute_contours_area_fast_plusplus

compute_contours_area_fast_plusplus(contours: Contours) -> int

cv2.contourArea() + cv2.arcLength() 로 contour 면적 계산 average error: 1.58%

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_contours_area_fast_plusplus(contours: Contours) -> int:
    """cv2.contourArea() + cv2.arcLength() 로 contour 면적 계산
    average error: 1.58%
    """
    outer = contours[0]
    area = cv2.contourArea(outer) + 0.5 * cv2.arcLength(outer, closed=True)
    for inner in contours[1:]:
        area -= cv2.contourArea(inner) + 0.5 * cv2.arcLength(inner, closed=True)
    return max(int(area), 1)

compute_contours_area

compute_contours_area(contours: Contours, method: str) -> int

contours의 면적을 계산합니다. 참고: https://www.notion.so/65e5a56b2b8744bea087b1a0d2f9bfbf

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_contours_area(contours: Contours, method: str) -> int:
    """contours의 면적을 계산합니다.
    참고: https://www.notion.so/65e5a56b2b8744bea087b1a0d2f9bfbf
    """
    if method not in contours_area_methods:
        raise KeyError
    return contours_area_methods[method](contours)

compute_segment_intersection_area

compute_segment_intersection_area(segment1: Segment, segment2: Segment) -> int

두 Segment 사이의 겹치는 영역 넓이를 계산합니다. bounding_box를 이용해 겹치는 박스를 먼저 계산한 뒤 해당 박스만 잘라서 겹치는 픽셀 수를 셉니다.

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_segment_intersection_area(segment1: Segment, segment2: Segment) -> int:
    """두 Segment 사이의 겹치는 영역 넓이를 계산합니다.
    bounding_box를 이용해 겹치는 박스를 먼저 계산한 뒤 해당 박스만 잘라서 겹치는 픽셀 수를 셉니다.
    """
    intersection_box = compute_box_intersection(segment1.bounding_box, segment2.bounding_box)
    if intersection_box is None:
        return 0

    left = intersection_box.left - segment1.bounding_box.left
    top = intersection_box.top - segment1.bounding_box.top
    bitmap1_intersection = segment1.bitmap[
        top : top + intersection_box.height,
        left : left + intersection_box.width,
    ]

    left = intersection_box.left - segment2.bounding_box.left
    top = intersection_box.top - segment2.bounding_box.top
    bitmap2_intersection = segment2.bitmap[
        top : top + intersection_box.height,
        left : left + intersection_box.width,
    ]
    return int(np.sum(bitmap1_intersection * bitmap2_intersection))

compute_segment_score

compute_segment_score(segment: Segment, scoremap: ndarray, method: str = 'mean', return_float: bool = False) -> Union[float, int]

scoremap 중 segment 영역의 score를 대표하는 값을 계산합니다.

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_segment_score(
    segment: Segment,
    scoremap: np.ndarray,
    method: str = "mean",
    return_float: bool = False,
) -> Union[float, int]:
    """scoremap 중 segment 영역의 score를 대표하는 값을 계산합니다."""
    bbox = segment.bounding_box
    scoremap_crop = scoremap[bbox.top : bbox.top + bbox.height, bbox.left : bbox.left + bbox.width]
    scoremap_masked = scoremap_crop[segment.bitmap > 0]

    if method == "mean":
        score = scoremap_masked.mean()
    elif method == "max":
        score = scoremap_masked.max()
    else:
        raise ValueError

    if (not return_float) and np.issubdtype(scoremap.dtype, np.integer):
        return int(score)
    else:
        return float(score)

compute_segment_properties_and_apply_threshold

compute_segment_properties_and_apply_threshold(segment: Segment, calc_area_and_apply_threshold: bool = False, area_method: str = 'fast_plus', area_threshold: int = 0, calc_score_and_apply_threshold: bool = False, scoremap: Optional[ndarray] = None, score_method: str = 'mean', score_threshold: Union[float, int] = 0, score_as_float: bool = False) -> Optional[Dict]

API 계산 결과로 필요한 Segment의 property 들을 계산하고 threshold를 적용합니다. threshold에 걸리지 않는 경우 각 property들의 Dict를, threshold 에 걸리는 경우 None을 리턴합니다. 계산 로직은 다음과 순서로 적용됩니다.

  1. calc_object_area_and_apply_threshold=True 인 경우
    1. area 계산
    2. area_threshold 적용
  2. calc_object_score_and_apply_threshold=True 인 경우
    1. score 계산
    2. score_threshold 적용
  3. 아래 항목들 계산
    1. bounding_box
    2. bounding_rotated_box
    3. fitted_ellipse

Parameters:

Returns:

  • Optional[Dict]

    Optional[Dict]: segment property dictionary. threshold에 걸려서 필터링 된 경우 None

Source code in SaigeToolkit/data/dataclass/segment.py
def compute_segment_properties_and_apply_threshold(
    segment: Segment,
    calc_area_and_apply_threshold: bool = False,
    area_method: str = "fast_plus",
    area_threshold: int = 0,
    calc_score_and_apply_threshold: bool = False,
    scoremap: Optional[np.ndarray] = None,
    score_method: str = "mean",
    score_threshold: Union[float, int] = 0,
    score_as_float: bool = False,
) -> Optional[Dict]:
    """API 계산 결과로 필요한 Segment의 property 들을 계산하고 threshold를 적용합니다.
    threshold에 걸리지 않는 경우 각 property들의 Dict를, threshold 에 걸리는 경우 None을 리턴합니다.
    계산 로직은 다음과 순서로 적용됩니다.

    1. `calc_object_area_and_apply_threshold=True` 인 경우
        1. area 계산
        2. area_threshold 적용
    2. `calc_object_score_and_apply_threshold=True` 인 경우
        1. score 계산
        2. score_threshold 적용
    3. 아래 항목들 계산
        1. bounding_box
        2. bounding_rotated_box
        3. fitted_ellipse

    Args:
        segment (Segment): segment

    Returns:
        Optional[Dict]: segment property dictionary. threshold에 걸려서 필터링 된 경우 None
    """
    properties = {}

    # area
    if calc_area_and_apply_threshold:
        area = compute_contours_area(contours=segment.contours, method=area_method)
        if area < area_threshold:
            return None
        properties["area"] = area

    # score
    if calc_score_and_apply_threshold:
        assert scoremap is not None
        score = compute_segment_score(
            segment=segment,
            scoremap=scoremap,
            method=score_method,
            return_float=score_as_float,
        )
        if score < score_threshold:
            return None
        properties["score"] = score

    # default properties
    if segment.class_index is not None:
        properties["class_index"] = segment.class_index
    properties["contours"] = segment.contours
    outer = segment.contours[0]

    # bounding_box: v1 legacy logic
    bbox = segment.bounding_box
    properties["bounding_box"] = [bbox.left, bbox.top, bbox.width, bbox.height]

    # bounding_rot_box: v1 legacy logic
    ((center_x, center_y), (width, height), angle) = cv2.minAreaRect(outer)
    rot_box_points = cv2.boxPoints(((center_x, center_y), (width, height), angle)).tolist()
    bounding_rot_box = [[center_x, center_y], [width, height], angle, rot_box_points]
    properties["bounding_rot_box"] = bounding_rot_box

    # fitted_ellipse: v1 legacy logic
    fitted_ellipse = bounding_rot_box[:3]
    if len(outer) >= 5:
        ((center_x, center_y), (width, height), angle) = cv2.fitEllipse(outer)
        if width > 1e-5 and height > 1e-5:
            fitted_ellipse = [[center_x, center_y], [width, height], angle]
    properties["fitted_ellipse"] = fitted_ellipse

    return properties

scale_segments_properties

scale_segments_properties(segments_properties: List[Dict], scale: Sequence[float]) -> List[Dict]
Source code in SaigeToolkit/data/dataclass/segment.py
def scale_segments_properties(segments_properties: List[Dict], scale: Sequence[float]) -> List[Dict]:
    return [
        scale_segment_properties(segment_properties, scale) for segment_properties in segments_properties
    ]

scale_segment_properties

scale_segment_properties(properties: Dict, scale: Sequence[float]) -> Dict
Source code in SaigeToolkit/data/dataclass/segment.py
def scale_segment_properties(properties: Dict, scale: Sequence[float]) -> Dict:
    scale_w, scale_h = scale[:2]

    scaled_properties = properties.copy()

    if "area" in scaled_properties:
        scaled_properties["area"] = int(scale_w * scale_h * scaled_properties["area"])

    if "contours" in scaled_properties:
        scaled_properties["contours"] = [
            np.round((contour * scale)).astype(contour.dtype)
            for contour in scaled_properties["contours"]
        ]

    if "bounding_box" in scaled_properties:
        left_, top_, width_, height_ = scaled_properties["bounding_box"]
        scaled_properties["bounding_box"] = [
            int(left_ * scale_w),
            int(top_ * scale_h),
            int(width_ * scale_w),
            int(height_ * scale_h),
        ]

    if "bounding_rot_box" in scaled_properties:
        ((center_x_, center_y_), (width_, height_), angle_, rot_box_points_) = scaled_properties[
            "bounding_rot_box"
        ]
        scaled_properties["bounding_rot_box"] = [
            [center_x_ * scale_w, center_y_ * scale_h],
            [width_ * scale_w, height_ * scale_h],
            angle_,
            [[rot_box[0] * scale_w, rot_box[1] * scale_h] for rot_box in rot_box_points_],
        ]

    if "fitted_ellipse" in scaled_properties:
        ((center_x_, center_y_), (width_, height_), angle_) = scaled_properties["fitted_ellipse"]
        scaled_properties["fitted_ellipse"] = [
            [center_x_ * scale_w, center_y_ * scale_h],
            [width_ * scale_w, height_ * scale_h],
            angle_,
        ]

    return scaled_properties

translate_segments_properties

translate_segments_properties(segments_properties: List[Dict], left: int, top: int, **kwargs) -> List[Dict]
Source code in SaigeToolkit/data/dataclass/segment.py
def translate_segments_properties(
    segments_properties: List[Dict],
    left: int,
    top: int,
    **kwargs,
) -> List[Dict]:
    return [
        translate_segment_properties(segment_properties, left, top, **kwargs)
        for segment_properties in segments_properties
    ]

translate_segment_properties

translate_segment_properties(properties: Dict, left: int, top: int, **kwargs) -> Dict
Source code in SaigeToolkit/data/dataclass/segment.py
def translate_segment_properties(
    properties: Dict,
    left: int,
    top: int,
    **kwargs,
) -> Dict:
    translated_properties = properties.copy()

    if "contours" in translated_properties:
        translated_properties["contours"] = [
            (contour + (left, top)).astype(contour.dtype)
            for contour in translated_properties["contours"]
        ]

    if "bounding_box" in translated_properties:
        left_, top_, width_, height_ = translated_properties["bounding_box"]
        translated_properties["bounding_box"] = [
            int(left_ + left),
            int(top_ + top),
            int(width_),
            int(height_),
        ]

    if "bounding_rot_box" in translated_properties:
        ((center_x_, center_y_), (width_, height_), angle_, rot_box_points_) = translated_properties[
            "bounding_rot_box"
        ]
        translated_properties["bounding_rot_box"] = [
            [center_x_ + left, center_y_ + top],
            [width_, height_],
            angle_,
            [[rot_box[0] + left, rot_box[1] + top] for rot_box in rot_box_points_],
        ]

    if "fitted_ellipse" in translated_properties:
        ((center_x_, center_y_), (width_, height_), angle_) = translated_properties["fitted_ellipse"]
        translated_properties["fitted_ellipse"] = [
            [center_x_ + left, center_y_ + top],
            [width_, height_],
            angle_,
        ]

    return translated_properties