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

Introduced area-based contours #7444

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 46 additions & 1 deletion sunpy/map/mapbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -2290,6 +2290,49 @@ def draw_quadrangle(self, bottom_left, *, width: (u.deg, u.pix) = None, height:
quad = Quadrangle(anchor, width, height, **kwergs)
axes.add_patch(quad)
return quad

def _calculate_contour_levels_by_area(self, levels):
Copy link
Contributor

Choose a reason for hiding this comment

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

This will also need to be tested both as a normal unit test but also as a figure test. See https://docs.sunpy.org/en/latest/dev_guide/contents/tests.html for more details.

"""
Calculate contour levels based on area containment.
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure the word containment is correct here.


Parameters
----------
levels : array_like
An array-like object specifying the percentages of the total area to be contained within the generated contour levels.
This can be a single value or an array of values representing the desired percentages, where each value should be in the range [0, 1].

Returns
-------
thresholds : ndarray
An array of threshold values representing the data values below which the specified percentages of the total area are contained.
The length of this array is equal to the length of the input `levels`.

Notes
-----
This function calculates contour levels based on the area contained within them relative to the maximum value in the map data.
Paras20222 marked this conversation as resolved.
Show resolved Hide resolved
It normalizes the map data, sorts the normalized values, calculates the cumulative sum, and finds thresholds corresponding to the specified percentages of the total area.
The thresholds are then returned as an array.
Paras20222 marked this conversation as resolved.
Show resolved Hide resolved

Examples
--------
>>> map_data = np.random.rand(100, 100)
>>> quantiles = [0.1, 0.3, 0.5, 0.7, 0.9]
>>> contour_levels = self._calculate_contour_levels_by_area(quantiles)
>>> print(contour_levels)
[0.3160296 0.54729944 0.70897832 0.83778233 0.95295992]
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the imports are missing?

Also the print is not needed here. The doctest will print out the return if you do not assign it to a variable.

"""

normalized_data = (self.data - np.nanmin(self.data)) / (np.nanmax(self.data) - np.nanmin(self.data))
sorted_data = np.sort(normalized_data.flatten())[::-1]

cumulative_sum = np.cumsum(sorted_data)
cumulative_sum /= cumulative_sum.max()

indices = np.searchsorted(cumulative_sum, levels)
thresholds = np.sort(sorted_data[indices])

return thresholds
Paras20222 marked this conversation as resolved.
Show resolved Hide resolved


def _process_levels_arg(self, levels):
"""
Expand All @@ -2305,7 +2348,9 @@ def _process_levels_arg(self, levels):
"it should be an Astropy Quantity object.")

if levels.unit == u.percent:
return 0.01 * levels.to_value('percent') * np.nanmax(self.data)
# Calculate contour levels based on area containment
contour_levels = self._calculate_contour_levels_by_area(levels)
Copy link
Contributor

Choose a reason for hiding this comment

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

We can't change the default behavior of this method.

I think for now, adding a "method" keyword (to the draw contours method) that allows you to select this new method would be better.

return contour_levels
elif self.unit is not None:
return levels.to_value(self.unit)
elif levels.unit.is_equivalent(u.dimensionless_unscaled):
Expand Down