Files

248 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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__中的keyvalue
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)