Release NaraMotion v0.26.0: Structure of Arrays (SoA), Path Arena Allocator, Uniform Grid Indexing, Dual-Buffer Rendering & Centroid Spring Pop-in
b516e00 verified Download tests/test_v26_engine.py from ArifiIslam/NaraMotion: direct link, hf CLI and curl.
- Browser
- Download file 7.02 kB
-
https://huggingface.co/ArifiIslam/NaraMotion/resolve/main/tests/test_v26_engine.py
- Command line
-
hf download hf://ArifiIslam/NaraMotion/tests/test_v26_engine.py
-
curl -L -o test_v26_engine.py https://huggingface.co/ArifiIslam/NaraMotion/resolve/main/tests/test_v26_engine.py
7.02 kB
| """ | |
| Unit tests for NaraMotion v0.26.0 High-Performance Engine Upgrades: | |
| - Structure of Arrays (SoA) and Path Arena Allocator (NativeGeoLayerSoA) | |
| - Direct Native WKB Batch Ingestion (Bypassing Python / Shapely) | |
| - Flat Uniform Grid Spatial Indexing in C++ (UniformGridIndex) | |
| - Dual-Buffer Polygon Render Pipeline (Batch Fills + Batch Strokes) | |
| - Local Centroid Spring Pop-In Animation (Scale = Easing::back_out(t)) | |
| - BBox Overdraw & Viewport Culling | |
| - High-Speed Retained GeoLayer Integration | |
| """ | |
| import os | |
| import pytest | |
| import numpy as np | |
| import naramotion as nm | |
| from naramotion import ( | |
| GeoCanvas, | |
| NativeGeoLayerSoA, | |
| UniformGridIndex, | |
| RetainedGeoLayer, | |
| Image, | |
| Context, | |
| GeoTransform, | |
| Format, | |
| VideoWriter, | |
| ) | |
| try: | |
| import shapely.geometry as sg | |
| import shapely | |
| _HAS_SHAPELY = True | |
| except ImportError: | |
| _HAS_SHAPELY = False | |
| def test_soa_lifecycle_and_polygon_addition(): | |
| """Verify NativeGeoLayerSoA memory allocation, polygon addition, and SoA arrays.""" | |
| layer = NativeGeoLayerSoA(capacity=100) | |
| assert len(layer) == 0 | |
| # Add a rectangle polygon | |
| coords = np.array([[10, 10], [90, 10], [90, 90], [10, 90], [10, 10]], dtype=np.float64) | |
| rings = [0, 5] | |
| idx = layer.add_polygon(coords, rings) | |
| assert idx == 0 | |
| assert len(layer) == 1 | |
| assert layer.count == 1 | |
| # Add second polygon | |
| coords2 = np.array([[100, 100], [200, 100], [200, 200], [100, 200], [100, 100]], dtype=np.float64) | |
| idx2 = layer.add_polygon(coords2, rings) | |
| assert idx2 == 1 | |
| assert len(layer) == 2 | |
| def test_soa_direct_wkb_batch_ingestion(): | |
| """Verify parsing WKB buffers directly in C++ into BLPathCore with holes.""" | |
| if not _HAS_SHAPELY: | |
| pytest.skip("Shapely required for WKB generation in test") | |
| # Outer box with a square hole cut out | |
| poly_with_hole = sg.Polygon( | |
| [(0, 0), (100, 0), (100, 100), (0, 100), (0, 0)], | |
| [[(25, 25), (75, 25), (75, 75), (25, 75), (25, 25)]] | |
| ) | |
| wkb1 = shapely.to_wkb(poly_with_hole) | |
| # MultiPolygon | |
| mpoly = sg.MultiPolygon([ | |
| sg.Polygon([(110, 0), (150, 0), (150, 40), (110, 40)]), | |
| sg.Polygon([(160, 0), (200, 0), (200, 40), (160, 40)]), | |
| ]) | |
| wkb2 = shapely.to_wkb(mpoly) | |
| layer = NativeGeoLayerSoA(capacity=50) | |
| parsed_count = layer.from_wkb_batch([wkb1, wkb2]) | |
| assert parsed_count == 2 | |
| assert len(layer) == 2 | |
| # Verify rendering to context succeeds | |
| img = Image(300, 200, Format.PRGB32) | |
| ctx = Context(img) | |
| res = layer.render_dual_buffer(ctx, fill_color="#38BDF8", stroke_color="#FFFFFF", stroke_width=1.5) | |
| ctx.end() | |
| assert res == 0 | |
| def test_soa_propagation_and_culling(): | |
| """Verify shockwave calculation and vectorized BBox viewport culling.""" | |
| layer = NativeGeoLayerSoA(capacity=10) | |
| # Feature A: near (0, 0) | |
| layer.add_polygon(np.array([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], dtype=np.float64), [0, 5]) | |
| # Feature B: far (100, 100) | |
| layer.add_polygon(np.array([[100, 100], [110, 100], [110, 110], [100, 110], [100, 100]], dtype=np.float64), [0, 5]) | |
| # Propagation from (0, 0) | |
| res = layer.compute_propagation(epicenters=[(0.0, 0.0)], duration_frames=100.0, speed=1.0) | |
| assert res == 0 | |
| # Viewport enclosing only Feature A: Feature B should be culled | |
| vis_count = layer.cull_and_update(viewport_bbox=(-5, -5, 50, 50), current_frame=10.0) | |
| assert vis_count == 1 | |
| # Viewport enclosing both at frame 0 (Feature B wave hasn't arrived) | |
| vis_early = layer.cull_and_update(viewport_bbox=(-5, -5, 200, 200), current_frame=5.0) | |
| assert vis_early == 1 | |
| # Viewport enclosing both at frame 90 (both waves arrived) | |
| vis_late = layer.cull_and_update(viewport_bbox=(-5, -5, 200, 200), current_frame=90.0) | |
| assert vis_late == 2 | |
| def test_soa_dual_buffer_and_spring_popin_rendering(): | |
| """Verify C++ Dual-Buffer batch pass and Centroid Spring Pop-In (back_out) scaling.""" | |
| layer = NativeGeoLayerSoA(capacity=10) | |
| coords = np.array([[50, 50], [150, 50], [150, 150], [50, 150], [50, 50]], dtype=np.float64) | |
| layer.add_polygon(coords, [0, 5]) | |
| layer.compute_propagation(epicenters=[(100.0, 100.0)], duration_frames=60.0) | |
| img = Image(300, 300, Format.PRGB32) | |
| ctx = Context(img) | |
| # 1. Dual-Buffer Batch Render | |
| res1 = layer.render_dual_buffer(ctx, fill_color="#00FFCC", stroke_color="#000000", stroke_width=2.0) | |
| assert res1 == 0 | |
| # 2. Centroid Spring Pop-In at mid-frame (elastic overshoot) | |
| res2 = layer.render_popin(ctx, current_frame=30.0, fill_color="#FF0055", stroke_color="#FFFFFF", stroke_width=1.0) | |
| assert res2 == 0 | |
| # 3. Centroid Spring Pop-In after completion (scale = 1.0) | |
| res3 = layer.render_popin(ctx, current_frame=70.0, fill_color="#FF0055", stroke_color="#FFFFFF", stroke_width=1.0) | |
| assert res3 == 0 | |
| ctx.end() | |
| def test_uniform_grid_spatial_index(): | |
| """Verify flat uniform grid spatial queries in C++.""" | |
| # Bounds: [0, 0, 1000, 1000] with 10x10 cells | |
| grid = UniformGridIndex(bounds=(0.0, 0.0, 1000.0, 1000.0), grid_w=10, grid_h=10) | |
| bboxes = np.array([ | |
| [10.0, 10.0, 50.0, 50.0], # In cell (0, 0) | |
| [510.0, 510.0, 550.0, 550.0], # In cell (5, 5) | |
| [910.0, 910.0, 950.0, 950.0], # In cell (9, 9) | |
| ], dtype=np.float64) | |
| built = grid.build_from_bboxes(bboxes) | |
| assert built == 0 | |
| # Query bottom-left region | |
| q1 = grid.query_bbox(0.0, 0.0, 100.0, 100.0) | |
| assert 0 in q1 | |
| assert 1 not in q1 | |
| assert 2 not in q1 | |
| # Query center region | |
| q2 = grid.query_bbox(500.0, 500.0, 600.0, 600.0) | |
| assert 1 in q2 | |
| assert 0 not in q2 | |
| assert 2 not in q2 | |
| # Query entire area | |
| q_all = grid.query_bbox(0.0, 0.0, 1000.0, 1000.0) | |
| assert len(q_all) == 3 | |
| def test_geocanvas_add_polygon_layer(): | |
| """Verify GeoCanvas integration with NativeGeoLayerSoA and RetainedGeoLayer.""" | |
| canvas = GeoCanvas(400, 300) | |
| # 1. Using NativeGeoLayerSoA | |
| layer_soa = NativeGeoLayerSoA(capacity=10) | |
| coords = np.array([[50, 50], [150, 50], [150, 150], [50, 150], [50, 50]], dtype=np.float64) | |
| layer_soa.add_polygon(coords, [0, 5]) | |
| canvas.add_polygon_layer(layer_soa, fill_color="#00E5FF", stroke_color="#FFFFFF", stroke_width=2.0) | |
| # 2. Using RetainedGeoLayer with popin and frame | |
| if _HAS_SHAPELY: | |
| poly = sg.Polygon([(200, 50), (280, 50), (280, 130), (200, 130)]) | |
| retained = canvas.create_retained_layer([poly], fill_color="#FF007F") | |
| canvas.add_polygon_layer(retained, popin=True, frame=10.0) | |
| img = canvas.render() | |
| assert img.width == 400 | |
| assert img.height == 300 | |
| def test_video_writer_buffer_pipe_streaming(): | |
| """Verify VideoWriter zero-copy buffer pipe access.""" | |
| img = Image(320, 240, Format.PRGB32) | |
| with Context(img) as ctx: | |
| ctx.fill_all("#111827") | |
| ctx.fill_circle(160, 120, 40, "#00E5FF") | |
| # Direct memoryview check | |
| buf = img.buffer | |
| assert isinstance(buf, memoryview) | |
| assert len(buf) == 320 * 240 * 4 | |