Skip to content
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

Implement HpxNDMap.convolve() #3323

Merged
merged 10 commits into from
May 5, 2021
Merged

Conversation

LauraOlivera
Copy link
Member

This pull requests implements several methods required for the convolution of a HealPix map with a kernel. There are several options to do this: the kernel being a WCS or Hpx map, the convolution happening in a small cutout or in the full sky map.

For now, I've implemented convolve_wcs() which is the "simplest" of these approaches in my opinion. For a "small map" (I set a limit of 10 degree width, but we can also refine that), it uses the HpxToWcsMapping to project the initial HpxNDMap into a WcsNDMap, convolves with a kernel using the existing WcsNDMap.convolve() and makes use of the mapping to go back to the inital HpxGeom. To make this work this I implemented a method HpxToWcsMapping.fill_hpx_map_from_wcs_data() which just does the inverse as the existing HpxToWcsMapping.fill_wcs_map_from_hpx_data(). Testing for this is not implemented yet. (TO DO)

The caveat is that the geometry of the kernel has to be compatible with the intermediate WcsGeom, and so the kernel has to be extracted using hpx_geom.to_wcs_geom() where hpx_geom is the inital map geometry. That means, that if this was used for PSF convolution in a MapDataset with Healpix geometry, the geometry of the PSFKernel (and Map?) would need to be different than the rest of components.

TO DO: The next step would be to, from an inital kernel in an unspecified geometry, extract the radial profile of the kernel in angular space (assumed symmetric), convert to a beam function using healpy.sphtfunc.beam2bl() and then make use of healpy.sphtfunc.smoothing. This happens in a full-sky map, which would require a cutout to be stacked in a full-sky map as an initial step. I suspect this will be much slower than the case above, but I have to check. For a kernel also in an HpxMap, there might be faster options.

@codecov
Copy link

codecov bot commented Apr 27, 2021

Codecov Report

Merging #3323 (ac6cdac) into master (4571366) will decrease coverage by 0.05%.
The diff coverage is 91.76%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3323      +/-   ##
==========================================
- Coverage   93.83%   93.78%   -0.06%     
==========================================
  Files         144      144              
  Lines       18050    18198     +148     
==========================================
+ Hits        16938    17067     +129     
- Misses       1112     1131      +19     
Impacted Files Coverage Δ
gammapy/maps/hpx.py 88.53% <86.66%> (+0.08%) ⬆️
gammapy/maps/hpxnd.py 94.57% <92.85%> (-0.14%) ⬇️
gammapy/modeling/scipy.py 87.32% <0.00%> (-12.68%) ⬇️
gammapy/makers/safe.py 91.02% <0.00%> (-2.22%) ⬇️
gammapy/modeling/fit.py 95.33% <0.00%> (-0.41%) ⬇️
gammapy/modeling/models/spectral.py 96.52% <0.00%> (-0.22%) ⬇️
gammapy/maps/wcsmap.py 96.29% <0.00%> (-0.11%) ⬇️
gammapy/irf/effective_area.py 89.36% <0.00%> (+0.11%) ⬆️
gammapy/estimators/flux_point.py 93.80% <0.00%> (+0.13%) ⬆️
... and 5 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4571366...ac6cdac. Read the comment docs.

@adonath adonath self-assigned this Apr 27, 2021
@adonath adonath added this to the v0.19 milestone Apr 27, 2021
@adonath adonath added this to To do in gammapy.maps via automation Apr 27, 2021
Copy link
Member

@adonath adonath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks lot @LauraOlivera, I have left some first comments, I'll check out the functionality locally now...

map : `HpxNDMap`
Convolved map.
"""
if self.geom.width < 10*u.deg:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the number "10 deg" should be introduce as a class level variable like:

class HpxNDMap:
    convolve_wcs_max_width

This allows users in general to overwrite it, in case they know what they are doing...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe even better: alternatively we could just say all partial maps are supported and we make a statement in the docstring on the accuracy for larger maps >~10 deg

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. My idea was that ideally we would have this method (ok for small maps) and then a full-sky map one that involves stacking a partial map into a full-sky map if needed (which I expect to be slower, but if it isn't then I need to rethink things), and if you try to use convolve_wcs on a partial map too large, you just get a warning suggesting that the other one is more suited or something. But it needs some more thought/work.

"""
if self.geom.width < 10*u.deg:
# Project to WCS and convolve
wcs_map = self.to_wcs(fill_nan = False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think .to_wcs() takes a pre-computed mapping as well. There might be a performance improvement if the HpxToWcsMapping is created outside and re-used for both cases i.e. .to_wcs() as well as the later inverse operation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this even offers the possibly to adjust the WCS geometry used for the conversion and convolution, to the WCS geometry of the kernel, that is passed in. This eliminates the need to compute a matching kernel outside. I haven't thought this through completely, but I think it works...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, you are right! that is a very good point! I'll try that out

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works but I am seeing a lot more issues with artifacts that only go away if the wcs_geom binning is very fine. The nice thing about using .to_wcs() is that it guaranteed the oversampling... I guess it could be fixed by requiring a minimum wcs pixel size ratio to hpx pixel size...

%%time
energy = MapAxis.from_bounds(1,100, unit='TeV', nbin=2, name='energy')

nside = 1024
hpx_geom = HpxGeom.create(nside=nside,
                         axes=[energy],
                         region='DISK(0,0,2.5)',
                         )

hpx_map = Map.from_geom(hpx_geom)
hpx_map.set_by_coord((0,0, [2,90]), 1)

wcs_geom = WcsGeom.create(width=5,
                          binsz = 0.05,
                         axes = [energy])
kernel = PSFKernel.from_gauss(wcs_geom, 0.5*u.deg)
convolved_map = hpx_map.convolve_wcs(kernel)
convolved_map.plot_interactive(add_cbar=True)

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for check! Indeed we should require the oversampling to avoid the artefacts. As an educated guess, I'd say an oversampling of >2 should be sufficient. So +1 to just include the check and document it.

gammapy.maps automation moved this from To do to In progress Apr 29, 2021
@LauraOlivera
Copy link
Member Author

I added another method convolve_full (not convinced about the name because it also works for not full-sky maps so suggestions welcome). What this does is extract the radial profile from the input kernel (assuming radial symmetry), convert it into a window beam (like in PR #3314) and convolve using healpy.sphtfunc.smoothing. If the input map is not a full-sky map, it first inserts the smaller map into a full sky map, and then extracts the same region once the convolution is over.

Because the convolution happens in a full sky map, the performance is worse if the pixelization is fine. That is why, even if the input map is "small", for a usual analysis pixel size (nside = 1024 is the standard for HAWC, which corresponds to a 0.025deg pixel size), this method is much slower than convolve_wcs even if the intermediate WCS geometry has smaller pixels by a factor >2:

energy = MapAxis.from_bounds(1,100, unit='TeV', nbin=2, name='energy_true')
nside = 1024
hpx_geom = HpxGeom.create(nside=nside,
                         axes=[energy],
                         region='DISK(0,0,3.5)',
                         )
hpx_map = Map.from_geom(hpx_geom)
hpx_map.set_by_coord((0,0, [2,90]), 1)
wcs_geom = WcsGeom.create(width=5,
                          binsz = 0.01, 
                         axes = [energy])
psf = PSFMap.from_gauss(
    energy_axis_true=energy, sigma=[0.3, 0.2] * u.deg
)
kernel = psf.get_psf_kernel(geom=wcs_geom, max_radius=1*u.deg)

#### Using the WCS projection
%%time 
convolved_map_wcs = hpx_map.convolve_wcs(kernel)
CPU times: user 36.7 ms, sys: 30 µs, total: 36.8 ms
Wall time: 35.8 ms

#### Using the full-sky convolution
%%time 
convolved_map = hpx_map.convolve_full(kernel)
CPU times: user 1min 56s, sys: 3.22 s, total: 2min
Wall time: 10.9 s

So my suggestion is to implement now the method HpxNDMap.convolve() which essentially decides which of the two are called based on the size of the input map.

I also compared the maps produced above with one another and this is the result of doing convolved_map - convolved_map_wcs
image

@adonath
Copy link
Member

adonath commented May 4, 2021

Thanks a lot for investigating @LauraOlivera! From your results it seems, there is hardly any situation where .convolve_all() would be useful, except if it is really an all sky map to be convolved. For the npred evaluation we basically only need the local convolution, because source models are evaluated on small cutouts only. I'll proceed with a short code review and then we discuss in there afternoon call, how to support the different convolve methods (i.e. whether to use a single .convolve() method etc.)

Copy link
Member

@adonath adonath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @LauraOlivera, for now I just left two minor comments. Let's discuss in the next co-working meeting on the details...

gammapy/maps/hpxnd.py Outdated Show resolved Hide resolved
gammapy/maps/hpxnd.py Show resolved Hide resolved
@adonath
Copy link
Member

adonath commented May 4, 2021

@LauraOlivera To keep the scope of this PR limited maybe just implement .convolve() with the WCS method here? Than we could go ahead already and make MapDataset.npred() work. The .convolve_all() could be added in a follow up PR, that can be used to evaluate the other method and provide better information on the validity of the WCS approximation...

@LauraOlivera LauraOlivera marked this pull request as ready for review May 5, 2021 09:39
Copy link
Member

@adonath adonath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @LauraOlivera! I think the code could need some polishing, however it's not a priority right now. Let's merge the PR and I'll prepare a follow up PR to do the clean up, while you and @vikasj78 can continue to work on the npred computation for HEALPix...

@adonath adonath merged commit 9c53c3f into gammapy:master May 5, 2021
gammapy.maps automation moved this from In progress to Done May 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
gammapy.maps
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

2 participants