-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add optional dynamic pixel range operations to tile factory (#9)
The GDAL tile factory can now automatically rescale pixel values to match a desired output type. This is commonly used when converting panchromatic imagery which often has 11-16 bits per pixel into an 8-bits per pixel representation for visualization. These operations look at the histogram of the input image pixel values and then map them to the output range.
- Loading branch information
Showing
7 changed files
with
248 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
from typing import List | ||
|
||
|
||
class DRAParameters: | ||
""" | ||
This class manages a set of parameters used to perform a Dynamic Range Adjustment that is applied when | ||
converting imagery pixel values (e.g. 11-bit per pixel panchromatic imagery to an 8-bit per pixel grayscale). | ||
""" | ||
|
||
def __init__( | ||
self, suggested_min_value: float, suggested_max_value: float, actual_min_value: float, actual_max_value: float | ||
): | ||
""" | ||
Constructor for this class. | ||
:param suggested_min_value: suggested minimum value of the relevant pixel range | ||
:param suggested_max_value: suggested maximum value of the relevant pixel range | ||
:param actual_min_value: actual minimum value of pixels in the image | ||
:param actual_max_value: actual maximum value of pixels in the image | ||
""" | ||
self.suggested_min_value = suggested_min_value | ||
self.suggested_max_value = suggested_max_value | ||
self.actual_min_value = actual_min_value | ||
self.actual_max_value = actual_max_value | ||
|
||
@staticmethod | ||
def from_counts( | ||
counts: List[float], min_percentage: float = 0.02, max_percentage: float = 0.98, a: float = 0.2, b: float = 0.4 | ||
) -> "DRAParameters": | ||
""" | ||
This static factory method computes a new set of DRA parameters given a histogram of pixel values. | ||
:param counts: histogram of the pixel values | ||
:param min_percentage: set point for low intensity pixels that may be outliers | ||
:param max_percentage: set point for high intensity pixels that may be outliers | ||
:param a: weighting factor for the low intensity range | ||
:param b: weighting factor for the high intensity range | ||
:return: a set of DRA parameters containing recommended and actual ranges of values | ||
""" | ||
num_histogram_bins = len(counts) | ||
|
||
# Find the first and last non-zero counts | ||
actual_min_value = 0 | ||
while actual_min_value < num_histogram_bins and counts[actual_min_value] == 0: | ||
actual_min_value += 1 | ||
|
||
actual_max_value = num_histogram_bins - 1 | ||
while actual_max_value > 0 and counts[actual_max_value] == 0: | ||
actual_max_value -= 1 | ||
|
||
# Compute the cumulative distribution | ||
cumulative_counts = counts.copy() | ||
for i in range(1, len(cumulative_counts)): | ||
cumulative_counts[i] = cumulative_counts[i] + cumulative_counts[i - 1] | ||
|
||
# Find the values that exclude the lowest and highest percentages of the counts. | ||
# This identifies the range that contains most of the pixels while excluding outliers. | ||
max_counts = cumulative_counts[-1] | ||
low_threshold = min_percentage * max_counts | ||
e_min = 0 | ||
while cumulative_counts[e_min] < low_threshold: | ||
e_min += 1 | ||
|
||
high_threshold = max_percentage * max_counts | ||
e_max = num_histogram_bins - 1 | ||
while cumulative_counts[e_max] > high_threshold: | ||
e_max -= 1 | ||
|
||
min_value = max([actual_min_value, e_min - a * (e_max - e_min)]) | ||
max_value = min([actual_max_value, e_max + b * (e_max - e_min)]) | ||
|
||
return DRAParameters( | ||
suggested_min_value=min_value, | ||
suggested_max_value=max_value, | ||
actual_min_value=actual_min_value, | ||
actual_max_value=actual_max_value, | ||
) | ||
|
||
def __repr__(self): | ||
return ( | ||
f"DRAParameters(min_value={self.suggested_min_value}, " | ||
f"max_value={self.suggested_max_value}, " | ||
f"e_first={self.actual_min_value}, " | ||
f"e_last={self.actual_max_value}, " | ||
f")" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import unittest | ||
|
||
|
||
class TestDRAParameters(unittest.TestCase): | ||
def test_from_counts(self): | ||
from aws.osml.gdal.dynamic_range_adjustment import DRAParameters | ||
|
||
counts = [0] * 1024 | ||
counts[1:99] = [1] * (99 - 1) | ||
counts[100:400] = [200] * (400 - 100) | ||
counts[1022] = 1 | ||
|
||
dra_parameters = DRAParameters.from_counts(counts=counts) | ||
|
||
self.assertEquals(dra_parameters.actual_min_value, 1) | ||
self.assertEquals(dra_parameters.actual_max_value, 1022) | ||
self.assertAlmostEqual(dra_parameters.suggested_min_value, 47, delta=1) | ||
self.assertAlmostEquals(dra_parameters.suggested_max_value, 506, delta=1) |
Oops, something went wrong.