2Nike2 commited on
Commit
5d3564f
1 Parent(s): 93324c5

add manhole model creation

Browse files
app.py CHANGED
@@ -11,6 +11,7 @@ from src.front_card_image_historic_site import create_historic_site_card_image
11
  from src.picture_box_model import create_picture_box_model
12
  from src.extracted_objects_model import create_extracted_objects_model
13
  from src.card_model import create_card_model
 
14
 
15
  language = os.environ.get('LANGUAGE', 'en')
16
  print(f'LANGUAGE: {language}')
@@ -64,6 +65,15 @@ def create_3dmodel(model_no, title, color, mark, historic_site_type, difficulty,
64
 
65
  return model_path
66
 
 
 
 
 
 
 
 
 
 
67
  with gr.Blocks() as demo:
68
 
69
  gr.Markdown(display_message_dict['header'])
@@ -114,5 +124,14 @@ with gr.Blocks() as demo:
114
  )
115
 
116
  gr.Markdown(display_message_dict['footer_historic_site_card'])
 
 
 
 
 
 
 
 
 
117
 
118
  demo.launch()
 
11
  from src.picture_box_model import create_picture_box_model
12
  from src.extracted_objects_model import create_extracted_objects_model
13
  from src.card_model import create_card_model
14
+ from src.manhole_model import create_manhole_model
15
 
16
  language = os.environ.get('LANGUAGE', 'en')
17
  print(f'LANGUAGE: {language}')
 
65
 
66
  return model_path
67
 
68
+ def create_manhole(image):
69
+ img_bytearray = BytesIO()
70
+ image['background'].save(img_bytearray, "JPEG", quality=95)
71
+ img_bytearray.seek(0) # Seek to the beginning of the image, otherwise it results in empty data.
72
+
73
+ model_path = create_manhole_model(img_bytearray)
74
+
75
+ return model_path
76
+
77
  with gr.Blocks() as demo:
78
 
79
  gr.Markdown(display_message_dict['header'])
 
124
  )
125
 
126
  gr.Markdown(display_message_dict['footer_historic_site_card'])
127
+
128
+ with gr.Tab(display_message_dict["tab_label_manhole"]):
129
+ image = gr.ImageEditor(image_mode='RGB', sources="upload", type="pil", label=display_message_dict['label_image'])
130
+ button = gr.Button(display_message_dict['label_button'])
131
+ button.click(
132
+ create_manhole,
133
+ inputs=[image],
134
+ outputs=[gr.Model3D(camera_position=(90, 90, 5))]
135
+ )
136
 
137
  demo.launch()
requirements.txt CHANGED
@@ -3,4 +3,5 @@ Pillow==9.5.0
3
  numpy==1.23.5
4
  gltflib==1.0.13
5
  triangle==20230923
6
- opencv-python==4.8.0.76
 
 
3
  numpy==1.23.5
4
  gltflib==1.0.13
5
  triangle==20230923
6
+ opencv-python==4.8.0.76
7
+ scipy==1.12.0
src/display_message_en.json CHANGED
@@ -2,6 +2,7 @@
2
  "header": "# 3D Card Creation \nPlease enter various settings, upload an image and specify its range. Then, press the \"Create 3D Card\" button. \nOnce the 3D card is completed, it will be displayed at the bottom of the screen and you will be able to download it.",
3
  "tab_label_card_general": "Card",
4
  "tab_label_historic_site_card": "Historic Site Card",
 
5
  "label_title": "Title",
6
  "placeholder_title": "The title displayed on the top of the card.",
7
  "label_card_type": "Card Type",
@@ -15,7 +16,7 @@
15
  "label_is_thick": "Thickness",
16
  "info_is_thick": "Please check this option if you want to add thickness to the card.",
17
  "label_image": "Image",
18
- "label_button": "Start 3D Card Creation",
19
  "label_color": "Color(Historic Site Classification)",
20
  "color_dict": {
21
  "茶": "Brown: Shell mounds, settlement ruins, ancient tombs, graveyards",
 
2
  "header": "# 3D Card Creation \nPlease enter various settings, upload an image and specify its range. Then, press the \"Create 3D Card\" button. \nOnce the 3D card is completed, it will be displayed at the bottom of the screen and you will be able to download it.",
3
  "tab_label_card_general": "Card",
4
  "tab_label_historic_site_card": "Historic Site Card",
5
+ "tab_label_manhole": "Manhole",
6
  "label_title": "Title",
7
  "placeholder_title": "The title displayed on the top of the card.",
8
  "label_card_type": "Card Type",
 
16
  "label_is_thick": "Thickness",
17
  "info_is_thick": "Please check this option if you want to add thickness to the card.",
18
  "label_image": "Image",
19
+ "label_button": "Start 3D Model Creation",
20
  "label_color": "Color(Historic Site Classification)",
21
  "color_dict": {
22
  "茶": "Brown: Shell mounds, settlement ruins, ancient tombs, graveyards",
src/display_message_ja.json CHANGED
@@ -2,6 +2,7 @@
2
  "header": "# 3Dカード作成 \n各種設定値の入力、画像のアップロードと範囲の指定を行った後、「3Dカード作成」ボタンを押してください。 \n3Dカードが完成したら画面下部に表示され、ダウンロードすることが出来ます。",
3
  "tab_label_card_general": "カード(一般)",
4
  "tab_label_historic_site_card": "史跡カード",
 
5
  "label_title": "タイトル",
6
  "placeholder_title": "カード上部に表示されるタイトルです。",
7
  "label_card_type": "カード種類",
@@ -15,7 +16,7 @@
15
  "label_is_thick": "厚み",
16
  "info_is_thick": "カードに厚みを付けるときはチェックして下さい。",
17
  "label_image": "画像",
18
- "label_button": "3Dカード作成",
19
  "label_color": "色(史跡の分類)",
20
  "color_dict": {
21
  "茶": "「茶」: 貝塚、集落跡、古墳、墓地等",
 
2
  "header": "# 3Dカード作成 \n各種設定値の入力、画像のアップロードと範囲の指定を行った後、「3Dカード作成」ボタンを押してください。 \n3Dカードが完成したら画面下部に表示され、ダウンロードすることが出来ます。",
3
  "tab_label_card_general": "カード(一般)",
4
  "tab_label_historic_site_card": "史跡カード",
5
+ "tab_label_manhole": "マンホール",
6
  "label_title": "タイトル",
7
  "placeholder_title": "カード上部に表示されるタイトルです。",
8
  "label_card_type": "カード種類",
 
16
  "label_is_thick": "厚み",
17
  "info_is_thick": "カードに厚みを付けるときはチェックして下さい。",
18
  "label_image": "画像",
19
+ "label_button": "3Dモデル作成",
20
  "label_color": "色(史跡の分類)",
21
  "color_dict": {
22
  "茶": "「茶」: 貝塚、集落跡、古墳、墓地等",
src/manhole_model.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import numpy as np
3
+ from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
4
+ from PIL import ImageDraw as PIL_ImageDraw # 上記と同じ命名規則にする。
5
+ from scipy.spatial import Delaunay
6
+ import cv2
7
+ import os
8
+ import struct
9
+ import matplotlib.pyplot as plt
10
+ import triangle
11
+ import uuid
12
+
13
+ from gltflib import (
14
+ GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType,
15
+ BufferTarget, ComponentType, GLBResource, FileResource, PBRMetallicRoughness)
16
+
17
+ # Common configuration information
18
+ # Parts that are independent of the binary section's offset and size can be predefined.
19
+
20
+ # Asset
21
+ asset=Asset()
22
+
23
+ # Image
24
+ images=[
25
+ Image(mimeType='image/jpeg', bufferView=4),
26
+ ]
27
+
28
+ # Sampler
29
+ samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング
30
+
31
+ # Texture
32
+ textures = [
33
+ Texture(name='Main',sampler=0,source=0),
34
+ ]
35
+
36
+ # Material
37
+ materials = [
38
+ Material(
39
+ pbrMetallicRoughness=PBRMetallicRoughness(
40
+ baseColorTexture=TextureInfo(index=0),
41
+ metallicFactor=0,
42
+ roughnessFactor=1
43
+ ),
44
+ name='Material0',
45
+ alphaMode='OPAQUE',
46
+ doubleSided=True
47
+ ),
48
+ ]
49
+
50
+ # Mesh
51
+ meshes = [
52
+ Mesh(name='Main', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2),
53
+ indices=3, material=0, mode=4)]),
54
+ ]
55
+
56
+ # Scene
57
+ scene = 0
58
+ scenes = [Scene(name='Scene', nodes=[0])]
59
+
60
+ def create_manhole_model(original_img_bytearray):
61
+
62
+ # Image
63
+ img = PIL_Image.open(original_img_bytearray).convert('RGB')
64
+
65
+ # Add edge color space.
66
+ # (edge color is decided at 'Decide edge color' section)
67
+ temp_img = PIL_Image.new('RGB', (img.size[0] + 3, img.size[1] + 3), 'black')
68
+ temp_img.paste(img, (3, 3))
69
+ img = temp_img.copy()
70
+
71
+ # Get coordinates of manhole
72
+ ret, mask = cv2.threshold(cv2.cvtColor(np.array(img), cv2.COLOR_RGB2GRAY), 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY_INV)
73
+ edges = cv2.Canny(mask , 50, 150, apertureSize=3)
74
+ circle = cv2.HoughCircles(edges,
75
+ cv2.HOUGH_GRADIENT, 1, int(max(img.size)*2),
76
+ param1=50, param2=30, minRadius=0, maxRadius=0)[0][0]
77
+
78
+ coordinate_list = []
79
+ num_polygons = 360
80
+ for i in range(num_polygons):
81
+ coordinate_list.append((
82
+ int(circle[0] + np.cos((i / num_polygons) * (2 * np.pi)) * circle[2]),
83
+ int(circle[1] + np.sin((i / num_polygons) * (2 * np.pi)) * circle[2])
84
+ ))
85
+ coordinate_list = [coordinate_list]
86
+
87
+ # Decide edge color
88
+ coordinate_colors = list(map(lambda x: img.getpixel(x), coordinate_list[0]))
89
+ edge_color = tuple(np.mean(np.array(coordinate_colors), axis=0).astype(np.uint8))
90
+
91
+ draw = PIL_ImageDraw.Draw(img)
92
+ draw.point(tuple((i, j) for i in range(3) for j in range(3)), fill=edge_color)
93
+
94
+ # image binary data
95
+ img_ext = "JPEG"
96
+ img_bytearray = io.BytesIO()
97
+ img.save(img_bytearray, format=img_ext, quality=95)
98
+ img_bytearray = img_bytearray.getvalue()
99
+ img_bytelen = len(img_bytearray)
100
+
101
+ # calculate scale of 3d model
102
+ scale_factor = np.power(img.size[0] * img.size[1], 0.5)
103
+ scale = (img.size[0] / scale_factor, img.size[1] / scale_factor, 0.4)
104
+
105
+ # Thickness of manhole
106
+ thickness = 0.2 # manhole
107
+
108
+ # Vertex data(POSITION)
109
+ front_vertex_list = []
110
+ back_vertex_list = []
111
+ for coordinates in coordinate_list:
112
+ front_vertices = []
113
+ back_vertices = []
114
+ # Be aware that the Y-axis direction is inverted between images and GLB files
115
+ for coordinate in coordinates:
116
+ front_vertices.append((coordinate[0] * 2 / img.size[0] - 1.0, -(coordinate[1] * 2 / img.size[1] - 1.0), thickness))
117
+ back_vertices.append((coordinate[0] * 2 / img.size[0] - 1.0, -(coordinate[1] * 2 / img.size[1] - 1.0), -thickness))
118
+
119
+ front_vertex_list.append(front_vertices)
120
+ back_vertex_list.append(back_vertices)
121
+
122
+ vertices = front_vertex_list[0] + back_vertex_list[0]
123
+ vertex_bytearray = bytearray()
124
+
125
+ # Set vertices with 'Front+Back' and 'Edge'
126
+ for i in range(2): # first for 'Front+Back', second for 'Edge'
127
+ for vertex in vertices:
128
+ for value in vertex:
129
+ vertex_bytearray.extend(struct.pack('f', value))
130
+
131
+ vertex_bytelen = len(vertex_bytearray)
132
+ mins = [min([vertex[i] for vertex in vertices]) for i in range(3)]
133
+ maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)]
134
+
135
+ # Normal data(NORMAL)
136
+ normals = [( 0.0, 0.0, 1.0)] * len(front_vertex_list[0]) + [( 0.0, 0.0, -1.0)] * len(back_vertex_list[0])
137
+
138
+ normal_bytearray = bytearray()
139
+
140
+ # Consider not only 'Front+Back' but also 'Edge'
141
+ for i in range(2): # first for 'Front+Back', second for 'Edge'
142
+ for normal in normals:
143
+ for value in normal:
144
+ normal_bytearray.extend(struct.pack('f', value))
145
+
146
+ normal_bytelen = len(normal_bytearray)
147
+
148
+ # Texture coordinates(TEXCOORD_0)
149
+ texcoord_0s = [
150
+ ((vertex[0] + 1.0) / 2.0, 1.0 - ((vertex[1] + 1.0) / 2.0) ) for vertex in vertices
151
+ ]
152
+ texcoord_0_bytearray = bytearray()
153
+
154
+ # 'Front+Back'
155
+ for texcoord_0 in texcoord_0s:
156
+ for value in texcoord_0:
157
+ texcoord_0_bytearray.extend(struct.pack('f', value))
158
+ # 'Edge'
159
+ for texcoord_0 in texcoord_0s:
160
+ for value in texcoord_0:
161
+ texcoord_0_bytearray.extend(struct.pack('f', 0.0))
162
+
163
+ texcoord_0_bytelen = len(texcoord_0_bytearray)
164
+
165
+ # Vertex indices
166
+ polygon = {
167
+ 'vertices': np.array(front_vertex_list[0])[:, :2],
168
+ 'segments': np.array([( i, (i + 1) % (len(front_vertex_list[0])) ) for i in range(len(front_vertex_list[0]))])
169
+ }
170
+ triangulate_result = triangle.triangulate(polygon, 'p')
171
+
172
+ vertex_indices = []
173
+
174
+ # Front
175
+ vertex_indices.extend(list(np.array(triangulate_result['triangles']).flatten()))
176
+
177
+ # Back
178
+ temp_list = list((np.array(triangulate_result['triangles'])+len(front_vertex_list[0])).flatten())
179
+ # Swap vertex indices to get Back vertex indices from Front vertex indices
180
+ def swap_elements(lst):
181
+ if len(lst) < 2:
182
+ return lst
183
+ for i in range(len(lst) - 1):
184
+ if i % 3 == 1:
185
+ lst[i], lst[i + 1] = lst[i + 1], lst[i]
186
+
187
+ return lst
188
+
189
+ temp_list = swap_elements(temp_list)
190
+ vertex_indices.extend(temp_list) # Back
191
+
192
+ # Edge
193
+ vertex_indices.extend(list(np.array([[len(vertices) + i,
194
+ len(vertices) + (i + 1) % len(front_vertex_list[0]),
195
+ len(vertices) + len(front_vertex_list[0]) + i]
196
+ for i in range(len(front_vertex_list[0]))]).flatten()))
197
+ vertex_indices.extend(list(np.array([[len(vertices) + len(front_vertex_list[0]) + (i + 1) % len(front_vertex_list[0]),
198
+ len(vertices) + len(front_vertex_list[0]) + i,
199
+ len(vertices) + (i + 1) % len(front_vertex_list[0])]
200
+ for i in range(len(front_vertex_list[0]))]).flatten()))
201
+
202
+ vertex_index_bytearray = bytearray()
203
+ for value in vertex_indices:
204
+ vertex_index_bytearray.extend(struct.pack('H', value))
205
+ vertex_index_bytelen = len(vertex_index_bytearray)
206
+
207
+ # Concatenate binar data
208
+ bytearray_list = [
209
+ vertex_bytearray,
210
+ normal_bytearray,
211
+ texcoord_0_bytearray,
212
+ vertex_index_bytearray,
213
+ img_bytearray,
214
+ ]
215
+ bytelen_list = [
216
+ vertex_bytelen,
217
+ normal_bytelen,
218
+ texcoord_0_bytelen,
219
+ vertex_index_bytelen,
220
+ img_bytelen,
221
+ ]
222
+ bytelen_cumsum_list = list(np.cumsum(bytelen_list))
223
+ bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list))
224
+
225
+ all_bytearray = bytearray()
226
+ for temp_bytearray in bytearray_list:
227
+ all_bytearray.extend(temp_bytearray)
228
+ offset_list = [0] + bytelen_cumsum_list # First offset is 0
229
+ offset_list.pop() # Delete the last element
230
+
231
+ # Resource
232
+ resources = [GLBResource(data=all_bytearray)]
233
+
234
+ # Buffer
235
+ buffers = [Buffer(byteLength=len(all_bytearray))]
236
+
237
+ # BufferView
238
+ bufferViews = [
239
+ BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value),
240
+ BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value),
241
+ BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value),
242
+ BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value),
243
+ BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None),
244
+ ]
245
+
246
+ # Accessor
247
+ accessors = [
248
+ Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices*2), type=AccessorType.VEC3.value, max=maxs, min=mins),
249
+ Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals*2), type=AccessorType.VEC3.value, max=None, min=None),
250
+ Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s*2), type=AccessorType.VEC2.value, max=None, min=None),
251
+ Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None)
252
+ ]
253
+
254
+ # Node
255
+ nodes = [
256
+ Node(mesh=0,rotation=None, scale=scale),
257
+ ]
258
+
259
+ model = GLTFModel(
260
+ asset=asset,
261
+ buffers=buffers,
262
+ bufferViews=bufferViews,
263
+ accessors=accessors,
264
+ images=images,
265
+ samplers=samplers,
266
+ textures=textures,
267
+ materials=materials,
268
+ meshes=meshes,
269
+ nodes=nodes,
270
+ scene=scene,
271
+ scenes=scenes,
272
+ )
273
+
274
+ gltf = GLTF(model=model, resources=resources)
275
+
276
+ tmp_filename = uuid.uuid4().hex
277
+ model_path = f'../tmp/{tmp_filename}.glb'
278
+
279
+ gltf.export(model_path)
280
+
281
+ return model_path