3 ;; run the following code to test the downhill simplex optimizer on a
8 (time (let ((start (make-array 2 :element-type
'my-float
9 :initial-contents
(list 1.5d0
1.5d0
))))
10 (simplex-anneal:anneal
(simplex-anneal:make-simplex start
1d0
)
22 ;; |-------------+-------+------- <- z
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
)
29 (destructuring-bind (z y x
)
30 (array-dimensions vol
)
31 (let* ((f (lens:focal-length-from-magnification
63d0
))
34 (bfp-radius (lens:back-focal-plane-radius f na
))
35 (obj (lens:make-thin-objective
:normal
(v 0d0
0d0 -
1d0
)
39 :numerical-aperture na
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
)
48 (cz (* .5d0 z
)) ;; position that is in the center of front focal plane
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
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
))
68 (p-z (plane :z
(- (* dz
(- (- z cz
)))
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
77 (v* (v (* (cos phi
) (sin theta
))
78 (* (sin phi
) (sin theta
))
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
91 (declare (ignore dir
))
94 ;; convert coordinates from mm into integer pixel positions
96 (declare (type (or null vec
) h
))
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
113 (remove-if-not #'(lambda (v)
115 (and (< -
1 (vec-i-x v
) x
)
117 (< -
1 (vec-i-z v
) z
))
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
))
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"
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)
148 ;; -/ h (3) | \--- (2) q_R=NA/ri*q_max
149 ;; -----------+------------/------------
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
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
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
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:
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
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.
239 (defun angular-psf (&key
(x 64) (y x
) (z 40)
240 (window-radius #.
(coerce .2 'my-float
))
241 (window-x (- #.
(coerce 1 'my-float
) window-radius
))
242 (window-y #.
(coerce 0 'my-float
))
245 (wavelength #.
(coerce .480 'my-float
))
246 (numerical-aperture #.
(coerce 1.38 'my-float
))
247 (immersion-index #.
(coerce 1.515 'my-float
))
248 (integrand-evaluations 30)
251 (declare (fixnum x y z integrand-evaluations
)
252 (my-float window-radius window-x window-y
253 wavelength numerical-aperture
255 ((or null my-float
) pixel-size-x pixel-size-z
)
256 (boolean debug initialize
)
257 (values (simple-array (complex my-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
)
262 (lambd (/ wavelength ri
))
263 (dz (* +one
+ .5 lambd
))
264 (dz2 (* dz
(/ 2 (- 1 (sqrt (- 1
265 (let ((sinphi (/ na ri
)))
266 (* sinphi sinphi
))))))))
267 (dx (* dz
(/ ri na
)))
269 (dx3 (if pixel-size-x
271 #+nil
(unless (< pixel-size-x dx2
)
272 (error "pixel-size-x is ~a but should be smaller than ~a"
276 (dz3 (if pixel-size-z
278 #+nil
(unless (< pixel-size-z dz2
)
279 (error "pixel-size-z is ~a but should be smaller than ~a"
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
(* dz3 z
) (* dx3 x
)
289 :numerical-aperture na
291 :wavelength wavelength
292 :integrand-evaluations integrand-evaluations
)
294 (write-pgm "/home/martin/tmp/cut-0psf.pgm"
295 (normalize-2-csf/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-csf/ub8-abs
(cross-section-xz k0
))))))
301 (let* ((cr window-radius
)
306 (window (make-array (list y x
) :element-type
'my-float
))
307 (kk0 (make-array (array-dimensions k0
)
308 :element-type
'(complex my-float
)))
309 (kk1 (make-array (array-dimensions k1
)
310 :element-type
'(complex my-float
)))
311 (kk2 (make-array (array-dimensions k2
)
312 :element-type
'(complex my-float
))))
314 (do-region ((j i
) (y x
))
315 (let* ((xx (- (* sx
(* 4 (- (* i
(/ +one
+ x
)) .5))) cx
))
316 (yy (- (* sx
(* 4 (- (* j
(/ +one
+ y
)) .5))) cy
))
317 (r2 (+ (* xx xx
) (* yy yy
))))
319 (setf (aref window j i
) +one
+))))
320 (do-region ((k j i
) (z y x
))
321 (setf (aref kk0 k j i
) (* (aref k0 k j i
) (aref window j i
))
322 (aref kk1 k j i
) (* (aref k1 k j i
) (aref window j i
))
323 (aref kk2 k j i
) (* (aref k2 k j i
) (aref window j i
))))
325 (write-pgm "/home/martin/tmp/cut-2psf-k-mul.pgm"
326 (normalize-2-csf/ub8-abs
(cross-section-xz kk0
))))
327 (let* ((e0 (ift (fftshift kk0
)))
328 (e1 (ift (fftshift kk1
)))
329 (e2 (ift (fftshift kk2
)))
330 (intens k0
)) ;; instead of allocating a new array
332 (do-region ((k j i
) (z y x
))
333 (setf (aref intens k j i
)
334 (+ (* (aref e0 k j i
) (conjugate (aref e0 k j i
)))
335 (* (aref e1 k j i
) (conjugate (aref e1 k j i
)))
336 (* (aref e2 k j i
) (conjugate (aref e2 k j i
))))))
339 "/home/martin/tmp/cut-3psf-intens.pgm"
340 (normalize-2-csf/ub8-realpart
(cross-section-xz intens
)))
341 (let ((k (fftshift (ft intens
))))
342 (write-pgm "/home/martin/tmp/cut-4psf-intk.pgm"
343 (normalize-2-csf/ub8-abs
(cross-section-xz k
)))))
347 (write-pgm "/home/martin/tmp/interpolation-test.pgm"
348 (normalize-2-sf/ub8
(.-
(resample-2-sf (draw-disk-sf 25.0 75 75) 1s0
1s0
.25 .25)
349 (draw-disk-sf 100.0 300 300))))
352 (angular-psf :x
128 :z
128 :integrand-evaluations
280 :debug t
:initialize t
)
355 (defmacro debug-out
(&rest rest
)
357 (list ,@(mapcar #'(lambda (x) `(list ,(format nil
"~a" x
) ,x
))
361 #|| sketch of the cap kz
(kx,ky
) of the k-vectors that enter the back
362 focal plane. the small circle with the x in the center is a
363 transparent window. the distance from the center of the window to the
364 center of the cap is rho. we are interested in the cap height at
365 points A and B along the vector rho. the point A will be the highest
366 point of the cap and B the lowest. when the window encloses the center
367 of the cap the highest point is k. when the window touches the
368 periphery of the bfp
, the lowest point is kz
(R), where R
=NA
*k0 is the
372 from top
: ---
/ | \---
395 from side ----
/ kz | \---v |
410 |-----------------------
+--o-------o------o--------
+ kx
417 (defun angular-intensity-psf-minimal-resolution
419 (x-um #.
(coerce 50 'my-float
)) (y-um x-um
) (z-um #.
(coerce 40 'my-float
))
420 (window-radius #.
(coerce .1 'my-float
))
421 (window-x #.
(coerce .5 'my-float
))
422 (window-y #.
(coerce 0 'my-float
))
423 (wavelength #.
(coerce .480 'my-float
))
424 (numerical-aperture #.
(coerce 1.38 'my-float
))
425 (immersion-index #.
(coerce 1.515 'my-float
))
426 (integrand-evaluations 30)
431 "Calculate angular intensity PSF, ensure that the maximum sampling
432 distance is chosen. Set INITIALIZE to nil if the e-field can be reused
433 from a previous calculation. In that case you may need to set
434 STORE-CAP to true for all evaluations and use a lot more memory. Only
435 if the window diameter is going to be bigger than the radius of the
436 back focal plane set BIG-WINDOW to true."
437 (declare ((my-float 0 1) window-radius
)
438 ((my-float -
1 1) window-x window-y
)
439 (my-float wavelength numerical-aperture immersion-index
441 (fixnum integrand-evaluations
)
442 (boolean debug initialize store-cap big-window
)
443 (values (simple-array (complex my-float
) 3)
444 my-float my-float
&optional
))
445 (let* ((k0 (/ #.
(coerce (* 2 pi
) 'my-float
) wavelength
))
446 (k (* immersion-index k0
))
447 (R (* numerical-aperture k0
))
448 (rr (* R window-radius
)))
450 (sqrt (- (* k k
) (* kx kx
)))))
451 (let* ((rho (sqrt (+ (* window-x window-x
)
452 (* window-y window-y
))))
453 (kza (kz (- rho rr
)))
454 (kzb (kz (+ rho rr
)))
458 (bot (if (< rho
(- R r
)) ;; window is in center of bfp
461 (kzextent (if store-cap
462 ;; store the full cap without wrapping,
463 ;; this way one can reuse the efields
465 ;; just leave enough space to accommodate
466 ;; the final donut, this improves
467 ;; performance a lot for small windows
469 (kxextent (if big-window
470 ;; for window diameter bigger than
471 ;; bfp-diam/2 transversally the bfp has to
472 ;; fit in twice, to accommodate the full
476 (pif #.
(coerce pi
'my-float
))
477 (dx (/ pif kxextent
))
478 (dz (/ pif kzextent
)))
479 (debug-out dx dz kxextent R rr kzextent k rho
)
481 (angular-psf :x
(floor x-um dx
) :y
(floor y-um dx
) :z
(floor z-um dz
)
482 :window-radius window-radius
483 :window-x window-x
:window-y window-y
484 :wavelength wavelength
485 :numerical-aperture numerical-aperture
486 :immersion-index immersion-index
487 :integrand-evaluations integrand-evaluations
491 :initialize initialize
)
496 (multiple-value-bind (a dx dz
)
497 (angular-intensity-psf-minimal-resolution
500 :window-radius
(* +one
+ .14)
501 :window-x
(* +one
+ .73)
502 :window-y
(* +one
+ 0)
503 :integrand-evaluations
180
506 (write-pgm "/home/martin/tmp/cut-5resampled.pgm"
507 (normalize-2-csf/ub8-realpart
509 (resample-3-csf a dx dx dz
.2 .2 .2))))
510 (sb-ext:gc
:full t
)))
513 (defmacro defstuff
()
515 ,@(loop for i in
'(*dims
* ;; dimensions of the input stack in
517 *centers
* ;; integral center coordinates of
518 ;; the nuclei (0 .. dim-x) ...
519 *index-spheres
* ;; each nuclei is drawn with its index
520 *spheres-c-r
* ;; scaled (isotropic axis, in
521 ;; micrometeres) and shifted (so
522 ;; that origin in center of
523 ;; volume) coordinates
526 `(defparameter ,i nil
))))
530 (defun create-sphere-array (dims centers
)
532 ((simple-array vec-i
1) centers
)
533 (values (simple-array sphere
1) &optional
))
534 (destructuring-bind (z y x
)
536 (declare (fixnum z y x
))
537 (let* ((dx (* +one
+ .2e-3))
539 (xh (* +one
+ .5d0 x
))
540 (yh (* +one
+ .5d0 y
))
541 (zh (* +one
+ .5d0 z
))
544 (labels ((convert-point (point)
545 (declare (vec-i point
)
546 (values vec
&optional
))
547 (v (* dx
(- (vec-i-x point
) xh
))
548 (* dx
(- (vec-i-y point
) yh
))
549 (* dz
(- (vec-i-z point
) zh
)))))
551 (push (make-sphere :center
(convert-point (aref centers i
))
554 (make-array (length result-l
) :element-type
'sphere
555 :initial-contents
(nreverse result-l
))))))
557 (defun init-angular-model ()
558 ;; find the centers of the nuclei and store into *centers*
559 #+nil
(multiple-value-bind (c ch dims
)
561 (declare (ignore ch
))
562 (defparameter *centers
* c
)
563 (defparameter *dims
* dims
)
566 (defparameter *dims
* '(34 206 296))
572 (loop for i below nx do
573 (loop for j below ny do
574 (let ((x (+ (floor dx
2) (* dx i
)))
575 (y (+ (floor dx
2) (* dx j
))))
576 (unless (and (= i
4) (= j
3))
577 (push (make-vec-i :x x
:y y
:z z
)
579 ;; the first sphere is the one i want to illuminate its center
580 ;; is in plane 10 (why is z inverted. in spheres the single
581 ;; nucleus is in plane 25)
582 (push (make-vec-i :x
130 :y
100 :z
10) result
)
583 (defparameter *centers
* (make-array (length result
)
585 :initial-contents result
))))
587 (defparameter *spheres-c-r
*
588 (create-sphere-array *dims
* *centers
*))
590 (destructuring-bind (z y x
)
592 (draw-indexed-ovals 12.0 *centers
* z y x
))))
593 (setf *index-spheres
* spheres
)
594 (write-pgm "/home/martin/tmp/angular-indexed-spheres-cut.pgm"
595 (normalize-2-csf/ub8-realpart
596 (cross-section-xz *index-spheres
*
597 (vec-i-y (elt *centers
* 0)))))
600 (destructuring-bind (z y x
)
602 (draw-ovals 12.0 *centers
* z y x
))))
603 (setf *spheres
* spheres
)
604 (save-stack-ub8 "/home/martin/tmp/angular-spheres/"
605 (normalize-3-csf/ub8-realpart
*spheres
*))
606 (write-pgm "/home/martin/tmp/angular-spheres-cut.pgm"
607 (normalize-2-csf/ub8-realpart
609 (cross-section-xz *spheres
*
610 (vec-i-y (elt *centers
* 0)))
612 (sb-ext:gc
:full t
)))
615 (time (init-angular-model))
618 (defun init-angular-psf ()
619 ;; calculate angular intensity psf, make extend in z big enough to
620 ;; span the full fluorophore concentration even when looking at the
621 ;; bottom plane of it
623 ;; the size of the k space must be big enough: 2*bfp-diameter for
624 ;; k_{x,y}, and 2*cap-height for k_z. then the full donut can be
627 ;; if only a small part of the cap is selected the dimensions can be
628 ;; reduced accordingly. calculating the size requires to find the
629 ;; intersection of a cylinder and the sphere cap.
632 (psf (destructuring-bind (z y x
)
634 (declare (ignore y x
))
639 :z
(* 8 z
) :x
(* 2 r
) :y
(* 2 r
)
640 :pixel-size-z
(* .25 dz
) :pixel-size-x
(* .5 dx
)
641 :integrand-evaluations
400
644 (defparameter *psf
* psf
)
645 (write-pgm "/home/martin/tmp/psf.pgm"
646 (normalize-2-csf/ub8-realpart
(cross-section-xz psf
)))
647 (sb-ext:gc
:full t
)))
650 (time (init-angular-psf)) ;; 62.2 s
652 #+nil
;; store indexed-spheres for inspection
653 (save-stack-ub8 "/home/martin/tmp/angular-indexed-spheres/"
654 (normalize-3-csf/ub8-realpart
*index-spheres
*))
657 (defun get-visible-nuclei (k)
658 "Find all the nuclei in slice K."
660 (values list
&optional
))
661 (destructuring-bind (z y x
)
662 (array-dimensions *index-spheres
*)
664 (error "slice k isn't contained in array *index-spheres*."))
665 ;; use bit-vector to store which nuclei are contained
666 (let* ((n (length *centers
*))
667 (result (make-array n
668 :element-type
'boolean
669 :initial-element nil
)))
670 (do-region ((j i
) (y x
))
671 (let ((v (round (realpart (aref *index-spheres
* k j i
)))))
673 (setf (aref result v
) t
))))
674 (loop for i from
1 below n
679 (get-visible-nuclei 25)
683 (loop for i below
(array-dimension *index-spheres
* 0)
685 (list i
(get-visible-nuclei i
))))
688 ;; create a volume containing just the current slice
689 (defun get-lcos-volume (k nucleus
)
691 (values (simple-array (complex my-float
) 3) &optional
))
692 (destructuring-bind (z y x
)
693 (array-dimensions *index-spheres
*)
695 (error "slice index k out of range."))
696 (let ((vol (make-array (list z y x
)
697 :element-type
'(complex my-float
))))
698 ;; only the current nucleus will be illuminated
699 ;; note that nucleus 0 has value 1 in index-spheres
700 (do-region ((j i
) (y x
))
701 (if (< (abs (- nucleus
(1- (aref *index-spheres
* k j i
)))) .5)
702 (setf (aref vol k j i
) (aref *spheres
* k j i
))))
705 (defun write-section (fn vol
&optional
(y (floor (array-dimension vol
1) 2)))
706 (declare (simple-string fn
)
707 ((simple-array (complex my-float
) 3) vol
)
708 (values null
&optional
))
709 (write-pgm fn
(normalize-2-csf/ub8-realpart
(cross-section-xz vol y
))))
714 (nuc (first (get-visible-nuclei k
)))
715 (vol (get-lcos-volume k nuc
)))
716 (format t
"~a~%" `(nuc ,nuc
))
717 (write-section "/home/martin/tmp/angular-0lcos-cut.pgm" vol
)
718 (save-stack-ub8 "/home/martin/tmp/angular-0lcos" (normalize-3-csf/ub8-realpart vol
)))
721 (defvar *nucleus-index
* 0)
722 (defvar *bfp-window-radius
* .08d0
)
724 ;; the following global variable contain state for merit-function:
725 ;; *bfp-window-radius* *nucleus-index* *spheres-c-r*
726 (defun merit-function (vec2)
727 (declare ((simple-array double-float
(2)) vec2
)
728 (values double-float
&optional
))
729 (let* ((border-value 0d0
) ;; value to return when outside of bfp
730 ;; this has to be considerably bigger than the maxima on the bfp
731 (border-width *bfp-window-radius
*) ;; in this range to the
736 (radius (norm2 vec2
)))
737 (if (< radius
(- .99d0 border-width
))
739 (loop for dirs in
'((:right
:left
)
741 (loop for dir in dirs do
742 (loop for bfp-dir in dirs do
744 (illuminate-ray *spheres-c-r
* *nucleus-index
* dir
745 (vec2-x vec2
) (vec2-y vec2
)
748 ;; in the border-width or outside of bfp
749 (incf sum border-value
))
752 (defun find-optimal-bfp-window-center (nucleus)
753 (declare (fixnum nucleus
)
754 (values vec2
&optional
))
755 (let ((*nucleus-index
* nucleus
))
757 (multiple-value-bind (min point
)
758 (simplex-anneal:anneal
(simplex-anneal:make-simplex
759 (make-vec2 :x -
1d0
:y -
1d0
) 1d0
)
761 ;; set temperature bigger than the
762 ;; maxima in the bfp but smaller
764 :start-temperature
2.4d0
769 (return-from find-optimal-bfp-window-center point
))))))
772 (find-optimal-bfp-window-center 0)
773 ;; FIXME: are these coordinates in mm or relative positions for a bfp-radius of 1?
774 ;; I think the latter, but I'm not sure.
780 (z (array-dimension *spheres
* 0))
781 (psf (angular-psf :x r
:y r
:z
(* 2 z
)
782 :pixel-size-x dx
:pixel-size-z dz
783 :window-radius
*bfp-window-radius
*
788 :integrand-evaluations
400)))
789 (save-stack-ub8 "/home/martin/tmp/psf" (normalize3-cdf/ub8-realpart psf
))
793 ;; calculate the excitation one nucleus
795 (defun calc-light-field (k nucleus
)
796 (declare (fixnum k nucleus
))
798 (lcos (get-lcos-volume k nucleus
))
799 (bfp-pos (find-optimal-bfp-window-center nucleus
))
803 (z (array-dimension *spheres
* 0)))
804 (multiple-value-bind (h ddx ddz
)
805 (angular-intensity-psf-minimal-resolution
806 :x-um
(* r dx
) :y-um
(* r dx
) :z-um
(* 2 z dz
)
807 :window-radius
*bfp-window-radius
*
808 :window-x
(vec2-x bfp-pos
)
809 :window-y
(vec2-y bfp-pos
)
812 :integrand-evaluations
1000)
813 (resample-3-cdf h ddx ddx ddz dx dx dz
)))))
814 (format t
"~a~%" `(bfp-pos ,bfp-pos
))
815 (write-section (format nil
"/home/martin/tmp/angular-1expsf-cut-~3,'0d.pgm" nucleus
) psf
)
817 (multiple-value-bind (conv conv-start
)
818 (convolve-nocrop lcos psf
)
819 ;; light distribution in sample
820 (defparameter *angular-light-field
* conv
)
821 (defparameter *angular-light-field-start
* conv-start
)
822 (write-section (format nil
"/home/martin/tmp/angular-2light-cut-~3,'0d.pgm" nucleus
)
824 (vec-i-y (aref *centers
* nucleus
)))
825 (save-stack-ub8 (format nil
"/home/martin/tmp/angular-2light-~3,'0d/" nucleus
)
826 (normalize-3-cdf/ub8-realpart conv
))
827 ;; multiply fluorophore concentration with light distribution
828 (let ((excite (.
* conv
*spheres
* conv-start
)))
829 (defparameter *excite
* excite
)
830 (write-section (format nil
"/home/martin/tmp/angular-3excite-cut-~3,'0d.pgm" nucleus
)
832 (vec-i-y (aref *centers
* nucleus
)))
833 (save-stack-ub8 (format nil
"/home/martin/tmp/angular-3excite-~3,'0d/" nucleus
)
834 (normalize-3-cdf/ub8-realpart excite
))
835 (destructuring-bind (z y x
)
836 (array-dimensions excite
)
837 (declare (ignorable z
))
838 (let* ((in-focus (extract-bbox-3-cdf excite
839 (make-bbox :start
(v 0d0
0d0
(* 1d0 k
))
840 :end
(v (* 1d0
(1- x
))
843 (save-stack-ub8 "/home/martin/tmp/angular-4in-focus/"
844 (normalize-3-cdf/ub8-realpart in-focus
))
845 (let*((mplane (mean (convert-3-cdf/df-realpart in-focus
)))
846 (mvol (mean (convert-3-cdf/df-realpart excite
)))
847 (gamma (/ mplane mvol
)))
848 (push (list mplane mvol gamma
) result
)
849 (debug-out mplane mvol gamma
)
850 (format t
"plane-result ~f ~f ~f~%" mplane mvol gamma
))
856 (with-open-file (*standard-output
* "/home/martin/tmp/angular-stack.log"
858 :if-exists
:supersede
859 :if-does-not-exist
:create
)
861 (dotimes (k (array-dimension *spheres
* 0))
862 (let* ((nucs (get-visible-nuclei k
)))
863 (loop for nuc in nucs do
864 (format t
"~a~%" (list 'doing k nucs
))
865 (push (list k nuc
(calc-light-field k nuc
)) result
))))
866 (defparameter *scan-result
* result
)))
867 (with-open-file (s "/home/martin/tmp/angular-stack-struc.lisp"
869 :if-exists
:supersede
870 :if-does-not-exist
:create
)
871 (write *scan-result
* :stream s
))))
873 #+nil
;; overlay lightfield and spheres
875 (save-stack-ub8 "/home/martin/tmp/sphere-and-excite/"
876 (normalize3-cdf/ub8-realpart
878 (convert3-ub8/cdf-complex
(normalize3-cdf/ub8-realpart vol
))))
879 (.
+ (con *angular-light-field
*)
880 (s* .7d0
(con *spheres
*))
881 *angular-light-field-start
*)))))
884 (dotimes (i (length *centers
*))
886 (raytrace::ray-spheres-intersection
(v) (v 0d0
0d0 -
1d0
) *sphere-c-r
* i
)))
890 (defun merit-function (vec)
892 (values my-float
&optional
))
893 (raytrace:ray-spheres-intersection
895 (normalize (direction (aref vec
0) (aref vec
1)))
897 (let ((start (make-array 2 :element-type
'my-float
898 :initial-contents
(list 100d0
100d0
))))
899 (with-open-file (*standard-output
* "/dev/shm/anneal.log"
901 :if-exists
:supersede
)
902 (anneal (make-simplex start
1d0
)
904 :start-temperature
3d0
))))
907 ;; The merit function should get two parameters r and phi. if r isn't
908 ;; inside the back focal plane radius (minus the diameter of the
909 ;; aperture window) some high value is returned. Several rays should
910 ;; be sent through the spheres starting from different positions on
911 ;; the aperture window and targetting different positions in the
912 ;; circle that should be illuminated in the sample.
914 ;; Maybe later I can add the aperture size in the back focal plane as
915 ;; another parameter. The bigger the aperture, the better for the
918 ;; Possibly I shouldn't call it merit function as I try to minimize
921 (defun get-ray-behind-objective (x-mm y-mm bfp-ratio-x bfp-ratio-y
)
922 "Take a point on the back focal plane and a point in the sample and
923 calculate the ray direction ro that leaves the objective. So its the
924 same calculation that is used for draw-ray-into-vol."
925 (declare (double-float x-mm y-mm bfp-ratio-x bfp-ratio-y
)
926 (values vec vec
&optional
))
927 (let* ((f (lens:focal-length-from-magnification
63d0
))
930 (bfp-radius (lens:back-focal-plane-radius f na
))
931 (obj (lens:make-thin-objective
:normal
(v 0d0
0d0 -
1d0
)
935 :numerical-aperture na
936 :immersion-index ri
))
937 (theta (lens:find-inverse-ray-angle x-mm y-mm obj
))
938 (phi (atan y-mm x-mm
))
939 (start (v (* bfp-ratio-x bfp-radius
)
940 (* bfp-ratio-y bfp-radius
)
942 (multiple-value-bind (ro s
)
943 (lens:thin-objective-ray obj
945 (v* (v (* (cos phi
) (sin theta
))
946 (* (sin phi
) (sin theta
))
952 (get-ray-behind-objective .1d0
.1d0
0d0
0d0
)
954 ;; In *spheres-c-r* I stored the coordinates of all the nuclei
955 ;; relative to the center of the initial stack of images. It also
956 ;; contains the radius of each nuclieus. Now I consider how to
957 ;; illuminate selected circles inside of the sample. The nucleus which
958 ;; is beeing illuminated will be centered on the focal plane. The
959 ;; length of the vector ro coming out of the objective is
960 ;; nf=1.515*2.6mm~3000um and therefore a lot bigger than the z extent
961 ;; of the stack (~40 um). It is not necessary to z-shift the nuclei
962 ;; before intersecting them with the rays. So I will just use the
963 ;; nucleus' x and y coordinates as arguments to
964 ;; get-ray-behind-objective. I also supply the position in the back
965 ;; focal plane from where the ray originates.
967 (deftype direction
()
968 `(member :left
:right
:top
:bottom
))
970 (defun sample-circle (center radius direction
)
971 "Given a circle CENTER and RADIUS return the point in the left,
972 right, top or bottom of its periphery. CENTER and result are complex
974 (declare ((complex double-float
) center
)
975 (double-float radius
)
976 (direction direction
)
977 (values (complex double-float
) &optional
))
978 (let ((phi (ecase direction
982 (:bottom
(* 1.5d0 pi
)))))
983 (+ center
(* radius
(exp (complex 0d0 phi
))))))
986 (sample-circle (complex 1d0
1d0
) 1d0
:right
)
988 #+nil
;; prepare volume for drawing lines
990 (defparameter *spheres-ub8
* (normalize-3-csf/ub8-realpart
991 (resample-3-csf *spheres
* .2 .2 1.0 .2 .2 .2)))
993 (save-stack-ub8 "/home/martin/tmp/spheres-ub8" *spheres-ub8
*)
996 (with-slots (center radius
)
997 (aref *spheres-c-r
* 0)
998 (let* ((sample-pos (complex 0d0
)#+nil
(sample-circle (complex (vec-x center
) (vec-y center
))
1001 (bfp-pos (complex 0d0
)#+nil
(sample-circle (complex 0d0
0)
1004 (draw-ray-into-vol (realpart sample-pos
)
1005 (imagpart sample-pos
)
1010 (init-angular-model)
1012 (destructuring-bind (z y x
)
1018 (f (lens:focal-length-from-magnification
63d0
))
1021 (bfp-radius (lens:back-focal-plane-radius f na
))
1022 (obj (lens:make-thin-objective
:normal
(v 0d0
0d0 -
1d0
)
1026 :numerical-aperture na
1027 :immersion-index ri
))
1028 (theta (lens:find-inverse-ray-angle x-mm y-mm obj
))
1029 (phi (atan y-mm x-mm
))
1030 (start (v (* bfp-radius bfp-ratio-x
)
1031 (* bfp-radius bfp-ratio-y
)
1035 (cz (* .5d0 z
)) ;; position that is in the center of front focal plane
1040 (debug-out f bfp-radius theta phi
)
1041 (macrolet ((plane (direction position
)
1042 ;; for defining a plane that is perpendicular to an
1043 ;; axis and crosses it at POSITION
1044 (declare (type (member :x
:y
:z
) direction
))
1045 (let* ((normal (ecase direction
1048 (:z
(v 0d0
0d0
1d0
)))))
1049 `(let* ((pos ,position
)
1050 (center (v* ,normal pos
))
1051 (outer-normal (normalize center
)))
1052 (declare (type double-float pos
))
1053 (lens::make-disk
:normal outer-normal
:center center
)))))
1054 (let ((p+z
(plane :z
(- (* dz
(- z cz
))
1056 (p-z (plane :z
(- (* dz
(- (- z cz
)))
1058 (p+y
(plane :y
(* dx
(- y cy
))))
1059 (p-y (plane :y
(* dx
(- (- y cy
)))))
1060 (p+x
(plane :x
(* dx
(- x cx
))))
1061 (p-x (plane :x
(* dx
(- (- x cx
))))))
1062 (multiple-value-bind (ro s
)
1063 (lens:thin-objective-ray obj
1065 (v* (v (* (cos phi
) (sin theta
))
1066 (* (sin phi
) (sin theta
))
1069 (format t
"~a~%" (list 'dir
(v* (v (* (cos phi
) (sin theta
))
1070 (* (sin phi
) (sin theta
))
1074 's-new
(v+ s
(v 0d0
0d0
(* dz shift-z
)))))
1075 (setf s
(v+ s
(v 0d0
0d0
(* dz shift-z
))))
1076 (let* ((nro (normalize ro
)))
1078 (macrolet ((hit (plane)
1079 ;; find intersection between plane and the ray
1080 `(multiple-value-bind (dir hit-point
)
1081 (lens::plane-ray
,plane
1082 ;; shift start of vector a bit
1085 (declare (ignore dir
))
1088 ;; convert coordinates from mm into integer pixel positions
1089 `(let ((h ,hit-expr
))
1090 (declare (type (or null vec
) h
))
1093 :z
(floor (+ cz
(/ (+ (aref h
2) nf
) dz
)))
1094 :y
(floor (+ cy
(/ (aref h
1) dx
)))
1095 :x
(floor (+ cx
(/ (aref h
0) dx
))))))))
1096 (let* ((h+z
(pixel (hit p
+z
)))
1097 (h-z (pixel (hit p-z
)))
1098 (h+y
(pixel (hit p
+y
)))
1099 (h-y (pixel (hit p-y
)))
1100 (h+x
(pixel (hit p
+x
)))
1101 (h-x (pixel (hit p-x
)))
1102 ;; make a list of all the points
1103 (hlist (list h
+z h-z h
+y h-y h
+x h-x
))
1104 ;; throw away points that are nil or that contain
1105 ;; coordinates outside of the array dimensions
1107 (remove-if-not #'(lambda (v)
1109 (and (< -
1 (vec-i-x v
) x
)
1110 (< -
1 (vec-i-y v
) y
)
1111 (< -
1 (vec-i-z v
) z
))
1113 ;; sort best points by x
1114 (choice (sort filtered-hlist
#'< :key
(lambda (v) (vec-i-x v
)))))
1116 (format t
"~a~%" (list 'choice choice
))
1117 #+nil
(scan-convert-line3
1120 *spheres-ub8
*)))))))))
1123 (destructuring-bind (z y x
)
1124 (array-dimensions vol
)
1125 (let* ((f (lens:focal-length-from-magnification
63d0
))
1128 (bfp-radius (lens:back-focal-plane-radius f na
))
1129 (obj (lens:make-thin-objective
:normal
(v 0d0
0d0 -
1d0
)
1133 :numerical-aperture na
1134 :immersion-index ri
))
1135 (theta (lens:find-inverse-ray-angle x-mm y-mm obj
))
1136 (phi (atan y-mm x-mm
))
1137 (start (v (* bfp-ratio-x bfp-radius
)
1138 (* bfp-ratio-y bfp-radius
)
1142 (cz (* .5d0 z
)) ;; position that is in the center of front focal plane
1146 (macrolet ((plane (direction position
)
1147 ;; for defining a plane that is perpendicular to an
1148 ;; axis and crosses it at POSITION
1149 (declare (type (member :x
:y
:z
) direction
))
1150 (let* ((normal (ecase direction
1153 (:z
(v 0d0
0d0
1d0
)))))
1154 `(let* ((pos ,position
)
1155 (center (v* ,normal pos
))
1156 (outer-normal (normalize center
)))
1157 (declare (type double-float pos
))
1158 (lens::make-disk
:normal outer-normal
:center center
)))))
1159 ;; define the borders of the viewing volume, distances in mm
1160 (let ((p+z
(plane :z
(- (* dz
(- z cz
))
1162 (p-z (plane :z
(- (* dz
(- (- z cz
)))
1164 (p+y
(plane :y
(* dx
(- y cy
))))
1165 (p-y (plane :y
(* dx
(- (- y cy
)))))
1166 (p+x
(plane :x
(* dx
(- x cx
))))
1167 (p-x (plane :x
(* dx
(- (- x cx
))))))
1168 (multiple-value-bind (ro s
)
1169 (lens:thin-objective-ray obj
1171 (v* (v (* (cos phi
) (sin theta
))
1172 (* (sin phi
) (sin theta
))
1175 (setf s
(v+ s
(v 0d0
0d0
(* dz shift-z
))))
1176 (let* ((nro (normalize ro
)))
1177 (macrolet ((hit (plane)
1178 ;; (declare (type lens::disk plane))
1179 ;; find intersection between plane and the ray
1180 `(multiple-value-bind (dir hit-point
)
1181 (lens::plane-ray
,plane
1182 ;; shift start of vector a bit
1185 (declare (ignore dir
))
1188 ;; convert coordinates from mm into integer pixel positions
1189 `(let ((h ,hit-expr
))
1190 (declare (type (or null vec
) h
))
1193 :z
(floor (+ cz
(/ (+ (aref h
2) nf
) dz
)))
1194 :y
(floor (+ cy
(/ (aref h
1) dx
)))
1195 :x
(floor (+ cx
(/ (aref h
0) dx
))))))))
1196 (let* ((h+z
(pixel (hit p
+z
)))
1197 (h-z (pixel (hit p-z
)))
1198 (h+y
(pixel (hit p
+y
)))
1199 (h-y (pixel (hit p-y
)))
1200 (h+x
(pixel (hit p
+x
)))
1201 (h-x (pixel (hit p-x
)))
1202 ;; make a list of all the points
1203 (hlist (list h
+z h-z h
+y h-y h
+x h-x
))
1204 ;; throw away points that are nil or that contain
1205 ;; coordinates outside of the array dimensions
1207 (remove-if-not #'(lambda (v)
1209 (and (< -
1 (vec-i-x v
) x
)
1210 (< -
1 (vec-i-y v
) y
)
1211 (< -
1 (vec-i-z v
) z
))
1213 ;; sort best points by x
1214 (choice (sort filtered-hlist
#'< :key
(lambda (v) (vec-i-x v
)))))
1215 (format t
"~a~%" (list 'choice choice
))
1221 (defun illuminate-ray (spheres-c-r illuminated-sphere-index
1223 bfp-center-x bfp-center-y
1224 bfp-radius bfp-position
)
1225 "Trace a ray from a point in the back focal plane through the disk
1226 that encompasses the nucleus with index
1227 ILLUMINATED-SPHERE-INDEX. SAMPLE-POSITION and BFP-POSITION can assume
1228 one of the four values :LEFT, :RIGHT, :TOP and :BOTTOM indicating
1229 which point on the periphery of the correspondi2ng circle is meant
1230 Coordinates in the back focal plane are ratios, e.g. bfp-center-x=1 is
1231 on the border and a window with bfp-radius=1 passes all light through
1233 (declare (fixnum illuminated-sphere-index
)
1234 (direction sample-position bfp-position
)
1235 (double-float bfp-center-x bfp-center-y bfp-radius
)
1236 ((simple-array sphere
1) spheres-c-r
)
1237 (values double-float
&optional
))
1238 (with-slots (center radius
)
1239 (aref spheres-c-r illuminated-sphere-index
)
1240 (let* ((sample-pos (sample-circle (complex (vec-x center
) (vec-y center
))
1243 (bfp-pos (sample-circle (complex bfp-center-x bfp-center-y
)
1246 (multiple-value-bind (ro s
)
1247 (get-ray-behind-objective (realpart sample-pos
)
1248 (imagpart sample-pos
)
1252 (ray-spheres-intersection
1253 ;; shift by nf so that sample is in origin
1257 (* 1.515d0
(lens:focal-length-from-magnification
63d0
))))
1260 illuminated-sphere-index
)))
1264 (illuminate-ray *spheres-c-r
* 20 :bottom
1268 (defvar *bfp-window-radius
* .0001d0
)
1270 #+nil
;; store the scan for each nucleus in the bfp
1273 (a (make-array (list n n
) :element-type
'double-float
))
1274 (nn (length *spheres-c-r
*))
1275 (mosaicx (ceiling (sqrt nn
)))
1276 (mosaic (make-array (list (* n mosaicx
) (* n mosaicx
))
1277 :element-type
'double-float
)))
1278 (dotimes (*nucleus-index
* 3 nn
)
1281 (let ((x (- (* 2d0
(/ i n
)) 1d0
))
1282 (y (- (* 2d0
(/ j n
)) 1d0
)))
1285 (make-vec2 :x x
:y y
))))))
1286 (do-region ((j i
) (n n
))
1287 (let ((x (mod *nucleus-index
* mosaicx
))
1288 (y (floor *nucleus-index
* mosaicx
)))
1289 (setf (aref mosaic
(+ (* n y
) j
) (+ (* n x
) i
))
1291 (write-pgm "/home/martin/tmp/scan-mosaic.pgm" (normalize-2-df/ub8 mosaic
))))
1294 (let ((vol (make-array dims
:element-type
'(unsigned-byte 8))))
1295 (loop for i in
'(4.0d-3 -
.2d-3
) do
1296 (draw-ray-into-vol i
0d0
.99d0
.0d0 vol
)
1297 #+nil
(draw-ray-into-vol i
0d0 -
.99d0
.0d0 vol
)
1298 (draw-ray-into-vol i
0d0
0d0
.99d0 vol
)
1299 (draw-ray-into-vol i
0d0
0d0 -
.99d0 vol
))
1301 (save-stack-ub8 "/home/martin/tmp/line"
1308 (a (make-array (list n n
) :element-type
'(unsigned-byte 8)))
1309 (nn (length *spheres-c-r
*))
1310 (mosaicx (ceiling (sqrt nn
)))
1311 (mosaic (make-array (list (* n mosaicx
) (* n mosaicx
))
1312 :element-type
'(unsigned-byte 8))))
1313 (with-open-file (*standard-output
* "/dev/shm/a"
1315 :if-exists
:supersede
)
1316 (dotimes (*nucleus-index
* nn
)
1319 (multiple-value-bind (min point
)
1320 (simplex-anneal:anneal
(simplex-anneal:make-simplex
1321 (make-vec2 :x -
1d0
:y -
1d0
) 1d0
)
1323 ;; set temperature bigger than the
1324 ;; maxima in the bfp but smaller
1325 ;; than border-value
1326 :start-temperature
2.4d0
1330 (unless (<= min
100d0
)
1332 (let* ((x (aref point
0))
1334 (ix (floor (* n
(+ x
1)) 2))
1335 (iy (floor (* n
(+ y
1)) 2))
1336 (mx (mod *nucleus-index
* mosaicx
))
1337 (my (floor *nucleus-index
* mosaicx
)))
1338 (incf (aref mosaic
(+ (* n my
) iy
) (+ (* n mx
) ix
)))
1339 (format t
"min ~a~%" (list min ix iy
))))))))
1340 (write-pgm "/home/martin/tmp/scan-mosaic-max.pgm" mosaic
)))