tornavis/tests/python/bl_pyapi_prop_array.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

86 lines
2.6 KiB
Python
Raw Normal View History

Python: add foreach_get and foreach_set methods to pyrna_prop_array This allows fast access to various arrays in the Python API. Most notably, `image.pixels` can be accessed much more efficiently now. **Benchmark** Below are the results of a benchmark that compares different ways to set/get all pixel values. I do the tests on 2048x2048 rgba images. The benchmark tests the following dimensions: - Byte vs. float per color channel - Python list vs. numpy array containing floats - `foreach_set` (new) vs. `image.pixels = ...` (old) ``` Pixel amount: 2048 * 2048 = 4.194.304 Byte buffer size: 16.8 mb Float buffer size: 67.1 mb Set pixel colors: byte - new - list: 271 ms byte - new - buffer: 29 ms byte - old - list: 350 ms byte - old - buffer: 2900 ms float - new - list: 249 ms float - new - buffer: 8 ms float - old - list: 330 ms float - old - buffer: 2880 ms Get pixel colors: byte - list: 128 ms byte - buffer: 9 ms float - list: 125 ms float - buffer: 8 ms ``` **Observations** The best set and get speed can be achieved with buffers and a float image, at the cost of higher memory consumption. Furthermore, using buffers when using `pixels = ...` is incredibly slow, because it is not optimized. Optimizing this is possible, but might not be trivial (there were multiple attempts afaik). Float images are faster due to overhead introduced by the api for byte images. If I profiled it correctly, a lot of time is spend in the `[0, 1] -> {0, ..., 255}` conversion. The functions doing that conversion is `unit_float_to_uchar_clamp`. While I have an idea on how it can be optimized, I do not know if it can be done without changing its functionality slightly. Performance wise the best solution would be to not do this conversion at all and accept byte input from the api user directly, but that seems to be a more involved task as well. Differential Revision: https://developer.blender.org/D7053 Reviewers: JacquesLucke, mont29
2020-03-13 12:57:12 +01:00
# Apache License, Version 2.0
# ./blender.bin --background -noaudio --python tests/python/bl_pyapi_prop_array.py -- --verbose
import bpy
import unittest
import numpy as np
class TestPropArray(unittest.TestCase):
def setUp(self):
bpy.types.Scene.test_array_f = bpy.props.FloatVectorProperty(size=10)
bpy.types.Scene.test_array_i = bpy.props.IntVectorProperty(size=10)
scene = bpy.context.scene
self.array_f = scene.test_array_f
self.array_i = scene.test_array_i
def test_foreach_getset_i(self):
with self.assertRaises(TypeError):
self.array_i.foreach_set(range(5))
self.array_i.foreach_set(range(5, 15))
with self.assertRaises(TypeError):
self.array_i.foreach_set(np.arange(5, dtype=np.int32))
with self.assertRaises(TypeError):
self.array_i.foreach_set(np.arange(10, dtype=np.int64))
with self.assertRaises(TypeError):
self.array_i.foreach_get(np.arange(10, dtype=np.float32))
a = np.arange(10, dtype=np.int32)
self.array_i.foreach_set(a)
with self.assertRaises(TypeError):
self.array_i.foreach_set(a[:5])
for v1, v2 in zip(a, self.array_i[:]):
self.assertEqual(v1, v2)
b = np.empty(10, dtype=np.int32)
self.array_i.foreach_get(b)
for v1, v2 in zip(a, b):
self.assertEqual(v1, v2)
b = [None] * 10
self.array_f.foreach_get(b)
for v1, v2 in zip(a, b):
self.assertEqual(v1, v2)
def test_foreach_getset_f(self):
with self.assertRaises(TypeError):
self.array_i.foreach_set(range(5))
self.array_f.foreach_set(range(5, 15))
with self.assertRaises(TypeError):
self.array_f.foreach_set(np.arange(5, dtype=np.float32))
with self.assertRaises(TypeError):
self.array_f.foreach_set(np.arange(10, dtype=np.int32))
with self.assertRaises(TypeError):
self.array_f.foreach_get(np.arange(10, dtype=np.float64))
a = np.arange(10, dtype=np.float32)
self.array_f.foreach_set(a)
for v1, v2 in zip(a, self.array_f[:]):
self.assertEqual(v1, v2)
b = np.empty(10, dtype=np.float32)
self.array_f.foreach_get(b)
for v1, v2 in zip(a, b):
self.assertEqual(v1, v2)
b = [None] * 10
self.array_f.foreach_get(b)
for v1, v2 in zip(a, b):
self.assertEqual(v1, v2)
if __name__ == '__main__':
import sys
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
unittest.main()