Skip to content

Commit

Permalink
Adding boxes to the geometry module
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasteuwen committed Aug 25, 2024
1 parent 4d5b066 commit 096d18d
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 28 deletions.
14 changes: 14 additions & 0 deletions .spin/cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,19 @@ def dist():
subprocess.run(["ls", "-l", "dist"], check=True)


@cli.command()
def format():
"""🛠️ Run clang-format and black"""
# Run clang-format
subprocess.run(
"find src -name '*.cpp' -o -name '*.h' -o -name '*.hpp' | xargs clang-format -i",
shell=True,
check=True,
)

# Run black
subprocess.run(["black", "."], check=True)


if __name__ == "__main__":
cli()
4 changes: 3 additions & 1 deletion dlup/_geometry.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ class Point:
def set_point_factory(factory: Callable[[Point_], Point_]) -> None: ...

class Box:
# TODO: Currently it returns a C++ Polygon. This should be changed to return a Python Polygon.
def as_polygon(self) -> Polygon: ...
@property
def area(self) -> float: ...
def scale(self, scaling: float) -> None: ...

def set_box_factory(factory: Callable[[Box_], Box_]) -> None: ...

Expand Down
2 changes: 1 addition & 1 deletion dlup/annotations_experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ def as_geojson(self) -> GeoJsonDict:
if self._layers.boxes:
warnings.warn("Bounding boxes are not supported in GeoJSON and will be skipped.", UserWarning)

all_layers = self._layers.polygons + self._layers.points
all_layers = self.layers.polygons + self.layers.points
for idx, curr_annotation in enumerate(all_layers):
json_dict = _geometry_to_geojson(curr_annotation, label=curr_annotation.label, color=curr_annotation.color)
json_dict["id"] = str(idx)
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ package = 'dlup'
".spin/cmds.py:coverage",
".spin/cmds.py:mypy",
".spin/cmds.py:lint",
".spin/cmds.py:format",
]
"Environments" = [
"spin.cmds.meson.run",
Expand Down
7 changes: 5 additions & 2 deletions src/geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@ PYBIND11_MODULE(_geometry, m) {
newBox->parameters_ = other.parameters_; // Copy the parameters
return newBox;
}))
.def("as_polygon", &Box::asPolygon, "Convert the box to a polygon")
.def("as_polygon", &Box::asPolygonPyObject, "Convert the box to a polygon")
.def("scale", &Box::scale, py::arg("scaling"), "Scale the box in-place by a factor")

.def_property_readonly("coordinates", &Box::getCoordinates,
"Get the top-left coordinates of the box as an (x, y) tuple")
.def_property_readonly("size", &Box::getSize, "Get the size of the box as an (h, w) tuple")
.def_property_readonly("area", &Box::getArea)
.def_property_readonly("wkt", &Box::toWkt, "Get the WKT representation of the box");

py::class_<Point, BaseGeometry, std::shared_ptr<Point>>(m, "Point")
Expand All @@ -104,7 +107,7 @@ PYBIND11_MODULE(_geometry, m) {
.def("distance_to", &Point::distanceTo, py::arg("other"), "Calculate the distance to another point")
.def("equals", &Point::equals, py::arg("other"), "Check if the point is equal to another point")
.def("within", &Point::within, py::arg("polygon"), "Check if the point is within a polygon")
.def("scale", &Point::scale, py::arg("scaling"), "Scale the in-place point by a factor")
.def("scale", &Point::scale, py::arg("scaling"), "Scale the point in-place point by a factor")
.def_property_readonly("wkt", &Point::toWkt, "Get the WKT representation of the point");

m.def("set_polygon_factory", &FactoryManager<Polygon>::setFactory, "Set the factory function for Polygons");
Expand Down
15 changes: 9 additions & 6 deletions src/geometry/box.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#pragma once

#include "exceptions.h"
#include "factory.h"
#include "polygon.h"
#include "utilities.h"
#include <boost/geometry.hpp>
Expand Down Expand Up @@ -35,11 +36,11 @@ class Box : public BaseGeometry {
bg::set<bg::max_corner, 1>(*box_, coordinates[1] + size[1]);
}

inline const std::array<double, 2> getCoordinates() {
inline std::array<double, 2> getCoordinates() const {
return {bg::get<bg::min_corner, 0>(*box_), bg::get<bg::min_corner, 1>(*box_)};
}

inline const std::array<double, 2> getSize() {
inline std::array<double, 2> getSize() const {
auto x1 = bg::get<bg::min_corner, 0>(*box_);
auto y1 = bg::get<bg::min_corner, 1>(*box_);
auto x2 = bg::get<bg::max_corner, 0>(*box_);
Expand All @@ -48,14 +49,14 @@ class Box : public BaseGeometry {
return {x2 - x1, y2 - y1};
}

inline double getArea() const {
std::array<double, 2> size = getSize();
return size[0] * size[1];
}
std::shared_ptr<Polygon> asPolygon() const {
BoostPolygon poly;
bg::convert(*box_, poly);
// std::shared_ptr<Polygon> polygon = GeometryCollection::polygonFactory(poly);

std::shared_ptr<Polygon> polygon = std::make_shared<Polygon>(poly);

// Copy all parameters from the Box to the new Polygon
for (const auto &param : parameters_) {
polygon->setField(param.first, param.second);
}
Expand All @@ -65,6 +66,8 @@ class Box : public BaseGeometry {

std::vector<std::pair<double, double>> getExterior() const { return asPolygon()->getExterior(); }

inline py::object asPolygonPyObject() const { return FactoryManager<Polygon>::callFactoryFunction(asPolygon()); }

void scale(double scaling) { utilities::AffineTransform(*box_, {0.0, 0.0}, scaling); }

// Factory function for creating boxes from Python
Expand Down
29 changes: 23 additions & 6 deletions src/geometry/collection.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,18 +189,27 @@ void RTreeWrapper::rebuild() {
// Mutex is handled in the wrapper
clear(); // Clear the existing R-tree

// Rebuild the tree using polygons and points from GeometryCollection
// Rebuild the tree using polygons, boxes, and points from GeometryCollection

// First insert polygons
const auto &polygons = geometryCollection->polygons_;
for (size_t i = 0; i < polygons.size(); ++i) {
BoostBox box;
bg::envelope(*(polygons[i]->polygon_), box);
insert(box, i);
}

// Next insert boxes
const auto &boxes = geometryCollection->boxes_;
for (size_t i = 0; i < boxes.size(); ++i) {
insert(*(boxes[i]->box_), polygons.size() + i);
}

// Finally, insert points
const auto &points = geometryCollection->points_;
for (size_t i = 0; i < points.size(); ++i) {
BoostBox box(*(points[i]->point_), *(points[i]->point_));
insert(box, polygons.size() + i);
insert(box, polygons.size() + boxes.size() + i);
}

rtree_invalidated_ = false;
Expand Down Expand Up @@ -366,7 +375,8 @@ AnnotationRegion GeometryCollection::readRegion(const std::pair<double, double>
std::sort(results.begin(), results.end(), [](const auto &a, const auto &b) { return a.second < b.second; });

std::vector<std::shared_ptr<Polygon>> intersected_polygons;
std::vector<std::shared_ptr<Point>> intersected_points;
std::vector<std::shared_ptr<Point>> current_points;
std::vector<std::shared_ptr<Box>> current_boxes;

for (const auto &result : results) {
size_t index = result.second;
Expand All @@ -377,14 +387,21 @@ AnnotationRegion GeometryCollection::readRegion(const std::pair<double, double>
utilities::AffineTransform(*intersected_polygon->polygon_, coordinates, scaling);
intersected_polygons.push_back(intersected_polygon);
}
} else if (index < polygons_.size() + boxes_.size()) {
auto &box = boxes_[index - polygons_.size()];
auto transformed_box = std::make_shared<Box>(*box);
utilities::AffineTransform(*transformed_box->box_, coordinates, scaling);
current_boxes.push_back(transformed_box);
} else {
auto &point = points_[index - polygons_.size()];
auto &point = points_[index - polygons_.size() - boxes_.size()];
auto transformed_point = std::make_shared<Point>(*point);
utilities::AffineTransform(*transformed_point->point_, coordinates, scaling);
intersected_points.push_back(transformed_point);
current_points.push_back(transformed_point);
}
}
return AnnotationRegion(std::move(intersected_polygons), {}, std::move(intersected_points), std::move(size));

return AnnotationRegion(std::move(intersected_polygons), std::move(current_boxes), std::move(current_points),
std::move(size));
}

#endif // DLUP_GEOMETRY_COLLECTION_H
11 changes: 0 additions & 11 deletions src/geometry/region.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,6 @@ class AnnotationRegion {
std::vector<py::object> getPoints() const { return point_region_.getObjects(); }
std::vector<py::object> getBoxes() const { return box_region_.getObjects(); }

// py::array_t<int> toMask(int default_value = 0) const {
// cv::Mat mask = generateMaskFromAnnotations(polygon_region_.getObjectVector(), mask_size_, default_value);

// // Create py::array_t<int> from cv::Mat
// return py::array_t<int>({mask.rows, mask.cols}, // shape of the array
// {mask.step[0], mask.step[1]}, // strides
// reinterpret_cast<int *>(mask.data), // pointer to the data
// nullptr // No need to manage the memory manually, OpenCV will handle it
// );
// }

py::array_t<int> toMask(int default_value = 0) const {
std::vector<int> mask = generateMaskFromAnnotations(polygon_region_.getObjectVector(), mask_size_, default_value);

Expand Down
28 changes: 27 additions & 1 deletion tests/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@
from shapely.geometry import Polygon as ShapelyPolygon

import dlup._geometry as dg
from dlup.geometry import GeometryCollection, Point, Polygon, _BaseGeometry, _point_factory, _polygon_factory
from dlup.geometry import (
Box,
GeometryCollection,
Point,
Polygon,
_BaseGeometry,
_box_factory,
_point_factory,
_polygon_factory,
)

polygons = [
Polygon(dg.Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], [])),
Expand Down Expand Up @@ -55,6 +64,23 @@ def test_polygon_factory(self):
polygon = _polygon_factory(c_polygon)
assert polygon == Polygon([(0, 0), (0, 3), (3, 3), (3, 0)])

def test_box_factory(self):
c_box = dg.Box((1, 1), (2, 2))
box = _box_factory(c_box)
assert box == Box((1, 1), (2, 2))

def test_box_area(self):
box = Box((1, 1), (2, 2))
box.area == 4
box.as_polygon().area == box.area

def box_to_polygon(self):
box = Box((1, 1), (2, 2))
polygon = box.as_polygon()
assert isinstance(box, Box)
assert isinstance(polygon, Polygon)
assert polygon == Polygon([(1, 1), (1, 2), (2, 2), (2, 1)])

@pytest.mark.parametrize(
"exterior,interiors,expected_area",
[
Expand Down

0 comments on commit 096d18d

Please sign in to comment.