From 206d3885b6f2532e0754b89ebd2a9fb88edfd9fb Mon Sep 17 00:00:00 2001 From: jan Date: Wed, 6 Sep 2017 01:17:38 -0700 Subject: [PATCH 01/33] Break up raster() into sub-functions. Documentation to follow... --- float_raster.py | 179 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 143 insertions(+), 36 deletions(-) diff --git a/float_raster.py b/float_raster.py index 50e39cb..7fe4656 100644 --- a/float_raster.py +++ b/float_raster.py @@ -12,7 +12,7 @@ from scipy import sparse __author__ = 'Jan Petykiewicz' -def raster(poly_xy: numpy.ndarray, +def raster(vertices: numpy.ndarray, grid_x: numpy.ndarray, grid_y: numpy.ndarray ) -> numpy.ndarray: @@ -23,25 +23,21 @@ def raster(poly_xy: numpy.ndarray, usually only allow for 256 (and often fewer) possible pixel values without performing (very slow) super-sampling. - :param poly_xy: 2xN ndarray containing x,y coordinates for each point in the polygon + Polygons are assumed to have clockwise vertex order; reversing the vertex order is equivalent + to multiplying the result by -1. + + :param vertices: 2xN ndarray containing x,y coordinates for each vertex of the polygon :param grid_x: x-coordinates for the edges of each pixel (ie, the leftmost two columns span x=grid_x[0] to x=grid_x[1] and x=grid_x[1] to x=grid_x[2]) :param grid_y: y-coordinates for the edges of each pixel (see grid_x) :return: 2D ndarray with pixel values in the range [0, 1] containing the anti-aliased polygon """ - poly_xy = numpy.array(poly_xy) + vertices = numpy.array(vertices) grid_x = numpy.array(grid_x) grid_y = numpy.array(grid_y) - if poly_xy.shape[0] != 2: - raise Exception('poly_xy must be 2xN') - if grid_x.size < 1 or grid_y.size < 1: - raise Exception('Grid must contain at least one full pixel') - - num_xy_px = numpy.array([grid_x.size, grid_y.size]) - 1 - - min_bounds = floor(poly_xy.min(axis=1)) - max_bounds = ceil(poly_xy.max(axis=1)) + min_bounds = floor(vertices.min(axis=1)) + max_bounds = ceil(vertices.max(axis=1)) keep_x = logical_and(grid_x >= min_bounds[0], grid_x <= max_bounds[0]) @@ -49,21 +45,47 @@ def raster(poly_xy: numpy.ndarray, grid_y <= max_bounds[1]) if not (keep_x.any() and keep_y.any()): # polygon doesn't overlap grid - return zeros(num_xy_px) + return zeros((grid_x.size - 1, grid_y.size - 1)) + + vertices = create_vertices(vertices, grid_x, grid_y) + parts_grid = get_raster_parts(vertices, grid_x, grid_y).toarray() + result_grid = numpy.real(parts_grid) + numpy.imag(parts_grid).cumsum(axis=0) + return result_grid + + +def calculate_intersection_vertices( + vertices: numpy.ndarray, + grid_x: numpy.ndarray, + grid_y: numpy.ndarray + ): + """ + """ + if vertices.shape[0] != 2: + raise Exception('vertices must be 2xN') + + min_bounds = floor(vertices.min(axis=1)) + max_bounds = ceil(vertices.max(axis=1)) + + keep_x = logical_and(grid_x >= min_bounds[0], + grid_x <= max_bounds[0]) + keep_y = logical_and(grid_y >= min_bounds[1], + grid_y <= max_bounds[1]) + + if not (keep_x.any() or keep_y.any()): # polygon doesn't overlap grid + mat_shape = (vertices.shape[1], grid_x.size + grid_y.size) + return zeros(mat_shape), zeros(mat_shape), zeros(mat_shape, dtype=bool) y_seg_xs = hstack((min_bounds[0], grid_x[keep_x], max_bounds[0])).T x_seg_ys = hstack((min_bounds[1], grid_y[keep_y], max_bounds[1])).T - num_poly_vertices = poly_xy.shape[1] - ''' Calculate intersections between polygon and grid line segments ''' - xy1b = numpy.roll(poly_xy, -1, axis=1) + xy1b = numpy.roll(vertices, -1, axis=1) # Lists of initial/final coordinates for polygon segments - xi1 = poly_xy[0, :, newaxis] - yi1 = poly_xy[1, :, newaxis] + xi1 = vertices[0, :, newaxis] + yi1 = vertices[1, :, newaxis] xf1 = xy1b[0, :, newaxis] yf1 = xy1b[1, :, newaxis] @@ -105,6 +127,27 @@ def raster(poly_xy: numpy.ndarray, int_normalized_distance_1to2 = u_a # print('sparsity', int_adjacency_matrix.astype(int).sum() / int_adjacency_matrix.size) + return int_normalized_distance_1to2, int_xy_matrix, int_adjacency_matrix + + +def create_vertices( + vertices: numpy.ndarray, + grid_x: numpy.ndarray, + grid_y: numpy.ndarray, + new_vertex_data=None + ) -> sparse.coo_matrix: + """ + """ + if vertices.shape[0] != 2: + raise Exception('vertices must be 2xN') + if grid_x.size < 1 or grid_y.size < 1: + raise Exception('Grid must contain at least one line in each direction?') + + num_poly_vertices = vertices.shape[1] + + if new_vertex_data is None: + new_vertex_data = calculate_intersection_vertices(vertices, grid_x, grid_y) + int_normalized_distance_1to2, int_xy_matrix, int_adjacency_matrix = new_vertex_data ''' Insert any polygon-grid intersections as new polygon vertices @@ -116,39 +159,104 @@ def raster(poly_xy: numpy.ndarray, sortix_paired = (numpy.arange(num_poly_vertices)[:, newaxis], sortix) assert(int_normalized_distance_1to2.shape[0] == num_poly_vertices) - # If any new points fall outside the window, shrink them back onto it + # if any new points fall outside the window, shrink them back onto it xy_shrunken = (numpy.real(int_xy_matrix).clip(grid_x[0], grid_x[-1]) + 1j * numpy.imag(int_xy_matrix).clip(grid_y[0], grid_y[-1])) # Use sortix to sort adjacency matrix and the intersection (x, y) coordinates, # and hstack the original points to the left of the new ones - xy_with_original = hstack((poly_xy[0, :, newaxis] + 1j * poly_xy[1, :, newaxis], + xy_with_original = hstack((vertices[0, :, newaxis] + 1j * vertices[1, :, newaxis], xy_shrunken[sortix_paired])) - has_intersection = hstack((ones((poly_xy.shape[1], 1), dtype=bool), + has_intersection = hstack((ones((vertices.shape[1], 1), dtype=bool), int_adjacency_matrix[sortix_paired])) # Now remove all extra entries which don't correspond to new vertices # (ie, no intersection happened), and then flatten, creating our - # polygon-with-extra-vertices, though some extra vertices are included, - # which we must remove manually. + # polygon-with-extra-vertices, though some redundant vertices are included, + # which we must later remove manually. vertices = xy_with_original[has_intersection] + return vertices + +def clip_vertices_to_window( + vertices: numpy.ndarray, + min_x: float = -numpy.inf, + max_x: float = numpy.inf, + min_y: float = -numpy.inf, + max_y: float = numpy.inf + ) -> numpy.ndarray: + """ + """ +# xy_shrunken = (numpy.real(vertices).clip(min_x, max_x) + 1j * +# numpy.imag(vertices).clip(max_y, min_y)) +# +# # forward_difference: 1 if either coordinate changed from the previous point +# forward_diff = (numpy.roll(xy_shrunken, 1) - xy_shrunken) != 0 +# xys = xy_shrunken[forward_diff] +# +# forward_diff = numpy.roll(xys, 1) - xys +# roll_diff = numpy.roll(forward_diff, -1) +# keep = numpy.logical_or( +# numpy.logical_and(numpy.real(forward_diff), +# numpy.real(roll_diff)), +# numpy.logical_and(numpy.imag(forward_diff), +# numpy.imag(roll_diff))) +# nondup = xys[keep] +# return nondup # Remove points outside the window (these will only be original points) # Since the boundaries of the window are also pixel boundaries, this just # makes the polygon boundary proceed along the window edge - inside = logical_and.reduce((numpy.real(vertices) <= grid_x[-1], - numpy.real(vertices) >= grid_x[0], - numpy.imag(vertices) <= grid_y[-1], - numpy.imag(vertices) >= grid_y[0])) + inside = logical_and.reduce((numpy.real(vertices) <= max_x, + numpy.real(vertices) >= min_x, + numpy.imag(vertices) <= max_y, + numpy.imag(vertices) >= min_y)) vertices = vertices[inside] # Remove consecutive duplicate vertices consecutive = numpy.ediff1d(vertices, to_begin=[1 + 1j]).astype(bool) vertices = vertices[consecutive] + return vertices + + +def get_raster_parts( + vertices: numpy.ndarray, + grid_x: numpy.ndarray, + grid_y: numpy.ndarray + ) -> sparse.coo_matrix: + """ + This function performs the same task as raster(...), but instead of returning a dense array + of pixel values, it returns a sparse array containing the value + (-area + 1j * cover) + for each pixel which contains a line segment, where + cover is the fraction of the pixel's y-length that is traversed by the segment, + multiplied by the sign of (y_final - y_initial) + area is the fraction of the pixel's area covered by the trapezoid formed by + the line segment's endpoints (clipped to the cell edges) and their projections + onto the pixel's left (i.e., lowest-x) edge, again multiplied by + the sign of (y_final - y_initial) + Note that polygons are assumed to be wound clockwise. + + The result from raster(...) can be obtained with + raster_result = numpy.real(lines_result) + numpy.imag(lines_result).cumsum(axis=0) + + :param vertices: 2xN ndarray containing x,y coordinates for each point in the polygon + :param grid_x: x-coordinates for the edges of each pixel (ie, the leftmost two columns span + x=grid_x[0] to x=grid_x[1] and x=grid_x[1] to x=grid_x[2]) + :param grid_y: y-coordinates for the edges of each pixel (see grid_x) + :return: Complex sparse COO matrix containing area and cover information + """ + if grid_x.size < 2 or grid_y.size < 2: + raise Exception('Grid must contain at least one full pixel') + + num_xy_px = numpy.array([grid_x.size, grid_y.size]) - 1 + + vertices = clip_vertices_to_window(vertices, + grid_x[0], grid_x[-1], + grid_y[0], grid_y[-1]) # If the shape fell completely outside our area, just return a blank grid if vertices.size == 0: - return zeros(num_xy_px) + return sparse.coo_matrix(shape=num_xy_px) ''' Calculate area, cover @@ -159,23 +267,22 @@ def raster(poly_xy: numpy.ndarray, # Remove segments along the right,top edges # (they correspond to outside pixels, but couldn't be removed until now - # because poly_xy stores points, not segments, and the edge points are needed + # because 'vertices' stored points, not segments, and the edge points are needed # when creating endpoint_avg) non_edge = numpy.logical_and(numpy.real(endpoint_avg) < grid_x[-1], numpy.imag(endpoint_avg) < grid_y[-1]) - endpoint_final = endpoint_avg[non_edge] - x_sub = numpy.digitize(numpy.real(endpoint_final), grid_x) - 1 - y_sub = numpy.digitize(numpy.imag(endpoint_final), grid_y) - 1 + endpoint_avg_final = endpoint_avg[non_edge] + x_sub = numpy.digitize(numpy.real(endpoint_avg_final), grid_x) - 1 + y_sub = numpy.digitize(numpy.imag(endpoint_avg_final), grid_y) - 1 cover = diff(numpy.imag(poly), axis=0)[non_edge] / diff(grid_y)[y_sub] - area = (numpy.real(endpoint_final) - grid_x[x_sub]) * cover / diff(grid_x)[x_sub] + area = (numpy.real(endpoint_avg_final) - grid_x[x_sub]) * cover / diff(grid_x)[x_sub] # Use coo_matrix(...).toarray() to efficiently convert from (x, y, v) pairs to ndarrays. # We can use v = (-area + 1j * cover) followed with calls to numpy.real() and numpy.imag() to # improve performance (Otherwise we'd have to call coo_matrix() twice. It's really inefficient # because it involves lots of random memory access, unlike real() and imag()). - poly_grid = sparse.coo_matrix((-area + 1j * cover, (x_sub, y_sub)), shape=num_xy_px).toarray() - result_grid = numpy.real(poly_grid) + numpy.imag(poly_grid).cumsum(axis=0) + poly_grid = sparse.coo_matrix((-area + 1j * cover, (x_sub, y_sub)), shape=num_xy_px) + return poly_grid - return result_grid From 8c8e41ac537a44622b64c5ae13879d1d18a33dfe Mon Sep 17 00:00:00 2001 From: jan Date: Wed, 6 Sep 2017 21:09:06 -0700 Subject: [PATCH 02/33] Rename some variables and functions, and add _very_ bare docs --- float_raster.py | 58 +++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/float_raster.py b/float_raster.py index 7fe4656..11cc11c 100644 --- a/float_raster.py +++ b/float_raster.py @@ -5,6 +5,7 @@ Module for rasterizing polygons, with float-precision anti-aliasing on See the documentation for raster(...) for details. """ +from typing import Tuple import numpy from numpy import logical_and, diff, floor, ceil, ones, zeros, hstack, full_like, newaxis from scipy import sparse @@ -53,12 +54,13 @@ def raster(vertices: numpy.ndarray, return result_grid -def calculate_intersection_vertices( +def find_intersections( vertices: numpy.ndarray, grid_x: numpy.ndarray, grid_y: numpy.ndarray - ): + ) -> Tuple[numpy.ndarray]: """ + Find intersections between a polygon and grid lines """ if vertices.shape[0] != 2: raise Exception('vertices must be 2xN') @@ -112,31 +114,31 @@ def calculate_intersection_vertices( u_a = numerator_a / denominator u_b = numerator_b / denominator - # Find the adjacency matrix A of intersecting lines. + # Find the adjacency matrix int_x = xi1 + dx1 * u_a int_y = yi1 + dy1 * u_a - int_b = logical_and.reduce((u_a >= 0, u_a <= 1, u_b >= 0, u_b <= 1)) + adjacency = logical_and.reduce((u_a >= 0, u_a <= 1, u_b >= 0, u_b <= 1)) # Arrange output. - # int_adjacency_matrix[i, j] tells us if polygon segment i intersects with grid line j - # int_xy_matrix[i, j] tells us the x,y coordinates of the intersection in the form x+iy - # int_normalized_distance_1to2[i, j] tells us the fraction of the segment i + # adjacency[i, j] tells us if polygon segment i intersects with grid line j + # xy[i, j] tells us the x,y coordinates of the intersection in the form x+iy + # normalized_distance[i, j] tells us the fraction of the segment i # we have to traverse in order to reach the intersection - int_adjacency_matrix = int_b - int_xy_matrix = (int_x + 1j * int_y) * int_b - int_normalized_distance_1to2 = u_a + xy = (int_x + 1j * int_y) * adjacency + normalized_distance = u_a - # print('sparsity', int_adjacency_matrix.astype(int).sum() / int_adjacency_matrix.size) - return int_normalized_distance_1to2, int_xy_matrix, int_adjacency_matrix + # print('sparsity', adjacency.astype(int).sum() / adjacency.size) + return normalized_distance, xy, adjacency def create_vertices( vertices: numpy.ndarray, grid_x: numpy.ndarray, grid_y: numpy.ndarray, - new_vertex_data=None + new_vertex_data: Tuple[numpy.ndarray] = None ) -> sparse.coo_matrix: """ + Create additional vertices where a polygon crosses gridlines """ if vertices.shape[0] != 2: raise Exception('vertices must be 2xN') @@ -146,8 +148,8 @@ def create_vertices( num_poly_vertices = vertices.shape[1] if new_vertex_data is None: - new_vertex_data = calculate_intersection_vertices(vertices, grid_x, grid_y) - int_normalized_distance_1to2, int_xy_matrix, int_adjacency_matrix = new_vertex_data + new_vertex_data = find_intersections(vertices, grid_x, grid_y) + normalized_distance, xy_matrix, adjacency_matrix = new_vertex_data ''' Insert any polygon-grid intersections as new polygon vertices @@ -155,20 +157,20 @@ def create_vertices( # Figure out how to sort each row of the intersection matrices # based on distance from (xi1, yi1) (the polygon segment's first point) # This lets us insert them as new vertices in the proper order - sortix = int_normalized_distance_1to2.argsort(axis=1) + sortix = normalized_distance.argsort(axis=1) sortix_paired = (numpy.arange(num_poly_vertices)[:, newaxis], sortix) - assert(int_normalized_distance_1to2.shape[0] == num_poly_vertices) + assert(normalized_distance.shape[0] == num_poly_vertices) # if any new points fall outside the window, shrink them back onto it - xy_shrunken = (numpy.real(int_xy_matrix).clip(grid_x[0], grid_x[-1]) + 1j * - numpy.imag(int_xy_matrix).clip(grid_y[0], grid_y[-1])) + xy_shrunken = (numpy.real(xy_matrix).clip(grid_x[0], grid_x[-1]) + 1j * + numpy.imag(xy_matrix).clip(grid_y[0], grid_y[-1])) # Use sortix to sort adjacency matrix and the intersection (x, y) coordinates, # and hstack the original points to the left of the new ones xy_with_original = hstack((vertices[0, :, newaxis] + 1j * vertices[1, :, newaxis], xy_shrunken[sortix_paired])) has_intersection = hstack((ones((vertices.shape[1], 1), dtype=bool), - int_adjacency_matrix[sortix_paired])) + adjacency_matrix[sortix_paired])) # Now remove all extra entries which don't correspond to new vertices # (ie, no intersection happened), and then flatten, creating our @@ -187,22 +189,6 @@ def clip_vertices_to_window( ) -> numpy.ndarray: """ """ -# xy_shrunken = (numpy.real(vertices).clip(min_x, max_x) + 1j * -# numpy.imag(vertices).clip(max_y, min_y)) -# -# # forward_difference: 1 if either coordinate changed from the previous point -# forward_diff = (numpy.roll(xy_shrunken, 1) - xy_shrunken) != 0 -# xys = xy_shrunken[forward_diff] -# -# forward_diff = numpy.roll(xys, 1) - xys -# roll_diff = numpy.roll(forward_diff, -1) -# keep = numpy.logical_or( -# numpy.logical_and(numpy.real(forward_diff), -# numpy.real(roll_diff)), -# numpy.logical_and(numpy.imag(forward_diff), -# numpy.imag(roll_diff))) -# nondup = xys[keep] -# return nondup # Remove points outside the window (these will only be original points) # Since the boundaries of the window are also pixel boundaries, this just # makes the polygon boundary proceed along the window edge From 07df98cd42ae18650ed6086256bcd6f77d067575 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 17 Oct 2017 12:57:06 -0700 Subject: [PATCH 03/33] bump version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c55a8a1..5f8374a 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup setup(name='float_raster', - version='0.3', + version='0.4', description='High-precision anti-aliasing polygon rasterizer', author='Jan Petykiewicz', author_email='anewusername@gmail.com', From 3ca8afb3ef9f2503ab2a280c265fd0cdb598ebef Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 16 Sep 2018 20:04:46 -0700 Subject: [PATCH 04/33] Use python3 for setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5f8374a..c726c99 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from setuptools import setup From 756c015b874e60d23485dfc1a9369e5d1d0a7d12 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 16 Sep 2018 20:05:13 -0700 Subject: [PATCH 05/33] Move version info inside module --- float_raster.py | 2 ++ setup.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/float_raster.py b/float_raster.py index 11cc11c..fed9bc1 100644 --- a/float_raster.py +++ b/float_raster.py @@ -12,6 +12,8 @@ from scipy import sparse __author__ = 'Jan Petykiewicz' +version = '0.4' + def raster(vertices: numpy.ndarray, grid_x: numpy.ndarray, diff --git a/setup.py b/setup.py index c726c99..fb5c7cc 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 from setuptools import setup +import float_raster setup(name='float_raster', - version='0.4', + version=float_raster.version, description='High-precision anti-aliasing polygon rasterizer', author='Jan Petykiewicz', author_email='anewusername@gmail.com', From 219e4b99262db50aa027bf29124810571b927837 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 16 Sep 2018 20:06:57 -0700 Subject: [PATCH 06/33] Use readme as long_description --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index fb5c7cc..e222780 100644 --- a/setup.py +++ b/setup.py @@ -3,9 +3,14 @@ from setuptools import setup import float_raster +with open('README.md', 'r') as f: + long_description = f.read() + setup(name='float_raster', version=float_raster.version, description='High-precision anti-aliasing polygon rasterizer', + long_description=long_description, + long_description_content_type='text/markdown', author='Jan Petykiewicz', author_email='anewusername@gmail.com', url='https://mpxd.net/gogs/jan/float_raster', From c10fd556e2aa02358822bab1546a518011fe124d Mon Sep 17 00:00:00 2001 From: jan Date: Sat, 9 Feb 2019 19:38:15 -0800 Subject: [PATCH 07/33] Update source url --- README.md | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bac1753..e81b072 100644 --- a/README.md +++ b/README.md @@ -14,5 +14,5 @@ Requirements: Install with pip, via git: ```bash -pip install git+https://mpxd.net/gogs/jan/float_raster.git@release +pip install git+https://mpxd.net/code/jan/float_raster.git@release ``` diff --git a/setup.py b/setup.py index e222780..f579d33 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup(name='float_raster', long_description_content_type='text/markdown', author='Jan Petykiewicz', author_email='anewusername@gmail.com', - url='https://mpxd.net/gogs/jan/float_raster', + url='https://mpxd.net/code/jan/float_raster', py_modules=['float_raster'], install_requires=[ 'numpy', From 76f6c68472641b770d4c7514edc64f4d564631d0 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 7 Apr 2019 17:00:55 -0700 Subject: [PATCH 08/33] add MANIFEST.in --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c28ab72 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md +include LICENSE.md From 1ab4b172476d43824f5ac58fb8d7645630ae2a7d Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 7 Apr 2019 17:12:31 -0700 Subject: [PATCH 09/33] add classifiers --- setup.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/setup.py b/setup.py index f579d33..b9f163d 100644 --- a/setup.py +++ b/setup.py @@ -19,4 +19,17 @@ setup(name='float_raster', 'numpy', 'scipy', ], + classifiers=[ + 'Programming Language :: Python :: 3', + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Manufacturing', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: GNU Affero General Public License v3', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Multimedia :: Graphics :: Graphics Conversion', + ], ) From f68ab6bedb3f300203d5499b051a1aed45af4947 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 7 Apr 2019 18:04:46 -0700 Subject: [PATCH 10/33] Update README --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e81b072..472d7b2 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,22 @@ float_raster calculates pixel values with float64 precision and is capable of dr with variable pixel widths and heights. +- [Source repository](https://mpxd.net/code/jan/float_raster) +- [PyPi](https://pypi.org/project/float_raster) + + ## Installation Requirements: * python 3 (written and tested with 3.5) * numpy -Install with pip, via git: +Install with pip: ```bash -pip install git+https://mpxd.net/code/jan/float_raster.git@release +pip3 install float_raster +``` + +Alternatively, install via git +```bash +pip3 install git+https://mpxd.net/code/jan/float_raster.git@release ``` From 50e3822eac66ff70f0906a6766cca1c50d540b2b Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 30 Sep 2019 23:14:13 -0700 Subject: [PATCH 11/33] Create folder-based module and use VERSION file use VERSION to avoid importing the module before it's installed --- MANIFEST.in | 1 + float_raster/VERSION | 1 + float_raster/__init__.py | 16 ++++++++++++++++ float_raster.py => float_raster/float_raster.py | 11 ----------- setup.py | 13 +++++++++---- 5 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 float_raster/VERSION create mode 100644 float_raster/__init__.py rename float_raster.py => float_raster/float_raster.py (98%) diff --git a/MANIFEST.in b/MANIFEST.in index c28ab72..1ea1a47 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include README.md include LICENSE.md +include float_raster/VERSION diff --git a/float_raster/VERSION b/float_raster/VERSION new file mode 100644 index 0000000..bd73f47 --- /dev/null +++ b/float_raster/VERSION @@ -0,0 +1 @@ +0.4 diff --git a/float_raster/__init__.py b/float_raster/__init__.py new file mode 100644 index 0000000..1d96371 --- /dev/null +++ b/float_raster/__init__.py @@ -0,0 +1,16 @@ +""" +Module for rasterizing polygons, with float-precision anti-aliasing on + a non-uniform rectangular grid. + +See the documentation for float_raster.raster(...) for details. +""" + +import pathlib + +from .float_raster import * + +__author__ = 'Jan Petykiewicz' + +with open(pathlib.Path(__file__).parent / 'VERSION', 'r') as f: + __version__ = f.read().strip() + diff --git a/float_raster.py b/float_raster/float_raster.py similarity index 98% rename from float_raster.py rename to float_raster/float_raster.py index fed9bc1..e43d234 100644 --- a/float_raster.py +++ b/float_raster/float_raster.py @@ -1,19 +1,8 @@ -""" -Module for rasterizing polygons, with float-precision anti-aliasing on - a non-uniform rectangular grid. - -See the documentation for raster(...) for details. -""" - from typing import Tuple import numpy from numpy import logical_and, diff, floor, ceil, ones, zeros, hstack, full_like, newaxis from scipy import sparse -__author__ = 'Jan Petykiewicz' - -version = '0.4' - def raster(vertices: numpy.ndarray, grid_x: numpy.ndarray, diff --git a/setup.py b/setup.py index b9f163d..08a7ed9 100644 --- a/setup.py +++ b/setup.py @@ -1,20 +1,25 @@ #!/usr/bin/env python3 -from setuptools import setup -import float_raster +from setuptools import setup, find_packages with open('README.md', 'r') as f: long_description = f.read() +with open('float_raster/VERSION', 'r') as f: + version = f.read().strip() + setup(name='float_raster', - version=float_raster.version, + version=version, description='High-precision anti-aliasing polygon rasterizer', long_description=long_description, long_description_content_type='text/markdown', author='Jan Petykiewicz', author_email='anewusername@gmail.com', url='https://mpxd.net/code/jan/float_raster', - py_modules=['float_raster'], + packages=find_packages(), + package_data={ + 'float_raster': ['VERSION'] + }, install_requires=[ 'numpy', 'scipy', From 99b35a156178831ddfc53467a22c7580a2c89e88 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 30 Sep 2019 23:18:41 -0700 Subject: [PATCH 12/33] gitignore build artifacts --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 715503a..3ef4b5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.pyc __pycache__ *.idea +build/ +dist/ +*.egg-info/ From 94ebf9fa18c3429b156e8f3249b62ea560ba780f Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 30 Sep 2019 23:21:59 -0700 Subject: [PATCH 13/33] bump version to 0.5 --- float_raster/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/float_raster/VERSION b/float_raster/VERSION index bd73f47..2eb3c4f 100644 --- a/float_raster/VERSION +++ b/float_raster/VERSION @@ -1 +1 @@ -0.4 +0.5 From 085bb79ed71dd8a464472a23c06e0f3a4d79077b Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 3 Nov 2020 01:16:43 -0800 Subject: [PATCH 14/33] add py.typed to enable downstream type checking --- float_raster/py.typed | 0 setup.py | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 float_raster/py.typed diff --git a/float_raster/py.typed b/float_raster/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py index 08a7ed9..e47add2 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,9 @@ setup(name='float_raster', url='https://mpxd.net/code/jan/float_raster', packages=find_packages(), package_data={ - 'float_raster': ['VERSION'] + 'float_raster': ['VERSION', + 'py.typed', + ] }, install_requires=[ 'numpy', From ecefdff781e3394a2e77d2c10c65c9f18440ad3e Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 3 Nov 2020 01:17:43 -0800 Subject: [PATCH 15/33] typing and comment updates --- float_raster/float_raster.py | 57 ++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/float_raster/float_raster.py b/float_raster/float_raster.py index e43d234..e5bf0d2 100644 --- a/float_raster/float_raster.py +++ b/float_raster/float_raster.py @@ -1,7 +1,7 @@ -from typing import Tuple -import numpy +from typing import Tuple, Optional +import numpy # type: ignore from numpy import logical_and, diff, floor, ceil, ones, zeros, hstack, full_like, newaxis -from scipy import sparse +from scipy import sparse # type: ignore def raster(vertices: numpy.ndarray, @@ -18,11 +18,14 @@ def raster(vertices: numpy.ndarray, Polygons are assumed to have clockwise vertex order; reversing the vertex order is equivalent to multiplying the result by -1. - :param vertices: 2xN ndarray containing x,y coordinates for each vertex of the polygon - :param grid_x: x-coordinates for the edges of each pixel (ie, the leftmost two columns span - x=grid_x[0] to x=grid_x[1] and x=grid_x[1] to x=grid_x[2]) - :param grid_y: y-coordinates for the edges of each pixel (see grid_x) - :return: 2D ndarray with pixel values in the range [0, 1] containing the anti-aliased polygon + Args: + vertices: 2xN ndarray containing `x,y` coordinates for each vertex of the polygon + grid_x: x-coordinates for the edges of each pixel (ie, the leftmost two columns span + `x=grid_x[0]` to `x=grid_x[1]` and `x=grid_x[1]` to `x=grid_x[2]`) + grid_y: y-coordinates for the edges of each pixel (see `grid_x`) + + Returns: + 2D ndarray with pixel values in the range [0, 1] containing the anti-aliased polygon """ vertices = numpy.array(vertices) grid_x = numpy.array(grid_x) @@ -49,7 +52,7 @@ def find_intersections( vertices: numpy.ndarray, grid_x: numpy.ndarray, grid_y: numpy.ndarray - ) -> Tuple[numpy.ndarray]: + ) -> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]: """ Find intersections between a polygon and grid lines """ @@ -126,7 +129,7 @@ def create_vertices( vertices: numpy.ndarray, grid_x: numpy.ndarray, grid_y: numpy.ndarray, - new_vertex_data: Tuple[numpy.ndarray] = None + new_vertex_data: Optional[Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]] = None ) -> sparse.coo_matrix: """ Create additional vertices where a polygon crosses gridlines @@ -171,6 +174,7 @@ def create_vertices( return vertices + def clip_vertices_to_window( vertices: numpy.ndarray, min_x: float = -numpy.inf, @@ -201,26 +205,29 @@ def get_raster_parts( grid_y: numpy.ndarray ) -> sparse.coo_matrix: """ - This function performs the same task as raster(...), but instead of returning a dense array + This function performs the same task as `raster(...)`, but instead of returning a dense array of pixel values, it returns a sparse array containing the value - (-area + 1j * cover) + `(-area + 1j * cover)` for each pixel which contains a line segment, where - cover is the fraction of the pixel's y-length that is traversed by the segment, - multiplied by the sign of (y_final - y_initial) - area is the fraction of the pixel's area covered by the trapezoid formed by - the line segment's endpoints (clipped to the cell edges) and their projections - onto the pixel's left (i.e., lowest-x) edge, again multiplied by - the sign of (y_final - y_initial) + `cover` is the fraction of the pixel's y-length that is traversed by the segment, + multiplied by the sign of `(y_final - y_initial)` + `area` is the fraction of the pixel's area covered by the trapezoid formed by + the line segment's endpoints (clipped to the cell edges) and their projections + onto the pixel's left (i.e., lowest-x) edge, again multiplied by + the sign of `(y_final - y_initial)` Note that polygons are assumed to be wound clockwise. - The result from raster(...) can be obtained with - raster_result = numpy.real(lines_result) + numpy.imag(lines_result).cumsum(axis=0) + The result from `raster(...)` can be obtained with + `raster_result = numpy.real(lines_result) + numpy.imag(lines_result).cumsum(axis=0)` - :param vertices: 2xN ndarray containing x,y coordinates for each point in the polygon - :param grid_x: x-coordinates for the edges of each pixel (ie, the leftmost two columns span - x=grid_x[0] to x=grid_x[1] and x=grid_x[1] to x=grid_x[2]) - :param grid_y: y-coordinates for the edges of each pixel (see grid_x) - :return: Complex sparse COO matrix containing area and cover information + Args: + vertices: 2xN ndarray containing `x, y` coordinates for each point in the polygon + grid_x: x-coordinates for the edges of each pixel (ie, the leftmost two columns span + `x=grid_x[0]` to `x=grid_x[1]` and `x=grid_x[1]` to `x=grid_x[2]`) + grid_y: y-coordinates for the edges of each pixel (see `grid_x`) + + Returns: + Complex sparse COO matrix containing area and cover information """ if grid_x.size < 2 or grid_y.size < 2: raise Exception('Grid must contain at least one full pixel') From 522b610209664c28af05708a752ac62eee1f09fa Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 3 Nov 2020 01:17:54 -0800 Subject: [PATCH 16/33] add .mypy_cache to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 3ef4b5d..d5e95b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ *.pyc __pycache__ + *.idea + build/ dist/ *.egg-info/ + +.mypy_cache From e5e30a9414ec22ad6e271f82836fbd78fd20e317 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 3 Nov 2020 01:20:53 -0800 Subject: [PATCH 17/33] Use VERSION.py instead of plain text VERSION --- float_raster/VERSION | 1 - float_raster/VERSION.py | 4 ++++ float_raster/__init__.py | 7 +------ setup.py | 9 ++++----- 4 files changed, 9 insertions(+), 12 deletions(-) delete mode 100644 float_raster/VERSION create mode 100644 float_raster/VERSION.py diff --git a/float_raster/VERSION b/float_raster/VERSION deleted file mode 100644 index 2eb3c4f..0000000 --- a/float_raster/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.5 diff --git a/float_raster/VERSION.py b/float_raster/VERSION.py new file mode 100644 index 0000000..d15f477 --- /dev/null +++ b/float_raster/VERSION.py @@ -0,0 +1,4 @@ +""" VERSION defintion. THIS FILE IS MANUALLY PARSED BY setup.py and REQUIRES A SPECIFIC FORMAT """ +__version__ = ''' +0.5 +''' diff --git a/float_raster/__init__.py b/float_raster/__init__.py index 1d96371..db3be2a 100644 --- a/float_raster/__init__.py +++ b/float_raster/__init__.py @@ -5,12 +5,7 @@ Module for rasterizing polygons, with float-precision anti-aliasing on See the documentation for float_raster.raster(...) for details. """ -import pathlib - +from .VERSION import __version__ from .float_raster import * __author__ = 'Jan Petykiewicz' - -with open(pathlib.Path(__file__).parent / 'VERSION', 'r') as f: - __version__ = f.read().strip() - diff --git a/setup.py b/setup.py index e47add2..aabd219 100644 --- a/setup.py +++ b/setup.py @@ -2,11 +2,12 @@ from setuptools import setup, find_packages + with open('README.md', 'r') as f: long_description = f.read() -with open('float_raster/VERSION', 'r') as f: - version = f.read().strip() +with open('float_raster/VERSION.py', 'rt') as f: + version = f.readlines()[2].strip() setup(name='float_raster', version=version, @@ -18,9 +19,7 @@ setup(name='float_raster', url='https://mpxd.net/code/jan/float_raster', packages=find_packages(), package_data={ - 'float_raster': ['VERSION', - 'py.typed', - ] + 'float_raster': ['py.typed'] }, install_requires=[ 'numpy', From f8d3a6600e6a5d72ebb4b57f559fdae5a36a901d Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 3 Nov 2020 01:21:26 -0800 Subject: [PATCH 18/33] bump version to v0.6 --- float_raster/VERSION.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/float_raster/VERSION.py b/float_raster/VERSION.py index d15f477..ba887e5 100644 --- a/float_raster/VERSION.py +++ b/float_raster/VERSION.py @@ -1,4 +1,4 @@ """ VERSION defintion. THIS FILE IS MANUALLY PARSED BY setup.py and REQUIRES A SPECIFIC FORMAT """ __version__ = ''' -0.5 +0.6 ''' From 6d861d8a9c3089a56983e097e0172d74899d5b03 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 3 Nov 2020 01:23:31 -0800 Subject: [PATCH 19/33] update req. python version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 472d7b2..54effc7 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ with variable pixel widths and heights. ## Installation Requirements: -* python 3 (written and tested with 3.5) +* python >=3.8 * numpy Install with pip: From 81e34520aa74d93c7489cc62db1416b3fa7e250c Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 11 Jul 2021 17:23:36 -0700 Subject: [PATCH 20/33] strip whitespace from version string --- float_raster/VERSION.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/float_raster/VERSION.py b/float_raster/VERSION.py index ba887e5..e4f476e 100644 --- a/float_raster/VERSION.py +++ b/float_raster/VERSION.py @@ -1,4 +1,4 @@ """ VERSION defintion. THIS FILE IS MANUALLY PARSED BY setup.py and REQUIRES A SPECIFIC FORMAT """ __version__ = ''' 0.6 -''' +'''.strip() From 38dc51aa7e0e1f2cac5687dfd4600e98648830d1 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 11 Jul 2021 17:23:41 -0700 Subject: [PATCH 21/33] update email --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index aabd219..9560673 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup(name='float_raster', long_description=long_description, long_description_content_type='text/markdown', author='Jan Petykiewicz', - author_email='anewusername@gmail.com', + author_email='jan@mpxd.net', url='https://mpxd.net/code/jan/float_raster', packages=find_packages(), package_data={ From 6ee12d8db9a18d62dfda831931e2a439a565cee2 Mon Sep 17 00:00:00 2001 From: jan Date: Sun, 24 Oct 2021 17:56:54 -0700 Subject: [PATCH 22/33] update pypi tags --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 9560673..ed502ba 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ setup(name='float_raster', 'License :: OSI Approved :: GNU Affero General Public License v3', 'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', - 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Multimedia :: Graphics :: Graphics Conversion', ], ) From 56989009e02ad9c85a5d1b63eaf93ce10d5afb7d Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Thu, 18 Aug 2022 23:10:02 -0700 Subject: [PATCH 23/33] move to hatch-based build --- MANIFEST.in | 3 --- float_raster/LICENSE.md | 1 + float_raster/README.md | 1 + float_raster/VERSION.py | 4 ---- float_raster/__init__.py | 2 +- pyproject.toml | 38 ++++++++++++++++++++++++++++++++++++++ setup.py | 40 ---------------------------------------- 7 files changed, 41 insertions(+), 48 deletions(-) delete mode 100644 MANIFEST.in create mode 120000 float_raster/LICENSE.md create mode 120000 float_raster/README.md delete mode 100644 float_raster/VERSION.py create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 1ea1a47..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include README.md -include LICENSE.md -include float_raster/VERSION diff --git a/float_raster/LICENSE.md b/float_raster/LICENSE.md new file mode 120000 index 0000000..7eabdb1 --- /dev/null +++ b/float_raster/LICENSE.md @@ -0,0 +1 @@ +../LICENSE.md \ No newline at end of file diff --git a/float_raster/README.md b/float_raster/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/float_raster/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/float_raster/VERSION.py b/float_raster/VERSION.py deleted file mode 100644 index e4f476e..0000000 --- a/float_raster/VERSION.py +++ /dev/null @@ -1,4 +0,0 @@ -""" VERSION defintion. THIS FILE IS MANUALLY PARSED BY setup.py and REQUIRES A SPECIFIC FORMAT """ -__version__ = ''' -0.6 -'''.strip() diff --git a/float_raster/__init__.py b/float_raster/__init__.py index db3be2a..cc23c3c 100644 --- a/float_raster/__init__.py +++ b/float_raster/__init__.py @@ -5,7 +5,7 @@ Module for rasterizing polygons, with float-precision anti-aliasing on See the documentation for float_raster.raster(...) for details. """ -from .VERSION import __version__ from .float_raster import * __author__ = 'Jan Petykiewicz' +__version__ = '0.6' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0229023 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "float_raster" +description = "High-precision anti-aliasing polygon rasterizer" +readme = "README.md" +license = { file = "LICENSE.md" } +authors = [ + { name="Jan Petykiewicz", email="jan@mpxd.net" }, + ] +homepage = "https://mpxd.net/code/jan/float_raster" +repository = "https://mpxd.net/code/jan/float_raster" +keywords = [ + "coverage", + ] +classifiers = [ + "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: Manufacturing", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", + "Topic :: Multimedia :: Graphics :: Graphics Conversion", + ] +requires-python = ">=3.8" +dynamic = ["version"] +dependencies = [ + "numpy~=1.21", + "scipy", + ] + +[tool.hatch.version] +path = "float_raster/__init__.py" diff --git a/setup.py b/setup.py deleted file mode 100644 index ed502ba..0000000 --- a/setup.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 - -from setuptools import setup, find_packages - - -with open('README.md', 'r') as f: - long_description = f.read() - -with open('float_raster/VERSION.py', 'rt') as f: - version = f.readlines()[2].strip() - -setup(name='float_raster', - version=version, - description='High-precision anti-aliasing polygon rasterizer', - long_description=long_description, - long_description_content_type='text/markdown', - author='Jan Petykiewicz', - author_email='jan@mpxd.net', - url='https://mpxd.net/code/jan/float_raster', - packages=find_packages(), - package_data={ - 'float_raster': ['py.typed'] - }, - install_requires=[ - 'numpy', - 'scipy', - ], - classifiers=[ - 'Programming Language :: Python :: 3', - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Manufacturing', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: GNU Affero General Public License v3', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', - 'Topic :: Multimedia :: Graphics :: Graphics Conversion', - ], - ) From 0a81c97af172b130dc0f046cf82dff09dff9d8ac Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Thu, 18 Aug 2022 23:10:39 -0700 Subject: [PATCH 24/33] bump version to v0.7 --- float_raster/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/float_raster/__init__.py b/float_raster/__init__.py index cc23c3c..5324115 100644 --- a/float_raster/__init__.py +++ b/float_raster/__init__.py @@ -8,4 +8,4 @@ See the documentation for float_raster.raster(...) for details. from .float_raster import * __author__ = 'Jan Petykiewicz' -__version__ = '0.6' +__version__ = '0.7' From 6e32eda1c7e32c1ac4094076e2cbf59a21f4b800 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Thu, 18 Jul 2024 00:30:18 -0700 Subject: [PATCH 25/33] update type hints --- float_raster/float_raster.py | 53 ++++++++++++++++++++---------------- pyproject.toml | 2 +- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/float_raster/float_raster.py b/float_raster/float_raster.py index e5bf0d2..5f1917b 100644 --- a/float_raster/float_raster.py +++ b/float_raster/float_raster.py @@ -1,13 +1,14 @@ -from typing import Tuple, Optional -import numpy # type: ignore +from numpy.typing import ArrayLike, NDArray +import numpy from numpy import logical_and, diff, floor, ceil, ones, zeros, hstack, full_like, newaxis -from scipy import sparse # type: ignore +from scipy import sparse -def raster(vertices: numpy.ndarray, - grid_x: numpy.ndarray, - grid_y: numpy.ndarray - ) -> numpy.ndarray: +def raster( + vertices: ArrayLike, + grid_x: ArrayLike, + grid_y: ArrayLike, + ) -> NDArray[numpy.float64]: """ Draws a polygon onto a 2D grid of pixels, setting pixel values equal to the fraction of the pixel area covered by the polygon. This implementation is written for accuracy and works with @@ -49,10 +50,10 @@ def raster(vertices: numpy.ndarray, def find_intersections( - vertices: numpy.ndarray, - grid_x: numpy.ndarray, - grid_y: numpy.ndarray - ) -> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]: + vertices: NDArray[numpy.float_], + grid_x: NDArray[numpy.float_], + grid_y: NDArray[numpy.float_], + ) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]]: """ Find intersections between a polygon and grid lines """ @@ -126,10 +127,10 @@ def find_intersections( def create_vertices( - vertices: numpy.ndarray, - grid_x: numpy.ndarray, - grid_y: numpy.ndarray, - new_vertex_data: Optional[Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]] = None + vertices: NDArray[numpy.float_], + grid_x: NDArray[numpy.float_], + grid_y: NDArray[numpy.float_], + new_vertex_data: tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]] | None = None ) -> sparse.coo_matrix: """ Create additional vertices where a polygon crosses gridlines @@ -176,12 +177,12 @@ def create_vertices( def clip_vertices_to_window( - vertices: numpy.ndarray, + vertices: NDArray[numpy.float64], min_x: float = -numpy.inf, max_x: float = numpy.inf, min_y: float = -numpy.inf, max_y: float = numpy.inf - ) -> numpy.ndarray: + ) -> NDArray[numpy.float64]: """ """ # Remove points outside the window (these will only be original points) @@ -200,9 +201,9 @@ def clip_vertices_to_window( def get_raster_parts( - vertices: numpy.ndarray, - grid_x: numpy.ndarray, - grid_y: numpy.ndarray + vertices: ArrayLike, + grid_x: ArrayLike, + grid_y: ArrayLike, ) -> sparse.coo_matrix: """ This function performs the same task as `raster(...)`, but instead of returning a dense array @@ -229,14 +230,20 @@ def get_raster_parts( Returns: Complex sparse COO matrix containing area and cover information """ + vertices = numpy.array(vertices) + grid_x = numpy.array(grid_x) + grid_y = numpy.array(grid_y) + if grid_x.size < 2 or grid_y.size < 2: raise Exception('Grid must contain at least one full pixel') num_xy_px = numpy.array([grid_x.size, grid_y.size]) - 1 - vertices = clip_vertices_to_window(vertices, - grid_x[0], grid_x[-1], - grid_y[0], grid_y[-1]) + vertices = clip_vertices_to_window( + vertices, + grid_x[0], grid_x[-1], + grid_y[0], grid_y[-1], + ) # If the shape fell completely outside our area, just return a blank grid if vertices.size == 0: diff --git a/pyproject.toml b/pyproject.toml index 0229023..e9b447d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ requires-python = ">=3.8" dynamic = ["version"] dependencies = [ - "numpy~=1.21", + "numpy~=1.26", "scipy", ] From 89d0611cfc4fa3bff7449d949ccfd4690f778fce Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 00:37:20 -0700 Subject: [PATCH 26/33] repeat names for re-export --- float_raster/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/float_raster/__init__.py b/float_raster/__init__.py index 5324115..b3bf735 100644 --- a/float_raster/__init__.py +++ b/float_raster/__init__.py @@ -5,7 +5,14 @@ Module for rasterizing polygons, with float-precision anti-aliasing on See the documentation for float_raster.raster(...) for details. """ -from .float_raster import * +from .float_raster import ( + raster as raster, + find_intersections as find_intersections, + create_vertices as create_vertices, + clip_vertices_to_window as clip_vertices_to_window, + get_raster_parts as get_raster_parts, + ) + __author__ = 'Jan Petykiewicz' __version__ = '0.7' From a12ef190fa905b643fa0d904284786e7329ab695 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 00:37:29 -0700 Subject: [PATCH 27/33] float_ -> floating --- float_raster/float_raster.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/float_raster/float_raster.py b/float_raster/float_raster.py index 5f1917b..ab4c258 100644 --- a/float_raster/float_raster.py +++ b/float_raster/float_raster.py @@ -50,9 +50,9 @@ def raster( def find_intersections( - vertices: NDArray[numpy.float_], - grid_x: NDArray[numpy.float_], - grid_y: NDArray[numpy.float_], + vertices: NDArray[numpy.floating], + grid_x: NDArray[numpy.floating], + grid_y: NDArray[numpy.floating], ) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]]: """ Find intersections between a polygon and grid lines @@ -127,9 +127,9 @@ def find_intersections( def create_vertices( - vertices: NDArray[numpy.float_], - grid_x: NDArray[numpy.float_], - grid_y: NDArray[numpy.float_], + vertices: NDArray[numpy.floating], + grid_x: NDArray[numpy.floating], + grid_y: NDArray[numpy.floating], new_vertex_data: tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]] | None = None ) -> sparse.coo_matrix: """ From 9749cecef81e8ec41455a5170ae4f632ee0d280a Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 00:38:25 -0700 Subject: [PATCH 28/33] add some more keywords --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e9b447d..e406000 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,9 @@ homepage = "https://mpxd.net/code/jan/float_raster" repository = "https://mpxd.net/code/jan/float_raster" keywords = [ "coverage", + "raster", + "anti-alias", + "polygon", ] classifiers = [ "Programming Language :: Python :: 3", From 1799faeddd872ddaf0ce4f61d278e5dce2797642 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 00:42:31 -0700 Subject: [PATCH 29/33] add custom exception type --- float_raster/float_raster.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/float_raster/float_raster.py b/float_raster/float_raster.py index ab4c258..26cc4a1 100644 --- a/float_raster/float_raster.py +++ b/float_raster/float_raster.py @@ -4,6 +4,11 @@ from numpy import logical_and, diff, floor, ceil, ones, zeros, hstack, full_like from scipy import sparse +class FloatRasterError(Exception): + """ Custom exception for float_raster """ + pass + + def raster( vertices: ArrayLike, grid_x: ArrayLike, @@ -58,7 +63,7 @@ def find_intersections( Find intersections between a polygon and grid lines """ if vertices.shape[0] != 2: - raise Exception('vertices must be 2xN') + raise FloatRasterError('vertices must be 2xN') min_bounds = floor(vertices.min(axis=1)) max_bounds = ceil(vertices.max(axis=1)) @@ -136,9 +141,9 @@ def create_vertices( Create additional vertices where a polygon crosses gridlines """ if vertices.shape[0] != 2: - raise Exception('vertices must be 2xN') + raise FloatRasterError('vertices must be 2xN') if grid_x.size < 1 or grid_y.size < 1: - raise Exception('Grid must contain at least one line in each direction?') + raise FloatRasterError('Grid must contain at least one line in each direction?') num_poly_vertices = vertices.shape[1] @@ -235,7 +240,7 @@ def get_raster_parts( grid_y = numpy.array(grid_y) if grid_x.size < 2 or grid_y.size < 2: - raise Exception('Grid must contain at least one full pixel') + raise FloatRasterError('Grid must contain at least one full pixel') num_xy_px = numpy.array([grid_x.size, grid_y.size]) - 1 From 379abb5e82e28b70094372f322055606cf20d6e6 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 00:42:49 -0700 Subject: [PATCH 30/33] add configs for ruff any mypy --- pyproject.toml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e406000..e64259d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,3 +39,44 @@ dependencies = [ [tool.hatch.version] path = "float_raster/__init__.py" + + +[tool.ruff] +exclude = [ + ".git", + "dist", + ] +line-length = 145 +indent-width = 4 +lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +lint.select = [ + "NPY", "E", "F", "W", "B", "ANN", "UP", "SLOT", "SIM", "LOG", + "C4", "ISC", "PIE", "PT", "RET", "TCH", "PTH", "INT", + "ARG", "PL", "R", "TRY", + "G010", "G101", "G201", "G202", + "Q002", "Q003", "Q004", + ] +lint.ignore = [ + #"ANN001", # No annotation + "ANN002", # *args + "ANN003", # **kwargs + "ANN401", # Any + "ANN101", # self: Self + "SIM108", # single-line if / else assignment + "RET504", # x=y+z; return x + "PIE790", # unnecessary pass + "ISC003", # non-implicit string concatenation + "C408", # dict(x=y) instead of {'x': y} + "PLR09", # Too many xxx + "PLR2004", # magic number + "PLC0414", # import x as x + "TRY003", # Long exception message + ] + + +[[tool.mypy.overrides]] +module = [ + "scipy", + "scipy.sparse", + ] +ignore_missing_imports = true From 7766a7c43ca17e9d42993fdd57c8aa24987b774a Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 02:06:47 -0700 Subject: [PATCH 31/33] bump minimum versions --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e64259d..4693336 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,11 +30,11 @@ classifiers = [ "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", "Topic :: Multimedia :: Graphics :: Graphics Conversion", ] -requires-python = ">=3.8" +requires-python = ">=3.11" dynamic = ["version"] dependencies = [ - "numpy~=1.26", - "scipy", + "numpy>=1.26", + "scipy~=1.14", ] [tool.hatch.version] From afe4b74edaad6c25d2283a5abb6a043eb2c2d7e4 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 02:07:32 -0700 Subject: [PATCH 32/33] update reqs in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54effc7..da140ef 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ with variable pixel widths and heights. ## Installation Requirements: -* python >=3.8 +* python >=3.11 * numpy Install with pip: From 6e66cba64c39df95f04f4cab7f9bc7ef7623d356 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 02:08:07 -0700 Subject: [PATCH 33/33] bump version to 0.8 --- float_raster/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/float_raster/__init__.py b/float_raster/__init__.py index b3bf735..4efd9b0 100644 --- a/float_raster/__init__.py +++ b/float_raster/__init__.py @@ -15,4 +15,4 @@ from .float_raster import ( __author__ = 'Jan Petykiewicz' -__version__ = '0.7' +__version__ = '0.8'