Skip to content

Commit 2afd2a6

Browse files
authored
Re-organize the elements to individual files (#62)
* remove the old elements * migrate to the new elements * remove from_series * Add additional load apis to init * Update element tests
1 parent c6a5c6f commit 2afd2a6

File tree

9 files changed

+708
-714
lines changed

9 files changed

+708
-714
lines changed

src/layoutparser/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@
2020
)
2121

2222
from .io import (
23-
load_json
23+
load_json,
24+
load_dict,
25+
load_csv,
26+
load_dataframe
2427
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from .base import BaseCoordElement, BaseLayoutElement
2+
from .layout_elements import (
3+
Interval,
4+
Rectangle,
5+
Quadrilateral,
6+
TextBlock,
7+
ALL_BASECOORD_ELEMENTS,
8+
BASECOORD_ELEMENT_NAMEMAP,
9+
BASECOORD_ELEMENT_INDEXMAP,
10+
)
11+
from .layout import Layout

src/layoutparser/elements/base.py

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
from typing import List, Dict, Dict, Any
2+
from abc import ABC, abstractmethod
3+
from copy import copy
4+
5+
class BaseLayoutElement:
6+
def set(self, inplace=False, **kwargs):
7+
8+
obj = self if inplace else copy(self)
9+
var_dict = vars(obj)
10+
for key, val in kwargs.items():
11+
if key in var_dict:
12+
var_dict[key] = val
13+
elif f"_{key}" in var_dict:
14+
var_dict[f"_{key}"] = val
15+
else:
16+
raise ValueError(f"Unknown attribute name: {key}")
17+
18+
return obj
19+
20+
def __repr__(self):
21+
22+
info_str = ", ".join([f"{key}={val}" for key, val in vars(self).items()])
23+
return f"{self.__class__.__name__}({info_str})"
24+
25+
def __eq__(self, other):
26+
27+
if other.__class__ is not self.__class__:
28+
return False
29+
30+
return vars(self) == vars(other)
31+
32+
33+
class BaseCoordElement(ABC, BaseLayoutElement):
34+
@property
35+
@abstractmethod
36+
def _name(self) -> str:
37+
"""The name of the class"""
38+
pass
39+
40+
@property
41+
@abstractmethod
42+
def _features(self) -> List[str]:
43+
"""A list of features names used for initializing the class object"""
44+
pass
45+
46+
#######################################################################
47+
######################### Layout Properties #########################
48+
#######################################################################
49+
50+
@property
51+
@abstractmethod
52+
def width(self):
53+
pass
54+
55+
@property
56+
@abstractmethod
57+
def height(self):
58+
pass
59+
60+
@property
61+
@abstractmethod
62+
def coordinates(self):
63+
pass
64+
65+
@property
66+
@abstractmethod
67+
def points(self):
68+
pass
69+
70+
@property
71+
@abstractmethod
72+
def area(self):
73+
pass
74+
75+
#######################################################################
76+
### Geometric Relations (relative to, condition on, and is in) ###
77+
#######################################################################
78+
79+
@abstractmethod
80+
def condition_on(self, other):
81+
"""
82+
Given the current element in relative coordinates to another element which is in absolute coordinates,
83+
generate a new element of the current element in absolute coordinates.
84+
85+
Args:
86+
other (:obj:`BaseCoordElement`):
87+
The other layout element involved in the geometric operations.
88+
89+
Raises:
90+
Exception: Raise error when the input type of the other element is invalid.
91+
92+
Returns:
93+
:obj:`BaseCoordElement`:
94+
The BaseCoordElement object of the original element in the absolute coordinate system.
95+
"""
96+
97+
pass
98+
99+
@abstractmethod
100+
def relative_to(self, other):
101+
"""
102+
Given the current element and another element both in absolute coordinates,
103+
generate a new element of the current element in relative coordinates to the other element.
104+
105+
Args:
106+
other (:obj:`BaseCoordElement`): The other layout element involved in the geometric operations.
107+
108+
Raises:
109+
Exception: Raise error when the input type of the other element is invalid.
110+
111+
Returns:
112+
:obj:`BaseCoordElement`:
113+
The BaseCoordElement object of the original element in the relative coordinate system.
114+
"""
115+
116+
pass
117+
118+
@abstractmethod
119+
def is_in(self, other, soft_margin={}, center=False):
120+
"""
121+
Identify whether the current element is within another element.
122+
123+
Args:
124+
other (:obj:`BaseCoordElement`):
125+
The other layout element involved in the geometric operations.
126+
soft_margin (:obj:`dict`, `optional`, defaults to `{}`):
127+
Enlarge the other element with wider margins to relax the restrictions.
128+
center (:obj:`bool`, `optional`, defaults to `False`):
129+
The toggle to determine whether the center (instead of the four corners)
130+
of the current element is in the other element.
131+
132+
Returns:
133+
:obj:`bool`: Returns `True` if the current element is in the other element and `False` if not.
134+
"""
135+
136+
pass
137+
138+
#######################################################################
139+
################# Shape Operations (intersect, union) ################
140+
#######################################################################
141+
142+
@abstractmethod
143+
def intersect(self, other: "BaseCoordElement", strict: bool = True):
144+
"""Intersect the current shape with the other object, with operations defined in
145+
:doc:`../notes/shape_operations`.
146+
"""
147+
148+
@abstractmethod
149+
def union(self, other: "BaseCoordElement", strict: bool = True):
150+
"""Union the current shape with the other object, with operations defined in
151+
:doc:`../notes/shape_operations`.
152+
"""
153+
154+
#######################################################################
155+
############### Geometric Operations (pad, shift, scale) ##############
156+
#######################################################################
157+
158+
@abstractmethod
159+
def pad(self, left=0, right=0, top=0, bottom=0, safe_mode=True):
160+
"""Pad the layout element on the four sides of the polygon with the user-defined pixels. If
161+
safe_mode is set to True, the function will cut off the excess padding that falls on the negative
162+
side of the coordinates.
163+
164+
Args:
165+
left (:obj:`int`, `optional`, defaults to 0): The number of pixels to pad on the upper side of the polygon.
166+
right (:obj:`int`, `optional`, defaults to 0): The number of pixels to pad on the lower side of the polygon.
167+
top (:obj:`int`, `optional`, defaults to 0): The number of pixels to pad on the left side of the polygon.
168+
bottom (:obj:`int`, `optional`, defaults to 0): The number of pixels to pad on the right side of the polygon.
169+
safe_mode (:obj:`bool`, `optional`, defaults to True): A bool value to toggle the safe_mode.
170+
171+
Returns:
172+
:obj:`BaseCoordElement`: The padded BaseCoordElement object.
173+
"""
174+
175+
pass
176+
177+
@abstractmethod
178+
def shift(self, shift_distance=0):
179+
"""
180+
Shift the layout element by user specified amounts on x and y axis respectively. If shift_distance is one
181+
numeric value, the element will by shifted by the same specified amount on both x and y axis.
182+
183+
Args:
184+
shift_distance (:obj:`numeric` or :obj:`Tuple(numeric)` or :obj:`List[numeric]`):
185+
The number of pixels used to shift the element.
186+
187+
Returns:
188+
:obj:`BaseCoordElement`: The shifted BaseCoordElement of the same shape-specific class.
189+
"""
190+
191+
pass
192+
193+
@abstractmethod
194+
def scale(self, scale_factor=1):
195+
"""
196+
Scale the layout element by a user specified amount on x and y axis respectively. If scale_factor is one
197+
numeric value, the element will by scaled by the same specified amount on both x and y axis.
198+
199+
Args:
200+
scale_factor (:obj:`numeric` or :obj:`Tuple(numeric)` or :obj:`List[numeric]`): The amount for downscaling or upscaling the element.
201+
202+
Returns:
203+
:obj:`BaseCoordElement`: The scaled BaseCoordElement of the same shape-specific class.
204+
"""
205+
206+
pass
207+
208+
#######################################################################
209+
################################# MISC ################################
210+
#######################################################################
211+
212+
@abstractmethod
213+
def crop_image(self, image):
214+
"""
215+
Crop the input image according to the coordinates of the element.
216+
217+
Args:
218+
image (:obj:`Numpy array`): The array of the input image.
219+
220+
Returns:
221+
:obj:`Numpy array`: The array of the cropped image.
222+
"""
223+
224+
pass
225+
226+
#######################################################################
227+
########################## Import and Export ##########################
228+
#######################################################################
229+
230+
def to_dict(self) -> Dict[str, Any]:
231+
"""
232+
Generate a dictionary representation of the current object:
233+
{
234+
"block_type": <"interval", "rectangle", "quadrilateral"> ,
235+
"non_empty_block_attr1": value1,
236+
...
237+
}
238+
"""
239+
240+
data = {
241+
key: getattr(self, key)
242+
for key in self._features
243+
if getattr(self, key) is not None
244+
}
245+
data["block_type"] = self._name
246+
return data
247+
248+
@classmethod
249+
def from_dict(cls, data: Dict[str, Any]) -> "BaseCoordElement":
250+
"""Initialize an instance based on the dictionary representation
251+
252+
Args:
253+
data (:obj:`dict`): The dictionary representation of the object
254+
"""
255+
256+
assert (
257+
cls._name == data["block_type"]
258+
), f"Incompatible block types {data['block_type']}"
259+
260+
return cls(**{f: data[f] for f in cls._features})
261+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class NotSupportedShapeError(Exception):
2+
"""For now (v0.2), if the created shape might be a polygon (shapes with more than 4 vertices),
3+
layoutparser will raise NotSupportedShapeError. It is expected to be fixed in the future versions.
4+
See
5+
:ref:`shape_operations:problems-related-to-the-quadrilateral-class`.
6+
"""
7+
8+
9+
class InvalidShapeError(Exception):
10+
"""For shape operations like intersection of union, lp will raise the InvalidShapeError when
11+
invalid shapes are created (e.g., intersecting a rectangle and an interval).
12+
"""

0 commit comments

Comments
 (0)