Adding pupil obscuration model to stack

Hello community!

I’m in the process of designing a full-field optical PSF model for the LSST stack. One of the necessary ingredients for this is a model of the pupil obscuration function, i.e., which parts of the pupil (more-or-less the primary mirror) are visible and reflect sky as opposed to struts, camera, etc. This boolean function will vary with position on the focal plane and the camera rotator angle, and it is probably a good idea to make the size and resolution of the output array configurable as well.

Looking through the available LSST packages, and talking/slacking with @KSK, @jbosch, and @RHL, I think afw.cameraGeom.Camera looks like the natural home for such a product. My understanding is that for a given data repository, cameras are uniquely accessible from the butler (i.e., there is exactly one Camera per data repo). So the PSF modeling code could access the Pupil through the repo Camera which is accessible through the Butler. Alternatively, the Pupil could exist at the same level as the Camera instead of being an attribute thereof, and directly accessible by the Butler. I personally favor the former approach though as it prevents enabling incompatible Camera/Pupil combinations.

I have a few additional implementation questions:

  • Should the pupil obscuration function be persistable? The pupil model I have in mind consists of set operations on various 2D geometric objects – circles, rays-with-finite-width, rectangles, … The parameters of each geometric object depend (linearly for now) on the focal plane position and trigonometrically on the camera rotator angle, which means it may be possible to create a generic PupilConfig format that could be persisted, and used to represent different cameras, or possibly different levels of complexity for a given camera. I’m not convinced at the moment, however, that this is actually worth the effort. It would be much simpler to just implement a specific class for each camera (all derived from the same abstract base class), and then attach that to the Camera object.

  • I think it makes sense to split the input arguments into rotator angle, size/scale params (which will be held constant for a given exposure) and focal plane x/y which, of course, vary object to object. So it may make sense to attach a camera-specific PupilFactory to the Camera object, which has a getPupil(VisitInfo, size, scale) method that returns a Pupil object. The Pupil object itself would then be callable or have a callable method as a function of focal plane x/y, returning a boolean array.

  • As for actually attaching the PupilFactory to the Camera, I see that, at least for HSC in obs_subaru, the Camera is constructed though the chain

HscMapper.__init__
CameraMapper.__init__
CameraMapper._makeCamera
cameraGeom.makeCameraFromPath
cameraGeom.Camera.__init__

So one possibility would be to add a pupilFactory kwarg to Camera.__init__ and make sure makeCameraFromPath (and any other camera factories) use it. CameraMapper could have a class variable PupilFactoryClass (similar to MakeRawVisitInfoClass) pointing to a default PupilFactory (possibly the ABC) and HscMapper.PupilFactoryClass could override this by pointing to HscPupilFactory. Then when _makeCamera gets called, it uses self.PupilFactoryClass to pass to makeCameraFromPath.

Thoughts?

Also, does any of this require an RFC?

Thanks,
-Josh

@jmeyers314 overall this sounds pretty good to me. Some answers:

  • persistence: It sounds like there is no obvious way to make this globally configurable: i.e. there is not a set of self consistent configuration parameters that could be used to describe all pupil models. I think that means that it should be implemented in code. I think that implies that we don’t need to make it explicitly persistable. If we need it, we could look at pickle.
  • design: The general design sounds good. My only question is what is the boolean array? I guess I would have assumed it would return a fraction of obscuration (or something similar).
  • instantiation: Your suggestion for how to attach a pupil model to a camera sounds good to me. I like the idea of having a PupilFactoryClass that gets passed to the Camera constructor.
  • I think the only part that requires a formal RFC is the change to the CameraMapper and Camera APIs. I don’t think you need to review the design of the pupil model (but you could if you want).

Thanks @KSK,

The Fourier optics PSF model requires the actual pattern of obscured/unobscured light (in pupil plane coordinates), not just the obscuration fraction.

Roughly speaking, for one spot in the focal plane,

$$\mathrm{PSF} \propto |FT[ P(u,v) \exp(2 \pi i W(u,v) / \lambda)]|^2$$

where $FT$ indicates a Fourier transform, $W(u,v)$ is the wavefront, and $P(u,v)$ is the pupil obscuration function, and $u$, $v$ are pupil plane coordinates.

Edited original post slightly. I accidentally left out part of the chain from HscMapper.__init__ to Camera.__init__ (specifically, makeCameraFromPath).

Adding this back in implies that code that calls makeCameraFromPath without passing through CameraMapper._makeCamera may require an update, though I think it’s also reasonable to use a default kwarg here which would prevent the need for said updates:

def makeCameraFromPath(..., pupilFactory=cameraGeom.BasePupilFactory):
    ...

My one worry, if the obscuration is implemented entirely in code, is what happens when some component of the system is replaced, or moves, or is measured to be different than we thought, thus resulting in a slightly different physical obscuration. Could the parameters of the model be persistable, in a way that only that specific model (e.g HscPupilFactory) can understand, so that we can change and version those parameters as the system evolves over time?

Thanks @parejkoj. I agree with your concern and that we should address it at some point. I assume (haven’t actually looked/attempted) that it’s easy to persist the values of some fixed set of parameters, and that would cover at least the case where a bad values get corrected. I think it would be nicer though to have something more generic, that could cover both multiple cameras and nontrivial updates/versions (new/different parameters) for a given camera. Something like a yaml file with lists of geometric obscurations + their transformation with rotation/focal plane position. I don’t think this would be too tricky, though at the moment it’s probably more of a distraction than an immediate requirement, so I’m inclined to just leave it on the back burner for now.