diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 0000000..387b2da --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,54 @@ +name: Upload FF-PIV to Test-PyPi + +on: + workflow_dispatch: + +jobs: + build-artifacts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + pip install tomli flit twine + - name: Build artifacts + run: flit build + - uses: actions/upload-artifact@v3 + with: + name: releases + path: dist + test-built-dist: + needs: build-artifacts + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + steps: + - uses: actions/checkout@v4 + with: + python-version: '3.11' + - name: Verify the built dist/wheel is valid + run: | + python -m pip install dist/ffpiv*.whl + python -c "import ffpiv;print(ffpiv.__version__)" + upload-to-test-pypi: + needs: test-built-dist + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: releases + path: dist + - name: Publish package to TestPyPI + uses: pypa/gh-action-pypi-publish@v1.5.1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} + verbose: true + skip_existing: true diff --git a/.github/workflows/publish-test-pypi.yml b/.github/workflows/publish-test-pypi.yml new file mode 100644 index 0000000..b443c9a --- /dev/null +++ b/.github/workflows/publish-test-pypi.yml @@ -0,0 +1,58 @@ +name: Upload FF-PIV to Test-PyPi + +on: + release: + types: [published] + # allows running the workflow manually + workflow_dispatch: + +jobs: + build-artifacts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + pip install tomli flit twine + - name: Build artifacts + run: flit build + - uses: actions/upload-artifact@v3 + with: + name: releases + path: dist + test-built-dist: + needs: build-artifacts + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + steps: + - uses: actions/checkout@v4 + with: + python-version: '3.11' + - name: Verify the built dist/wheel is valid + run: | + python -m pip install dist/ffpiv*.whl + python -c "import ffpiv;print(ffpiv.__version__)" + upload-to-test-pypi: + needs: test-built-dist + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: releases + path: dist + - name: Publish package to TestPyPI + uses: pypa/gh-action-pypi-publish@v1.5.1 + with: + user: __token__ + password: ${{ secrets.PYPI_TEST_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + verbose: true + skip_existing: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1114c5c..9c5f929 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest" ] #, "macos-latest", "windows-latest"] - python-version: ["3.10"] # fix tests to support older versions + python-version: ["3.11"] # fix tests to support older versions steps: - uses: actions/checkout@v4 @@ -47,7 +47,7 @@ jobs: # run all tests - name: Test - run: python -m pytest --verbose --cov=pyorc --cov-report xml + run: python -m pytest --verbose --cov=ffpiv --cov-report xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 diff --git a/README.md b/README.md index 28d75ab..899353f 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,17 @@ image2 = np.array(Image.open(file_frame2)) u, v = piv(image1, image2) # Plot the velocity field -plt.quiver(u, v) -plt.xlabel("x [window]") -plt.ylabel("y [window]") -plt.title("64x64 one image pair") +ax = plt.axes() +ax.quiver(u, v) +ax.invert_yaxis() # make sure that the coordinate order is according to real-world + +ax.set_xlabel("x [window]") +ax.set_ylabel("y [window]") +ax.set_title("64x64 one image pair") plt.show() ``` +![piv_1_img](https://github.com/user-attachments/assets/2020e5c0-aca2-4f3d-8813-5bdcf5ec6841) + In this example: - We first load two consecutive images from the sample dataset - We call the `piv` function, passing the images. @@ -128,13 +133,18 @@ overlap = (32, 32) u, v = piv(image1, image2, window_size=window_size, overlap=overlap) # Plot velocity field -plt.quiver(u, v) -plt.xlabel("x [window]") -plt.ylabel("y [window]") -plt.title("64x64, 32x32 overlap one image pair") +ax = plt.axes() +ax.quiver(u, v) +ax.invert_yaxis() + +ax.set_xlabel("x [window]") +ax.set_ylabel("y [window]") +ax.set_title("64x64, 32x32 overlap one image pair") plt.show() ``` +![piv_1_img_overlap](https://github.com/user-attachments/assets/95a95102-f162-411a-b472-6142a21e84c3) + Here we specify the `window_size` and `overlap` parameters. Now, cross correlation is computed on pixel patches of 64 by 64 pixels, and overlap of 32 pixels in both directions is used to extract windows. This results in twice as many velocity vectors as shown in the resulting plot @@ -171,13 +181,19 @@ plt.figure(figsize=(10, 5)) for i, (u, v) in enumerate(zip(u_stack[0:2], v_stack[0:2])): # Display the first image of the pair - plt.subplot(1, 2, i + 1) - plt.quiver(u, v) - plt.title(f'Image pair {i+1}') + ax = plt.subplot(1, 2, i + 1) + ax.quiver(u, v) + ax.set_title(f'Image pair {i+1}') + ax.invert_yaxis() + ax.set_xlabel("x [window]") + ax.set_ylabel("y [window]") + plt.suptitle("2 image pairs") plt.show() ``` +![piv_2_img](https://github.com/user-attachments/assets/46cd091f-a974-41c4-98a1-382a45687e09) + In this example: - We first load a stack of images into a full array. You may alter last_image to a max of 122 to check how fast this is. @@ -205,23 +221,29 @@ x, y = coords(dim_size, window_size=window_size, overlap=overlap) # window_size ax = plt.axes() pix_y = np.arange(im_sample.shape[0]) pix_x = np.arange(im_sample.shape[1]) -ax.pcolor(pix_x, pix_y, im_sample, cmap="Greys_r") +ax.pcolor(pix_x, pix_y, im_sample, vmax=512, cmap="Greys_r") # plot a bit dark so that we see the quiver plot # compute the time averaged velocities u, v = u_stack.mean(axis=0), v_stack.mean(axis=0) s = np.sqrt(u**2 + v**2) # plot the vectors on top of this p = ax.quiver(x, y, u, v, s, cmap="rainbow") -cb = plt.colorbar(p) + +# make an inset axis for the colorbar +cax = ax.inset_axes([0.8, 0.1, 0.02, 0.4]) +cb = plt.colorbar(p, cax=cax) cb.set_label(label="velocity [pix/frame]") -plt.title("frame + average velocities") ax.set_aspect('equal', adjustable='box') # ensure x and y coordinates have same visual length +ax.set_xlabel("x [pix]") +ax.set_ylabel("y [pix]") +ax.set_title("frame + average velocities") plt.show() - ``` +![im_piv](https://github.com/user-attachments/assets/bd30791e-6a3c-41fe-8ba9-f4e92d2b554e) + In this example, you can ensure the coordinates are commensurate with the original data and plot the coordinates on -top of your original data. +top of your original data. The plot axis are now in pixel units instead of window units. ### Work with intermediate results @@ -280,27 +302,38 @@ corr_max = corr_max.reshape(-1, n_rows, n_cols) _, axs = plt.subplots(nrows=1, ncols=3, figsize=(16, 9)) p0 = axs[0].pcolor(x, y, corr_max.mean(axis=0)) axs[0].set_title("Image mean maximum correlation") -plt.colorbar(p0, ax=axs[0]) +cax = axs[0].inset_axes([0.8, 0.1, 0.02, 0.4]) +cb = plt.colorbar(p0, cax=cax) +cb.set_label(label="log s2n [-]") -p1 = axs[1].pcolor(x, y, np.log(s2n.mean(axis=0))) + +p1 = axs[1].pcolor(x, y, np.log(s2n.mean(axis=0)), cmap="Blues") axs[1].set_title("Image mean signal-to-noise ratio") -plt.colorbar(p1, ax=axs[1]) +cax = axs[1].inset_axes([0.8, 0.1, 0.02, 0.4]) +cb = plt.colorbar(p1, cax=cax) +cb.set_label(label="log s2n [-]") + -axs[2].pcolor(pix_x, pix_y, im_sample, cmap="Greys_r") +axs[2].pcolor(pix_x, pix_y, im_sample, vmax=512, cmap="Greys_r") s = np.sqrt(u**2 + v**2) + # plot the vectors on top of this -p = axs[2].quiver(x, y, u, v, s, cmap="rainbow", scale_units="xy", scale=0.3) -cb = plt.colorbar(p, ax=axs[2]) +p2 = axs[2].quiver(x, y, u, v, s, cmap="rainbow", scale_units="xy", scale=0.3) +cax = axs[2].inset_axes([0.8, 0.1, 0.02, 0.4]) +cb = plt.colorbar(p2, cax=cax) cb.set_label(label="velocity [pix/frame]") axs[2].set_title("frame + velocity") # set all axes to equal sizing for ax in axs: ax.set_aspect('equal', adjustable='box') ax.invert_yaxis() + ax.set_xlabel("x [pix]") + ax.set_ylabel("y [pix]") plt.show() ``` -![corr_piv](https://github.com/user-attachments/assets/bd2254d4-9a2d-4721-b532-1b1eed760c66) + +![corr_piv](https://github.com/user-attachments/assets/9827a5f8-6909-4bd2-84ad-91d234edb5cc) In this example, we first calculate the cross correlations and do not reduce them into vectors yet. We use all images, to demonstrate that FF-PIV is truly fast! You need enough free memory for this. diff --git a/tests/test_fft.py b/tests/test_fft.py index 09977e5..4206f7c 100644 --- a/tests/test_fft.py +++ b/tests/test_fft.py @@ -32,9 +32,6 @@ def correlations(img_pair): return corrs * np.random.rand(*corrs.shape) * 0.005 -# def test_normalize_intensity(imgs_win): - - def test_ncc(img_pair): """Test correlation analysis on a pair of image windows.""" image_a, image_b = img_pair @@ -48,7 +45,7 @@ def test_ncc(img_pair): t2 = time.time() time_np = t2 - t1 print(f"Numpy took {time_np} secs.") - assert np.allclose(res_nb, res_np) + assert np.allclose(res_nb, res_np, atol=1e-6, rtol=1e-5) # plt.imshow(res_nb[0]) # plt.colorbar() # plt.show() @@ -68,7 +65,7 @@ def test_multi_img_ncc(imgs_win_stack, mask): t2 = time.time() time_nb = t2 - t1 print(f"Numpy took {time_nb} secs.") - assert np.allclose(res_nb, res_np) + assert np.allclose(res_nb, res_np, atol=1e-6, rtol=1e-5) def test_u_v_displacement(correlations, dims):