|
|
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_2 = xr_2 - xl_2 |
|
|
|
|
|
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_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 |
|
|
|
|
|
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.append(und / n1) |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
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.append(und / n1) |
|
|
|
|
|
|
|
|
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 |
|
|
), |
|
|
} |
|
|
|