layout-underlay-effectiveness / layout-underlay-effectiveness.py
shunk031's picture
deploy: fb8481effdf5a0b23ff86fad414906046d7620bd
ae50e0e
from typing import Dict, List, Union
import datasets as ds
import evaluate
import numpy as np
import numpy.typing as npt
from evaluate.utils.file_utils import add_start_docstrings
_DESCRIPTION = r"""\
Computes the ratio of valid underlay elements to total underlay elements used in PosterLayout. Intuitively, underlay should be placed under other non-underlay elements.
- strict: scoring the underlay as:
- 1: there is a non-underlay element completely inside
- 0: otherwise
- loose: Calcurate (ai/a2).
"""
_KWARGS_DESCRIPTION = """\
Args:
predictions (`list` of `list` of `float`): A list of lists of floats representing normalized `ltrb`-format bounding boxes.
gold_labels (`list` of `list` of `int`): A list of lists of integers representing class labels.
canvas_width (`int`, *optional*): Width of the canvas in pixels. Can be provided at initialization or during computation.
canvas_height (`int`, *optional*): Height of the canvas in pixels. Can be provided at initialization or during computation.
text_label_index (`int`, *optional*, defaults to 1): The label index for text elements.
decoration_label_index (`int`, *optional*, defaults to 3): The label index for decoration (underlay) elements.
Returns:
dict: A dictionary containing two underlay effectiveness metrics:
- `und_l` (loose): The average ratio of intersection area between underlay and other elements to underlay area. Higher values indicate better underlay effectiveness.
- `und_s` (strict): The ratio of underlay elements that completely contain at least one non-underlay element. Higher values indicate better underlay effectiveness.
Examples:
>>> import evaluate
>>> metric = evaluate.load("creative-graphic-design/layout-underlay-effectiveness")
>>> # Underlay box with text box on top
>>> predictions = [[[0.1, 0.1, 0.5, 0.5], [0.2, 0.2, 0.4, 0.4]]]
>>> gold_labels = [[3, 1]] # 3 is decoration (underlay), 1 is text
>>> result = metric.compute(
... predictions=predictions,
... gold_labels=gold_labels,
... canvas_width=512,
... canvas_height=512
... )
>>> print(f"Loose underlay effectiveness: {result['und_l']:.4f}")
>>> print(f"Strict underlay effectiveness: {result['und_s']:.4f}")
"""
_CITATION = """\
@inproceedings{hsu2023posterlayout,
title={Posterlayout: A new benchmark and approach for content-aware visual-textual presentation layout},
author={Hsu, Hsiao Yuan and He, Xiangteng and Peng, Yuxin and Kong, Hao and Zhang, Qing},
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
pages={6018--6026},
year={2023}
}
"""
@add_start_docstrings(_DESCRIPTION, _KWARGS_DESCRIPTION)
class LayoutUnderlayEffectiveness(evaluate.Metric):
def __init__(
self,
canvas_width: int | None = None,
canvas_height: int | None = None,
text_label_index: int = 1,
decoration_label_index: int = 3,
**kwargs,
) -> None:
super().__init__(**kwargs)
self.canvas_width = canvas_width
self.canvas_height = canvas_height
self.text_label_index = text_label_index
self.decoration_label_index = decoration_label_index
def _info(self) -> evaluate.EvaluationModuleInfo:
return evaluate.MetricInfo(
description=_DESCRIPTION,
citation=_CITATION,
inputs_description=_KWARGS_DESCRIPTION,
features=ds.Features(
{
"predictions": ds.Sequence(ds.Sequence(ds.Value("float64"))),
"gold_labels": ds.Sequence(ds.Sequence(ds.Value("int64"))),
}
),
codebase_urls=[
"https://github.com/PKU-ICST-MIPL/PosterLayout-CVPR2023/blob/main/eval.py#L224-L252",
"https://github.com/PKU-ICST-MIPL/PosterLayout-CVPR2023/blob/main/eval.py#L265-L292",
],
)
def get_rid_of_invalid(
self,
predictions: npt.NDArray[np.float64],
gold_labels: npt.NDArray[np.int64],
canvas_width: int,
canvas_height: int,
) -> npt.NDArray[np.int64]:
assert len(predictions) == len(gold_labels)
w = canvas_width / 100
h = canvas_height / 100
for i, prediction in enumerate(predictions):
for j, b in enumerate(prediction):
xl, yl, xr, yr = b
xl = max(0, xl)
yl = max(0, yl)
xr = min(canvas_width, xr)
yr = min(canvas_height, yr)
if abs((xr - xl) * (yr - yl)) < w * h * 10:
if gold_labels[i, j]:
gold_labels[i, j] = 0
return gold_labels
def metrics_inter_oneside(self, bb1, bb2):
xl_1, yl_1, xr_1, yr_1 = bb1
xl_2, yl_2, xr_2, yr_2 = bb2
# w_1 = xr_1 - xl_1
w_2 = xr_2 - xl_2
# h_1 = yr_1 - yl_1
h_2 = yr_2 - yl_2
w_inter = min(xr_1, xr_2) - max(xl_1, xl_2)
h_inter = min(yr_1, yr_2) - max(yl_1, yl_2)
# a_1 = w_1 * h_1
a_2 = w_2 * h_2
a_inter = w_inter * h_inter
if w_inter <= 0 or h_inter <= 0:
a_inter = 0
return a_inter / a_2
def _compute_und_l(
self, predictions: npt.NDArray[np.float64], gold_labels: npt.NDArray[np.int64]
) -> float:
# metrics, avali = 0.0, 0
metrics = []
avali = 0
for gold_label, prediction in zip(gold_labels, predictions):
und = 0
mask_deco = (gold_label == 3).reshape(-1)
mask_other = (gold_label > 0).reshape(-1) & (gold_label != 3).reshape(-1)
box_deco = prediction[mask_deco]
box_other = prediction[mask_other]
n1, n2 = len(box_deco), len(box_other)
if not n1:
continue
avali += 1
for i in range(n1):
max_ios = 0
bb1 = box_deco[i]
for j in range(n2):
bb2 = box_other[j]
ios = self.metrics_inter_oneside(bb1, bb2)
max_ios = max(max_ios, ios)
und += max_ios
# metrics += und / n1
metrics.append(und / n1)
# return metrics / avali if avali > 0 else 0.0
# return {"mean": np.mean(metrics), "std": np.std(metrics)}
return float(np.mean(metrics))
def _compute_und_s(
self, predictions: npt.NDArray[np.float64], gold_labels: npt.NDArray[np.int64]
) -> float:
def is_contain(bb1, bb2):
xl_1, yl_1, xr_1, yr_1 = bb1
xl_2, yl_2, xr_2, yr_2 = bb2
c1 = xl_1 <= xl_2
c2 = yl_1 <= yl_2
c3 = xr_2 >= xr_2
c4 = yr_1 >= yr_2
return c1 and c2 and c3 and c4
# metrics, avali = 0.0, 0
metrics = []
avali = 0
for gold_label, prediction in zip(gold_labels, predictions):
und = 0
mask_deco = (gold_label == 3).reshape(-1)
mask_other = (gold_label > 0).reshape(-1) & (gold_label != 3).reshape(-1)
box_deco = prediction[mask_deco]
box_other = prediction[mask_other]
n1, n2 = len(box_deco), len(box_other)
if not n1:
continue
avali += 1
for i in range(n1):
bb1 = box_deco[i]
for j in range(n2):
bb2 = box_other[j]
if is_contain(bb1, bb2):
und += 1
break
# metrics += und / n1
metrics.append(und / n1)
# return metrics / avali if avali > 0 else 0.0
return float(np.mean(metrics))
def _compute(
self,
*,
predictions: Union[npt.NDArray[np.float64], List[List[float]]],
gold_labels: Union[npt.NDArray[np.int64], List[int]],
canvas_width: int | None = None,
canvas_height: int | None = None,
text_label_index: int | None = None,
decoration_label_index: int | None = None,
) -> Dict[str, float]:
# パラメータの優先順位処理
canvas_width = canvas_width if canvas_width is not None else self.canvas_width
canvas_height = (
canvas_height if canvas_height is not None else self.canvas_height
)
text_label_index = (
text_label_index if text_label_index is not None else self.text_label_index
)
decoration_label_index = (
decoration_label_index
if decoration_label_index is not None
else self.decoration_label_index
)
if canvas_width is None or canvas_height is None:
raise ValueError(
"canvas_width and canvas_height must be provided either "
"at initialization or during computation"
)
predictions = np.array(predictions)
gold_labels = np.array(gold_labels)
predictions[:, :, ::2] *= canvas_width
predictions[:, :, 1::2] *= canvas_height
gold_labels = self.get_rid_of_invalid(
predictions=predictions,
gold_labels=gold_labels,
canvas_width=canvas_width,
canvas_height=canvas_height,
)
return {
"und_l": self._compute_und_l(
predictions=predictions, gold_labels=gold_labels
),
"und_s": self._compute_und_s(
predictions=predictions, gold_labels=gold_labels
),
}