5 ;; run the following code to test the downhill simplex optimizer on a
15 ;; |-------------+-------+------- <- z
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
)
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)
241 (window-x (- 1d0 window-radius
))
246 (numerical-aperture 1.38d0
)
247 (immersion-index 1.515d0
)
248 (integrand-evaluations 30)
251 (declare (fixnum x y z integrand-evaluations
)
252 (double-float window-radius window-x window-y
253 wavelength numerical-aperture
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
)
262 (lambd (/ wavelength ri
))
264 (dz2 (* dz
(/ 2d0
(- 1d0
(sqrt (- 1d0
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
(* z dz3
) (* x dx3
)
289 :numerical-aperture na
291 :wavelength wavelength
292 :integrand-evaluations integrand-evaluations
)
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
)
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
))))
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
))))
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
))))))
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
)))))
344 (angular-psf :x
128 :z
64 :integrand-evaluations
280 :debug t
)
347 (defmacro debug-out
(&rest rest
)
349 (list ,@(mapcar #'(lambda (x) `(list ,(format nil
"~a" x
) ,x
))
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
364 from top
: ---
/ | \---
387 from side ----
/ kz | \---v |
402 |-----------------------
+--o-------o------o--------
+ kx
409 (defun angular-intensity-psf-minimal-resolution (&key
410 (x-um 50d0
) (y-um x-um
) (z-um 40d0
)
415 (numerical-aperture 1.38d0
)
416 (immersion-index 1.515d0
)
417 (integrand-evaluations 30)
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
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
)))
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
)))
449 (bot (if (< rho
(- R r
)) ;; window is in center of bfp
452 (kzextent (if store-cap
453 ;; store the full cap without wrapping,
454 ;; this way one can reuse the efields
456 ;; just leave enough space to accommodate
457 ;; the final donut, this improves
458 ;; performance a lot for small windows
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
468 (dz (/ pi kzextent
)))
469 (debug-out dx dz kxextent R rr kzextent k rho
)
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
481 :initialize initialize
)
486 (multiple-value-bind (a dx dz
)
487 (angular-intensity-psf-minimal-resolution :x-um
20d0
:z-um
40d0
491 :integrand-evaluations
1000
493 (write-pgm "/home/martin/tmp/cut5-resampled.pgm"
494 (normalize2-cdf/ub8-realpart
496 (resample3-cdf a dx dx dz
.2d0
.2d0
.2d0
))))))
500 (defmacro defstuff
()
502 ,@(loop for i in
'(*dims
* ;; dimensions of the input stack in
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
513 `(defparameter ,i nil
))))
517 (defun create-sphere-array (dims centers
)
519 ((simple-array vec-i
1) centers
)
520 (values (simple-array sphere
1) &optional
))
521 (destructuring-bind (z y x
)
523 (declare (fixnum z y x
))
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
)))))
538 (push (make-sphere :center
(convert-point (aref centers i
))
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
)
548 (declare (ignore ch
))
549 (defparameter *centers
* c
)
550 (defparameter *dims
* dims
)
553 (defparameter *spheres-c-r
*
554 (create-sphere-array *dims
* *centers
*))
556 (destructuring-bind (z y x
)
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)))))
566 (destructuring-bind (z y x
)
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
)))
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
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.
593 (psf (destructuring-bind (z y x
)
595 (declare (ignore y x
))
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
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
)))
611 (time (init-angular-psf)) ;; 62.2 s
613 (defun get-visible-nuclei (k)
614 "Find all the nuclei in slice K."
616 (values list
&optional
))
617 (destructuring-bind (z y x
)
618 (array-dimensions *index-spheres
*)
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
)))))
629 (setf (aref result v
) t
))))
630 (loop for i from
1 below n
636 (loop for i below
(array-dimension *index-spheres
* 0)
638 (list i
(get-visible-nuclei i
))))
641 ;; create a volume containing just the current slice
642 (defun get-lcos-volume (k nucleus
)
644 (values (simple-array (complex double-float
) 3) &optional
))
645 (destructuring-bind (z y x
)
646 (array-dimensions *index-spheres
*)
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
))))
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
688 (radius (norm2 vec2
)))
689 (if (< radius
(- .99d0 border-width
))
691 (loop for dirs in
'((:right
:left
)
693 (loop for dir in dirs do
694 (loop for bfp-dir in dirs do
696 (illuminate-ray *spheres-c-r
* *nucleus-index
* dir
697 (vec2-x vec2
) (vec2-y vec2
)
700 ;; in the border-width or outside of bfp
701 (incf sum border-value
))
704 (defun find-optimal-bfp-window-center (nucleus)
705 (declare (fixnum nucleus
)
706 (values vec2
&optional
))
707 (let ((*nucleus-index
* nucleus
))
709 (multiple-value-bind (min point
)
710 (simplex-anneal:anneal
(simplex-anneal:make-simplex
711 (make-vec2 :x -
1d0
:y -
1d0
) 1d0
)
713 ;; set temperature bigger than the
714 ;; maxima in the bfp but smaller
716 :start-temperature
2.4d0
721 (return-from find-optimal-bfp-window-center point
))))))
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.
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
*
740 :integrand-evaluations
400)))
741 (save-stack-ub8 "/home/martin/tmp/psf" (normalize3-cdf/ub8-realpart psf
))
745 ;; calculate the excitation one nucleus
746 (defun calc-light-field (k nucleus
)
747 (declare (fixnum k nucleus
))
749 (lcos (get-lcos-volume k nucleus
))
750 (bfp-pos (find-optimal-bfp-window-center nucleus
))
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
)
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
)
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
)
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
))
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
))
807 (with-open-file (*standard-output
* "/home/martin/tmp/angular-stack.log"
809 :if-exists
:supersede
810 :if-does-not-exist
:create
)
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"
820 :if-exists
:supersede
821 :if-does-not-exist
:create
)
822 (write *scan-result
* :stream s
))))
824 #+nil
;; overlay lightfield and spheres
826 (save-stack-ub8 "/home/martin/tmp/sphere-and-excite/"
827 (normalize3-cdf/ub8-realpart
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
*)))))
835 (dotimes (i (length *centers
*))
837 (raytrace::ray-spheres-intersection
(v) (v 0d0
0d0 -
1d0
) *sphere-c-r
* i
)))
841 (defun merit-function (vec)
843 (values double-float
&optional
))
844 (raytrace:ray-spheres-intersection
846 (normalize (direction (aref vec
0) (aref vec
1)))
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"
852 :if-exists
:supersede
)
853 (anneal (make-simplex start
1d0
)
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
869 ;; Possibly I shouldn't call it merit function as I try to minimize
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
))
881 (bfp-radius (lens:back-focal-plane-radius f na
))
882 (obj (lens:make-thin-objective
:normal
(v 0d0
0d0 -
1d0
)
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
)
893 (multiple-value-bind (ro s
)
894 (lens:thin-objective-ray obj
896 (v* (v (* (cos phi
) (sin theta
))
897 (* (sin phi
) (sin theta
))
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
925 (declare ((complex double-float
) center
)
926 (double-float radius
)
927 (direction direction
)
928 (values (complex double-float
) &optional
))
929 (let ((phi (ecase direction
933 (:bottom
(* 1.5 pi
)))))
934 (+ center
(* radius
(exp (complex 0d0 phi
))))))
937 (sample-circle (complex 1d0
1d0
) 1d0
:right
)
939 (defun illuminate-ray (spheres-c-r illuminated-sphere-index
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
))
958 (bfp-pos (sample-circle (complex bfp-center-x bfp-center-y
)
961 (multiple-value-bind (ro s
)
962 (get-ray-behind-objective (realpart sample-pos
)
963 (imagpart sample-pos
)
967 (ray-spheres-intersection
968 ;; shift by nf so that sample is in origin
972 (* 1.515 (lens:focal-length-from-magnification
63d0
))))
975 illuminated-sphere-index
)))
979 (illuminate-ray *spheres-c-r
* 30 :bottom
983 #+nil
;; store the scan for each nucleus in the bfp
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
)
994 (let ((x (- (* 2d0
(/ i n
)) 1d0
))
995 (y (- (* 2d0
(/ j n
)) 1d0
)))
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
))
1004 (write-pgm "/home/martin/tmp/scan-mosaic.pgm" (normalize-ub8 mosaic
))))
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"
1017 :if-exists
:supersede
)
1018 (dotimes (*nucleus-index
* nn
)
1021 (multiple-value-bind (min point
)
1022 (simplex-anneal:anneal
(simplex-anneal:make-simplex
1023 (make-vec2 :x -
1d0
:y -
1d0
) 1d0
)
1025 ;; set temperature bigger than the
1026 ;; maxima in the bfp but smaller
1027 ;; than border-value
1028 :start-temperature
2.4d0
1032 (unless (<= min
100d0
)
1034 (let* ((x (aref point
0))
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
)))