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