Newer
Older
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 2D Structure tensor - a few examples\n",
"A few examples of using 2D structure tensor. The examples show different ways of visualizing the orientations, visualizing the distribution of orientations, and how parameter choices influences the orientationa analysis.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import skimage.io\n",
"import scipy.ndimage\n",
"import matplotlib.pyplot as plt\n",
"import st2d "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 1\n",
"Orientations computed from simple testing image, and visualized using orientation arrows or color. \n",
"\n",
"When visualizing predominant orientation information as color without the overlay of intensity image, it may seem as orientation changes abruptly in some areas. However while dominant orientation changes, the anisotropy is low in these areas. This means that transition from one orientation (ellipse elonagted in one direction) to another orientation (ellipse elongated in another directrion) is over an isotropy (a circle). Therefore, the full orientation information (dominant orientation and anisotropy) is smoothly changing. "
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The image has a shape (50, 50), i.e. 2500 pixels.\n",
"Structure tensor information is carried in a (3, 2500) array.\n",
"Orientation information is carried in a (2, 2500) array.\n",
"Anisotropy information is carried in a (2, 2500) array.\n"
]
},
{
"ename": "NameError",
"evalue": "name 'plot_orientations' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-2-a3b5350fc3be>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msubplots\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfigsize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfigsize\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msharex\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msharey\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimshow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcmap\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgray\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0mplot_orientations\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0max\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mimage\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvec\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 18\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_title\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Orientation as arrows'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0morientation_st_rgba\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhsv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marctan2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvec\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvec\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreshape\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mNameError\u001b[0m: name 'plot_orientations' is not defined"
]
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 1440x360 with 5 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"filename = 'example_data_2D/drawn_fibres_B.png';\n",
"sigma = 0.5\n",
"rho = 2\n",
"\n",
"image = skimage.io.imread(filename)\n",
"S = st2d.structure_tensor(image, sigma, rho)\n",
"val,vec = st2d.eig_special(S)\n",
"print(f'The image has a shape {image.shape}, i.e. {image.size} pixels.')\n",
"print(f'Structure tensor information is carried in a {S.shape} array.')\n",
"print(f'Orientation information is carried in a {vec.shape} array.')\n",
"print(f'Anisotropy information is carried in a {val.shape} array.')\n",
"\n",
"# visualization\n",
"figsize = (20,5)\n",
"fig, ax = plt.subplots(1, 5, figsize=figsize, sharex=True, sharey=True)\n",
"ax[0].imshow(image, cmap=plt.cm.gray)\n",
"plot_orientations(ax[0], image.shape, vec)\n",
"ax[0].set_title('Orientation as arrows')\n",
"orientation_st_rgba = plt.cm.hsv((np.arctan2(vec[1], vec[0])/np.pi).reshape(image.shape))\n",
"ax[1].imshow(plt.cm.gray(image)*orientation_st_rgba)\n",
"ax[1].set_title('Orientation as color on image')\n",
"ax[2].imshow(orientation_st_rgba)\n",
"ax[2].set_title('Orientation as color')\n",
"anisotropy = (1-val[0]/val[1]).reshape(image.shape)\n",
"ax[3].imshow(anisotropy)\n",
"ax[3].set_title('Degree of anisotropy')\n",
"ax[4].imshow(plt.cm.gray(anisotropy)*orientation_st_rgba)\n",
"ax[4].set_title('Orientation and anisotropy')\n",
"plt.show() "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 2\n",
"Orientation analysis of a slice showing carbord fibres. This example shows how orientation may be collected and shown as a histogram over angles or as a polar histogram. \n",
"\n",
"Here I collect the orientation information on the whole image. However, as shown above, the orientation information is reliable only in areas of hight anisotropy. For this reason, and depending on problem at hand, it may be desirable to weight the orientation using the anisotropy (remove parts with low anisotropy) or the intensity (remove the background and keep only information of fibres). "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"filename = 'example_data_2D/10X.png';\n",
"sigma = 0.5\n",
"rho = 15\n",
"N = 180 # number of angle bins for orientation histogram\n",
"\n",
"# computation\n",
"image = skimage.io.imread(filename)\n",
"image = np.mean(image[:,:,0:3],axis=2).astype(np.uint8)\n",
"S = st2d.structure_tensor(image, sigma, rho)\n",
"val,vec = st2d.eig_special(S)\n",
"angles = np.arctan2(vec[1], vec[0]) # angles from 0 to pi\n",
"distribution = np.histogram(angles, bins=N, range=(0.0, np.pi))[0]\n",
"\n",
"# visualization\n",
"figsize = (10,5)\n",
"fig, ax = plt.subplots(1, 2, figsize=figsize, sharex=True, sharey=True)\n",
"ax[0].imshow(image,cmap=plt.cm.gray)\n",
"ax[0].set_title('Input image')\n",
"orientation_st_rgba = plt.cm.hsv((angles/np.pi).reshape(image.shape))\n",
"ax[1].imshow(plt.cm.gray(image)*orientation_st_rgba)\n",
"ax[1].set_title('Orientation as color on image')\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=figsize)\n",
"bin_centers = (np.arange(N)+0.5)*np.pi/N # halp circle (180 deg)\n",
"colors = plt.cm.hsv(bin_centers/np.pi)\n",
"ax[0].bar(bin_centers, distribution, width = np.pi/N, color = colors)\n",
"ax[0].set_xlabel('angle')\n",
"ax[0].set_xlim([0,np.pi])\n",
"ax[0].set_aspect(np.pi/ax[0].get_ylim()[1])\n",
"ax[0].set_xticks([0,np.pi/2,np.pi])\n",
"ax[0].set_xticklabels(['0','pi/2','pi'])\n",
"ax[0].set_ylabel('count')\n",
"ax[0].set_title('Histogram over angles')\n",
"polar_histogram(ax[1], distribution)\n",
"ax[1].set_title('Polar histogram')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## OCT example\n",
"A example of of computing orientation information from the OCT image of retina."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"filename = 'example_data_2D/OCT_im_org.png';\n",
"sigma = 0.5\n",
"rho = 5\n",
"N = 180 # number of angle bins for orientation histogram\n",
"\n",
"# computation\n",
"image = skimage.io.imread(filename)\n",
"S = st2d.structure_tensor(image, sigma, rho)\n",
"val,vec = st2d.eig_special(S)\n",
"angles = np.arctan2(vec[1], vec[0]) # angles from 0 to pi\n",
"distribution = np.histogram(angles, bins=N, range=(0.0, np.pi))[0]\n",
"\n",
"# visualization\n",
"figsize = (10,5)\n",
"fig, ax = plt.subplots(1, 2, figsize=figsize, sharex=True, sharey=True)\n",
"ax[0].imshow(image, cmap=plt.cm.gray)\n",
"ax[0].set_title('Input image')\n",
"orientation_st_rgba = plt.cm.hsv((angles/np.pi).reshape(image.shape))\n",
"ax[1].imshow(plt.cm.gray(image)*orientation_st_rgba)\n",
"ax[1].set_title('Orientation as color on image')\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=figsize)\n",
"bin_centers = (np.arange(N)+0.5)*np.pi/N # halp circle (180 deg)\n",
"colors = plt.cm.hsv(bin_centers/np.pi)\n",
"ax[0].bar(bin_centers, distribution, width = np.pi/N, color = colors)\n",
"ax[0].set_xlabel('angle')\n",
"ax[0].set_xlim([0,np.pi])\n",
"ax[0].set_aspect(np.pi/ax[0].get_ylim()[1])\n",
"ax[0].set_xticks([0,np.pi/2,np.pi])\n",
"ax[0].set_xticklabels(['0','pi/2','pi'])\n",
"ax[0].set_ylabel('count')\n",
"ax[0].set_title('Histogram over angles')\n",
"polar_histogram(ax[1], distribution)\n",
"ax[1].set_title('Polar histogram')\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Investigating the effect of integration size rho\n",
"Integration size rho is a crucial parameter when working with structure tensor, and especially if ofientation information is to be sampled om the image. This example shows how small rho captures the randomness of the individual fibre, and equidistantly placed orientation arrows will appear random. Larger rho captures the orientation on a larger scale, and flow appears smoother. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"filename = 'example_data_2D/short_fibres.png'\n",
"image = skimage.io.imread(filename)\n",
"image = np.mean(image[:,:,0:3],axis=2)\n",
"image -= np.min(image)\n",
"image /= np.max(image)\n",
"s = 128 # quiver arrow spacing\n",
"sigma = 0.5\n",
"figsize = (20,10)\n",
"\n",
"rhos = [2,10,20,50]\n",
"\n",
"fig, ax = plt.subplots(2, len(rhos), figsize=figsize, sharex=True, sharey=True)\n",
"for k in range(4):\n",
"\n",
" # computation\n",
" rho = rhos[k] # changing integration radius\n",
" S = st2d.structure_tensor(image,sigma,rho)\n",
" val,vec = st2d.eig_special(S)\n",
"\n",
" # visualization\n",
" ax[0][k].imshow(image,cmap=plt.cm.gray)\n",
" plot_orientations(ax[0][k], image.shape, vec, s = s)\n",
" ax[0][k].set_title(f'Rho = {rho}, arrows')\n",
" intensity_rgba = plt.cm.gray(image)\n",
" orientation_st_rgba = plt.cm.hsv((np.arctan2(vec[1], vec[0])/np.pi).reshape(image.shape))\n",
" ax[1][k].imshow((0.5+0.5*intensity_rgba)*orientation_st_rgba)\n",
" ax[1][k].set_title(f'Rho = {rho}, color')\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Investigating the effect of downsampling\n",
"This example demonstrates the robustness of structure tensor to downsampling. In highest resolution, individual fibres can be distinguished, and large rho is used to capture a smooth flow. As smaller resolutions, individual fibres cannot be distinguished, but the orientation information, now using a smaller rho, is still rather stable."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"filename = 'example_data_2D/short_fibres.png'\n",
"downsampling_range = 4\n",
"figsize = (20,10) \n",
"fig, ax = plt.subplots(2, downsampling_range, figsize=figsize)\n",
"\n",
"for k in range(downsampling_range):\n",
"\n",
" # downsampling and computation\n",
" scale = 2**k\n",
" f = 1/scale # image scale factor\n",
" s = 128//scale # quiver arrow spacing\n",
" sigma = 0.5 # would it make sense to scale this too?\n",
" rho = 50/scale # scaling the integration radius\n",
" image = skimage.io.imread(filename)\n",
" image = np.mean(image[:,:,0:3],axis=2)\n",
" image = skimage.transform.rescale(image,f,multichannel=False)\n",
" image -= np.min(image)\n",
" image /= np.max(image)\n",
" S = st2d.structure_tensor(image,sigma,rho)\n",
" val,vec = st2d.eig_special(S)\n",
"\n",
" # visualization\n",
" ax[0][k].imshow(image,cmap=plt.cm.gray)\n",
" plot_orientations(ax[0][k], image.shape, vec, s = s)\n",
" ax[0][k].set_title(f'Downsample = {scale}, arrows')\n",
" intensity_rgba = plt.cm.gray(image)\n",
" orientation_st_rgba = plt.cm.hsv((np.arctan2(vec[1], vec[0])/np.pi).reshape(image.shape))\n",
" ax[1][k].imshow((0.5+0.5*intensity_rgba)*orientation_st_rgba) \n",
" ax[1][k].set_title(f'Downsample = {scale}, color')\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Comparing the dominant orientation with the optical flow\n",
"When imaged structures are mostly unidirectional, and we are interested in computing the deviation from this given direction, it may be favorable to compute optical flow. While orientation from structure tensor yields a $(x,y)$ vector per pixel, optical flow in $y$ direction yields a $(x,1)$ vector. However, where assumption on uni-directionality is broken, optical flow is still returning a flow in $y$ direction. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"image = skimage.io.imread('example_data_2D/drawn_fibres_B.png');\n",
"\n",
"# computing structure tensor, orientation and optical flow\n",
"sigma = 0.5\n",
"rho = 5\n",
"S = st2d.structure_tensor(image,sigma,rho)\n",
"val,vec = st2d.eig_special(S) # dominant orientation\n",
"fx = st2d.solve_flow(S) # optical flow\n",
"\n",
"# visualization\n",
"figsize = (10,10)\n",
"fy = np.ones(image.shape)\n",
"fig, ax = plt.subplots(2,2,figsize=figsize)\n",
"\n",
"ax[0][0].imshow(image,cmap=plt.cm.gray)\n",
"plot_orientations(ax[0][0], image.shape, vec)\n",
"ax[0][0].set_title('Orientation from structure tensor, arrows')\n",
"ax[0][1].imshow(image,cmap=plt.cm.gray)\n",
"plot_orientations(ax[0][1], image.shape, np.r_[fx,np.ones((1,image.size))])\n",
"ax[0][1].set_title('Orientation from optical flow, arrows')\n",
"intensity_rgba = plt.cm.gray(image)\n",
"orientation_st_rgba = plt.cm.hsv((np.arctan2(vec[1],vec[0])/np.pi).reshape(image.shape))\n",
"orientation_of_rgba = plt.cm.hsv((np.arctan2(1,fx)/np.pi).reshape(image.shape))\n",
"ax[1][0].imshow(intensity_rgba*orientation_st_rgba)\n",
"ax[1][0].set_title('Dominant orientation from structure tensor, color')\n",
"ax[1][1].imshow(intensity_rgba*orientation_of_rgba)\n",
"ax[1][1].set_title('Orientation from optical flow, color')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}