Skip to content

Commit a0d76a7

Browse files
bottlerfacebook-github-bot
authored andcommitted
join_scene fix for TexturesUV
Summary: Fix issue #826. This is a correction to the joining of TexturesUV into a single scene. Reviewed By: nikhilaravi Differential Revision: D30767092 fbshipit-source-id: 03ba6a1d2f22e569d1b3641cd13ddbb8dcb87ec7
1 parent 46f727c commit a0d76a7

File tree

6 files changed

+74
-16
lines changed

6 files changed

+74
-16
lines changed

pytorch3d/renderer/mesh/textures.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,22 +1157,46 @@ def join_scene(self) -> "TexturesUV":
11571157
new_uvs = torch.Tensor(new_uvs)
11581158

11591159
# If align_corners is True, then an index of x (where x is in
1160-
# the range 0 .. map_.shape[]-1) in one of the input maps
1161-
# was hit by a u of x/(map_.shape[]-1).
1162-
# That x is located at the index loc[] + x in the single_map, and
1163-
# to hit that we need u to equal (loc[] + x) / (total_size[]-1)
1160+
# the range 0 .. map_.shape[1]-1) in one of the input maps
1161+
# was hit by a u of x/(map_.shape[1]-1).
1162+
# That x is located at the index loc[1] + x in the single_map, and
1163+
# to hit that we need u to equal (loc[1] + x) / (total_size[1]-1)
11641164
# so the old u should be mapped to
1165-
# { u*(map_.shape[]-1) + loc[] } / (total_size[]-1)
1165+
# { u*(map_.shape[1]-1) + loc[1] } / (total_size[1]-1)
1166+
1167+
# Also, an index of y (where y is in
1168+
# the range 0 .. map_.shape[0]-1) in one of the input maps
1169+
# was hit by a v of 1 - y/(map_.shape[0]-1).
1170+
# That y is located at the index loc[0] + y in the single_map, and
1171+
# to hit that we need v to equal 1 - (loc[0] + y) / (total_size[0]-1)
1172+
# so the old v should be mapped to
1173+
# 1 - { (1-v)*(map_.shape[0]-1) + loc[0] } / (total_size[0]-1)
1174+
# =
1175+
# { v*(map_.shape[0]-1) + total_size[0] - map.shape[0] - loc[0] }
1176+
# / (total_size[0]-1)
11661177

11671178
# If align_corners is False, then an index of x (where x is in
1168-
# the range 1 .. map_.shape[]-2) in one of the input maps
1169-
# was hit by a u of (x+0.5)/(map_.shape[]).
1170-
# That x is located at the index loc[] + 1 + x in the single_map,
1179+
# the range 1 .. map_.shape[1]-2) in one of the input maps
1180+
# was hit by a u of (x+0.5)/(map_.shape[1]).
1181+
# That x is located at the index loc[1] + 1 + x in the single_map,
11711182
# (where the 1 is for the border)
1172-
# and to hit that we need u to equal (loc[] + 1 + x + 0.5) / (total_size[])
1183+
# and to hit that we need u to equal (loc[1] + 1 + x + 0.5) / (total_size[1])
11731184
# so the old u should be mapped to
1174-
# { loc[] + 1 + u*map_.shape[]-0.5 + 0.5 } / (total_size[])
1175-
# = { loc[] + 1 + u*map_.shape[] } / (total_size[])
1185+
# { loc[1] + 1 + u*map_.shape[1]-0.5 + 0.5 } / (total_size[1])
1186+
# = { loc[1] + 1 + u*map_.shape[1] } / (total_size[1])
1187+
1188+
# Also, an index of y (where y is in
1189+
# the range 1 .. map_.shape[0]-2) in one of the input maps
1190+
# was hit by a v of 1 - (y+0.5)/(map_.shape[0]).
1191+
# That y is located at the index loc[0] + 1 + y in the single_map,
1192+
# (where the 1 is for the border)
1193+
# and to hit that we need v to equal 1 - (loc[0] + 1 + y + 0.5) / (total_size[0])
1194+
# so the old v should be mapped to
1195+
# 1 - { loc[0] + 1 + (1-v)*map_.shape[0]-0.5 + 0.5 } / (total_size[0])
1196+
# = { total_size[0] - loc[0] -1 - (1-v)*map_.shape[0] }
1197+
# / (total_size[0])
1198+
# = { total_size[0] - loc[0] - map.shape[0] - 1 + v*map_.shape[0] }
1199+
# / (total_size[0])
11761200

11771201
# We change the y's in new_uvs for the scaling of height,
11781202
# and the x's for the scaling of width.
@@ -1184,7 +1208,9 @@ def join_scene(self) -> "TexturesUV":
11841208
denom_y = merging_plan.total_size[1] - one_if_align
11851209
scale_y = y_shape - one_if_align
11861210
new_uvs[:, 1] *= scale_x / denom_x
1187-
new_uvs[:, 1] += (loc.x + one_if_not_align) / denom_x
1211+
new_uvs[:, 1] += (
1212+
merging_plan.total_size[0] - x_shape - loc.x - one_if_not_align
1213+
) / denom_x
11881214
new_uvs[:, 0] *= scale_y / denom_y
11891215
new_uvs[:, 0] += (loc.y + one_if_not_align) / denom_y
11901216

pytorch3d/vis/texture_vis.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ def texturesuv_image_matplotlib(
3636
color: any matplotlib-understood color for the circles.
3737
subsample: if not None, number of points to plot.
3838
Otherwise all points are plotted.
39-
origin: "upper" or "lower" like matplotlib.imshow
39+
origin: "upper" or "lower" like matplotlib.imshow .
40+
upper (the default) matches texturesuv_image_PIL.
4041
"""
4142

4243
import matplotlib.pyplot as plt

tests/data/test_joinuvs0_final.png

148 Bytes
Loading

tests/data/test_joinuvs1_final.png

12 Bytes
Loading

tests/data/test_joinuvs2_final.png

164 Bytes
Loading

tests/test_render_meshes.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ def test_join_uvs(self):
753753
Image.fromarray((output.numpy() * 255).astype(np.uint8)).save(
754754
DATA_DIR / f"test_joinuvs{i}_final_.png"
755755
)
756-
Image.fromarray((output.numpy() * 255).astype(np.uint8)).save(
756+
Image.fromarray((merged.numpy() * 255).astype(np.uint8)).save(
757757
DATA_DIR / f"test_joinuvs{i}_merged.png"
758758
)
759759

@@ -782,10 +782,41 @@ def test_join_uvs(self):
782782
)
783783
).save(DATA_DIR / f"test_joinuvs{i}_map3.png")
784784

785-
self.assertClose(output, merged, atol=0.015)
786-
self.assertClose(output, image_ref, atol=0.05)
785+
self.assertClose(output, merged)
786+
self.assertClose(output, image_ref, atol=0.005)
787787
self.assertClose(mesh.textures.maps_padded()[0].cpu(), map_ref, atol=0.05)
788788

789+
def test_join_uvs_simple(self):
790+
# Example from issue #826
791+
a = TexturesUV(
792+
maps=torch.full((1, 4000, 4000, 3), 0.8),
793+
faces_uvs=torch.arange(300).reshape(1, 100, 3),
794+
verts_uvs=torch.rand(1, 300, 2) * 0.4 + 0.1,
795+
)
796+
b = TexturesUV(
797+
maps=torch.full((1, 2000, 2000, 3), 0.7),
798+
faces_uvs=torch.arange(150).reshape(1, 50, 3),
799+
verts_uvs=torch.rand(1, 150, 2) * 0.2 + 0.3,
800+
)
801+
c = a.join_batch([b]).join_scene()
802+
803+
color = c.faces_verts_textures_packed()
804+
color1 = color[:100, :, 0].flatten()
805+
color2 = color[100:, :, 0].flatten()
806+
expect1 = color1.new_tensor(0.8)
807+
expect2 = color2.new_tensor(0.7)
808+
self.assertClose(color1.min(), expect1)
809+
self.assertClose(color1.max(), expect1)
810+
self.assertClose(color2.min(), expect2)
811+
self.assertClose(color2.max(), expect2)
812+
813+
if DEBUG:
814+
from pytorch3d.vis.texture_vis import texturesuv_image_PIL as PI
815+
816+
PI(a, radius=5).save(DATA_DIR / "test_join_uvs_simple_a.png")
817+
PI(b, radius=5).save(DATA_DIR / "test_join_uvs_simple_b.png")
818+
PI(c, radius=5).save(DATA_DIR / "test_join_uvs_simple_c.png")
819+
789820
def test_join_verts(self):
790821
"""Meshes with TexturesVertex joined into a scene"""
791822
# Test the result of rendering two tori with separate textures.

0 commit comments

Comments
 (0)