-
Notifications
You must be signed in to change notification settings - Fork 269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PSF model #2643
base: main
Are you sure you want to change the base?
Conversation
Quick question: are these models applicable for both gamma-ray and optical PSF? If not, maybe call it OpticalPSFModel to be more clear where it should be used. |
I think this is purely optical, not IRF. Also, I think the appropriate place would be in |
The model accounts for coma abbarations and the default parameters i have would be for photons in the optical range. With different parameters this should work with gamma photons, but i am not sure. I could call the ComeModel OpticalComaModel and keep the parent class as is. Then later models specifically for gamma photons can be added. |
Makes sense. I will move it to optics. |
Side note: we should check with what is used in SimPipe's instrument model, to ensure what is output here is compatible with what the simulations expect. |
I don't think there is a model like this in SimPipe, but @orelgueta or @GernotMaier can confirm. Rather coma is naturally resulting from the definition of the mirror facets. |
The Edit: to be more clear, I would expect something like this to work: psf : Optional[PSFModel] = subarray.tel[tel_id].optics.psf
if psf:
plot_psf(psf) # some general function that plots any PSFModel |
src/ctapipe/instrument/optics.py
Outdated
""" | ||
|
||
@classmethod | ||
def from_name(cls, name, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be confusing here... In the instrument model, we use from_name
to load an instance of named model from a file (e.g. Telecope.from_name("MST_MST_NectarCam"))
, not to get a sub-class... this is a general API conflict with the Component
classes, however, which is a larger issue, but if this is in instrument, it should at least be consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have renamed it for now to avoid confusion. The main purpose of this feature was meant to allow the processing of star images to be more easily configurable. The class that processes strar images (e.g. find the stars) can be configured then with a string giving the name of the PSFModel subclass to use. If there is a better way to configure that tool later i could then remove this.
src/ctapipe/instrument/optics.py
Outdated
return requested_subclass(**kwargs) | ||
|
||
@classmethod | ||
def non_abstract_subclasses(cls): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed I think. This is not a Component class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am removing it
if it is okay then i will open an issue for this |
Here it is: #2647 |
src/ctapipe/instrument/optics.py
Outdated
Parameters | ||
---------- | ||
asymmetry_params: list | ||
Parameters describing the dependency of the asymmetry of the psf on the distance to the center of the camera |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These parameters need better explanations and possibly units.
I suggest to add the relevant formulas to the docstring, see
https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-math
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add units where possible, but for the most part they won't have them. I will add the documentation when I arrive home.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is an ansatz recursive parametrization (parametrization of the parameters) :)
Done empirically by fitting the best-fit values of the PSF parameters for simulations of stars with different offset. The formulas should indeed be provided as a part of the documentation.
src/ctapipe/instrument/optics.py
Outdated
self.radial_pdf_params = (k, self.radial_pdf_params[1], sr) | ||
self.azimuthal_pdf_params = (self.azimuthal_pdf_params[0], sf) | ||
|
||
def update_location(self, r, f): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need an "update" function? Do we have to pre-compute expensive operations?
Why is evaluating the PSF not enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have right now 2 use cases for this PSF model: processing variance images (e.g. finding stars in variance images) and fitting the PSF parameters based on stars in variance images. for the former i need to evaluate it the PSF at different locations for each possible star location, in the latter i also need to vary the psf parameters for fitting. It made sense to me to have these 2 functions available to modify these parameters.
Also just to clarify, update location is where the "point" in point spread function is, as in if you want the PSF for a point source at r = 1m and f = 1.0rad these coordinates will need to be put in this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i will try and make the documentation more clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The question is why this point needs to be state on the class and cannot just be evaluated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is a remnant of the debugging code I used during the development... :)
@ctoennis, we don't need this step to be factored out like this anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, i'll put it all together then. I am having a little issue with the docustrings i'm sorting out now. I'll get to it after that.
az_scale_params=[0.24271557, 7.5511501, 0.02037972], | ||
): | ||
""" | ||
PSF model, describing purely the effect of coma aberration on the PSF |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a citable reference for the math here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it is: https://www.aanda.org/articles/aa/full_html/2023/11/aa47128-23/aa47128-23.html
I will add this to the documentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bib file is here:
https://github.com/cta-observatory/ctapipe/blob/main/docs/references.bib
you can then cite using:
:cite:p:`<bibtex key>`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome. will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which makes me realize we didn't add a reference for the hipparcos catalog
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i can add that.
src/ctapipe/instrument/optics.py
Outdated
|
||
def __init__( | ||
self, | ||
asymmetry_params=[0.49244797, 9.23573115, 0.15216096], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do these magic numbers come from?
How are you expecting we determine these numbers? Is there a tool to compute them from a series of PSF images or pedestal images?
How do you expect them to be fed into ctapipe for the telescopes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got these numbers from https://gitlab.cern.ch/cta-unige/startracker (line [162 https://gitlab.cern.ch/cta-unige/startracker/-/blob/master/startracker/analysis/image.py?ref_type=heads]). @mexanick generated these parameters based on some fits. I can remove these as default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, when i will use this for extracting stars these parameters will not need to be altered. However for fitting the PSF using stars they will need to be altered like a regular variable. My idea would be to have the tool that extracts stars to have these parameters as traitlets, so that when you process data they can just be in a yml file.
For PSF fitting we don't need these exact values. I will remove these values here and have them in the config files later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, when i will use this for extracting stars these parameters will not need to be altered
for LST-1, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, they need to be configured per telescope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, then I think we should discuss a bit about API.
I think, it's natural then to make this a TelescopeComponent
and if there is code that can take a list of images and fit the result, we could have that as a
@classmethod
def fit(self, ...) -> Parameters:
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that makes sense. For the fitting though my idea was to fit the PSF based on images with multiple stars. I have made some draft of a function that would generate a picture based on star locations, magnitudes and psf parameters that would be used as a model for fitting:
def get_star_pdf(r0, f0, geometry, psf, n_pdf_bins, pdf_bin_size, focal_length):
image = np.zeros(len(geometry))
dr = pdf_bin_size * np.rad2deg(np.arctan(1 / focal_length.to_value(u.m))) / 3600.0
r = np.linspace(
r0 - dr * n_pdf_bins / 2.0,
r0 + dr * n_pdf_bins / 2.0,
n_pdf_bins,
)
df = np.deg2rad(pdf_bin_size / 3600.0) * 100
f = np.linspace(
f0 - df * n_pdf_bins / 2.0,
f0 + df * n_pdf_bins / 2.0,
n_pdf_bins,
)
for r_ in r:
for f_ in f:
val = psf.pdf(r_, f_, r0, f0) * dr * df
x, y, _ = r_ * s2c(f_, 0)
pixelN = geometry.position_to_pix_index(x * u.m, y * u.m)
if pixelN > -1 and pixelN < len(image):
image[pixelN] += val
return image
def StarImageGenerator(
radius,
phi,
magnitude,
n_pdf_bins,
pdf_bin_size,
psf_model,
camera_geometry,
focal_length,
):
"""
:param list stars: list of star containers, stars to be placed in image
:param dict psf_model_pars: psf model parameters
"""
image = np.zeros(len(camera_geometry))
for r, p, m in zip(radius, phi, magnitude):
image += np.power(10,-0.4*m) * get_star_pdf(
r, p, camera_geometry, psf_model, n_pdf_bins, pdf_bin_size, focal_length
)
return image
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the PSFModel into a TelescopeComponent
. I will need to see how i will handle the fitting in detail, but i can work that out.
|
||
|
||
class PSFModel(TelescopeComponent): | ||
def __init__(self, subarray, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed, since identical with baseclass.
Docstring should be at the class definition, not __init__
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll fix it.
super().__init__(subarray=subarray, **kwargs) | ||
|
||
@abstractmethod | ||
def pdf(self, *args): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a docstring so that subclass implementers know what is expected.
Also the signature should be the same for all classes, so it should be explicit here in the interface definition.
I am adding PSF models to ctapipe.image.psf_model. I made a parent class called PSFModel and a PSF model based on pure coma abberation called ComaModel.
This will be used in the pointing calculation using star tracking and PSF fitting using stars.