Skip to content

Commit

Permalink
Add pathlib support for IO functions
Browse files Browse the repository at this point in the history
  • Loading branch information
sitic committed Jan 22, 2024
1 parent 4214a0d commit 9339c6e
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 82 deletions.
121 changes: 61 additions & 60 deletions cpp/pybind/io/class_io.cpp

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions cpp/pybind/open3d_pybind.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
// every compilation unit.
#include "pybind/core/tensor_type_caster.h"

// Replace with <pybind11/stl/filesystem.h> when we require C++17.
#include "pybind_filesystem.h"

namespace py = pybind11;
using namespace py::literals;

Expand Down
109 changes: 109 additions & 0 deletions cpp/pybind/pybind_filesystem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// ----------------------------------------------------------------------------
// - Open3D: www.open3d.org -
// ----------------------------------------------------------------------------
// Copyright (c) 2018-2023 www.open3d.org
// SPDX-License-Identifier: MIT
// ----------------------------------------------------------------------------

// Adapted from <pybind11/stl/filesystem.h> to support C++14.
// Original attribution:
// Copyright (c) 2021 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license.

#pragma once

#include <pybind11/cast.h>
#include <pybind11/detail/common.h>
#include <pybind11/detail/descr.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>

#include <string>

#ifdef WIN32
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#endif
#ifdef __APPLE__
#include <filesystem>
namespace fs = std::__fs::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#endif

namespace pybind11 {
namespace detail {

template <typename T>
struct path_caster {
private:
static PyObject *unicode_from_fs_native(const std::string &w) {
#if !defined(PYPY_VERSION)
return PyUnicode_DecodeFSDefaultAndSize(w.c_str(), ssize_t(w.size()));
#else
// PyPy mistakenly declares the first parameter as non-const.
return PyUnicode_DecodeFSDefaultAndSize(const_cast<char *>(w.c_str()),
ssize_t(w.size()));
#endif
}

static PyObject *unicode_from_fs_native(const std::wstring &w) {
return PyUnicode_FromWideChar(w.c_str(), ssize_t(w.size()));
}

public:
static handle cast(const T &path, return_value_policy, handle) {
if (auto py_str = unicode_from_fs_native(path.native())) {
return module_::import("pathlib")
.attr("Path")(reinterpret_steal<object>(py_str))
.release();
}
return nullptr;
}

bool load(handle handle, bool) {
// PyUnicode_FSConverter and PyUnicode_FSDecoder normally take care of
// calling PyOS_FSPath themselves, but that's broken on PyPy (PyPy
// issue #3168) so we do it ourselves instead.
PyObject *buf = PyOS_FSPath(handle.ptr());
if (!buf) {
PyErr_Clear();
return false;
}
PyObject *native = nullptr;
if (std::is_same<typename T::value_type, char>::value) {
if (PyUnicode_FSConverter(buf, &native) != 0) {
if (auto *c_str = PyBytes_AsString(native)) {
// AsString returns a pointer to the internal buffer, which
// must not be free'd.
value = c_str;
}
}
} else if (std::is_same<typename T::value_type, wchar_t>::value) {
if (PyUnicode_FSDecoder(buf, &native) != 0) {
if (auto *c_str = PyUnicode_AsWideCharString(native, nullptr)) {
// AsWideCharString returns a new string that must be
// free'd.
value = c_str; // Copies the string.
PyMem_Free(c_str);
}
}
}
Py_XDECREF(native);
Py_DECREF(buf);
if (PyErr_Occurred()) {
PyErr_Clear();
return false;
}
return true;
}

PYBIND11_TYPE_CASTER(T, const_name("os.PathLike"));
};

template <>
struct type_caster<fs::path> : public path_caster<fs::path> {};

} // namespace detail
} // namespace pybind11
27 changes: 13 additions & 14 deletions cpp/pybind/t/io/class_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ void pybind_class_io(py::module &m_io) {
// open3d::t::geometry::Image
m_io.def(
"read_image",
[](const std::string &filename) {
[](const fs::path &filename) {
py::gil_scoped_release release;
geometry::Image image;
ReadImage(filename, image);
ReadImage(filename.string(), image);
return image;
},
"Function to read image from file.", "filename"_a);
Expand All @@ -83,10 +83,10 @@ void pybind_class_io(py::module &m_io) {

m_io.def(
"write_image",
[](const std::string &filename, const geometry::Image &image,
[](const fs::path &filename, const geometry::Image &image,
int quality) {
py::gil_scoped_release release;
return WriteImage(filename, image, quality);
return WriteImage(filename.string(), image, quality);
},
"Function to write Image to file.", "filename"_a, "image"_a,
"quality"_a = kOpen3DImageIODefaultQuality);
Expand All @@ -96,12 +96,12 @@ void pybind_class_io(py::module &m_io) {
// open3d::t::geometry::PointCloud
m_io.def(
"read_point_cloud",
[](const std::string &filename, const std::string &format,
[](const fs::path &filename, const std::string &format,
bool remove_nan_points, bool remove_infinite_points,
bool print_progress) {
py::gil_scoped_release release;
t::geometry::PointCloud pcd;
ReadPointCloud(filename, pcd,
ReadPointCloud(filename.string(), pcd,
{format, remove_nan_points,
remove_infinite_points, print_progress});
return pcd;
Expand All @@ -114,12 +114,12 @@ void pybind_class_io(py::module &m_io) {

m_io.def(
"write_point_cloud",
[](const std::string &filename,
[](const fs::path &filename,
const t::geometry::PointCloud &pointcloud, bool write_ascii,
bool compressed, bool print_progress) {
py::gil_scoped_release release;
return WritePointCloud(
filename, pointcloud,
filename.string(), pointcloud,
{write_ascii, compressed, print_progress});
},
"Function to write PointCloud with tensor attributes to file.",
Expand All @@ -131,14 +131,14 @@ void pybind_class_io(py::module &m_io) {
// open3d::geometry::TriangleMesh
m_io.def(
"read_triangle_mesh",
[](const std::string &filename, bool enable_post_processing,
[](const fs::path &filename, bool enable_post_processing,
bool print_progress) {
py::gil_scoped_release release;
t::geometry::TriangleMesh mesh;
open3d::io::ReadTriangleMeshOptions opt;
opt.enable_post_processing = enable_post_processing;
opt.print_progress = print_progress;
ReadTriangleMesh(filename, mesh, opt);
ReadTriangleMesh(filename.string(), mesh, opt);
return mesh;
},
"Function to read TriangleMesh from file", "filename"_a,
Expand Down Expand Up @@ -178,13 +178,12 @@ The following example reads a triangle mesh with the .ply extension::

m_io.def(
"write_triangle_mesh",
[](const std::string &filename,
const t::geometry::TriangleMesh &mesh, bool write_ascii,
bool compressed, bool write_vertex_normals,
[](const fs::path &filename, const t::geometry::TriangleMesh &mesh,
bool write_ascii, bool compressed, bool write_vertex_normals,
bool write_vertex_colors, bool write_triangle_uvs,
bool print_progress) {
py::gil_scoped_release release;
return WriteTriangleMesh(filename, mesh, write_ascii,
return WriteTriangleMesh(filename.string(), mesh, write_ascii,
compressed, write_vertex_normals,
write_vertex_colors,
write_triangle_uvs, print_progress);
Expand Down
8 changes: 4 additions & 4 deletions cpp/pybind/visualization/renderoption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ void pybind_renderoption(py::module &m) {
})
.def(
"load_from_json",
[](RenderOption &ro, const std::string &filename) {
io::ReadIJsonConvertible(filename, ro);
[](RenderOption &ro, const fs::path &filename) {
io::ReadIJsonConvertible(filename.string(), ro);
},
"Function to load RenderOption from a JSON "
"file.",
"filename"_a)
.def(
"save_to_json",
[](RenderOption &ro, const std::string &filename) {
io::WriteIJsonConvertible(filename, ro);
[](RenderOption &ro, const fs::path &filename) {
io::WriteIJsonConvertible(filename.string(), ro);
},
"Function to save RenderOption to a JSON "
"file.",
Expand Down
8 changes: 4 additions & 4 deletions cpp/pybind/visualization/utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,12 @@ void pybind_visualization_utility_methods(py::module &m) {
[](const std::vector<std::shared_ptr<const geometry::Geometry>>
&geometry_ptrs,
const std::string &window_name, int width, int height, int left,
int top, const std::string &json_filename) {
int top, const fs::path &json_filename) {
std::string current_dir =
utility::filesystem::GetWorkingDirectory();
DrawGeometriesWithCustomAnimation(geometry_ptrs, window_name,
width, height, left, top,
json_filename);
json_filename.string());
utility::filesystem::ChangeWorkingDirectory(current_dir);
},
"Function to draw a list of geometry::Geometry objects with a GUI "
Expand Down Expand Up @@ -251,9 +251,9 @@ void pybind_visualization_utility_methods(py::module &m) {

m.def(
"read_selection_polygon_volume",
[](const std::string &filename) {
[](const fs::path &filename) {
SelectionPolygonVolume vol;
io::ReadIJsonConvertible(filename, vol);
io::ReadIJsonConvertible(filename.string(), vol);
return vol;
},
"Function to read SelectionPolygonVolume from file", "filename"_a);
Expand Down
21 changes: 21 additions & 0 deletions python/test/io/test_pathlib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ----------------------------------------------------------------------------
# - Open3D: www.open3d.org -
# ----------------------------------------------------------------------------
# Copyright (c) 2018-2023 www.open3d.org
# SPDX-License-Identifier: MIT
# ----------------------------------------------------------------------------

from pathlib import Path

import open3d as o3d


def test_pathlib_support():
pcd_pointcloud = o3d.data.PCDPointCloud()
assert isinstance(pcd_pointcloud.path, str)

pcd = o3d.io.read_point_cloud(pcd_pointcloud.path)
assert pcd.has_points()

pcd = o3d.io.read_point_cloud(Path(pcd_pointcloud.path))
assert pcd.has_points()

0 comments on commit 9339c6e

Please sign in to comment.