From 0c2e7fc8d4fae32acaeaf1c187b0b20eb4d33eac Mon Sep 17 00:00:00 2001 From: William Silversmith Date: Tue, 25 Jun 2024 22:49:26 -0400 Subject: [PATCH 1/3] wip: experimenting with a smooth path feature --- src/fastxs3d.cpp | 90 +++++++++++++++++++++++++++++++++++++ src/xs3d.hpp | 54 +++++++++++++++++----- xs3d/__init__.py | 115 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 246 insertions(+), 13 deletions(-) diff --git a/src/fastxs3d.cpp b/src/fastxs3d.cpp index bfadda2..cced849 100644 --- a/src/fastxs3d.cpp +++ b/src/fastxs3d.cpp @@ -155,8 +155,98 @@ auto projection( } } +auto projection_with_frame( + const py::array &labels, + const py::array_t &point, + const py::array_t &basis1, + const py::array_t &basis2, + const py::array_t &anisotropy +) { + const uint64_t sx = labels.shape()[0]; + const uint64_t sy = labels.ndim() < 2 + ? 1 + : labels.shape()[1]; + const uint64_t sz = labels.ndim() < 3 + ? 1 + : labels.shape()[2]; + + float wx = anisotropy.at(0); + float wy = anisotropy.at(1); + float wz = anisotropy.at(2); + + float minval = std::min(std::min(wx,wy), wz); + wx /= minval; wy /= minval; wz /= minval; + float maxval = std::max(std::max(std::abs(wx), std::abs(wy)), std::abs(wz)); + + const uint64_t distortion = static_cast(ceil(maxval)); + + // rational approximation of sqrt(3) is 97/56 + // result is more likely to be same across compilers + uint64_t psx = distortion * 2 * 97 * std::max(std::max(sx,sy), sz) / 56 + 1; + uint64_t pvoxels = psx * psx; + + py::array arr; + + xs3d::Vec3 b1(basis1.at(0), basis1.at(1), basis1.at(2)); + xs3d::Vec3 b2(basis2.at(0), basis2.at(1), basis2.at(2)); + + auto projectionfn = [&](auto dtype) { + arr = py::array_t({ psx, psx }); + auto out = reinterpret_cast(arr.request().ptr); + auto data = reinterpret_cast(labels.request().ptr); + std::fill(out, out + pvoxels, 0); + + std::tuple tup = xs3d::cross_section_projection( + data, + sx, sy, sz, + point.at(0), point.at(1), point.at(2), + b1, b2, + anisotropy.at(0), anisotropy.at(1), anisotropy.at(2), + out + ); + + xs3d::Bbox2d bbox = std::get<1>(tup); + bbox.x_max++; + bbox.y_max++; + + auto cutout = py::array_t({ bbox.sx(), bbox.sy() }); + auto cutout_ptr = reinterpret_cast(cutout.request().ptr); + + int64_t csx = bbox.sx(); + + for (int64_t y = bbox.y_min; y < bbox.y_max; y++) { + for (int64_t x = bbox.x_min; x < bbox.x_max; x++) { + cutout_ptr[ + (x - bbox.x_min) + csx * (y - bbox.y_min) + ] = out[x + psx * y]; + } + } + + return cutout.view(py::str(labels.dtype())); + }; + + int data_width = labels.dtype().itemsize(); + + if (data_width == 1) { + return projectionfn(uint8_t{}); + } + else if (data_width == 2) { + return projectionfn(uint16_t{}); + } + else if (data_width == 4) { + return projectionfn(uint32_t{}); + } + else if (data_width == 8) { + return projectionfn(uint64_t{}); + } + else { + throw new std::runtime_error("should never happen"); + } +} + PYBIND11_MODULE(fastxs3d, m) { m.doc() = "Finding cross sectional area in 3D voxelized images."; + m.def("projection_with_frame", &projection_with_frame, "Project a cross section of a 3D image onto a 2D plane"); m.def("projection", &projection, "Project a cross section of a 3D image onto a 2D plane"); m.def("section", §ion, "Return a binary image that highlights the voxels contributing area to a cross section."); m.def("area", &area, "Find the cross sectional area for a given binary image, point, and normal vector."); diff --git a/src/xs3d.hpp b/src/xs3d.hpp index c7af68f..7dd98bc 100644 --- a/src/xs3d.hpp +++ b/src/xs3d.hpp @@ -10,9 +10,7 @@ #include #include -namespace { - -static uint8_t _dummy_contact = false; +namespace xs3d { class Vec3 { public: @@ -137,6 +135,14 @@ class Vec3 { } }; +}; + +namespace { + +using namespace xs3d; + +static uint8_t _dummy_contact = false; + const Vec3 ihat = Vec3(1,0,0); const Vec3 jhat = Vec3(0,1,0); const Vec3 khat = Vec3(0,0,1); @@ -763,11 +769,9 @@ template std::tuple cross_section_projection( const LABEL* labels, const uint64_t sx, const uint64_t sy, const uint64_t sz, - const float px, const float py, const float pz, - const float nx, const float ny, const float nz, + const Vec3& basis_1, const Vec3& basis_2, const float wx, const float wy, const float wz, - const bool positive_basis, LABEL* out = NULL ) { @@ -802,12 +806,9 @@ std::tuple cross_section_projection( } const Vec3 pos(px, py, pz); - Vec3 normal(nx, ny, nz); - normal /= normal.norm(); - auto bases = create_orthonormal_basis(normal, positive_basis); - Vec3 basis1 = std::get<0>(bases) * anisotropy; - Vec3 basis2 = std::get<1>(bases) * anisotropy; + Vec3 basis1 = basis_1 / basis_1.norm() * anisotropy; + Vec3 basis2 = basis_2 / basis_2.norm() * anisotropy; uint64_t plane_pos_x = psx / 2; uint64_t plane_pos_y = psy / 2; @@ -880,6 +881,37 @@ std::tuple cross_section_projection( return std::tuple(out, bbx); } +template +std::tuple cross_section_projection( + const LABEL* labels, + const uint64_t sx, const uint64_t sy, const uint64_t sz, + const float px, const float py, const float pz, + const float nx, const float ny, const float nz, + const float wx, const float wy, const float wz, + const bool positive_basis, + LABEL* out = NULL +) { + Vec3 normal(nx, ny, nz); + normal /= normal.norm(); + + Vec3 anisotropy(wx, wy, wz); + anisotropy /= anisotropy.min(); + anisotropy = Vec3(1,1,1) / anisotropy; + + auto bases = create_orthonormal_basis(normal, positive_basis); + Vec3 basis1 = std::get<0>(bases) * anisotropy; + Vec3 basis2 = std::get<1>(bases) * anisotropy; + + return cross_section_projection