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