brandondube / prysm

physical optics: integrated modeling, phase retrieval, segmented systems, polynomials and fitting, sequential raytracing...
https://prysm.readthedocs.io/en/stable/
MIT License
257 stars 44 forks source link

controling the amount of defocus with sum_of_2d_modes (vs old syntax) #39

Closed diplodocuslongus closed 3 years ago

diplodocuslongus commented 3 years ago

Hi Brandon,

This is somehow a follow-up to issue https://github.com/brandondube/prysm/issues/38 but I thought it'd be better to have a dedicated write-up for clarity and conciseness (and future reference).

Considering the following as an illustrative example:

nms = [noll_to_nm(j) for j in range(2,25)]
idefocus = nms.index((4,0)) # index for defocus, element 9 in the list above
basis_set = list(zernike_nm_sequence(nms, r, t)) # (r,t having been adequately defined) 
coefs = np.zeros(len(nms)) 
phs = sum_of_2d_modes(basis_set, coefs)  # no defocus, no phase change
coefs[idefocus] = 1e-6 # defocus is now weighted, is it 1um of Z4 defocus?
phs_defc = sum_of_2d_modes(basis_set, coefs)
# go on and e.g., compute a pupil and derive a psf

Thanks, Ludo

brandondube commented 3 years ago

That still has my typo -- awful how those propagate :( The second line should be (2,0) instead of (4,0) -- defocus is the 2nd order term, not 4th order.

Anyway...

The new design is completely decoupled. The polynomial code knows nothing at all of the propagator, in fact that function is just a nicer name for a one-liner that is the fastest way to do that calculation -- https://github.com/brandondube/prysm/blob/master/prysm/polynomials/__init__.py#L163-L181

So at the time that you are making some Zernike basis thing (the output of sum of 2D modes), the units are whatever you like them to be. You could think of that "as in your head," too.

from_amp_and_phase expects nanometers, https://github.com/brandondube/prysm/blob/master/prysm/propagation.py#L420

so it would be logical to think in nanometers for everything, in which case your 1e-6 there is a femtometer.

Microns are slightly ambiguous units. You can have a micron of OPD, you can also have a micron of motion of the image plane. For a micron of OPD, you have all the machinery there. For a micron of image plane motion, you can use thinlens.image_displacement_to_defocus to compute how much defocus it is. Those numbers are W020 (hopkins' expansion) though, i.e., r^2. If you use norm=False to make Zernikes, then the Zernike defocus is 2r^2 - 1, or sqrt(3) (2r^2 - 1) with norm=True. The piston (1) is not meaningful and you can ignore it. If your Zernikes are normed, you should divide the return of image displacement to defocus by 2 sqrt3, in addition to any other unit conversion you need to do.

The reference to lentil, etc, is more of a "if you came from tool __, this is the nearest equivalent function in prysm" not "this function fulfills exactly the same interface."

diplodocuslongus commented 3 years ago

That still has my typo -- awful how those propagate :( The second line should be (2,0) instead of (4,0) -- defocus is the 2nd order term, not 4th

My fault. And I knew: at the top of my notebook mentioned in this post of #38 I had put a reminder that Noll Z4 corresponded to n,m=2,0, and I blindly copied your example.

in fact that function is just a nicer name for a one-liner that is the fastest way to do that calculation

Sure sum_of_2d_modes is more explicit than np.tensordot...

from_amp_and_phase expects nanometers, https://github.com/brandondube/prysm/blob/master/prysm/propagation.py#L420

It's actually in the doc too.

so it would be logical to think in nanometers for everything, in which case your 1e-6 there is a femtometer.

1fm of phase difference didn't show up in the psf or convolution :-) (but 5nm did).

You can have a micron of OPD, you can also have a micron of motion of the image plane.

The former relates to the wavefront while the later I would connect to the depth of focus (tolerance on the image plane), so 2 different notion indeed, and the difference may easily be overlooked so it's good to remind about it.

For a micron of OPD, you have all the machinery there.

Yes, updated my notebook here.

For a micron of image plane motion, you can use thinlens.image_displacement_to_defocus to compute how much defocus it is.

Good to know. What I want to do next is to try to model the image of a simple 3D object (a hole), but before that I'll spend more time with getting familiar with prysm.

The reference to lentil, etc, is more of a "if you came from tool __, this is the nearest equivalent function in prysm"

That's how I had understood you though I wasn't coming from any tool in particular (I knew very little about poppy and pyoptica, dates back 2yrs ago, didn't know lentil). :-)

brandondube commented 3 years ago

Sure sum_of_2d_modes is more explicit than np.tensordot...

Yes, and IMO it is a faux-pas to require the user to understand how to drive tensordot. There are other ways to do it, like a loop, or using einsum. This way also boxes the implementation with a name, so that if the implementation were to change as long as the interface does not, there is no problem.

It's actually in the doc too.

the link I sent is where the docs come from =)

As one more aside, with the MTF plot you can see what Q controls. Look at the "whole" MTF with no phase error at Q=2 and you'll see it reaches zero right when the data (array) ends. Do it again at Q > 2 and note the ratio of the frequency where the zero is to where the data ends.