Delete mesh-candidate_bestfit
Browse files- mesh-candidate_bestfit/README.md +0 -102
- mesh-candidate_bestfit/augmentation.py +0 -88
- mesh-candidate_bestfit/bestfit_generator.py +0 -143
- mesh-candidate_bestfit/combine_layouts.py +0 -32
- mesh-candidate_bestfit/map_dict.py +0 -48
- mesh-candidate_bestfit/rendering.py +0 -83
- mesh-candidate_bestfit/utils/base.py +0 -29
- mesh-candidate_bestfit/utils/process.py +0 -42
- mesh-candidate_bestfit/visualize.ipynb +0 -0
mesh-candidate_bestfit/README.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
| 1 |
-
## Pretraining Data Generation via Mesh-candidate Bestfit
|
| 2 |
-
|
| 3 |
-
<p align="center">
|
| 4 |
-
<img src="../assets/Mesh-candidate Bestfit.png" width=100%> <br>
|
| 5 |
-
<i><small>Mesh-candidate Bestfit iteratively inserts elements from a small set of public datasets by searching for the best match between sampled candidates and the available grids in the current layout, ultimately achieving document synthesis.</i>
|
| 6 |
-
</p>
|
| 7 |
-
|
| 8 |
-
You can generate a large scale of diverse data for pretraining applying our proposed method Mesh-candidate Bestfit, just follow steps below:
|
| 9 |
-
|
| 10 |
-
### 1. Environment Setup
|
| 11 |
-
|
| 12 |
-
You need to install [PyMuPDF](https://pypi.org/project/PyMuPDF/1.23.7/) for subsequent rendering via pip:
|
| 13 |
-
|
| 14 |
-
```bash
|
| 15 |
-
cd mesh-candidate_bestfit
|
| 16 |
-
pip install pymupdf==1.23.7
|
| 17 |
-
```
|
| 18 |
-
|
| 19 |
-
### 2. Preprocessing
|
| 20 |
-
|
| 21 |
-
- **Data Preparation**
|
| 22 |
-
|
| 23 |
-
Two primary things need to be well prepared before starting generation:
|
| 24 |
-
|
| 25 |
-
1\. **Original Annotation File of Initial Dataset**
|
| 26 |
-
|
| 27 |
-
* The annotation file follows **COCO format**, a **JSON file** contains images and instances annotations.
|
| 28 |
-
* Each instance should have a **unique** ```instance_id```.
|
| 29 |
-
* The file should be placed under `./`.
|
| 30 |
-
|
| 31 |
-
2\. **Element Pool**
|
| 32 |
-
|
| 33 |
-
Element Pool is constructed according to annotation file. Specifically, crop all the instances images and organize them in a category-wise manner. The structure of element pool is as follows (folder is named by each category and cropped image is named by unique ```instance_id```):
|
| 34 |
-
|
| 35 |
-
```bash
|
| 36 |
-
./element_pool
|
| 37 |
-
βββ advertisement
|
| 38 |
-
β βββ 727.jpg
|
| 39 |
-
β βββ 919.jpg
|
| 40 |
-
β βββ 1423.jpg
|
| 41 |
-
β βββ ...
|
| 42 |
-
βββ algorithm
|
| 43 |
-
β βββ 12653.jpg
|
| 44 |
-
β βββ 17485.jpg
|
| 45 |
-
β βββ 44364.jpg
|
| 46 |
-
β βββ ...
|
| 47 |
-
βββ ...
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
**Note:** For convenience, we provide original annotation file and element pool for M6Doc-test dataset, which can be downloaded from [annotation file](https://drive.google.com/file/d/1ua41Gs3UW8iuoJp21tZ4-lczVrcEm-gP/view?usp=sharing) and [element pool](https://drive.google.com/file/d/1MrIFObKr1bDGgZLBQM_c_Dvobkp6mjFE/view?usp=sharing), respectively. And you can run the script below to decompress the element pool properly:
|
| 51 |
-
|
| 52 |
-
```bash
|
| 53 |
-
unzip /path/to/your/element_pool.zip -d ./element_pool/
|
| 54 |
-
```
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
- **Data Augmentation(Optional)**
|
| 58 |
-
|
| 59 |
-
If you want to apply our designed augmentation pipeline to your element pool, you can just run:
|
| 60 |
-
|
| 61 |
-
```bash
|
| 62 |
-
python augmentation.py --min_count 100 --aug_times 10
|
| 63 |
-
```
|
| 64 |
-
|
| 65 |
-
The script will perform augmentation pipeline `aug_times` times on each element of categories whose element number is less than `min_count`. If you want to generate large amount of data, try larger `aug_times`. In contrast, you want to shorten this process, try smaller `aug_times`. During DocSynth300K generation, we use ```--aug_times 50```.
|
| 66 |
-
|
| 67 |
-
- **Map Dict**
|
| 68 |
-
|
| 69 |
-
To facilitate the random selection of candidates during the rendering phase, it is necessary to establish a mapping from candidate elements to all of their candidate paths (passing ```--use_aug``` is augmentation is implemented):
|
| 70 |
-
|
| 71 |
-
```bash
|
| 72 |
-
python map_dict.py --save_path ./map_dict.json --use_aug
|
| 73 |
-
```
|
| 74 |
-
|
| 75 |
-
### 3. Layout Generation
|
| 76 |
-
|
| 77 |
-
Now, you can generate diverse layouts using Mesh-candidate Bestfit algorithm. To prevent process blocking, it will save the result of each layout in a timely manner, but you can use the [combine_layouts.py](./combine_layouts.py) script to combine them all together like this:
|
| 78 |
-
|
| 79 |
-
```bash
|
| 80 |
-
python bestfit_generator.py --generate_num 100 --n_jobs 5 --json_path ./annotation_file.json --output_dir ./generated_layouts/seperate
|
| 81 |
-
python combine_layouts.py --seperate_layouts_dir ./generated_layouts/seperate --save_path ./generated_layouts/combined_layouts.json
|
| 82 |
-
```
|
| 83 |
-
|
| 84 |
-
Afterwards, feel free to delete the seperate layouts since they are no longer used.
|
| 85 |
-
|
| 86 |
-
**Note:** Due to multiprocessing used in layout generation, set proper ```--n_jobs``` to avoid process blocking.
|
| 87 |
-
|
| 88 |
-
### 4. Rendering
|
| 89 |
-
|
| 90 |
-
Finally, you can render generated layouts and save the results in yolo format via the script below:
|
| 91 |
-
|
| 92 |
-
```bash
|
| 93 |
-
python rendering.py --json_path ./generated_layouts/combined_layouts.json --n_jobs 5 --map_dict_path ./map_dict.json --save_dir ./generated_dataset
|
| 94 |
-
```
|
| 95 |
-
|
| 96 |
-
### Visualization
|
| 97 |
-
|
| 98 |
-
We provide [visualize.ipynb](./visualize.ipynb) to visualize the layouts generated by our proposed methods. Here, we display some generation cases below:
|
| 99 |
-
|
| 100 |
-
<p align="center">
|
| 101 |
-
<img src="../assets/visualization.png" width=100%> <br>
|
| 102 |
-
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mesh-candidate_bestfit/augmentation.py
DELETED
|
@@ -1,88 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import cv2
|
| 3 |
-
import time
|
| 4 |
-
import argparse
|
| 5 |
-
import numpy as np
|
| 6 |
-
from tqdm import tqdm
|
| 7 |
-
import albumentations as A
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
class EdgeDetection(A.ImageOnlyTransform):
|
| 11 |
-
"""
|
| 12 |
-
A class for edge extraction of images with the sobel filter
|
| 13 |
-
"""
|
| 14 |
-
def apply(self, img, **params):
|
| 15 |
-
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
|
| 16 |
-
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
|
| 17 |
-
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
|
| 18 |
-
mag = np.hypot(sobelx, sobely)
|
| 19 |
-
mag = mag / np.max(mag) * 255
|
| 20 |
-
return np.uint8(mag)
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def pipeline(h, w):
|
| 24 |
-
"""
|
| 25 |
-
Whole data augmentation pipeline with the input of image size
|
| 26 |
-
|
| 27 |
-
Args:
|
| 28 |
-
h (float): Height of the image.
|
| 29 |
-
w (float): Width of the image.
|
| 30 |
-
"""
|
| 31 |
-
return A.Compose([
|
| 32 |
-
A.RandomBrightnessContrast(p=0.5),
|
| 33 |
-
A.RandomResizedCrop(height=h, width=w, scale=(0.5, 0.9), ratio=(w / h, w / h), p=0.7), # keep h/w ratio the same
|
| 34 |
-
EdgeDetection(p=0.2),
|
| 35 |
-
A.ElasticTransform(alpha_affine=5, p=0.2),
|
| 36 |
-
A.GaussNoise(var_limit=(100, 1200), p=1),
|
| 37 |
-
])
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
def perform_augmentation(img, transform, save_dir, prefix_id, aug_times):
|
| 41 |
-
"""
|
| 42 |
-
Perform augmentation for a single element with many times.
|
| 43 |
-
|
| 44 |
-
Args:
|
| 45 |
-
img (image): An elment.
|
| 46 |
-
transform (sequence): Data augmentation pipeline.
|
| 47 |
-
save_dir (str): Root directory to save.
|
| 48 |
-
prefix_id (str): Raw id for the element.
|
| 49 |
-
aug_times (int): Augmentation times.
|
| 50 |
-
"""
|
| 51 |
-
for _ in range(aug_times):
|
| 52 |
-
transformed = transform(image=img)
|
| 53 |
-
transformed_image = transformed["image"]
|
| 54 |
-
transformed_image_bgr = cv2.cvtColor(transformed_image, cv2.COLOR_RGB2BGR)
|
| 55 |
-
|
| 56 |
-
suffix_id = str(time.time()).replace(".", "_")
|
| 57 |
-
prefix_id_dir = os.path.join(save_dir, prefix_id)
|
| 58 |
-
os.makedirs(prefix_id_dir, exist_ok=True)
|
| 59 |
-
aug_element_path = os.path.join(prefix_id_dir, f'{prefix_id}_{suffix_id}.jpg')
|
| 60 |
-
cv2.imwrite(aug_element_path, transformed_image_bgr)
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
if __name__ == "__main__":
|
| 65 |
-
|
| 66 |
-
parser = argparse.ArgumentParser(description="Perform Image Augmentation")
|
| 67 |
-
parser.add_argument("--min_count", type=int, default=100, help="Minimum number of elements for categories that do not require data augmentation")
|
| 68 |
-
parser.add_argument("--aug_times", type=int, default=50, help="Number of augmentations per element")
|
| 69 |
-
args = parser.parse_args()
|
| 70 |
-
|
| 71 |
-
root_dir = './element_pool'
|
| 72 |
-
|
| 73 |
-
for category in tqdm(os.listdir(root_dir),desc='Categories done'):
|
| 74 |
-
category_dir = os.path.join(root_dir,category)
|
| 75 |
-
if len(os.listdir(category_dir)) > args.min_count:
|
| 76 |
-
continue
|
| 77 |
-
else:
|
| 78 |
-
save_dir = os.path.join(category_dir, 'aug')
|
| 79 |
-
for raw_element in os.listdir(category_dir):
|
| 80 |
-
raw_element_path = os.path.join(category_dir, raw_element)
|
| 81 |
-
img = cv2.imread(raw_element_path)
|
| 82 |
-
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 83 |
-
h, w, c = img.shape
|
| 84 |
-
element_id = raw_element.split('.')[0]
|
| 85 |
-
|
| 86 |
-
transform = pipeline(h, w)
|
| 87 |
-
|
| 88 |
-
perform_augmentation(img=img,transform=transform,save_dir=save_dir,prefix_id=element_id,aug_times=args.aug_times)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mesh-candidate_bestfit/bestfit_generator.py
DELETED
|
@@ -1,143 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import json
|
| 3 |
-
import time
|
| 4 |
-
import torch
|
| 5 |
-
import random
|
| 6 |
-
import datetime
|
| 7 |
-
import argparse
|
| 8 |
-
import itertools
|
| 9 |
-
import torchvision
|
| 10 |
-
import multiprocessing
|
| 11 |
-
from utils.process import *
|
| 12 |
-
|
| 13 |
-
random.seed(datetime.datetime.now().timestamp())
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
def bestfit_generator(element_all):
|
| 17 |
-
"""
|
| 18 |
-
Apply the Mesh-candidate Bestfit algorithm to generate diverse layouts.
|
| 19 |
-
|
| 20 |
-
Args:
|
| 21 |
-
element_all (dict): Loaded elements from dataset json file.
|
| 22 |
-
output_dir (str): Directory to save the generated layouts.
|
| 23 |
-
"""
|
| 24 |
-
# Default candidate_num = 500
|
| 25 |
-
candidate_num = 500
|
| 26 |
-
large_elements_idx = random.sample(list(range(len(element_all['large']))), int(candidate_num*0.99))
|
| 27 |
-
small_elements_idx = random.sample(list(range(len(element_all['small']))), int(candidate_num*0.01))
|
| 28 |
-
cand_elements = [element_all['large'][large_idx] for large_idx in large_elements_idx] + [element_all['small'][small_idx] for small_idx in small_elements_idx]
|
| 29 |
-
|
| 30 |
-
# Initially, randomly put an element
|
| 31 |
-
put_elements = []
|
| 32 |
-
e0 = random.choice(cand_elements)
|
| 33 |
-
cx = random.uniform(min(e0.w/2, 1-e0.w/2), max(e0.w/2, 1-e0.w/2))
|
| 34 |
-
cy = random.uniform(min(e0.h/2, 1-e0.h/2), max(e0.h/2, 1-e0.h/2))
|
| 35 |
-
e0.cx, e0.cy = cx, cy
|
| 36 |
-
put_elements = [e0]
|
| 37 |
-
cand_elements.remove(e0)
|
| 38 |
-
small_cnt = 1 if e0.w < 0.05 or e0.h < 0.05 else 0
|
| 39 |
-
|
| 40 |
-
# Iterativelly insert elements
|
| 41 |
-
while True:
|
| 42 |
-
# Construct meshgrid based on current layout
|
| 43 |
-
put_element_boxes = []
|
| 44 |
-
xticks, yticks = [0,1], [0,1]
|
| 45 |
-
for e in put_elements:
|
| 46 |
-
x1, y1, x2, y2 = e.cx-e.w/2, e.cy-e.h/2, e.cx+e.w/2, e.cy+e.h/2
|
| 47 |
-
xticks.append(x1)
|
| 48 |
-
xticks.append(x2)
|
| 49 |
-
yticks.append(y1)
|
| 50 |
-
yticks.append(y2)
|
| 51 |
-
put_element_boxes.append([x1, y1, x2, y2])
|
| 52 |
-
xticks, yticks = list(set(xticks)), list(set(yticks))
|
| 53 |
-
pticks = list(itertools.product(xticks, yticks))
|
| 54 |
-
meshgrid = list(itertools.product(pticks, pticks))
|
| 55 |
-
put_element_boxes = torch.Tensor(put_element_boxes)
|
| 56 |
-
|
| 57 |
-
# Filter out invlid grids
|
| 58 |
-
meshgrid = [grid for grid in meshgrid if grid[0][0] < grid[1][0] and grid[0][1] < grid[1][1]]
|
| 59 |
-
meshgrid_tensor = torch.Tensor([p1 + p2 for p1, p2 in meshgrid])
|
| 60 |
-
iou_res = torchvision.ops.box_iou(meshgrid_tensor, put_element_boxes)
|
| 61 |
-
valid_grid_idx = (iou_res.sum(dim=1) == 0).nonzero().flatten().tolist()
|
| 62 |
-
meshgrid = meshgrid_tensor[valid_grid_idx].tolist()
|
| 63 |
-
|
| 64 |
-
# Search for the Mesh-candidate Bestfit pair
|
| 65 |
-
max_fill, max_grid_idx, max_element_idx = 0, -1, -1
|
| 66 |
-
for element_idx, e in enumerate(cand_elements):
|
| 67 |
-
for grid_idx, grid in enumerate(meshgrid):
|
| 68 |
-
if e.w > grid[2] - grid[0] or e.h > grid[3] - grid[1]:
|
| 69 |
-
continue
|
| 70 |
-
element_area = e.w * e.h
|
| 71 |
-
grid_area = (grid[2] - grid[0]) * (grid[3] - grid[1])
|
| 72 |
-
if element_area/grid_area > max_fill:
|
| 73 |
-
max_fill = element_area/grid_area
|
| 74 |
-
max_grid_idx = grid_idx
|
| 75 |
-
max_element_idx = element_idx
|
| 76 |
-
|
| 77 |
-
# Termination condition
|
| 78 |
-
if max_element_idx == -1 or max_grid_idx == -1:
|
| 79 |
-
break
|
| 80 |
-
else:
|
| 81 |
-
maxfit_element = cand_elements[max_element_idx]
|
| 82 |
-
if maxfit_element.w < 0.05 or maxfit_element.h < 0.05:
|
| 83 |
-
small_cnt += 1
|
| 84 |
-
if small_cnt > 5:
|
| 85 |
-
break
|
| 86 |
-
else:
|
| 87 |
-
pass
|
| 88 |
-
|
| 89 |
-
# Put the candidate to the center of the grid
|
| 90 |
-
cand_elements.remove(maxfit_element)
|
| 91 |
-
maxfit_element.cx = (meshgrid[max_grid_idx][0] + meshgrid[max_grid_idx][2])/2
|
| 92 |
-
maxfit_element.cy = (meshgrid[max_grid_idx][1] + meshgrid[max_grid_idx][3])/2
|
| 93 |
-
put_elements.append(maxfit_element)
|
| 94 |
-
|
| 95 |
-
# Apply a rescale transform to introduce more diversity
|
| 96 |
-
for _, e in enumerate(put_elements):
|
| 97 |
-
e.gen_real_bbox()
|
| 98 |
-
layout = Layout(cand_elements=put_elements)
|
| 99 |
-
|
| 100 |
-
# Convert the layout to json file format
|
| 101 |
-
boxes, categories, relpaths = [], [], []
|
| 102 |
-
for element in layout.cand_elements:
|
| 103 |
-
cx, cy, w, h = element.get_real_bbox()
|
| 104 |
-
x1, y1, x2, y2 = cx-w/2, cy-h/2, cx+w/2, cy+h/2
|
| 105 |
-
boxes.append([x1, y1, x2, y2])
|
| 106 |
-
categories.append(element.category-1) # Exclude the "__background__" category (category_id = 0)
|
| 107 |
-
relpaths.append(element.filepath)
|
| 108 |
-
|
| 109 |
-
output_layout = {
|
| 110 |
-
"boxes": boxes,
|
| 111 |
-
"categories": categories,
|
| 112 |
-
"relpaths": relpaths
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
# To prevent process blocking, save the result of each layout in a timely manner.
|
| 116 |
-
with open(os.path.join(OUTPUT_DIR,str(time.time()).replace(".", "_")+'.json'),'w') as f:
|
| 117 |
-
json.dump(output_layout, f)
|
| 118 |
-
|
| 119 |
-
return output_layout
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
if __name__ == "__main__":
|
| 124 |
-
|
| 125 |
-
parser = argparse.ArgumentParser()
|
| 126 |
-
parser.add_argument('--generate_num', default=None, required=True, type=int, help='number of layouts to generate')
|
| 127 |
-
parser.add_argument('--n_jobs', default=None, required=True, type=int, help='number of processes to use in multiprocessing')
|
| 128 |
-
parser.add_argument('--json_path', default=None, required=True, type=str, help='original json file of the dataset')
|
| 129 |
-
parser.add_argument('--output_dir', default='./generated_layouts/seperate', type=str, help='output directory of generated seperate layouts')
|
| 130 |
-
args = parser.parse_args()
|
| 131 |
-
|
| 132 |
-
element_all = read_data(args.json_path)
|
| 133 |
-
OUTPUT_DIR = args.output_dir
|
| 134 |
-
os.makedirs(OUTPUT_DIR,exist_ok=True)
|
| 135 |
-
|
| 136 |
-
# Using multiprocessing to accelerate generation
|
| 137 |
-
n_jobs = args.n_jobs
|
| 138 |
-
with multiprocessing.Pool(n_jobs) as p:
|
| 139 |
-
generated_layout = p.starmap(
|
| 140 |
-
bestfit_generator, [(element_all,) for _ in range(args.generate_num)]
|
| 141 |
-
)
|
| 142 |
-
p.close()
|
| 143 |
-
p.join()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mesh-candidate_bestfit/combine_layouts.py
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import json
|
| 3 |
-
import argparse
|
| 4 |
-
from tqdm import tqdm
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def combine_layouts(seperate_layouts_dir):
|
| 8 |
-
"""
|
| 9 |
-
Combining seperate layouts into one json.
|
| 10 |
-
|
| 11 |
-
Args:
|
| 12 |
-
seperate_layouts_dir (str): Directory to save seperate layouts json files generated by bestfit_generator.py
|
| 13 |
-
"""
|
| 14 |
-
combined_layouts = []
|
| 15 |
-
for item in tqdm(os.listdir(seperate_layouts_dir),desc='Combining seperate layouts'):
|
| 16 |
-
abs_path = os.path.join(seperate_layouts_dir,item)
|
| 17 |
-
json_file = json.load(open(abs_path))
|
| 18 |
-
combined_layouts.append(json_file)
|
| 19 |
-
return combined_layouts
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
if __name__ == "__main__":
|
| 23 |
-
|
| 24 |
-
parser = argparse.ArgumentParser()
|
| 25 |
-
parser.add_argument('--seperate_layouts_dir', default="./generated_layouts/seperate", type=str, help="directory to save seperate layouts")
|
| 26 |
-
parser.add_argument('--save_path', default="./generated_layouts/combined_layouts.json", type=str, help='save path for combined generated layouts')
|
| 27 |
-
args = parser.parse_args()
|
| 28 |
-
|
| 29 |
-
combined_layouts = combine_layouts(seperate_layouts_dir=args.seperate_layouts_dir)
|
| 30 |
-
|
| 31 |
-
with open(args.save_path,'w') as f:
|
| 32 |
-
f.write(json.dumps(combined_layouts,indent=4))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mesh-candidate_bestfit/map_dict.py
DELETED
|
@@ -1,48 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import json
|
| 3 |
-
import argparse
|
| 4 |
-
from tqdm import tqdm
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def get_map_dict(use_aug):
|
| 8 |
-
"""
|
| 9 |
-
Get a map from a element to its corresponding save paths.
|
| 10 |
-
|
| 11 |
-
Args:
|
| 12 |
-
use_aug (bool): Whether use augmentation elements or not.
|
| 13 |
-
"""
|
| 14 |
-
instance2pathlist = {}
|
| 15 |
-
root_dir = './element_pool'
|
| 16 |
-
for category in os.listdir(root_dir):
|
| 17 |
-
if category == '.DS_Store':
|
| 18 |
-
continue
|
| 19 |
-
category_dir = os.path.join(root_dir,category)
|
| 20 |
-
filelist = os.listdir(category_dir)
|
| 21 |
-
for filename in tqdm(filelist):
|
| 22 |
-
if filename == 'aug':
|
| 23 |
-
continue
|
| 24 |
-
else:
|
| 25 |
-
sin_id_pathlist = []
|
| 26 |
-
start_id = filename.split('.')[0]
|
| 27 |
-
origin_path = os.path.join(category_dir,filename)
|
| 28 |
-
sin_id_pathlist.append(origin_path)
|
| 29 |
-
if 'aug' in filelist and use_aug:
|
| 30 |
-
bottom_dir = os.path.join(category_dir,f'aug/{start_id}')
|
| 31 |
-
aug_paths = os.listdir(bottom_dir)
|
| 32 |
-
aug_pathlist = [os.path.join(bottom_dir,path) for path in aug_paths]
|
| 33 |
-
sin_id_pathlist += aug_pathlist
|
| 34 |
-
instance2pathlist[start_id] = sin_id_pathlist
|
| 35 |
-
return instance2pathlist
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
if __name__ == "__main__":
|
| 39 |
-
|
| 40 |
-
parser = argparse.ArgumentParser()
|
| 41 |
-
parser.add_argument('--use_aug', action='store_true', help="whether to use data augmentation")
|
| 42 |
-
parser.add_argument('--save_path', default="./map_dict.json", type=str, help='save path for the map dict')
|
| 43 |
-
args = parser.parse_args()
|
| 44 |
-
|
| 45 |
-
map_dict = get_map_dict(use_aug=args.use_aug)
|
| 46 |
-
|
| 47 |
-
with open(args.save_path,'w') as f:
|
| 48 |
-
f.write(json.dumps(map_dict))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mesh-candidate_bestfit/rendering.py
DELETED
|
@@ -1,83 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import time
|
| 3 |
-
import fitz
|
| 4 |
-
import json
|
| 5 |
-
import random
|
| 6 |
-
import argparse
|
| 7 |
-
import datetime
|
| 8 |
-
import multiprocessing
|
| 9 |
-
from utils.process import *
|
| 10 |
-
|
| 11 |
-
random.seed(datetime.datetime.now().timestamp())
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
def render_layout(layouts):
|
| 15 |
-
"""
|
| 16 |
-
Render layouts to images and save in the yolo format.
|
| 17 |
-
|
| 18 |
-
Args:
|
| 19 |
-
layouts (list): List of generated layouts information.
|
| 20 |
-
"""
|
| 21 |
-
doc = fitz.open()
|
| 22 |
-
w, h = sample_hw(
|
| 23 |
-
width_range=[1200, 2000],
|
| 24 |
-
ratio_range=[0.7,1.5],
|
| 25 |
-
max_height=3000,
|
| 26 |
-
)
|
| 27 |
-
page = doc.new_page(width=w, height=h)
|
| 28 |
-
|
| 29 |
-
annotation_json = {"bbox": [], "labels": [], "width":w, "height":h}
|
| 30 |
-
for bbox, category, relpath in zip(layouts["boxes"], layouts["categories"], layouts["relpaths"]):
|
| 31 |
-
bbox[0], bbox[2] = bbox[0]*w, bbox[2]*w
|
| 32 |
-
bbox[1], bbox[3] = bbox[1]*h, bbox[3]*h
|
| 33 |
-
rect = fitz.Rect([bbox[i] for i in range(4)])
|
| 34 |
-
|
| 35 |
-
abs_filepath = os.path.join('./element_pool',relpath)
|
| 36 |
-
start_str = abs_filepath.rsplit('/',1)[1].split('.')[0]
|
| 37 |
-
sampled_path = random.choice(INSTANCE2PATHLIST[start_str])
|
| 38 |
-
|
| 39 |
-
page.insert_image(rect, filename=sampled_path, keep_proportion=False)
|
| 40 |
-
|
| 41 |
-
annotation_json["bbox"].append(bbox)
|
| 42 |
-
annotation_json["labels"].append(category)
|
| 43 |
-
|
| 44 |
-
_id = str(time.time()).replace(".", "_")
|
| 45 |
-
pix = page.get_pixmap()
|
| 46 |
-
pix.save(os.path.join(IMAGE_DIR, f"{_id}.jpg"))
|
| 47 |
-
anno_txt = open(os.path.join(ANNO_DIR, f"{_id}.txt"), "w")
|
| 48 |
-
for bbox, category_id in zip(annotation_json["bbox"], annotation_json["labels"]):
|
| 49 |
-
w, h = annotation_json["width"], annotation_json["height"]
|
| 50 |
-
x0, y0, x1, y1 = bbox
|
| 51 |
-
x0, y0 = x0/w, y0/h
|
| 52 |
-
x1, y1 = x1/w, y1/h
|
| 53 |
-
anno_txt.write(f"{category_id} {x0} {y0} {x1} {y0} {x1} {y1} {x0} {y1}\n")
|
| 54 |
-
anno_txt.close()
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
if __name__ == "__main__":
|
| 58 |
-
|
| 59 |
-
parser = argparse.ArgumentParser()
|
| 60 |
-
parser.add_argument('--save_dir', default="./generated_dataset", type=str, help='planned root dir for generated dataset')
|
| 61 |
-
parser.add_argument('--n_jobs', default=None, required=True, type=int, help='number of processes to use in multiprocessing')
|
| 62 |
-
parser.add_argument('--json_path', default=None, required=True, type=str, help='json path for layouts generated by the Mesh-candidate Bestfit alogorithm')
|
| 63 |
-
parser.add_argument('--map_dict_path', default=None, required=True, type=str, help='json path for element to pathlist map dict')
|
| 64 |
-
args = parser.parse_args()
|
| 65 |
-
|
| 66 |
-
# Setting save path
|
| 67 |
-
IMAGE_DIR = os.path.join(args.save_dir, "images")
|
| 68 |
-
ANNO_DIR = os.path.join(args.save_dir, "labels")
|
| 69 |
-
os.makedirs(IMAGE_DIR, exist_ok=True)
|
| 70 |
-
os.makedirs(ANNO_DIR, exist_ok=True)
|
| 71 |
-
|
| 72 |
-
# Load layout data
|
| 73 |
-
layout_json = json.load(open(args.json_path))
|
| 74 |
-
INSTANCE2PATHLIST = json.load(open(args.map_dict_path))
|
| 75 |
-
|
| 76 |
-
# Using multiprocessing to accelerate rendering
|
| 77 |
-
n_jobs = args.n_jobs
|
| 78 |
-
with multiprocessing.Pool(n_jobs) as p:
|
| 79 |
-
result = p.starmap(
|
| 80 |
-
render_layout, [(layout,) for layout in layout_json]
|
| 81 |
-
)
|
| 82 |
-
p.close()
|
| 83 |
-
p.join()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mesh-candidate_bestfit/utils/base.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
import random
|
| 2 |
-
|
| 3 |
-
class element(object):
|
| 4 |
-
def __init__(self, cx, cy, h, w, category, filepath):
|
| 5 |
-
self.cx = cx
|
| 6 |
-
self.cy = cy
|
| 7 |
-
self.h = h
|
| 8 |
-
self.w = w
|
| 9 |
-
self.category = category
|
| 10 |
-
self.filepath = filepath
|
| 11 |
-
self.ratio = h / w
|
| 12 |
-
self.area = h * w
|
| 13 |
-
|
| 14 |
-
def gen_real_bbox(self):
|
| 15 |
-
self.real_cx, self.real_cy = self.cx, self.cy
|
| 16 |
-
self.real_w, self.real_h = self.w*random.uniform(0.8,0.95), self.h*random.uniform(0.8,0.95)
|
| 17 |
-
|
| 18 |
-
def get_real_bbox(self):
|
| 19 |
-
return self.real_cx, self.real_cy, self.real_w, self.real_h
|
| 20 |
-
|
| 21 |
-
def __repr__(self):
|
| 22 |
-
return f'cx: {self.cx}, cy: {self.cy}, h:{self.h}, w:{self.w}, category:{self.category}'
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
class Layout(object):
|
| 26 |
-
def __init__(self, cand_elements, align=None, fill=None):
|
| 27 |
-
self.cand_elements = cand_elements
|
| 28 |
-
self.align = align
|
| 29 |
-
self.fill = fill
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mesh-candidate_bestfit/utils/process.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import json
|
| 3 |
-
from .base import *
|
| 4 |
-
|
| 5 |
-
def read_data(json_file):
|
| 6 |
-
"""
|
| 7 |
-
Load elements from dataset json file.
|
| 8 |
-
|
| 9 |
-
Args:
|
| 10 |
-
json_file (str): A dataset json file path with coco format.
|
| 11 |
-
"""
|
| 12 |
-
data = json.load(open(json_file))
|
| 13 |
-
category_id2name = {item['id']:item['name'] for item in data['categories']}
|
| 14 |
-
element_all = {'large':[], "small":[]}
|
| 15 |
-
image2anno = {image["id"]:image for image in data["images"]}
|
| 16 |
-
for anno in data["annotations"]:
|
| 17 |
-
H, W = image2anno[anno["image_id"]]["height"], image2anno[anno["image_id"]]["width"]
|
| 18 |
-
w, h = anno["bbox"][2], anno["bbox"][3]
|
| 19 |
-
if w/W < 0.01 or h/H < 0.01:
|
| 20 |
-
continue
|
| 21 |
-
anno_id, category_id = anno['id'], anno["category_id"]
|
| 22 |
-
e = element(cx=None, cy=None, h=h/H, w=w/W, category=category_id,filepath=f'{category_id2name[category_id]}/{anno_id}.jpg')
|
| 23 |
-
if w/W >= 0.05 and h/H >= 0.05:
|
| 24 |
-
element_all['large'].append(e)
|
| 25 |
-
else:
|
| 26 |
-
element_all['small'].append(e)
|
| 27 |
-
return element_all
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
def sample_hw(width_range, ratio_range, max_height):
|
| 31 |
-
"""
|
| 32 |
-
Randomly sample a (w,h) size for rendering from a given range.
|
| 33 |
-
|
| 34 |
-
Args:
|
| 35 |
-
width_range (list): Given range of width.
|
| 36 |
-
ratio_range (list): Given range of h/w ratio.
|
| 37 |
-
max_height (int): Upper bound of height.
|
| 38 |
-
"""
|
| 39 |
-
w = random.randint(width_range[0], width_range[1])
|
| 40 |
-
ratio = random.uniform(ratio_range[0], ratio_range[1])
|
| 41 |
-
h = min(max_height, int(w*ratio))
|
| 42 |
-
return w, h
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mesh-candidate_bestfit/visualize.ipynb
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|