Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
0755f43
Implemented the Fourier Normailizing Radial Gradient Filter
vatch123 Mar 3, 2019
9268712
Updated the variable name
vatch123 Mar 3, 2019
fd5eb43
Added the attenuation coefficients
vatch123 Mar 3, 2019
f4da119
Merge pull request #1 from sunpy/master
vatch123 Mar 3, 2019
51db695
Updated the docstrings
vatch123 Mar 3, 2019
b7d7903
Updated docs, Added tests
vatch123 Mar 7, 2019
13775b5
added the changelog file
vatch123 Mar 7, 2019
a9e7d11
Fixed PEP8 issues
vatch123 Mar 7, 2019
eddd3bd
Update sunkit_image/offlimb_enhance.py
nabobalis Mar 7, 2019
19eaca6
Update sunkit_image/offlimb_enhance.py
nabobalis Mar 7, 2019
73075e0
Update sunkit_image/offlimb_enhance.py
nabobalis Mar 7, 2019
aa87701
Update sunkit_image/offlimb_enhance.py
nabobalis Mar 7, 2019
c75dde9
Apply suggestions from code review
nabobalis Mar 7, 2019
616e4ab
Updated the tests
vatch123 Mar 8, 2019
8c16423
Merge pull request #2 from sunpy/master
vatch123 Mar 12, 2019
1810bfd
Changed the test images to ones with radial profiles
vatch123 May 18, 2019
f97340f
Added one more test to NRGF
vatch123 May 18, 2019
23dbeb3
Spelling Corrected
vatch123 May 19, 2019
abff13b
Minor fixes
vatch123 May 20, 2019
80d6737
Tests added to FNRGF
vatch123 May 20, 2019
2f11ddd
Apply suggestions from code review
vatch123 May 20, 2019
f63d7f0
Resolved merge conflicts
vatch123 May 21, 2019
adbd9f8
Merge pull request #3 from sunpy/master
vatch123 May 24, 2019
5ea0601
Changed the way test maps are created
vatch123 May 25, 2019
32e0fae
Fixed the confest.py
vatch123 May 26, 2019
fd99563
Modules and functions renamed
vatch123 May 27, 2019
319967e
FIxed a corner case
vatch123 May 27, 2019
f5e4e0f
Added examples
vatch123 May 27, 2019
e2680ff
FIxed the docs file of offlimb
vatch123 May 27, 2019
e09fd95
Fixed the doc file again
vatch123 May 27, 2019
cb40301
Minor fixes
vatch123 May 27, 2019
bc7a7e4
Applied the suggestion
vatch123 May 27, 2019
0acfc51
Fixed the examples
vatch123 May 28, 2019
9817ba5
Merge conflicts resolution
vatch123 May 28, 2019
9555beb
Removed offlimb_enhance
vatch123 May 28, 2019
2b1b82d
fixed indentation and tests
vatch123 May 29, 2019
eb2c77c
Added figure tests
vatch123 May 30, 2019
3c41f99
marker for future tests
vatch123 May 30, 2019
bb6d2ab
Minor changes
vatch123 Jun 3, 2019
1e3c22f
Capitalized "Fourier"
vatch123 Jun 6, 2019
c2dc74a
Removed merge conflicts
vatch123 Jun 8, 2019
40af095
Apply suggestions from code review
vatch123 Jun 8, 2019
ec93937
Made changes to the offlimb file
vatch123 Jun 8, 2019
7e095c4
CHanged to radial
vatch123 Jun 8, 2019
a2308d1
Renamed test file
vatch123 Jun 8, 2019
1fcabf8
Added test for helpers
vatch123 Jun 8, 2019
236786b
Fixed the examples and figure hashes
vatch123 Jun 8, 2019
a7e7b22
fixed hash value
vatch123 Jun 8, 2019
71426aa
Docs fixed
vatch123 Jun 9, 2019
e67eb0f
Apply suggestions from code review
vatch123 Jun 9, 2019
c8fed34
added tests
vatch123 Jun 9, 2019
0adcdc1
Apply suggestions from code review
vatch123 Jun 10, 2019
d82fc41
Added the coefficient function and doc changes
vatch123 Jun 10, 2019
72b9566
Docs formatting done
vatch123 Jun 10, 2019
897a9f0
Added tests for warning
vatch123 Jun 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/17.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a new feature ```fourier_normalizing_radial_gradient_filter``` to normalize the radial brightness gradient using a fourier approximation.
194 changes: 186 additions & 8 deletions sunkit_image/offlimb_enhance.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import sunpy.map

from sunpy.coordinates import frames

from sunkit_image.utils.utils import (
bin_edge_summary,
find_pixel_radii,
Expand Down Expand Up @@ -206,10 +208,10 @@ def normalizing_radial_gradient_filter(
smap,
radial_bin_edges,
scale=None,
intensity_summary=np.mean,
intensity_summary_kwargs=None,
intensity_summary=np.nanmean,
intensity_summary_kwargs={},
width_function=np.std,
width_function_kwargs=None,
width_function_kwargs={},
application_radius=1 * u.R_sun,
):
"""
Expand All @@ -232,12 +234,9 @@ def normalizing_radial_gradient_filter(
For example, in typical Helioprojective Cartesian maps the solar radius is expressed in
units of arcseconds.
Defaults to None, which means that the map scale is used.
summarize_bin_edges : `str`, optional
How to summarize the bin edges.s
Defaults to "center".
intensity_summary : `function`, optional
A function that returns a summary statistic of the radial intensity.
Defaults to `numpy.mean`.
Defaults to `numpy.nanmean`.
intensity_summary_kwargs : None, `dict`
Keywords applicable to the summary function.
width_function : `function`
Expand All @@ -257,7 +256,8 @@ def normalizing_radial_gradient_filter(

References
----------
* Morgan, Habbal & Woo, 2006, Sol. Phys., 236, 263. https://link.springer.com/article/10.1007%2Fs11207-006-0113-6
* Morgan, Habbal & Woo, 2006, Sol. Phys., 236, 263.
https://link.springer.com/article/10.1007%2Fs11207-006-0113-6
"""

# Get the radii for every pixel
Expand Down Expand Up @@ -295,3 +295,181 @@ def normalizing_radial_gradient_filter(
) / radial_intensity_distribution_summary[i]

return sunpy.map.Map(data, smap.meta)


def fourier_normalizing_radial_gradient_filter(
smap,
radial_bin_edges,
order,
attenuation_coefficients,
ratio_mix=[15, 1],
scale=None,
intensity_summary=np.nanmean,
intensity_summary_kwargs={},
width_function=np.std,
width_function_kwargs={},
application_radius=1 * u.R_sun,
number_angular_segments=130,
):
"""
Implementation of the fourier normalizing radial gradient filter (FNRGF).

..note ::

After applying the filter, current plot settings such as the image normalization
may have to be changed in order to obtain a good-looking plot

Parameters
----------
smap : `sunpy.map.Map`
A SunPy map
radial_bin_edges : `astropy.units.Quantity`
A two-dimensional array of bin edges of size [2, nbins] where nbins is
the number of bins.
order : `int`
Order (Number) of fourier coefficients.
attenuation_coefficients : `float`
A two dimensional array of shape [2, order + 1]. The first row contain attenuation
coefficients for mean calculations. The second row contains attenuation coefficients
for standard deviation calculation.
ratio_mix : `float`
A one dimensional array of shape [2, 1] with values equal to [K1, K2].
The ratio in which the original image and filtered image are mixed.
Defaults to [15,1].
scale : None or `astropy.units.Quantity`, optional
The radius of the Sun expressed in map units. For example, in typical
helioprojective Cartesian maps the solar radius is expressed in units
of arcseconds. If None, then the map scale is used.
intensity_summary :`function`, optional
A function that returns a summary statistic of the radial intensity,
Default is numpy.nanmean
intensity_summary_kwargs : None, `~dict`
Keywords applicable to the summary function.
width_function : `function`
A function that returns a summary statistic of the distribution of intensity,
at a given radius.
Defaults to `numpy.std`.
width_function_kwargs : `function`
Keywords applicable to the width function.
application_radius : `astropy.units.Quantity`
The FNRGF is applied to emission at radii above the application_radius.
Defaults to 1 solar radii.
number_angular_segments : `int`
Number of angular segments in a circular annulus.
Defaults to 130.

Returns
-------
new_map : `sunpy.map.Map`
A SunPy map that has had the FNRGF applied to it.

References
----------
* Morgan, Habbal & Druckmüllerová, 2011, Astrophysical Journal 737, 88.
https://iopscience.iop.org/article/10.1088/0004-637X/737/2/88/pdf.
* The implementation is highly inspired by this doctoral thesis.
DRUCKMÜLLEROVÁ, H. Application of adaptive filters in processing of solar corona images.
https://dspace.vutbr.cz/bitstream/handle/11012/34520/DoctoralThesis.pdf.
"""

# Get the radii for every pixel
map_r = find_pixel_radii(smap).to(u.R_sun)

# Get the Helioprojective coordinates of each pixel
x, y = np.meshgrid(*[np.arange(v.value) for v in smap.dimensions]) * u.pix
coords = smap.pixel_to_world(x, y).transform_to(frames.Helioprojective)

# Get angles associated with every pixel
angles = np.arctan2(coords.Ty.value, coords.Tx.value)

# Making sure all angles are between (0, 2 * pi)
angles = np.where(angles < 0, angles + (2*np.pi), angles)

# Number of radial bins
nbins = radial_bin_edges.shape[1]

# Storage for the filtered data
data = np.zeros_like(smap.data)

# Iterate over each circular ring
for i in range(0, nbins):

# Finding the pixels which belong to a certain circular ring
annulus = np.logical_and(map_r > radial_bin_edges[0, i], map_r < radial_bin_edges[1, i])
annulus = np.logical_and(annulus, map_r > application_radius)

# The angle subtended by each segment
segment_angle = 2*np.pi / number_angular_segments

# Storage of mean and standard deviation of each segment in a circular ring
average_segments = np.zeros((1, number_angular_segments))
std_dev = np.zeros((1, number_angular_segments))

# Calculating sin and cos of the angles to be multiplied with the means and standard
# deviations to give the fourier approximation
cos_matrix = np.cos(np.array([[(2 * np.pi * (j + 1) * (i + 0.5)) / number_angular_segments
for j in range(order)] for i in range(number_angular_segments)]))
sin_matrix = np.sin(np.array([[(2 * np.pi * (j + 1) * (i + 0.5)) / number_angular_segments
for j in range(order)] for i in range(number_angular_segments)]))

# Iterate over each segment in a circular ring
for j in range(0, number_angular_segments, 1):

# Finding all the pixels whose angle values lie in the segment
angular_segment = np.logical_and(angles > segment_angle * j,
angles < segment_angle * (j + 1))

# Finding the particular segment in the circular ring
annulus_segment = np.logical_and(annulus, angular_segment)

# Finding mean and standard deviation in each segnment. If takes care of the empty
# slices.
if np.sum([annulus_segment > 0]) == 0:
average_segments[0, j] = 0
std_dev[0, j] = 0
else:
average_segments[0, j] = intensity_summary(smap.data[annulus_segment])
std_dev[0, j] = width_function(smap.data[annulus_segment])

# Calculating the fourier coefficients multiplied with the attenuation coefficients
fourier_coefficient_a_0 = np.sum(average_segments) * (2 / number_angular_segments)
fourier_coefficient_a_0 = fourier_coefficient_a_0 * (attenuation_coefficients[0, 1])

fourier_coefficients_a_k = np.matmul(average_segments, cos_matrix) * (2 / number_angular_segments)
fourier_coefficients_a_k = fourier_coefficients_a_k * attenuation_coefficients[0][1:]

fourier_coefficients_b_k = np.matmul(average_segments, sin_matrix) * (2 / number_angular_segments)
fourier_coefficients_b_k = fourier_coefficients_b_k * attenuation_coefficients[0][1:]

fourier_coefficient_c_0 = np.sum(std_dev) * (2 / number_angular_segments)
fourier_coefficient_c_0 = fourier_coefficient_c_0 * attenuation_coefficients[1, 1]

fourier_coefficients_c_k = np.matmul(std_dev, cos_matrix) * (2 / number_angular_segments)
fourier_coefficients_c_k = fourier_coefficients_c_k * attenuation_coefficients[1][1:]

fourier_coefficients_d_k = np.matmul(std_dev, sin_matrix) * (2 / number_angular_segments)
fourier_coefficients_d_k = fourier_coefficients_d_k * attenuation_coefficients[1][1:]

# To calculate the multiples of angles of each pixel for finding the fourier approximation
# at that point. See equations 6.8 and 6.9 of the doctoral thesis.
K_matrix = np.ones((order, np.sum(annulus > 0))) * np.array(range(1, order+1)).T.reshape(order, 1)
phi_matrix = angles[annulus].reshape((1, angles[annulus].shape[0]))
angles_of_pixel = K_matrix * phi_matrix

# Get the approxiamted value of mean
mean_approximated = np.matmul(fourier_coefficients_a_k, np.cos(angles_of_pixel))
mean_approximated = mean_approximated + np.matmul(fourier_coefficients_b_k, np.sin(angles_of_pixel))
mean_approximated = mean_approximated + fourier_coefficient_a_0 / 2

# Get the approxiamted value of standard deviation
std_approximated = np.matmul(fourier_coefficients_c_k, np.cos(angles_of_pixel))
std_approximated = std_approximated + np.matmul(fourier_coefficients_d_k, np.sin(angles_of_pixel))
std_approximated = std_approximated + fourier_coefficient_c_0 / 2

# Normailize the data
data[annulus] = np.ravel((smap.data[annulus] - mean_approximated) / std_approximated)

# Linear combination of original image and the filtered data.
data[annulus] = ratio_mix[0] * smap.data[annulus] + ratio_mix[1] * data[annulus]

return sunpy.map.Map(data, smap.meta)
51 changes: 47 additions & 4 deletions sunkit_image/tests/test_offlimb_enhance.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,55 @@
import os

import numpy as np
import pytest
import astropy.units as u
from numpy.testing import assert_allclose

import sunpy
import sunpy.map
import sunpy.data.test
import sunkit_image.utils.utils as utils
import sunkit_image.offlimb_enhance as off


test_map_data = np.ones((5, 5))
header = {}
test_map = sunpy.map.Map((test_map_data, header))
radial_bin_edges = utils.equally_spaced_bins()
radial_bin_edges = radial_bin_edges*u.R_sun


def set_attenuation_coefficients(order):
attenuation_coefficients = np.zeros((2, order + 1))
attenuation_coefficients[0][:] = np.linspace(1, 0, order + 1)
attenuation_coefficients[1][:] = np.linspace(1, 0, order + 1)
return attenuation_coefficients


def test_normalizing_radial_gradient_filter():

with pytest.warns(Warning, match="Missing metadata for solar radius: assuming photospheric limb as seen from Earth"):

result = np.zeros_like(test_map_data)
expect = off.normalizing_radial_gradient_filter(test_map, radial_bin_edges)

assert np.allclose(expect.data.shape, test_map.data.shape)
assert np.allclose(expect.data, result)


def test_fourier_normalizing_radial_gradient_filter():

with pytest.warns(Warning, match="Missing metadata for solar radius: assuming photospheric limb as seen from Earth"):

result = np.zeros_like(test_map_data)

order = 20
attenuation_coefficients = set_attenuation_coefficients(order)
expect = off.fourier_normalizing_radial_gradient_filter(test_map, radial_bin_edges, order,
attenuation_coefficients)
assert np.allclose(expect.data.shape, test_map.data.shape)
assert np.allclose(expect.data, result)

# TODO: Write this
order = 33
attenuation_coefficients = set_attenuation_coefficients(order)
expect = off.fourier_normalizing_radial_gradient_filter(test_map, radial_bin_edges, order,
attenuation_coefficients)
assert np.allclose(expect.data.shape, test_map.data.shape)
assert np.allclose(expect.data, result)