Skip to main content

Fast Largest Interior Rectangle calculation

Project description

lir

Fast Largest Interior Rectangle calculation within a binary grid.

sample1 sample2 sample4

:rocket: Through Numba the Python code is compiled to machine code for execution at native machine code speed!

Installation

Use pip to install largestinteriorrectangle from PyPI.

pip install largestinteriorrectangle

Usage

import largestinteriorrectangle as lir
import numpy as np

grid = np.array([[0, 0, 1, 0, 0, 0, 0, 0, 0],
                 [0, 0, 1, 0, 1, 1, 0, 0, 0],
                 [0, 0, 1, 1, 1, 1, 1, 0, 0],
                 [0, 0, 1, 1, 1, 1, 1, 1, 0],
                 [0, 0, 1, 1, 1, 1, 1, 1, 0],
                 [0, 1, 1, 1, 1, 1, 1, 0, 0],
                 [0, 0, 1, 1, 1, 1, 0, 0, 0],
                 [0, 0, 1, 1, 1, 1, 0, 0, 0],
                 [1, 1, 1, 1, 1, 1, 0, 0, 0],
                 [1, 1, 0, 0, 0, 1, 1, 1, 1],
                 [0, 0, 0, 0, 0, 0, 0, 0, 0]],
                "bool")

lir.lir(grid) # array([2, 2, 4, 7])

For significant performance enhancement in larger grids specify the contours(s) of the polygons to consider. If the grid only has one polygon like in the example the contour can be obtained as so (with opencv).

import cv2 as cv
cv_grid = grid.astype("uint8") * 255
contours, _ = \
    cv.findContours(cv_grid, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
contour = contours[0][:, 0, :]

then calculate the rectangle.

lir.lir(grid, contour) # array([2, 2, 4, 7])

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

Run tests using

python -m unittest

License

Apache License 2.0

Acknowledgements

Thanks to Tim Swan for making his Largest Interior Rectangle implementation in C# open source and did a great blog post about it. The first part was mainly reimplementing his solution in Python.

The used Algorithm was described 2019 in Algorithm for finding the largest inscribed rectangle in polygon by Zahraa Marzeh, Maryam Tahmasbi and Narges Mireh.

Thanks also to Mark Setchell and joni who greatly helped optimizing the performance using cpython/numba in this SO querstion

How it works

For a binary grid:

grid

We can specify for each cell how far one can go to the right and to the bottom:

Horizontal Adjacency Vertical Adjacency
h_adjacency v_adjacency

Now the goal is to find the possible rectangles for each cell. For that, we can specify a Horizontal Vector based on the Horizontal Adjacency and Vertical Vector based on the Vertical Adjacency:

Horizontal Vector (2,2) Vertical Vector (2,2)
h_vector h_vector

So at the given cell (2,2) the Horizontal Vector is (5,4) and the Vertical Vector is (7,4).

Reversing either vector lets you create the spans by stacking the vectors, so for example reversing the Vertical Vector to (4,7) gives a set of spans of (5 by 4) and (4 by 7).

Since 4*7=28 > 5*4=20 a rectangle with width 4 and height 7 is the biggest possible rectangle for cell (2,2). The width and height is stored in a span map, where the widths and heights of the maximum rectangles are stored for all cells. Using the area we can identify the biggest rectangle at (2, 2) with width 4 and height 7.

Widths Heights Areas
span_map_widths span_map_heights span_map_areas

LIR based on contour

Especially for bigger grids the functionality can be further optimized by only analysing the outline of a polygon. Here are timings created by calculating the lir for masks in different resolutions:

Timings Timings (log transformed)
performance_comparison performance_comparison_log

The computation costs are saved by analysing only the contour pixels instead of all cells. We utilize the fact that the LIR always touches the outline of the polygon. Here is how it works:

grid

An outline cell can span into one (blue), two (green) or three (red) directions (up, down, left, right):

direction_map

By calculating the spans in all possible directions we can obtain a span map:

Widths Heights Areas
span_map_widths span_map_heights span_map_areas

To analyse what happens here we'll have a closer look at cell (4,2).

direction_map_cell_2_2

It can span into 3 directions: left, down and right. Going to left and down the maximum span is (3 by 7). The final spans are noted in left2right and top2bottom notation. In this case, however, the width is calculated from right2left. We can transform it with the simple formula x = cell_x - span_width + 1, in this case 4 - 3 + 1 = 2. Since the height is already calculated from top2bottom y doesn't change and the span (3 by 7) is allocated to cell (2,2) (black dotted).

(2,2) is (besides (1,6)) the cell with the biggest area in the span map. However, the information that the rectangle can be expanded to the right (turquoise dotted) is missing.

So for "candidate cells" like (2,2) which do not lie on the outline and come from outline cells going in 3 directions, we create a new span map (using left2right and top2bottom adjacencies):

candidate_map

Widths Heights Areas
span_map2_widths span_map2_heights span_map2_areas

The biggest span of the two span maps are compared and the bigger one returned as lir, in this case cell (2,2) with a span (4 by 7)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

largestinteriorrectangle-0.2.0.tar.gz (14.0 kB view details)

Uploaded Source

Built Distribution

largestinteriorrectangle-0.2.0-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

Details for the file largestinteriorrectangle-0.2.0.tar.gz.

File metadata

File hashes

Hashes for largestinteriorrectangle-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ed8902d591c3d73c5533a9c5af2840b58fd04626be0e223f215041b2d82b8e4b
MD5 83b4ee82db22f6bd343b6b86f3d0d2a8
BLAKE2b-256 e4d9905ebb5e689781ec6ebfb1bbc82842c79b56393ed8b946ffe7614f0ca2d3

See more details on using hashes here.

File details

Details for the file largestinteriorrectangle-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for largestinteriorrectangle-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 023ad6b3c9e83aefdec87c67edf3b5acd303bfd88140cf534af74ab3dcfaa799
MD5 dc156f444b05a8eaf01325ff03b2dae7
BLAKE2b-256 8a0ee017f1fc32d0f06368bcef9afee464f2dcd1c5f5e6699d5a024d5e8275bf

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page