OpenPIV 0.26.0__tar.gz → 0.26.1__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 (177) hide show
  1. {openpiv-0.26.0 → openpiv-0.26.1}/PKG-INFO +87 -9
  2. {openpiv-0.26.0 → openpiv-0.26.1}/README.md +86 -8
  3. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/conf.py +11 -2
  4. openpiv-0.26.1/openpiv/docs/generate_figures.py +267 -0
  5. openpiv-0.26.1/openpiv/docs/images/dual_backend_architecture.png +0 -0
  6. openpiv-0.26.1/openpiv/docs/images/piv_validation_demo.png +0 -0
  7. openpiv-0.26.1/openpiv/docs/images/speedup_benchmarks.png +0 -0
  8. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/index.rst +8 -0
  9. openpiv-0.26.1/openpiv/docs/src/fft_correlation_backends.rst +245 -0
  10. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/installation_instruction.rst +16 -19
  11. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/piv.py +11 -4
  12. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/pyprocess.py +54 -7
  13. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/settings.py +1 -1
  14. openpiv-0.26.1/openpiv/test/test_dual_backends.py +160 -0
  15. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/windef.py +2 -2
  16. {openpiv-0.26.0 → openpiv-0.26.1}/pyproject.toml +1 -1
  17. openpiv-0.26.0/openpiv/docs/src/fft_correlation_backends.rst +0 -162
  18. {openpiv-0.26.0 → openpiv-0.26.1}/LICENSE.txt +0 -0
  19. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/PIV_3D_plotting.py +0 -0
  20. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/__init__.py +0 -0
  21. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test1/exp1_001_a.bmp +0 -0
  22. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test1/exp1_001_b.bmp +0 -0
  23. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test1/test_data.vec +0 -0
  24. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_00.tif +0 -0
  25. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_01.tif +0 -0
  26. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_10.tif +0 -0
  27. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_11.tif +0 -0
  28. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_20.tif +0 -0
  29. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_21.tif +0 -0
  30. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_30.tif +0 -0
  31. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_31.tif +0 -0
  32. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_40.tif +0 -0
  33. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_41.tif +0 -0
  34. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_50.tif +0 -0
  35. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_51.tif +0 -0
  36. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test3/pair_4_frame_0.jpg +0 -0
  37. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test3/pair_4_frame_1.jpg +0 -0
  38. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test4/Camera1-0101.tif +0 -0
  39. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test4/Camera1-0102.tif +0 -0
  40. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_10_A.tif +0 -0
  41. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_10_B.tif +0 -0
  42. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_1_A.tif +0 -0
  43. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_1_B.tif +0 -0
  44. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_2_A.tif +0 -0
  45. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_2_B.tif +0 -0
  46. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_3_A.tif +0 -0
  47. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_3_B.tif +0 -0
  48. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_4_A.tif +0 -0
  49. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_4_B.tif +0 -0
  50. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_5_A.tif +0 -0
  51. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_5_B.tif +0 -0
  52. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_6_A.tif +0 -0
  53. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_6_B.tif +0 -0
  54. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_7_A.tif +0 -0
  55. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_7_B.tif +0 -0
  56. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_8_A.tif +0 -0
  57. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_8_B.tif +0 -0
  58. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_9_A.tif +0 -0
  59. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_9_B.tif +0 -0
  60. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0001_A.tif +0 -0
  61. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0002_A.tif +0 -0
  62. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0003_A.tif +0 -0
  63. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0004_A.tif +0 -0
  64. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0005_A.tif +0 -0
  65. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1001_B.tif +0 -0
  66. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1002_B.tif +0 -0
  67. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1003_B.tif +0 -0
  68. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1004_B.tif +0 -0
  69. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1005_B.tif +0 -0
  70. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/Makefile +0 -0
  71. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/OpenPIV.pdf +0 -0
  72. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/OpenPIV_advanced_processing_parameters.pdf +0 -0
  73. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/B005_1.tif +0 -0
  74. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/B005_2.tif +0 -0
  75. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/image1.png +0 -0
  76. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/image2.png +0 -0
  77. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/image3.png +0 -0
  78. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/image4.png +0 -0
  79. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/make.bat +0 -0
  80. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/requirements.txt +0 -0
  81. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/api_reference.rst +0 -0
  82. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/developers.rst +0 -0
  83. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/faq.rst +0 -0
  84. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.filters._gaussian_kernel.rst +0 -0
  85. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.filters.gaussian.rst +0 -0
  86. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.filters.replace_outliers.rst +0 -0
  87. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.lib.replace_nans.rst +0 -0
  88. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.lib.sincinterp.rst +0 -0
  89. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.preprocess.dynamic_masking.rst +0 -0
  90. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.CorrelationFunction.rst +0 -0
  91. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.correlate_windows.rst +0 -0
  92. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.extended_search_area_piv.rst +0 -0
  93. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.get_coordinates.rst +0 -0
  94. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.get_field_shape.rst +0 -0
  95. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.normalize_intensity.rst +0 -0
  96. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.correlate_windows.rst +0 -0
  97. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.find_first_peak.rst +0 -0
  98. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.find_second_peak.rst +0 -0
  99. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.find_subpixel_peak_position.rst +0 -0
  100. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.get_coordinates.rst +0 -0
  101. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.get_field_shape.rst +0 -0
  102. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.moving_window_array.rst +0 -0
  103. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.normalize_intensity.rst +0 -0
  104. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.piv.rst +0 -0
  105. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.scaling.uniform.rst +0 -0
  106. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.Multiprocesser.rst +0 -0
  107. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.display.rst +0 -0
  108. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.display_vector_field.rst +0 -0
  109. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.imread.rst +0 -0
  110. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.save.rst +0 -0
  111. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.validation.global_std.rst +0 -0
  112. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.validation.global_val.rst +0 -0
  113. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.validation.local_median_val.rst +0 -0
  114. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.validation.sig2noise_val.rst +0 -0
  115. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/gui_doc.rst +0 -0
  116. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/introduction.rst +0 -0
  117. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/masking.ipynb +0 -0
  118. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/modules.rst +0 -0
  119. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/openpiv.rst +0 -0
  120. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/openpiv_pivuq.ipynb +0 -0
  121. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/piv_basics.ipynb +0 -0
  122. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/tutorial1.ipynb +0 -0
  123. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/windef.ipynb +0 -0
  124. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/filters.py +0 -0
  125. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/lib.py +0 -0
  126. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/phase_separation.py +0 -0
  127. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/preprocess.py +0 -0
  128. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/preprocess.py,cover +0 -0
  129. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/pyprocess3D.py +0 -0
  130. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/scaling.py +0 -0
  131. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/smoothn.py +0 -0
  132. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/OpenPIV_results_16_test1/field_A0000.png +0 -0
  133. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/__init__.py +0 -0
  134. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/conftest.py +0 -0
  135. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/extended_search_area_vectorized.ipynb +0 -0
  136. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/moon.png +0 -0
  137. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_PIV_3D_plotting.py +0 -0
  138. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_filters.ipynb +0 -0
  139. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_filters.py +0 -0
  140. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_find_peaks.ipynb +0 -0
  141. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_lib.py +0 -0
  142. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_package_metadata.py +0 -0
  143. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_performance.py +0 -0
  144. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_piv.py +0 -0
  145. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_preprocess.py +0 -0
  146. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_process.ipynb +0 -0
  147. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_process.py +0 -0
  148. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_pyprocess.py +0 -0
  149. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_pyprocess3D.py +0 -0
  150. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_rust_backend.py +0 -0
  151. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_rust_subpixel.py +0 -0
  152. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_rust_windef.py +0 -0
  153. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_scaling.py +0 -0
  154. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_smoothn.py +0 -0
  155. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools.ipynb +0 -0
  156. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools.png +0 -0
  157. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools.py +0 -0
  158. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_background.py +0 -0
  159. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_basic_utils.py +0 -0
  160. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_image_processing.py +0 -0
  161. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_multiprocessing.py +0 -0
  162. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_vector_field.py +0 -0
  163. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_validation.ipynb +0 -0
  164. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_validation.py +0 -0
  165. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_vectorized_extended_search.py +0 -0
  166. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef.ipynb +0 -0
  167. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef.py +0 -0
  168. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef_coverage.py +0 -0
  169. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef_detailed.py +0 -0
  170. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef_final.py +0 -0
  171. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tools.py +0 -0
  172. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/Example for overlap setting change.ipynb +0 -0
  173. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/masking_tutorial.py +0 -0
  174. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/tutorial1.py +0 -0
  175. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/tutorial2.py +0 -0
  176. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/windef_tutorial.py +0 -0
  177. {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenPIV
3
- Version: 0.26.0
3
+ Version: 0.26.1
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
6
  License-File: LICENSE.txt
@@ -95,21 +95,99 @@ If you previously installed OpenPIV via conda, you can migrate to pip or uv:
95
95
 
96
96
  ### To build from source
97
97
 
98
- <!-- TODO: Change this build method to use poetry -->
99
-
100
- Download the package from the Github: https://github.com/OpenPIV/openpiv-python/archive/master.zip
101
- or clone using git
98
+ Clone using git:
102
99
 
103
100
  git clone https://github.com/OpenPIV/openpiv-python.git
101
+ cd openpiv-python
102
+
103
+ To build the Rust acceleration extension locally (requires Rust toolchain and maturin):
104
+
105
+ pip install maturin
106
+ maturin develop --release -m crates/openpiv_rust/Cargo.toml
107
+ pip install -e .
108
+
109
+
110
+ ## High-Performance Dual-Backend Acceleration (Rust & SciPy)
111
+
112
+ OpenPIV features a parallel dual-backend architecture designed for high throughput without sacrificing numerical reproducibility:
113
+
114
+ * **⚡ Parallel Rust Backend (`openpiv_rust`)**: Multithreaded execution across all CPU cores using Rayon and real-to-complex FFTW/RustFFT routines. Delivers up to **580x** faster outlier validation, **19x** faster subpixel peak interpolation and SNR calculation, **4-6x** faster windowing and FFT cross-correlation, and **3.7x** faster end-to-end PIV pipelines.
115
+ * **🐍 Pure Python / SciPy Backend**: Complete, zero-dependency reference implementation that runs everywhere without a compiler.
116
+
117
+ When you install OpenPIV from PyPI via `pip` or `uv`, pre-compiled binary wheels with the Rust acceleration backend are installed automatically.
118
+
119
+ ### Explicit Backend Control
120
+
121
+ All core processing functions accept a `backend` parameter:
122
+
123
+ | `backend` Option | Behavior |
124
+ | :--- | :--- |
125
+ | `"auto"` *(default)* | Automatically selects the parallel Rust backend if available; cleanly and transparently falls back to pure Python/SciPy if not. |
126
+ | `"rust"` | Enforces the parallel Rust backend. Raises an informative `ImportError` if the Rust extension is not compiled. |
127
+ | `"scipy"` (or `"python"`) | Enforces the pure Python/SciPy reference path. |
128
+
129
+ Both backends produce identical numerical results (`diff = 0.0`).
130
+
131
+ ---
132
+
133
+ ### Code Examples
134
+
135
+ #### 1. Quick Analysis with `simple_piv`
136
+
137
+ ```python
138
+ from openpiv import piv
139
+
140
+ # Default: auto-selects fast Rust backend with fallback
141
+ x, y, u, v, s2n = piv.simple_piv("exp1_001_a.bmp", "exp1_001_b.bmp", backend="auto")
142
+
143
+ # Explicitly force the parallel Rust backend
144
+ x, y, u, v, s2n = piv.simple_piv("exp1_001_a.bmp", "exp1_001_b.bmp", backend="rust")
145
+
146
+ # Explicitly force the SciPy reference backend
147
+ x, y, u, v, s2n = piv.simple_piv("exp1_001_a.bmp", "exp1_001_b.bmp", backend="scipy")
148
+ ```
149
+
150
+ #### 2. Standard PIV with `extended_search_area_piv`
151
+
152
+ ```python
153
+ from openpiv import pyprocess, tools
154
+
155
+ frame_a = tools.imread("exp1_001_a.bmp")
156
+ frame_b = tools.imread("exp1_001_b.bmp")
157
+
158
+ # Run with parallel Rust acceleration
159
+ u, v, s2n = pyprocess.extended_search_area_piv(
160
+ frame_a,
161
+ frame_b,
162
+ window_size=32,
163
+ overlap=16,
164
+ search_area_size=32,
165
+ correlation_method="circular",
166
+ backend="rust", # 'auto', 'rust', or 'scipy'
167
+ )
168
+ ```
169
+
170
+ #### 3. Multi-Pass Window Deformation (`windef`)
171
+
172
+ ```python
173
+ from openpiv import windef, tools
104
174
 
105
- Using distutils create a local (in the same directory) compilation of the Cython files:
175
+ settings = windef.PIVSettings()
176
+ settings.windowsizes = (64, 32, 16)
177
+ settings.overlap = (32, 16, 8)
178
+ settings.num_iterations = 3
106
179
 
107
- python setup.py build_ext --inplace
180
+ # Choose backend in PIVSettings: 'auto', 'rust', or 'scipy'
181
+ settings.backend = "auto"
108
182
 
109
- Or for the global installation, use:
183
+ frame_a = tools.imread("exp1_001_a.bmp")
184
+ frame_b = tools.imread("exp1_001_b.bmp")
110
185
 
111
- python setup.py install
186
+ # Executes all deformation passes with the chosen backend
187
+ x, y, u, v, mask = windef.simple_multipass(frame_a, frame_b, settings)
188
+ ```
112
189
 
190
+ ---
113
191
 
114
192
  ## Documentation
115
193
 
@@ -58,21 +58,99 @@ If you previously installed OpenPIV via conda, you can migrate to pip or uv:
58
58
 
59
59
  ### To build from source
60
60
 
61
- <!-- TODO: Change this build method to use poetry -->
62
-
63
- Download the package from the Github: https://github.com/OpenPIV/openpiv-python/archive/master.zip
64
- or clone using git
61
+ Clone using git:
65
62
 
66
63
  git clone https://github.com/OpenPIV/openpiv-python.git
64
+ cd openpiv-python
65
+
66
+ To build the Rust acceleration extension locally (requires Rust toolchain and maturin):
67
+
68
+ pip install maturin
69
+ maturin develop --release -m crates/openpiv_rust/Cargo.toml
70
+ pip install -e .
71
+
72
+
73
+ ## High-Performance Dual-Backend Acceleration (Rust & SciPy)
74
+
75
+ OpenPIV features a parallel dual-backend architecture designed for high throughput without sacrificing numerical reproducibility:
76
+
77
+ * **⚡ Parallel Rust Backend (`openpiv_rust`)**: Multithreaded execution across all CPU cores using Rayon and real-to-complex FFTW/RustFFT routines. Delivers up to **580x** faster outlier validation, **19x** faster subpixel peak interpolation and SNR calculation, **4-6x** faster windowing and FFT cross-correlation, and **3.7x** faster end-to-end PIV pipelines.
78
+ * **🐍 Pure Python / SciPy Backend**: Complete, zero-dependency reference implementation that runs everywhere without a compiler.
79
+
80
+ When you install OpenPIV from PyPI via `pip` or `uv`, pre-compiled binary wheels with the Rust acceleration backend are installed automatically.
81
+
82
+ ### Explicit Backend Control
83
+
84
+ All core processing functions accept a `backend` parameter:
85
+
86
+ | `backend` Option | Behavior |
87
+ | :--- | :--- |
88
+ | `"auto"` *(default)* | Automatically selects the parallel Rust backend if available; cleanly and transparently falls back to pure Python/SciPy if not. |
89
+ | `"rust"` | Enforces the parallel Rust backend. Raises an informative `ImportError` if the Rust extension is not compiled. |
90
+ | `"scipy"` (or `"python"`) | Enforces the pure Python/SciPy reference path. |
91
+
92
+ Both backends produce identical numerical results (`diff = 0.0`).
93
+
94
+ ---
95
+
96
+ ### Code Examples
97
+
98
+ #### 1. Quick Analysis with `simple_piv`
99
+
100
+ ```python
101
+ from openpiv import piv
102
+
103
+ # Default: auto-selects fast Rust backend with fallback
104
+ x, y, u, v, s2n = piv.simple_piv("exp1_001_a.bmp", "exp1_001_b.bmp", backend="auto")
105
+
106
+ # Explicitly force the parallel Rust backend
107
+ x, y, u, v, s2n = piv.simple_piv("exp1_001_a.bmp", "exp1_001_b.bmp", backend="rust")
108
+
109
+ # Explicitly force the SciPy reference backend
110
+ x, y, u, v, s2n = piv.simple_piv("exp1_001_a.bmp", "exp1_001_b.bmp", backend="scipy")
111
+ ```
112
+
113
+ #### 2. Standard PIV with `extended_search_area_piv`
114
+
115
+ ```python
116
+ from openpiv import pyprocess, tools
117
+
118
+ frame_a = tools.imread("exp1_001_a.bmp")
119
+ frame_b = tools.imread("exp1_001_b.bmp")
120
+
121
+ # Run with parallel Rust acceleration
122
+ u, v, s2n = pyprocess.extended_search_area_piv(
123
+ frame_a,
124
+ frame_b,
125
+ window_size=32,
126
+ overlap=16,
127
+ search_area_size=32,
128
+ correlation_method="circular",
129
+ backend="rust", # 'auto', 'rust', or 'scipy'
130
+ )
131
+ ```
132
+
133
+ #### 3. Multi-Pass Window Deformation (`windef`)
134
+
135
+ ```python
136
+ from openpiv import windef, tools
67
137
 
68
- Using distutils create a local (in the same directory) compilation of the Cython files:
138
+ settings = windef.PIVSettings()
139
+ settings.windowsizes = (64, 32, 16)
140
+ settings.overlap = (32, 16, 8)
141
+ settings.num_iterations = 3
69
142
 
70
- python setup.py build_ext --inplace
143
+ # Choose backend in PIVSettings: 'auto', 'rust', or 'scipy'
144
+ settings.backend = "auto"
71
145
 
72
- Or for the global installation, use:
146
+ frame_a = tools.imread("exp1_001_a.bmp")
147
+ frame_b = tools.imread("exp1_001_b.bmp")
73
148
 
74
- python setup.py install
149
+ # Executes all deformation passes with the chosen backend
150
+ x, y, u, v, mask = windef.simple_multipass(frame_a, frame_b, settings)
151
+ ```
75
152
 
153
+ ---
76
154
 
77
155
  ## Documentation
78
156
 
@@ -17,9 +17,18 @@ import os
17
17
  # If extensions (or modules to document with autodoc) are in another directory,
18
18
  # add these directories to sys.path here. If the directory is relative to the
19
19
  # documentation root, use os.path.abspath to make it absolute, like shown here.
20
- sys.path.insert(0, os.path.abspath('../openpiv'))
20
+ sys.path.insert(0, os.path.abspath('../..'))
21
+ sys.path.insert(0, os.path.abspath('..'))
21
22
  sys.path.append(os.path.abspath("sphinxext"))
22
23
 
24
+ try:
25
+ import pypandoc
26
+ pandoc_dir = os.path.dirname(pypandoc.get_pandoc_path())
27
+ if pandoc_dir not in os.environ["PATH"]:
28
+ os.environ["PATH"] = pandoc_dir + os.pathsep + os.environ["PATH"]
29
+ except Exception:
30
+ pass
31
+
23
32
  # -- General configuration -----------------------------------------------------
24
33
 
25
34
  # If your documentation needs a minimal Sphinx version, state it here.
@@ -64,7 +73,7 @@ copyright = '2014, OpenPIV team'
64
73
  # built documents.
65
74
  #
66
75
  # The short X.Y version.
67
- version = '0.26.0'
76
+ version = '0.26.1'
68
77
  # The full version, including alpha/beta/rc tags.
69
78
  # release = '0.0.1a'
70
79
 
@@ -0,0 +1,267 @@
1
+ import os
2
+ from pathlib import Path
3
+ import matplotlib
4
+ matplotlib.use("Agg")
5
+ import matplotlib.pyplot as plt
6
+ import matplotlib.patches as patches
7
+ import numpy as np
8
+ from importlib.resources import files
9
+ from openpiv import tools, pyprocess, validation
10
+
11
+ DOCS_IMG_DIR = Path(__file__).resolve().parent / "images"
12
+ DOCS_IMG_DIR.mkdir(parents=True, exist_ok=True)
13
+
14
+ # Set publication style
15
+ plt.rcParams.update({
16
+ "font.size": 11,
17
+ "font.family": "sans-serif",
18
+ "axes.labelsize": 12,
19
+ "axes.titlesize": 13,
20
+ "xtick.labelsize": 10,
21
+ "ytick.labelsize": 10,
22
+ "figure.titlesize": 14,
23
+ })
24
+
25
+
26
+ def generate_benchmark_figure():
27
+ """Generate a horizontal bar chart showing speedups across OpenPIV components."""
28
+ components = [
29
+ "Normalized Median Val.\n(Westerweel 64x64)",
30
+ "Peak-to-Peak SNR\n(961 windows)",
31
+ "Subpixel Peak Fitting\n(961 windows)",
32
+ "Peak-to-Mean SNR\n(961 windows)",
33
+ "Linear Correlation\n(225 windows)",
34
+ "Window Array Extraction\n(3969 windows)",
35
+ "Circular Correlation\n(225 windows)",
36
+ "End-to-End Search Area PIV\n(512x512 image)",
37
+ "Multi-Pass Windef\n(3 passes, 256x256)",
38
+ ]
39
+
40
+ speedups = [581.6, 18.9, 16.8, 7.6, 6.2, 3.4, 3.2, 3.7, 1.4]
41
+ scipy_times = ["890.3 ms", "111.2 ms", "9.1 ms", "30.4 ms", "63.5 ms", "74.8 ms", "10.6 ms", "111.6 ms", "158.8 ms"]
42
+ rust_times = ["1.53 ms", "5.89 ms", "0.54 ms", "3.99 ms", "10.18 ms", "21.98 ms", "3.31 ms", "30.00 ms", "113.39 ms"]
43
+
44
+ # Reverse order so top is highest speedup
45
+ components.reverse()
46
+ speedups.reverse()
47
+ scipy_times.reverse()
48
+ rust_times.reverse()
49
+
50
+ fig, ax = plt.subplots(figsize=(10, 6.5), dpi=300)
51
+ y_pos = np.arange(len(components))
52
+
53
+ # Gradient colors
54
+ colors = plt.cm.viridis(np.linspace(0.3, 0.9, len(components)))
55
+
56
+ bars = ax.barh(y_pos, speedups, color=colors, height=0.65, edgecolor="black", linewidth=0.8)
57
+
58
+ ax.set_xscale("log")
59
+ ax.set_xlabel("Speedup Factor (Rust / SciPy, Log Scale)", fontweight="bold")
60
+ ax.set_title("OpenPIV Acceleration: Parallel Rust vs Reference SciPy", fontweight="bold", pad=15)
61
+ ax.set_yticks(y_pos)
62
+ ax.set_yticklabels(components)
63
+ ax.set_xlim(1.0, 1000)
64
+
65
+ # Gridlines
66
+ ax.grid(axis="x", which="both", linestyle="--", alpha=0.5)
67
+ ax.set_axisbelow(True)
68
+
69
+ # Annotate bars
70
+ for i, (bar, spd, st, rt) in enumerate(zip(bars, speedups, scipy_times, rust_times)):
71
+ width = bar.get_width()
72
+ label_text = f" {spd:.1f}x ({st} \u2192 {rt})"
73
+ ax.text(width * 1.08, bar.get_y() + bar.get_height() / 2, label_text,
74
+ va="center", ha="left", fontsize=9.5, fontweight="semibold", color="#1a1a1a")
75
+
76
+ plt.tight_layout()
77
+ out_path = DOCS_IMG_DIR / "speedup_benchmarks.png"
78
+ plt.savefig(out_path, bbox_inches="tight")
79
+ plt.close()
80
+ print(f"Saved: {out_path}")
81
+
82
+
83
+ def generate_architecture_diagram():
84
+ """Create a high-level architecture diagram illustrating the dual-backend dispatch."""
85
+ fig, ax = plt.subplots(figsize=(12, 7), dpi=300)
86
+ ax.axis("off")
87
+ ax.set_xlim(0, 100)
88
+ ax.set_ylim(0, 100)
89
+
90
+ # Color palette
91
+ box_blue = "#d0e1fd"
92
+ border_blue = "#1a73e8"
93
+ box_rust = "#fce8e6"
94
+ border_rust = "#d93025"
95
+ box_scipy = "#e6f4ea"
96
+ border_scipy = "#1e8e3e"
97
+ box_gray = "#f1f3f4"
98
+ border_gray = "#5f6368"
99
+
100
+ # Title
101
+ ax.text(50, 96, "OpenPIV Dual-Backend Processing Architecture",
102
+ ha="center", va="center", fontsize=15, fontweight="bold", color="#202124")
103
+
104
+ # User Entry Points
105
+ rect_user = patches.FancyBboxPatch((5, 78), 90, 12, boxstyle="round,pad=0.8,rounding_size=2",
106
+ facecolor=box_gray, edgecolor=border_gray, linewidth=1.5)
107
+ ax.add_patch(rect_user)
108
+ ax.text(50, 85, "High-Level API (backend='auto' | 'rust' | 'scipy')",
109
+ ha="center", va="center", fontsize=12, fontweight="bold", color="#202124")
110
+ ax.text(50, 80, "piv.simple_piv() \u2022 pyprocess.extended_search_area_piv() \u2022 windef.simple_multipass()",
111
+ ha="center", va="center", fontsize=10, color="#3c4043")
112
+
113
+ # Dispatch Controller
114
+ rect_disp = patches.FancyBboxPatch((30, 62), 40, 10, boxstyle="round,pad=0.8,rounding_size=2",
115
+ facecolor=box_blue, edgecolor=border_blue, linewidth=1.8)
116
+ ax.add_patch(rect_disp)
117
+ ax.text(50, 67, "Unified Backend Dispatch Controller",
118
+ ha="center", va="center", fontsize=11, fontweight="bold", color="#1a73e8")
119
+ ax.text(50, 64, "Auto-detects openpiv_rust with transparent SciPy fallback",
120
+ ha="center", va="center", fontsize=9, color="#174ea6")
121
+
122
+ # Arrows from user to dispatch
123
+ ax.annotate("", xy=(50, 72), xytext=(50, 78),
124
+ arrowprops=dict(arrowstyle="->", lw=1.8, color="#5f6368"))
125
+
126
+ # Backend Columns
127
+ # Left: Rust Engine
128
+ rect_rust = patches.FancyBboxPatch((5, 12), 42, 44, boxstyle="round,pad=0.8,rounding_size=2",
129
+ facecolor=box_rust, edgecolor=border_rust, linewidth=2)
130
+ ax.add_patch(rect_rust)
131
+ ax.text(26, 52, "Parallel Rust Engine (openpiv_rust)",
132
+ ha="center", va="center", fontsize=12, fontweight="bold", color="#c5221f")
133
+ ax.text(26, 48, "Compiled CPython C-FFI / Rayon Multithreading",
134
+ ha="center", va="center", fontsize=9, style="italic", color="#5f6368")
135
+
136
+ rust_features = [
137
+ "+ fast_batch_cross_correlation (RealFFT)",
138
+ "+ sliding_window_array (zero-allocation copies)",
139
+ "+ batch_correlation_to_displacement (parallel)",
140
+ "+ sig2noise_ratio (fused Rayon peak scan)",
141
+ "+ local_norm_median_val (quickselect <2 ms)",
142
+ ]
143
+ for idx, feat in enumerate(rust_features):
144
+ ax.text(8, 42 - idx * 6.5, feat, ha="left", va="center", fontsize=9.5, color="#202124")
145
+
146
+ # Right: SciPy / Python Fallback
147
+ rect_scipy = patches.FancyBboxPatch((53, 12), 42, 44, boxstyle="round,pad=0.8,rounding_size=2",
148
+ facecolor=box_scipy, edgecolor=border_scipy, linewidth=2)
149
+ ax.add_patch(rect_scipy)
150
+ ax.text(74, 52, "Pure Python / SciPy Reference",
151
+ ha="center", va="center", fontsize=12, fontweight="bold", color="#137333")
152
+ ax.text(74, 48, "Zero compiler requirement, 100% portable",
153
+ ha="center", va="center", fontsize=9, style="italic", color="#5f6368")
154
+
155
+ scipy_features = [
156
+ "+ scipy.fft with power-of-2 padding",
157
+ "+ numpy.lib.stride_tricks indexing",
158
+ "+ Python peak finding loop (scipy reference)",
159
+ "+ numpy.ma.MaskedArray second peak finder",
160
+ "+ scipy.ndimage.generic_filter reference",
161
+ ]
162
+ for idx, feat in enumerate(scipy_features):
163
+ ax.text(56, 42 - idx * 6.5, feat, ha="left", va="center", fontsize=9.5, color="#202124")
164
+
165
+ # Dispatch arrows
166
+ ax.annotate("backend in ('auto', 'rust')", xy=(26, 56), xytext=(40, 62),
167
+ arrowprops=dict(arrowstyle="->", lw=1.8, color="#d93025"),
168
+ ha="right", va="bottom", fontsize=9, fontweight="bold", color="#d93025")
169
+ ax.annotate("backend in ('scipy', 'fallback')", xy=(74, 56), xytext=(60, 62),
170
+ arrowprops=dict(arrowstyle="->", lw=1.8, color="#1e8e3e"),
171
+ ha="left", va="bottom", fontsize=9, fontweight="bold", color="#1e8e3e")
172
+
173
+ # Parity Banner at Bottom
174
+ rect_parity = patches.FancyBboxPatch((15, 2), 70, 7, boxstyle="round,pad=0.5,rounding_size=1.5",
175
+ facecolor="#fff8e1", edgecolor="#f9ab00", linewidth=1.5)
176
+ ax.add_patch(rect_parity)
177
+ ax.text(50, 5.5, "Dual-Backend Parity Guarantee: Identical Numerical Outputs (max diff = 0.0)",
178
+ ha="center", va="center", fontsize=10.5, fontweight="bold", color="#b06000")
179
+
180
+ plt.tight_layout()
181
+ out_path = DOCS_IMG_DIR / "dual_backend_architecture.png"
182
+ plt.savefig(out_path, bbox_inches="tight")
183
+ plt.close()
184
+ print(f"Saved: {out_path}")
185
+
186
+
187
+ def generate_pipeline_demo_figure():
188
+ """Run an end-to-end PIV computation and generate vector field & validation visualizations."""
189
+ im1_path = Path(files("openpiv.data").joinpath("test1/exp1_001_a.bmp"))
190
+ im2_path = Path(files("openpiv.data").joinpath("test1/exp1_001_b.bmp"))
191
+ frame_a = tools.imread(im1_path)
192
+ frame_b = tools.imread(im2_path)
193
+
194
+ # Process with extended search area PIV
195
+ ws = 32
196
+ ov = 16
197
+ u, v, s2n = pyprocess.extended_search_area_piv(
198
+ frame_a.astype(np.int32),
199
+ frame_b.astype(np.int32),
200
+ window_size=ws,
201
+ overlap=ov,
202
+ search_area_size=ws,
203
+ correlation_method="circular",
204
+ backend="auto",
205
+ )
206
+ x, y = pyprocess.get_coordinates(image_size=frame_a.shape, search_area_size=ws, overlap=ov)
207
+
208
+ # Validate using accelerated normalized median test (Westerweel Universal Outlier Detection)
209
+ mask = validation.local_norm_median_val(u, v, 0.1, 2.0, size=2, backend="auto")
210
+
211
+ speed = np.sqrt(u**2 + v**2)
212
+
213
+ fig, axes = plt.subplots(1, 3, figsize=(15, 5), dpi=300)
214
+
215
+ # Panel 1: Particle Image Frame A
216
+ axes[0].imshow(frame_a, cmap="gray", origin="upper")
217
+ axes[0].set_title("(a) Raw Particle Image Pair (Frame A)", fontweight="bold")
218
+ axes[0].set_xlabel("X [pixels]")
219
+ axes[0].set_ylabel("Y [pixels]")
220
+
221
+ # Overlay grid
222
+ for gx in np.arange(0, frame_a.shape[1], ws):
223
+ axes[0].axvline(gx, color="cyan", alpha=0.15, lw=0.5)
224
+ for gy in np.arange(0, frame_a.shape[0], ws):
225
+ axes[0].axhline(gy, color="cyan", alpha=0.15, lw=0.5)
226
+
227
+ # Panel 2: Quiver Velocity Field
228
+ axes[1].imshow(frame_a, cmap="gray", alpha=0.35, origin="upper")
229
+ q = axes[1].quiver(
230
+ x, y, u, -v, speed,
231
+ cmap="turbo",
232
+ scale=50,
233
+ width=0.005,
234
+ pivot="mid",
235
+ )
236
+ cbar = plt.colorbar(q, ax=axes[1], fraction=0.046, pad=0.04)
237
+ cbar.set_label("Velocity Magnitude [px/dt]")
238
+ axes[1].set_title("(b) Vector Field (Fast FFT + Peak Fit)", fontweight="bold")
239
+ axes[1].set_xlabel("X [pixels]")
240
+ axes[1].set_ylabel("Y [pixels]")
241
+ axes[1].invert_yaxis()
242
+
243
+ # Panel 3: Outlier Detection Map
244
+ im3 = axes[2].imshow(s2n, cmap="viridis", origin="upper")
245
+ cbar2 = plt.colorbar(im3, ax=axes[2], fraction=0.046, pad=0.04)
246
+ cbar2.set_label("Signal-to-Noise Ratio (Peak-to-Peak)")
247
+
248
+ # Highlight flagged outliers from local_norm_median_val
249
+ outlier_y, outlier_x = np.where(mask)
250
+ if len(outlier_x) > 0:
251
+ axes[2].scatter(outlier_x, outlier_y, marker="x", color="red", s=70, linewidth=2, label=f"Outlier ({len(outlier_x)})")
252
+ axes[2].legend(loc="upper right", framealpha=0.8)
253
+ axes[2].set_title("(c) Westerweel Outlier Validation (<2 ms)", fontweight="bold")
254
+ axes[2].set_xlabel("Grid Column")
255
+ axes[2].set_ylabel("Grid Row")
256
+
257
+ plt.tight_layout()
258
+ out_path = DOCS_IMG_DIR / "piv_validation_demo.png"
259
+ plt.savefig(out_path, bbox_inches="tight")
260
+ plt.close()
261
+ print(f"Saved: {out_path}")
262
+
263
+
264
+ if __name__ == "__main__":
265
+ generate_benchmark_figure()
266
+ generate_architecture_diagram()
267
+ generate_pipeline_demo_figure()
@@ -16,6 +16,14 @@ to OpenPIV, with code, suggestions and critics.
16
16
 
17
17
  OpenPIV exists in three forms: Matlab, C++ and Python. This is the home page of the **Python** implementation.
18
18
 
19
+ High-Performance Dual-Backend Engine
20
+ ------------------------------------
21
+
22
+ OpenPIV features an integrated dual-backend architecture:
23
+
24
+ * **Parallel Rust Backend** (``openpiv_rust``): Multi-core Rayon parallelization delivering up to **580x** faster outlier validation, **19x** faster peak interpolation & SNR estimation, and **4-6x** faster cross-correlation. Pre-compiled wheels are published to PyPI for Linux, macOS, and Windows.
25
+ * **Pure Python / SciPy Reference**: Zero-compiler portable fallback ensuring 100% numerical parity across platforms.
26
+
19
27
  =========
20
28
  Contents:
21
29
  =========