missing quote in coerce
[woropt.git] / angular-illumination.lisp
blob753236d800d5bcf02ff7086498dfecca4939058e
1 ;; load run.lisp before running this file
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 (normalize2-cdf/ub8-abs (cross-section-xz e0))))
296 (setf k0 (fftshift3 (ft3 e0))
297 k1 (fftshift3 (ft3 e1))
298 k2 (fftshift3 (ft3 e2)))
299 (when debug (write-pgm "/home/martin/tmp/cut-1psf-k.pgm"
300 (normalize2-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-rectangle (j i 0 y 0 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-box (k j i 0 z 0 y 0 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 (normalize2-cdf/ub8-abs (cross-section-xz kk0))))
324 (let* ((e0 (ift3 (fftshift3 kk0)))
325 (e1 (ift3 (fftshift3 kk1)))
326 (e2 (ift3 (fftshift3 kk2)))
327 (intens k0)) ;; instead of allocating a new array we store into k0
328 (do-box (k j i 0 z 0 y 0 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-ub8 (cross-section-xz intens)))
336 (let ((k (fftshift3 (ft3 intens))))
337 (write-pgm "/home/martin/tmp/cut-4psf-intk.pgm"
338 (normalize2-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 (normalize2-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 (normalize2-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 (normalize2-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-rectangle (j i 0 y 0 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-rectangle (j i 0 y 0 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 (normalize2-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* ((lcos (get-lcos-volume k nucleus))
749 (bfp-pos (find-optimal-bfp-window-center nucleus))
750 (psf (let ((dx .2d0)
751 (dz 1d0)
752 (r 100)
753 (z (array-dimension *spheres* 0)))
754 (multiple-value-bind (h ddx ddz)
755 (angular-intensity-psf-minimal-resolution
756 :x-um (* r dx) :y-um (* r dx) :z-um (* 2 z dz)
757 :window-radius *bfp-window-radius*
758 :window-x (vec2-x bfp-pos)
759 :window-y (vec2-y bfp-pos)
760 :initialize t
761 :debug t
762 :integrand-evaluations 1000)
763 (resample3-cdf h ddx ddx ddz dx dx dz)))))
764 (format t "~a~%" `(bfp-pos ,bfp-pos))
765 (write-section (format nil "/home/martin/tmp/angular-1expsf-cut-~3,'0d.pgm" nucleus) psf)
767 (multiple-value-bind (conv conv-start)
768 (convolve3-nocrop lcos psf)
769 ;; light distribution in sample
770 (defparameter *angular-light-field* conv)
771 (defparameter *angular-light-field-start* conv-start)
772 (write-section (format nil "/home/martin/tmp/angular-2light-cut-~3,'0d.pgm" nucleus)
773 conv
774 (vec-i-y (aref *centers* nucleus)))
775 (save-stack-ub8 (format nil "/home/martin/tmp/angular-2light-~3,'0d/" nucleus)
776 (normalize3-cdf/ub8-realpart conv))
777 ;; multiply fluorophore concentration with light distribution
778 (let ((excite (.* conv *spheres* conv-start)))
779 (defparameter *excite* excite)
780 (write-section (format nil "/home/martin/tmp/angular-3excite-cut-~3,'0d.pgm" nucleus)
781 excite
782 (vec-i-y (aref *centers* nucleus)))
783 (save-stack-ub8 (format nil "/home/martin/tmp/angular-3excite-~3,'0d/" nucleus)
784 (normalize3-cdf/ub8-realpart excite))
785 (destructuring-bind (z y x)
786 (array-dimensions excite)
787 (let* ((in-focus (extract-bbox3-cdf excite
788 (make-bbox :start (v 0d0 0d0 (* 1d0 k))
789 :end (v (* 1d0 (1- x))
790 (* 1d0 (1- y))
791 (* 1d0 k))))))
792 (save-stack-ub8 "/home/martin/tmp/angular-4in-focus/"
793 (normalize3-cdf/ub8-realpart in-focus))
794 (let*((mplane (mean-realpart in-focus))
795 (mvol (mean-realpart excite))
796 (gamma (/ mplane mvol)))
797 (debug-out mplane mvol gamma))))))))
798 (array-dimensions *spheres*)
799 #+nil
800 (time
801 (with-open-file (*standard-output* "/home/martin/tmp/angular-stack.log"
802 :direction :output
803 :if-exists :supersede
804 :if-does-not-exist :create)
805 (dotimes (k (array-dimension *spheres* 0))
806 (let* ((nucs (get-visible-nuclei k)))
807 (loop for nuc in nucs do
808 (calc-light-field k nuc))))))
810 #+nil ;; overlay lightfield and spheres
811 (time
812 (save-stack-ub8 "/home/martin/tmp/sphere-and-excite/"
813 (normalize3-cdf/ub8-realpart
814 (labels ((con (vol)
815 (convert3-ub8/cdf-complex (normalize3-cdf/ub8-realpart vol))))
816 (.+ (con *angular-light-field*)
817 (s* .7d0 (con *spheres*))
818 *angular-light-field-start*)))))
820 #+nil
821 (dotimes (i (length *centers*))
822 (format t "~a~%"
823 (raytrace::ray-spheres-intersection (v) (v 0d0 0d0 -1d0) *sphere-c-r* i)))
825 #+nil
826 (progn
827 (defun merit-function (vec)
828 (declare (vec vec)
829 (values double-float &optional))
830 (raytrace:ray-spheres-intersection
831 (v 0d0 0d0 0d0)
832 (normalize (direction (aref vec 0) (aref vec 1)))
833 *sphere-c-r*))
834 (let ((start (make-array 2 :element-type 'double-float
835 :initial-contents (list 100d0 100d0))))
836 (with-open-file (*standard-output* "/dev/shm/anneal.log"
837 :direction :output
838 :if-exists :supersede)
839 (anneal (make-simplex start 1d0)
840 #'merit-function
841 :start-temperature 3d0))))
844 ;; The merit function should get two parameters r and phi. if r isn't
845 ;; inside the back focal plane radius (minus the diameter of the
846 ;; aperture window) some high value is returned. Several rays should
847 ;; be sent through the spheres starting from different positions on
848 ;; the aperture window and targetting different positions in the
849 ;; circle that should be illuminated in the sample.
851 ;; Maybe later I can add the aperture size in the back focal plane as
852 ;; another parameter. The bigger the aperture, the better for the
853 ;; optimization.
855 ;; Possibly I shouldn't call it merit function as I try to minimize
856 ;; its result.
858 (defun get-ray-behind-objective (x-mm y-mm bfp-ratio-x bfp-ratio-y)
859 "Take a point on the back focal plane and a point in the sample and
860 calculate the ray direction ro that leaves the objective. So its the
861 same calculation that is used for draw-ray-into-vol."
862 (declare (double-float x-mm y-mm bfp-ratio-x bfp-ratio-y)
863 (values vec vec &optional))
864 (let* ((f (lens:focal-length-from-magnification 63d0))
865 (na 1.38d0)
866 (ri 1.515d0)
867 (bfp-radius (lens:back-focal-plane-radius f na))
868 (obj (lens:make-thin-objective :normal (v 0d0 0d0 -1d0)
869 :center (v)
870 :focal-length f
871 :radius bfp-radius
872 :numerical-aperture na
873 :immersion-index ri))
874 (theta (lens:find-inverse-ray-angle x-mm y-mm obj))
875 (phi (atan y-mm x-mm))
876 (start (v (* bfp-ratio-x bfp-radius)
877 (* bfp-ratio-y bfp-radius)
878 f)))
879 (multiple-value-bind (ro s)
880 (lens:thin-objective-ray obj
881 start
882 (v* (v (* (cos phi) (sin theta))
883 (* (sin phi) (sin theta))
884 (cos theta))
885 -1d0))
886 (values ro s))))
888 #+nil
889 (get-ray-behind-objective .1d0 .1d0 0d0 0d0)
891 ;; In *spheres-c-r* I stored the coordinates of all the nuclei
892 ;; relative to the center of the initial stack of images. It also
893 ;; contains the radius of each nuclieus. Now I consider how to
894 ;; illuminate selected circles inside of the sample. The nucleus which
895 ;; is beeing illuminated will be centered on the focal plane. The
896 ;; length of the vector ro coming out of the objective is
897 ;; nf=1.515*2.6mm~3000um and therefore a lot bigger than the z extent
898 ;; of the stack (~40 um). It is not necessary to z-shift the nuclei
899 ;; before intersecting them with the rays. So I will just use the
900 ;; nucleus' x and y coordinates as arguments to
901 ;; get-ray-behind-objective. I also supply the position in the back
902 ;; focal plane from where the ray originates.
904 (deftype direction ()
905 `(member :left :right :top :bottom))
907 (defun sample-circle (center radius direction)
908 "Given a circle CENTER and RADIUS return the point in the left,
909 right, top or bottom of its periphery. CENTER and result are complex
910 numbers x+i y."
911 (declare ((complex double-float) center)
912 (double-float radius)
913 (direction direction)
914 (values (complex double-float) &optional))
915 (let ((phi (ecase direction
916 (:right 0d0)
917 (:top (* .5d0 pi))
918 (:left pi)
919 (:bottom (* 1.5 pi)))))
920 (+ center (* radius (exp (complex 0d0 phi))))))
922 #+nil
923 (sample-circle (complex 1d0 1d0) 1d0 :right)
925 (defun illuminate-ray (spheres-c-r illuminated-sphere-index
926 sample-position
927 bfp-center-x bfp-center-y
928 bfp-radius bfp-position)
929 "Trace a ray from a point in the back focal plane through the disk
930 that encompasses the nucleus with index
931 ILLUMINATED-SPHERE-INDEX. SAMPLE-POSITION and BFP-POSITION can assume
932 one of the four values :LEFT, :RIGHT, :TOP and :BOTTOM indicating
933 which point on the periphery of the corresponding circle is meant."
934 (declare (fixnum illuminated-sphere-index)
935 (direction sample-position bfp-position)
936 (double-float bfp-center-x bfp-center-y bfp-radius)
937 ((simple-array sphere 1) spheres-c-r)
938 (values double-float &optional))
939 (with-slots (center radius)
940 (aref spheres-c-r illuminated-sphere-index)
941 (let* ((sample-pos (sample-circle (complex (vec-x center) (vec-y center))
942 radius
943 sample-position))
944 (bfp-pos (sample-circle (complex bfp-center-x bfp-center-y)
945 bfp-radius
946 bfp-position)))
947 (multiple-value-bind (ro s)
948 (get-ray-behind-objective (realpart sample-pos)
949 (imagpart sample-pos)
950 (realpart bfp-pos)
951 (imagpart bfp-pos))
952 (let* ((exposure
953 (ray-spheres-intersection
954 ;; shift by nf so that sample is in origin
955 (v+ s
956 (v 0d0
958 (* 1.515 (lens:focal-length-from-magnification 63d0))))
959 (normalize ro)
960 spheres-c-r
961 illuminated-sphere-index)))
962 exposure)))))
964 #+nil
965 (illuminate-ray *spheres-c-r* 30 :bottom
966 .1d0 .0d0
967 .01d0 :right)
969 #+nil ;; store the scan for each nucleus in the bfp
970 (time
971 (let* ((n 100)
972 (a (make-array (list n n) :element-type 'double-float))
973 (nn (length *spheres-c-r*))
974 (mosaicx (ceiling (sqrt nn)))
975 (mosaic (make-array (list (* n mosaicx) (* n mosaicx))
976 :element-type 'double-float)))
977 (dotimes (*nucleus-index* nn)
978 (dotimes (i n)
979 (dotimes (j n)
980 (let ((x (- (* 2d0 (/ i n)) 1d0))
981 (y (- (* 2d0 (/ j n)) 1d0)))
982 (setf (aref a j i)
983 (merit-function
984 (make-vec2 :x x :y y))))))
985 (do-rectangle (j i 0 n 0 n)
986 (let ((x (mod *nucleus-index* mosaicx))
987 (y (floor *nucleus-index* mosaicx)))
988 (setf (aref mosaic (+ (* n y) j) (+ (* n x) i))
989 (aref a j i)))))
990 (write-pgm "/home/martin/tmp/scan-mosaic.pgm" (normalize-ub8 mosaic))))
993 #+nil
994 (time
995 (let* ((n 100)
996 (a (make-array (list n n) :element-type '(unsigned-byte 8)))
997 (nn (length *spheres-c-r*))
998 (mosaicx (ceiling (sqrt nn)))
999 (mosaic (make-array (list (* n mosaicx) (* n mosaicx))
1000 :element-type '(unsigned-byte 8))))
1001 (with-open-file (*standard-output* "/dev/shm/a"
1002 :direction :output
1003 :if-exists :supersede)
1004 (dotimes (*nucleus-index* nn)
1005 (dotimes (i 10)
1006 (tagbody again
1007 (multiple-value-bind (min point)
1008 (simplex-anneal:anneal (simplex-anneal:make-simplex
1009 (make-vec2 :x -1d0 :y -1d0) 1d0)
1010 #'merit-function
1011 ;; set temperature bigger than the
1012 ;; maxima in the bfp but smaller
1013 ;; than border-value
1014 :start-temperature 2.4d0
1015 :eps/m .02d0
1016 :itmax 1000
1017 :ftol 1d-3)
1018 (unless (<= min 100d0)
1019 (go again))
1020 (let* ((x (aref point 0))
1021 (y (aref point 1))
1022 (ix (floor (* n (+ x 1)) 2))
1023 (iy (floor (* n (+ y 1)) 2))
1024 (mx (mod *nucleus-index* mosaicx))
1025 (my (floor *nucleus-index* mosaicx)))
1026 (incf (aref mosaic (+ (* n my) iy) (+ (* n mx) ix)))
1027 (format t "min ~a~%" (list min ix iy))))))))
1028 (write-pgm "/home/martin/tmp/scan-mosaic-max.pgm" mosaic)))