File: _gradient_search.pyx

package info (click to toggle)
pyresample 1.33.0-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 34,808 kB
  • sloc: python: 20,229; cpp: 463; makefile: 105
file content (269 lines) | stat: -rw-r--r-- 9,890 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (c) 2013-2022

# Author(s):

#   Martin Raspaud <martin.raspaud@smhi.se>
#   Leon Majewski  <leon.majewski@bom.gov.au>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import numpy as np

cimport cython
cimport numpy as np
from libc.math cimport fabs, isinf

ctypedef fused data_type:
    np.float64_t
    np.float32_t

ctypedef np.float64_t float_index
float_index_dtype = np.float64

np.import_array()

@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline void nn(const data_type[:, :, :] data, int l0, int p0, float_index dl, float_index dp, int lmax, int pmax, data_type[:] res) noexcept nogil:
    cdef int nnl, nnp
    cdef size_t z_size = res.shape[0]
    cdef size_t i
    nnl = l0
    if dl < -0.5 and nnl > 0:
        nnl -= 1
    elif dl > 0.5 and nnl < lmax:
        nnl += 1
    nnp = p0
    if dp < -0.5 and nnp > 0:
        nnp -= 1
    elif dp > 0.5 and nnp < pmax:
        nnp += 1
    for i in range(z_size):
        res[i] = data[i, nnl, nnp]


@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline void bil(const data_type[:, :, :] data, int l0, int p0, float_index dl, float_index dp, int lmax, int pmax, data_type[:] res) noexcept nogil:
    cdef int l_a, l_b, p_a, p_b
    cdef float_index w_l, w_p
    cdef size_t z_size = res.shape[0]
    cdef size_t i
    if dl < 0:
        l_a = max(0, l0 - 1)
        l_b = l0
        w_l = 1 + dl
    else:
        l_a = l0
        l_b = min(l0 + 1, lmax)
        w_l = dl
    if dp < 0:
        p_a = max(0, p0 - 1)
        p_b = p0
        w_p = 1 + dp
    else:
        p_a = p0
        p_b = min(p0 + 1, pmax)
        w_p = dp
    for i in range(z_size):
        res[i] = <data_type>((1 - w_l) * (1 - w_p) * data[i, l_a, p_a] +
                             (1 - w_l) * w_p * data[i, l_a, p_b] +
                             w_l * (1 - w_p) * data[i, l_b, p_a] +
                             w_l * w_p * data[i, l_b, p_b])


@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline void indices_xy(const data_type[:, :, :] data, int l0, int p0, float_index dl, float_index dp, int lmax, int pmax, data_type[:] res) noexcept nogil:
    cdef int nnl, nnp
    cdef size_t z_size = res.shape[0]
    cdef size_t i
    res[1] = dl + l0
    res[0] = dp + p0


ctypedef void (*FN)(const data_type[:, :, :] data, int l0, int p0, float_index dl, float_index dp, int lmax, int pmax, data_type[:] res) noexcept nogil


@cython.boundscheck(False)
@cython.wraparound(False)
cpdef one_step_gradient_search(const data_type[:, :, :] data,
                               float_index [:, :] src_x,
                               float_index [:, :] src_y,
                               float_index [:, :] xl,
                               float_index [:, :] xp,
                               float_index [:, :] yl,
                               float_index [:, :] yp,
                               float_index [:, :] dst_x,
                               float_index [:, :] dst_y,
                               str method='bilinear'):
    """Gradient search, simple case variant."""
    cdef FN fun
    if method == 'bilinear':
        fun = bil
    else:
        fun = nn

    # change the output size (x_size, y_size) to match area_def.shape:
    # (lines,pixels)
    cdef size_t z_size = data.shape[0]
    cdef size_t y_size = dst_y.shape[0]
    cdef size_t x_size = dst_x.shape[1]

    if data_type is double:
        dtype = np.float64
    else:
        dtype = np.float32

    # output image array --> needs to be (lines, pixels) --> y,x
    image = np.full([z_size, y_size, x_size], np.nan, dtype=dtype)
    cdef data_type[:, :, :] image_view = image
    with nogil:
        one_step_gradient_search_no_gil(data,
                                        src_x, src_y,
                                        xl, xp, yl, yp,
                                        dst_x, dst_y,
                                        x_size, y_size,
                                        fun, image_view)
    # return the output image
    return image


@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cdef void one_step_gradient_search_no_gil(const data_type[:, :, :] data,
                                          const float_index[:, :] src_x,
                                          const float_index[:, :] src_y,
                                          const float_index[:, :] xl,
                                          const float_index[:, :] xp,
                                          const float_index[:, :] yl,
                                          const float_index[:, :] yp,
                                          const float_index[:, :] dst_x,
                                          const float_index[:, :] dst_y,
                                          const size_t x_size,
                                          const size_t y_size,
                                          FN fun,
                                          data_type[:, :, :] result_array) noexcept nogil:

    # pixel max ---> data is expected in [lines, pixels]
    cdef int pmax = src_x.shape[1] - 1
    cdef int lmax = src_x.shape[0] - 1
    # centre of input image - starting point
    cdef int p0 = pmax // 2
    cdef int l0 = lmax // 2
    cdef int last_p0 = p0
    cdef int last_l0 = l0

    # intermediate variables:
    cdef int l_a, l_b, p_a, p_b
    cdef size_t i, j, elt
    cdef float_index dx, dy, d, dl, dp
    cdef int col_step = -1
    # number of iterations
    cdef int cnt = 0
    for i in range(y_size):
        # swap column iteration direction for every row
        if col_step == -1:
            j = 0
            col_step = 1
        else:
            j = x_size - 1
            col_step = -1

        for _ in range(x_size):
            if isinf(dst_x[i, j]):
                j += col_step
                continue
            cnt = 0
            while True:
                cnt += 1
                # algorithm does not converge.
                if cnt > 5:
                    p0 = last_p0
                    l0 = last_l0
                    break
                # check we are within the input image bounds
                if lmax >= l0 >= 0 and pmax >= p0 >= 0:
                    # step size
                    dx = dst_x[i, j] - src_x[l0, p0]
                    dy = dst_y[i, j] - src_y[l0, p0]
                else:
                    # reset such that we are back in the input image bounds
                    l0 = max(0, min(lmax, l0))
                    p0 = max(0, min(pmax, p0))
                    continue

                # distance from pixel/line to output location
                d = yl[l0, p0] * xp[l0, p0] - yp[l0, p0] * xl[l0, p0]
                if d == 0.0:
                    # There's no gradient, try again
                    continue
                dl = (xp[l0, p0] * dy - yp[l0, p0] * dx) / d
                dp = (yl[l0, p0] * dx - xl[l0, p0] * dy) / d
                # check that our distance to an output location is less than 1
                # pixel/line
                if fabs(dp) < 1 and fabs(dl) < 1:
                    last_p0 = p0
                    last_l0 = l0
                    if 0 <= dl + l0 <= lmax and 0 <= dp + p0 <= pmax:
                        fun(data, l0, p0, dl, dp, lmax, pmax, result_array[:, i, j])
                    # found our solution, next
                    break
                else:
                    # increment...
                    l0 = int(l0 + dl)
                    p0 = int(p0 + dp)
            j += col_step


@cython.boundscheck(False)
@cython.wraparound(False)
cpdef one_step_gradient_indices(float_index [:, :] src_x,
                                float_index [:, :] src_y,
                                float_index [:, :] xl,
                                float_index [:, :] xp,
                                float_index [:, :] yl,
                                float_index [:, :] yp,
                                float_index [:, :] dst_x,
                                float_index [:, :] dst_y):
    """Gradient search, simple case variant, returning float indices.

    This is appropriate for monotonous gradients only, i.e. not modis or viirs in satellite projection.
    """

    # change the output size (x_size, y_size) to match area_def.shape:
    # (lines,pixels)
    cdef size_t y_size = dst_y.shape[0]
    cdef size_t x_size = dst_x.shape[1]


    # output indices arrays --> needs to be (lines, pixels) --> y,x
    indices = np.full([2, y_size, x_size], np.nan, dtype=float_index_dtype)
    cdef float_index [:, :, :] indices_view_result = indices

    # fake_data is not going to be used anyway as we just fill in the indices
    cdef float_index [:, :, :] fake_data = np.full([1, 1, 1], np.nan, dtype=float_index_dtype)

    with nogil:
        one_step_gradient_search_no_gil[float_index](fake_data,
                                                     src_x, src_y,
                                                     xl, xp, yl, yp,
                                                     dst_x, dst_y,
                                                     x_size, y_size,
                                                     indices_xy, indices_view_result)
    return indices