diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 8c4130f..90e0185 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -19,7 +19,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 with: lfs: true - uses: actions/setup-python@v6 @@ -36,8 +36,8 @@ jobs: - name: Install dependencies run: pip install -e .[docs] - name: Build GitHub Pages site - run: scripts/build_gh_pages.sh - - uses: actions/upload-pages-artifact@v3 + run: EXECUTE=1 scripts/build_gh_pages.sh + - uses: actions/upload-pages-artifact@v5 with: path: _site @@ -49,4 +49,4 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} steps: - id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/pr-agent.yml b/.github/workflows/pr-agent.yml index ab5f2cc..d357fb4 100644 --- a/.github/workflows/pr-agent.yml +++ b/.github/workflows/pr-agent.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: PR Agent action step - uses: the-pr-agent/pr-agent@v0.36.1 + uses: the-pr-agent/pr-agent@v0.38.0 env: OPENAI_KEY: ${{ secrets.OPENAI_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cf59f60..626a61d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,7 +8,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -18,7 +18,7 @@ jobs: needs: [lint] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 with: lfs: true - uses: actions/setup-python@v6 @@ -41,7 +41,7 @@ jobs: python -m pip install build python -m build - name: Store the distribution packages - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: python-package-distributions path: dist/ @@ -57,7 +57,7 @@ jobs: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 79a7c55..8399423 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -11,7 +11,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -25,7 +25,7 @@ jobs: matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 with: lfs: true - uses: actions/setup-python@v6 @@ -41,7 +41,7 @@ jobs: run: pip install -e ".[test]" - name: Test with pytest run: pytest - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 if: ${{ matrix.python-version == '3.12' }} with: name: coverage-report diff --git a/.gitignore b/.gitignore index 7a6a429..f120624 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ __pycache__ /dist/ /*.egg-info /_site/ +.idea +docs/*.html diff --git a/Alignment v2.ipynb b/Alignment v2.ipynb index 93d18d9..7bfeb7e 100644 --- a/Alignment v2.ipynb +++ b/Alignment v2.ipynb @@ -697,9 +697,9 @@ "# Compute and display a histogram\n", "# ndvi_hist_min = np.min(ndvi)\n", "# ndvi_hist_max = np.max(ndvi)\n", - "ndvi_hist_min = np.min(np.percentile(ndvi, 0.5))\n", - "ndvi_hist_max = np.max(np.percentile(ndvi, 99.5))\n", - "fig, axis = plt.subplots(1, 1, figsize=(10, 4))\n", + "ndvi_hist_min = np.min(np.percentile(ndvi.compressed(), 0.5))\n", + "ndvi_hist_max = np.max(np.percentile(ndvi.compressed(), 99.5))\n", + "fig, axis = plt.subplots(1, 1, figsize=(10,4))\n", "axis.hist(ndvi.ravel(), bins=512, range=(ndvi_hist_min, ndvi_hist_max))\n", "plt.title(\"NDVI Histogram\")\n", "plt.show()\n", @@ -707,7 +707,7 @@ "min_display_ndvi = 0.45 # further mask soil by removing low-ndvi values\n", "# min_display_ndvi = np.percentile(ndvi.flatten(), 5.0) # modify with these percentilse to adjust contrast\n", "max_display_ndvi = np.percentile(\n", - " ndvi.flatten(), 99.5\n", + " ndvi.compressed(), 99.5\n", ") # for many images, 0.5 and 99.5 are good values\n", "masked_ndvi = np.ma.masked_where(ndvi < min_display_ndvi, ndvi)\n", "\n", @@ -752,15 +752,15 @@ "masked_ndre = np.ma.masked_where(ndvi < min_display_ndvi, ndre)\n", "\n", "# Compute a histogram\n", - "ndre_hist_min = np.min(np.percentile(masked_ndre, 0.5))\n", - "ndre_hist_max = np.max(np.percentile(masked_ndre, 99.5))\n", - "fig, axis = plt.subplots(1, 1, figsize=(10, 4))\n", + "ndre_hist_min = np.min(np.percentile(masked_ndre.compressed(), 0.5))\n", + "ndre_hist_max = np.max(np.percentile(masked_ndre.compressed(), 99.5))\n", + "fig, axis = plt.subplots(1, 1, figsize=(10,4))\n", "axis.hist(masked_ndre.ravel(), bins=512, range=(ndre_hist_min, ndre_hist_max))\n", "plt.title(\"NDRE Histogram (filtered to only plants)\")\n", "plt.show()\n", "\n", - "min_display_ndre = np.percentile(masked_ndre, 5)\n", - "max_display_ndre = np.percentile(masked_ndre, 99.5)\n", + "min_display_ndre = np.percentile(masked_ndre.compressed(), 5)\n", + "max_display_ndre = np.percentile(masked_ndre.compressed(), 99.5)\n", "\n", "fig, axis = plotutils.plot_overlay_withcolorbar(\n", " gamma_corr_rgb,\n", diff --git a/Alignment-10Band.ipynb b/Alignment-10Band.ipynb index ef22176..2fa43d6 100644 --- a/Alignment-10Band.ipynb +++ b/Alignment-10Band.ipynb @@ -403,9 +403,9 @@ "plt.show()\n", "\n", "min_display_ndvi = 0.45 # further mask soil by removing low-ndvi values\n", - "# min_display_ndvi = np.percentile(ndvi.flatten(), 5.0) # modify with these percentilse to adjust contrast\n", + "# min_display_ndvi = np.percentile(ndvi.compressed(), 5.0) # modify with these percentilse to adjust contrast\n", "max_display_ndvi = np.percentile(\n", - " ndvi.flatten(), 99.5\n", + " ndvi.compressed(), 99.5\n", ") # for many images, 0.5 and 99.5 are good values\n", "masked_ndvi = np.ma.masked_where(ndvi < min_display_ndvi, ndvi)\n", "\n", @@ -456,8 +456,8 @@ "plt.title(\"NDRE Histogram (filtered to only plants)\")\n", "plt.show()\n", "\n", - "min_display_ndre = np.percentile(masked_ndre, 5)\n", - "max_display_ndre = np.percentile(masked_ndre, 99.5)\n", + "min_display_ndre = np.percentile(masked_ndre.compressed(), 5)\n", + "max_display_ndre = np.percentile(masked_ndre.compressed(), 99.5)\n", "\n", "fig, axis = plotutils.plot_overlay_withcolorbar(\n", " gamma_corr_rgb,\n", diff --git a/Alignment-RigRelatives.ipynb b/Alignment-RigRelatives.ipynb index f775312..02db654 100644 --- a/Alignment-RigRelatives.ipynb +++ b/Alignment-RigRelatives.ipynb @@ -372,9 +372,9 @@ "plt.show()\n", "\n", "min_display_ndvi = 0.45 # further mask soil by removing low-ndvi values\n", - "# min_display_ndvi = np.percentile(ndvi.flatten(), 5.0) # modify with these percentilse to adjust contrast\n", + "# min_display_ndvi = np.percentile(ndvi.compressed(), 5.0) # modify with these percentilse to adjust contrast\n", "max_display_ndvi = np.percentile(\n", - " ndvi.flatten(), 99.5\n", + " ndvi.compressed(), 99.5\n", ") # for many images, 0.5 and 99.5 are good values\n", "masked_ndvi = np.ma.masked_where(ndvi < min_display_ndvi, ndvi)\n", "\n", @@ -426,8 +426,8 @@ "plt.title(\"NDRE Histogram (filtered to only plants)\")\n", "plt.show()\n", "\n", - "min_display_ndre = np.percentile(masked_ndre, 5)\n", - "max_display_ndre = np.percentile(masked_ndre, 99.5)\n", + "min_display_ndre = np.percentile(masked_ndre.compressed(), 5)\n", + "max_display_ndre = np.percentile(masked_ndre.compressed(), 99.5)\n", "\n", "fig, axis = plotutils.plot_overlay_withcolorbar(\n", " gamma_corr_rgb,\n", diff --git a/Alignment.ipynb b/Alignment.ipynb index 82b2dc7..35aa509 100644 --- a/Alignment.ipynb +++ b/Alignment.ipynb @@ -422,9 +422,9 @@ "plt.show()\n", "\n", "min_display_ndvi = 0.45 # further mask soil by removing low-ndvi values\n", - "# min_display_ndvi = np.percentile(ndvi.flatten(), 5.0) # modify with these percentilse to adjust contrast\n", + "# min_display_ndvi = np.percentile(ndvi.compressed(), 5.0) # modify with these percentilse to adjust contrast\n", "max_display_ndvi = np.percentile(\n", - " ndvi.flatten(), 99.5\n", + " ndvi.compressed(), 99.5\n", ") # for many images, 0.5 and 99.5 are good values\n", "masked_ndvi = np.ma.masked_where(ndvi < min_display_ndvi, ndvi)\n", "\n", @@ -475,8 +475,8 @@ "plt.title(\"NDRE Histogram (filtered to only plants)\")\n", "plt.show()\n", "\n", - "min_display_ndre = np.percentile(masked_ndre, 5)\n", - "max_display_ndre = np.percentile(masked_ndre, 99.5)\n", + "min_display_ndre = np.percentile(masked_ndre.compressed(), 5)\n", + "max_display_ndre = np.percentile(masked_ndre.compressed(), 99.5)\n", "\n", "fig, axis = plotutils.plot_overlay_withcolorbar(\n", " gamma_corr_rgb,\n", diff --git a/docs/Alignment v2.html b/docs/Alignment v2.html deleted file mode 100644 index 004c9a5..0000000 --- a/docs/Alignment v2.html +++ /dev/null @@ -1,16037 +0,0 @@ - - - - - -Alignment v2 - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
- - - - - - - - - diff --git a/docs/Alignment-10Band.html b/docs/Alignment-10Band.html deleted file mode 100644 index a3bc4c2..0000000 --- a/docs/Alignment-10Band.html +++ /dev/null @@ -1,15702 +0,0 @@ - - - - - -Alignment-10Band - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
- - - - - - - - - diff --git a/docs/Alignment-RigRelatives.html b/docs/Alignment-RigRelatives.html deleted file mode 100644 index 08e88b1..0000000 --- a/docs/Alignment-RigRelatives.html +++ /dev/null @@ -1,15397 +0,0 @@ - - - - - -Alignment-RigRelatives - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
- - - - - - - - - diff --git a/docs/Alignment.html b/docs/Alignment.html deleted file mode 100644 index 3b0e6cb..0000000 --- a/docs/Alignment.html +++ /dev/null @@ -1,15551 +0,0 @@ - - - - - -Alignment - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
- - - - - - - - - diff --git a/docs/Batch Processing v2.html b/docs/Batch Processing v2.html deleted file mode 100644 index 50d08e4..0000000 --- a/docs/Batch Processing v2.html +++ /dev/null @@ -1,15707 +0,0 @@ - - - - - -Batch Processing v2 - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
- - -
- -
- - - - - - - - - - - diff --git a/docs/Batch Processing.html b/docs/Batch Processing.html deleted file mode 100644 index c666e3f..0000000 --- a/docs/Batch Processing.html +++ /dev/null @@ -1,15747 +0,0 @@ - - - - - -Batch Processing - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
- - -
- -
- - - - - -
- -
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
- - -
- -
- - - - - - - - - diff --git a/docs/Captures.html b/docs/Captures.html deleted file mode 100644 index f704bb6..0000000 --- a/docs/Captures.html +++ /dev/null @@ -1,14760 +0,0 @@ - - - - - -Captures - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
- - - - - - - - - diff --git a/docs/ImageSets.html b/docs/ImageSets.html deleted file mode 100644 index d6e8c47..0000000 --- a/docs/ImageSets.html +++ /dev/null @@ -1,15632 +0,0 @@ - - - - - -ImageSets - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
- - - - - - - - - diff --git a/docs/Images.html b/docs/Images.html deleted file mode 100644 index b1db7c8..0000000 --- a/docs/Images.html +++ /dev/null @@ -1,14746 +0,0 @@ - - - - - -Images - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
-
- - -
-
- - - - - - - - - diff --git a/docs/MicaSense Image Processing Setup.html b/docs/MicaSense Image Processing Setup.html deleted file mode 100644 index ac5a8a6..0000000 --- a/docs/MicaSense Image Processing Setup.html +++ /dev/null @@ -1,14901 +0,0 @@ - - - - - -MicaSense Image Processing Setup - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
- - - - - - - - - diff --git a/docs/MicaSense Image Processing Tutorial 1.html b/docs/MicaSense Image Processing Tutorial 1.html deleted file mode 100644 index e914e22..0000000 --- a/docs/MicaSense Image Processing Tutorial 1.html +++ /dev/null @@ -1,15372 +0,0 @@ - - - - - -MicaSense Image Processing Tutorial 1 - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
-
- - -
-
- - - - - - - - - diff --git a/docs/MicaSense Image Processing Tutorial 2.html b/docs/MicaSense Image Processing Tutorial 2.html deleted file mode 100644 index f5b9937..0000000 --- a/docs/MicaSense Image Processing Tutorial 2.html +++ /dev/null @@ -1,15115 +0,0 @@ - - - - - -MicaSense Image Processing Tutorial 2 - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
-
- - -
-
- - - - - - - - - diff --git a/docs/MicaSense Image Processing Tutorial 3.html b/docs/MicaSense Image Processing Tutorial 3.html deleted file mode 100644 index 502fca0..0000000 --- a/docs/MicaSense Image Processing Tutorial 3.html +++ /dev/null @@ -1,14927 +0,0 @@ - - - - - -MicaSense Image Processing Tutorial 3 - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
-
-
- - -
-
- - - - - - - - - diff --git a/docs/Panels.html b/docs/Panels.html deleted file mode 100644 index d3aee74..0000000 --- a/docs/Panels.html +++ /dev/null @@ -1,14797 +0,0 @@ - - - - - -Panels - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
- - -
- -
-
- - -
- -
- - - - - -
- -
-
- - -
- -
- - - - - -
- -
-
-
- - -
-
- - - - - - - - - diff --git a/micasense/capture.py b/micasense/capture.py index 88e85e7..a311996 100644 --- a/micasense/capture.py +++ b/micasense/capture.py @@ -343,7 +343,7 @@ def compute_undistorted_reflectance( ): """ Compute undistorted image reflectance from irradiance list. - :param irradiance_list: List returned from Capture.dls_irradiance() or Capture.panel_irradiance() TODO: improve this docstring + :param irradiance_list: List of mean irradiance values for each band, e.g., from Capture.dls_irradiance() or Capture.panel_irradiance(). :param force_recompute: boolean to determine if reflectance is recomputed. :return: None """ @@ -386,7 +386,7 @@ def lw_indices(self): def reflectance(self, irradiance_list): """ Compute reflectance Images. - :param irradiance_list: List returned from Capture.dls_irradiance() or Capture.panel_irradiance() TODO: improve this docstring + :param irradiance_list: List of mean irradiance values for each band, e.g., from Capture.dls_irradiance() or Capture.panel_irradiance(). :return: List of reflectance EO and long wave infrared Images for given irradiance. """ eo_imgs = [ @@ -399,7 +399,7 @@ def reflectance(self, irradiance_list): def undistorted_reflectance(self, irradiance_list): """ Compute undistorted reflectance Images. - :param irradiance_list: List returned from Capture.dls_irradiance() or Capture.panel_irradiance() TODO: improve this docstring + :param irradiance_list: List of mean irradiance values for each band, e.g., from Capture.dls_irradiance() or Capture.panel_irradiance(). :return: List of undistorted reflectance images for given irradiance. """ eo_imgs = [ @@ -517,7 +517,7 @@ def plot_panels(self): def set_external_rig_relatives(self, external_rig_relatives): """ Set external rig relatives. - :param external_rig_relatives: TODO: Write this parameter docstring + :param external_rig_relatives: Dictionary of external rig relative coordinates keyed by string of band index. :return: None """ for i, img in enumerate(self.images): @@ -557,7 +557,6 @@ def create_aligned_capture( Creates aligned Capture. Computes undistorted radiance or reflectance images if necessary. :param irradiance_list: List of mean panel region irradiance. :param warp_matrices: 2d List of warp matrices derived from Capture.get_warp_matrices() - :param normalize: FIXME: This parameter isn't used? :param img_type: str 'radiance' or 'reflectance' depending on image metadata. :param motion_type: OpenCV import. Also know as warp_mode. MOTION_HOMOGRAPHY or MOTION_AFFINE. For Altum images only use HOMOGRAPHY. @@ -1281,12 +1280,12 @@ def SIFT_align_capture( # most of the time this will occur for the thermal image, as we have a hard time # finding a good matches between panchro & thermal in most cases else: - P = ProjectiveTransform(matrix=warp_matrices_calibrated[ix]) logger.warning( "Insufficient SIFT matches for band index %s (%s); using calibrated warp matrix.", ix, self.images[ix].band_name, ) + P = ProjectiveTransform(matrix=warp_matrices_calibrated[ix]) models.append(P) if verbose > 0: logger.info("Finished aligning band %d", ix) diff --git a/micasense/image.py b/micasense/image.py index 0ff8fbe..8480e5a 100644 --- a/micasense/image.py +++ b/micasense/image.py @@ -244,19 +244,30 @@ def __ne__(self, other): def raw(self): """Lazy load the raw image once necessary""" if self.__raw_image is None: + raw_image = None try: import rawpy # to support 12-bit DNG files, otherwise we get "SIFT found no features" error + raw_image = rawpy.imread(self.path).raw_image if self.bits_per_pixel == 12: - self.__raw_image = rawpy.imread(self.path).raw_image * 16 - else: - self.__raw_image = rawpy.imread(self.path).raw_image + raw_image = raw_image * 16 except ImportError: - self.__raw_image = cv2.imread(self.path, -1) + pass except IOError: logger.error("Could not open image at path %s", self.path) raise + except Exception as exc: + if exc.__class__.__name__ != "LibRawFileUnsupportedError": + raise + + if raw_image is None: + raw_image = cv2.imread(self.path, -1) + if raw_image is None: + logger.error("Could not open image at path %s", self.path) + raise IOError("Could not open image at path {}".format(self.path)) + + self.__raw_image = raw_image return self.__raw_image def set_raw(self, img): diff --git a/micasense/imageutils.py b/micasense/imageutils.py index 6229856..5799d8e 100644 --- a/micasense/imageutils.py +++ b/micasense/imageutils.py @@ -33,7 +33,6 @@ from skimage.morphology import closing from skimage.morphology import disk from skimage.transform import warp -from skimage.util import img_as_ubyte logger = logging.getLogger(__name__) @@ -136,9 +135,7 @@ def normalize(im, local_min=None, local_max=None): def local_normalize(im): - norm = img_as_ubyte( - normalize(im) - ) # TODO: mainly using this as a type conversion, but it's expensive + norm = (normalize(im) * 255).astype(np.uint8) width, _ = im.shape disksize = int(width / 5) if disksize % 2 == 0: @@ -204,11 +201,11 @@ def align(pair): # Initialize the matrix to identity if warp_mode == cv2.MOTION_HOMOGRAPHY: # warp_matrix = np.array([[1,0,0],[0,1,0],[0,0,1]], dtype=float) - warp_matrix = pair["warp_matrix_init"] + warp_matrix = np.array(pair["warp_matrix_init"], dtype=np.float32) else: # warp_matrix = np.array([[1,0,0],[0,1,0]], dtype=float) warp_matrix = np.array( - [[1, 0, translations[1]], [0, 1, translations[0]]], dtype=float + [[1, 0, translations[1]], [0, 1, translations[0]]], dtype=np.float32 ) w = pair["ref_image"].shape[1] @@ -294,6 +291,10 @@ def align(pair): cc, warp_matrix = cv2.findTransformECC( grad1, grad2, warp_matrix, warp_mode, criteria ) + except cv2.error as e: + logger.warning( + "ECC failed to converge at pyramid level %s: %s", level, e + ) if show_debug_images: logger.info("Warp after alignment level %s is \n%s", level, warp_matrix) diff --git a/micasense/panel.py b/micasense/panel.py index 337c4ff..e2d45bb 100644 --- a/micasense/panel.py +++ b/micasense/panel.py @@ -199,8 +199,8 @@ def panel_corners(self): for rotation in range(0, 4): qr_points = np.roll(reference_qr_pts, rotation, axis=0) - src = np.asarray([tuple(row) for row in qr_points[:]], float) - dst = np.asarray([tuple(row) for row in self.qr_corners()[:]], float) + src = np.asarray(qr_points, dtype=np.float32) + dst = np.asarray(self.qr_corners(), dtype=np.float32) # we determine the homography from the 4 corner points warp_matrix = cv2.getPerspectiveTransform(src, dst) diff --git a/scripts/build_gh_pages.sh b/scripts/build_gh_pages.sh index bc9edf8..1af2815 100755 --- a/scripts/build_gh_pages.sh +++ b/scripts/build_gh_pages.sh @@ -15,7 +15,7 @@ nbconvert_args=( if [[ "${EXECUTE}" == "1" ]]; then nbconvert_args+=( - --ExecutePreprocessor.timeout=60 + --ExecutePreprocessor.timeout=600 --execute ) fi diff --git a/tests/test_panel_corners_examples.py b/tests/test_panel_corners_examples.py new file mode 100644 index 0000000..0b6759a --- /dev/null +++ b/tests/test_panel_corners_examples.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# coding: utf-8 +"""Smoke tests for Panel.panel_corners() on bundled example imagery. + +The regression in the latest commit was caused by OpenCV being strict about the +input dtype to cv2.getPerspectiveTransform. This test exercises panel_corners() +end-to-end on the repository's example panel images to ensure it runs without +raising and returns a valid 4-corner polygon. +""" + +from __future__ import annotations + +import glob +from pathlib import Path + +import pytest + +import micasense.image as image +import micasense.panel as panel + + +def _example_panel_images() -> list[str]: + repo_root = Path(__file__).parent.parent + + patterns = [ + repo_root / "data" / "REDEDGE-MX" / "IMG_0001_*.tif", + repo_root / "data" / "REDEDGE-P" / "IMG_0000_*.tif", + repo_root / "data" / "REDEDGE-MX-DUAL" / "IMG_0000_*.tif", + ] + + files: list[str] = [] + for pat in patterns: + files.extend(sorted(glob.glob(str(pat)))) + + # Keep only images which are likely to contain a panel. If the list is empty, + # don't hard fail locally; CI/packaging should include the sample imagery. + return files + + +@pytest.mark.parametrize("image_path", _example_panel_images()) +def test_panel_corners_runs_on_example_panels(image_path: str): + img = image.Image(image_path) + pan = panel.Panel(img) + + corners = pan.panel_corners() + + assert corners is not None, f"panel_corners() returned None for {image_path}" + assert len(corners) == 4, f"expected 4 corners for {image_path}, got {len(corners)}" + + +def test_panel_corners_examples_present(): + """Fail with a clear message if sample data is missing.""" + files = _example_panel_images() + assert files, ( + "No example panel images found under data/. Did you fetch LFS/sample data?" + )