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.
- {openpiv-0.26.0 → openpiv-0.26.1}/PKG-INFO +87 -9
- {openpiv-0.26.0 → openpiv-0.26.1}/README.md +86 -8
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/conf.py +11 -2
- openpiv-0.26.1/openpiv/docs/generate_figures.py +267 -0
- openpiv-0.26.1/openpiv/docs/images/dual_backend_architecture.png +0 -0
- openpiv-0.26.1/openpiv/docs/images/piv_validation_demo.png +0 -0
- openpiv-0.26.1/openpiv/docs/images/speedup_benchmarks.png +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/index.rst +8 -0
- openpiv-0.26.1/openpiv/docs/src/fft_correlation_backends.rst +245 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/installation_instruction.rst +16 -19
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/piv.py +11 -4
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/pyprocess.py +54 -7
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/settings.py +1 -1
- openpiv-0.26.1/openpiv/test/test_dual_backends.py +160 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/windef.py +2 -2
- {openpiv-0.26.0 → openpiv-0.26.1}/pyproject.toml +1 -1
- openpiv-0.26.0/openpiv/docs/src/fft_correlation_backends.rst +0 -162
- {openpiv-0.26.0 → openpiv-0.26.1}/LICENSE.txt +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/PIV_3D_plotting.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/__init__.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test1/exp1_001_a.bmp +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test1/exp1_001_b.bmp +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test1/test_data.vec +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_00.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_01.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_10.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_11.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_20.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_21.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_30.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_31.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_40.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_41.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_50.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test2/2image_51.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test3/pair_4_frame_0.jpg +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test3/pair_4_frame_1.jpg +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test4/Camera1-0101.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test4/Camera1-0102.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_10_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_10_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_1_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_1_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_2_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_2_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_3_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_3_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_4_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_4_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_5_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_5_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_6_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_6_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_7_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_7_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_8_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_8_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_9_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test5/Pattern_9_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0001_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0002_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0003_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0004_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_0005_A.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1001_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1002_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1003_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1004_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/data/test6/Pattern_1005_B.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/Makefile +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/OpenPIV.pdf +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/OpenPIV_advanced_processing_parameters.pdf +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/B005_1.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/B005_2.tif +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/image1.png +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/image2.png +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/image3.png +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/images/image4.png +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/make.bat +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/requirements.txt +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/api_reference.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/developers.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/faq.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.filters._gaussian_kernel.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.filters.gaussian.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.filters.replace_outliers.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.lib.replace_nans.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.lib.sincinterp.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.preprocess.dynamic_masking.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.CorrelationFunction.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.correlate_windows.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.extended_search_area_piv.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.get_coordinates.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.get_field_shape.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.process.normalize_intensity.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.correlate_windows.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.find_first_peak.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.find_second_peak.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.find_subpixel_peak_position.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.get_coordinates.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.get_field_shape.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.moving_window_array.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.normalize_intensity.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.pyprocess.piv.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.scaling.uniform.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.Multiprocesser.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.display.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.display_vector_field.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.imread.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.tools.save.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.validation.global_std.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.validation.global_val.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.validation.local_median_val.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/generated/openpiv.validation.sig2noise_val.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/gui_doc.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/introduction.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/masking.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/modules.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/openpiv.rst +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/openpiv_pivuq.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/piv_basics.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/tutorial1.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/docs/src/windef.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/filters.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/lib.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/phase_separation.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/preprocess.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/preprocess.py,cover +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/pyprocess3D.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/scaling.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/smoothn.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/OpenPIV_results_16_test1/field_A0000.png +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/__init__.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/conftest.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/extended_search_area_vectorized.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/moon.png +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_PIV_3D_plotting.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_filters.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_filters.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_find_peaks.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_lib.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_package_metadata.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_performance.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_piv.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_preprocess.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_process.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_process.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_pyprocess.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_pyprocess3D.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_rust_backend.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_rust_subpixel.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_rust_windef.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_scaling.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_smoothn.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools.png +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_background.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_basic_utils.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_image_processing.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_multiprocessing.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_tools_vector_field.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_validation.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_validation.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_vectorized_extended_search.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef_coverage.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef_detailed.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/test/test_windef_final.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tools.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/Example for overlap setting change.ipynb +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/masking_tutorial.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/tutorial1.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/tutorial2.py +0 -0
- {openpiv-0.26.0 → openpiv-0.26.1}/openpiv/tutorials/windef_tutorial.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
settings = windef.PIVSettings()
|
|
176
|
+
settings.windowsizes = (64, 32, 16)
|
|
177
|
+
settings.overlap = (32, 16, 8)
|
|
178
|
+
settings.num_iterations = 3
|
|
106
179
|
|
|
107
|
-
|
|
180
|
+
# Choose backend in PIVSettings: 'auto', 'rust', or 'scipy'
|
|
181
|
+
settings.backend = "auto"
|
|
108
182
|
|
|
109
|
-
|
|
183
|
+
frame_a = tools.imread("exp1_001_a.bmp")
|
|
184
|
+
frame_b = tools.imread("exp1_001_b.bmp")
|
|
110
185
|
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
+
settings = windef.PIVSettings()
|
|
139
|
+
settings.windowsizes = (64, 32, 16)
|
|
140
|
+
settings.overlap = (32, 16, 8)
|
|
141
|
+
settings.num_iterations = 3
|
|
69
142
|
|
|
70
|
-
|
|
143
|
+
# Choose backend in PIVSettings: 'auto', 'rust', or 'scipy'
|
|
144
|
+
settings.backend = "auto"
|
|
71
145
|
|
|
72
|
-
|
|
146
|
+
frame_a = tools.imread("exp1_001_a.bmp")
|
|
147
|
+
frame_b = tools.imread("exp1_001_b.bmp")
|
|
73
148
|
|
|
74
|
-
|
|
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('
|
|
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.
|
|
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()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
=========
|