Skip to content

Commit b2ffa12

Browse files
added code for finding peaks on healpix maps and getting starlet peak counts on the sphere
1 parent fbd50aa commit b2ffa12

1 file changed

Lines changed: 157 additions & 0 deletions

File tree

pycs/astro/wl/hos_peaks_l1.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,83 @@ def get_peaks(image, threshold=None, ordered=True, mask=None, include_border=Fal
225225
return X, Y, heights
226226

227227

228+
def get_peaks_sphere(image, nside, threshold=None, ordered=True, mask=None):
229+
"""
230+
Identify peaks in a spherical HEALPix map using a vectorized approach.
231+
232+
A peak is defined as a pixel with a value greater than all of its neighbors.
233+
The neighborhood is determined by the healpy's `get_all_neighbors` function.
234+
235+
Parameters
236+
----------
237+
image : array_like
238+
HEALPix map (1D array).
239+
nside : int
240+
Nside parameter of the HEALPix map.
241+
threshold : float, optional
242+
Minimum pixel amplitude to be considered as a peak. If not provided,
243+
the default value is set to the minimum of `image`.
244+
ordered : bool, optional
245+
If True, return peaks in decreasing order according to height.
246+
mask : array_like (same shape as `image`), optional
247+
Boolean array identifying which pixels of `image` to consider/exclude
248+
in finding peaks. A numerical array will be converted to binary, where
249+
only zero values are considered masked.
250+
251+
Returns
252+
-------
253+
X, heights : tuple of 1D numpy arrays
254+
Pixel indices of peak positions and their associated heights.
255+
"""
256+
image = np.atleast_1d(image)
257+
npix = hp.nside2npix(nside)
258+
259+
if mask is None:
260+
mask = np.ones(npix, dtype=bool)
261+
else:
262+
mask = mask.astype(bool)
263+
264+
# Handle threshold like the original implementation
265+
if threshold is None:
266+
threshold = image[mask].min()
267+
268+
# Get the indices of all neighbors for every pixel
269+
neighbors_indices = hp.get_all_neighbours(nside, np.arange(npix))
270+
271+
# neighbors_indices has shape (8, npix) - 8 neighbors for each pixel
272+
# We need to transpose it to get (npix, 8) for easier processing
273+
neighbors_indices = neighbors_indices.T # Now shape is (npix, 8)
274+
275+
# Pad the image to handle -1 indices
276+
image_padded = np.zeros(npix + 1)
277+
image_padded[1:] = image[:]
278+
# Set the value for index -1 to a very low number to ensure it is never
279+
# considered the maximum. This corresponds to a padding value for invalid neighbors.
280+
image_padded[0] = -np.inf
281+
282+
# Shift neighbor indices by +1 so -1 becomes 0
283+
neighbors_indices_padded = neighbors_indices + 1
284+
285+
# Get the values of all neighbors from the padded image.
286+
neighbor_values = image_padded[neighbors_indices_padded]
287+
288+
# Find the maximum value among all neighbors for each pixel
289+
max_neighbor_values = np.max(neighbor_values, axis=1)
290+
291+
# A pixel is a peak if its value is strictly greater than the maximum of its neighbors
292+
# AND it meets the threshold AND it's in the mask
293+
peaks_mask = (image > max_neighbor_values) & (image >= threshold) & mask
294+
295+
peak_indices = np.where(peaks_mask)[0]
296+
peak_heights = image[peak_indices]
297+
298+
if ordered:
299+
inds = np.argsort(peak_heights)[::-1]
300+
return peak_indices[inds], peak_heights[inds]
301+
302+
return peak_indices, peak_heights
303+
304+
228305
# Class to compute multiscale peaks and wavelet l1 norm for an image
229306
class HOS_starlet_l1norm_peaks:
230307
NBins = 0 # int: Number of bins to use
@@ -683,6 +760,86 @@ def get_wtl1_sphere(
683760
return np.array(bins_coll), np.array(l1norm_coll)
684761

685762

763+
def get_wtpeaks_sphere(
764+
Map, nscales, noise_std=None, Mask=None, nbins=31, Min=-2, Max=6, verbose=False
765+
):
766+
"""
767+
Calculate the histogram of peak counts at all scales for a spherical HEALPix map.
768+
The overall logic and functionality is similar to the get_wtpeaks function.
769+
The wavelet transform is performed using CMRStarlet().
770+
771+
Parameters
772+
----------
773+
Map : array_like
774+
HEALPix map to analyze.
775+
nscales : int
776+
Number of wavelet scales to use.
777+
noise_std : float, optional
778+
Standard deviation of the noise. If provided, the wavelet coefficients
779+
are divided by this value to obtain an SNR map.
780+
Mask : array_like, optional
781+
Caculate the histogram of peak counts only in the mask area.
782+
The default is None, the whole image is used at every scale.
783+
nbins : int, optional
784+
Number of bins for the histogram. The default is 31.
785+
Min : int, optional
786+
Minimum value for the histogram bins. The default is -2.
787+
Max : int, optional
788+
Maximum value for the histogram bins. The default is 6.
789+
verbose : bool, optional
790+
If True, print the minimum and maximum values at each scale. The default is False.
791+
792+
Returns
793+
-------
794+
tuple of lists
795+
(Peaks_Count, TabBinsCenter) where:
796+
- Peaks_Count[i] is a numpy array containing the histogram of peak counts for scale i.
797+
- TabBinsCenter is a numpy array with the center of each bin.
798+
"""
799+
800+
# Determine Nside from the input map
801+
Nside = hp.npix2nside(Map.shape[0])
802+
803+
# Initialize and perform CMRStarlet transform
804+
C = CMRStarlet()
805+
C.init_starlet(Nside, nscale=nscales)
806+
C.transform(Map)
807+
808+
# Set up histogram bins
809+
TabBins = np.linspace(Min, Max, nbins)
810+
TabBinsCenter = 0.5 * (TabBins[:-1] + TabBins[1:])
811+
812+
Peaks_Count = []
813+
814+
# Loop through each scale
815+
for i in range(nscales):
816+
# Get normalized wavelet coefficients for the i-th scale
817+
if C.TabNorm[i] == 0:
818+
ScaleCoeffs = C.coef[i].copy()
819+
else:
820+
ScaleCoeffs = C.coef[i] / C.TabNorm[i]
821+
822+
# If noise_std is provided, convert to SNR
823+
if noise_std is not None:
824+
ScaleCoeffs = ScaleCoeffs / noise_std
825+
826+
if verbose:
827+
print(
828+
f"Scale {i + 1}: Min = {ScaleCoeffs.min():.4f}, Max = {ScaleCoeffs.max():.4f}"
829+
)
830+
831+
# Get peaks for the current scale
832+
peak_indices, peak_heights = get_peaks_sphere(
833+
ScaleCoeffs, Nside, threshold=None, ordered=True, mask=Mask
834+
)
835+
836+
# Calculate histogram of peak heights
837+
counts, bin_edges = np.histogram(peak_heights, bins=TabBins)
838+
Peaks_Count.append(counts)
839+
840+
return Peaks_Count, TabBinsCenter
841+
842+
686843
############# TESTS routine ############
687844

688845

0 commit comments

Comments
 (0)