shape-fitting / app.py
darwinharianto's picture
added japanese
4fbe86d
from nest2D import Point, Box, Item, nest, SVGWriter
import plotly
import numpy as np
import plotly.graph_objects as go
import cv2
import gradio as gr
def transform(point:list, x:float, y:float, rotation:float):
if point is None:
return None
point = np.array([point[0], point[1], 1])
matrix = np.array([[np.cos(rotation), -np.sin(rotation), x],
[np.sin(rotation), np.cos(rotation), y],
[0,0,1]])
return (matrix@point)[:2]
class BinPacking:
def __init__(self, width:int, height:int, image:np.ndarray, imageScale:float=1) -> None:
self.width = width
self.height = height
self.pgrp = None
self.total = 0
self.image = cv2.resize(image, (int(image.shape[1]*imageScale), int(image.shape[0]*imageScale)))
if self.image.shape[2] == 4:
x,y = np.where(self.image[:,:,3]==0)
self.image[x,y] = np.array([255,255,255,0])
self.imgray = cv2.cvtColor(self.image, cv2.COLOR_BGRA2GRAY)
else:
self.imgray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
self.imgray = cv2.bitwise_not(self.imgray)
@property
def box(self):
return Box(self.width, self.height)
def pack(self):
# make margin
imagem = cv2.dilate(self.imgray, np.ones((3,3), np.uint8), iterations=10)
_, thresh = cv2.threshold(imagem, 127, 255, 0)
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contourList = []
for contour in contours:
contourList.extend(contour)
hull = cv2.convexHull(np.array(contourList).reshape(-1,2))
hull = np.append(hull, [hull[0]], axis=0).reshape(-1,2)
item = Item(
[
Point(point[0], point[1])
for point in np.flip(hull, 0)
]
)
max_item = int(self.width*self.height/item.area)
self.pgrp = nest([item,]*max_item, self.box)
if self.pgrp is None or len(self.pgrp) == 0:
self.total = 0
else:
self.total = len(self.pgrp[0])
return self
def visualize(self):
fig = go.Figure()
widthScale = self.width/self.height
fig.update_layout(
autosize=False,
width=500*widthScale,
height=500,
margin=dict(
l=50,
r=50,
b=50,
t=50,
pad=4
),
paper_bgcolor="LightSteelBlue",
showlegend=False,
)
if self.pgrp == None or len(self.pgrp) == 0:
return fig
_, thresh = cv2.threshold(self.imgray, 127, 255, 0)
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
transformedPoints = []
for item in self.pgrp[0]:
for contour in contours:
for point in contour:
transformedPoints.append(transform(point[0], item.translation.x, item.translation.y, item.rotation))
transformedPoints.append([None,None])
transformedPoints.append([None,None])
fig.add_trace(go.Scatter(
x=[0, self.width, self.width, 0, 0],
y=[0, 0, self.height, self.height, 0],
fill="toself",
mode="lines",
textposition="bottom right",
))
fig.add_trace(go.Scatter(
x=[point[0] for point in transformedPoints],
y=[point[1] for point in transformedPoints],
mode="lines",
textposition="bottom right",
))
return fig
def process(width:int, height:int, scale:float, image: np.ndarray):
packer = BinPacking(width, height, image, scale)
packer.pack()
figure = packer.visualize()
if packer.total == 0:
text = "Input too big"
else:
text = f"Fit {packer.total} instances"
return [figure, text]
def change_language(languageSelection):
return [
gr.Dropdown.update(value="English" if languageSelection == "English" else "日本語"),
gr.Slider.update(label="Width" if languageSelection == "English" else "横長"),
gr.Slider.update(label="Height" if languageSelection == "English" else "縦長"),
gr.Slider.update(label="Input Scale" if languageSelection == "English" else "入力拡大"),
gr.Markdown.update(
"""
# Image fitting
### Given Image input, fit as many as possible x number of input on canvas
"""
if languageSelection == "English" else
"""
# 画像フィッティング
### 画像入力すると、キャンバス上の入力の数 x できるだけ多く収まります
"""),
gr.Image.update(label="Image" if languageSelection == "English" else "画像入力", ),
gr.Text.update(label="Result" if languageSelection == "English" else "結果"),
gr.Button.update("Submit" if languageSelection == "English" else "送信"),
gr.Plot.update(label="Plot" if languageSelection == "English" else "プロット"),
]
if __name__ == "__main__":
options = ["English", "日本語"]
with gr.Blocks() as demo:
with gr.Row():
with gr.Column(scale=8):
pass
with gr.Column():
languageSelection = gr.Dropdown(options, value="English", show_label=False)
desc = gr.Markdown(
"""
# Image fitting
### Given Image input, fit as many as possible x number of input on canvas
"""
)
with gr.Row():
with gr.Column():
image = gr.Image(image_mode="RGBA", label="Image")
with gr.Row():
width = gr.Slider(value=1500, minimum=100, maximum=8000, label="Width")
height = gr.Slider(value=1500, minimum=100, maximum=8000, label="Height")
scale = gr.Slider(value=1.0, minimum=0, maximum=5, step=0.01, label="Input Scale")
fit = gr.Button("Submit")
with gr.Column():
plot_output = gr.Plot(label="Plot")
text = gr.Text(label="Result")
languageSelection.change(fn=change_language,inputs=[languageSelection] , outputs=[languageSelection, width, height, scale, desc, image, text, fit, plot_output])
fit.click(fn=process, inputs=[width, height, scale, image], outputs=[plot_output, text])
demo.launch()