|
|
from lxml import etree |
|
|
import os |
|
|
import copy |
|
|
import glob |
|
|
import numpy as np |
|
|
from sklearn.linear_model import LinearRegression, LogisticRegression |
|
|
import matplotlib.pyplot as plt |
|
|
import matplotlib.patches as patches |
|
|
|
|
|
def parse_xml_with_recovery(xml_file_path): |
|
|
parser = etree.XMLParser(recover=True) |
|
|
tree = etree.parse(xml_file_path, parser) |
|
|
return tree.getroot() |
|
|
|
|
|
def parse_poster_xml(xml_file): |
|
|
""" |
|
|
Parse an XML describing a single poster layout, e.g.: |
|
|
|
|
|
<Poster Width="685" Height="968"> |
|
|
<Panel left="5" right="160" width="674" height="123"> |
|
|
<Text>Introduction</Text> |
|
|
<Figure left="567" right="178" width="81" height="99" no="1" ... /> |
|
|
</Panel> |
|
|
... |
|
|
</Poster> |
|
|
|
|
|
Returns a dict with: |
|
|
{ |
|
|
'poster_width': float, |
|
|
'poster_height': float, |
|
|
'panels': [ |
|
|
{ |
|
|
'x': float, |
|
|
'y': float, |
|
|
'width': float, |
|
|
'height': float, |
|
|
'text_blocks': [string, string, ...], |
|
|
'figure_blocks': [(fx, fy, fw, fh), ...] |
|
|
}, |
|
|
... |
|
|
] |
|
|
} |
|
|
""" |
|
|
root = parse_xml_with_recovery(xml_file) |
|
|
|
|
|
|
|
|
poster_w = float(root.get("Width", "1")) |
|
|
poster_h = float(root.get("Height", "1")) |
|
|
|
|
|
panels_data = [] |
|
|
|
|
|
|
|
|
for panel_node in root.findall("Panel"): |
|
|
x = float(panel_node.get("left", "0")) |
|
|
y = float(panel_node.get("right", "0")) |
|
|
w = float(panel_node.get("width", "0")) |
|
|
h = float(panel_node.get("height", "0")) |
|
|
|
|
|
|
|
|
text_blocks = [] |
|
|
for text_node in panel_node.findall("Text"): |
|
|
txt = text_node.text or "" |
|
|
txt = txt.strip() |
|
|
if txt: |
|
|
text_blocks.append(txt) |
|
|
|
|
|
|
|
|
figure_blocks = [] |
|
|
for fig_node in panel_node.findall("Figure"): |
|
|
fx = float(fig_node.get("left", "0")) |
|
|
fy = float(fig_node.get("right", "0")) |
|
|
fw = float(fig_node.get("width", "0")) |
|
|
fh = float(fig_node.get("height", "0")) |
|
|
figure_blocks.append((fx, fy, fw, fh)) |
|
|
|
|
|
panel_info = { |
|
|
"x": x, |
|
|
"y": y, |
|
|
"width": w, |
|
|
"height": h, |
|
|
"text_blocks": text_blocks, |
|
|
"figure_blocks": figure_blocks |
|
|
} |
|
|
panels_data.append(panel_info) |
|
|
|
|
|
return { |
|
|
"poster_width": poster_w, |
|
|
"poster_height": poster_h, |
|
|
"panels": panels_data |
|
|
} |
|
|
|
|
|
def compute_panel_attributes(poster_data): |
|
|
""" |
|
|
Given poster_data, compute: |
|
|
- tp: ratio of text length for each panel |
|
|
- gp: ratio of figure area for each panel |
|
|
- sp: ratio of panel area to total poster area |
|
|
- rp: aspect ratio (width / height) |
|
|
|
|
|
Returns a list of dicts, each: |
|
|
{ |
|
|
'tp': float, |
|
|
'gp': float, |
|
|
'sp': float, |
|
|
'rp': float |
|
|
} |
|
|
""" |
|
|
|
|
|
poster_w = poster_data["poster_width"] |
|
|
poster_h = poster_data["poster_height"] |
|
|
panels = poster_data["panels"] |
|
|
|
|
|
poster_area = max(poster_w * poster_h, 1.0) |
|
|
|
|
|
|
|
|
|
|
|
total_text_length = 0 |
|
|
total_figure_area = 0 |
|
|
|
|
|
|
|
|
panel_list = [] |
|
|
for p in panels: |
|
|
|
|
|
panel_text_joined = " ".join(p["text_blocks"]) |
|
|
panel_text_len = len(panel_text_joined) |
|
|
|
|
|
|
|
|
panel_fig_area = 0.0 |
|
|
for (fx, fy, fw, fh) in p["figure_blocks"]: |
|
|
panel_fig_area += (fw * fh) |
|
|
|
|
|
panel_list.append({ |
|
|
"x": p["x"], |
|
|
"y": p["y"], |
|
|
"width": p["width"], |
|
|
"height": p["height"], |
|
|
"text_len": panel_text_len, |
|
|
"fig_area": panel_fig_area |
|
|
}) |
|
|
|
|
|
total_text_length += panel_text_len |
|
|
total_figure_area += panel_fig_area |
|
|
|
|
|
|
|
|
if total_text_length < 1: |
|
|
total_text_length = 1 |
|
|
if total_figure_area < 1e-9: |
|
|
total_figure_area = 1e-9 |
|
|
|
|
|
|
|
|
results = [] |
|
|
for pinfo in panel_list: |
|
|
pw = pinfo["width"] |
|
|
ph = pinfo["height"] |
|
|
|
|
|
panel_area = pw * ph |
|
|
sp = panel_area / poster_area |
|
|
rp = (pw / ph) if ph > 0 else 1.0 |
|
|
|
|
|
tp = pinfo["text_len"] / float(total_text_length) |
|
|
gp = pinfo["fig_area"] / float(total_figure_area) |
|
|
|
|
|
results.append({ |
|
|
"tp": tp, |
|
|
"gp": gp, |
|
|
"sp": sp, |
|
|
"rp": rp |
|
|
}) |
|
|
|
|
|
return results |
|
|
|
|
|
def train_panel_attribute_inference(panel_records): |
|
|
""" |
|
|
The training data `panel_records` is a list of dicts, each containing: |
|
|
{ |
|
|
'tp': float, |
|
|
'gp': float, |
|
|
'sp': float, # (label for the sp regression) |
|
|
'rp': float # (label for the rp regression) |
|
|
} |
|
|
|
|
|
We'll train two linear regressors: |
|
|
sp = w_s * [tp, gp, 1] |
|
|
rp = w_r * [tp, gp, 1] |
|
|
|
|
|
Returns dict with learned parameters: |
|
|
{ |
|
|
'w_s': array, # shape (3,) => sp = w_s[0]*tp + w_s[1]*gp + w_s[2] |
|
|
'sigma_s': float, # variance of residual for sp |
|
|
'w_r': array, |
|
|
'sigma_r': float |
|
|
} |
|
|
""" |
|
|
|
|
|
X_list = [] |
|
|
sp_list = [] |
|
|
rp_list = [] |
|
|
|
|
|
for rec in panel_records: |
|
|
tp = rec['tp'] |
|
|
gp = rec['gp'] |
|
|
sp = rec['sp'] |
|
|
rp = rec['rp'] |
|
|
|
|
|
X_list.append([tp, gp, 1.0]) |
|
|
sp_list.append(sp) |
|
|
rp_list.append(rp) |
|
|
|
|
|
X_array = np.array(X_list, dtype=float) |
|
|
y_sp = np.array(sp_list, dtype=float) |
|
|
y_rp = np.array(rp_list, dtype=float) |
|
|
|
|
|
|
|
|
linreg_sp = LinearRegression(fit_intercept=False) |
|
|
linreg_sp.fit(X_array, y_sp) |
|
|
w_s = linreg_sp.coef_ |
|
|
pred_sp = linreg_sp.predict(X_array) |
|
|
residual_sp = y_sp - pred_sp |
|
|
sigma_s = np.var(residual_sp, ddof=1) |
|
|
|
|
|
|
|
|
linreg_rp = LinearRegression(fit_intercept=False) |
|
|
linreg_rp.fit(X_array, y_rp) |
|
|
w_r = linreg_rp.coef_ |
|
|
pred_rp = linreg_rp.predict(X_array) |
|
|
residual_rp = y_rp - pred_rp |
|
|
sigma_r = np.var(residual_rp, ddof=1) |
|
|
|
|
|
model_params = { |
|
|
"w_s": w_s, |
|
|
"sigma_s": sigma_s, |
|
|
"w_r": w_r, |
|
|
"sigma_r": sigma_r |
|
|
} |
|
|
return model_params |
|
|
|
|
|
|
|
|
def parse_poster_xml_for_figures(xml_path): |
|
|
root = parse_xml_with_recovery(xml_path) |
|
|
|
|
|
poster_w = float(root.get("Width", "1")) |
|
|
poster_h = float(root.get("Height", "1")) |
|
|
poster_area = poster_w * poster_h |
|
|
|
|
|
records = [] |
|
|
|
|
|
for panel in root.findall("Panel"): |
|
|
px, py = float(panel.get("left", 0)), float(panel.get("right", 0)) |
|
|
pw, ph = float(panel.get("width", 1)), float(panel.get("height", 1)) |
|
|
panel_area = pw * ph |
|
|
sp = panel_area / poster_area |
|
|
rp = pw / ph if ph > 0 else 1.0 |
|
|
|
|
|
lp = sum(len(t.text.strip()) for t in panel.findall("Text") if t.text) |
|
|
|
|
|
for fig in panel.findall("Figure"): |
|
|
fx, fy = float(fig.get("left", 0)), float(fig.get("right", 0)) |
|
|
fw, fh = float(fig.get("width", 1)), float(fig.get("height", 1)) |
|
|
|
|
|
sg = (fw * fh) / poster_area |
|
|
rg = fw / fh if fh > 0 else 1.0 |
|
|
ug = fw / pw if pw > 0 else 0.1 |
|
|
|
|
|
panel_center_x = px + pw / 2 |
|
|
fig_center_x = fx + fw / 2 |
|
|
delta_x = fig_center_x - panel_center_x |
|
|
|
|
|
hg = 0 if delta_x < -pw / 6 else (2 if delta_x > pw / 6 else 1) |
|
|
|
|
|
record = {"sp": sp, "rp": rp, "lp": lp, "sg": sg, "rg": rg, "hg": hg, "ug": ug} |
|
|
records.append(record) |
|
|
|
|
|
return records |
|
|
|
|
|
|
|
|
def train_figure_model(figure_records): |
|
|
X_hg, y_hg, X_ug, y_ug = [], [], [], [] |
|
|
for r in figure_records: |
|
|
feats = [r["sp"], r["lp"], r["sg"], 1.0] |
|
|
X_hg.append(feats) |
|
|
y_hg.append(r["hg"]) |
|
|
X_ug.append(feats) |
|
|
y_ug.append(r["ug"]) |
|
|
|
|
|
clf_hg = LogisticRegression(multi_class="multinomial", solver="lbfgs", fit_intercept=False) |
|
|
clf_hg.fit(X_hg, y_hg) |
|
|
|
|
|
lin_ug = LinearRegression(fit_intercept=False) |
|
|
lin_ug.fit(X_ug, y_ug) |
|
|
residuals = y_ug - lin_ug.predict(X_ug) |
|
|
sigma_u = np.var(residuals, ddof=1) |
|
|
|
|
|
return { |
|
|
"clf_hg": clf_hg, |
|
|
"w_u": lin_ug.coef_, |
|
|
"sigma_u": sigma_u |
|
|
} |
|
|
|
|
|
|
|
|
def main_train(): |
|
|
poster_dataset_path = 'assets/poster_data/Train' |
|
|
|
|
|
xml_files = [] |
|
|
for folder in os.listdir(poster_dataset_path): |
|
|
folder_path = os.path.join(poster_dataset_path, folder) |
|
|
if os.path.isdir(folder_path): |
|
|
|
|
|
xml_files.extend(glob.glob(os.path.join(folder_path, "*.txt"))) |
|
|
|
|
|
all_panel_records = [] |
|
|
for xml_file in xml_files: |
|
|
poster_data = parse_poster_xml(xml_file) |
|
|
|
|
|
panel_attrs = compute_panel_attributes(poster_data) |
|
|
|
|
|
all_panel_records.extend(panel_attrs) |
|
|
|
|
|
all_figure_records = [] |
|
|
for xml_path in xml_files: |
|
|
recs = parse_poster_xml_for_figures(xml_path) |
|
|
all_figure_records.extend(recs) |
|
|
|
|
|
panel_model_params = train_panel_attribute_inference(all_panel_records) |
|
|
figure_model_params = train_figure_model(all_figure_records) |
|
|
|
|
|
return panel_model_params, figure_model_params |
|
|
|
|
|
def place_text_and_figures_exact(panel_dict, figure_model_params, section_title_height=32): |
|
|
""" |
|
|
Lay out text and figure boxes inside a panel. |
|
|
|
|
|
The figure’s aspect ratio (width / height) is now enforced strictly: |
|
|
• width ≤ panel width |
|
|
• height ≤ 0.60 × panel height (empirical upper‑bound you already used) |
|
|
• width / height == panel_dict["figure_aspect"] |
|
|
""" |
|
|
|
|
|
char_width_px = 7 |
|
|
line_height_px = 16 |
|
|
chars_per_line = max(int(panel_dict["width"] / char_width_px), 1) |
|
|
|
|
|
total_lines_text = np.ceil(panel_dict["text_len"] / chars_per_line) |
|
|
total_text_height = total_lines_text * line_height_px |
|
|
|
|
|
x_p, y_p = panel_dict["x"], panel_dict["y"] |
|
|
w_p, h_p = panel_dict["width"], panel_dict["height"] |
|
|
|
|
|
figure_boxes, text_boxes = [], [] |
|
|
|
|
|
panel_name_lower = panel_dict["panel_name"].lower() |
|
|
has_title_in_name = "title" in panel_name_lower |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_text_box(panel_id, x, y, width, height, textbox_id, textbox_name): |
|
|
return { |
|
|
"panel_id": panel_id, |
|
|
"x": float(x), |
|
|
"y": float(y), |
|
|
"width": float(width), |
|
|
"height": float(height), |
|
|
"textbox_id": textbox_id, |
|
|
"textbox_name": textbox_name, |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if panel_dict["figure_size"] <= 0: |
|
|
if has_title_in_name: |
|
|
text_boxes.append( |
|
|
make_text_box(panel_dict["panel_id"], x_p, y_p, w_p, h_p, |
|
|
textbox_id=0, |
|
|
textbox_name=f'p<{panel_dict["panel_name"]}>_t0') |
|
|
) |
|
|
else: |
|
|
title_h = min(section_title_height, h_p) |
|
|
text_boxes.extend([ |
|
|
make_text_box(panel_dict["panel_id"], x_p, y_p, w_p, title_h, |
|
|
textbox_id=0, |
|
|
textbox_name=f'p<{panel_dict["panel_name"]}>_t0'), |
|
|
make_text_box(panel_dict["panel_id"], x_p, y_p + title_h, w_p, h_p - title_h, |
|
|
textbox_id=1, |
|
|
textbox_name=f'p<{panel_dict["panel_name"]}>_t1'), |
|
|
]) |
|
|
return text_boxes, figure_boxes |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
feat = np.array([panel_dict["sp"], |
|
|
panel_dict["text_len"], |
|
|
panel_dict["figure_size"], |
|
|
1.0]).reshape(1, -1) |
|
|
|
|
|
clf_hg = figure_model_params["clf_hg"] |
|
|
hg_sample = int(np.argmax(clf_hg.predict_proba(feat)[0])) |
|
|
|
|
|
mean_ug = float(np.dot(figure_model_params["w_u"], feat.flatten())) |
|
|
sigma_u = float(np.sqrt(figure_model_params["sigma_u"])) |
|
|
ug_sample = float(np.clip(np.random.normal(mean_ug, sigma_u), 0.10, 0.80)) |
|
|
|
|
|
|
|
|
aspect = float(panel_dict["figure_aspect"]) |
|
|
fig_w = ug_sample * w_p |
|
|
fig_h = fig_w / aspect |
|
|
|
|
|
max_fig_h = 0.60 * h_p |
|
|
if fig_h > max_fig_h: |
|
|
scale = max_fig_h / fig_h |
|
|
fig_w *= scale |
|
|
fig_h = max_fig_h |
|
|
|
|
|
|
|
|
if hg_sample == 0: |
|
|
fig_x = x_p |
|
|
elif hg_sample == 2: |
|
|
fig_x = x_p + w_p - fig_w |
|
|
else: |
|
|
fig_x = x_p + 0.5 * (w_p - fig_w) |
|
|
|
|
|
fig_y = y_p + 0.5 * (h_p - fig_h) |
|
|
|
|
|
|
|
|
top_text_h = (fig_y - y_p) |
|
|
bottom_text_h = (y_p + h_p) - (fig_y + fig_h) |
|
|
|
|
|
|
|
|
if has_title_in_name: |
|
|
text_boxes.append( |
|
|
make_text_box(panel_dict["panel_id"], x_p, y_p, w_p, top_text_h, |
|
|
textbox_id=0, |
|
|
textbox_name=f'p<{panel_dict["panel_name"]}>_t0') |
|
|
) |
|
|
next_id = 1 |
|
|
else: |
|
|
title_h = min(section_title_height, top_text_h) |
|
|
text_boxes.extend([ |
|
|
make_text_box(panel_dict["panel_id"], x_p, y_p, w_p, title_h, |
|
|
textbox_id=0, |
|
|
textbox_name=f'p<{panel_dict["panel_name"]}>_t0'), |
|
|
make_text_box(panel_dict["panel_id"], x_p, y_p + title_h, w_p, top_text_h - title_h, |
|
|
textbox_id=1, |
|
|
textbox_name=f'p<{panel_dict["panel_name"]}>_t1'), |
|
|
]) |
|
|
next_id = 2 |
|
|
|
|
|
|
|
|
text_boxes.append( |
|
|
make_text_box(panel_dict["panel_id"], x_p, fig_y + fig_h, w_p, bottom_text_h, |
|
|
textbox_id=next_id, |
|
|
textbox_name=f'p<{panel_dict["panel_name"]}>_t{next_id}') |
|
|
) |
|
|
|
|
|
|
|
|
figure_boxes.append({ |
|
|
"panel_id": panel_dict["panel_id"], |
|
|
"x": float(fig_x), |
|
|
"y": float(fig_y), |
|
|
"width": float(fig_w), |
|
|
"height": float(fig_h), |
|
|
"figure_id": 0, |
|
|
"figure_name": f'p<{panel_dict["panel_name"]}>_f0', |
|
|
}) |
|
|
|
|
|
return text_boxes, figure_boxes |
|
|
|
|
|
|
|
|
def to_inches(value_in_units, units_per_inch=72): |
|
|
""" |
|
|
Convert a single coordinate or dimension from 'units' to inches. |
|
|
For example, if your units are 'points' (72 points = 1 inch), |
|
|
then units_per_inch=72. |
|
|
If your units are 'pixels' at 96 DPI, then units_per_inch=96. |
|
|
""" |
|
|
return value_in_units / units_per_inch |
|
|
|
|
|
|
|
|
def from_inches(value_in_inches, units_per_inch=72): |
|
|
""" |
|
|
Convert from inches back to the original 'units'. |
|
|
""" |
|
|
return value_in_inches * units_per_inch |
|
|
|
|
|
|
|
|
def softmax(logits): |
|
|
s = sum(np.exp(logits)) |
|
|
return [np.exp(l)/s for l in logits] |
|
|
|
|
|
|
|
|
def infer_panel_attrs(panel_model, tp, gp): |
|
|
|
|
|
|
|
|
vec = np.array([tp, gp, 1.0]) |
|
|
w_s = panel_model["w_s"] |
|
|
w_r = panel_model["w_r"] |
|
|
sp = np.dot(w_s, vec) |
|
|
rp = np.dot(w_r, vec) |
|
|
|
|
|
sp = max(sp, 0.01) |
|
|
rp = max(rp, 0.05) |
|
|
return sp, rp |
|
|
|
|
|
|
|
|
def panel_layout_generation(panels, x, y, w, h): |
|
|
|
|
|
if len(panels) == 1: |
|
|
p = panels[0] |
|
|
cur_rp = (w/h) if h>1e-9 else p["rp"] |
|
|
loss = abs(p["rp"] - cur_rp) |
|
|
arrangement = [{ |
|
|
"panel_name": p["section_name"], |
|
|
"panel_id": p["panel_id"], |
|
|
"x": x, "y": y, |
|
|
"width": w, "height": h |
|
|
}] |
|
|
return loss, arrangement |
|
|
|
|
|
best_loss = float('inf') |
|
|
best_arr = [] |
|
|
total_sp = sum(pp["sp"] for pp in panels) |
|
|
n = len(panels) |
|
|
|
|
|
for i in range(1, n): |
|
|
subset1 = panels[:i] |
|
|
subset2 = panels[i:] |
|
|
sp1 = sum(pp["sp"] for pp in subset1) |
|
|
ratio = sp1 / total_sp |
|
|
|
|
|
|
|
|
h_top = ratio * h |
|
|
if 0 < h_top < h: |
|
|
l1, a1 = panel_layout_generation(subset1, x, y, w, h_top) |
|
|
l2, a2 = panel_layout_generation(subset2, x, y + h_top, w, h - h_top) |
|
|
if (l1 + l2) < best_loss: |
|
|
best_loss = l1 + l2 |
|
|
best_arr = a1 + a2 |
|
|
|
|
|
|
|
|
w_left = ratio * w |
|
|
if 0 < w_left < w: |
|
|
l1, a1 = panel_layout_generation(subset1, x, y, w_left, h) |
|
|
l2, a2 = panel_layout_generation(subset2, x + w_left, y, w - w_left, h) |
|
|
if (l1 + l2) < best_loss: |
|
|
best_loss = l1 + l2 |
|
|
best_arr = a1 + a2 |
|
|
|
|
|
return best_loss, best_arr |
|
|
|
|
|
def split_textbox(textbox, ratio): |
|
|
""" |
|
|
Splits a textbox dictionary horizontally into two parts. |
|
|
|
|
|
Parameters: |
|
|
textbox (dict): A dictionary with the keys |
|
|
'panel_id', 'x', 'y', 'width', 'height', 'textbox_id', 'textbox_name' |
|
|
ratio (float or int): Ratio of top height to bottom height. |
|
|
For example, if ratio is 3, then: |
|
|
top_height = (3/4) * height |
|
|
bottom_height = (1/4) * height |
|
|
|
|
|
Returns: |
|
|
tuple: Two dictionaries corresponding to the top and bottom split textboxes. |
|
|
""" |
|
|
|
|
|
total_ratio = ratio + 1 |
|
|
top_height = textbox['height'] * ratio / total_ratio |
|
|
bottom_height = textbox['height'] * 1 / total_ratio |
|
|
|
|
|
|
|
|
|
|
|
base_name = textbox['textbox_name'].rsplit('_t', 1)[0] |
|
|
|
|
|
|
|
|
top_box = dict(textbox) |
|
|
top_box['height'] = top_height |
|
|
|
|
|
top_box['textbox_name'] = f"{base_name}_t0" |
|
|
|
|
|
|
|
|
bottom_box = dict(textbox) |
|
|
bottom_box['y'] = textbox['y'] + top_height |
|
|
bottom_box['height'] = bottom_height |
|
|
bottom_box['textbox_name'] = f"{base_name}_t1" |
|
|
|
|
|
return top_box, bottom_box |
|
|
|
|
|
def generate_constrained_layout(paper_panels, poster_w, poster_h, title_height_ratio=0.1): |
|
|
|
|
|
try: |
|
|
title_panel = next(p for p in paper_panels if ('title' in p["section_name"].lower())) |
|
|
other_panels = [p for p in paper_panels if ('title' not in p["section_name"].lower())] |
|
|
except StopIteration: |
|
|
print('Oops, no title found, please try again.') |
|
|
raise |
|
|
|
|
|
title_h = poster_h * title_height_ratio |
|
|
title_layout = { |
|
|
"panel_name": title_panel["section_name"], |
|
|
"panel_id": title_panel["panel_id"], |
|
|
"x": 0, "y": 0, |
|
|
"width": poster_w, "height": title_h |
|
|
} |
|
|
|
|
|
|
|
|
layout_loss, remaining_layout = panel_layout_generation( |
|
|
other_panels, |
|
|
x=0, y=title_h, |
|
|
w=poster_w, h=poster_h - title_h |
|
|
) |
|
|
|
|
|
|
|
|
complete_layout = [title_layout] + remaining_layout |
|
|
return layout_loss, complete_layout |
|
|
|
|
|
|
|
|
def main_inference( |
|
|
paper_panels, |
|
|
panel_model_params, |
|
|
figure_model_params, |
|
|
poster_width=1200, |
|
|
poster_height=800, |
|
|
shrink_margin=0 |
|
|
): |
|
|
for p in paper_panels: |
|
|
sp, rp = infer_panel_attrs(panel_model_params, p["tp"], p["gp"]) |
|
|
p["sp"] = sp |
|
|
p["rp"] = rp |
|
|
|
|
|
layout_loss, panel_arrangement = generate_constrained_layout(paper_panels, poster_width, poster_height, title_height_ratio=0.1) |
|
|
print("Panel layout cost:", layout_loss) |
|
|
for p in panel_arrangement: |
|
|
print("Panel:", p) |
|
|
|
|
|
panel_map = {} |
|
|
for p in paper_panels: |
|
|
panel_map[p["panel_id"]] = p |
|
|
|
|
|
final_panels = [] |
|
|
for pa in panel_arrangement: |
|
|
|
|
|
pid = pa["panel_id"] |
|
|
merged_panel = { |
|
|
"panel_id": pid, |
|
|
"panel_name": pa['panel_name'], |
|
|
"x": pa["x"] + shrink_margin, |
|
|
"y": pa["y"] + shrink_margin, |
|
|
"width": pa["width"] - 2 * shrink_margin, |
|
|
"height": pa["height"] - 2 * shrink_margin, |
|
|
"sp": panel_map[pid]["sp"], |
|
|
"rp": panel_map[pid]["rp"], |
|
|
"text_len": panel_map[pid]["text_len"], |
|
|
"figure_size": panel_map[pid]["figure_size"], |
|
|
"figure_aspect": panel_map[pid]["figure_aspect"] |
|
|
} |
|
|
final_panels.append(merged_panel) |
|
|
|
|
|
text_arrangement = [] |
|
|
figure_arrangement = [] |
|
|
|
|
|
for p in final_panels: |
|
|
text_boxes, fig_boxes = place_text_and_figures_exact(p, figure_model_params) |
|
|
text_arrangement.extend(text_boxes) |
|
|
figure_arrangement.extend(fig_boxes) |
|
|
|
|
|
return panel_arrangement, figure_arrangement, text_arrangement |
|
|
|
|
|
def visualize_complete_layout( |
|
|
panels, text_boxes, figure_boxes, poster_width, poster_height |
|
|
): |
|
|
fig, ax = plt.subplots(figsize=(12,8)) |
|
|
ax.set_xlim(0, poster_width) |
|
|
ax.set_ylim(0, poster_height) |
|
|
ax.set_aspect('equal') |
|
|
|
|
|
|
|
|
for panel in panels: |
|
|
rect = patches.Rectangle( |
|
|
(panel["x"], panel["y"]), panel["width"], panel["height"], |
|
|
linewidth=1, edgecolor='black', facecolor='none' |
|
|
) |
|
|
ax.add_patch(rect) |
|
|
ax.text( |
|
|
panel["x"] + 5, panel["y"] + panel["height"] - 5, |
|
|
f'Panel {panel["panel_id"]}', fontsize=8, va='top', color='black' |
|
|
) |
|
|
|
|
|
|
|
|
for txt in text_boxes: |
|
|
rect = patches.Rectangle( |
|
|
(txt["x"], txt["y"]), txt["width"], txt["height"], |
|
|
linewidth=1, edgecolor='green', linestyle='-.', facecolor='none' |
|
|
) |
|
|
ax.add_patch(rect) |
|
|
ax.text( |
|
|
txt["x"] + 2, txt["y"] + txt["height"] - 2, |
|
|
f'Text {txt["panel_id"]}', fontsize=7, color='green', va='top' |
|
|
) |
|
|
|
|
|
|
|
|
for fig_box in figure_boxes: |
|
|
rect = patches.Rectangle( |
|
|
(fig_box["x"], fig_box["y"]), fig_box["width"], fig_box["height"], |
|
|
linewidth=1, edgecolor='blue', linestyle='--', facecolor='none' |
|
|
) |
|
|
ax.add_patch(rect) |
|
|
ax.text( |
|
|
fig_box["x"] + 2, fig_box["y"] + 2, |
|
|
f'Fig {fig_box["panel_id"]}', fontsize=7, color='blue', va='bottom' |
|
|
) |
|
|
|
|
|
plt.gca().invert_yaxis() |
|
|
plt.show() |
|
|
|
|
|
|
|
|
def get_arrangments_in_inches( |
|
|
width, |
|
|
height, |
|
|
panel_arrangement, |
|
|
figure_arrangement, |
|
|
text_arrangement, |
|
|
units_per_inch=72 |
|
|
): |
|
|
|
|
|
panel_arrangement_inches = copy.deepcopy(panel_arrangement) |
|
|
figure_arrangement_inches = copy.deepcopy(figure_arrangement) |
|
|
text_arrangement_inches = copy.deepcopy(text_arrangement) |
|
|
|
|
|
for p in panel_arrangement_inches: |
|
|
p["x"] = to_inches(p["x"], units_per_inch) |
|
|
p["y"] = to_inches(p["y"], units_per_inch) |
|
|
p["width"] = to_inches(p["width"], units_per_inch) |
|
|
p["height"] = to_inches(p["height"], units_per_inch) |
|
|
|
|
|
for f in figure_arrangement_inches: |
|
|
f["x"] = to_inches(f["x"], units_per_inch) |
|
|
f["y"] = to_inches(f["y"], units_per_inch) |
|
|
f["width"] = to_inches(f["width"], units_per_inch) |
|
|
f["height"] = to_inches(f["height"], units_per_inch) |
|
|
|
|
|
for t in text_arrangement_inches: |
|
|
t["x"] = to_inches(t["x"], units_per_inch) |
|
|
t["y"] = to_inches(t["y"], units_per_inch) |
|
|
t["width"] = to_inches(t["width"], units_per_inch) |
|
|
t["height"] = to_inches(t["height"], units_per_inch) |
|
|
|
|
|
width_inch, height_inch = to_inches(width, units_per_inch), to_inches(height, units_per_inch) |
|
|
return width_inch, height_inch, panel_arrangement_inches, figure_arrangement_inches, text_arrangement_inches |