From ca37d04e7d9a914a1a5e7e4b86b2a9c3e43e9b42 Mon Sep 17 00:00:00 2001 From: janezlapajne Date: Wed, 18 Sep 2024 15:06:34 +0200 Subject: [PATCH 1/3] docs: Add core.exceptions API documentation and update mkdocs.yml --- docs/api/core/exceptions.md | 2 ++ mkdocs.yml | 1 + 2 files changed, 3 insertions(+) create mode 100644 docs/api/core/exceptions.md diff --git a/docs/api/core/exceptions.md b/docs/api/core/exceptions.md new file mode 100644 index 0000000..b131531 --- /dev/null +++ b/docs/api/core/exceptions.md @@ -0,0 +1,2 @@ + +::: siapy.core.exceptions diff --git a/mkdocs.yml b/mkdocs.yml index 7c9d150..6f89bb5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,6 +66,7 @@ nav: - API Documentation: - Core: - Configs: api/core/configs.md + - Exceptions: api/core/exceptions.md - Logger: api/core/logger.md - Types: api/core/types.md - Datasets: From 12ffa05a19053dc220c5dbac6c351d2c116716ee Mon Sep 17 00:00:00 2001 From: janezlapajne Date: Wed, 18 Sep 2024 16:03:43 +0200 Subject: [PATCH 2/3] fix: Refactor, add and correct type annotations --- siapy/core/types.py | 2 +- siapy/entities/images.py | 6 ++++-- siapy/entities/pixels.py | 3 ++- siapy/features/features.py | 20 ++++++++++++++++++-- siapy/features/helpers.py | 4 ++-- siapy/transformations/corregistrator.py | 8 +++++--- siapy/utils/images.py | 2 +- 7 files changed, 33 insertions(+), 12 deletions(-) diff --git a/siapy/core/types.py b/siapy/core/types.py index c87e8bf..da87947 100644 --- a/siapy/core/types.py +++ b/siapy/core/types.py @@ -19,7 +19,7 @@ SpectralType = sp.io.envi.BilFile | sp.io.envi.BipFile | sp.io.envi.BsqFile ImageType = SpectralImage | np.ndarray | Image -ImageSizeType = int | tuple[int, int] +ImageSizeType = int | tuple[int, ...] ImageDataType = ( np.uint8 | np.int16 diff --git a/siapy/entities/images.py b/siapy/entities/images.py index a39f559..9134662 100644 --- a/siapy/entities/images.py +++ b/siapy/entities/images.py @@ -2,7 +2,7 @@ import sys from dataclasses import dataclass from pathlib import Path -from typing import TYPE_CHECKING, Any, Iterable, Iterator +from typing import TYPE_CHECKING, Any, Iterable, Iterator, Sequence import numpy as np import spectral as sp @@ -265,7 +265,9 @@ def to_subarray(self, pixels: "Pixels") -> np.ndarray: image_arr_area[v_norm, u_norm, :] = image_arr[pixels.v(), pixels.u(), :] return image_arr_area - def mean(self, axis: int | tuple[int] | None = None) -> float | np.ndarray: + def mean( + self, axis: int | tuple[int, ...] | Sequence[int] | None = None + ) -> float | np.ndarray: image_arr = self.to_numpy() return np.nanmean(image_arr, axis=axis) diff --git a/siapy/entities/pixels.py b/siapy/entities/pixels.py index 04f3ca5..201e3ca 100644 --- a/siapy/entities/pixels.py +++ b/siapy/entities/pixels.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from pathlib import Path -from typing import Annotated, ClassVar, Iterable, NamedTuple +from typing import Annotated, ClassVar, Iterable, NamedTuple, Sequence import numpy as np import pandas as pd @@ -32,6 +32,7 @@ def from_iterable( Annotated[int, "u coordinate on the image"], Annotated[int, "v coordinate on the image"], ] + | Sequence[int] ], ) -> "Pixels": df = pd.DataFrame(iterable, columns=[Pixels.coords.U, Pixels.coords.V]) diff --git a/siapy/features/features.py b/siapy/features/features.py index e9bdb2c..e324a21 100644 --- a/siapy/features/features.py +++ b/siapy/features/features.py @@ -28,7 +28,15 @@ def __init__( feateng_steps: int = 2, featsel_runs: int = 5, max_gb: int | None = None, - transformations: list | tuple = ("1/", "exp", "log", "abs", "sqrt", "^2", "^3"), + transformations: list[str] | tuple[str, ...] = ( + "1/", + "exp", + "log", + "abs", + "sqrt", + "^2", + "^3", + ), apply_pi_theorem: bool = True, always_return_numpy: bool = False, n_jobs: int = 1, @@ -78,7 +86,15 @@ def __init__( feateng_steps: int = 2, featsel_runs: int = 5, max_gb: int | None = None, - transformations: list | tuple = ("1/", "exp", "log", "abs", "sqrt", "^2", "^3"), + transformations: list[str] | tuple[str, ...] = ( + "1/", + "exp", + "log", + "abs", + "sqrt", + "^2", + "^3", + ), apply_pi_theorem: bool = True, always_return_numpy: bool = False, n_jobs: int = 1, diff --git a/siapy/features/helpers.py b/siapy/features/helpers.py index 0294972..3eae7b8 100644 --- a/siapy/features/helpers.py +++ b/siapy/features/helpers.py @@ -16,7 +16,7 @@ class FeatureSelectorConfig(BaseModel): k_features: Annotated[ - int | tuple | str, + int | str | tuple[int, ...], "can be: 'best' - most extensive, (1, n) - check range of features, n - exact number of features", ] = (1, 20) cv: int = 3 @@ -38,7 +38,7 @@ def feature_selector_factory( problem_type: Literal["regression", "classification"], *, k_features: Annotated[ - int | tuple | str, + int | str | tuple[int, ...], "can be: 'best' - most extensive, (1, n) - check range of features, n - exact number of features", ] = (1, 20), cv: int = 3, diff --git a/siapy/transformations/corregistrator.py b/siapy/transformations/corregistrator.py index 4efad7b..f3ff610 100644 --- a/siapy/transformations/corregistrator.py +++ b/siapy/transformations/corregistrator.py @@ -1,3 +1,5 @@ +from typing import Sequence + import matplotlib.pyplot as plt import numpy as np @@ -23,10 +25,10 @@ def map_affine_approx_2d(points_ref: np.ndarray, points_mov: np.ndarray) -> np.n def affine_matx_2d( - scale: tuple[float, float] = (1, 1), - trans: tuple[float, float] = (0, 0), + scale: tuple[float, float] | Sequence[float] = (1, 1), + trans: tuple[float, float] | Sequence[float] = (0, 0), rot: float = 0, - shear: tuple[float, float] = (0, 0), + shear: tuple[float, float] | Sequence[float] = (0, 0), ) -> np.ndarray: """Create arbitrary affine transformation matrix""" rot = rot * np.pi / 180 diff --git a/siapy/utils/images.py b/siapy/utils/images.py index 80323fe..f1d51fc 100644 --- a/siapy/utils/images.py +++ b/siapy/utils/images.py @@ -172,7 +172,7 @@ def convert_radiance_image_to_reflectance( panel_correction: np.ndarray, save_path: Annotated[ str | Path | None, "Header file (with '.hdr' extension) name with path." - ], + ] = None, **kwargs: Any, ) -> np.ndarray | SpectralImage: image_ref_np = image.to_numpy() * panel_correction From b3e559f3216f593d72c6375f530d66ad6c13da48 Mon Sep 17 00:00:00 2001 From: janezlapajne Date: Wed, 18 Sep 2024 16:44:27 +0200 Subject: [PATCH 3/3] refactor: added option to directly calculate correction factor from image --- siapy/utils/images.py | 30 ++++++++++++++++++++++-------- tests/utils/test_utils_images.py | 15 +++++++++++++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/siapy/utils/images.py b/siapy/utils/images.py index f1d51fc..7719030 100644 --- a/siapy/utils/images.py +++ b/siapy/utils/images.py @@ -6,6 +6,7 @@ import spectral as sp from siapy.core import logger +from siapy.core.exceptions import InvalidInputError from siapy.core.types import ImageDataType, ImageType from siapy.entities import SpectralImage from siapy.transformations.image import rescale @@ -187,14 +188,27 @@ def convert_radiance_image_to_reflectance( def calculate_correction_factor_from_panel( image: SpectralImage, panel_reference_reflectance: float, - panel_shape_label: str = "reference_panel", -) -> np.ndarray | None: - panel_shape = image.geometric_shapes.get_by_name(panel_shape_label) - if panel_shape is None: - return None - - panel_signatures = image.to_signatures(panel_shape.convex_hull()) - panel_radiance_mean = panel_signatures.signals.mean() + panel_shape_label: str | None = None, +) -> np.ndarray: + if panel_shape_label: + panel_shape = image.geometric_shapes.get_by_name(panel_shape_label) + if not panel_shape: + raise InvalidInputError( + input_value={"panel_shape_label": panel_shape_label}, + message="Panel shape label not found.", + ) + panel_signatures = image.to_signatures(panel_shape.convex_hull()) + panel_radiance_mean = panel_signatures.signals.mean() + + else: + temp_mean = image.mean(axis=(0, 1)) + if not isinstance(temp_mean, np.ndarray): + raise InvalidInputError( + input_value={"image": image}, + message=f"Expected image.mean(axis=(0, 1)) to return np.ndarray, but got {type(temp_mean).__name__}.", + ) + panel_radiance_mean = temp_mean + panel_reflectance_mean = np.full(image.bands, panel_reference_reflectance) panel_correction = panel_reflectance_mean / panel_radiance_mean return panel_correction diff --git a/tests/utils/test_utils_images.py b/tests/utils/test_utils_images.py index 179954e..1b3f37e 100644 --- a/tests/utils/test_utils_images.py +++ b/tests/utils/test_utils_images.py @@ -142,7 +142,7 @@ def shape(self) -> tuple[int, int, int]: assert save_path.exists() -def test_calculate_correction_factor_from_panel(spectral_images): +def test_calculate_correction_factor_from_panel_with_label(spectral_images): pixels = Pixels.from_iterable([(900, 1150), (1050, 1300)]) rect = Shape.from_shape_type( shape_type="rectangle", pixels=pixels, label="reference_panel" @@ -156,7 +156,6 @@ def test_calculate_correction_factor_from_panel(spectral_images): panel_shape_label="reference_panel", ) - assert panel_correction is not None assert isinstance(panel_correction, np.ndarray) assert panel_correction.shape == (image_vnir.bands,) @@ -168,6 +167,18 @@ def test_calculate_correction_factor_from_panel(spectral_images): ) +def test_calculate_correction_factor_from_panel_without_label(spectral_images): + image_vnir = spectral_images.vnir + panel_correction = calculate_correction_factor_from_panel( + image=image_vnir, + panel_reference_reflectance=0.3, + ) + direct_panel_calculation = np.full(image_vnir.bands, 0.3) / image_vnir.mean( + axis=(0, 1) + ) + assert np.array_equal(direct_panel_calculation, panel_correction) + + def test_convert_radiance_image_to_reflectance_without_saving(spectral_images): image_vnir = spectral_images.vnir panel_correction = np.random.default_rng().random(image_vnir.bands)