fosanalysis
A framework to evaluate distributed fiber optic sensor data
Loading...
Searching...
No Matches
windows.py
Go to the documentation of this file.
2r"""
3Contains functions to shift a (partly) view of an array across the full array.
4
5\author Bertram Richter
6\date 2024
7"""
8
9import numpy as np
10import itertools
11
12from . import misc
13
14def sliding_window_function(arr: np.array,
15 radius,
16 fn,
17 pad_mode: str = None,
18 *args, **kwargs) -> np.array:
19 r"""
20 Applies the function `fn` to a sliding window over the array `arr`.
21 \note Results in the margins (the first and last entries closer than
22 `radius` to the edge of the array in each direction) are not
23 reliable, as they suffer from boundary effects.
24 This is caused by the sliding window only visit "complete" windows.
25 In the margins, the values of the edge is repeated.
26 The padding behavior can be changed with `pad_mode`.
27 \param arr Array of data, over which the window should slide.
28 \param radius Inradius of the window, sets the window's widths.
29 Along an axis, the window has a width of \f$(2r+1)\f$.
30 It is expected to be an `int` a `tuple`.
31 If `radius` is an integer, it is used for all axes.
32 The windows will contain \f$(2r+1)^n\f$ entries.
33 To set indiviual widths along for each dimension, use a `tuple`.
34 This `tuple`'s length has to match the dimension of `arr`.
35 \param fn A function object (type: `callable`), taking a `np.array`
36 as input and returning a `float`.
37 \param pad_mode Mode for padding the edges of the result array.
38 Defaults to `"edge"`, which repeats the result value on the edge.
39 For more, options, see [`numpy.pad()`](https://numpy.org/doc/stable/reference/generated/numpy.pad.html)
40 \param *args Additional positional arguments; ignored.
41 \param **kwargs Additional keyword arguments; ignored.
42 \return Return a `np.array` with the same shape as `arr`.
43 Each entry is the result of applying `fn` to a window reaching
44 `radius` into each direction.
45 """
46 pad_mode = pad_mode if pad_mode is not None else "edge"
47 arr = np.array(arr)
48 radius = misc.np_to_python(radius)
49 if isinstance(radius, int):
50 radius = (radius,)*arr.ndim
51 try:
52 assert len(radius) == arr.ndim
53 radius = tuple([int(r) for r in radius])
54 except AssertionError:
55 raise ValueError("Shape of radius ({}) does not match the shape of array ({})".format(radius, arr.ndim))
56 window_size = tuple([int(r * 2 + 1) for r in radius])
57 view = np.lib.stride_tricks.sliding_window_view(arr, window_size)
58 axis = tuple(range(-1, -arr.ndim - 1, -1)) if arr.ndim > 1 else -1
59 fn_result = fn(view, axis=axis)
60 pad = np.pad(fn_result, pad_width=radius, mode=pad_mode)
61 return pad
62
63def sliding(data_array: np.array, radius):
64 r"""
65 Generates a sliding window over an array.
66 This function returns a generator, hence, it should be use like:
67 ```.py
68 for pixel, window in sliding(<array>, <radius>):
69 # do something
70 pass
71 ```
72 In contrast to other function like
73 [`numpy.lib.stride_tricks.sliding_window_view()`](https://numpy.org/devdocs/reference/generated/numpy.lib.stride_tricks.sliding_window_view.html),
74 this window slides over each pixel, even in the margin areas of `data_array`.
75 So, for each entry in `data_array`, a view to the window surrounding it is yielded.
76 In the margins, the window contains fewer entries.
77 Thus, boundary effects are to be expected, when using this function.
78 \remark Note, that a views of the original array are yielded, not copies.
79 Changing a pixel's value in the window will change the original array.
80 \param data_array Array of data, over which the window should slide.
81 \param radius Inradius of the window, sets the window's widths.
82 Along an axis, the window has a width of \f$(2r+1)\f$.
83 It is expected to be an `int` or `iterable`.
84 If `radius` is an integer, it is used for all axes.
85 The window will contain (up to) \f$(2r+1)^2\f$ entries.
86 To set different widths for axes, use an `iterable`, such as a `tuple`.
87 This `tuple`'s length has to match the dimension of `data_array`.
88 \return In each iteration, a tuple like `(pixel, window)` is yielded.
89 \retval pixel Index of the window's central pixel in `data_array`.
90 \retval window Sub-array view of the `data_array` centered around `pixel`.
91 """
92 data_array = np.array(data_array)
93 radius = misc.np_to_python(radius)
94 try:
95 assert len(radius) == data_array.ndim
96 radius = tuple(radius)
97 except TypeError:
98 radius = (radius,)*data_array.ndim
99 except AssertionError:
100 raise ValueError("Shape of radius ({}) does not match the shape of array ({})".format(len(radius), data_array.ndim))
101 if isinstance(radius, int):
102 radius = (radius,)*data_array.ndim
103 iterator = np.nditer(data_array, flags=["multi_index"])
104 for pixel_value in iterator:
105 pixel = iterator.multi_index
106 slices = tuple(slice(max(p-r, 0), min(p+r+1, z+1)) for p, r, z in zip(pixel, radius, data_array.shape))
107 yield pixel, data_array[slices]
108
109def moving(data_array: np.array,
110 radius: tuple,
111 start_pixel: tuple = None,
112 step_size: tuple = None,
113 ):
114 r"""
115 Generates a symmetric moving window over an array.
116 This function returns a generator that yields information about the window_center_orig, target_pixel, and the content of the window.
117 Converts data_array to a Numpy array. Ensuring radius as a tuple. do this similar for stepsize and the start pixel
118 (start pixel if not given equal to radius). Determine orig_index_lists based on array dimensions and provided indices.
119 It generates combinations of indices. It iterates over combinations and yields window information.
120 \param data_array Array of data over which the window should move.
121 \param radius Inradius of the window's rectangle.
122 If `radius` is an `int`, all axes will use this radius and the
123 window is a square.
124 For non-square windows, pass a tuple with a radius for each
125 dimension of `data_array`.
126 Along an axis, the window has a width of \f$2r + 1\f$ for each
127 element \f$r\f$ of `radius`.
128 \param start_pixel Index of the first window's central pixel.
129 If `start_pixel` is an `int`, it is used for all dimensions of `data_array`.
130 To specify a custom starting element, pass a tuple with a step
131 size for each dimension of `data_array`.
132 If `None`, it defaults to `radius`, the moving window starts with
133 a full slice.
134 \param step_size Step size how far the window moves in one step.
135 If `step_size` is an `int`, it is used for all dimensions of `data_array`.
136 If `None`, it defaults to \f$2r + 1\f$ for each element \f$r\f$
137 of `radius`, which is equivalent to a rolling window.
138 \return Generator yielding in each iteration a tuple like
139 `(orig_pixel, target_pixel, window_content)`.
140 \retval orig_pixel Index of the window's center in `data_array`.
141 \retval target_pixel Index of the entry in a new array, in which the
142 aggregated result is to be stored.
143 \retval window_content A view of the `data_array`, around the `orig_pixel`.
144 """
145 moving_params = determine_moving_parameters(
146 data_array, radius, start_pixel, step_size
147 )
148 orig_index_lists, radius, start_pixel, step_size = moving_params
149 # Generate index combinations for the original pixels
150 orig_index_combinations = list(itertools.product(*orig_index_lists))
151 # Generate index combinations for the target pixels
152 target_pixels = itertools.product(*(range(len(x)) for x in orig_index_lists))
153 # Iterate over combinations and yield window information
154 for (orig_pixel, target_pixel) in zip(orig_index_combinations, target_pixels):
155 window_content = data_array[tuple(slice(max(0, i - r), min(s, i + r + 1)) for i, r, s in zip(orig_pixel, radius, data_array.shape))]
156 yield orig_pixel, target_pixel, window_content
157
159 data_array: np.array,
160 radius: tuple,
161 start_pixel: tuple = None,
162 step_size: tuple = None,
163 ) -> list:
164 r"""
165 Generate indices for a moving window and check the other parameters.
166 \param data_array Array of data over which the window should move.
167 \param radius Inradius of the window's rectangle.
168 If `radius` is an `int`, all axes will use this radius and the
169 window is a square.
170 For non-square windows, pass a tuple with a radius for each
171 dimension of `data_array`.
172 Along an axis, the window has a width of \f$2r + 1\f$ for each
173 element \f$r\f$ of `radius`.
174 \param start_pixel Index of the first window's central pixel.
175 If `start_pixel` is an `int`, it is used for all dimensions of `data_array`.
176 To specify a custom starting element, pass a tuple with a step
177 size for each dimension of `data_array`.
178 If `None`, it defaults to `radius`, the moving window starts with
179 a full slice.
180 \param step_size Step size for estimation.
181 If `step_size` is an `int`, it is used for all dimensions of `data_array`.
182 If `None`, it defaults to \f$2r + 1\f$ for each element \f$r\f$
183 of `radius`, which is equivalent to a rolling window.
184 \return Retuns a tuple like `(orig_index_lists, radius, start_pixel, step_size)`:
185 \retval orig_index_lists List of lists, containing the indices along each axis.
186 \retval radius A tuple, see above.
187 \retval start_pixel A tuple, see above.
188 \retval step_size A tuple, see above.
189 """
190 # Assert that the data_array is an array, but the other parameters are not
191 data_array = np.array(data_array)
192 radius = misc.np_to_python(radius)
193 start_pixel = misc.np_to_python(start_pixel)
194 step_size = misc.np_to_python(step_size)
195 # Convert the radius to tuple
196 try:
197 assert radius is not None
198 except AssertionError:
199 raise ValueError("Parameter radius must not be None!")
200 try:
201 assert len(radius) == len(data_array.shape)
202 except TypeError:
203 radius = (radius,) * data_array.ndim
204 except AssertionError:
205 err_msg = "Dimensions non-conformant: data_array.shape: {}, radius: {}"
206 raise ValueError(err_msg.format(data_array.shape, radius))
207 # Assign defaults
208 start_pixel = radius if start_pixel is None else start_pixel
209 step_size = tuple((r*2 + 1) for r in radius) if step_size is None else step_size
210 # Assert, that stepsize and step_size are tuples conformant with data_array
211 try:
212 assert len(start_pixel) == len(data_array.shape)
213 except TypeError:
214 start_pixel = (start_pixel,) * data_array.ndim
215 except AssertionError:
216 err_msg = "Dimensions non-conformant: data_array.shape: {}, start_pixel: {}"
217 raise ValueError(err_msg.format(data_array.shape, start_pixel))
218 try:
219 assert len(step_size) == len(data_array.shape)
220 except TypeError:
221 step_size = (step_size,) * data_array.ndim
222 except AssertionError:
223 err_msg = "Dimensions non-conformant: data_array.shape: {}, step_size: {}"
224 raise ValueError(err_msg.format(data_array.shape, start_pixel))
225 try:
226 orig_index_lists = [list(range(start, stop, step)) for start, stop, step in zip(start_pixel, data_array.shape, step_size)]
227 return (orig_index_lists, radius, start_pixel, step_size)
228 except TypeError:
229 raise ValueError("Something went wrong generating indices, please check parameters.")
230
moving(np.array data_array, tuple radius, tuple start_pixel=None, tuple step_size=None)
Generates a symmetric moving window over an array.
Definition windows.py:113
np.array sliding_window_function(np.array arr, radius, fn, str pad_mode=None, *args, **kwargs)
Applies the function fn to a sliding window over the array arr.
Definition windows.py:18
sliding(np.array data_array, radius)
Generates a sliding window over an array.
Definition windows.py:63
list determine_moving_parameters(np.array data_array, tuple radius, tuple start_pixel=None, tuple step_size=None)
Generate indices for a moving window and check the other parameters.
Definition windows.py:163