diff --git a/CHANGES.md b/CHANGES.md index d48dd4c3..634e8073 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,10 @@ See also the [unreleased changes](https://foss.heptapod.net/fluiddyn/fluidimage/-/compare/0.4.3...branch%2Fdefault). +## [0.4.5] (2024-04-17) + +- Performance optimizations for PIV (- ~40% of CPU time without TPS) + ## [0.4.4] (2024-04-16) - `fluidimage-monitor` [Textual](https://textual.textualize.io/) app to monitor diff --git a/doc/examples/profile_piv_work.py b/doc/examples/profile_piv_work.py new file mode 100644 index 00000000..78b606d1 --- /dev/null +++ b/doc/examples/profile_piv_work.py @@ -0,0 +1,63 @@ +"""Profile PIV work with pyinstrument + +Result with fluidimage 0.4.4: + +total: ~4.7 s + +- ~25% _compute_indices_max, fluidimage/calcul/correl.py:78 +- ~25% CorrelFFTW.__call__, fluidimage/calcul/correl.py:690 (10.4% fftshift, ~1% fft) +- ~10% WorkPIVFromDisplacement._crop_im0 (and 1) fluidimage/works/piv/singlepass.py:403 +- ~17% smooth_clean fluidimage/calcul/smooth_clean.py:25 (9% griddata) + +""" + +import os + +from pyinstrument import Profiler +from pyinstrument.renderers import ConsoleRenderer + +from fluidimage.piv import Work + +os.environ["OMP_NUM_THREADS"] = "1" + +params = Work.create_default_params() + +params.series.path = "../../image_samples/wake_legi/images/B*.png" + +params.piv0.shape_crop_im0 = 40 +params.piv0.displacement_max = 14 + +params.piv0.nb_peaks_to_search = 2 +params.piv0.particle_radius = 3 + +params.mask.strcrop = ":, :1500" + +params.multipass.number = 2 + +params.multipass.use_tps = "last" +# params.multipass.use_tps = False +params.multipass.subdom_size = 200 +params.multipass.smoothing_coef = 10.0 +params.multipass.threshold_tps = 0.5 + +params.fix.correl_min = 0.15 +params.fix.threshold_diff_neighbour = 3 + +work = Work(params=params) + +profiler = Profiler() +profiler.start() +piv = work.process_1_serie() +profiler.stop() + +print( + profiler.output( + renderer=ConsoleRenderer( + unicode=True, + color=True, + show_all=False, + # time="percent_of_total", + # flat=True, # buggy with pyinstrument 4.6.2! + ) + ) +) diff --git a/doc/examples/test_run_examples.py b/doc/examples/test_run_examples.py index ec6b8d23..2528c697 100644 --- a/doc/examples/test_run_examples.py +++ b/doc/examples/test_run_examples.py @@ -46,6 +46,7 @@ def teardown_module(module): "surface_tracking.py", "preproc_sback1_filter.py", "preproc_sback2_rescale.py", + "profile_piv_work.py", ] ) diff --git a/image_samples/wake_legi/images/B00001A.png b/image_samples/wake_legi/images/B00001A.png new file mode 100644 index 00000000..636d556a Binary files /dev/null and b/image_samples/wake_legi/images/B00001A.png differ diff --git a/image_samples/wake_legi/images/B00001B.png b/image_samples/wake_legi/images/B00001B.png new file mode 100644 index 00000000..24b42ef0 Binary files /dev/null and b/image_samples/wake_legi/images/B00001B.png differ diff --git a/pdm.lock b/pdm.lock index 9ecbc782..e16a2007 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "all", "build", "dev", "dev-doc", "doc", "graph", "opencv", "pims", "pytest", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:043123af279d8037dff5c283c24ca6405baed62a783d567d9bad32b2b8499ad7" +content_hash = "sha256:303e1d77e5ae7858a3297b6d4e01c291178305b38841e248e13227be2a2db1f5" [[package]] name = "accessible-pygments" @@ -22,7 +22,7 @@ files = [ [[package]] name = "aiohttp" -version = "3.9.4" +version = "3.9.5" requires_python = ">=3.8" summary = "Async http client/server framework (asyncio)" groups = ["dev"] @@ -35,67 +35,67 @@ dependencies = [ "yarl<2.0,>=1.0", ] files = [ - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:76d32588ef7e4a3f3adff1956a0ba96faabbdee58f2407c122dd45aa6e34f372"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56181093c10dbc6ceb8a29dfeea1e815e1dfdc020169203d87fd8d37616f73f9"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7a5b676d3c65e88b3aca41816bf72831898fcd73f0cbb2680e9d88e819d1e4d"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1df528a85fb404899d4207a8d9934cfd6be626e30e5d3a5544a83dbae6d8a7e"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f595db1bceabd71c82e92df212dd9525a8a2c6947d39e3c994c4f27d2fe15b11"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0b09d76e5a4caac3d27752027fbd43dc987b95f3748fad2b924a03fe8632ad"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689eb4356649ec9535b3686200b231876fb4cab4aca54e3bece71d37f50c1d13"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3666cf4182efdb44d73602379a66f5fdfd5da0db5e4520f0ac0dcca644a3497"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b65b0f8747b013570eea2f75726046fa54fa8e0c5db60f3b98dd5d161052004a"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1885d2470955f70dfdd33a02e1749613c5a9c5ab855f6db38e0b9389453dce7"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0593822dcdb9483d41f12041ff7c90d4d1033ec0e880bcfaf102919b715f47f1"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:47f6eb74e1ecb5e19a78f4a4228aa24df7fbab3b62d4a625d3f41194a08bd54f"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c8b04a3dbd54de6ccb7604242fe3ad67f2f3ca558f2d33fe19d4b08d90701a89"}, - {file = "aiohttp-3.9.4-cp310-cp310-win32.whl", hash = "sha256:8a78dfb198a328bfb38e4308ca8167028920fb747ddcf086ce706fbdd23b2926"}, - {file = "aiohttp-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:e78da6b55275987cbc89141a1d8e75f5070e577c482dd48bd9123a76a96f0bbb"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c111b3c69060d2bafc446917534150fd049e7aedd6cbf21ba526a5a97b4402a5"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbdd51872cf170093998c87ccdf3cb5993add3559341a8e5708bcb311934c94"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bfdb41dc6e85d8535b00d73947548a748e9534e8e4fddd2638109ff3fb081df"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd9d334412961125e9f68d5b73c1d0ab9ea3f74a58a475e6b119f5293eee7ba"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35d78076736f4a668d57ade00c65d30a8ce28719d8a42471b2a06ccd1a2e3063"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:824dff4f9f4d0f59d0fa3577932ee9a20e09edec8a2f813e1d6b9f89ced8293f"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52b8b4e06fc15519019e128abedaeb56412b106ab88b3c452188ca47a25c4093"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae569fb1e7559d4f3919965617bb39f9e753967fae55ce13454bec2d1c54f09"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69b97aa5792428f321f72aeb2f118e56893371f27e0b7d05750bcad06fc42ca1"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d79aad0ad4b980663316f26d9a492e8fab2af77c69c0f33780a56843ad2f89e"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d6577140cd7db19e430661e4b2653680194ea8c22c994bc65b7a19d8ec834403"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:9860d455847cd98eb67897f5957b7cd69fbcb436dd3f06099230f16a66e66f79"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69ff36d3f8f5652994e08bd22f093e11cfd0444cea310f92e01b45a4e46b624e"}, - {file = "aiohttp-3.9.4-cp311-cp311-win32.whl", hash = "sha256:e27d3b5ed2c2013bce66ad67ee57cbf614288bda8cdf426c8d8fe548316f1b5f"}, - {file = "aiohttp-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d6a67e26daa686a6fbdb600a9af8619c80a332556245fa8e86c747d226ab1a1e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c5ff8ff44825736a4065d8544b43b43ee4c6dd1530f3a08e6c0578a813b0aa35"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d12a244627eba4e9dc52cbf924edef905ddd6cafc6513849b4876076a6f38b0e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcad56c8d8348e7e468899d2fb3b309b9bc59d94e6db08710555f7436156097f"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7e69a7fd4b5ce419238388e55abd220336bd32212c673ceabc57ccf3d05b55"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4870cb049f10d7680c239b55428916d84158798eb8f353e74fa2c98980dcc0b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2feaf1b7031ede1bc0880cec4b0776fd347259a723d625357bb4b82f62687b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939393e8c3f0a5bcd33ef7ace67680c318dc2ae406f15e381c0054dd658397de"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d2334e387b2adcc944680bebcf412743f2caf4eeebd550f67249c1c3696be04"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0198ea897680e480845ec0ffc5a14e8b694e25b3f104f63676d55bf76a82f1a"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e40d2cd22914d67c84824045861a5bb0fb46586b15dfe4f046c7495bf08306b2"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:aba80e77c227f4234aa34a5ff2b6ff30c5d6a827a91d22ff6b999de9175d71bd"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:fb68dc73bc8ac322d2e392a59a9e396c4f35cb6fdbdd749e139d1d6c985f2527"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f3460a92638dce7e47062cf088d6e7663adb135e936cb117be88d5e6c48c9d53"}, - {file = "aiohttp-3.9.4-cp312-cp312-win32.whl", hash = "sha256:32dc814ddbb254f6170bca198fe307920f6c1308a5492f049f7f63554b88ef36"}, - {file = "aiohttp-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:63f41a909d182d2b78fe3abef557fcc14da50c7852f70ae3be60e83ff64edba5"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:84e90494db7df3be5e056f91412f9fa9e611fbe8ce4aaef70647297f5943b276"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d4845f8501ab28ebfdbeab980a50a273b415cf69e96e4e674d43d86a464df9d"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69046cd9a2a17245c4ce3c1f1a4ff8c70c7701ef222fce3d1d8435f09042bba1"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b73a06bafc8dcc508420db43b4dd5850e41e69de99009d0351c4f3007960019"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:418bb0038dfafeac923823c2e63226179976c76f981a2aaad0ad5d51f2229bca"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a8f241456b6c2668374d5d28398f8e8cdae4cce568aaea54e0f39359cd928d"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935c369bf8acc2dc26f6eeb5222768aa7c62917c3554f7215f2ead7386b33748"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4e48c8752d14ecfb36d2ebb3d76d614320570e14de0a3aa7a726ff150a03c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:916b0417aeddf2c8c61291238ce25286f391a6acb6f28005dd9ce282bd6311b6"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9b6787b6d0b3518b2ee4cbeadd24a507756ee703adbac1ab6dc7c4434b8c572a"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:221204dbda5ef350e8db6287937621cf75e85778b296c9c52260b522231940ed"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:10afd99b8251022ddf81eaed1d90f5a988e349ee7d779eb429fb07b670751e8c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2506d9f7a9b91033201be9ffe7d89c6a54150b0578803cce5cb84a943d075bc3"}, - {file = "aiohttp-3.9.4-cp39-cp39-win32.whl", hash = "sha256:e571fdd9efd65e86c6af2f332e0e95dad259bfe6beb5d15b3c3eca3a6eb5d87b"}, - {file = "aiohttp-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:7d29dd5319d20aa3b7749719ac9685fbd926f71ac8c77b2477272725f882072d"}, - {file = "aiohttp-3.9.4.tar.gz", hash = "sha256:6ff71ede6d9a5a58cfb7b6fffc83ab5d4a63138276c771ac91ceaaddf5459644"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [[package]] @@ -1071,7 +1071,7 @@ name = "gprof2dot" version = "2022.7.29" requires_python = ">=2.7" summary = "Generate a dot graph from the output of several profilers." -groups = ["graph"] +groups = ["all", "graph"] files = [ {file = "gprof2dot-2022.7.29-py2.py3-none-any.whl", hash = "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6"}, {file = "gprof2dot-2022.7.29.tar.gz", hash = "sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5"}, @@ -2491,7 +2491,7 @@ name = "opencv-python" version = "4.9.0.80" requires_python = ">=3.6" summary = "Wrapper package for OpenCV python bindings." -groups = ["opencv"] +groups = ["all", "opencv"] dependencies = [ "numpy>=1.17.0; python_version >= \"3.7\"", "numpy>=1.17.3; python_version >= \"3.8\"", @@ -2931,6 +2931,56 @@ files = [ {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] +[[package]] +name = "pyinstrument" +version = "4.6.2" +requires_python = ">=3.7" +summary = "Call stack profiler for Python. Shows you why your code is slow!" +groups = ["dev"] +files = [ + {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a1b1cd768ea7ea9ab6f5490f7e74431321bcc463e9441dbc2f769617252d9e2"}, + {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a386b9d09d167451fb2111eaf86aabf6e094fed42c15f62ec51d6980bce7d96"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3e3ca8553b9aac09bd978c73d21b9032c707ac6d803bae6a20ecc048df4a8"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f329f5534ca069420246f5ce57270d975229bcb92a3a3fd6b2ca086527d9764"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4dcdcc7ba224a0c5edfbd00b0f530f5aed2b26da5aaa2f9af5519d4aa8c7e41"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73db0c2c99119c65b075feee76e903b4ed82e59440fe8b5724acf5c7cb24721f"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:da58f265326f3cf3975366ccb8b39014f1e69ff8327958a089858d71c633d654"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:feebcf860f955401df30d029ec8de7a0c5515d24ea809736430fd1219686fe14"}, + {file = "pyinstrument-4.6.2-cp310-cp310-win32.whl", hash = "sha256:b2b66ff0b16c8ecf1ec22de001cfff46872b2c163c62429055105564eef50b2e"}, + {file = "pyinstrument-4.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8d104b7a7899d5fa4c5bf1ceb0c1a070615a72c5dc17bc321b612467ad5c5d88"}, + {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:62f6014d2b928b181a52483e7c7b82f2c27e22c577417d1681153e5518f03317"}, + {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5c8d763c5df55131670ba2a01a8aebd0d490a789904a55eb6a8b8d497f110"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ed4e8c6c84e0e6429ba7008a66e435ede2d8cb027794c20923c55669d9c5633"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c0f0e1d8f8c70faa90ff57f78ac0dda774b52ea0bfb2d9f0f41ce6f3e7c869e"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3c44cb037ad0d6e9d9a48c14d856254ada641fbd0ae9de40da045fc2226a2a"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:be9901f17ac2f527c352f2fdca3d717c1d7f2ce8a70bad5a490fc8cc5d2a6007"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a9791bf8916c1cf439c202fded32de93354b0f57328f303d71950b0027c7811"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d6162615e783c59e36f2d7caf903a7e3ecb6b32d4a4ae8907f2760b2ef395bf6"}, + {file = "pyinstrument-4.6.2-cp311-cp311-win32.whl", hash = "sha256:28af084aa84bbfd3620ebe71d5f9a0deca4451267f363738ca824f733de55056"}, + {file = "pyinstrument-4.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:dd6007d3c2e318e09e582435dd8d111cccf30d342af66886b783208813caf3d7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3813c8ecfab9d7d855c5f0f71f11793cf1507f40401aa33575c7fd613577c23"}, + {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c761372945e60fc1396b7a49f30592e8474e70a558f1a87346d27c8c4ce50f7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fba3244e94c117bf4d9b30b8852bbdcd510e7329fdd5c7c8b3799e00a9215a8"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:803ac64e526473d64283f504df3b0d5c2c203ea9603cab428641538ffdc753a7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2e554b1bb0df78f5ce8a92df75b664912ca93aa94208386102af454ec31b647"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7c671057fad22ee3ded897a6a361204ea2538e44c1233cad0e8e30f6d27f33db"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d02f31fa13a9e8dc702a113878419deba859563a32474c9f68e04619d43d6f01"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b55983a884f083f93f0fc6d12ff8df0acd1e2fb0580d2f4c7bfe6def33a84b58"}, + {file = "pyinstrument-4.6.2-cp312-cp312-win32.whl", hash = "sha256:fdc0a53b27e5d8e47147489c7dab596ddd1756b1e053217ef5bc6718567099ff"}, + {file = "pyinstrument-4.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:dd5c53a0159126b5ce7cbc4994433c9c671e057c85297ff32645166a06ad2c50"}, + {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3a165e0d2deb212d4cf439383982a831682009e1b08733c568cac88c89784e62"}, + {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ba858b3d6f6e5597c641edcc0e7e464f85aba86d71bc3b3592cb89897bf43f6"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fd8e547cf3df5f0ec6e4dffbe2e857f6b28eda51b71c3c0b5a2fc0646527835"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de2c1714a37a820033b19cf134ead43299a02662f1379140974a9ab733c5f3a"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01fc45dedceec3df81668d702bca6d400d956c8b8494abc206638c167c78dfd9"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5b6e161ef268d43ee6bbfae7fd2cdd0a52c099ddd21001c126ca1805dc906539"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6ba8e368d0421f15ba6366dfd60ec131c1b46505d021477e0f865d26cf35a605"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edca46f04a573ac2fb11a84b937844e6a109f38f80f4b422222fb5be8ecad8cb"}, + {file = "pyinstrument-4.6.2-cp39-cp39-win32.whl", hash = "sha256:baf375953b02fe94d00e716f060e60211ede73f49512b96687335f7071adb153"}, + {file = "pyinstrument-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:af1a953bce9fd530040895d01ff3de485e25e1576dccb014f76ba9131376fcad"}, + {file = "pyinstrument-4.6.2.tar.gz", hash = "sha256:0002ee517ed8502bbda6eb2bb1ba8f95a55492fcdf03811ba13d4806e50dd7f6"}, +] + [[package]] name = "pylint" version = "3.1.0" diff --git a/pixi.lock b/pixi.lock index d981d673..acafd607 100644 --- a/pixi.lock +++ b/pixi.lock @@ -13350,6 +13350,75 @@ package: license_family: Apache size: 207974 timestamp: 1607309596528 +- platform: linux-64 + name: linkify-it-py + version: 2.0.3 + category: main + manager: conda + dependencies: + - python >=3.7 + - uc-micro-py + url: https://conda.anaconda.org/conda-forge/noarch/linkify-it-py-2.0.3-pyhd8ed1ab_0.conda + hash: + md5: f1b64ca4faf563605cf6f6ca93f9ff3f + sha256: aa99d44e8c83865026575a8af253141c53e0b3ab05f053befaa7757c8525064f + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: linux-64 + build_number: 0 + license: MIT + license_family: MIT + noarch: python + size: 24035 + timestamp: 1707129321841 + purls: + - pkg:pypi/linkify-it-py +- platform: osx-64 + name: linkify-it-py + version: 2.0.3 + category: main + manager: conda + dependencies: + - python >=3.7 + - uc-micro-py + url: https://conda.anaconda.org/conda-forge/noarch/linkify-it-py-2.0.3-pyhd8ed1ab_0.conda + hash: + md5: f1b64ca4faf563605cf6f6ca93f9ff3f + sha256: aa99d44e8c83865026575a8af253141c53e0b3ab05f053befaa7757c8525064f + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: osx-64 + build_number: 0 + license: MIT + license_family: MIT + noarch: python + size: 24035 + timestamp: 1707129321841 + purls: + - pkg:pypi/linkify-it-py +- platform: win-64 + name: linkify-it-py + version: 2.0.3 + category: main + manager: conda + dependencies: + - python >=3.7 + - uc-micro-py + url: https://conda.anaconda.org/conda-forge/noarch/linkify-it-py-2.0.3-pyhd8ed1ab_0.conda + hash: + md5: f1b64ca4faf563605cf6f6ca93f9ff3f + sha256: aa99d44e8c83865026575a8af253141c53e0b3ab05f053befaa7757c8525064f + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: win-64 + build_number: 0 + license: MIT + license_family: MIT + noarch: python + size: 24035 + timestamp: 1707129321841 + purls: + - pkg:pypi/linkify-it-py - platform: osx-64 name: llvm-openmp version: 18.1.3 @@ -17795,6 +17864,69 @@ package: timestamp: 1709992719691 purls: - pkg:pypi/pytest +- platform: linux-64 + name: pytest-asyncio + version: 0.23.6 + category: main + manager: conda + dependencies: + - pytest >=7.0.0,<9 + - python >=3.8 + url: https://conda.anaconda.org/conda-forge/noarch/pytest-asyncio-0.23.6-pyhd8ed1ab_0.conda + hash: + md5: 811340befcada7641384d4f115ddbd6e + sha256: 4867959aacaf4402a3e12bb283b8e09a0f8cdc1f6bd7aab84e0d4d2f33b5b994 + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: linux-64 + build_number: 0 + license: Apache-2.0 + license_family: APACHE + noarch: python + size: 33372 + timestamp: 1710849429651 +- platform: osx-64 + name: pytest-asyncio + version: 0.23.6 + category: main + manager: conda + dependencies: + - pytest >=7.0.0,<9 + - python >=3.8 + url: https://conda.anaconda.org/conda-forge/noarch/pytest-asyncio-0.23.6-pyhd8ed1ab_0.conda + hash: + md5: 811340befcada7641384d4f115ddbd6e + sha256: 4867959aacaf4402a3e12bb283b8e09a0f8cdc1f6bd7aab84e0d4d2f33b5b994 + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: osx-64 + build_number: 0 + license: Apache-2.0 + license_family: APACHE + noarch: python + size: 33372 + timestamp: 1710849429651 +- platform: win-64 + name: pytest-asyncio + version: 0.23.6 + category: main + manager: conda + dependencies: + - pytest >=7.0.0,<9 + - python >=3.8 + url: https://conda.anaconda.org/conda-forge/noarch/pytest-asyncio-0.23.6-pyhd8ed1ab_0.conda + hash: + md5: 811340befcada7641384d4f115ddbd6e + sha256: 4867959aacaf4402a3e12bb283b8e09a0f8cdc1f6bd7aab84e0d4d2f33b5b994 + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: win-64 + build_number: 0 + license: Apache-2.0 + license_family: APACHE + noarch: python + size: 33372 + timestamp: 1710849429651 - platform: linux-64 name: pytest-cov version: 5.0.0 @@ -20975,6 +21107,72 @@ package: noarch: generic size: 119815 timestamp: 1706886945727 +- platform: linux-64 + name: uc-micro-py + version: 1.0.3 + category: main + manager: conda + dependencies: + - python >=3.6 + url: https://conda.anaconda.org/conda-forge/noarch/uc-micro-py-1.0.3-pyhd8ed1ab_0.conda + hash: + md5: 3b7fc78d7be7b450952aaa916fb78877 + sha256: 54293cd94da3a6b978b353eb7897555055d925ad0008bc73e85cca19e2587ed0 + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: linux-64 + build_number: 0 + license: MIT + license_family: MIT + noarch: python + size: 11162 + timestamp: 1707507514699 + purls: + - pkg:pypi/uc-micro-py +- platform: osx-64 + name: uc-micro-py + version: 1.0.3 + category: main + manager: conda + dependencies: + - python >=3.6 + url: https://conda.anaconda.org/conda-forge/noarch/uc-micro-py-1.0.3-pyhd8ed1ab_0.conda + hash: + md5: 3b7fc78d7be7b450952aaa916fb78877 + sha256: 54293cd94da3a6b978b353eb7897555055d925ad0008bc73e85cca19e2587ed0 + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: osx-64 + build_number: 0 + license: MIT + license_family: MIT + noarch: python + size: 11162 + timestamp: 1707507514699 + purls: + - pkg:pypi/uc-micro-py +- platform: win-64 + name: uc-micro-py + version: 1.0.3 + category: main + manager: conda + dependencies: + - python >=3.6 + url: https://conda.anaconda.org/conda-forge/noarch/uc-micro-py-1.0.3-pyhd8ed1ab_0.conda + hash: + md5: 3b7fc78d7be7b450952aaa916fb78877 + sha256: 54293cd94da3a6b978b353eb7897555055d925ad0008bc73e85cca19e2587ed0 + build: pyhd8ed1ab_0 + arch: x86_64 + subdir: win-64 + build_number: 0 + license: MIT + license_family: MIT + noarch: python + size: 11162 + timestamp: 1707507514699 + purls: + - pkg:pypi/uc-micro-py - platform: win-64 name: ucrt version: 10.0.22621.0 diff --git a/pixi.toml b/pixi.toml index 7c9ff319..c0a8302e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -37,3 +37,5 @@ pythran = ">=0.15.0" pytest = ">=8.0.0" pytest-cov = ">=4.1.0" coverage = ">=7.4.1" +pytest-asyncio = ">=0.23.6,<0.24" +linkify-it-py = ">=2.0.3,<2.1" diff --git a/pyproject.toml b/pyproject.toml index f49bebec..1801d090 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = 'mesonpy' [project] name = "fluidimage" -version = "0.4.4" +version = "0.4.5" description = "Fluid image processing with Python." authors = [ {name = "Pierre Augier", email = "pierre.augier@legi.cnrs.fr"}, @@ -64,7 +64,7 @@ pims = ["pims"] opencv = ["opencv-python"] graph = ["gprof2dot"] pytest = ["pytest", "pytest-asyncio"] -all = ["fluidimage[pims, opencv-python, gprof2dot, pytest]"] +all = ["fluidimage[pims, opencv, graph, pytest]"] # qt = ["PySide6"] [project.entry-points."fluidimage.executors"] @@ -124,6 +124,7 @@ dev = [ "isort", "ipdb>=0.13.13", "textual-dev", + "pyinstrument", ] [tool.pdm.scripts] diff --git a/src/fluidimage/calcul/_evaluate_subpix.py b/src/fluidimage/calcul/_evaluate_subpix.py index 4756069e..b23710fd 100644 --- a/src/fluidimage/calcul/_evaluate_subpix.py +++ b/src/fluidimage/calcul/_evaluate_subpix.py @@ -23,11 +23,6 @@ except ImportError: classes.pop("skcufft") -try: - import theano -except ImportError: - classes.pop("theano") - def setUpImages(nx, ny, dx, dy, part_size): displacements = np.array([dx, dy]) diff --git a/src/fluidimage/calcul/correl.py b/src/fluidimage/calcul/correl.py index 639d6e45..762683e1 100644 --- a/src/fluidimage/calcul/correl.py +++ b/src/fluidimage/calcul/correl.py @@ -24,10 +24,6 @@ :members: :private-members: -.. autoclass:: CorrelTheano - :members: - :private-members: - .. autoclass:: CorrelFFTNumpy :members: :private-members: @@ -46,22 +42,19 @@ """ +from abc import ABC, abstractmethod + import numpy as np from numpy.fft import fft2, ifft2 from scipy.ndimage import correlate from scipy.signal import correlate2d -from transonic import boost +from transonic import Array, Type, boost from .correl_pycuda import correl_pycuda from .errors import PIVError from .fft import CUFFT2DReal2Complex, FFTW2DReal2Complex, SKCUFFT2DReal2Complex from .subpix import SubPix -try: - import theano -except ImportError: - pass - def compute_indices_from_displacement(dx, dy, indices_no_displ): return indices_no_displ[1] - dx, indices_no_displ[0] - dy @@ -75,8 +68,82 @@ def parse_displacement_max(displ_max, im0_shape): return displ_max -def _compute_indices_max(correl, norm): - iy, ix = np.unravel_index(np.nanargmax(correl), correl.shape) +A2dC = Array[Type(np.float32, np.float64), "2d", "C"] +A2df32 = "float32[][]" + + +def _is_there_a_nan(arr): + arr = arr.ravel() + for idx in range(9): + if np.isnan(arr[idx]): + return True + return False + + +@boost +def nan_indices_max( + correl: A2dC, + i0_start: np.int32, + i0_stop: np.int32, + i1_start: np.int32, + i1_stop: np.int32, +): + + correl_max = np.nan + + # first, get the first non nan value + n0, n1 = correl.shape + correl_flatten = correl.ravel() + for i_flat in range(i0_start * n1 + i1_start, n0 * n1): + value = correl_flatten[i_flat] + if not np.isnan(value): + correl_max = value + break + + assert not np.isnan(correl_max) + + i0_max = 0 + i1_max = 0 + + for i0 in range(i0_start, i0_stop): + for i1 in range(i1_start, i1_stop): + value = correl[i0, i1] + if np.isnan(value): + continue + if value >= correl_max: + correl_max = value + i0_max = i0 + i1_max = i1 + + error_message = "" + + i0, i1 = i0_max, i1_max + + if i0 == 0 or i0 == n0 - 1 or i1 == 0 or i1 == n1 - 1: + error_message = "Correlation peak touching boundary." + elif _is_there_a_nan(correl[i0 - 1 : i0 + 2, i1 - 1 : i1 + 2]): + error_message = "Correlation peak touching nan." + + return i0_max, i1_max, error_message + + +def _compute_indices_max( + correl, norm, start_stop_for_search0, start_stop_for_search1 +): + """Compute the indices of the maximum correlation + + Warning: important for perf, so use Pythran + + """ + i0_start, i0_stop = start_stop_for_search0 + i1_start, i1_stop = start_stop_for_search1 + + if i0_stop is None: + i0_stop, i1_stop = correl.shape + + iy, ix, error_message = nan_indices_max( + correl, i0_start, i0_stop, i1_start, i1_stop + ) if norm == 0: # I hope it is ok (Pierre) @@ -84,25 +151,15 @@ def _compute_indices_max(correl, norm): else: correl_max = correl[iy, ix] / norm - if ( - iy == 0 - or iy == correl.shape[0] - 1 - or ix == 0 - or ix == correl.shape[1] - 1 - ): - error = PIVError(explanation="Correlation peak touching boundary.") - error.results = (ix, iy, correl_max) - raise error - - if np.isnan(np.sum(correl[iy - 1 : iy + 2 : 2, ix - 1 : ix + 2 : 2])): - error = PIVError(explanation="Correlation peak touching nan.") + if error_message: + error = PIVError(explanation=error_message) error.results = (ix, iy, correl_max) raise error return ix, iy, correl_max -class CorrelBase: +class CorrelBase(ABC): """This class is meant to be subclassed, not instantiated directly.""" _tag = "base" @@ -133,26 +190,39 @@ def __init__( self.particle_radius = particle_radius self.nb_peaks_to_search = nb_peaks_to_search - self._init2() + self.start_stop_for_search0 = [0, None] + self.start_stop_for_search1 = [0, None] + self._finalize_init() - def _init2(self): - pass + @abstractmethod + def __call__(self, im0, im1): + """Compute the correlation from 2 images.""" + + def _finalize_init(self): + """Finalize initialization""" def compute_displacement_from_indices(self, ix, iy): """Compute the displacement from a couple of indices.""" return self.ix0 - ix, self.iy0 - iy def compute_indices_from_displacement(self, dx, dy): + """Compute the indices corresponding to a displacement""" return self.ix0 - dx, self.iy0 - dy def get_indices_no_displacement(self): + """Get the indices corresponding to no displacement""" return self.iy0, self.ix0 + def _compute_indices_max(self, correl, norm): + return _compute_indices_max( + correl, norm, self.start_stop_for_search0, self.start_stop_for_search1 + ) + def compute_displacements_from_correl(self, correl, norm=1.0): """Compute the displacement from a correlation.""" try: - ix, iy, correl_max = _compute_indices_max(correl, norm) + ix, iy, correl_max = self._compute_indices_max(correl, norm) except PIVError as piv_error: ix, iy, correl_max = piv_error.results # second chance to find a better peak... @@ -161,7 +231,7 @@ def compute_displacements_from_correl(self, correl, norm=1.0): ix - self.particle_radius : ix + self.particle_radius + 1, ] = np.nan try: - ix2, iy2, correl_max2 = _compute_indices_max(correl, norm) + ix2, iy2, correl_max2 = self._compute_indices_max(correl, norm) except PIVError as _piv_error: dx, dy = self.compute_displacement_from_indices(ix, iy) _piv_error.results = (dx, dy, correl_max) @@ -177,13 +247,15 @@ def compute_displacements_from_correl(self, correl, norm=1.0): elif self.nb_peaks_to_search >= 1: correl = correl.copy() other_peaks = [] - for ip in range(0, self.nb_peaks_to_search - 1): + for _ in range(0, self.nb_peaks_to_search - 1): correl[ iy - self.particle_radius : iy + self.particle_radius + 1, ix - self.particle_radius : ix + self.particle_radius + 1, ] = np.nan try: - ix, iy, correl_max_other = _compute_indices_max(correl, norm) + ix, iy, correl_max_other = self._compute_indices_max( + correl, norm + ) except PIVError: break @@ -204,11 +276,8 @@ def apply_subpix(self, dx, dy, correl): return self.compute_displacement_from_indices(ix, iy) -A = "float32[][]" - - @boost -def correl_numpy(im0: A, im1: A, disp_max: int): +def correl_numpy(im0: A2df32, im1: A2df32, disp_max: int): """Correlations by hand using only numpy. Parameters @@ -307,13 +376,11 @@ def correl_numpy(im0: A, im1: A, disp_max: int): class CorrelPythran(CorrelBase): - """Correlation using pythran. - Correlation class by hands with with numpy. - """ + """Correlation computed "by hands" with Numpy and Pythran""" _tag = "pythran" - def _init2(self): + def _finalize_init(self): if self.displacement_max is None: if self.im0_shape == self.im1_shape: displacement_max = min(self.im0_shape) // 2 - 1 @@ -338,18 +405,17 @@ def _init2(self): def __call__(self, im0, im1): """Compute the correlation from images.""" - correl, norm = correl_numpy(im0, im1, self.displacement_max) - return correl, norm + return correl_numpy(im0, im1, self.displacement_max) class CorrelPyCuda(CorrelBase): """Correlation using pycuda. - Correlation class by hands with with cuda. + Correlation class by hands with cuda. """ _tag = "pycuda" - def _init2(self): + def _finalize_init(self): if self.displacement_max is None: if self.im0_shape == self.im1_shape: displacement_max = min(self.im0_shape) // 2 @@ -375,7 +441,8 @@ def _init2(self): def __call__(self, im0, im1): """Compute the correlation from images.""" correl, norm = correl_pycuda(im0, im1, self.displacement_max) - self._add_info_to_correl(correl) + # ??? + # self._add_info_to_correl(correl) return correl, norm @@ -384,7 +451,7 @@ class CorrelScipySignal(CorrelBase): _tag = "scipy.signal" - def _init2(self): + def _finalize_init(self): if self.mode is None: self.mode = "same" @@ -412,7 +479,7 @@ def _init2(self): def __call__(self, im0, im1): """Compute the correlation from images.""" - norm = np.sqrt(np.sum(im1**2) * np.sum(im0**2)) + norm = _norm_images(im0, im1) if self.mode == "valid": correl = correlate2d(im0, im1, mode="valid") elif self.mode == "same": @@ -428,194 +495,13 @@ class CorrelScipyNdimage(CorrelBase): _tag = "scipy.ndimage" - def _init2(self): + def _finalize_init(self): self.iy0, self.ix0 = (i // 2 for i in self.im0_shape) def __call__(self, im0, im1): """Compute the correlation from images.""" norm = np.sum(im1**2) correl = correlate(im0, im1, mode="constant", cval=im1.min()) - - return correl, norm - - -class CorrelTheano(CorrelBase): - """Correlations using theano.tensor.nnet.conv2d""" - - _tag = "theano" - - def _init2(self): - if self.mode is None: - mode = self.mode = "disp" - - im0_shape = self.im0_shape - im1_shape = self.im1_shape - - if im1_shape is None: - im1_shape = im0_shape - - if self.displacement_max is None: - if im0_shape == im1_shape: - displacement_max = max(im0_shape) // 2 - else: - displacement_max = ( - max(im0_shape[0] - im1_shape[0], im0_shape[1] - im1_shape[1]) - // 2 - - 1 - ) - if displacement_max <= 0: - raise ValueError("displacement_max <= 0 : problem with images shapes") - - modes = ["valid", "same", "disp"] - if mode not in modes: - raise ValueError("mode should be in " + modes) - - self.mode = mode - self.ny0, self.nx0 = im0_shape - self.ny1, self.nx1 = im1_shape - self.displacement_max = displacement_max - if mode == "same": - self.ny, self.nx = im0_shape - if self.nx % 2 == 0: - ind0x = self.nx // 2 - 1 - else: - ind0x = self.nx // 2 - if self.ny % 2 == 0: - ind0y = self.ny // 2 - 1 - else: - ind0y = self.ny // 2 - - elif mode == "valid": - self.ny, self.nx = np.array(im0_shape) - np.array(im1_shape) + 1 - ind0x = self.nx // 2 - ind0y = self.ny // 2 - else: - self.ny = displacement_max * 2 + 1 - self.nx = self.ny - ind0x = displacement_max - ind0y = displacement_max - - im00 = theano.tensor.tensor4("im00", dtype="float32") - im11 = theano.tensor.tensor4("im11", dtype="float32") - modec = theano.compile.get_default_mode() - # modec = modec.including('conv_meta') - if mode == "same": - correl_theano = theano.tensor.nnet.conv2d( - im00, - im11, - image_shape=(1, 1, 2 * self.ny0 - 1, 2 * self.nx0 - 1), - filter_shape=(1, 1) + im1_shape, - border_mode="valid", - ) - elif mode == "valid": - correl_theano = theano.tensor.nnet.conv2d( - im00, - im11, - input_shape=(1, 1) + im0_shape, - filter_shape=(1, 1) + im1_shape, - border_mode="valid", - ) - else: - if (self.ny0 <= 2 * self.displacement_max + self.ny1) & ( - self.nx0 <= 2 * self.displacement_max + self.nx1 - ): - correl_theano = theano.tensor.nnet.conv2d( - im00, - im11, - input_shape=( - 1, - 1, - 2 * displacement_max + self.ny1, - 2 * displacement_max + self.nx1, - ), - filter_shape=(1, 1) + im1_shape, - border_mode="valid", - ) - elif (self.ny0 > 2 * self.displacement_max + self.ny1) & ( - self.nx0 > 2 * self.displacement_max + self.nx1 - ): - correl_theano = theano.tensor.nnet.conv2d( - im00, - im11, - image_shape=(1, 1) + im0_shape, - filter_shape=(1, 1) + im1_shape, - border_mode="valid", - ) - else: - assert False, "Bad value for self.mode" - - self.correlf = theano.function( - inputs=[im00, im11], outputs=[correl_theano], mode=modec - ) - - self.iy0, self.ix0 = (ind0y, ind0x) - - def __call__(self, im0, im1): - """Compute the correlation from images.""" - norm = np.sqrt(np.sum(im1**2) * np.sum(im0**2)) - im1 = np.rot90(im1, 2) - im1 = im1.reshape(1, 1, self.ny1, self.nx1) - if self.mode == "valid": - im0 = im0.reshape(1, 1, self.ny0, self.nx0) - elif self.mode == "same": - im0b = im1.min() * np.ones( - (2 * self.ny - 1, 2 * self.nx - 1), dtype=np.float32 - ) - im0b[ - self.ny // 2 - 1 : self.ny + self.ny // 2 - 1, - self.nx // 2 - 1 : self.nx + self.nx // 2 - 1, - ] = im0 - # Correlation with periodic condition (==FFT version) : - # im0 = np.tile(im0, (3, 3)) - # im0 = im0[self.nx//2+1:2*self.nx+self.nx//2, - # self.ny//2+1:2*self.ny+self.ny//2] - im0 = im0b.reshape(1, 1, 2 * self.ny - 1, 2 * self.nx - 1) - elif self.mode == "disp": - if (self.ny0 < 2 * self.displacement_max + self.ny1) & ( - self.nx0 < 2 * self.displacement_max + self.nx1 - ): - im0b = np.zeros( - ( - 2 * self.displacement_max + self.ny1, - 2 * self.displacement_max + self.nx1, - ), - dtype=np.float32, - ) - i00 = (2 * self.displacement_max + self.nx1 - self.nx0) // 2 - j00 = (2 * self.displacement_max + self.ny1 - self.ny0) // 2 - im0b[j00 : self.ny0 + j00, i00 : self.nx0 + i00] = im0 - im0 = im0b.reshape( - 1, - 1, - 2 * self.displacement_max + self.ny1, - 2 * self.displacement_max + self.nx1, - ) - elif (self.ny0 > 2 * self.displacement_max + self.ny1) & ( - self.nx0 > 2 * self.displacement_max + self.nx1 - ): - im0 = im0.reshape(1, 1, self.ny0, self.nx0) - else: - assert False, "Bad value for self.mode" - - correl = self.correlf(im0, im1) - correl = np.asarray(correl) - if ( - (self.ny0 > 2 * self.displacement_max + self.ny1) - & (self.nx0 > 2 * self.displacement_max + self.nx1) - & (self.mode == "disp") - ): - i00 = (self.nx0 - self.nx1 + 1) // 2 - self.displacement_max - j00 = (self.ny0 - self.ny1 + 1) // 2 - self.displacement_max - correl = correl[ - 0, - 0, - 0, - j00 : j00 + 2 * self.displacement_max + 1, - i00 : i00 + 2 * self.displacement_max + 1, - ] - else: - correl = correl.reshape(self.ny, self.nx) - return correl, norm @@ -624,7 +510,7 @@ class CorrelFFTBase(CorrelBase): _tag = "fft.base" - def _init2(self): + def _finalize_init(self): if self.displacement_max is not None: where_large_displacement = np.zeros(self.im0_shape, dtype=bool) @@ -636,6 +522,25 @@ def _init2(self): self.where_large_displacement = where_large_displacement + n0, n1 = where_large_displacement.shape + for i0_start in range(n0): + if not all(where_large_displacement[i0_start, :]): + break + for i1_start in range(n1): + if not all(where_large_displacement[:, i1_start]): + break + for i0_stop in range(n0 - 1, -1, -1): + if not all(where_large_displacement[i0_stop, :]): + break + i0_stop += 1 + for i1_stop in range(n1 - 1, -1, -1): + if not all(where_large_displacement[:, i1_stop]): + break + i1_stop += 1 + + self.start_stop_for_search0 = (i0_start, i0_stop) + self.start_stop_for_search1 = (i1_start, i1_stop) + self._check_im_shape(self.im0_shape, self.im1_shape) def _check_im_shape(self, im0_shape, im1_shape): @@ -658,6 +563,74 @@ def compute_displacements_from_correl(self, correl, norm=1.0): return super().compute_displacements_from_correl(correl, norm=norm) +@boost +def _norm_images(im0: A2df32, im1: A2df32): + """Less accurate than the numpy equivalent but much faster + + Should return something close to: + + np.sqrt(np.sum(im1**2) * np.sum(im0**2)) + + """ + im0 = im0.ravel() + im1 = im1.ravel() + tmp0 = np.float64(im0[0] ** 2) + tmp1 = np.float64(im1[0] ** 2) + if im0.size != im1.size: + for idx in range(1, im0.size): + tmp0 += im0[idx] ** 2 + for idx in range(1, im1.size): + tmp1 += im1[idx] ** 2 + else: + for idx in range(1, im0.size): + tmp0 += im0[idx] ** 2 + tmp1 += im1[idx] ** 2 + return np.sqrt(tmp0 * tmp1) + + +@boost +def _like_fftshift(arr: A2dC): + """Pythran optimized function doing the equivalent of + + np.ascontiguousarray(np.fft.fftshift(arr[::-1, ::-1])) + + """ + n0, n1 = arr.shape + + arr = np.ascontiguousarray(arr[::-1, ::-1]) + tmp = np.empty_like(arr) + + if n1 % 2 == 0: + for i0 in range(n0): + for i1 in range(n1 // 2): + tmp[i0, n1 // 2 + i1] = arr[i0, i1] + tmp[i0, i1] = arr[i0, n1 // 2 + i1] + else: + for i0 in range(n0): + for i1 in range(n1 // 2 + 1): + tmp[i0, n1 // 2 + i1] = arr[i0, i1] + + for i1 in range(n1 // 2): + tmp[i0, i1] = arr[i0, n1 // 2 + 1 + i1] + + arr_1d_view = arr.ravel() + tmp_1d_view = tmp.ravel() + + if n0 % 2 == 0: + n_half = (n0 // 2) * n1 + for idx in range(n_half): + arr_1d_view[idx + n_half] = tmp_1d_view[idx] + arr_1d_view[idx] = tmp_1d_view[idx + n_half] + else: + n_half_a = (n0 // 2 + 1) * n1 + n_half_b = (n0 // 2) * n1 + for idx in range(n_half_a): + arr_1d_view[idx + n_half_b] = tmp_1d_view[idx] + for idx in range(n_half_b): + arr_1d_view[idx] = tmp_1d_view[idx + n_half_a] + return arr + + class CorrelFFTNumpy(CorrelFFTBase): """Correlations using numpy.fft.""" @@ -665,70 +638,52 @@ class CorrelFFTNumpy(CorrelFFTBase): def __call__(self, im0, im1): """Compute the correlation from images.""" - norm = np.sqrt(np.sum(im1**2) * np.sum(im0**2)) - corr = ifft2(fft2(im0).conj() * fft2(im1)).real - correl = np.fft.fftshift(corr[::-1, ::-1]) - return correl, norm + norm = _norm_images(im0, im1) + correl = ifft2(fft2(im0).conj() * fft2(im1)).real + return _like_fftshift(np.ascontiguousarray(correl)), norm -class CorrelFFTW(CorrelFFTBase): - """Correlations using fluidimage.fft.FFTW2DReal2Complex""" +class CorrelFFTWithOperBase(CorrelFFTBase): - FFTClass = FFTW2DReal2Complex - _tag = "fftw" + FFTClass: object - def _init2(self): - CorrelFFTBase._init2(self) + def _finalize_init(self): + CorrelFFTBase._finalize_init(self) n0, n1 = self.im0_shape - self.op = self.FFTClass(n1, n0) + self.oper = self.FFTClass(n1, n0) def __call__(self, im0, im1): - """Compute the correlation from images.""" - norm = np.sqrt(np.sum(im1**2) * np.sum(im0**2)) * im0.size - op = self.op - corr = op.ifft(op.fft(im0).conj() * op.fft(im1)) - correl = np.fft.fftshift(corr[::-1, ::-1]) - return correl, norm + """Compute the correlation from images. + Warning: important for perf, so use Pythran -class CorrelCuFFT(CorrelFFTBase): - _tag = "cufft" - """Correlations using fluidimage.fft.CUFFT2DReal2Complex""" - FFTClass = CUFFT2DReal2Complex + """ + norm = _norm_images(im0, im1) * im0.size + oper = self.oper + correl = oper.ifft(oper.fft(im0).conj() * oper.fft(im1)) + return _like_fftshift(correl), norm - def _init2(self): - CorrelFFTBase._init2(self) - n0, n1 = self.im0_shape - self.op = self.FFTClass(n1, n0) - def __call__(self, im0, im1): - """Compute the correlation from images.""" - norm = np.sqrt(np.sum(im1**2) * np.sum(im0**2)) * im0.size - op = self.op - corr = op.ifft(op.fft(im0).conj() * op.fft(im1)).real * im0.size**2 - correl = np.fft.fftshift(corr[::-1, ::-1]) - return correl, norm +class CorrelFFTW(CorrelFFTWithOperBase): + """Correlations using fluidimage.fft.FFTW2DReal2Complex""" + + FFTClass = FFTW2DReal2Complex + _tag = "fftw" + + +class CorrelCuFFT(CorrelFFTWithOperBase): + """Correlations using fluidimage.fft.CUFFT2DReal2Complex""" + + _tag = "cufft" + FFTClass = CUFFT2DReal2Complex -class CorrelSKCuFFT(CorrelFFTBase): +class CorrelSKCuFFT(CorrelFFTWithOperBase): """Correlations using fluidimage.fft.FFTW2DReal2Complex""" FFTClass = SKCUFFT2DReal2Complex _tag = "skcufft" - def _init2(self): - CorrelFFTBase._init2(self) - n0, n1 = self.im0_shape - self.op = self.FFTClass(n1, n0) - - def __call__(self, im0, im1): - """Compute the correlation from images.""" - norm = np.sqrt(np.sum(im1**2) * np.sum(im0**2)) * im0.size - op = self.op - corr = op.ifft(op.fft(im0).conj() * op.fft(im1)) - correl = np.fft.fftshift(corr[::-1, ::-1]) - return correl, norm - correlation_classes = { v._tag: v diff --git a/src/fluidimage/calcul/smooth_clean.py b/src/fluidimage/calcul/smooth_clean.py index a4b39863..9a1ef0e8 100644 --- a/src/fluidimage/calcul/smooth_clean.py +++ b/src/fluidimage/calcul/smooth_clean.py @@ -23,6 +23,11 @@ def _smooth(a, for_norm): def smooth_clean(xs, ys, deltaxs, deltays, iyvecs, ixvecs, threshold): + """Smooth and clean the displacements + + Warning: important for perf (~25% for PIV) + + """ nx = len(ixvecs) ny = len(iyvecs) diff --git a/src/fluidimage/calcul/test_correl.py b/src/fluidimage/calcul/test_correl.py index ecaff504..3d368ada 100644 --- a/src/fluidimage/calcul/test_correl.py +++ b/src/fluidimage/calcul/test_correl.py @@ -8,7 +8,7 @@ CorrelPyCuda, CorrelPythran, CorrelScipySignal, - CorrelTheano, + _like_fftshift, correlation_classes, ) from fluidimage.synthetic import make_synthetic_images @@ -18,8 +18,7 @@ classes = {k.replace(".", "_"): v for k, v in correlation_classes.items()} classes2 = { - "sig": CorrelScipySignal, - "theano": CorrelTheano, + "signal": CorrelScipySignal, "pycuda": CorrelPyCuda, "pythran": CorrelPythran, } @@ -42,12 +41,6 @@ except ImportError: classes.pop("skcufft") -try: - import theano -except ImportError: - classes.pop("theano") - classes2.pop("theano") - class TestCorrel(unittest.TestCase): @classmethod @@ -246,7 +239,23 @@ def _test2(self, cls=cls, k=k): np.allclose(self.displacements, displacement_computed, atol=0.8) ) - exec("TestCorrel2.test_correl_images_diff_sizes" + k + " = _test2") + exec("TestCorrel2.test_correl_images_diff_sizes_" + k + " = _test2") + + +def _test_like_fftshift(n0, n1): + correl = np.reshape(np.arange(n0 * n1, dtype=np.float32), (n0, n1)) + assert np.allclose( + _like_fftshift(correl), + np.ascontiguousarray(np.fft.fftshift(correl[::-1, ::-1])), + ) + + +def test_like_fftshift(): + _test_like_fftshift(24, 32) + _test_like_fftshift(21, 32) + _test_like_fftshift(12, 13) + _test_like_fftshift(7, 9) + if __name__ == "__main__": unittest.main() diff --git a/src/fluidimage/works/piv/singlepass.py b/src/fluidimage/works/piv/singlepass.py index 6dd56c6f..45a02884 100644 --- a/src/fluidimage/works/piv/singlepass.py +++ b/src/fluidimage/works/piv/singlepass.py @@ -401,7 +401,10 @@ def _init_crop(self): self._stop_for_crop1 = tuple(_stop_for_crop1) def _crop_im0(self, ixvec, iyvec, im): - """Crop image 0.""" + """Crop image 0. + + Warning: important for perf (~12% for PIV with _crop_im1) + """ subim = im[ iyvec - self._start_for_crop0[0] : iyvec + self._stop_for_crop0[0], ixvec - self._start_for_crop0[1] : ixvec + self._stop_for_crop0[1], @@ -536,7 +539,7 @@ def apply_interp(self, piv_results, last=False): else: print("no erratic vector found.") - print(" Statistics on TPS subdomains:") + print(f" Statistics on the {tps.nb_subdom} TPS subdomains:") for key, value in summary.items(): fmt = ".3f" if isinstance(value[0], float) else "d" print(