Skip to content

Commit 5b43dc0

Browse files
authored
Merge pull request #12 from LaurentRDC/develop
Release version 0.4.5
2 parents 90e114b + 7fb11c2 commit 5b43dc0

119 files changed

Lines changed: 927 additions & 377 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,4 @@ ENV/
9999

100100
# PyCharm
101101
.idea/
102+
benchmark.py

docs/source/api.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ Baseline-determination
5656

5757
Image Analysis
5858
==============
59-
.. automodule:: skued.image_analysis.powder
59+
.. automodule:: skued.image.powder
6060

61-
.. automodule:: skued.image_analysis.alignment
61+
.. automodule:: skued.image.alignment
62+
63+
.. automodule:: skued.image.streaming
64+
65+
.. automodule:: skued.image.symmetry
66+
67+
.. automodule:: skued.image.correlation

docs/source/tutorials/image.rst

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
.. include:: ../references.txt
2+
3+
.. _image_analysis_tutorial:
4+
5+
***********************
6+
Image Analysis Tutorial
7+
***********************
8+
9+
Due to the high electron cross-section, background signals (or baseline) are
10+
much more of a problem for electron diffraction than equivalent X-ray experiments.
11+
12+
Contents
13+
========
14+
15+
* :ref:`streaming`
16+
* :ref:`powder`
17+
18+
.. _streaming:
19+
20+
Streaming Image Processing
21+
==========================
22+
23+
Diffraction datasets can be large, much larger than a machine can handle
24+
in-memory. In this case, it makes sense to assemble processing pipelines instead
25+
of working on the data all at once.
26+
27+
Consider the following snippet to combine 50 images
28+
from an iterable :code:`source`::
29+
30+
import numpy as np
31+
32+
images = np.empty( shape = (2048, 2048, 50) )
33+
from index, im in enumerate(source):
34+
images[:,:,index] = im
35+
36+
avg = np.average(images, axis = 2)
37+
38+
If the :code:`source` iterable provided 1000 images, the above routine would
39+
not work on most machines. Moreover, what if we want to transform the images
40+
one by one before averaging them? What about looking at the average while it
41+
is being computed?
42+
43+
Scikit-ued provides some functions that can make streaming processing possible. These
44+
function will have an 'i' prefix (for :code:`iterator`). Let's look at an example::
45+
46+
import numpy as np
47+
from skued.image import ialign, iaverage
48+
from skimage.io import imread
49+
50+
stream = map(imread, list_of_filenames)
51+
aligned = ialign(stream)
52+
averaged = iaverage(aligned)
53+
54+
At this point, the generators :code:`map`, :code:`ialign`, and :code:`iaverage` are 'wired'
55+
but will not compute anything until it is requested. We can use the function
56+
:code:`last` to get at the final average, but we could also look at the average
57+
step-by-step by calling :code:`next`::
58+
59+
avg = next(averaged) # only one images is loaded, aligned and added to the average
60+
total = last(averaged) # average of the entire stream
61+
62+
An important advantage of processing images in a streaming fashion is the lower
63+
memory usage; this allows the use of multiple processes in parallel::
64+
65+
from skued import pmap
66+
67+
def align_and_avg(filenames):
68+
stream = map(imread, filenames)
69+
aligned = ialign(stream)
70+
return last(iaverage(aligned))
71+
72+
batches = [list_of_filenames1, list_of_filenames2, list_of_filenames3]
73+
74+
for avg in pmap(align_and_avg, batches, processes = 3):
75+
# write to disk of display
76+
pass
77+
78+
Example: averaging with error
79+
------------------------------
80+
81+
It is possible to combine :code:`iaverage` and :code:`isem` into a single stream using :code:`itertools.tee`.
82+
Here is a recipe for it::
83+
84+
def iaverage_with_error(images, weights):
85+
"""
86+
Combined streaming average and standard error of diffraction images.
87+
88+
Parameters
89+
----------
90+
images : iterable of ndarrays
91+
Images to be averaged. This iterable can also a generator.
92+
weights : iterable of ndarray, iterable of floats, or None, optional
93+
Array of weights. See `numpy.average` for further information. If None (default),
94+
total picture intensity of valid pixels is used to weight each picture.
95+
96+
Yields
97+
------
98+
avg: `~numpy.ndarray`
99+
Weighted average.
100+
sem: `~numpy.ndarray`
101+
Weighted average.
102+
"""
103+
stream1, stream2 = itertools.tee(images, n = 2)
104+
averages = iaverage(stream1, weights = weights)
105+
errors = isem(stream2)
106+
yield from zip(averages, errors)
107+
108+
.. _powder:
109+
110+
Image analysis on polycrystalline diffraction patterns
111+
======================================================
112+
113+
Center-finding
114+
--------------
115+
Polycrystalline diffraction patterns display concentric rings, and finding
116+
the center of those concentric rings is important.
117+
118+
Let's load a test image::
119+
120+
from skimage import img_as_uint
121+
from skimage.io import imread
122+
import matplotlib.pyplot as plt
123+
124+
path = '\\data\\vo2.tif'
125+
im = img_as_uint(imread(path, plugin = 'tifffile'))
126+
127+
mask = np.zeros_like(im, dtype = np.bool)
128+
mask[0:1250, 700:1100] = True
129+
im[mask] = 0
130+
131+
plt.imshow(im, vmin = 1000, vmax = 1200)
132+
plt.show()
133+
134+
.. plot::
135+
136+
from skimage import img_as_uint
137+
from skimage.io import imread
138+
import matplotlib.pyplot as plt
139+
path = 'data\\vo2.tif'
140+
im = img_as_uint(imread(path, plugin = 'tifffile'))
141+
mask = np.zeros_like(im, dtype = np.bool)
142+
mask[0:1250, 700:1100] = True
143+
im[mask] = 0
144+
plt.imshow(im, vmin = 1000, vmax = 1200)
145+
plt.show()
146+
147+
This is a noisy diffraction pattern of polycrystalline vanadium dioxide.
148+
Finding the center of such a symmetry pattern can be done with the
149+
:code:`powder_center` routine::
150+
151+
from skued.image import powder_center
152+
ic, jc = powder_center(im, mask = mask)
153+
154+
# Plotting the center as a black disk
155+
import numpy as np
156+
ii, jj = np.meshgrid(np.arange(im.shape[0]), np.arange(im.shape[1]),
157+
indexing = 'ij')
158+
rr = np.sqrt((ii - ic)**2 + (jj - jc)**2)
159+
im[rr < 100] = 0
160+
161+
plt.imshow(im, vmax = 1200)
162+
plt.show()
163+
164+
.. plot::
165+
166+
from skimage import img_as_uint
167+
from skimage.io import imread
168+
import numpy as np
169+
import matplotlib.pyplot as plt
170+
path = 'data\\vo2.tif'
171+
im = img_as_uint(imread(path, plugin = 'tifffile'))
172+
from skued.image import powder_center
173+
mask = np.zeros_like(im, dtype = np.bool)
174+
mask[0:1250, 700:1100] = True
175+
ic, jc = powder_center(im, mask = mask)
176+
ii, jj = np.meshgrid(np.arange(im.shape[0]), np.arange(im.shape[1]),indexing = 'ij')
177+
rr = np.sqrt((ii - ic)**2 + (jj - jc)**2)
178+
im[rr < 100] = 0
179+
plt.imshow(im, vmin = 1000, vmax = 1200)
180+
plt.show()
181+
182+
Angular average
183+
---------------
184+
185+
First, we create a test image::
186+
187+
import numpy as np
188+
import matplotlib.pyplot as plt
189+
from skued import gaussian
190+
191+
image = np.zeros( (256, 256) )
192+
xc, yc = image.shape[0]/2, image.shape[1]/2 # center
193+
194+
extent = np.arange(0, image.shape[0])
195+
xx, yy = np.meshgrid(extent, extent)
196+
rr = np.sqrt((xx - xc)**2 + (yy-yc)**2)
197+
image += gaussian([xx, yy], center = [xc, yc], fwhm = 200)
198+
image[np.logical_and(rr < 40, rr > 38)] = 1
199+
image[np.logical_and(rr < 100, rr > 98)] = 0.5
200+
image /= image.max() # Normalize max to 1
201+
image += np.random.random(size = image.shape)
202+
203+
plt.imshow(image)
204+
plt.show()
205+
206+
.. plot::
207+
208+
import numpy as np
209+
import matplotlib.pyplot as plt
210+
from skued import gaussian
211+
image = np.zeros( (256, 256) )
212+
xc, yc = image.shape[0]/2, image.shape[1]/2 # center
213+
extent = np.arange(0, image.shape[0])
214+
xx, yy = np.meshgrid(extent, extent)
215+
rr = np.sqrt((xx - xc)**2 + (yy-yc)**2)
216+
image += gaussian([xx, yy], center = [xc, yc], fwhm = 200)
217+
image[np.logical_and(rr < 40, rr > 38)] = 1
218+
image[np.logical_and(rr < 100, rr > 98)] = 0.5
219+
image /= image.max() # Normalize max to 1
220+
image += np.random.random(size = image.shape)
221+
plt.imshow(image)
222+
plt.show()
223+
224+
225+
... and we can easily compute an angular average::
226+
227+
from skued.image import angular_average
228+
229+
radius, intensity = angular_average(image, (xc, yc))
230+
231+
plt.plot(radius, intensity)
232+
233+
.. plot::
234+
235+
from skued.image import angular_average
236+
from skued import gaussian
237+
import numpy as np
238+
import matplotlib.pyplot as plt
239+
from skued import gaussian
240+
image = np.zeros( (256, 256) )
241+
xc, yc = image.shape[0]/2, image.shape[1]/2 # center
242+
extent = np.arange(0, image.shape[0])
243+
xx, yy = np.meshgrid(extent, extent)
244+
rr = np.sqrt((xx - xc)**2 + (yy-yc)**2)
245+
image += gaussian([xx, yy], center = [xc, yc], fwhm = 200)
246+
image[np.logical_and(rr < 40, rr > 38)] = 1
247+
image[np.logical_and(rr < 100, rr > 98)] = 0.5
248+
image /= image.max() # Normalize max to 1
249+
image += np.random.random(size = image.shape)
250+
radius, intensity = angular_average(image, (xc, yc))
251+
plt.plot(radius, intensity)
252+
plt.show()
253+
254+
:ref:`Return to Top <image_analysis_tutorial>`

0 commit comments

Comments
 (0)