OpenPIV 0.25.3__tar.gz → 0.26.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. {openpiv-0.25.3 → openpiv-0.26.0}/PKG-INFO +38 -20
  2. {openpiv-0.25.3 → openpiv-0.26.0}/README.md +24 -7
  3. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/PIV_3D_plotting.py +16 -10
  4. openpiv-0.26.0/openpiv/__init__.py +10 -0
  5. openpiv-0.26.0/openpiv/data/test1/test_data.vec +71 -0
  6. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/conf.py +1 -1
  7. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/index.rst +1 -0
  8. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/requirements.txt +0 -1
  9. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/developers.rst +61 -0
  10. openpiv-0.26.0/openpiv/docs/src/fft_correlation_backends.rst +162 -0
  11. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/installation_instruction.rst +27 -8
  12. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/filters.py +20 -14
  13. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/lib.py +38 -63
  14. openpiv-0.26.0/openpiv/piv.py +288 -0
  15. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/preprocess.py +150 -138
  16. openpiv-0.26.0/openpiv/preprocess.py,cover +579 -0
  17. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/pyprocess.py +151 -73
  18. openpiv-0.26.0/openpiv/settings.py +167 -0
  19. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/smoothn.py +183 -219
  20. openpiv-0.26.0/openpiv/test/OpenPIV_results_16_test1/field_A0000.png +0 -0
  21. openpiv-0.26.0/openpiv/test/conftest.py +57 -0
  22. openpiv-0.26.0/openpiv/test/test_PIV_3D_plotting.py +236 -0
  23. openpiv-0.26.0/openpiv/test/test_filters.py +190 -0
  24. openpiv-0.26.0/openpiv/test/test_lib.py +224 -0
  25. openpiv-0.26.0/openpiv/test/test_package_metadata.py +7 -0
  26. openpiv-0.26.0/openpiv/test/test_performance.py +161 -0
  27. openpiv-0.26.0/openpiv/test/test_piv.py +440 -0
  28. openpiv-0.26.0/openpiv/test/test_preprocess.py +491 -0
  29. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_process.py +90 -23
  30. openpiv-0.26.0/openpiv/test/test_pyprocess.py +438 -0
  31. openpiv-0.26.0/openpiv/test/test_rust_backend.py +174 -0
  32. openpiv-0.26.0/openpiv/test/test_rust_subpixel.py +101 -0
  33. openpiv-0.26.0/openpiv/test/test_rust_windef.py +121 -0
  34. openpiv-0.26.0/openpiv/test/test_smoothn.py +611 -0
  35. openpiv-0.26.0/openpiv/test/test_tools.py +233 -0
  36. openpiv-0.26.0/openpiv/test/test_tools_background.py +199 -0
  37. openpiv-0.26.0/openpiv/test/test_tools_basic_utils.py +131 -0
  38. openpiv-0.26.0/openpiv/test/test_tools_image_processing.py +229 -0
  39. openpiv-0.26.0/openpiv/test/test_tools_multiprocessing.py +197 -0
  40. openpiv-0.26.0/openpiv/test/test_tools_vector_field.py +230 -0
  41. openpiv-0.26.0/openpiv/test/test_validation.py +421 -0
  42. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_windef.py +30 -22
  43. openpiv-0.26.0/openpiv/test/test_windef_coverage.py +215 -0
  44. openpiv-0.26.0/openpiv/test/test_windef_detailed.py +808 -0
  45. openpiv-0.26.0/openpiv/test/test_windef_final.py +76 -0
  46. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tools.py +140 -119
  47. openpiv-0.26.0/openpiv/tutorials/Example for overlap setting change.ipynb +410 -0
  48. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tutorials/masking_tutorial.py +1 -1
  49. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tutorials/tutorial1.py +1 -1
  50. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tutorials/tutorial2.py +1 -1
  51. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tutorials/windef_tutorial.py +1 -1
  52. openpiv-0.26.0/openpiv/validation.py +549 -0
  53. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/windef.py +242 -348
  54. {openpiv-0.25.3 → openpiv-0.26.0}/pyproject.toml +17 -14
  55. openpiv-0.25.3/openpiv/__init__.py +0 -4
  56. openpiv-0.25.3/openpiv/data/test1/test_data.vec +0 -71
  57. openpiv-0.25.3/openpiv/piv.py +0 -117
  58. openpiv-0.25.3/openpiv/test/test_filters.py +0 -83
  59. openpiv-0.25.3/openpiv/test/test_preprocess.py +0 -46
  60. openpiv-0.25.3/openpiv/test/test_tools.py +0 -78
  61. openpiv-0.25.3/openpiv/test/test_validation.py +0 -155
  62. openpiv-0.25.3/openpiv/validation.py +0 -316
  63. {openpiv-0.25.3 → openpiv-0.26.0}/LICENSE.txt +0 -0
  64. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test1/exp1_001_a.bmp +0 -0
  65. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test1/exp1_001_b.bmp +0 -0
  66. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_00.tif +0 -0
  67. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_01.tif +0 -0
  68. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_10.tif +0 -0
  69. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_11.tif +0 -0
  70. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_20.tif +0 -0
  71. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_21.tif +0 -0
  72. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_30.tif +0 -0
  73. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_31.tif +0 -0
  74. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_40.tif +0 -0
  75. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_41.tif +0 -0
  76. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_50.tif +0 -0
  77. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_51.tif +0 -0
  78. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test3/pair_4_frame_0.jpg +0 -0
  79. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test3/pair_4_frame_1.jpg +0 -0
  80. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test4/Camera1-0101.tif +0 -0
  81. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test4/Camera1-0102.tif +0 -0
  82. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_10_A.tif +0 -0
  83. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_10_B.tif +0 -0
  84. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_1_A.tif +0 -0
  85. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_1_B.tif +0 -0
  86. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_2_A.tif +0 -0
  87. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_2_B.tif +0 -0
  88. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_3_A.tif +0 -0
  89. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_3_B.tif +0 -0
  90. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_4_A.tif +0 -0
  91. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_4_B.tif +0 -0
  92. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_5_A.tif +0 -0
  93. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_5_B.tif +0 -0
  94. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_6_A.tif +0 -0
  95. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_6_B.tif +0 -0
  96. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_7_A.tif +0 -0
  97. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_7_B.tif +0 -0
  98. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_8_A.tif +0 -0
  99. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_8_B.tif +0 -0
  100. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_9_A.tif +0 -0
  101. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_9_B.tif +0 -0
  102. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0001_A.tif +0 -0
  103. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0002_A.tif +0 -0
  104. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0003_A.tif +0 -0
  105. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0004_A.tif +0 -0
  106. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0005_A.tif +0 -0
  107. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1001_B.tif +0 -0
  108. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1002_B.tif +0 -0
  109. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1003_B.tif +0 -0
  110. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1004_B.tif +0 -0
  111. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1005_B.tif +0 -0
  112. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/Makefile +0 -0
  113. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/OpenPIV.pdf +0 -0
  114. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/OpenPIV_advanced_processing_parameters.pdf +0 -0
  115. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/B005_1.tif +0 -0
  116. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/B005_2.tif +0 -0
  117. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/image1.png +0 -0
  118. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/image2.png +0 -0
  119. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/image3.png +0 -0
  120. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/image4.png +0 -0
  121. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/make.bat +0 -0
  122. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/api_reference.rst +0 -0
  123. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/faq.rst +0 -0
  124. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.filters._gaussian_kernel.rst +0 -0
  125. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.filters.gaussian.rst +0 -0
  126. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.filters.replace_outliers.rst +0 -0
  127. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.lib.replace_nans.rst +0 -0
  128. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.lib.sincinterp.rst +0 -0
  129. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.preprocess.dynamic_masking.rst +0 -0
  130. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.CorrelationFunction.rst +0 -0
  131. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.correlate_windows.rst +0 -0
  132. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.extended_search_area_piv.rst +0 -0
  133. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.get_coordinates.rst +0 -0
  134. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.get_field_shape.rst +0 -0
  135. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.normalize_intensity.rst +0 -0
  136. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.correlate_windows.rst +0 -0
  137. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.find_first_peak.rst +0 -0
  138. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.find_second_peak.rst +0 -0
  139. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.find_subpixel_peak_position.rst +0 -0
  140. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.get_coordinates.rst +0 -0
  141. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.get_field_shape.rst +0 -0
  142. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.moving_window_array.rst +0 -0
  143. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.normalize_intensity.rst +0 -0
  144. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.piv.rst +0 -0
  145. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.scaling.uniform.rst +0 -0
  146. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.Multiprocesser.rst +0 -0
  147. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.display.rst +0 -0
  148. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.display_vector_field.rst +0 -0
  149. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.imread.rst +0 -0
  150. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.save.rst +0 -0
  151. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.validation.global_std.rst +0 -0
  152. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.validation.global_val.rst +0 -0
  153. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.validation.local_median_val.rst +0 -0
  154. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.validation.sig2noise_val.rst +0 -0
  155. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/gui_doc.rst +0 -0
  156. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/introduction.rst +0 -0
  157. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/masking.ipynb +0 -0
  158. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/modules.rst +0 -0
  159. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/openpiv.rst +0 -0
  160. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/openpiv_pivuq.ipynb +0 -0
  161. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/piv_basics.ipynb +0 -0
  162. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/tutorial1.ipynb +0 -0
  163. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/windef.ipynb +0 -0
  164. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/phase_separation.py +0 -0
  165. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/pyprocess3D.py +0 -0
  166. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/scaling.py +0 -0
  167. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/__init__.py +0 -0
  168. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/extended_search_area_vectorized.ipynb +0 -0
  169. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/moon.png +0 -0
  170. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_filters.ipynb +0 -0
  171. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_find_peaks.ipynb +0 -0
  172. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_process.ipynb +0 -0
  173. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_pyprocess3D.py +0 -0
  174. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_scaling.py +0 -0
  175. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_tools.ipynb +0 -0
  176. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_tools.png +0 -0
  177. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_validation.ipynb +0 -0
  178. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_vectorized_extended_search.py +0 -0
  179. {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_windef.ipynb +0 -0
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: OpenPIV
3
- Version: 0.25.3
3
+ Version: 0.26.0
4
4
  Summary: OpenPIV consists in a Python and Cython modules for scripting and executing the analysis of a set of PIV image pairs. In addition, a Qt and Tk graphical user interfaces are in development, to ease the use for those users who don't have python skills.
5
5
  License: GPLv3
6
+ License-File: LICENSE.txt
6
7
  Author: Alex Liberzon
7
8
  Requires-Python: >=3.10
8
9
  Classifier: Development Status :: 4 - Beta
@@ -18,27 +19,26 @@ Classifier: Programming Language :: Python :: 3
18
19
  Classifier: Programming Language :: Python :: 3.10
19
20
  Classifier: Programming Language :: Python :: 3.11
20
21
  Classifier: Programming Language :: Python :: 3.12
21
- Classifier: Programming Language :: Python :: 3.7
22
- Classifier: Programming Language :: Python :: 3.8
23
- Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
24
  Classifier: Topic :: Scientific/Engineering
25
- Requires-Dist: imageio (>=2.22.4,<3.0.0)
26
- Requires-Dist: importlib_resources (==5.12.0)
27
- Requires-Dist: matplotlib (>=3,<4)
28
- Requires-Dist: natsort (>=8.4.0,<9.0.0)
29
- Requires-Dist: numpy (>=1.21.6,<2.0.0)
30
- Requires-Dist: scikit-image (>=0.23)
31
- Requires-Dist: scipy (>=1.7.3,<2.0.0)
32
- Requires-Dist: tqdm (>=4.66.1,<5.0.0)
25
+ Provides-Extra: rust
26
+ Requires-Dist: imageio (>=2.35.0)
27
+ Requires-Dist: matplotlib (>=3.8.0)
28
+ Requires-Dist: natsort (>=8.4.0)
29
+ Requires-Dist: numpy (>=2.0.0)
30
+ Requires-Dist: openpiv-rust (>=0.1.0) ; extra == "rust"
31
+ Requires-Dist: scikit-image (>=0.23.0)
32
+ Requires-Dist: scipy (>=1.11.0)
33
+ Requires-Dist: tqdm (>=4.66.0)
33
34
  Project-URL: Documentation, http://openpiv.readthedocs.org
34
35
  Project-URL: Repository, https://pypi.python.org/pypi/OpenPIV
35
36
  Description-Content-Type: text/markdown
36
37
 
37
38
  # OpenPIV
38
- ![Build and upload to PyPI](https://github.com/OpenPIV/openpiv-python/workflows/Build%20and%20upload%20to%20PyPI/badge.svg)
39
- [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4409178.svg)](https://doi.org/10.5281/zenodo.4409178)
39
+ [![Python package](https://github.com/OpenPIV/openpiv-python/actions/workflows/testing.yml/badge.svg)](https://github.com/OpenPIV/openpiv-python/actions/workflows/testing.yml)
40
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18304582.svg)](https://doi.org/10.5281/zenodo.18304582)
40
41
  ![PyPI](https://img.shields.io/pypi/v/openpiv)
41
- ![Anaconda](https://anaconda.org/openpiv/openpiv/badges/version.svg)
42
42
 
43
43
 
44
44
  OpenPIV consists in a Python and Cython modules for scripting and executing the analysis of
@@ -61,18 +61,36 @@ Click the link - thanks to BinderHub, Jupyter and Conda you can now get it in yo
61
61
 
62
62
  ## Installing
63
63
 
64
+ ### Recommended: Using uv (fastest)
65
+
66
+ [uv](https://github.com/astral-sh/uv) is a fast Python package installer and resolver written in Rust:
67
+
68
+ pip install uv
69
+ uv pip install openpiv
70
+
71
+ ### Using pip (standard)
72
+
64
73
  Use PyPI: <https://pypi.python.org/pypi/OpenPIV>:
65
74
 
66
75
  pip install openpiv
67
76
 
77
+ ### Or [Poetry](https://python-poetry.org/)
68
78
 
69
- ## Or `conda`
79
+ poetry add openpiv
70
80
 
71
- conda install -c alexlib openpiv
81
+ ### Note on Conda/Anaconda
72
82
 
73
- ## Or [Poetry](https://python-poetry.org/)
83
+ ⚠️ **Conda packages are no longer actively maintained.** The conda-forge package may be outdated.
74
84
 
75
- poetry add openpiv
85
+ If you previously installed OpenPIV via conda, you can migrate to pip or uv:
86
+
87
+ # Remove the conda package
88
+ conda remove openpiv
89
+
90
+ # Install with pip or uv
91
+ pip install openpiv
92
+ # or
93
+ uv pip install openpiv
76
94
 
77
95
 
78
96
  ### To build from source
@@ -1,8 +1,7 @@
1
1
  # OpenPIV
2
- ![Build and upload to PyPI](https://github.com/OpenPIV/openpiv-python/workflows/Build%20and%20upload%20to%20PyPI/badge.svg)
3
- [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4409178.svg)](https://doi.org/10.5281/zenodo.4409178)
2
+ [![Python package](https://github.com/OpenPIV/openpiv-python/actions/workflows/testing.yml/badge.svg)](https://github.com/OpenPIV/openpiv-python/actions/workflows/testing.yml)
3
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18304582.svg)](https://doi.org/10.5281/zenodo.18304582)
4
4
  ![PyPI](https://img.shields.io/pypi/v/openpiv)
5
- ![Anaconda](https://anaconda.org/openpiv/openpiv/badges/version.svg)
6
5
 
7
6
 
8
7
  OpenPIV consists in a Python and Cython modules for scripting and executing the analysis of
@@ -25,18 +24,36 @@ Click the link - thanks to BinderHub, Jupyter and Conda you can now get it in yo
25
24
 
26
25
  ## Installing
27
26
 
27
+ ### Recommended: Using uv (fastest)
28
+
29
+ [uv](https://github.com/astral-sh/uv) is a fast Python package installer and resolver written in Rust:
30
+
31
+ pip install uv
32
+ uv pip install openpiv
33
+
34
+ ### Using pip (standard)
35
+
28
36
  Use PyPI: <https://pypi.python.org/pypi/OpenPIV>:
29
37
 
30
38
  pip install openpiv
31
39
 
40
+ ### Or [Poetry](https://python-poetry.org/)
32
41
 
33
- ## Or `conda`
42
+ poetry add openpiv
34
43
 
35
- conda install -c alexlib openpiv
44
+ ### Note on Conda/Anaconda
36
45
 
37
- ## Or [Poetry](https://python-poetry.org/)
46
+ ⚠️ **Conda packages are no longer actively maintained.** The conda-forge package may be outdated.
38
47
 
39
- poetry add openpiv
48
+ If you previously installed OpenPIV via conda, you can migrate to pip or uv:
49
+
50
+ # Remove the conda package
51
+ conda remove openpiv
52
+
53
+ # Install with pip or uv
54
+ pip install openpiv
55
+ # or
56
+ uv pip install openpiv
40
57
 
41
58
 
42
59
  ### To build from source
@@ -71,7 +71,7 @@ def scatter_3D(a, cmap="jet", sca_args=None, control="color", size=60):
71
71
  sm = matplotlib.cm.ScalarMappable(cmap=cmap, norm=norm)
72
72
  sm.set_array([])
73
73
  # different option
74
- cm = matplotlib.cm.get_cmap(cmap)
74
+ cm = matplotlib.colormaps.get_cmap(cmap)
75
75
  colors = cm(norm(a)).reshape(a.shape[0] * a.shape[1] * a.shape[2], 4) #
76
76
  # plotting
77
77
  nan_filter = ~np.isnan(a.flatten())
@@ -83,7 +83,7 @@ def scatter_3D(a, cmap="jet", sca_args=None, control="color", size=60):
83
83
  s=size,
84
84
  **scatter_args
85
85
  )
86
- plt.colorbar(sm)
86
+ fig.colorbar(sm, ax=ax)
87
87
 
88
88
  if control == "alpha":
89
89
  # untested #
@@ -92,8 +92,12 @@ def scatter_3D(a, cmap="jet", sca_args=None, control="color", size=60):
92
92
  plt.show()
93
93
 
94
94
  if control == "size":
95
- sizes = (a - a.min()) * size / a.ptp()
96
- ax.scatter(x, y, z, a, s=sizes, **scatter_args)
95
+ value_range = np.ptp(a)
96
+ if value_range == 0:
97
+ sizes = np.full(a.shape, size, dtype=float)
98
+ else:
99
+ sizes = (a - a.min()) * size / value_range
100
+ ax.scatter(x, y, z, c=a.flatten(), s=sizes.flatten(), **scatter_args)
97
101
  ax_scale = plt.axes([0.88, 0.1, 0.05, 0.7])
98
102
  # ax_scale.set_ylim((0.1,1.2))
99
103
  nm = 5
@@ -172,12 +176,13 @@ def plot_3D_alpha(data):
172
176
  z[:, :, 1::2] += 0.95
173
177
 
174
178
  fig = plt.figure()
175
- ax = fig.gca(projection="3d")
179
+ ax = fig.add_subplot(projection="3d")
176
180
  ax.voxels(x, y, z, fill, facecolors=col_exp, edgecolors=col_exp)
177
181
  ax.set_xlabel("x")
178
182
  ax.set_ylabel("y")
179
183
  ax.set_zlabel("z")
180
184
  plt.show()
185
+ return fig
181
186
 
182
187
 
183
188
  def quiver_3D(
@@ -332,11 +337,11 @@ def quiver_3D(
332
337
 
333
338
  # plotting
334
339
  fig = plt.figure()
335
- ax = fig.gca(projection="3d", rasterized=True)
340
+ ax = fig.add_subplot(projection="3d", rasterized=True)
336
341
  ax.quiver(
337
342
  xf, yf, zf, vf * scale, uf * scale, wf * scale, colors=colors, **quiver_args
338
343
  )
339
- plt.colorbar(sm)
344
+ fig.colorbar(sm, ax=ax)
340
345
 
341
346
  ax.set_xlim(ax_dims[0])
342
347
  ax.set_ylim(ax_dims[1])
@@ -348,8 +353,9 @@ def quiver_3D(
348
353
  ax.set_xlabel("x")
349
354
  ax.set_ylabel("y")
350
355
  ax.set_zlabel("z")
351
- ax.w_xaxis.set_pane_color((0.2, 0.2, 0.2, 1.0))
352
- ax.w_yaxis.set_pane_color((0.2, 0.2, 0.2, 1.0))
353
- ax.w_zaxis.set_pane_color((0.2, 0.2, 0.2, 1.0))
356
+ for axis in (ax.xaxis, ax.yaxis, ax.zaxis):
357
+ pane = getattr(axis, "pane", None)
358
+ if pane is not None:
359
+ pane.set_facecolor((0.2, 0.2, 0.2, 1.0))
354
360
 
355
361
  return fig
@@ -0,0 +1,10 @@
1
+ from importlib.metadata import version
2
+
3
+
4
+ __version__ = version("OpenPIV")
5
+
6
+
7
+ def test():
8
+ import pytest
9
+
10
+ pytest.main()
@@ -0,0 +1,71 @@
1
+ # x y u v flags mask
2
+ 4.0406e-01 3.3983e+00 -5.2592e-03 -5.9910e-02 0.0000e+00 0.0000e+00
3
+ 9.0137e-01 3.3983e+00 -9.6075e-04 -6.4036e-02 0.0000e+00 0.0000e+00
4
+ 1.3987e+00 3.3983e+00 -3.4784e-06 -5.9356e-02 1.0000e+00 0.0000e+00
5
+ 1.8960e+00 3.3983e+00 4.9192e-03 -6.1691e-02 0.0000e+00 0.0000e+00
6
+ 2.3933e+00 3.3983e+00 -9.2873e-04 -5.6505e-02 1.0000e+00 0.0000e+00
7
+ 2.8906e+00 3.3983e+00 -5.9565e-03 -5.0575e-02 0.0000e+00 0.0000e+00
8
+ 3.3879e+00 3.3983e+00 2.1094e-04 -5.7925e-02 0.0000e+00 0.0000e+00
9
+ 3.8852e+00 3.3983e+00 -2.7240e-03 -6.0472e-02 0.0000e+00 0.0000e+00
10
+ 4.3825e+00 3.3983e+00 -3.7283e-03 -5.8267e-02 1.0000e+00 0.0000e+00
11
+ 4.8798e+00 3.3983e+00 2.4173e-03 -6.4150e-02 0.0000e+00 0.0000e+00
12
+ 4.0406e-01 2.9010e+00 -1.8216e-03 -5.4789e-02 0.0000e+00 0.0000e+00
13
+ 9.0137e-01 2.9010e+00 -1.8865e-04 -5.9088e-02 1.0000e+00 0.0000e+00
14
+ 1.3987e+00 2.9010e+00 5.2738e-03 -5.5928e-02 0.0000e+00 0.0000e+00
15
+ 1.8960e+00 2.9010e+00 8.0944e-03 -5.4726e-02 0.0000e+00 0.0000e+00
16
+ 2.3933e+00 2.9010e+00 -2.6124e-03 -5.5983e-02 0.0000e+00 0.0000e+00
17
+ 2.8906e+00 2.9010e+00 -4.3661e-03 -5.1347e-02 0.0000e+00 0.0000e+00
18
+ 3.3879e+00 2.9010e+00 -3.3565e-03 -5.6682e-02 0.0000e+00 0.0000e+00
19
+ 3.8852e+00 2.9010e+00 -3.5475e-03 -6.1713e-02 0.0000e+00 0.0000e+00
20
+ 4.3825e+00 2.9010e+00 -3.8556e-03 -5.6242e-02 1.0000e+00 0.0000e+00
21
+ 4.8798e+00 2.9010e+00 -6.4612e-03 -6.4347e-02 0.0000e+00 0.0000e+00
22
+ 4.0406e-01 2.4036e+00 8.3705e-04 -5.9559e-02 0.0000e+00 0.0000e+00
23
+ 9.0137e-01 2.4036e+00 1.4459e-03 -6.0987e-02 0.0000e+00 0.0000e+00
24
+ 1.3987e+00 2.4036e+00 -3.0819e-04 -5.8402e-02 1.0000e+00 0.0000e+00
25
+ 1.8960e+00 2.4036e+00 -7.8548e-03 -6.5643e-02 0.0000e+00 0.0000e+00
26
+ 2.3933e+00 2.4036e+00 -6.7928e-04 -6.3742e-02 0.0000e+00 0.0000e+00
27
+ 2.8906e+00 2.4036e+00 1.2726e-04 -4.9550e-02 0.0000e+00 0.0000e+00
28
+ 3.3879e+00 2.4036e+00 -6.4997e-03 -4.9535e-02 0.0000e+00 0.0000e+00
29
+ 3.8852e+00 2.4036e+00 -5.7793e-03 -5.4359e-02 0.0000e+00 0.0000e+00
30
+ 4.3825e+00 2.4036e+00 -7.7358e-03 -5.8984e-02 0.0000e+00 0.0000e+00
31
+ 4.8798e+00 2.4036e+00 -3.6829e-03 -5.6436e-02 1.0000e+00 0.0000e+00
32
+ 4.0406e-01 1.9063e+00 -8.4074e-03 -5.9327e-02 0.0000e+00 0.0000e+00
33
+ 9.0137e-01 1.9063e+00 2.5401e-03 -5.4095e-02 0.0000e+00 0.0000e+00
34
+ 1.3987e+00 1.9063e+00 -3.3883e-03 -5.7585e-02 0.0000e+00 0.0000e+00
35
+ 1.8960e+00 1.9063e+00 1.9393e-03 -5.9907e-02 0.0000e+00 0.0000e+00
36
+ 2.3933e+00 1.9063e+00 1.4774e-03 -6.0280e-02 0.0000e+00 0.0000e+00
37
+ 2.8906e+00 1.9063e+00 -1.7161e-03 -5.2136e-02 0.0000e+00 0.0000e+00
38
+ 3.3879e+00 1.9063e+00 -9.2400e-03 -4.8946e-02 0.0000e+00 0.0000e+00
39
+ 3.8852e+00 1.9063e+00 -7.1067e-03 -4.8031e-02 0.0000e+00 0.0000e+00
40
+ 4.3825e+00 1.9063e+00 -1.2505e-03 -5.2882e-02 0.0000e+00 0.0000e+00
41
+ 4.8798e+00 1.9063e+00 6.4614e-04 -5.0812e-02 0.0000e+00 0.0000e+00
42
+ 4.0406e-01 1.4090e+00 2.2486e-03 -5.0550e-02 0.0000e+00 0.0000e+00
43
+ 9.0137e-01 1.4090e+00 -6.7877e-03 -5.4202e-02 0.0000e+00 0.0000e+00
44
+ 1.3987e+00 1.4090e+00 -2.5288e-03 -6.3126e-02 0.0000e+00 0.0000e+00
45
+ 1.8960e+00 1.4090e+00 -5.9178e-04 -5.8457e-02 0.0000e+00 0.0000e+00
46
+ 2.3933e+00 1.4090e+00 5.8459e-03 -5.2184e-02 0.0000e+00 0.0000e+00
47
+ 2.8906e+00 1.4090e+00 5.4790e-03 -4.3826e-02 0.0000e+00 0.0000e+00
48
+ 3.3879e+00 1.4090e+00 -7.8645e-04 -5.1268e-02 0.0000e+00 0.0000e+00
49
+ 3.8852e+00 1.4090e+00 -5.9201e-03 -5.2166e-02 0.0000e+00 0.0000e+00
50
+ 4.3825e+00 1.4090e+00 -4.3095e-03 -5.2602e-02 0.0000e+00 0.0000e+00
51
+ 4.8798e+00 1.4090e+00 -2.2094e-03 -5.4975e-02 0.0000e+00 0.0000e+00
52
+ 4.0406e-01 9.1173e-01 9.7924e-03 -5.5734e-02 0.0000e+00 0.0000e+00
53
+ 9.0137e-01 9.1173e-01 7.5636e-03 -5.2847e-02 0.0000e+00 0.0000e+00
54
+ 1.3987e+00 9.1173e-01 9.0445e-03 -4.9824e-02 0.0000e+00 0.0000e+00
55
+ 1.8960e+00 9.1173e-01 1.4482e-03 -5.3496e-02 0.0000e+00 0.0000e+00
56
+ 2.3933e+00 9.1173e-01 -3.3418e-03 -5.3215e-02 0.0000e+00 0.0000e+00
57
+ 2.8906e+00 9.1173e-01 -5.2835e-03 -4.9696e-02 0.0000e+00 0.0000e+00
58
+ 3.3879e+00 9.1173e-01 -5.0878e-03 -5.0559e-02 0.0000e+00 0.0000e+00
59
+ 3.8852e+00 9.1173e-01 -5.8373e-03 -5.1331e-02 0.0000e+00 0.0000e+00
60
+ 4.3825e+00 9.1173e-01 -9.5435e-04 -5.1071e-02 0.0000e+00 0.0000e+00
61
+ 4.8798e+00 9.1173e-01 -1.7836e-03 -5.0735e-02 0.0000e+00 0.0000e+00
62
+ 4.0406e-01 4.1442e-01 7.7151e-03 -5.9321e-02 0.0000e+00 0.0000e+00
63
+ 9.0137e-01 4.1442e-01 1.0424e-02 -5.2889e-02 0.0000e+00 0.0000e+00
64
+ 1.3987e+00 4.1442e-01 -1.2132e-03 -5.1367e-02 0.0000e+00 0.0000e+00
65
+ 1.8960e+00 4.1442e-01 1.8418e-03 -5.2428e-02 1.0000e+00 0.0000e+00
66
+ 2.3933e+00 4.1442e-01 3.0095e-03 -4.7837e-02 0.0000e+00 0.0000e+00
67
+ 2.8906e+00 4.1442e-01 2.7171e-03 -5.1027e-02 0.0000e+00 0.0000e+00
68
+ 3.3879e+00 4.1442e-01 -5.2003e-03 -5.0522e-02 0.0000e+00 0.0000e+00
69
+ 3.8852e+00 4.1442e-01 -4.6899e-03 -5.3185e-02 0.0000e+00 0.0000e+00
70
+ 4.3825e+00 4.1442e-01 -2.9401e-03 -5.4006e-02 0.0000e+00 0.0000e+00
71
+ 4.8798e+00 4.1442e-01 2.9101e-03 -5.1380e-02 0.0000e+00 0.0000e+00
@@ -64,7 +64,7 @@ copyright = '2014, OpenPIV team'
64
64
  # built documents.
65
65
  #
66
66
  # The short X.Y version.
67
- version = '0.24.4a'
67
+ version = '0.26.0'
68
68
  # The full version, including alpha/beta/rc tags.
69
69
  # release = '0.0.1a'
70
70
 
@@ -31,6 +31,7 @@ Contents:
31
31
  src/windef
32
32
  src/masking
33
33
  src/developers
34
+ src/fft_correlation_backends
34
35
  src/api_reference
35
36
  src/faq
36
37
 
@@ -1,4 +1,3 @@
1
- numpy>=1.9
2
1
  openpiv
3
2
  sphinx
4
3
  recommonmark
@@ -63,3 +63,64 @@ If you need to install cv2::
63
63
  conda install -c conda-forge opencv
64
64
 
65
65
 
66
+ Developing with or without Rust
67
+ --------------------------------
68
+
69
+ OpenPIV supports dual computational backends: **pure Python / SciPy** (default) and **compiled Rust** (via ``openpiv_rust``).
70
+
71
+ Option A: Developing in Pure Python (without Rust)
72
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73
+
74
+ You do not need a Rust compiler installed. Simply install OpenPIV in editable mode:
75
+
76
+ .. code-block:: bash
77
+
78
+ poetry install
79
+ # or: pip install -e .
80
+
81
+ All core functionality, single-pass, and multi-pass deformation will use the optimized
82
+ ``scipy.fft`` backend automatically.
83
+
84
+ To run the test suite:
85
+
86
+ .. code-block:: bash
87
+
88
+ pytest openpiv/test
89
+
90
+ Option B: Developing with the Rust Extension
91
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
92
+
93
+ If you have the Rust toolchain (``cargo``) installed:
94
+
95
+ 1. Install ``maturin``:
96
+
97
+ .. code-block:: bash
98
+
99
+ pip install maturin
100
+
101
+ 2. Compile and link ``openpiv_rust`` directly into your Python environment:
102
+
103
+ .. code-block:: bash
104
+
105
+ cd crates/openpiv_rust
106
+ maturin develop --release
107
+ cd ../..
108
+
109
+ 3. Verify that the Rust backend is active:
110
+
111
+ .. code-block:: bash
112
+
113
+ python -c "import openpiv_rust; print('Rust backend ready!')"
114
+
115
+ Running the Performance Benchmarks
116
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
117
+
118
+ OpenPIV includes a dedicated benchmark suite comparing cross-correlation, subpixel
119
+ peak finding, and end-to-end multi-pass Windef across backends:
120
+
121
+ .. code-block:: bash
122
+
123
+ python benchmarks/run_benchmarks.py
124
+
125
+
126
+
@@ -0,0 +1,162 @@
1
+ FFT Correlation Backends & Performance Study
2
+ ============================================
3
+
4
+ This document details the comparative study of 2D cross-correlation algorithms,
5
+ backends, and performance characteristics in OpenPIV, focusing on both **Circular**
6
+ and **Linear** correlation, `scipy.fft` (PocketFFT), and the `openpiv_rust` compiled backend.
7
+
8
+ .. contents:: Table of Contents
9
+ :depth: 2
10
+ :local:
11
+
12
+ Overview
13
+ --------
14
+
15
+ In Particle Image Velocimetry (PIV), cross-correlation between interrogation windows
16
+ is the core computational step. For an image pair split into :math:`N` interrogation
17
+ windows of size :math:`(H \times W)`, the correlation maps are computed using the
18
+ Wiener-Khinchin theorem via Fast Fourier Transforms:
19
+
20
+ .. math::
21
+
22
+ C = \mathcal{F}^{-1} \left( \mathcal{F}(I_B) \cdot \mathcal{F}^*(I_A) \right)
23
+
24
+ OpenPIV supports two primary correlation paradigms:
25
+
26
+ 1. **Circular Correlation (Standard OpenPIV)**:
27
+ Assumes periodic boundary conditions (toroidal wraparound).
28
+ Inputs of size :math:`(N \times N)` yield correlation maps of size :math:`(N \times N)`.
29
+ No zero-padding is required.
30
+
31
+ 2. **Linear Correlation (Full / Extended)**:
32
+ Evaluates true spatial shift without periodic wraparound.
33
+ For windows of size :math:`s_1` and :math:`s_2`, the full linear correlation has size
34
+ :math:`(s_1 + s_2 - 1)`. To prevent time-domain aliasing, inputs must be zero-padded
35
+ to at least this size before applying the FFT.
36
+
37
+
38
+ The 63x63 vs 64x64 Power-of-2 Finding
39
+ -------------------------------------
40
+
41
+ In previous versions of OpenPIV, linear correlation in ``openpiv.pyprocess.fft_correlate_images``
42
+ computed the padded transform size as:
43
+
44
+ .. code-block:: python
45
+
46
+ size = s1 + s2 - 1 # e.g., 32 + 32 - 1 = 63
47
+ fsize = 2 ** np.ceil(np.log2(size)).astype(int) - 1 # 64 - 1 = 63 (BUG!)
48
+
49
+ Why 63 Disabled PocketFFT Optimization
50
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
51
+
52
+ SciPy delegates FFT operations to **PocketFFT** (written in C++ by Martin Reinecke),
53
+ which includes hand-tuned AVX2/FMA vector kernels. However, these SIMD routines rely on
54
+ smooth radix factors (powers of 2: 2, 4, 8, 16, 32, 64).
55
+
56
+ 1. Subtracting 1 produced an **odd composite size** :math:`63 = 3 \times 3 \times 7`.
57
+ 2. This forced PocketFFT into slow Bluestein/composite transforms, disabling SIMD vectorization.
58
+ 3. Furthermore, padding to 63 left an off-by-one boundary alignment when slicing the central window.
59
+
60
+ The Power-of-2 Solution
61
+ ^^^^^^^^^^^^^^^^^^^^^^^
62
+
63
+ By keeping the clean power-of-2 transform size :math:`fsize = 2^{\lceil \log_2(size) \rceil}`
64
+ (e.g., :math:`64 \times 64` for :math:`32 \times 32` windows), PocketFFT achieves optimal
65
+ SIMD execution speed.
66
+
67
+ To recover the exact central correlation window matching ``scipy.signal.correlate``
68
+ to machine precision (:math:`\le 10^{-13}`), the centered slice is:
69
+
70
+ .. code-block:: python
71
+
72
+ # Exact power of 2 transform size
73
+ fsize = 2 ** np.ceil(np.log2(size)).astype(int)
74
+
75
+ # Centered slice extracting (s1) around the zero-displacement lag
76
+ fslice = (
77
+ slice(0, image_a.shape[0]),
78
+ slice(fsize[0] // 2 - s1[0] // 2, fsize[0] // 2 - s1[0] // 2 + s1[0]),
79
+ slice(fsize[1] // 2 - s1[1] // 2, fsize[1] // 2 - s1[1] // 2 + s1[1]),
80
+ )
81
+ f2a = conj(rfft2(image_a, fsize, axes=(-2, -1), workers=workers))
82
+ f2b = rfft2(image_b, fsize, axes=(-2, -1), workers=workers)
83
+ corr = fftshift(irfft2(f2a * f2b, axes=(-2, -1)).real, axes=(-2, -1))[fslice]
84
+
85
+
86
+ Batched Multi-Threading in scipy.fft
87
+ ------------------------------------
88
+
89
+ ``scipy.fft`` supports a ``workers`` parameter (e.g., ``workers=-1`` for all logical cores).
90
+ However, profiling reveals:
91
+
92
+ * On large 2D or 3D volumes, PocketFFT multi-threading scales well.
93
+ * On **batches of tiny 2D windows** (:math:`16 \times 16` or :math:`32 \times 32`),
94
+ thread synchronization overhead inside PocketFFT often negates multi-core gains
95
+ (scaling from :math:`1.0\times` to :math:`1.1\times`), because thread splitting occurs
96
+ per-transform rather than distributing independent windows across workers.
97
+
98
+
99
+ The Rust Acceleration Engine (openpiv_rust)
100
+ -------------------------------------------
101
+
102
+ To overcome Python GIL overhead and achieve linear CPU scaling across window batches,
103
+ OpenPIV provides an optional compiled Rust extension (``openpiv_rust``) built with
104
+ PyO3, Rayon, and RealFFT.
105
+
106
+ Key Architectural Optimizations in Rust
107
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
108
+
109
+ 1. **Zero-Allocation Rayon Scratch Pool**:
110
+ Window transforms use ``par_chunks_exact_mut().for_each_init(...)``, pre-allocating
111
+ frequency buffers once per worker thread. For 961 windows, this eliminates over
112
+ 8,600 heap allocations per pass.
113
+
114
+ 2. **Native Power-of-2 Real FFT**:
115
+ Uses ``realfft`` (Real-to-Complex forward and Complex-to-Real inverse), cutting
116
+ arithmetic and memory bandwidth by 50% compared to full complex transforms.
117
+
118
+ 3. **Batched Subpixel Peak Finding**:
119
+ In addition to cross-correlation, `openpiv_rust` provides
120
+ ``batch_correlation_to_displacement``, executing Gaussian/Centroid/Parabolic subpixel
121
+ peak fitting directly across all windows in parallel, yielding a **28x to 65x speedup**
122
+ over the nested Python loop.
123
+
124
+
125
+ Benchmark Results
126
+ -----------------
127
+
128
+ Linear Correlation (225 windows of 32x32)
129
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
130
+
131
+ ============================================ ==================== ==================
132
+ Implementation Runtime Relative Speed
133
+ ============================================ ==================== ==================
134
+ ``scipy.signal.correlate`` (Python loop) 70.63 ms 1.0x (baseline)
135
+ OpenPIV SciPy (legacy ``fsize=63``) 49.28 ms 1.4x
136
+ OpenPIV SciPy (power-of-2 ``fsize=64``) 39.10 ms 1.8x
137
+ **openpiv_rust** (Rayon + realfft) **8.26 ms** **8.5x**
138
+ ============================================ ==================== ==================
139
+
140
+ Displacement Calculation (Peak Finding)
141
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
142
+
143
+ ============================================ ==================== ==================
144
+ Grid / Windows Python Loop openpiv_rust
145
+ ============================================ ==================== ==================
146
+ 225 windows (32x32) 5.19 ms **0.18 ms (28x)**
147
+ 961 windows (16x16) 19.84 ms **0.30 ms (65x)**
148
+ ============================================ ==================== ==================
149
+
150
+ End-to-End Multi-Pass Windef (3 passes on 256x256 image)
151
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
152
+
153
+ * **SciPy backend**: 110.59 ms
154
+ * **Rust backend (with subpixel Rust engine)**: **81.66 ms** (:math:`1.35\times` speedup)
155
+
156
+ Conclusion & Usage Guidelines
157
+ -----------------------------
158
+
159
+ * For systems with compiled binary extensions available, set ``settings.backend = 'rust'``
160
+ to maximize throughput in both single-pass and multi-pass deformation workflows.
161
+ * On pure Python / NumPy / SciPy environments, using the exact power-of-2 padding rule
162
+ ensures optimal PocketFFT vectorization while maintaining exact numerical consistency.
@@ -19,23 +19,42 @@ The dependencies are:
19
19
  * `Python <http://python.org/>`_
20
20
  * `Scipy <http://numpy.scipy.org/>`_
21
21
  * `Numpy <http://www.scipy.org/>`_
22
- * `scikit-image <http://scikit-image.org/>`_
23
-
24
- On all platforms, the following Python distribution is recommended:
25
-
26
- * Anaconda <https://store.continuum.io/cshop/anaconda/>
22
+ * `scikit-image <http://scikit-image.org/>`_
27
23
 
28
24
 
29
25
  Installation
30
26
  ============
31
27
 
32
- Use `conda` ::
28
+ Recommended: Use `uv` (fastest)
29
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30
+
31
+ `uv <https://github.com/astral-sh/uv>`_ is a fast Python package installer and resolver written in Rust.
32
+ It provides faster and more reliable package installation compared to traditional tools.
33
+
34
+ To install OpenPIV with uv::
33
35
 
34
- conda install -c alexlib openpiv
36
+ pip install uv
37
+ uv pip install openpiv
35
38
 
36
- Or use `pip` ::
39
+ Or use `pip` (standard)
40
+ ^^^^^^^^^^^^^^^^^^^^^^^^
41
+
42
+ ::
37
43
 
38
44
  pip install openpiv
45
+
46
+ .. warning::
47
+ **Conda packages are no longer actively maintained.** The conda-forge package may be outdated.
48
+
49
+ If you previously installed OpenPIV via conda, you can migrate to pip or uv::
50
+
51
+ # Remove the conda package
52
+ conda remove openpiv
53
+
54
+ # Install with pip or uv
55
+ pip install openpiv
56
+ # or
57
+ uv pip install openpiv
39
58
 
40
59
  Get OpenPIV source code!
41
60
  ========================
@@ -44,8 +44,13 @@ def _gaussian_kernel(half_width: int=1)-> np.ndarray:
44
44
 
45
45
  """
46
46
  # size = int(half_width)
47
- x, y = np.mgrid[-half_width:half_width + 1, -half_width:half_width + 1]
48
- g = np.exp(-(x ** 2 / float(half_width) + y ** 2 / float(half_width)))
47
+
48
+ if half_width == 0:
49
+ return 1
50
+ else:
51
+ x, y = np.mgrid[-half_width:half_width + 1, -half_width:half_width + 1]
52
+ g = np.exp(-(x ** 2 / float(half_width) + y ** 2 / float(half_width)))
53
+
49
54
  return g / g.sum()
50
55
 
51
56
 
@@ -169,21 +174,21 @@ def replace_outliers(
169
174
  # regardless the grid_mask (which is a user-provided masked region)
170
175
 
171
176
 
172
- if not isinstance(u, np.ma.MaskedArray):
173
- u = np.ma.masked_array(u, mask=np.ma.nomask)
174
-
175
- # store grid_mask for reinforcement
176
- grid_mask = u.mask.copy()
177
-
178
- u[flags] = np.nan
179
- v[flags] = np.nan
177
+ has_mask = isinstance(u, np.ma.MaskedArray)
178
+ grid_mask = np.ma.getmaskarray(u).copy() if has_mask else np.ma.nomask
179
+
180
+ u_data = np.array(np.ma.getdata(u), copy=True)
181
+ v_data = np.array(np.ma.getdata(v), copy=True)
182
+
183
+ u_data[flags] = np.nan
184
+ v_data[flags] = np.nan
180
185
 
181
186
  uf = replace_nans(
182
- u, method=method, max_iter=max_iter, tol=tol,
187
+ u_data, method=method, max_iter=max_iter, tol=tol,
183
188
  kernel_size=kernel_size
184
189
  )
185
190
  vf = replace_nans(
186
- v, method=method, max_iter=max_iter, tol=tol,
191
+ v_data, method=method, max_iter=max_iter, tol=tol,
187
192
  kernel_size=kernel_size
188
193
  )
189
194
 
@@ -192,9 +197,10 @@ def replace_outliers(
192
197
  vf = np.ma.masked_array(vf, mask=grid_mask)
193
198
 
194
199
  if isinstance(w, np.ndarray):
195
- w[flags] = np.nan
200
+ w_data = np.array(np.ma.getdata(w), copy=True)
201
+ w_data[flags] = np.nan
196
202
  wf = replace_nans(
197
- w, method=method, max_iter=max_iter, tol=tol,
203
+ w_data, method=method, max_iter=max_iter, tol=tol,
198
204
  kernel_size=kernel_size
199
205
  )
200
206
  wf = np.ma.masked_array(wf, mask=grid_mask)