2
2
3
3
import itertools
4
4
import warnings
5
- from typing import Dict , List , Optional , Tuple , Union
5
+ from typing import TYPE_CHECKING , Dict , List , Optional , Tuple , Union
6
6
7
7
import torch
8
8
import torch .nn .functional as F
9
9
from pytorch3d .ops import interpolate_face_attributes
10
10
from pytorch3d .structures .utils import list_to_packed , list_to_padded , padded_to_list
11
11
from torch .nn .functional import interpolate
12
12
13
+ from .utils import pack_rectangles
14
+
13
15
14
16
# This file contains classes and helper functions for texturing.
15
17
# There are three types of textures: TexturesVertex, TexturesAtlas
@@ -329,18 +331,23 @@ def __init__(self, atlas: Union[torch.Tensor, List, None]):
329
331
330
332
[1] Liu et al, 'Soft Rasterizer: A Differentiable Renderer for Image-based
331
333
3D Reasoning', ICCV 2019
334
+ See also https://github.com/ShichenLiu/SoftRas/issues/21
332
335
"""
333
336
if isinstance (atlas , (list , tuple )):
334
337
correct_format = all (
335
338
(
336
339
torch .is_tensor (elem )
337
340
and elem .ndim == 4
338
341
and elem .shape [1 ] == elem .shape [2 ]
342
+ and elem .shape [1 ] == atlas [0 ].shape [1 ]
339
343
)
340
344
for elem in atlas
341
345
)
342
346
if not correct_format :
343
- msg = "Expected atlas to be a list of tensors of shape (F, R, R, D)"
347
+ msg = (
348
+ "Expected atlas to be a list of tensors of shape (F, R, R, D) "
349
+ "with the same value of R."
350
+ )
344
351
raise ValueError (msg )
345
352
self ._atlas_list = atlas
346
353
self ._atlas_padded = None
@@ -529,6 +536,12 @@ def join_batch(self, textures: List["TexturesAtlas"]) -> "TexturesAtlas":
529
536
new_tex ._num_faces_per_mesh = num_faces_per_mesh
530
537
return new_tex
531
538
539
+ def join_scene (self ) -> "TexturesAtlas" :
540
+ """
541
+ Return a new TexturesAtlas amalgamating the batch.
542
+ """
543
+ return self .__class__ (atlas = [torch .cat (self .atlas_list ())])
544
+
532
545
533
546
class TexturesUV (TexturesBase ):
534
547
def __init__ (
@@ -560,7 +573,7 @@ def __init__(
560
573
the two align_corners options at
561
574
https://discuss.pytorch.org/t/22663/9 .
562
575
563
- An example of how the indexing into the maps, with align_corners=True
576
+ An example of how the indexing into the maps, with align_corners=True,
564
577
works is as follows.
565
578
If maps[i] has shape [101, 1001] and the value of verts_uvs[i][j]
566
579
is [0.4, 0.3], then a value of j in faces_uvs[i] means a vertex
@@ -574,10 +587,11 @@ def __init__(
574
587
If maps[i] has shape [100, 1000] and the value of verts_uvs[i][j]
575
588
is [0.405, 0.2995], then a value of j in faces_uvs[i] means a vertex
576
589
whose color is given by maps[i][700, 40].
577
- In this case, padding_mode even matters for values in verts_uvs
578
- slightly above 0 or slightly below 1. In this case, it matters if the
579
- first value is outside the interval [0.0005, 0.9995] or if the second
580
- is outside the interval [0.005, 0.995].
590
+ When align_corners=False, padding_mode even matters for values in
591
+ verts_uvs slightly above 0 or slightly below 1. In this case, the
592
+ padding_mode matters if the first value is outside the interval
593
+ [0.0005, 0.9995] or if the second is outside the interval
594
+ [0.005, 0.995].
581
595
"""
582
596
super ().__init__ ()
583
597
self .padding_mode = padding_mode
@@ -805,12 +819,9 @@ def verts_uvs_list(self) -> List[torch.Tensor]:
805
819
def maps_padded (self ) -> torch .Tensor :
806
820
return self ._maps_padded
807
821
808
- def maps_list (self ) -> torch .Tensor :
809
- # maps_list is not used anywhere currently - maps
810
- # are padded to ensure the (H, W) of all maps is the
811
- # same across the batch and we don't store the
812
- # unpadded sizes of the maps. Therefore just return
813
- # the unbinded padded tensor.
822
+ def maps_list (self ) -> List [torch .Tensor ]:
823
+ if self ._maps_list is not None :
824
+ return self ._maps_list
814
825
return self ._maps_padded .unbind (0 )
815
826
816
827
def extend (self , N : int ) -> "TexturesUV" :
@@ -965,6 +976,143 @@ def join_batch(self, textures: List["TexturesUV"]) -> "TexturesUV":
965
976
new_tex ._num_faces_per_mesh = num_faces_per_mesh
966
977
return new_tex
967
978
979
+ def _place_map_into_single_map (
980
+ self ,
981
+ single_map : torch .Tensor ,
982
+ map_ : torch .Tensor ,
983
+ location : Tuple [int , int , bool ], # (x,y) and whether flipped
984
+ ) -> None :
985
+ """
986
+ Copy map into a larger tensor single_map at the destination specified by location.
987
+ If align_corners is False, we add the needed border around the destination.
988
+
989
+ Used by join_scene.
990
+
991
+ Args:
992
+ single_map: (total_H, total_W, 3)
993
+ map_: (H, W, 3) source data
994
+ location: where to place map
995
+ """
996
+ do_flip = location [2 ]
997
+ source = map_ .transpose (0 , 1 ) if do_flip else map_
998
+ border_width = 0 if self .align_corners else 1
999
+ lower_u = location [0 ] + border_width
1000
+ lower_v = location [1 ] + border_width
1001
+ upper_u = lower_u + source .shape [0 ]
1002
+ upper_v = lower_v + source .shape [1 ]
1003
+ single_map [lower_u :upper_u , lower_v :upper_v ] = source
1004
+
1005
+ if self .padding_mode != "zeros" and not self .align_corners :
1006
+ single_map [lower_u - 1 , lower_v :upper_v ] = single_map [
1007
+ lower_u , lower_v :upper_v
1008
+ ]
1009
+ single_map [upper_u , lower_v :upper_v ] = single_map [
1010
+ upper_u - 1 , lower_v :upper_v
1011
+ ]
1012
+ single_map [lower_u :upper_u , lower_v - 1 ] = single_map [
1013
+ lower_u :upper_u , lower_v
1014
+ ]
1015
+ single_map [lower_u :upper_u , upper_v ] = single_map [
1016
+ lower_u :upper_u , upper_v - 1
1017
+ ]
1018
+ single_map [lower_u - 1 , lower_v - 1 ] = single_map [lower_u , lower_v ]
1019
+ single_map [lower_u - 1 , upper_v ] = single_map [lower_u , upper_v - 1 ]
1020
+ single_map [upper_u , lower_v - 1 ] = single_map [upper_u - 1 , lower_v ]
1021
+ single_map [upper_u , upper_v ] = single_map [upper_u - 1 , upper_v - 1 ]
1022
+
1023
+ def join_scene (self ) -> "TexturesUV" :
1024
+ """
1025
+ Return a new TexturesUV amalgamating the batch.
1026
+
1027
+ We calculate a large single map which contains the original maps,
1028
+ and find verts_uvs to point into it. This will not replicate
1029
+ behavior of padding for verts_uvs values outside [0,1].
1030
+
1031
+ If align_corners=False, we need to add an artificial border around
1032
+ every map.
1033
+
1034
+ We use the function `pack_rectangles` to provide a layout for the
1035
+ single map. _place_map_into_single_map is used to copy the maps
1036
+ into the single map. The merging of verts_uvs and faces_uvs are
1037
+ handled locally in this function.
1038
+ """
1039
+ maps = self .maps_list ()
1040
+ heights_and_widths = []
1041
+ extra_border = 0 if self .align_corners else 2
1042
+ for map_ in maps :
1043
+ heights_and_widths .append (
1044
+ (map_ .shape [0 ] + extra_border , map_ .shape [1 ] + extra_border )
1045
+ )
1046
+ merging_plan = pack_rectangles (heights_and_widths )
1047
+ # pyre-fixme[16]: `Tensor` has no attribute `new_zeros`.
1048
+ single_map = maps [0 ].new_zeros ((* merging_plan .total_size , 3 ))
1049
+ verts_uvs = self .verts_uvs_list ()
1050
+ verts_uvs_merged = []
1051
+
1052
+ for map_ , loc , uvs in zip (maps , merging_plan .locations , verts_uvs ):
1053
+ new_uvs = uvs .clone ()
1054
+ self ._place_map_into_single_map (single_map , map_ , loc )
1055
+ do_flip = loc [2 ]
1056
+ x_shape = map_ .shape [1 ] if do_flip else map_ .shape [0 ]
1057
+ y_shape = map_ .shape [0 ] if do_flip else map_ .shape [1 ]
1058
+
1059
+ if do_flip :
1060
+ # Here we have flipped / transposed the map.
1061
+ # In uvs, the y values are decreasing from 1 to 0 and the x
1062
+ # values increase from 0 to 1. We subtract all values from 1
1063
+ # as the x's become y's and the y's become x's.
1064
+ new_uvs = 1.0 - new_uvs [:, [1 , 0 ]]
1065
+ if TYPE_CHECKING :
1066
+ new_uvs = torch .Tensor (new_uvs )
1067
+
1068
+ # If align_corners is True, then an index of x (where x is in
1069
+ # the range 0 .. map_.shape[]-1) in one of the input maps
1070
+ # was hit by a u of x/(map_.shape[]-1).
1071
+ # That x is located at the index loc[] + x in the single_map, and
1072
+ # to hit that we need u to equal (loc[] + x) / (total_size[]-1)
1073
+ # so the old u should be mapped to
1074
+ # { u*(map_.shape[]-1) + loc[] } / (total_size[]-1)
1075
+
1076
+ # If align_corners is False, then an index of x (where x is in
1077
+ # the range 1 .. map_.shape[]-2) in one of the input maps
1078
+ # was hit by a u of (x+0.5)/(map_.shape[]).
1079
+ # That x is located at the index loc[] + 1 + x in the single_map,
1080
+ # (where the 1 is for the border)
1081
+ # and to hit that we need u to equal (loc[] + 1 + x + 0.5) / (total_size[])
1082
+ # so the old u should be mapped to
1083
+ # { loc[] + 1 + u*map_.shape[]-0.5 + 0.5 } / (total_size[])
1084
+ # = { loc[] + 1 + u*map_.shape[] } / (total_size[])
1085
+
1086
+ # We change the y's in new_uvs for the scaling of height,
1087
+ # and the x's for the scaling of width.
1088
+ # That is why the 1's and 0's are mismatched in these lines.
1089
+ one_if_align = 1 if self .align_corners else 0
1090
+ one_if_not_align = 1 - one_if_align
1091
+ denom_x = merging_plan .total_size [0 ] - one_if_align
1092
+ scale_x = x_shape - one_if_align
1093
+ denom_y = merging_plan .total_size [1 ] - one_if_align
1094
+ scale_y = y_shape - one_if_align
1095
+ new_uvs [:, 1 ] *= scale_x / denom_x
1096
+ new_uvs [:, 1 ] += (loc [0 ] + one_if_not_align ) / denom_x
1097
+ new_uvs [:, 0 ] *= scale_y / denom_y
1098
+ new_uvs [:, 0 ] += (loc [1 ] + one_if_not_align ) / denom_y
1099
+
1100
+ verts_uvs_merged .append (new_uvs )
1101
+
1102
+ faces_uvs_merged = []
1103
+ offset = 0
1104
+ for faces_uvs_ , verts_uvs_ in zip (self .faces_uvs_list (), verts_uvs ):
1105
+ faces_uvs_merged .append (offset + faces_uvs_ )
1106
+ offset += verts_uvs_ .shape [0 ]
1107
+
1108
+ return self .__class__ (
1109
+ maps = [single_map ],
1110
+ verts_uvs = [torch .cat (verts_uvs_merged )],
1111
+ faces_uvs = [torch .cat (faces_uvs_merged )],
1112
+ align_corners = self .align_corners ,
1113
+ padding_mode = self .padding_mode ,
1114
+ )
1115
+
968
1116
969
1117
class TexturesVertex (TexturesBase ):
970
1118
def __init__ (
@@ -1156,3 +1304,9 @@ def join_batch(self, textures: List["TexturesVertex"]) -> "TexturesVertex":
1156
1304
new_tex = self .__class__ (verts_features = verts_features_list )
1157
1305
new_tex ._num_verts_per_mesh = num_faces_per_mesh
1158
1306
return new_tex
1307
+
1308
+ def join_scene (self ) -> "TexturesVertex" :
1309
+ """
1310
+ Return a new TexturesVertex amalgamating the batch.
1311
+ """
1312
+ return self .__class__ (verts_features = [torch .cat (self .verts_features_list ())])
0 commit comments