GeoRacoon 1.0.0__py3-none-any.whl

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.
convster/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ """
2
+ convster — Spatial filter application and derived metric computation for raster maps.
3
+
4
+ This package provides tools for applying spatial filters (e.g. Gaussian blur)
5
+ to land-cover type raster maps and for computing per-cell derived metrics such
6
+ as Shannon entropy and multi-layer interaction values. Processing is designed to
7
+ work block-wise for memory-efficient handling of large rasters.
8
+
9
+ Submodules
10
+ ----------
11
+ filters
12
+ Implementations of spatial filters ready for use with raster data,
13
+ including Gaussian blurring via :mod:`~convster.filters.gaussian`.
14
+ helper
15
+ Array utility functions, including non-zero index lookups used
16
+ internally during filter application.
17
+ processing
18
+ Functions for category selection, entropy computation, and multi-layer
19
+ interaction metrics. Supports per-category filtering and rescaling.
20
+ parallel
21
+ Parallelized application of filters and entropy/interaction computations
22
+ across spatial blocks of a raster.
23
+ """
24
+
25
+ _answer_to_everything = 42
@@ -0,0 +1,22 @@
1
+ """
2
+ This package contains functions for specific filters that can be applied to
3
+ blur land-cover type maps.
4
+
5
+ Whenever possible the filters are not implemented directly but imported from
6
+ packages with efficient implementations.
7
+ In particular implementations from https://scikit-image.org are used.
8
+
9
+ Usually, within a specific sub-module a filter method of the same name exists.
10
+ So, for example, within `filters.gaussian`, `skimage.filters.gaussian` is
11
+ available as `gaussian`.
12
+ """
13
+
14
+ from .gaussian import gaussian as _gauss_filter
15
+ from .gaussian import bpgaussian
16
+ from .gaussian import get_kernel_diameter as _gauss_get_kd
17
+ from .gaussian import get_kernel_size as _gauss_get_ks
18
+ from .gaussian import get_blur_params
19
+
20
+ _filters = [_gauss_filter, ]
21
+ _get_kernel_diam = [_gauss_get_kd, ]
22
+ _get_kernel_size = [_gauss_get_ks, ]
@@ -0,0 +1,282 @@
1
+ """
2
+ This module provides various functions to facilitate the usage of
3
+ `skimage.filters.gaussian`
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import numpy as np
9
+ from numpy.typing import NDArray
10
+
11
+ from skimage.filters import gaussian
12
+
13
+ from ..helper import (first_nonzero,
14
+ last_nonzero)
15
+
16
+ # we abstract the specific filters:
17
+ img_filter = gaussian
18
+
19
+
20
+ def get_kernel_diameter(sigma: float, **params) -> int:
21
+ """
22
+ Compute the effective Gaussian kernel diameter in number of cells.
23
+
24
+ The diameter is estimated by applying a Gaussian filter to a single impulse
25
+ in the center of a zero image, and measuring the spread of the nonzero
26
+ region. The result is always odd to ensure symmetry.
27
+
28
+ Parameters
29
+ ----------
30
+ sigma : float
31
+ Standard deviation for the Gaussian kernel.
32
+ Currently only single scalar values are supported.
33
+ **params
34
+ Additional keyword arguments passed to the Gaussian filter.
35
+
36
+ Returns
37
+ -------
38
+ int
39
+ Estimated kernel diameter in pixels (always an odd number).
40
+
41
+ Notes
42
+ -----
43
+ This function determines the kernel diameter adaptively. Starting from
44
+ an initial guess of `10 * sigma`, the size is increased until the blurred
45
+ impulse response fits within the kernel. Uses :func:`skimage.filters.gaussian`
46
+ internally to compute the impulse response.
47
+
48
+ See Also
49
+ --------
50
+ :func:`get_kernel_size` : Return the radius (half-diameter) of the Gaussian kernel.
51
+ :func:`compatible_border_size` : Assert or compute a border size compatible with a kernel.
52
+
53
+ Examples
54
+ --------
55
+ >>> get_kernel_diameter(1)
56
+ 7
57
+ >>> get_kernel_diameter(2)
58
+ 15
59
+ """
60
+ msize = int(10 * sigma)
61
+ # make sure it is odd
62
+ if not msize & 1:
63
+ msize += 1
64
+ diameter = msize
65
+ while diameter == msize:
66
+ msize += int(max(1, round(0.1 * msize)))
67
+ tmap = np.zeros((msize, msize), dtype=np.uint8)
68
+ half = int(0.5 * (msize - 1))
69
+ tmap[half, half] = 255
70
+ blurred = gaussian(tmap, sigma=sigma, **params)
71
+ diameter = max(np.unique(last_nonzero(blurred, axis=1)
72
+ - first_nonzero(blurred, axis=1))) + 1
73
+ return diameter
74
+
75
+
76
+ def get_kernel_size(sigma: float, **params):
77
+ """
78
+ Return the radius of a Gaussian kernel (center to border distance).
79
+
80
+ The kernel size is defined as half of the kernel diameter minus one,
81
+ i.e. size = (diameter - 1) / 2 where `diameter` is determined
82
+ by :func:`get_kernel_diameter`.
83
+
84
+ Parameters
85
+ ----------
86
+ sigma : float
87
+ Standard deviation for the Gaussian kernel.
88
+ **params
89
+ Additional keyword arguments passed to :func:`get_kernel_diameter`
90
+ (e.g. `truncate`, `mode`).
91
+
92
+ Returns
93
+ -------
94
+ int
95
+ The radius of the Gaussian kernel in pixels.
96
+
97
+ See Also
98
+ --------
99
+ :func:`get_kernel_diameter` : Compute the full kernel diameter.
100
+ :func:`compatible_border_size` : Assert or compute a border size compatible with a kernel.
101
+
102
+ Examples
103
+ --------
104
+ >>> get_kernel_size(1)
105
+ 3
106
+ >>> get_kernel_size(2)
107
+ 7
108
+ """
109
+ return int(0.5 * (get_kernel_diameter(sigma, **params) - 1))
110
+
111
+
112
+ def compatible_border_size(sigma: float | int, border: tuple[int, int] | None = None,
113
+ **params) -> tuple[int, int]:
114
+ """
115
+ Assert that the border size is compatible with the specified parameter
116
+
117
+ This method asserts that the kernel size determined by `sigma` and further
118
+ parametrization is smaller than the border.
119
+ If no border is provided, then the minimal border size (in number of
120
+ pixels) is returned.
121
+
122
+ Parameters
123
+ ----------
124
+ sigma : float or int
125
+ Standard deviation for Gaussian kernel
126
+ border : tuple[int, int] or None
127
+ The border size (width, height) in number of pixels along each axis
128
+ **params
129
+ Additional keyword arguments passed to :func:`get_kernel_size`.
130
+
131
+ Returns
132
+ -------
133
+ tuple[int, int]
134
+ The border (width, height) compatible with the specified parameters.
135
+ If a border was provided already, it is returned again, if no border
136
+ was provided, the smallest compatible border is returned.
137
+
138
+ See Also
139
+ --------
140
+ :func:`get_kernel_size` : Compute the kernel radius used in compatibility checks.
141
+ :func:`get_kernel_diameter` : Compute the full diameter of the Gaussian kernel.
142
+ """
143
+ ks = get_kernel_size(sigma=sigma, **params)
144
+ if border:
145
+ assert all(ks <= b for b in border), f"A dimension of {border=} " \
146
+ f"exceeds the kernel size {ks}"
147
+ bs = border
148
+ else:
149
+ bs = (ks + 1,) * 2
150
+ return bs
151
+
152
+
153
+ def bpgaussian(data: NDArray, **filter_params) -> NDArray:
154
+ """Applies a border-preserving Gaussian filter
155
+
156
+ The approach considers a Gaussian blur to be a weighted average over
157
+ all pixels within the kernel diameter with the weight being given by
158
+ the Gaussian function.
159
+ Pixels close to `np.nan` values should simply "ignore" `np.nan` pixels
160
+ and perform the weighted average over all non-`np.nan` pixels within
161
+ the Gaussian kernel.
162
+ This can be achieved with a normal Gaussian filter in a three-step process:
163
+
164
+ 1. Perform a Gaussian filter on the data with `np.nan` substituted by
165
+ the neutral element in terms of addition, i.e. 0.0.
166
+ This leads to a weighted average that is not properly normalized as
167
+ the former `np.nan` pixels do not contribute to the value, but are
168
+ considered in the normalizing sum of the weights.
169
+ 2. To properly normalize all pixels, create a binary array in the same shape
170
+ as `data` with `np.nan` values becoming `0` and all other pixels `1`.
171
+ Apply the same Gaussian filter to this binary array which results in an
172
+ array holding the sum of weights for the weighted average.
173
+ 3. Dividing the blurred array from point 1. by the sum-of-weights array form
174
+ step 2 results in a properly normalized weighted average and thus a blurred
175
+ version of the input data with preserved borders.
176
+
177
+ Parameters
178
+ ----------
179
+ data : NDArray
180
+ Array to apply the Gaussian filter on.
181
+ **filter_params : dict
182
+ Additional keyword arguments passed to :func:`skimage.filters.gaussian`.
183
+ Common parameters include:
184
+
185
+ - ``sigma`` : float
186
+ Standard deviation for Gaussian kernel.
187
+ - ``truncate`` : float
188
+ Truncate filter at this many standard deviations.
189
+
190
+ See :func:`skimage.filters.gaussian` for further parameters.
191
+
192
+ Returns
193
+ -------
194
+ NDArray
195
+ Blurred version of `data` with borders preserved at NaN boundaries.
196
+
197
+ See Also
198
+ --------
199
+ :func:`get_kernel_diameter` : Compute the effective kernel diameter for a given sigma.
200
+ :func:`get_blur_params` : Compute Gaussian blur parameters from diameter or sigma.
201
+ """
202
+ # Substitute `np.nan`s with 0.0 and apply filter
203
+ _data_nonnan = np.where(np.isnan(data), 0.0, data)
204
+ _data_nonnan_blurred = gaussian(image=_data_nonnan, **filter_params)
205
+
206
+ _data_binary = np.where(np.isnan(data), 0.0, 1.0)
207
+ _data_binary_blurred = gaussian(image=_data_binary, **filter_params)
208
+
209
+ _blurred_data = np.divide(_data_nonnan_blurred, _data_binary_blurred,
210
+ out=np.full(data.shape, np.nan),
211
+ where=~np.isnan(data))
212
+ return _blurred_data
213
+
214
+
215
+ def get_blur_params(diameter: float | None = None, sigma: float | None = None,
216
+ truncate: float = 3) -> dict[str, float]:
217
+ """
218
+ Compute Gaussian blur parameters from either `diameter` or `sigma`.
219
+
220
+ Either `diameter` or `sigma` must be provided. Missing values are inferred
221
+ from the others, and `truncate` is used or recomputed to maintain consistency.
222
+
223
+ Parameters
224
+ ----------
225
+ diameter : float or None
226
+ Kernel diameter. If provided with `sigma`, `truncate` is recomputed.
227
+ sigma : float or None
228
+ Standard deviation of the Gaussian kernel. If provided with `diameter`,
229
+ `truncate` is recomputed.
230
+ truncate : float
231
+ Number of standard deviations at which to truncate the kernel.
232
+ Default is 3. Ignored if both `diameter` and `sigma` are provided
233
+ (recomputed).
234
+
235
+ Returns
236
+ -------
237
+ dict[str, float]
238
+ Dictionary containing:
239
+ - `diameter`: computed kernel diameter
240
+ - `sigma`: computed standard deviation
241
+ - `truncate`: final truncate value
242
+
243
+ Raises
244
+ ------
245
+ TypeError
246
+ If neither `diameter` nor `sigma` is provided.
247
+
248
+ Notes
249
+ -----
250
+ The function ensures that `diameter`, `sigma`, and `truncate` are consistent
251
+ according to Gaussian kernel conventions.
252
+
253
+ See Also
254
+ --------
255
+ :func:`get_kernel_diameter` : Compute the effective kernel diameter from sigma.
256
+ :func:`get_kernel_size` : Compute the kernel radius from sigma.
257
+ :func:`compatible_border_size` : Assert or compute a border compatible with the kernel.
258
+
259
+ Examples
260
+ --------
261
+ >>> get_blur_params(diameter=15)
262
+ {'diameter': 15, 'sigma': 2.5, 'truncate': 3}
263
+ >>> get_blur_params(sigma=2.0)
264
+ {'diameter': 12.0, 'sigma': 2.0, 'truncate': 3}
265
+ >>> get_blur_params(diameter=15, sigma=3)
266
+ {'diameter': 15, 'sigma': 3, 'truncate': 2.5}
267
+ """
268
+ if diameter is None and sigma is None:
269
+ raise TypeError("Either the `diameter` or the `sigma` parameter "
270
+ f"must be provided. \nGot: {diameter=}, {sigma=}")
271
+
272
+ if diameter:
273
+ if sigma:
274
+ truncate = 0.5 * diameter / sigma
275
+ else:
276
+ if truncate:
277
+ sigma = 0.5 * diameter / truncate
278
+ else:
279
+ if sigma:
280
+ diameter = 2 * sigma * truncate
281
+
282
+ return dict(diameter=diameter, sigma=sigma, truncate=truncate)
convster/helper.py ADDED
@@ -0,0 +1,94 @@
1
+ """
2
+ Array utility functions for the convster package.
3
+
4
+ This module provides low-level helper functions for inspecting and manipulating
5
+ NumPy arrays. Currently, it exposes utilities for locating the first and last
6
+ non-zero element along a given axis, which are used internally during filter
7
+ application to determine valid data ranges within raster bands.
8
+ """
9
+
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+
14
+ def first_nonzero(data: NDArray, axis: int = 0, no_value: int = -1) -> NDArray:
15
+ """
16
+ Return the index of the first non-zero value along the given axis.
17
+
18
+ Parameters
19
+ ----------
20
+ data : NDArray
21
+ Input array to examine.
22
+ axis : int
23
+ Array axis along which to search for the first non-zero. Default is 0.
24
+ no_value : int
25
+ Value to return when no non-zero entries are found along an axis.
26
+ Default is -1.
27
+
28
+ Returns
29
+ -------
30
+ indices
31
+ Array indices of the first non-zero values along the specified axis.
32
+ If no non-zero is found, returns `no_value` for that slice.
33
+
34
+ See Also
35
+ --------
36
+ :func:`last_nonzero` : Return the index of the last non-zero value.
37
+
38
+ Examples
39
+ --------
40
+ >>> a = np.array([
41
+ ... [0, 0, 3, 0],
42
+ ... [1, 0, 0, 0],
43
+ ... [0, 2, 0, 0],
44
+ ... [0, 0, 0, 4]
45
+ ... ])
46
+ >>> first_nonzero(a, axis=1)
47
+ array([2, 0, 1, 3])
48
+ >>> first_nonzero(a, axis=0)
49
+ array([1, 2, 0, 3])
50
+ """
51
+ mask = data != 0
52
+ return np.where(mask.any(axis=axis), mask.argmax(axis=axis), no_value)
53
+
54
+
55
+ def last_nonzero(data: NDArray, axis: int = 0, no_value: int = -1) -> NDArray:
56
+ """
57
+ Return the index of the last non-zero value along the given axis.
58
+
59
+ Parameters
60
+ ----------
61
+ data : NDArray
62
+ Input array to examine.
63
+ axis : int
64
+ Array axis along which to search for the last non-zero. Default is 0.
65
+ no_value : int
66
+ Value to return when no non-zero entries are found along an axis.
67
+ Default is -1.
68
+
69
+ Returns
70
+ -------
71
+ indices
72
+ Array indices of the last non-zero values along the specified axis.
73
+ If no non-zero is found, returns `no_value` for that slice.
74
+
75
+ See Also
76
+ --------
77
+ :func:`first_nonzero` : Return the index of the first non-zero value.
78
+
79
+ Examples
80
+ --------
81
+ >>> a = np.array([
82
+ ... [0, 0, 3, 0],
83
+ ... [1, 0, 0, 0],
84
+ ... [0, 2, 0, 0],
85
+ ... [0, 0, 0, 4]
86
+ ... ])
87
+ >>> last_nonzero(a, axis=1)
88
+ array([2, 0, 1, 3])
89
+ >>> last_nonzero(a, axis=0)
90
+ array([1, 2, 2, 3])
91
+ """
92
+ mask = data != 0
93
+ loc = data.shape[axis] - np.flip(mask, axis=axis).argmax(axis=axis) - 1
94
+ return np.where(mask.any(axis=axis), loc, no_value)