Merge in RR/cv-analysis from evaluate_layout_detection to master
Squashed commit of the following:
commit 8ec2f69fc61d1e15bd502b0a2c1f720cbec2b34e
Author: llocarnini <lillian.locarnini@iqser.com>
Date: Tue Aug 23 15:07:21 2022 +0200
repaired is_not_included() logic (did drop the outer rectangle, not the included)
commit 97be081d1e60989313924ceac0bfb3062229411e
Merge: 2c28fa2 2b5c4f1
Author: llocarnini <lillian.locarnini@iqser.com>
Date: Tue Aug 23 14:28:14 2022 +0200
Merge branch 'master' of ssh://git.iqser.com:2222/rr/cv-analysis into evaluate_layout_detection
commit 2c28fa280b7eff922c715245fffe69702c7e6742
Author: llocarnini <lillian.locarnini@iqser.com>
Date: Tue Aug 23 13:50:17 2022 +0200
del print statements
commit c60121fc4faebc5de556ec0ab7a3af4f815f7ce1
Author: llocarnini <lillian.locarnini@iqser.com>
Date: Mon Aug 22 10:51:52 2022 +0200
few changes to connect_rects.py
commit a99719905d58cbe856fa020177abd7e317c1d072
Author: llocarnini <lillian.locarnini@iqser.com>
Date: Thu Aug 18 08:37:12 2022 +0200
layout parsing improved with connect_rects.py
commit d693688a0f0d63395cfd36645de7b3417f64de30
Author: llocarnini <lillian.locarnini@iqser.com>
Date: Tue Aug 2 09:31:19 2022 +0200
removed vizlogger instances
132 lines
4.3 KiB
Python
132 lines
4.3 KiB
Python
from json import dumps
|
|
|
|
from typing import Iterable
|
|
import numpy as np
|
|
from funcy import identity
|
|
|
|
|
|
class Rectangle:
|
|
def __init__(self, x1=None, y1=None, w=None, h=None, x2=None, y2=None, indent=4, format="xywh", discrete=True):
|
|
make_discrete = int if discrete else identity
|
|
|
|
try:
|
|
self.x1 = make_discrete(x1)
|
|
self.y1 = make_discrete(y1)
|
|
self.w = make_discrete(w) if w else make_discrete(x2 - x1)
|
|
self.h = make_discrete(h) if h else make_discrete(y2 - y1)
|
|
self.x2 = make_discrete(x2) if x2 else self.x1 + self.w
|
|
self.y2 = make_discrete(y2) if y2 else self.y1 + self.h
|
|
assert np.isclose(self.x1 + self.w, self.x2)
|
|
assert np.isclose(self.y1 + self.h, self.y2)
|
|
self.indent = indent
|
|
self.format = format
|
|
except Exception as err:
|
|
raise Exception("x1, y1, (w|x2), and (h|y2) must be defined.") from err
|
|
|
|
def json_xywh(self):
|
|
return {"x": self.x1, "y": self.y1, "width": self.w, "height": self.h}
|
|
|
|
def json_xyxy(self):
|
|
return {"x1": self.x1, "y1": self.y1, "x2": self.x2, "y2": self.y2}
|
|
|
|
def json_full(self):
|
|
# TODO: can we make all coords x0, y0 based? :)
|
|
return {
|
|
"x0": self.x1,
|
|
"y0": self.y1,
|
|
"x1": self.x2,
|
|
"y1": self.y2,
|
|
"width": self.w,
|
|
"height": self.h,
|
|
}
|
|
|
|
def json(self):
|
|
json_func = {"xywh": self.json_xywh, "xyxy": self.json_xyxy}.get(self.format, self.json_full)
|
|
return json_func()
|
|
|
|
def xyxy(self):
|
|
return self.x1, self.y1, self.x2, self.y2
|
|
|
|
def xywh(self):
|
|
return self.x1, self.y1, self.w, self.h
|
|
|
|
def intersection(self, rect):
|
|
bx1, by1, bx2, by2 = rect.xyxy()
|
|
if (self.x1 > bx2) or (bx1 > self.x2) or (self.y1 > by2) or (by1 > self.y2):
|
|
return 0
|
|
intersection_ = (min(self.x2, bx2) - max(self.x1, bx1)) * (min(self.y2, by2) - max(self.y1, by1))
|
|
return intersection_
|
|
|
|
def area(self):
|
|
return (self.x2 - self.x1) * (self.y2 - self.y1)
|
|
|
|
def iou(self, rect):
|
|
intersection = self.intersection(rect)
|
|
if intersection == 0:
|
|
return 0
|
|
union = self.area() + rect.area() - intersection
|
|
return intersection / union
|
|
|
|
def includes(self, other: "Rectangle", tol=3):
|
|
"""does a include b?"""
|
|
return (
|
|
other.x1 + tol >= self.x1
|
|
and other.y1 + tol >= self.y1
|
|
and other.x2 - tol <= self.x2
|
|
and other.y2 - tol <= self.y2
|
|
)
|
|
|
|
def is_included(self, rectangles: Iterable["Rectangle"]):
|
|
return any(rect.includes(self) for rect in rectangles if not rect == self)
|
|
|
|
def adjacent(self, rect2: "Rectangle", tolerance=7):
|
|
# tolerance=1 was set too low; most lines are 2px wide
|
|
def adjacent2d(sixtuple):
|
|
g, h, i, j, k, l = sixtuple
|
|
return (abs(g - h) <= tolerance) and any(k <= p <= l for p in [i, j])
|
|
|
|
if rect2 is None:
|
|
return False
|
|
return any(
|
|
map(
|
|
adjacent2d,
|
|
[
|
|
(self.x2, rect2.x1, rect2.y1, rect2.y2, self.y1, self.y2),
|
|
(self.x1, rect2.x2, rect2.y1, rect2.y2, self.y1, self.y2),
|
|
(self.y2, rect2.y1, rect2.x1, rect2.x2, self.x1, self.x2),
|
|
(self.y1, rect2.y2, rect2.x1, rect2.x2, self.x1, self.x2),
|
|
],
|
|
)
|
|
)
|
|
|
|
@classmethod
|
|
def from_xyxy(cls, xyxy_tuple, discrete=True):
|
|
x1, y1, x2, y2 = xyxy_tuple
|
|
return cls(x1=x1, y1=y1, x2=x2, y2=y2, discrete=discrete)
|
|
|
|
@classmethod
|
|
def from_xywh(cls, xywh_tuple, discrete=True):
|
|
x, y, w, h = xywh_tuple
|
|
return cls(x1=x, y1=y, w=w, h=h, discrete=discrete)
|
|
|
|
@classmethod
|
|
def from_dict_xywh(cls, xywh_dict, discrete=True):
|
|
return cls(x1=xywh_dict["x"], y1=xywh_dict["y"], w=xywh_dict["width"], h=xywh_dict["height"], discrete=discrete)
|
|
|
|
def __str__(self):
|
|
return dumps(self.json(), indent=self.indent)
|
|
|
|
def __repr__(self):
|
|
return str(self.json())
|
|
|
|
def __iter__(self):
|
|
return list(self.json().values()).__iter__()
|
|
|
|
def __eq__(self, rect):
|
|
return all([self.x1 == rect.x1, self.y1 == rect.y1, self.w == rect.w, self.h == rect.h])
|
|
|
|
|
|
class Contour:
|
|
def __init__(self):
|
|
pass
|