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()