a few more cleanups in angular-ill.. and moved state for minimum search from global...
[woropt.git] / angular-illumination.lisp
blob5bde469f45c06e03ab101f86350ba49e293ff539
1 #.(require :gui)
2 (in-package :run)
4 ;; |
5 ;; -------+-------
6 ;; -/ h (3) | \--- (2) q_R=NA/ri*q_max
7 ;; -----------+------------/------------
8 ;; | alpha /--- \-
9 ;; | /-- \
10 ;; | /--- \
11 ;; ---------+-----------------+-------
12 ;; | (0) / (1) q_max=1/(2*pixel)
14 ;; The resolution of the image has to be big enough to fit the top
15 ;; section of the k-sphere with radius |k|=2pi*q_max into the k space.
16 ;; q_max (see (1)) is due to the nyquist theorem and corresponds to 1
17 ;; over two sample widths. The radius of the backfocal plane
18 ;; corresponds to q_R (see (2) ri is the refractive index,
19 ;; e.g. 1.515). It is bigger for an objective with a high NA.
21 ;; A transform with uneven number of samples doesn't have a bin for
22 ;; the nyquist sampling (draw the unit circle and divide it into n
23 ;; equal bins starting from e^(i*0). For uneven n there will be no bin
24 ;; on e^-i (i.e. -1, 1, -1 ...), e.g. n=3). For even n there will be
25 ;; n/2+1 bins ontop of the real axis (e.g. 0=1, 1=e^(-i pi/2), 2=e^-i
26 ;; for n=4, the arguments to the exponential are (i 2 pi j/n) for the
27 ;; j-th bin) and n/2-1 bins below (e.g 3=e^(i pi/2)). In order to
28 ;; simplify fftshift3 I only consider transforms with even n.
29 ;; fftshift moves the n/2+1 bins from the front of the array to the
30 ;; back (for n=4: [0 1 2 3] -> [3 0 1 2]). In the shifted array the
31 ;; highest reverse frequency (bin 3) is mapped to index 0. The origin
32 ;; of k-space (see (0) in the sketch) is therefor mapped to bin n/2-1
33 ;; (bin 1 for n=4). The nyquist frequency is in the last bin n-1 (bin
34 ;; 3 for n=4).
36 ;; We now search for the right z-sampling dz to fit the top of the
37 ;; sphere below the nyquist bin (which corresponds to q_max=1/(2*dz)).
38 ;; |k|=2 pi/lambda = 2 pi q_max, with wavelength lambda
39 ;; lambda=2 dz -> dz = lambda/2.
41 ;; We could use the same sampling x and y to represent the electric
42 ;; field. For small numerical apertures the sampling distance can be
43 ;; increased. This time the radius q_R has to be smaller than the nyquist
44 ;; frequency:
45 ;; 1/(2*dx)=q_R=NA/ri * 1/lambda
46 ;; -> dx= lambda/2 * ri/NA= dz *ri/NA=dz*1.515/1.38
48 ;; The sampling distances dz and dx that I derived above are only good
49 ;; to represent the amplitude psf. When the intensity is to be
50 ;; calculated the sampling distance has to be changed to accomodate
51 ;; for the convolution in k space.
53 ;; The height of the sphere cap (h see (3) in sketch) is
54 ;; h=q_max-q_max*cos(alpha)=q_max ( 1-cos(alpha))
55 ;; =q_max*(1-sqrt(1-sin(alpha)^2))=q_max*(1-sqrt(1-(NA/ri)^2)) The z
56 ;; sample distance dz2 for the intensity psf should correspond to 1/(2
57 ;; dz2)=2 h, i.e. dz2=1/h=dz*2/(1-sqrt(1-(NA/ri)^2))>dz so the necessary z
58 ;; sampling distance for the intensity is in general bigger than for
59 ;; the amplitude.
61 ;; The radius of the convolved donut shape is 2 q_R. Therefor the
62 ;; transversal sampling distance for the intensity has to be smaller:
63 ;; dx2=dx/2.
65 ;; As we are only interested in the intensity psf we can sample the
66 ;; amplitude psf with a sampling distance dz2. The sphere cap is
67 ;; possibly wrapped along the k_z direction. The transversal direction
68 ;; of the amplitude psf has to be oversampled with dx2.
70 ;; To get an angular illumination psf we multiply the values on the
71 ;; sphere with a k_z plane containing a disk that is centered at any
72 ;; k_x and k_y inside the back focal plane. Later I might want to
73 ;; replace this with a gaussian or a more elaborate window function.
75 ;; With a sampling dx2 the radius of the backfocal plane fills half of
76 ;; the k space. The coordinate calculations below are corrected for
77 ;; this. So setting cx to 1. and cy to 0. below would center the
78 ;; circle on the border of the bfp.
80 ;; For n=1.515 and NA=1.38 the ratio dz2/dx2 is ca. 6. Angular
81 ;; blocking allows to increase dz2 and dx2 a bit. Depending on which
82 ;; and how big an area of the BFP is transmitted. Calculating these
83 ;; smaller bounds seems to be quite complicated and I don't think it
84 ;; will speed things up considerably. Also it will be possible to
85 ;; calculate many different angular illuminations from an amplitude
86 ;; otf that has been sampled with dx2 and dz2 without reevalutation of
87 ;; Wolfs integrals.
89 ;; I want to be able to set dz3 and dx3 to the same values that
90 ;; Jean-Yves used for the confocal stack. I have to introduce
91 ;; sx=dx2/dx3 to scale cx and cy into the back focal plane.
93 (let ((k0 nil)
94 (k1 nil)
95 (k2 nil))
96 (defun angular-psf (&key (x 64) (y x) (z 40)
97 (window-radius #.(coerce .2 'my-float))
98 (window-x (- #.(coerce 1 'my-float) window-radius))
99 (window-y #.(coerce 0 'my-float))
100 (pixel-size-x nil)
101 (pixel-size-z nil)
102 (wavelength #.(coerce .480 'my-float))
103 (numerical-aperture #.(coerce 1.38 'my-float))
104 (immersion-index #.(coerce 1.515 'my-float))
105 (integrand-evaluations 30)
106 (debug nil)
107 (initialize nil))
108 (declare (fixnum x y z integrand-evaluations)
109 (my-float window-radius window-x window-y
110 wavelength numerical-aperture
111 immersion-index)
112 ((or null my-float) pixel-size-x pixel-size-z)
113 (boolean debug initialize)
114 (values (simple-array (complex my-float) 3) &optional))
115 ;; changing z,y,x without touching dx or dz leaves the area that is
116 ;; shown in k space constant
117 (let* ((na numerical-aperture)
118 (ri immersion-index)
119 (lambd (/ wavelength ri))
120 (dz (* +one+ .5 lambd))
121 (dz2 (* dz (/ 2 (- 1 (sqrt (- 1
122 (let ((sinphi (/ na ri)))
123 (* sinphi sinphi))))))))
124 (dx (* dz (/ ri na)))
125 (dx2 (* dx .5))
126 (dx3 (if pixel-size-x
127 (progn
128 #+nil (unless (< pixel-size-x dx2)
129 (error "pixel-size-x is ~a but should be smaller than ~a"
130 pixel-size-x dx2))
131 pixel-size-x)
132 dx2))
133 (dz3 (if pixel-size-z
134 (progn
135 #+nil(unless (< pixel-size-z dz2)
136 (error "pixel-size-z is ~a but should be smaller than ~a"
137 pixel-size-z dz2))
138 pixel-size-z)
139 dz2)))
141 ;; electric field caps are only calculated upon first call FIXME: or
142 ;; when parameters change (implemented via reinitialize argument)
143 (when (or (null k0) (null k1) (null k2) initialize)
144 (multiple-value-bind (e0 e1 e2)
145 (psf:electric-field-psf z y x (* dz3 z) (* dx3 x)
146 :numerical-aperture na
147 :immersion-index ri
148 :wavelength wavelength
149 :integrand-evaluations integrand-evaluations)
150 (when debug
151 (write-pgm "/home/martin/tmp/cut-0psf.pgm"
152 (normalize-2-csf/ub8-abs (cross-section-xz e0))))
153 (setf k0 (fftshift (ft e0))
154 k1 (fftshift (ft e1))
155 k2 (fftshift (ft e2)))
156 (when debug (write-pgm "/home/martin/tmp/cut-1psf-k.pgm"
157 (normalize-2-csf/ub8-abs (cross-section-xz k0))))))
158 (let* ((cr window-radius)
159 (cx window-x)
160 (cy window-y)
161 (sx (/ dx2 dx3))
162 (cr2 (* cr cr))
163 (window (make-array (list y x) :element-type 'my-float))
164 (kk0 (make-array (array-dimensions k0)
165 :element-type '(complex my-float)))
166 (kk1 (make-array (array-dimensions k1)
167 :element-type '(complex my-float)))
168 (kk2 (make-array (array-dimensions k2)
169 :element-type '(complex my-float))))
170 ;; 2d window
171 (do-region ((j i) (y x))
172 (let* ((xx (- (* sx (* 4 (- (* i (/ +one+ x)) .5))) cx))
173 (yy (- (* sx (* 4 (- (* j (/ +one+ y)) .5))) cy))
174 (r2 (+ (* xx xx) (* yy yy))))
175 (when (< r2 cr2)
176 (setf (aref window j i) +one+))))
177 (do-region ((k j i) (z y x))
178 (setf (aref kk0 k j i) (* (aref k0 k j i) (aref window j i))
179 (aref kk1 k j i) (* (aref k1 k j i) (aref window j i))
180 (aref kk2 k j i) (* (aref k2 k j i) (aref window j i))))
181 (when debug
182 (write-pgm "/home/martin/tmp/cut-2psf-k-mul.pgm"
183 (normalize-2-csf/ub8-abs (cross-section-xz kk0))))
184 (let* ((e0 (ift (fftshift kk0)))
185 (e1 (ift (fftshift kk1)))
186 (e2 (ift (fftshift kk2)))
187 (intens k0)) ;; instead of allocating a new array
188 ;; we store into k0
189 (do-region ((k j i) (z y x))
190 (setf (aref intens k j i)
191 (+ (* (aref e0 k j i) (conjugate (aref e0 k j i)))
192 (* (aref e1 k j i) (conjugate (aref e1 k j i)))
193 (* (aref e2 k j i) (conjugate (aref e2 k j i))))))
194 (when debug
195 (write-pgm
196 "/home/martin/tmp/cut-3psf-intens.pgm"
197 (normalize-2-csf/ub8-realpart (cross-section-xz intens)))
198 (let ((k (fftshift (ft intens))))
199 (write-pgm "/home/martin/tmp/cut-4psf-intk.pgm"
200 (normalize-2-csf/ub8-abs (cross-section-xz k)))))
201 intens)))))
203 #+nil
204 (write-pgm "/home/martin/tmp/interpolation-test.pgm"
205 (normalize-2-sf/ub8 (.- (resample-2-sf (draw-disk-sf 25.0 75 75) 1s0 1s0 .25 .25)
206 (draw-disk-sf 100.0 300 300))))
207 #+nil
208 (time (progn
209 (angular-psf :x 128 :z 128 :integrand-evaluations 280 :debug t :initialize t)
210 nil))
212 (defmacro debug-out (&rest rest)
213 `(format t "~a~%"
214 (list ,@(mapcar #'(lambda (x) `(list ,(format nil "~a" x) ,x))
215 rest))))
218 #|| sketch of the cap kz(kx,ky) of the k-vectors that enter the back
219 focal plane. the small circle with the x in the center is a
220 transparent window. the distance from the center of the window to the
221 center of the cap is rho. we are interested in the cap height at
222 points A and B along the vector rho. the point A will be the highest
223 point of the cap and B the lowest. when the window encloses the center
224 of the cap the highest point is k. when the window touches the
225 periphery of the bfp, the lowest point is kz(R), where R=NA*k0 is the
226 diameter of the bfp.
228 -----+-----
229 from top: ---/ | \---
230 -/ | \-
231 -/ | \- / kx
232 / | \/
233 / | ----- / \
234 / | / \ \
235 / | / \ \
236 | | | x | |
237 / | | | \
238 | | |\ /| |
239 | o | ----- | |
240 | | | | |
241 \ | | | |
242 | | | | ||
243 \ | | | /|
244 \ | | | / |
245 \ | | | / |
246 \ | | | / |
247 -\ | | |/- |
248 -\ | | /+ |
249 ---\ | | /--- | |
250 -----+--+-- | |
251 ------+--+--- | |
252 from side ----/ kz | \---v |
253 --/ | \-- |
254 -/ | \- |
255 -/ | \/
256 -/ | -/ \-
257 / | / \ kz(kx)
258 / | -/ \
259 / | / \
260 / | -/ \
261 / | alph -/ \
262 / | / \
263 | | -/ |
264 / | / \
265 | | -/ |
266 | |/ |
267 |-----------------------+--o-------o------o--------+ kx
268 | | A B R |
269 | | k=k0*n
270 \ | |-------| /
271 | | 2 rr |
274 (defun angular-intensity-psf-minimal-resolution
275 (&key
276 (x-um #.(coerce 50 'my-float)) (y-um x-um) (z-um #.(coerce 40 'my-float))
277 (window-radius #.(coerce .1 'my-float))
278 (window-x #.(coerce .5 'my-float))
279 (window-y #.(coerce 0 'my-float))
280 (wavelength #.(coerce .480 'my-float))
281 (numerical-aperture #.(coerce 1.38 'my-float))
282 (immersion-index #.(coerce 1.515 'my-float))
283 (integrand-evaluations 30)
284 (debug nil)
285 (initialize t)
286 (store-cap nil)
287 (big-window nil))
288 "Calculate angular intensity PSF, ensure that the maximum sampling
289 distance is chosen. Set INITIALIZE to nil if the e-field can be reused
290 from a previous calculation. In that case you may need to set
291 STORE-CAP to true for all evaluations and use a lot more memory. Only
292 if the window diameter is going to be bigger than the radius of the
293 back focal plane set BIG-WINDOW to true."
294 (declare ((my-float 0 1) window-radius)
295 ((my-float -1 1) window-x window-y)
296 (my-float wavelength numerical-aperture immersion-index
297 x-um y-um z-um)
298 (fixnum integrand-evaluations)
299 (boolean debug initialize store-cap big-window)
300 (values (simple-array (complex my-float) 3)
301 my-float my-float &optional))
302 (let* ((k0 (/ #.(coerce (* 2 pi) 'my-float) wavelength))
303 (k (* immersion-index k0))
304 (R (* numerical-aperture k0))
305 (rr (* R window-radius)))
306 (labels ((kz (kx)
307 (sqrt (- (* k k) (* kx kx)))))
308 (let* ((rho (sqrt (+ (* window-x window-x)
309 (* window-y window-y))))
310 (kza (kz (- rho rr)))
311 (kzb (kz (+ rho rr)))
312 (top (if (< rho rr)
313 (max k kza kzb)
314 (max kza kzb)))
315 (bot (if (< rho (- R r)) ;; window is in center of bfp
316 (min (kz R) kza kzb)
317 (min kza kzb)))
318 (kzextent (if store-cap
319 ;; store the full cap without wrapping,
320 ;; this way one can reuse the efields
321 (- k (kz R))
322 ;; just leave enough space to accommodate
323 ;; the final donut, this improves
324 ;; performance a lot for small windows
325 (* 16 (- top bot))))
326 (kxextent (if big-window
327 ;; for window diameter bigger than
328 ;; bfp-diam/2 transversally the bfp has to
329 ;; fit in twice, to accommodate the full
330 ;; donut
331 (* +one+ 2 R)
332 (* +one+ R)))
333 (pif #.(coerce pi 'my-float))
334 (dx (/ pif kxextent))
335 (dz (/ pif kzextent)))
336 (debug-out dx dz kxextent R rr kzextent k rho)
337 (values
338 (angular-psf :x (floor x-um dx) :y (floor y-um dx) :z (floor z-um dz)
339 :window-radius window-radius
340 :window-x window-x :window-y window-y
341 :wavelength wavelength
342 :numerical-aperture numerical-aperture
343 :immersion-index immersion-index
344 :integrand-evaluations integrand-evaluations
345 :pixel-size-x dx
346 :pixel-size-z dz
347 :debug debug
348 :initialize initialize)
349 dx dz)))))
351 #+nil
352 (time
353 (multiple-value-bind (a dx dz)
354 (angular-intensity-psf-minimal-resolution
355 :x-um (* +one+ 20)
356 :z-um (* +one+ 40)
357 :window-radius (* +one+ .14)
358 :window-x (* +one+ .73)
359 :window-y (* +one+ 0)
360 :integrand-evaluations 180
361 :initialize t
362 :debug t)
363 (write-pgm "/home/martin/tmp/cut-5resampled.pgm"
364 (normalize-2-csf/ub8-realpart
365 (cross-section-xz
366 (resample-3-csf a dx dx dz .2 .2 .2))))
367 (sb-ext:gc :full t)))
370 (defmacro defstuff ()
371 `(progn
372 ,@(loop for i in '(*dims* ;; dimensions of the input stack in
373 ;; pixels and slices
374 *centers* ;; integral center coordinates of
375 ;; the nuclei (0 .. dim-x) ...
376 *index-spheres* ;; each nuclei is drawn with its index
377 *spheres-c-r* ;; scaled (isotropic axis, in
378 ;; mm) and shifted (so that
379 ;; origin in center of volume)
380 ;; coordinates
382 collect
383 `(defparameter ,i nil))))
385 (defstuff)
387 (defun create-sphere-array (centers)
388 (declare ((simple-array vec-i 1) centers)
389 (values (simple-array sphere 1) &optional))
390 (let* ((ri 1.515d0)
391 (dx (* +one+ ri .2e-3))
392 (dz (* +one+ ri 1e-3))
393 (n (length centers))
394 (result-l nil))
395 (labels ((convert-point (point)
396 (declare (vec-i point)
397 (values vec &optional))
398 (make-vec (* dx (vec-i-x point))
399 (* dx (vec-i-y point))
400 (* dz (vec-i-z point)))))
401 (dotimes (i n)
402 (push (make-instance 'sphere
403 :center (convert-point (aref centers i))
404 :radius (* dx 12d0))
405 result-l))
406 (make-array (length result-l) :element-type 'sphere
407 :initial-contents (nreverse result-l)))) )
409 (defun init-angular-model ()
410 ;; find the centers of the nuclei and store into *centers*
411 #+nil(multiple-value-bind (c ch dims)
412 (find-centers)
413 (declare (ignore ch))
414 (defparameter *centers* c)
415 (defparameter *dims* dims)
416 (sb-ext:gc :full t))
417 (progn
418 (defparameter *dims* '(34 206 296))
419 (let* ((result nil)
420 (nx 10)
421 (ny 7)
422 (z 20)
423 (dx 30))
424 (loop for i below nx do
425 (loop for j below ny do
426 (let ((x (+ (floor dx 2) (* dx i)))
427 (y (+ (floor dx 2) (* dx j))))
428 (unless (and (= i 4) (= j 3))
429 (push (make-vec-i :x x :y y :z z)
430 result)))))
431 ;; the first sphere is the one i want to illuminate its center
432 ;; is in plane 10 (why is z inverted. in spheres the single
433 ;; nucleus is in plane 25)
434 (push (make-vec-i :x 130 :y 100 :z 10) result)
435 (defparameter *centers* (make-array (length result)
436 :element-type 'vec-i
437 :initial-contents result))))
439 (defparameter *spheres-c-r*
440 (create-sphere-array *centers*))
441 (let ((spheres
442 (destructuring-bind (z y x)
443 *dims*
444 (draw-indexed-ovals 12.0 *centers* z y x))))
445 (setf *index-spheres* spheres)
446 (write-pgm "/home/martin/tmp/angular-indexed-spheres-cut.pgm"
447 (normalize-2-csf/ub8-realpart
448 (cross-section-xz *index-spheres*
449 (vec-i-y (elt *centers* 0)))))
450 (sb-ext:gc :full t))
451 (let ((spheres
452 (destructuring-bind (z y x)
453 *dims*
454 (draw-ovals 12.0 *centers* z y x))))
455 (setf *spheres* spheres)
456 (save-stack-ub8 "/home/martin/tmp/angular-spheres/"
457 (normalize-3-csf/ub8-realpart *spheres*))
458 (write-pgm "/home/martin/tmp/angular-spheres-cut.pgm"
459 (normalize-2-csf/ub8-realpart
460 (resample-2-csf
461 (cross-section-xz *spheres*
462 (vec-i-y (elt *centers* 0)))
463 .2 1.0 .2 .2)))
464 (sb-ext:gc :full t)))
466 #+nil
467 (time (init-angular-model))
469 (defun get-visible-nuclei (k)
470 "Find all the nuclei in slice K."
471 (declare (fixnum k)
472 (values list &optional))
473 (destructuring-bind (z y x)
474 (array-dimensions *index-spheres*)
475 (unless (< k z)
476 (error "slice k isn't contained in array *index-spheres*."))
477 ;; use bit-vector to store which nuclei are contained
478 (let* ((n (length *centers*))
479 (result (make-array n
480 :element-type 'boolean
481 :initial-element nil)))
482 (do-region ((j i) (y x))
483 (let ((v (round (realpart (aref *index-spheres* k j i)))))
484 (when (< 0 v n)
485 (setf (aref result v) t))))
486 (loop for i from 1 below n
487 when (aref result i)
488 collect
489 (1- i)))))
490 #+nil
491 (get-visible-nuclei 25)
493 #+nil
494 (time
495 (loop for i below (array-dimension *index-spheres* 0)
496 collect
497 (list i (get-visible-nuclei i))))
499 ;; create a volume containing just the current slice
500 (defun get-lcos-volume (k nucleus)
501 (declare (fixnum k)
502 (values (simple-array (complex my-float) 3) &optional))
503 (destructuring-bind (z y x)
504 (array-dimensions *index-spheres*)
505 (unless (< 0 k z)
506 (error "slice index k out of range."))
507 (let ((vol (make-array (list z y x)
508 :element-type '(complex my-float))))
509 ;; only the current nucleus will be illuminated
510 ;; note that nucleus 0 has value 1 in index-spheres
511 (do-region ((j i) (y x))
512 (if (< (abs (- nucleus (1- (aref *index-spheres* k j i)))) .5)
513 (setf (aref vol k j i) (aref *spheres* k j i))))
514 vol)))
516 (defun write-section (fn vol &optional (y (floor (array-dimension vol 1) 2)))
517 (declare (simple-string fn)
518 ((simple-array (complex my-float) 3) vol)
519 (values null &optional))
520 (write-pgm fn (normalize-2-csf/ub8-realpart (cross-section-xz vol y))))
523 #+nil
524 (let* ((k 25)
525 (nuc (first (get-visible-nuclei k)))
526 (vol (get-lcos-volume k nuc)))
527 (format t "~a~%" `(nuc ,nuc))
528 (write-section "/home/martin/tmp/angular-0lcos-cut.pgm" vol)
529 (save-stack-ub8 "/home/martin/tmp/angular-0lcos" (normalize-3-csf/ub8-realpart vol)))
531 ;; the parameter params contains state in the following list
532 ;; (objective window-radius nucleus-index spheres-c-r)
533 (defun merit-function (vec2 params)
534 (declare ((simple-array double-float (2)) vec2)
535 (cons params)
536 (values double-float &optional))
537 (destructuring-bind (objective window-radius nucleus-index spheres-c-r)
538 params
539 (let* ((border-value 0d0) ;; value to return when outside of bfp
540 ;; this has to be considerably bigger than the maxima on the bfp
541 (border-width window-radius) ;; in this range to the
542 ;; border of the bfp
543 ;; enforce bad merit
544 ;; function
545 (sum 0d0)
546 (radius (norm2 vec2)))
547 (if (< radius (- .99d0 border-width))
548 ;; inside
549 (loop for dirs in '((:right :left)
550 (:top :bottom)) do
551 (loop for dir in dirs do
552 (loop for bfp-dir in dirs do
553 (incf sum
554 (illuminate-ray objective
555 spheres-c-r nucleus-index dir
556 (vec2-x vec2) (vec2-y vec2)
557 window-radius bfp-dir)))))
558 ;; in the border-width or outside of bfp
559 (incf sum border-value))
560 sum)))
562 (defun find-optimal-bfp-window-center (nucleus params)
563 (declare (fixnum nucleus)
564 (cons params)
565 (values vec2 &optional))
566 (let ((*nucleus-index* nucleus))
567 (loop
568 (multiple-value-bind (min point)
569 (simplex-anneal:anneal (simplex-anneal:make-simplex
570 (make-vec2 :x -1d0 :y -1d0) 1d0)
571 #'merit-function
572 ;; set temperature bigger than the
573 ;; maxima in the bfp but smaller
574 ;; than border-value
575 :start-temperature 2.4d0
576 :eps/m .02d0
577 :itmax 1000
578 :ftol 1d-3
579 :params params)
580 (when (< min 100d0)
581 (return-from find-optimal-bfp-window-center point))))))
583 #+nil
584 (find-optimal-bfp-window-center 0)
585 ;; FIXME: are these coordinates in mm or relative positions for a bfp-radius of 1?
586 ;; I think the latter, but I'm not sure.
587 #+nil
588 (time
589 (let*((dx .2d0)
590 (dz 1d0)
591 (r 100)
592 (z (array-dimension *spheres* 0))
593 (psf (angular-psf :x r :y r :z (* 2 z)
594 :pixel-size-x dx :pixel-size-z dz
595 :window-radius *bfp-window-radius*
596 :window-x -.714d0
597 :window-y .16d0
598 :initialize t
599 :debug t
600 :integrand-evaluations 400)))
601 (save-stack-ub8 "/home/martin/tmp/psf" (normalize3-cdf/ub8-realpart psf))
602 nil))
605 ;; calculate the excitation one nucleus
606 #+bla
607 (defun calc-light-field (k nucleus)
608 (declare (fixnum k nucleus))
609 (let* ((result nil)
610 (lcos (get-lcos-volume k nucleus))
611 (bfp-pos (find-optimal-bfp-window-center nucleus))
612 (psf (let ((dx .2d0)
613 (dz 1d0)
614 (r 100)
615 (z (array-dimension *spheres* 0)))
616 (multiple-value-bind (h ddx ddz)
617 (angular-intensity-psf-minimal-resolution
618 :x-um (* r dx) :y-um (* r dx) :z-um (* 2 z dz)
619 :window-radius *bfp-window-radius*
620 :window-x (vec2-x bfp-pos)
621 :window-y (vec2-y bfp-pos)
622 :initialize t
623 :debug t
624 :integrand-evaluations 1000)
625 (resample-3-cdf h ddx ddx ddz dx dx dz)))))
626 (format t "~a~%" `(bfp-pos ,bfp-pos))
627 (write-section (format nil "/home/martin/tmp/angular-1expsf-cut-~3,'0d.pgm" nucleus) psf)
629 (multiple-value-bind (conv conv-start)
630 (convolve-nocrop lcos psf)
631 ;; light distribution in sample
632 (defparameter *angular-light-field* conv)
633 (defparameter *angular-light-field-start* conv-start)
634 (write-section (format nil "/home/martin/tmp/angular-2light-cut-~3,'0d.pgm" nucleus)
635 conv
636 (vec-i-y (aref *centers* nucleus)))
637 (save-stack-ub8 (format nil "/home/martin/tmp/angular-2light-~3,'0d/" nucleus)
638 (normalize-3-cdf/ub8-realpart conv))
639 ;; multiply fluorophore concentration with light distribution
640 (let ((excite (.* conv *spheres* conv-start)))
641 (defparameter *excite* excite)
642 (write-section (format nil "/home/martin/tmp/angular-3excite-cut-~3,'0d.pgm" nucleus)
643 excite
644 (vec-i-y (aref *centers* nucleus)))
645 (save-stack-ub8 (format nil "/home/martin/tmp/angular-3excite-~3,'0d/" nucleus)
646 (normalize-3-cdf/ub8-realpart excite))
647 (destructuring-bind (z y x)
648 (array-dimensions excite)
649 (declare (ignorable z))
650 (let* ((in-focus (extract-bbox-3-cdf excite
651 (make-bbox :start (v 0d0 0d0 (* 1d0 k))
652 :end (v (* 1d0 (1- x))
653 (* 1d0 (1- y))
654 (* 1d0 k))))))
655 (save-stack-ub8 "/home/martin/tmp/angular-4in-focus/"
656 (normalize-3-cdf/ub8-realpart in-focus))
657 (let*((mplane (mean (convert-3-cdf/df-realpart in-focus)))
658 (mvol (mean (convert-3-cdf/df-realpart excite)))
659 (gamma (/ mplane mvol)))
660 (push (list mplane mvol gamma) result)
661 (debug-out mplane mvol gamma)
662 (format t "plane-result ~f ~f ~f~%" mplane mvol gamma))
663 result))))))
665 #+nil
666 (time
667 (progn
668 (with-open-file (*standard-output* "/home/martin/tmp/angular-stack.log"
669 :direction :output
670 :if-exists :supersede
671 :if-does-not-exist :create)
672 (let ((result nil))
673 (dotimes (k (array-dimension *spheres* 0))
674 (let* ((nucs (get-visible-nuclei k)))
675 (loop for nuc in nucs do
676 (format t "~a~%" (list 'doing k nucs))
677 (push (list k nuc (calc-light-field k nuc)) result))))
678 (defparameter *scan-result* result)))
679 (with-open-file (s "/home/martin/tmp/angular-stack-struc.lisp"
680 :direction :output
681 :if-exists :supersede
682 :if-does-not-exist :create)
683 (write *scan-result* :stream s))))
685 #+nil ;; overlay lightfield and spheres
686 (time
687 (save-stack-ub8 "/home/martin/tmp/sphere-and-excite/"
688 (normalize3-cdf/ub8-realpart
689 (labels ((con (vol)
690 (convert3-ub8/cdf-complex (normalize3-cdf/ub8-realpart vol))))
691 (.+ (con *angular-light-field*)
692 (s* .7d0 (con *spheres*))
693 *angular-light-field-start*)))))
695 #+nil
696 (dotimes (i (length *centers*))
697 (format t "~a~%"
698 (raytrace::ray-spheres-intersection (v) (v 0d0 0d0 -1d0) *sphere-c-r* i)))
700 #+nil
701 (progn
702 (defun merit-function (vec)
703 (declare (vec vec)
704 (values my-float &optional))
705 (raytrace:ray-spheres-intersection
706 (v 0d0 0d0 0d0)
707 (normalize (direction (aref vec 0) (aref vec 1)))
708 *sphere-c-r*))
709 (let ((start (make-array 2 :element-type 'my-float
710 :initial-contents (list 100d0 100d0))))
711 (with-open-file (*standard-output* "/dev/shm/anneal.log"
712 :direction :output
713 :if-exists :supersede)
714 (anneal (make-simplex start 1d0)
715 #'merit-function
716 :start-temperature 3d0))))
719 ;; The merit function should get two parameters r and phi. if r isn't
720 ;; inside the back focal plane radius (minus the diameter of the
721 ;; aperture window) some high value is returned. Several rays should
722 ;; be sent through the spheres starting from different positions on
723 ;; the aperture window and targetting different positions in the
724 ;; circle that should be illuminated in the sample.
726 ;; Maybe later I can add the aperture size in the back focal plane as
727 ;; another parameter. The bigger the aperture, the better for the
728 ;; optimization.
730 ;; Possibly I shouldn't call it merit function as I try to minimize
731 ;; its result.
733 (defmethod get-ray-behind-objective ((obj lens::objective)
734 x-mm y-mm bfp-ratio-x bfp-ratio-y)
735 "Take a point on the back focal plane and a point in the sample and
736 calculate the ray direction ro that leaves the objective. So its the
737 same calculation that is used for draw-ray-into-vol."
738 (declare (double-float x-mm y-mm bfp-ratio-x bfp-ratio-y)
739 (values ray &optional))
740 (with-slots ((bfp lens::bfp-radius)
741 (f lens::focal-length)) obj
742 (let* ((theta (lens:find-inverse-ray-angle obj x-mm y-mm))
743 (phi (atan y-mm x-mm)))
744 (lens:refract (make-instance 'ray
745 :start (make-vec (* bfp-ratio-x bfp)
746 (* bfp-ratio-y bfp)
747 (- f))
748 :direction (v-spherical theta phi))
749 obj))))
751 #+nil
752 (get-ray-behind-objective .1d0 .1d0 0d0 0d0)
754 ;; In *spheres-c-r* I stored the coordinates of all the nuclei
755 ;; relative to the center of the initial stack of images. It also
756 ;; contains the radius of each nuclieus. Now I consider how to
757 ;; illuminate selected circles inside of the sample. The nucleus which
758 ;; is beeing illuminated will be centered on the focal plane. The
759 ;; length of the vector ro coming out of the objective is
760 ;; nf=1.515*2.6mm~3000um and therefore a lot bigger than the z extent
761 ;; of the stack (~40 um). It is not necessary to z-shift the nuclei
762 ;; before intersecting them with the rays. So I will just use the
763 ;; nucleus' x and y coordinates as arguments to
764 ;; get-ray-behind-objective. I also supply the position in the back
765 ;; focal plane from where the ray originates.
767 (deftype direction ()
768 `(member :left :right :top :bottom))
770 (defun sample-circle (center radius direction)
771 "Given a circle CENTER and RADIUS return the point in the left,
772 right, top or bottom of its periphery. CENTER and result are complex
773 numbers x+i y."
774 (declare ((complex double-float) center)
775 (double-float radius)
776 (direction direction)
777 (values (complex double-float) &optional))
778 (let ((phi (ecase direction
779 (:right 0d0)
780 (:top (* .5d0 pi))
781 (:left pi)
782 (:bottom (* 1.5d0 pi)))))
783 (+ center (* radius (exp (complex 0d0 phi))))))
785 #+nil
786 (sample-unit-circle (complex 1d0 1d0) :right)
788 #+buk
789 (with-slots (center radius)
790 (aref *spheres-c-r* 0)
791 (let* ((sample-pos (complex 0d0)#+nil(sample-circle (complex (vec-x center) (vec-y center))
792 radius
793 :left))
794 (bfp-pos (complex 0d0)#+nil(sample-circle (complex 0d0 0)
795 .1d0
796 :left)))
797 (draw-ray-into-vol (realpart sample-pos)
798 (imagpart sample-pos)
799 (realpart bfp-pos)
800 (imagpart bfp-pos)
801 *spheres-ub8*)))
802 #+nil
803 (init-angular-model)
805 (defmethod illuminate-ray ((objective lens::objective) spheres-c-r
806 illuminated-sphere-index sample-position
807 window-radius-ratio bfp-ratio-x bfp-ratio-y
808 bfp-position)
809 "Trace a ray from a point in the back focal plane through the disk
810 that encompasses the nucleus with index
811 ILLUMINATED-SPHERE-INDEX. SAMPLE-POSITION and BFP-POSITION can assume
812 one of the four values :LEFT, :RIGHT, :TOP and :BOTTOM indicating
813 which point on the periphery of the correspondi2ng circle is meant
814 Coordinates in the back focal plane are ratios, e.g. bfp-ratio-x=-1
815 and 1 are on the border and a window with window-radius-ratio=1 passes
816 all light through the bfp. When the ray gets lost in the
817 objective (shouldn't happen if you stay inside the bfp) a big value is
818 returned. "
819 (declare (fixnum illuminated-sphere-index)
820 (direction sample-position bfp-position)
821 (double-float bfp-ratio-x bfp-ratio-y window-radius-ratio)
822 ((simple-array sphere 1) spheres-c-r)
823 (values double-float &optional))
824 (with-slots ((center raytrace::center)
825 (radius raytrace::center))
826 (aref spheres-c-r illuminated-sphere-index)
827 (with-slots ((bfp-radius lens::bfp-radius)
828 (ri lens::immersion-index)
829 (f lens::focal-length)) objective
830 (handler-case
831 (let* ((sample-pos (sample-circle
832 (complex (vec-x center) (vec-y center))
833 radius sample-position))
834 (bfp-pos (sample-circle (complex bfp-ratio-x bfp-ratio-y)
835 window-radius-ratio bfp-position))
836 (ray1 (get-ray-behind-objective
837 objective
838 (realpart sample-pos) (imagpart sample-pos)
839 (realpart bfp-pos) (imagpart bfp-pos)))
840 (ray2 (make-instance
841 'ray
842 :start
843 (v- (vector::start ray1)
844 (make-vec 0d0
846 (- (* ri f) (vec-z center))))
847 :direction (normalize (vector::direction ray1))))
848 (exposure (ray-spheres-intersection ray2
849 spheres-c-r
850 illuminated-sphere-index)))
851 exposure)
852 (ray-lost () 100d0)))))
854 #+nil ;; store the scan for each nucleus in the bfp
855 (time
856 (let* ((n 100)
857 (a (make-array (list n n) :element-type 'double-float))
858 (nn (length *spheres-c-r*))
859 (mosaicx (ceiling (sqrt nn)))
860 (mosaic (make-array (list (* n mosaicx) (* n mosaicx))
861 :element-type 'double-float)))
862 (dotimes (*nucleus-index* 3 nn)
863 (dotimes (i n)
864 (dotimes (j n)
865 (let ((x (- (* 2d0 (/ i n)) 1d0))
866 (y (- (* 2d0 (/ j n)) 1d0)))
867 (setf (aref a j i)
868 (merit-function
869 (make-vec2 :x x :y y))))))
870 (do-region ((j i) (n n))
871 (let ((x (mod *nucleus-index* mosaicx))
872 (y (floor *nucleus-index* mosaicx)))
873 (setf (aref mosaic (+ (* n y) j) (+ (* n x) i))
874 (aref a j i)))))
875 (write-pgm "/home/martin/tmp/scan-mosaic.pgm" (normalize-2-df/ub8 mosaic))))
877 #+nil
878 (let ((vol (make-array dims :element-type '(unsigned-byte 8))))
879 (loop for i in '(4.0d-3 -.2d-3) do
880 (draw-ray-into-vol i 0d0 .99d0 .0d0 vol)
881 #+nil(draw-ray-into-vol i 0d0 -.99d0 .0d0 vol)
882 (draw-ray-into-vol i 0d0 0d0 .99d0 vol)
883 (draw-ray-into-vol i 0d0 0d0 -.99d0 vol))
885 (save-stack-ub8 "/home/martin/tmp/line"
886 vol))
889 #+nil
890 (time
891 (let* ((n 100)
892 (a (make-array (list n n) :element-type '(unsigned-byte 8)))
893 (nn (length *spheres-c-r*))
894 (mosaicx (ceiling (sqrt nn)))
895 (mosaic (make-array (list (* n mosaicx) (* n mosaicx))
896 :element-type '(unsigned-byte 8))))
897 (with-open-file (*standard-output* "/dev/shm/a"
898 :direction :output
899 :if-exists :supersede)
900 (dotimes (*nucleus-index* nn)
901 (dotimes (i 10)
902 (tagbody again
903 (multiple-value-bind (min point)
904 (simplex-anneal:anneal (simplex-anneal:make-simplex
905 (make-vec2 :x -1d0 :y -1d0) 1d0)
906 #'merit-function
907 ;; set temperature bigger than the
908 ;; maxima in the bfp but smaller
909 ;; than border-value
910 :start-temperature 2.4d0
911 :eps/m .02d0
912 :itmax 1000
913 :ftol 1d-3)
914 (unless (<= min 100d0)
915 (go again))
916 (let* ((x (aref point 0))
917 (y (aref point 1))
918 (ix (floor (* n (+ x 1)) 2))
919 (iy (floor (* n (+ y 1)) 2))
920 (mx (mod *nucleus-index* mosaicx))
921 (my (floor *nucleus-index* mosaicx)))
922 (incf (aref mosaic (+ (* n my) iy) (+ (* n mx) ix)))
923 (format t "min ~a~%" (list min ix iy))))))))
924 (write-pgm "/home/martin/tmp/scan-mosaic-max.pgm" mosaic)))