3
3
__version__ = "0.1.0"
4
4
5
5
__all__ = [
6
- ' load_image' ,
7
- ' normalize_psf' ,
8
- ' center_psf' ,
9
- ' generate_otf' ,
10
- ' normalize_otf' ,
11
- ' save_otf' ,
12
- ' load_otf' ,
13
- ]
6
+ " load_image" ,
7
+ " normalize_psf" ,
8
+ " center_psf" ,
9
+ " generate_otf" ,
10
+ " normalize_otf" ,
11
+ " save_otf" ,
12
+ " load_otf" ,
13
+ ]
14
14
15
15
import itk
16
16
import numpy as np
17
17
import scipy .optimize
18
18
19
+
19
20
def load_image (filename , nphases = 3 , norientations = 1 , spacing = None ):
20
21
"""Load SIM acquisition data from an image file.
21
22
@@ -48,16 +49,20 @@ def load_image(filename, nphases=3, norientations=1, spacing=None):
48
49
data_view = itk .array_view_from_image (image )
49
50
50
51
size = itk .size (image )
51
- components = nphases * norientations
52
+ components = nphases * norientations
52
53
53
54
result_shape = list (data_view .shape )
54
55
result_shape [0 ] = int (result_shape [0 ] / components )
55
- result_shape = result_shape + [components ,]
56
+ result_shape = result_shape + [
57
+ components ,
58
+ ]
56
59
result = np .empty (result_shape , dtype = np .float32 )
57
60
for component in range (components ):
58
- result [:,:,:, component ] = data_view [component ::components ,:, :]
61
+ result [:, :, :, component ] = data_view [component ::components , :, :]
59
62
60
- result = itk .PyBuffer [itk .VectorImage [itk .F , 3 ]].GetImageViewFromArray (result , is_vector = True )
63
+ result = itk .PyBuffer [itk .VectorImage [itk .F , 3 ]].GetImageViewFromArray (
64
+ result , is_vector = True
65
+ )
61
66
result .SetOrigin (image .GetOrigin ())
62
67
result .SetSpacing (image .GetSpacing ())
63
68
result .SetDirection (image .GetDirection ())
@@ -66,6 +71,7 @@ def load_image(filename, nphases=3, norientations=1, spacing=None):
66
71
67
72
return result
68
73
74
+
69
75
def normalize_psf (psf ):
70
76
"""Remove DC fourier component, normalize information component to sum to
71
77
unity.
@@ -92,13 +98,16 @@ def normalize_psf(psf):
92
98
sum_ = np .sum (result [..., component ])
93
99
result [..., component ] = result [..., component ] / sum_
94
100
95
- result_image = itk .PyBuffer [itk .VectorImage [itk .F , 3 ]].GetImageViewFromArray (result , is_vector = True )
101
+ result_image = itk .PyBuffer [itk .VectorImage [itk .F , 3 ]].GetImageViewFromArray (
102
+ result , is_vector = True
103
+ )
96
104
result_image .SetOrigin (psf .GetOrigin ())
97
105
result_image .SetSpacing (psf .GetSpacing ())
98
106
result_image .SetDirection (psf .GetDirection ())
99
107
100
108
return result_image
101
109
110
+
102
111
def center_psf (psf ):
103
112
"""Center the PSF in the image. The data is resampled around the center and
104
113
the origin of the output is set to the center.
@@ -124,44 +133,50 @@ def center_psf(psf):
124
133
widefield_psf /= np .sum (widefield_psf )
125
134
126
135
(nz , ny , nx ) = widefield_psf .shape
127
- (zz , yy , xx ) = np .meshgrid (np .arange (nz ), np .arange (ny ), np .arange (nx ), indexing = 'ij' )
136
+ (zz , yy , xx ) = np .meshgrid (
137
+ np .arange (nz ), np .arange (ny ), np .arange (nx ), indexing = "ij"
138
+ )
128
139
peak_image_flat = np .ravel (widefield_psf )
129
- peak_data = np .stack ((peak_image_flat ,
130
- np .ravel (zz ),
131
- np .ravel (yy ),
132
- np .ravel (xx )),
133
- axis = 1 )
140
+ peak_data = np .stack (
141
+ (peak_image_flat , np .ravel (zz ), np .ravel (yy ), np .ravel (xx )), axis = 1
142
+ )
134
143
135
144
max_value = np .max (peak_image_flat )
136
145
max_index = np .argmax (peak_image_flat )
137
146
(cz , cy , cx ) = np .unravel_index (max_index , widefield_psf .shape )
138
147
139
148
initial_params = np .zeros ((7 ,), dtype = np .float64 )
140
- initial_params [0 ] = max_value # max value in the window
141
- initial_params [1 ] = cz # z pixel index of the peak
142
- initial_params [2 ] = cy # y pixel index of the peak
143
- initial_params [3 ] = cx # x pixel index of the peak
144
- initial_params [4 ] = 1.5 # z sigma
145
- initial_params [5 ] = 1.5 # y sigma
146
- initial_params [6 ] = 1.5 # x sigma
149
+ initial_params [0 ] = max_value # max value in the window
150
+ initial_params [1 ] = cz # z pixel index of the peak
151
+ initial_params [2 ] = cy # y pixel index of the peak
152
+ initial_params [3 ] = cx # x pixel index of the peak
153
+ initial_params [4 ] = 1.5 # z sigma
154
+ initial_params [5 ] = 1.5 # y sigma
155
+ initial_params [6 ] = 1.5 # x sigma
147
156
148
157
def gaussian_fit_objective (params , peak_data ):
149
- actual = peak_data [:,0 ]
150
- expected = params [0 ] * np .exp (- ((peak_data [:,1 ] - params [1 ])** 2 / (2 * params [4 ]** 2 ) + \
151
- (peak_data [:,2 ] - params [2 ])** 2 / (2 * params [5 ]** 2 ) + \
152
- (peak_data [:,3 ] - params [3 ])** 2 / (2 * params [6 ]** 2 )))
158
+ actual = peak_data [:, 0 ]
159
+ expected = params [0 ] * np .exp (
160
+ - (
161
+ (peak_data [:, 1 ] - params [1 ]) ** 2 / (2 * params [4 ] ** 2 )
162
+ + (peak_data [:, 2 ] - params [2 ]) ** 2 / (2 * params [5 ] ** 2 )
163
+ + (peak_data [:, 3 ] - params [3 ]) ** 2 / (2 * params [6 ] ** 2 )
164
+ )
165
+ )
153
166
err = actual - expected
154
- result = np .sum (err ** 2 )
167
+ result = np .sum (err ** 2 )
155
168
return result
156
169
157
- params_opt = scipy .optimize .fmin (func = gaussian_fit_objective ,
158
- x0 = initial_params ,
159
- args = (peak_data ,),
160
- maxiter = 10000 ,
161
- maxfun = 10000 ,
162
- disp = False ,
163
- xtol = 1e-6 ,
164
- ftol = 1e-6 )
170
+ params_opt = scipy .optimize .fmin (
171
+ func = gaussian_fit_objective ,
172
+ x0 = initial_params ,
173
+ args = (peak_data ,),
174
+ maxiter = 10000 ,
175
+ maxfun = 10000 ,
176
+ disp = False ,
177
+ xtol = 1e-6 ,
178
+ ftol = 1e-6 ,
179
+ )
165
180
(cz_opt , cy_opt , cx_opt ) = params_opt [1 :4 ]
166
181
167
182
result = np .copy (psf_arr )
@@ -171,21 +186,25 @@ def gaussian_fit_objective(params, peak_data):
171
186
transform_params [1 ] = float (cy ) - cy_opt
172
187
transform_params [2 ] = float (cz ) - cz_opt
173
188
transform .SetParameters (transform_params )
174
- interpolator = itk .WindowedSincInterpolateImageFunction [itk .Image [itk .F ,3 ],
175
- 3 , itk .HammingWindowFunction [3 ]].New ()
189
+ interpolator = itk .WindowedSincInterpolateImageFunction [
190
+ itk .Image [itk .F , 3 ], 3 , itk .HammingWindowFunction [3 ]
191
+ ].New ()
176
192
for component in range (psf_arr .shape [- 1 ]):
177
193
component_arr = psf_arr [..., component ]
178
194
component_image = itk .image_view_from_array (component_arr )
179
- resampled = itk .resample_image_filter (component_image ,
180
- use_reference_image = True ,
181
- reference_image = component_image ,
182
- transform = transform ,
183
- interpolator = interpolator ,
184
- )
195
+ resampled = itk .resample_image_filter (
196
+ component_image ,
197
+ use_reference_image = True ,
198
+ reference_image = component_image ,
199
+ transform = transform ,
200
+ interpolator = interpolator ,
201
+ )
185
202
resampled_arr = itk .array_view_from_image (resampled )
186
203
result [..., component ] = resampled_arr
187
204
188
- result_image = itk .PyBuffer [itk .VectorImage [itk .F , 3 ]].GetImageViewFromArray (result , is_vector = True )
205
+ result_image = itk .PyBuffer [itk .VectorImage [itk .F , 3 ]].GetImageViewFromArray (
206
+ result , is_vector = True
207
+ )
189
208
origin = list (itk .origin (psf ))
190
209
spacing = itk .spacing (psf )
191
210
origin [0 ] -= cx * spacing [0 ]
@@ -197,6 +216,7 @@ def gaussian_fit_objective(params, peak_data):
197
216
198
217
return result_image
199
218
219
+
200
220
def make_forward_separation_matrix (nphases , norders , lattice_period , phase_step ):
201
221
"""Generate the forward matrix required to separate OTF orders from an image
202
222
acquired under multiple phases of illumination. This matrix will need
@@ -205,25 +225,35 @@ def make_forward_separation_matrix(nphases, norders, lattice_period, phase_step)
205
225
"""
206
226
207
227
# After this step, separation of orders will be [0, +1, -1, +2, -2,...]
208
- delta_phi = phase_step / lattice_period * 2 * np .pi # Convert phase step in nm to phase step as a fraction of 2pi
228
+ delta_phi = (
229
+ phase_step / lattice_period * 2 * np .pi
230
+ ) # Convert phase step in nm to phase step as a fraction of 2pi
209
231
210
232
sep_matrix = np .zeros ((nphases , norders ), dtype = np .complex128 )
211
233
for phase in range (nphases ):
212
234
counter = 0
213
235
phase_sign = 1
214
236
for order in range (norders ):
215
- sep_matrix [phase , order ] = np .exp (1.j * phase_sign * counter * phase * delta_phi )
216
- counter += np .mod (order + 1 , 2 )
217
- phase_sign = - 1 + 2 * np .mod (order + 1 , 2 )
237
+ sep_matrix [phase , order ] = np .exp (
238
+ 1.0j * phase_sign * counter * phase * delta_phi
239
+ )
240
+ counter += np .mod (order + 1 , 2 )
241
+ phase_sign = - 1 + 2 * np .mod (order + 1 , 2 )
218
242
219
243
# Reorganize so that orders will be [..-2,-1,0,+1,+2...]
220
244
sep_matrix_reorder = np .zeros_like (sep_matrix )
221
- sep_matrix_reorder [:, :int (sep_matrix_reorder .shape [1 ]/ 2 )+ 1 ] = sep_matrix [:, ::- 2 ]
222
- sep_matrix_reorder [:, int (sep_matrix_reorder .shape [1 ]/ 2 )+ 1 :] = sep_matrix [:, 1 ::2 ]
245
+ sep_matrix_reorder [:, : int (sep_matrix_reorder .shape [1 ] / 2 ) + 1 ] = sep_matrix [
246
+ :, ::- 2
247
+ ]
248
+ sep_matrix_reorder [:, int (sep_matrix_reorder .shape [1 ] / 2 ) + 1 :] = sep_matrix [
249
+ :, 1 ::2
250
+ ]
223
251
return sep_matrix_reorder
224
252
225
253
226
- def generate_otf (psf , lattice_period , phase_step , nphases = 3 , norientations = 1 , norders = 3 ):
254
+ def generate_otf (
255
+ psf , lattice_period , phase_step , nphases = 3 , norientations = 1 , norders = 3
256
+ ):
227
257
"""Generate the OTF from the PSF.
228
258
229
259
Fourier transform and separate information components.
@@ -252,31 +282,40 @@ def generate_otf(psf, lattice_period, phase_step, nphases=3, norientations=1, no
252
282
"""
253
283
254
284
psf_arr = itk .array_view_from_image (psf )
255
- spacing = np .asarray (itk .spacing (psf ))[::- 1 ] # z, y, x
256
- dk_psf = 1. / (psf_arr .shape [:3 ] * spacing )
285
+ spacing = np .asarray (itk .spacing (psf ))[::- 1 ] # z, y, x
286
+ dk_psf = 1.0 / (psf_arr .shape [:3 ] * spacing )
257
287
258
288
otf = np .zeros (psf_arr .shape [:3 ] + (norders , norientations ), dtype = np .complex128 )
259
289
260
- sep_matrix = make_forward_separation_matrix (nphases , norders ,
261
- lattice_period , phase_step )
290
+ sep_matrix = make_forward_separation_matrix (
291
+ nphases , norders , lattice_period , phase_step
292
+ )
262
293
inv_sep_matrix = np .linalg .pinv (sep_matrix )
263
294
264
295
for orientation in range (norientations ):
265
296
Dr = np .zeros_like (psf_arr )
266
297
Dk = np .zeros (psf_arr .shape , dtype = np .complex128 )
267
298
268
299
for phase in range (nphases ):
269
- Dr [:,:,:,phase :phase + 1 ] = psf_arr [:,:,:,phase + orientation * nphases ::nphases * norientations ]
270
- Dk [:,:,:,phase ] = np .fft .fftshift (np .fft .ifftn (np .fft .ifftshift (Dr [:,:,:,phase ]))) * np .prod (dk_psf )
300
+ Dr [:, :, :, phase : phase + 1 ] = psf_arr [
301
+ :, :, :, phase + orientation * nphases :: nphases * norientations
302
+ ]
303
+ Dk [:, :, :, phase ] = np .fft .fftshift (
304
+ np .fft .ifftn (np .fft .ifftshift (Dr [:, :, :, phase ]))
305
+ ) * np .prod (dk_psf )
271
306
272
307
# Separate OFT orders by solving the linear system of equations for each
273
308
# pixel
274
309
for ii in range (nphases ):
275
310
for kk in range (nphases ):
276
- otf [:,:,:,ii ,orientation ] = otf [:,:,:,ii ,orientation ] + inv_sep_matrix [ii ,kk ]* Dk [:,:,:,kk ]
311
+ otf [:, :, :, ii , orientation ] = (
312
+ otf [:, :, :, ii , orientation ]
313
+ + inv_sep_matrix [ii , kk ] * Dk [:, :, :, kk ]
314
+ )
277
315
278
316
return otf
279
317
318
+
280
319
def normalize_otf (otf , spacing , norientations = 1 , norders = 3 ):
281
320
"""Normalize the OTF's so that the 0th order of each orientation has unit
282
321
energy.
@@ -301,13 +340,15 @@ def normalize_otf(otf, spacing, norientations=1, norders=3):
301
340
"""
302
341
303
342
normalized_otf = np .copy (otf )
304
- dk_psf = 1. / (otf .shape [:3 ] * np .asarray (spacing ))
343
+ dk_psf = 1.0 / (otf .shape [:3 ] * np .asarray (spacing ))
305
344
306
345
for orientation in range (norientations ):
307
- otf_0 = otf [:,:,:, int (np .ceil (norders / 2. )),orientation ]
346
+ otf_0 = otf [:, :, :, int (np .ceil (norders / 2.0 )), orientation ]
308
347
otf_0_ravel = otf_0 .ravel ()
309
348
energy = np .sum (otf_0_ravel * np .conj (otf_0_ravel * np .prod (dk_psf )))
310
- normalized_otf [:,:,:,:,orientation ] = otf [:,:,:,:,orientation ] / np .sqrt (energy )
349
+ normalized_otf [:, :, :, :, orientation ] = otf [
350
+ :, :, :, :, orientation
351
+ ] / np .sqrt (energy )
311
352
312
353
return normalized_otf
313
354
@@ -327,6 +368,7 @@ def save_otf(otf, filename):
327
368
328
369
np .save (filename , otf )
329
370
371
+
330
372
def load_otf (filename ):
331
373
"""Load the OTF from a file.
332
374
0 commit comments