Пример #1
0
def _save_ply(
    f,
    verts: torch.Tensor,
    faces: torch.LongTensor,
    verts_normals: torch.Tensor,
    decimal_places: Optional[int] = None,
) -> None:
    """
    Internal implementation for saving 3D data to a .ply file.

    Args:
        f: File object to which the 3D data should be written.
        verts: FloatTensor of shape (V, 3) giving vertex coordinates.
        faces: LongTensor of shsape (F, 3) giving faces.
        verts_normals: FloatTensor of shape (V, 3) giving vertex normals.
        decimal_places: Number of decimal places for saving.
    """
    assert not len(verts) or (verts.dim() == 2 and verts.size(1) == 3)
    assert not len(faces) or (faces.dim() == 2 and faces.size(1) == 3)
    assert not len(verts_normals) or (verts_normals.dim() == 2
                                      and verts_normals.size(1) == 3)

    print("ply\nformat ascii 1.0", file=f)
    print(f"element vertex {verts.shape[0]}", file=f)
    print("property float x", file=f)
    print("property float y", file=f)
    print("property float z", file=f)
    if verts_normals.numel() > 0:
        print("property float nx", file=f)
        print("property float ny", file=f)
        print("property float nz", file=f)
    print(f"element face {faces.shape[0]}", file=f)
    print("property list uchar int vertex_index", file=f)
    print("end_header", file=f)

    if not (len(verts) or len(faces)):
        warnings.warn("Empty 'verts' and 'faces' arguments provided")
        return

    if decimal_places is None:
        float_str = "%f"
    else:
        float_str = "%" + ".%df" % decimal_places

    vert_data = torch.cat((verts, verts_normals), dim=1)
    np.savetxt(f, vert_data.detach().numpy(), float_str)

    faces_array = faces.detach().numpy()

    _check_faces_indices(faces, max_index=verts.shape[0])

    if len(faces_array):
        np.savetxt(f, faces_array, "3 %d %d %d")
Пример #2
0
def _write_off_data(
    file,
    verts: torch.Tensor,
    verts_colors: Optional[torch.Tensor] = None,
    faces: Optional[torch.LongTensor] = None,
    faces_colors: Optional[torch.Tensor] = None,
    decimal_places: Optional[int] = None,
) -> None:
    """
    Internal implementation for saving 3D data to a .off file.

    Args:
        file: Binary file object to which the 3D data should be written.
        verts: FloatTensor of shape (V, 3) giving vertex coordinates.
        verts_colors: FloatTensor of shape (V, C) giving vertex colors where C is 3 or 4.
        faces: LongTensor of shape (F, 3) giving faces.
        faces_colors: FloatTensor of shape (V, C) giving face colors where C is 3 or 4.
        decimal_places: Number of decimal places for saving.
    """
    nfaces = 0 if faces is None else faces.shape[0]
    file.write(f"off\n{verts.shape[0]} {nfaces} 0\n".encode("ascii"))

    if verts_colors is not None:
        verts = torch.cat((verts, verts_colors), dim=1)
    if decimal_places is None:
        float_str = "%f"
    else:
        float_str = "%" + ".%df" % decimal_places
    np.savetxt(file, verts.cpu().detach().numpy(), float_str)

    if faces is not None:
        _check_faces_indices(faces, max_index=verts.shape[0])

    if faces_colors is not None:
        face_data = torch.cat(
            [
                cast(torch.Tensor, faces).cpu().to(torch.float64),
                faces_colors.detach().cpu().to(torch.float64),
            ],
            dim=1,
        )
        format = "3 %d %d %d" + " %f" * faces_colors.shape[1]
        np.savetxt(file, face_data.numpy(), format)
    elif faces is not None:
        np.savetxt(file, faces.cpu().detach().numpy(), "3 %d %d %d")
Пример #3
0
def _format_faces_indices(faces_indices, max_index: int, device, pad_value=None):
    """
    Format indices and check for invalid values. Indices can refer to
    values in one of the face properties: vertices, textures or normals.
    See comments of the load_obj function for more details.

    Args:
        faces_indices: List of ints of indices.
        max_index: Max index for the face property.
        pad_value: if any of the face_indices are padded, specify
            the value of the padding (e.g. -1). This is only used
            for texture indices indices where there might
            not be texture information for all the faces.

    Returns:
        faces_indices: List of ints of indices.

    Raises:
        ValueError if indices are not in a valid range.
    """
    faces_indices = _make_tensor(
        faces_indices, cols=3, dtype=torch.int64, device=device
    )

    if pad_value is not None:
        # pyre-fixme[28]: Unexpected keyword argument `dim`.
        mask = faces_indices.eq(pad_value).all(dim=-1)

    # Change to 0 based indexing.
    faces_indices[(faces_indices > 0)] -= 1

    # Negative indexing counts from the end.
    faces_indices[(faces_indices < 0)] += max_index

    if pad_value is not None:
        # pyre-fixme[61]: `mask` is undefined, or not always defined.
        faces_indices[mask] = pad_value

    return _check_faces_indices(faces_indices, max_index, pad_value)
Пример #4
0
def _save_ply(
    f,
    verts: torch.Tensor,
    faces: torch.LongTensor,
    verts_normals: torch.Tensor,
    ascii: bool,
    decimal_places: Optional[int] = None,
) -> None:
    """
    Internal implementation for saving 3D data to a .ply file.

    Args:
        f: File object to which the 3D data should be written.
        verts: FloatTensor of shape (V, 3) giving vertex coordinates.
        faces: LongTensor of shsape (F, 3) giving faces.
        verts_normals: FloatTensor of shape (V, 3) giving vertex normals.
        ascii: (bool) whether to use the ascii ply format.
        decimal_places: Number of decimal places for saving if ascii=True.
    """
    assert not len(verts) or (verts.dim() == 2 and verts.size(1) == 3)
    assert not len(faces) or (faces.dim() == 2 and faces.size(1) == 3)
    assert not len(verts_normals) or (verts_normals.dim() == 2
                                      and verts_normals.size(1) == 3)

    if ascii:
        f.write(b"ply\nformat ascii 1.0\n")
    elif sys.byteorder == "big":
        f.write(b"ply\nformat binary_big_endian 1.0\n")
    else:
        f.write(b"ply\nformat binary_little_endian 1.0\n")
    f.write(f"element vertex {verts.shape[0]}\n".encode("ascii"))
    f.write(b"property float x\n")
    f.write(b"property float y\n")
    f.write(b"property float z\n")
    if verts_normals.numel() > 0:
        f.write(b"property float nx\n")
        f.write(b"property float ny\n")
        f.write(b"property float nz\n")
    f.write(f"element face {faces.shape[0]}\n".encode("ascii"))
    f.write(b"property list uchar int vertex_index\n")
    f.write(b"end_header\n")

    if not (len(verts) or len(faces)):
        warnings.warn("Empty 'verts' and 'faces' arguments provided")
        return

    vert_data = torch.cat((verts, verts_normals), dim=1).detach().numpy()
    if ascii:
        if decimal_places is None:
            float_str = "%f"
        else:
            float_str = "%" + ".%df" % decimal_places
        np.savetxt(f, vert_data, float_str)
    else:
        assert vert_data.dtype == np.float32
        if isinstance(f, BytesIO):
            # tofile only works with real files, but is faster than this.
            f.write(vert_data.tobytes())
        else:
            vert_data.tofile(f)

    faces_array = faces.detach().numpy()

    _check_faces_indices(faces, max_index=verts.shape[0])

    if len(faces_array):
        if ascii:
            np.savetxt(f, faces_array, "3 %d %d %d")
        else:
            # rows are 13 bytes: a one-byte 3 followed by three four-byte face indices.
            faces_uints = np.full((len(faces_array), 13), 3, dtype=np.uint8)
            faces_uints[:, 1:] = faces_array.astype(np.uint32).view(np.uint8)
            if isinstance(f, BytesIO):
                f.write(faces_uints.tobytes())
            else:
                faces_uints.tofile(f)
Пример #5
0
def load_ply(f, path_manager: Optional[PathManager] = None):
    """
    Load the data from a .ply file.

    Example .ply file format:

    ply
    format ascii 1.0           { ascii/binary, format version number }
    comment made by Greg Turk  { comments keyword specified, like all lines }
    comment this file is a cube
    element vertex 8           { define "vertex" element, 8 of them in file }
    property float x           { vertex contains float "x" coordinate }
    property float y           { y coordinate is also a vertex property }
    property float z           { z coordinate, too }
    element face 6             { there are 6 "face" elements in the file }
    property list uchar int vertex_index { "vertex_indices" is a list of ints }
    end_header                 { delimits the end of the header }
    0 0 0                      { start of vertex list }
    0 0 1
    0 1 1
    0 1 0
    1 0 0
    1 0 1
    1 1 1
    1 1 0
    4 0 1 2 3                  { start of face list }
    4 7 6 5 4
    4 0 4 5 1
    4 1 5 6 2
    4 2 6 7 3
    4 3 7 4 0

    Args:
        f:  A binary or text file-like object (with methods read, readline,
            tell and seek), a pathlib path or a string containing a file name.
            If the ply file is in the binary ply format rather than the text
            ply format, then a text stream is not supported.
            It is easiest to use a binary stream in all cases.
        path_manager: PathManager for loading if f is a str.


    Returns:
        verts: FloatTensor of shape (V, 3).
        faces: LongTensor of vertex indices, shape (F, 3).
    """
    if path_manager is None:
        path_manager = PathManager()
    header, elements = _load_ply_raw(f, path_manager=path_manager)

    vertex = elements.get("vertex", None)
    if vertex is None:
        raise ValueError("The ply file has no vertex element.")

    face = elements.get("face", None)
    if face is None:
        raise ValueError("The ply file has no face element.")

    if len(vertex) and (not isinstance(vertex, np.ndarray) or vertex.ndim != 2
                        or vertex.shape[1] != 3):
        raise ValueError("Invalid vertices in file.")
    verts = _make_tensor(vertex, cols=3, dtype=torch.float32)

    face_head = next(head for head in header.elements if head.name == "face")
    if len(face_head.properties
           ) != 1 or face_head.properties[0].list_size_type is None:
        raise ValueError("Unexpected form of faces data.")
    # face_head.properties[0].name is usually "vertex_index" or "vertex_indices"
    # but we don't need to enforce this.

    if not len(face):
        # pyre-fixme[28]: Unexpected keyword argument `size`.
        faces = torch.zeros(size=(0, 3), dtype=torch.int64)
    elif isinstance(face,
                    np.ndarray) and face.ndim == 2:  # Homogeneous elements
        if face.shape[1] < 3:
            raise ValueError("Faces must have at least 3 vertices.")
        face_arrays = [
            face[:, [0, i + 1, i + 2]] for i in range(face.shape[1] - 2)
        ]
        faces = torch.LongTensor(np.vstack(face_arrays))
    else:
        face_list = []
        for face_item in face:
            if face_item.ndim != 1:
                raise ValueError("Bad face data.")
            if face_item.shape[0] < 3:
                raise ValueError("Faces must have at least 3 vertices.")
            for i in range(face_item.shape[0] - 2):
                face_list.append(
                    [face_item[0], face_item[i + 1], face_item[i + 2]])
        # pyre-fixme[6]: Expected `dtype` for 3rd param but got `Type[torch.int64]`.
        faces = _make_tensor(face_list, cols=3, dtype=torch.int64)

    _check_faces_indices(faces, max_index=verts.shape[0])
    return verts, faces
Пример #6
0
def _load_ply(
    f,
    *,
    path_manager: PathManager,
    return_vertex_colors: bool = False
) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
    """
    Load the data from a .ply file.

    Args:
        f:  A binary or text file-like object (with methods read, readline,
            tell and seek), a pathlib path or a string containing a file name.
            If the ply file is in the binary ply format rather than the text
            ply format, then a text stream is not supported.
            It is easiest to use a binary stream in all cases.
        path_manager: PathManager for loading if f is a str.
        return_vertex_colors: whether to return vertex colors.

    Returns:
        verts: FloatTensor of shape (V, 3).
        faces: None or LongTensor of vertex indices, shape (F, 3).
        vertex_colors: None or FloatTensor of shape (V, 3), only if requested
    """
    header, elements = _load_ply_raw(f, path_manager=path_manager)

    verts, vertex_colors = _get_verts(header, elements)

    face = elements.get("face", None)
    if face is not None:
        face_head = next(head for head in header.elements
                         if head.name == "face")
        if (len(face_head.properties) != 1
                or face_head.properties[0].list_size_type is None):
            raise ValueError("Unexpected form of faces data.")
        # face_head.properties[0].name is usually "vertex_index" or "vertex_indices"
        # but we don't need to enforce this.

    if face is None:
        faces = None
    elif not len(face):
        # pyre is happier when this condition is not joined to the
        # previous one with `or`.
        faces = None
    elif isinstance(face,
                    np.ndarray) and face.ndim == 2:  # Homogeneous elements
        if face.shape[1] < 3:
            raise ValueError("Faces must have at least 3 vertices.")
        face_arrays = [
            face[:, [0, i + 1, i + 2]] for i in range(face.shape[1] - 2)
        ]
        faces = torch.LongTensor(np.vstack(face_arrays))
    else:
        face_list = []
        for face_item in face:
            if face_item.ndim != 1:
                raise ValueError("Bad face data.")
            if face_item.shape[0] < 3:
                raise ValueError("Faces must have at least 3 vertices.")
            for i in range(face_item.shape[0] - 2):
                face_list.append(
                    [face_item[0], face_item[i + 1], face_item[i + 2]])
        faces = torch.tensor(face_list, dtype=torch.int64)

    if faces is not None:
        _check_faces_indices(faces, max_index=verts.shape[0])

    if return_vertex_colors:
        return verts, faces, vertex_colors
    return verts, faces, None