mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-16 01:40:46 +08:00
https://github.com/MaaAssistantArknights/MaaAssistantArknights/actions/runs/20508148724 [skip changelog]
248 lines
7.6 KiB
Python
248 lines
7.6 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Optional
|
||
|
||
|
||
# 描述符基类,用于类型检查
|
||
class Typed(object):
|
||
def __init__(self, name, expected_type):
|
||
self.name = name
|
||
self.expected_type = expected_type
|
||
|
||
def __get__(self, instance, cls):
|
||
if instance is None:
|
||
return self
|
||
else:
|
||
return instance.__dict__[self.name]
|
||
|
||
def __set__(self, instance, value):
|
||
if not isinstance(value, self.expected_type):
|
||
raise TypeError("Expected " + str(self.expected_type))
|
||
instance.__dict__[self.name] = value
|
||
|
||
def __delete__(self, instance):
|
||
del instance.__dict__[self.name]
|
||
|
||
|
||
# 类装饰器,用来装饰自定义的类, 且将类属性设置为某特定类型的Typed实例
|
||
def typeassert(**kwargs):
|
||
def decorate(cls):
|
||
for name, expected_type in kwargs.items():
|
||
# 将名字name和期望类型创建一个Typed实例
|
||
iTyped = Typed(name, expected_type)
|
||
# 将名字name和iType,设置到cls中,作为对应cls.__dict__中的key,value
|
||
setattr(cls, name, iTyped)
|
||
return cls
|
||
|
||
return decorate
|
||
|
||
|
||
@typeassert(
|
||
width=(float, int),
|
||
height=(float, int),
|
||
x=(float, int),
|
||
y=(float, int),
|
||
zoom=(float, int),
|
||
)
|
||
class Roi(object):
|
||
def __init__(
|
||
self,
|
||
width: float,
|
||
height: float,
|
||
x: float = 0,
|
||
y: float = 0,
|
||
parent: Optional[Roi] = None,
|
||
zoom: float = 1,
|
||
) -> None:
|
||
"""感兴趣区域
|
||
|
||
相对于父 Roi,由宽、高、左上角顶点坐标所描述的一个感兴趣区域
|
||
|
||
Args:
|
||
width: float
|
||
Roi 的长度,当值 <= 0 且不为根 Roi 时,自动计算为 parent.width - x
|
||
height: float
|
||
Roi 的高度,当值 <= 0 且不为根 Roi 时,自动计算为 parent.height - y
|
||
x: float = 0
|
||
Roi 左上角顶点的 x 坐标,当值小于 0 时,置为0
|
||
y: float = 0
|
||
Roi 左上角顶点的 y 坐标,当值小于 0 时,置为0
|
||
parent: Roi = None
|
||
Roi 的父 Roi
|
||
zoom: float = 1
|
||
缩放倍数(标记宽、高、左上角顶点坐标相对父 Roi 的倍数)
|
||
使用 getZoomRoi 获得经过缩放后的 Roi
|
||
|
||
Returns:
|
||
Roi
|
||
"""
|
||
self.x = x
|
||
self.y = y
|
||
self.width = width
|
||
self.height = height
|
||
self.zoom = zoom # 加了缩放系数之后,就开始烧脑了(
|
||
self.__parent = parent
|
||
self.__check()
|
||
|
||
def __check(self) -> None:
|
||
if self.isRoot:
|
||
assert self.x == 0, self.x
|
||
assert self.y == 0, self.y
|
||
assert self.width > 0, self.width
|
||
assert self.height > 0, self.height
|
||
assert self.zoom == 1, self.zoom
|
||
return
|
||
self.x = max(0, min(self.x, self.parent.width * self.zoom - 1))
|
||
self.y = max(0, min(self.y, self.parent.height * self.zoom - 1))
|
||
if self.width <= 0:
|
||
self.width = self.parent.width * self.zoom - self.x
|
||
assert self.width > 0, self.width
|
||
if self.x + self.width > self.parent.width * self.zoom:
|
||
self.width = min(self.width, self.parent.width * self.zoom)
|
||
self.x = self.parent.width * self.zoom - self.width
|
||
assert self.x >= 0, self.x
|
||
if self.height <= 0:
|
||
self.height = self.parent.height * self.zoom - self.y
|
||
assert self.height > 0, self.height
|
||
if self.y + self.height > self.parent.height * self.zoom:
|
||
self.height = min(self.height, self.parent.height * self.zoom)
|
||
self.y = self.parent.height * self.zoom - self.height
|
||
assert self.y >= 0, self.y
|
||
|
||
@property
|
||
def parent(self) -> Roi:
|
||
"""Roi 父节点"""
|
||
return self.__parent # type: ignore
|
||
|
||
@property
|
||
def isRoot(self) -> bool:
|
||
"""是否是根节点"""
|
||
return self.parent is None
|
||
|
||
@property
|
||
def point(self) -> tuple[int, int]:
|
||
"""Roi 左上角顶点坐标 (x, y)"""
|
||
return (int(self.x), int(self.y))
|
||
|
||
@property
|
||
def size(self) -> tuple[int, int]:
|
||
"""Roi 大小 (width, height)"""
|
||
return (int(self.width), int(self.height))
|
||
|
||
@property
|
||
def rectangle(self) -> list[int]:
|
||
"""Roi 区域 [ x, y, width, height ],由左上角顶点 (x, y) 和宽高描述"""
|
||
return [int(self.x), int(self.y), int(self.width), int(self.height)]
|
||
|
||
@property
|
||
def rectanglePoints(self) -> tuple[tuple[int, int], tuple[int, int]]:
|
||
"""Roi 区域 ( x1, y1, x2, y2 ),由左上角顶点 (x1, y1) 和右下角顶点 (x2, y2) 描述"""
|
||
return (
|
||
(int(self.x), int(self.y)),
|
||
(int(self.x + self.width - 1), int(self.y + self.height - 1)),
|
||
)
|
||
|
||
def getZoomRoi(self, zoom: float) -> Roi:
|
||
"""
|
||
获取相对于 parent,基于当前Roi,从 zoom 放大的一个新 Roi
|
||
|
||
如果 self 为 root,则相对于 root
|
||
|
||
Args:
|
||
zoom: float
|
||
缩放倍数
|
||
|
||
Returns:
|
||
Roi
|
||
"""
|
||
parent = self if self.isRoot else self.parent
|
||
return self.__class__(
|
||
self.width * zoom,
|
||
self.height * zoom,
|
||
self.x * zoom,
|
||
self.y * zoom,
|
||
parent,
|
||
zoom,
|
||
)
|
||
|
||
def getCropRoi(self, x: float, y: float) -> Roi:
|
||
"""
|
||
获取相对于 parent,基于当前Roi,从坐标计算的一个新 Roi
|
||
|
||
如果 self 为 root,则相对于 root
|
||
|
||
Args:
|
||
x: float
|
||
x 轴坐标
|
||
y: float
|
||
y 轴坐标
|
||
|
||
Returns:
|
||
Roi
|
||
"""
|
||
parent = self if self.isRoot else self.parent
|
||
if x > self.x:
|
||
width = 1 + x - self.x
|
||
x = self.x
|
||
else:
|
||
width = 1 - x + self.x
|
||
if y > self.y:
|
||
height = 1 + y - self.y
|
||
y = self.y
|
||
else:
|
||
height = 1 - y + self.y
|
||
return self.__class__(width, height, x, y, parent, self.zoom)
|
||
|
||
def getRoiFromParent(self) -> Roi:
|
||
"""
|
||
获取相对于 parent.parent,基于当前Roi,从父 Roi 坐标计算的一个新 Roi
|
||
|
||
若 self 为 root,则返回 self
|
||
|
||
Returns:
|
||
Roi
|
||
"""
|
||
if self.isRoot:
|
||
return self
|
||
if self.parent.isRoot and self.zoom == 1.0:
|
||
return self
|
||
x, y = self.parent.point
|
||
p = self.parent if self.parent.isRoot else self.parent.parent
|
||
return self.__class__(
|
||
self.width / self.zoom,
|
||
self.height / self.zoom,
|
||
x + self.x / self.zoom,
|
||
y + self.y / self.zoom,
|
||
p,
|
||
self.parent.zoom,
|
||
)
|
||
|
||
def getRoiInRoot(self) -> Roi:
|
||
"""
|
||
获取相对于 root,基于 self,从 root 坐标计算的一个新 Roi
|
||
|
||
若 self 为 root,则返回 self
|
||
|
||
Returns:
|
||
Roi
|
||
"""
|
||
roi = self.getRoiFromParent()
|
||
return self if self is roi else roi.getRoiInRoot()
|
||
|
||
def copy(self, parent: Optional[Roi] = None, zoom: Optional[float] = None) -> Roi:
|
||
"""
|
||
深复制 Roi
|
||
|
||
Args:
|
||
parent: Roi = None
|
||
父节点
|
||
zoom: float = None
|
||
缩放倍数
|
||
|
||
Returns:
|
||
Roi
|
||
"""
|
||
parent = self.parent if parent is None else parent
|
||
zoom = self.zoom if zoom is None else zoom
|
||
return self.__class__(self.width, self.height, self.x, self.y, parent, zoom)
|