Commit c6a22019532ade5b966532f4ad235d7c816d09bc
Committed by
GitHub
1 parent
f4c771e9
Exists in
master
Lanczos 3d (#108)
* Testing with lanczos 3D * Using lanczos to interpolate after applying a reorientation * More options and pointer to function * lanczos a=4
Showing
5 changed files
with
161 additions
and
30 deletions
Show diff stats
invesalius/data/interpolation.pxd
... | ... | @@ -3,3 +3,6 @@ from .cy_my_types cimport image_t |
3 | 3 | cdef double interpolate(image_t[:, :, :], double, double, double) nogil |
4 | 4 | cdef double tricub_interpolate(image_t[:, :, :], double, double, double) nogil |
5 | 5 | cdef double tricubicInterpolate (image_t[:, :, :], double, double, double) nogil |
6 | +cdef double lanczos3 (image_t[:, :, :], double, double, double) nogil | |
7 | + | |
8 | +cdef double nearest_neighbour_interp(image_t[:, :, :], double, double, double) nogil | ... | ... |
invesalius/data/interpolation.pyx
... | ... | @@ -4,9 +4,12 @@ import numpy as np |
4 | 4 | cimport numpy as np |
5 | 5 | cimport cython |
6 | 6 | |
7 | -from libc.math cimport floor, ceil, sqrt, fabs, round | |
7 | +from libc.math cimport floor, ceil, sqrt, fabs, round, sin, M_PI | |
8 | 8 | from cython.parallel import prange |
9 | 9 | |
10 | +DEF LANCZOS_A = 4 | |
11 | +DEF SIZE_LANCZOS_TMP = LANCZOS_A * 2 - 1 | |
12 | + | |
10 | 13 | cdef double[64][64] temp = [ |
11 | 14 | [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], |
12 | 15 | [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], |
... | ... | @@ -77,6 +80,12 @@ cdef double[64][64] temp = [ |
77 | 80 | @cython.boundscheck(False) # turn of bounds-checking for entire function |
78 | 81 | @cython.cdivision(True) |
79 | 82 | @cython.wraparound(False) |
83 | +cdef double nearest_neighbour_interp(image_t[:, :, :] V, double x, double y, double z) nogil: | |
84 | + return V[<int>round(z), <int>round(y), <int>round(x)] | |
85 | + | |
86 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
87 | +@cython.cdivision(True) | |
88 | +@cython.wraparound(False) | |
80 | 89 | cdef double interpolate(image_t[:, :, :] V, double x, double y, double z) nogil: |
81 | 90 | cdef double xd, yd, zd |
82 | 91 | cdef double c00, c10, c01, c11 |
... | ... | @@ -123,6 +132,76 @@ cdef double interpolate(image_t[:, :, :] V, double x, double y, double z) nogil: |
123 | 132 | @cython.boundscheck(False) # turn of bounds-checking for entire function |
124 | 133 | @cython.cdivision(True) |
125 | 134 | @cython.wraparound(False) |
135 | +cdef inline double lanczos3_L(double x, int a) nogil: | |
136 | + if x == 0: | |
137 | + return 1.0 | |
138 | + elif -a <= x < a: | |
139 | + return (a * sin(M_PI * x) * sin(M_PI * (x / a)))/(M_PI**2 * x**2) | |
140 | + else: | |
141 | + return 0.0 | |
142 | + | |
143 | + | |
144 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
145 | +@cython.cdivision(True) | |
146 | +@cython.wraparound(False) | |
147 | +cdef double lanczos3(image_t[:, :, :] V, double x, double y, double z) nogil: | |
148 | + cdef int a = LANCZOS_A | |
149 | + | |
150 | + cdef int xd = <int>floor(x) | |
151 | + cdef int yd = <int>floor(y) | |
152 | + cdef int zd = <int>floor(z) | |
153 | + | |
154 | + cdef int xi = xd - a + 1 | |
155 | + cdef int xf = xd + a | |
156 | + | |
157 | + cdef int yi = yd - a + 1 | |
158 | + cdef int yf = yd + a | |
159 | + | |
160 | + cdef int zi = zd - a + 1 | |
161 | + cdef int zf = zd + a | |
162 | + | |
163 | + cdef double lx = 0.0 | |
164 | + cdef double ly = 0.0 | |
165 | + cdef double lz = 0.0 | |
166 | + | |
167 | + cdef double[SIZE_LANCZOS_TMP][SIZE_LANCZOS_TMP] temp_x | |
168 | + cdef double[SIZE_LANCZOS_TMP] temp_y | |
169 | + | |
170 | + cdef int i, j, k | |
171 | + cdef int m, n, o | |
172 | + | |
173 | + m = 0 | |
174 | + for k in xrange(zi, zf): | |
175 | + n = 0 | |
176 | + for j in xrange(yi, yf): | |
177 | + lx = 0 | |
178 | + for i in xrange(xi, xf): | |
179 | + lx += _G(V, i, j, k) * lanczos3_L(x - i, a) | |
180 | + temp_x[m][n] = lx | |
181 | + n += 1 | |
182 | + m += 1 | |
183 | + | |
184 | + m = 0 | |
185 | + for k in xrange(zi, zf): | |
186 | + n = 0 | |
187 | + ly = 0 | |
188 | + for j in xrange(yi, yf): | |
189 | + ly += temp_x[m][n] * lanczos3_L(y - j, a) | |
190 | + n += 1 | |
191 | + temp_y[m] = ly | |
192 | + m += 1 | |
193 | + | |
194 | + m = 0 | |
195 | + for k in xrange(zi, zf): | |
196 | + lz += temp_y[m] * lanczos3_L(z - k, a) | |
197 | + m += 1 | |
198 | + | |
199 | + return lz | |
200 | + | |
201 | + | |
202 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
203 | +@cython.cdivision(True) | |
204 | +@cython.wraparound(False) | |
126 | 205 | cdef image_t _G(image_t[:, :, :] V, int x, int y, int z) nogil: |
127 | 206 | cdef int dz, dy, dx |
128 | 207 | dz = V.shape[0] - 1 |
... | ... | @@ -311,4 +390,3 @@ def tricub_interpolate2_py(image_t[:, :, :] V, double x, double y, double z): |
311 | 390 | |
312 | 391 | def trilin_interpolate_py(image_t[:, :, :] V, double x, double y, double z): |
313 | 392 | return interpolate(V, x, y, z) |
314 | - | ... | ... |
invesalius/data/slice_.py
... | ... | @@ -91,6 +91,8 @@ class Slice(object): |
91 | 91 | self._type_projection = const.PROJECTION_NORMAL |
92 | 92 | self.n_border = const.PROJECTION_BORDER_SIZE |
93 | 93 | |
94 | + self.interp_method = 2 | |
95 | + | |
94 | 96 | self._spacing = (1.0, 1.0, 1.0) |
95 | 97 | self.center = [0, 0, 0] |
96 | 98 | |
... | ... | @@ -198,6 +200,8 @@ class Slice(object): |
198 | 200 | |
199 | 201 | Publisher.subscribe(self._fill_holes_auto, 'Fill holes automatically') |
200 | 202 | |
203 | + Publisher.subscribe(self._set_interpolation_method, 'Set interpolation method') | |
204 | + | |
201 | 205 | def GetMaxSliceNumber(self, orientation): |
202 | 206 | shape = self.matrix.shape |
203 | 207 | |
... | ... | @@ -624,7 +628,8 @@ class Slice(object): |
624 | 628 | if orientation == 'AXIAL': |
625 | 629 | tmp_array = np.array(self.matrix[slice_number:slice_number + number_slices]) |
626 | 630 | if np.any(self.q_orientation[1::]): |
627 | - transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, 2, self.matrix.min(), tmp_array) | |
631 | + transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, self.interp_method, self.matrix.min(), tmp_array) | |
632 | + print ">>>", tmp_array.min(), tmp_array.max() | |
628 | 633 | if self._type_projection == const.PROJECTION_NORMAL: |
629 | 634 | n_image = tmp_array.squeeze() |
630 | 635 | else: |
... | ... | @@ -671,7 +676,7 @@ class Slice(object): |
671 | 676 | elif orientation == 'CORONAL': |
672 | 677 | tmp_array = np.array(self.matrix[:, slice_number: slice_number + number_slices, :]) |
673 | 678 | if np.any(self.q_orientation[1::]): |
674 | - transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, 2, self.matrix.min(), tmp_array) | |
679 | + transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, self.interp_method, self.matrix.min(), tmp_array) | |
675 | 680 | |
676 | 681 | if self._type_projection == const.PROJECTION_NORMAL: |
677 | 682 | n_image = tmp_array.squeeze() |
... | ... | @@ -721,7 +726,7 @@ class Slice(object): |
721 | 726 | elif orientation == 'SAGITAL': |
722 | 727 | tmp_array = np.array(self.matrix[:, :, slice_number: slice_number + number_slices]) |
723 | 728 | if np.any(self.q_orientation[1::]): |
724 | - transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, 2, self.matrix.min(), tmp_array) | |
729 | + transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, self.interp_method, self.matrix.min(), tmp_array) | |
725 | 730 | |
726 | 731 | if self._type_projection == const.PROJECTION_NORMAL: |
727 | 732 | n_image = tmp_array.squeeze() |
... | ... | @@ -953,6 +958,10 @@ class Slice(object): |
953 | 958 | tprojection = pubsub_evt.data |
954 | 959 | self.SetTypeProjection(tprojection) |
955 | 960 | |
961 | + def _set_interpolation_method(self, pubsub_evt): | |
962 | + interp_method = pubsub_evt.data | |
963 | + self.SetInterpolationMethod(interp_method) | |
964 | + | |
956 | 965 | def SetTypeProjection(self, tprojection): |
957 | 966 | if self._type_projection != tprojection: |
958 | 967 | if self._type_projection == const.PROJECTION_NORMAL: |
... | ... | @@ -969,6 +978,13 @@ class Slice(object): |
969 | 978 | |
970 | 979 | Publisher.sendMessage('Check projection menu', tprojection) |
971 | 980 | |
981 | + def SetInterpolationMethod(self, interp_method): | |
982 | + if self.interp_method != interp_method: | |
983 | + self.interp_method = interp_method | |
984 | + for buffer_ in self.buffer_slices.values(): | |
985 | + buffer_.discard_buffer() | |
986 | + Publisher.sendMessage('Reload actual slice') | |
987 | + | |
972 | 988 | def UpdateWindowLevelBackground(self, pubsub_evt): |
973 | 989 | window, level = pubsub_evt.data |
974 | 990 | self.window_width = window |
... | ... | @@ -1423,7 +1439,7 @@ class Slice(object): |
1423 | 1439 | T1 = transformations.translation_matrix((cz, cy, cx)) |
1424 | 1440 | M = transformations.concatenate_matrices(T1, R.T, T0) |
1425 | 1441 | |
1426 | - transforms.apply_view_matrix_transform(mcopy, self.spacing, M, 0, 'AXIAL', 2, mcopy.min(), self.matrix) | |
1442 | + transforms.apply_view_matrix_transform(mcopy, self.spacing, M, 0, 'AXIAL', self.interp_method, mcopy.min(), self.matrix) | |
1427 | 1443 | |
1428 | 1444 | del mcopy |
1429 | 1445 | os.remove(temp_file) | ... | ... |
invesalius/data/transforms.pyx
... | ... | @@ -3,11 +3,12 @@ cimport numpy as np |
3 | 3 | cimport cython |
4 | 4 | |
5 | 5 | from .cy_my_types cimport image_t |
6 | -from .interpolation cimport interpolate, tricub_interpolate, tricubicInterpolate | |
6 | +from .interpolation cimport interpolate, tricub_interpolate, tricubicInterpolate, lanczos3, nearest_neighbour_interp | |
7 | 7 | |
8 | 8 | from libc.math cimport floor, ceil, sqrt, fabs, round |
9 | 9 | from cython.parallel import prange |
10 | 10 | |
11 | +ctypedef double (*interp_function)(image_t[:, :, :], double, double, double) nogil | |
11 | 12 | |
12 | 13 | @cython.boundscheck(False) # turn of bounds-checking for entire function |
13 | 14 | @cython.cdivision(True) |
... | ... | @@ -25,7 +26,7 @@ cdef void mul_mat4_vec4(double[:, :] M, |
25 | 26 | @cython.boundscheck(False) # turn of bounds-checking for entire function |
26 | 27 | @cython.cdivision(True) |
27 | 28 | @cython.wraparound(False) |
28 | -cdef image_t coord_transform(image_t[:, :, :] volume, double[:, :] M, int x, int y, int z, double sx, double sy, double sz, short minterpol, image_t cval) nogil: | |
29 | +cdef image_t coord_transform(image_t[:, :, :] volume, double[:, :] M, int x, int y, int z, double sx, double sy, double sz, interp_function f_interp, image_t cval) nogil: | |
29 | 30 | |
30 | 31 | cdef double coord[4] |
31 | 32 | coord[0] = z*sz |
... | ... | @@ -53,15 +54,21 @@ cdef image_t coord_transform(image_t[:, :, :] volume, double[:, :] M, int x, int |
53 | 54 | cdef double v |
54 | 55 | |
55 | 56 | if 0 <= nz <= (dz-1) and 0 <= ny <= (dy-1) and 0 <= nx <= (dx-1): |
56 | - if minterpol == 0: | |
57 | - return volume[<int>round(nz), <int>round(ny), <int>round(nx)] | |
58 | - elif minterpol == 1: | |
59 | - return <image_t>interpolate(volume, nx, ny, nz) | |
60 | - else: | |
61 | - v = tricubicInterpolate(volume, nx, ny, nz) | |
62 | - if (v < cval): | |
63 | - v = cval | |
64 | - return <image_t>v | |
57 | + return <image_t>f_interp(volume, nx, ny, nz) | |
58 | + # if minterpol == 0: | |
59 | + # return <image_t>nearest_neighbour_interp(volume, nx, ny, nz) | |
60 | + # elif minterpol == 1: | |
61 | + # return <image_t>interpolate(volume, nx, ny, nz) | |
62 | + # elif minterpol == 2: | |
63 | + # v = tricubicInterpolate(volume, nx, ny, nz) | |
64 | + # if (v < cval): | |
65 | + # v = cval | |
66 | + # return <image_t>v | |
67 | + # else: | |
68 | + # v = lanczos3(volume, nx, ny, nz) | |
69 | + # if (v < cval): | |
70 | + # v = cval | |
71 | + # return <image_t>v | |
65 | 72 | else: |
66 | 73 | return cval |
67 | 74 | |
... | ... | @@ -95,23 +102,34 @@ def apply_view_matrix_transform(image_t[:, :, :] volume, |
95 | 102 | sy = spacing[1] |
96 | 103 | sz = spacing[2] |
97 | 104 | |
105 | + cdef interp_function f_interp | |
106 | + | |
107 | + if minterpol == 0: | |
108 | + f_interp = nearest_neighbour_interp | |
109 | + elif minterpol == 1: | |
110 | + f_interp = interpolate | |
111 | + elif minterpol == 2: | |
112 | + f_interp = tricubicInterpolate | |
113 | + else: | |
114 | + f_interp = lanczos3 | |
115 | + | |
98 | 116 | if orientation == 'AXIAL': |
99 | 117 | for z in xrange(n, n+odz): |
100 | 118 | for y in prange(dy, nogil=True): |
101 | 119 | for x in xrange(dx): |
102 | - out[count, y, x] = coord_transform(volume, M, x, y, z, sx, sy, sz, minterpol, cval) | |
120 | + out[count, y, x] = coord_transform(volume, M, x, y, z, sx, sy, sz, f_interp, cval) | |
103 | 121 | count += 1 |
104 | 122 | |
105 | 123 | elif orientation == 'CORONAL': |
106 | 124 | for y in xrange(n, n+ody): |
107 | 125 | for z in prange(dz, nogil=True): |
108 | 126 | for x in xrange(dx): |
109 | - out[z, count, x] = coord_transform(volume, M, x, y, z, sx, sy, sz, minterpol, cval) | |
127 | + out[z, count, x] = coord_transform(volume, M, x, y, z, sx, sy, sz, f_interp, cval) | |
110 | 128 | count += 1 |
111 | 129 | |
112 | 130 | elif orientation == 'SAGITAL': |
113 | 131 | for x in xrange(n, n+odx): |
114 | 132 | for z in prange(dz, nogil=True): |
115 | 133 | for y in xrange(dy): |
116 | - out[z, y, count] = coord_transform(volume, M, x, y, z, sx, sy, sz, minterpol,cval) | |
134 | + out[z, y, count] = coord_transform(volume, M, x, y, z, sx, sy, sz, f_interp, cval) | |
117 | 135 | count += 1 | ... | ... |
invesalius/gui/dialogs.py
... | ... | @@ -1883,26 +1883,38 @@ class ReorientImageDialog(wx.Dialog): |
1883 | 1883 | self._bind_events_wx() |
1884 | 1884 | |
1885 | 1885 | def _init_gui(self): |
1886 | + interp_methods_choices = ((_("Nearest Neighbour"), 0), | |
1887 | + (_("Trilinear"), 1), | |
1888 | + (_("Tricubic"), 2), | |
1889 | + (_("Lanczos"), 3)) | |
1890 | + self.interp_method = wx.ComboBox(self, -1, choices=[], style=wx.CB_READONLY) | |
1891 | + for txt, im_code in interp_methods_choices: | |
1892 | + self.interp_method.Append(txt, im_code) | |
1893 | + self.interp_method.SetValue(interp_methods_choices[2][0]) | |
1894 | + | |
1886 | 1895 | self.anglex = wx.TextCtrl(self, -1, "0.0", style=wx.TE_READONLY) |
1887 | 1896 | self.angley = wx.TextCtrl(self, -1, "0.0", style=wx.TE_READONLY) |
1888 | 1897 | self.anglez = wx.TextCtrl(self, -1, "0.0", style=wx.TE_READONLY) |
1889 | 1898 | |
1890 | 1899 | self.btnapply = wx.Button(self, -1, _("Apply")) |
1891 | 1900 | |
1892 | - sizer = wx.BoxSizer(wx.HORIZONTAL) | |
1901 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
1893 | 1902 | |
1894 | - sizer.Add(wx.StaticText(self, -1, _("Angle X")), 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) | |
1895 | - sizer.Add(self.anglex, 0, wx.EXPAND | wx.ALL, 5) | |
1896 | - sizer.AddSpacer(5) | |
1903 | + angles_sizer = wx.FlexGridSizer(3, 2, 5, 5) | |
1904 | + angles_sizer.AddMany([ | |
1905 | + (wx.StaticText(self, -1, _("Angle X")), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5), | |
1906 | + (self.anglex, 0, wx.EXPAND | wx.ALL, 5), | |
1897 | 1907 | |
1898 | - sizer.Add(wx.StaticText(self, -1, _("Angle Y")), 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) | |
1899 | - sizer.Add(self.angley, 0, wx.EXPAND | wx.ALL, 5) | |
1900 | - sizer.AddSpacer(5) | |
1908 | + (wx.StaticText(self, -1, _("Angle Y")), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5), | |
1909 | + (self.angley, 0, wx.EXPAND | wx.ALL, 5), | |
1901 | 1910 | |
1902 | - sizer.Add(wx.StaticText(self, -1, _("Angle Z")), 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) | |
1903 | - sizer.Add(self.anglez, 0, wx.EXPAND | wx.ALL, 5) | |
1904 | - sizer.AddSpacer(5) | |
1911 | + (wx.StaticText(self, -1, _("Angle Z")), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5), | |
1912 | + (self.anglez, 0, wx.EXPAND | wx.ALL, 5), | |
1913 | + ]) | |
1905 | 1914 | |
1915 | + sizer.Add(wx.StaticText(self, -1, _("Interpolation method:")), 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5) | |
1916 | + sizer.Add(self.interp_method, 0, wx.EXPAND | wx.ALL, 5) | |
1917 | + sizer.Add(angles_sizer, 0, wx.EXPAND | wx.ALL, 5) | |
1906 | 1918 | sizer.Add(self.btnapply, 0, wx.EXPAND | wx.ALL, 5) |
1907 | 1919 | sizer.AddSpacer(5) |
1908 | 1920 | |
... | ... | @@ -1914,6 +1926,7 @@ class ReorientImageDialog(wx.Dialog): |
1914 | 1926 | Publisher.subscribe(self._close_dialog, 'Close reorient dialog') |
1915 | 1927 | |
1916 | 1928 | def _bind_events_wx(self): |
1929 | + self.interp_method.Bind(wx.EVT_COMBOBOX, self.OnSelect) | |
1917 | 1930 | self.btnapply.Bind(wx.EVT_BUTTON, self.apply_reorientation) |
1918 | 1931 | self.Bind(wx.EVT_CLOSE, self.OnClose) |
1919 | 1932 | |
... | ... | @@ -1935,6 +1948,9 @@ class ReorientImageDialog(wx.Dialog): |
1935 | 1948 | Publisher.sendMessage('Enable style', const.STATE_DEFAULT) |
1936 | 1949 | self.Destroy() |
1937 | 1950 | |
1951 | + def OnSelect(self, evt): | |
1952 | + im_code = self.interp_method.GetClientData(self.interp_method.GetSelection()) | |
1953 | + Publisher.sendMessage('Set interpolation method', im_code) | |
1938 | 1954 | |
1939 | 1955 | |
1940 | 1956 | class ImportBitmapParameters(wx.Dialog): | ... | ... |