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.
- {openpiv-0.25.3 → openpiv-0.26.0}/PKG-INFO +38 -20
- {openpiv-0.25.3 → openpiv-0.26.0}/README.md +24 -7
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/PIV_3D_plotting.py +16 -10
- openpiv-0.26.0/openpiv/__init__.py +10 -0
- openpiv-0.26.0/openpiv/data/test1/test_data.vec +71 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/conf.py +1 -1
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/index.rst +1 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/requirements.txt +0 -1
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/developers.rst +61 -0
- openpiv-0.26.0/openpiv/docs/src/fft_correlation_backends.rst +162 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/installation_instruction.rst +27 -8
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/filters.py +20 -14
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/lib.py +38 -63
- openpiv-0.26.0/openpiv/piv.py +288 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/preprocess.py +150 -138
- openpiv-0.26.0/openpiv/preprocess.py,cover +579 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/pyprocess.py +151 -73
- openpiv-0.26.0/openpiv/settings.py +167 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/smoothn.py +183 -219
- openpiv-0.26.0/openpiv/test/OpenPIV_results_16_test1/field_A0000.png +0 -0
- openpiv-0.26.0/openpiv/test/conftest.py +57 -0
- openpiv-0.26.0/openpiv/test/test_PIV_3D_plotting.py +236 -0
- openpiv-0.26.0/openpiv/test/test_filters.py +190 -0
- openpiv-0.26.0/openpiv/test/test_lib.py +224 -0
- openpiv-0.26.0/openpiv/test/test_package_metadata.py +7 -0
- openpiv-0.26.0/openpiv/test/test_performance.py +161 -0
- openpiv-0.26.0/openpiv/test/test_piv.py +440 -0
- openpiv-0.26.0/openpiv/test/test_preprocess.py +491 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_process.py +90 -23
- openpiv-0.26.0/openpiv/test/test_pyprocess.py +438 -0
- openpiv-0.26.0/openpiv/test/test_rust_backend.py +174 -0
- openpiv-0.26.0/openpiv/test/test_rust_subpixel.py +101 -0
- openpiv-0.26.0/openpiv/test/test_rust_windef.py +121 -0
- openpiv-0.26.0/openpiv/test/test_smoothn.py +611 -0
- openpiv-0.26.0/openpiv/test/test_tools.py +233 -0
- openpiv-0.26.0/openpiv/test/test_tools_background.py +199 -0
- openpiv-0.26.0/openpiv/test/test_tools_basic_utils.py +131 -0
- openpiv-0.26.0/openpiv/test/test_tools_image_processing.py +229 -0
- openpiv-0.26.0/openpiv/test/test_tools_multiprocessing.py +197 -0
- openpiv-0.26.0/openpiv/test/test_tools_vector_field.py +230 -0
- openpiv-0.26.0/openpiv/test/test_validation.py +421 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_windef.py +30 -22
- openpiv-0.26.0/openpiv/test/test_windef_coverage.py +215 -0
- openpiv-0.26.0/openpiv/test/test_windef_detailed.py +808 -0
- openpiv-0.26.0/openpiv/test/test_windef_final.py +76 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tools.py +140 -119
- openpiv-0.26.0/openpiv/tutorials/Example for overlap setting change.ipynb +410 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tutorials/masking_tutorial.py +1 -1
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tutorials/tutorial1.py +1 -1
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tutorials/tutorial2.py +1 -1
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/tutorials/windef_tutorial.py +1 -1
- openpiv-0.26.0/openpiv/validation.py +549 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/windef.py +242 -348
- {openpiv-0.25.3 → openpiv-0.26.0}/pyproject.toml +17 -14
- openpiv-0.25.3/openpiv/__init__.py +0 -4
- openpiv-0.25.3/openpiv/data/test1/test_data.vec +0 -71
- openpiv-0.25.3/openpiv/piv.py +0 -117
- openpiv-0.25.3/openpiv/test/test_filters.py +0 -83
- openpiv-0.25.3/openpiv/test/test_preprocess.py +0 -46
- openpiv-0.25.3/openpiv/test/test_tools.py +0 -78
- openpiv-0.25.3/openpiv/test/test_validation.py +0 -155
- openpiv-0.25.3/openpiv/validation.py +0 -316
- {openpiv-0.25.3 → openpiv-0.26.0}/LICENSE.txt +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test1/exp1_001_a.bmp +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test1/exp1_001_b.bmp +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_00.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_01.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_10.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_11.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_20.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_21.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_30.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_31.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_40.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_41.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_50.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test2/2image_51.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test3/pair_4_frame_0.jpg +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test3/pair_4_frame_1.jpg +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test4/Camera1-0101.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test4/Camera1-0102.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_10_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_10_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_1_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_1_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_2_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_2_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_3_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_3_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_4_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_4_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_5_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_5_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_6_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_6_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_7_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_7_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_8_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_8_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_9_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test5/Pattern_9_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0001_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0002_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0003_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0004_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_0005_A.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1001_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1002_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1003_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1004_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/data/test6/Pattern_1005_B.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/Makefile +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/OpenPIV.pdf +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/OpenPIV_advanced_processing_parameters.pdf +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/B005_1.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/B005_2.tif +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/image1.png +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/image2.png +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/image3.png +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/images/image4.png +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/make.bat +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/api_reference.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/faq.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.filters._gaussian_kernel.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.filters.gaussian.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.filters.replace_outliers.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.lib.replace_nans.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.lib.sincinterp.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.preprocess.dynamic_masking.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.CorrelationFunction.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.correlate_windows.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.extended_search_area_piv.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.get_coordinates.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.get_field_shape.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.process.normalize_intensity.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.correlate_windows.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.find_first_peak.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.find_second_peak.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.find_subpixel_peak_position.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.get_coordinates.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.get_field_shape.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.moving_window_array.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.normalize_intensity.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.pyprocess.piv.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.scaling.uniform.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.Multiprocesser.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.display.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.display_vector_field.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.imread.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.tools.save.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.validation.global_std.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.validation.global_val.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.validation.local_median_val.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/generated/openpiv.validation.sig2noise_val.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/gui_doc.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/introduction.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/masking.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/modules.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/openpiv.rst +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/openpiv_pivuq.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/piv_basics.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/tutorial1.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/docs/src/windef.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/phase_separation.py +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/pyprocess3D.py +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/scaling.py +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/__init__.py +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/extended_search_area_vectorized.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/moon.png +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_filters.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_find_peaks.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_process.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_pyprocess3D.py +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_scaling.py +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_tools.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_tools.png +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_validation.ipynb +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_vectorized_extended_search.py +0 -0
- {openpiv-0.25.3 → openpiv-0.26.0}/openpiv/test/test_windef.ipynb +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: OpenPIV
|
|
3
|
-
Version: 0.
|
|
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.
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.
|
|
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
|
-
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist: matplotlib (>=3
|
|
28
|
-
Requires-Dist: natsort (>=8.4.0
|
|
29
|
-
Requires-Dist: numpy (>=
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist:
|
|
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
|
-
](https://github.com/OpenPIV/openpiv-python/actions/workflows/testing.yml)
|
|
40
|
+
[](https://doi.org/10.5281/zenodo.18304582)
|
|
40
41
|

|
|
41
|
-

|
|
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
|
-
|
|
79
|
+
poetry add openpiv
|
|
70
80
|
|
|
71
|
-
|
|
81
|
+
### Note on Conda/Anaconda
|
|
72
82
|
|
|
73
|
-
|
|
83
|
+
⚠️ **Conda packages are no longer actively maintained.** The conda-forge package may be outdated.
|
|
74
84
|
|
|
75
|
-
|
|
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
|
-
](https://github.com/OpenPIV/openpiv-python/actions/workflows/testing.yml)
|
|
3
|
+
[](https://doi.org/10.5281/zenodo.18304582)
|
|
4
4
|

|
|
5
|
-

|
|
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
|
-
|
|
42
|
+
poetry add openpiv
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
### Note on Conda/Anaconda
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
⚠️ **Conda packages are no longer actively maintained.** The conda-forge package may be outdated.
|
|
38
47
|
|
|
39
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
352
|
-
|
|
353
|
-
|
|
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,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
|
|
@@ -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 `
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
+
w_data = np.array(np.ma.getdata(w), copy=True)
|
|
201
|
+
w_data[flags] = np.nan
|
|
196
202
|
wf = replace_nans(
|
|
197
|
-
|
|
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)
|