Skip to content

Commit

Permalink
[SEDONA-493] Update default behavior of RS_NormalizeAll (#1234)
Browse files Browse the repository at this point in the history
* Update default behavior of RS_NormalizeAll

* Update Scala test

* Fix typo in test

* Add tests for noDataValue set to null; Update docs

* Move RS_NormalizeAll to Raster Editors; Update docs

* Fix indentation
  • Loading branch information
prantogg committed Feb 9, 2024
1 parent 712a284 commit 89e17c8
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 291 deletions.
122 changes: 0 additions & 122 deletions common/src/main/java/org/apache/sedona/common/raster/MapAlgebra.java
Original file line number Diff line number Diff line change
Expand Up @@ -442,128 +442,6 @@ public static double[] normalize(double[] bandValues) {
return bandValues;
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom) {
return normalizeAll(rasterGeom, 0d, 255d, null, null, null, true);
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim) {
return normalizeAll(rasterGeom, minLim, maxLim, null, null, null, true);
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim, double noDataValue) {
return normalizeAll(rasterGeom, minLim, maxLim, noDataValue, null, null, true);
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim, Double noDataValue, boolean normalizeAcrossBands) {
return normalizeAll(rasterGeom, minLim, maxLim, noDataValue, null, null, normalizeAcrossBands);
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim, Double noDataValue, Double minValue, Double maxValue) {
return normalizeAll(rasterGeom, minLim, maxLim, noDataValue, minValue, maxValue, true);
}

/**
*
* @param rasterGeom Raster to be normalized
* @param minLim Lower limit of normalization range
* @param maxLim Upper limit of normalization range
* @param noDataValue NoDataValue used in raster
* @param minValue Minimum value in raster
* @param maxValue Maximum value in raster
* @param normalizeAcrossBands flag to determine the normalization method
* @return a raster with all values in all bands normalized between minLim and maxLim
*/
public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim, Double noDataValue, Double minValue, Double maxValue, boolean normalizeAcrossBands) {
if (minLim > maxLim) {
throw new IllegalArgumentException("minLim cannot be greater than maxLim");
}

int numBands = rasterGeom.getNumSampleDimensions();
RenderedImage renderedImage = rasterGeom.getRenderedImage();
int rasterDataType = renderedImage.getSampleModel().getDataType();

double globalMin = minValue != null ? minValue : Double.MAX_VALUE;
double globalMax = maxValue != null ? maxValue : -Double.MAX_VALUE;

// Initialize arrays to store band-wise min and max values
double[] minValues = new double[numBands];
double[] maxValues = new double[numBands];
Arrays.fill(minValues, Double.MAX_VALUE);
Arrays.fill(maxValues, -Double.MAX_VALUE);

// Compute global min and max values across all bands if necessary and not provided
if (minValue == null || maxValue == null) {
for (int bandIndex = 0; bandIndex < numBands; bandIndex++) {
double[] bandValues = bandAsArray(rasterGeom, bandIndex + 1);
double bandNoDataValue = RasterUtils.getNoDataValue(rasterGeom.getSampleDimension(bandIndex));

if (noDataValue == null) {
noDataValue = maxLim;
}

for (double val : bandValues) {
if (val != bandNoDataValue) {
if (normalizeAcrossBands) {
globalMin = Math.min(globalMin, val);
globalMax = Math.max(globalMax, val);
} else {
minValues[bandIndex] = Math.min(minValues[bandIndex], val);
maxValues[bandIndex] = Math.max(maxValues[bandIndex], val);
}
}
}
}
} else {
globalMin = minValue;
globalMax = maxValue;
}

// Normalize each band
for (int bandIndex = 0; bandIndex < numBands; bandIndex++) {
double[] bandValues = bandAsArray(rasterGeom, bandIndex + 1);
double bandNoDataValue = RasterUtils.getNoDataValue(rasterGeom.getSampleDimension(bandIndex));
double currentMin = normalizeAcrossBands ? globalMin : (minValue != null ? minValue : minValues[bandIndex]);
double currentMax = normalizeAcrossBands ? globalMax : (maxValue != null ? maxValue : maxValues[bandIndex]);

if (Double.compare(currentMax, currentMin) == 0) {
Arrays.fill(bandValues, minLim);
} else {
for (int i = 0; i < bandValues.length; i++) {
if (bandValues[i] != bandNoDataValue) {
double normalizedValue = minLim + ((bandValues[i] - currentMin) * (maxLim - minLim)) / (currentMax - currentMin);
bandValues[i] = castRasterDataType(normalizedValue, rasterDataType);
} else {
bandValues[i] = noDataValue;
}
}
}

// Update the raster with the normalized band and noDataValue
rasterGeom = addBandFromArray(rasterGeom, bandValues, bandIndex+1);
rasterGeom = RasterBandEditors.setBandNoDataValue(rasterGeom, bandIndex+1, noDataValue);
}

return rasterGeom;
}

private static double castRasterDataType(double value, int dataType) {
switch (dataType) {
case DataBuffer.TYPE_BYTE:
return (byte) value;
case DataBuffer.TYPE_SHORT:
return (short) value;
case DataBuffer.TYPE_INT:
return (int) value;
case DataBuffer.TYPE_USHORT:
return (char) value;
case DataBuffer.TYPE_FLOAT:
return (float) value;
case DataBuffer.TYPE_DOUBLE:
default:
return value;
}
}

/**
* @param band1 band values
* @param band2 band values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@

import javax.media.jai.Interpolation;
import java.awt.geom.Point2D;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;

import static org.apache.sedona.common.raster.MapAlgebra.addBandFromArray;
import static org.apache.sedona.common.raster.MapAlgebra.bandAsArray;

public class RasterEditors
{
public static GridCoverage2D setSrid(GridCoverage2D raster, int srid)
Expand Down Expand Up @@ -228,4 +234,130 @@ private static boolean noConfigChange(int oldWidth, int oldHeight, double oldUpp
return ((approximateEquals(originalScaleX, newScaleX)) && (approximateEquals(originalScaleY, newScaleY)) && (approximateEquals(oldUpperX, newUpperX)) && (approximateEquals(oldUpperY, newUpperY)));
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom) {
return normalizeAll(rasterGeom, 0d, 255d, true, null, null, null);
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim) {
return normalizeAll(rasterGeom, minLim, maxLim, true, null, null, null);
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim, boolean normalizeAcrossBands) {
return normalizeAll(rasterGeom, minLim, maxLim, normalizeAcrossBands, null, null, null);
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim, boolean normalizeAcrossBands, Double noDataValue) {
return normalizeAll(rasterGeom, minLim, maxLim, normalizeAcrossBands, noDataValue, null, null);
}

public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim, Double noDataValue, Double minValue, Double maxValue) {
return normalizeAll(rasterGeom, minLim, maxLim, true, noDataValue, minValue, maxValue);
}

/**
*
* @param rasterGeom Raster to be normalized
* @param minLim Lower limit of normalization range
* @param maxLim Upper limit of normalization range
* @param normalizeAcrossBands flag to determine the normalization method
* @param noDataValue NoDataValue used in raster
* @param minValue Minimum value in raster
* @param maxValue Maximum value in raster
* @return a raster with all values in all bands normalized between minLim and maxLim
*/
public static GridCoverage2D normalizeAll(GridCoverage2D rasterGeom, double minLim, double maxLim, boolean normalizeAcrossBands, Double noDataValue, Double minValue, Double maxValue) {
if (minLim > maxLim) {
throw new IllegalArgumentException("minLim cannot be greater than maxLim");
}

int numBands = rasterGeom.getNumSampleDimensions();
RenderedImage renderedImage = rasterGeom.getRenderedImage();
int rasterDataType = renderedImage.getSampleModel().getDataType();

double globalMin = minValue != null ? minValue : Double.MAX_VALUE;
double globalMax = maxValue != null ? maxValue : -Double.MAX_VALUE;

// Initialize arrays to store band-wise min and max values
double[] minValues = new double[numBands];
double[] maxValues = new double[numBands];
Arrays.fill(minValues, Double.MAX_VALUE);
Arrays.fill(maxValues, -Double.MAX_VALUE);

// Trigger safe mode if noDataValue is null - noDataValue is set to maxLim and data values are normalized to range [minLim, maxLim-1].
// This is done to prevent setting valid data as noDataValue.
double safetyTrigger = (noDataValue == null) ? 1 : 0;

// Compute global min and max values across all bands if necessary and not provided
if (minValue == null || maxValue == null) {
for (int bandIndex = 0; bandIndex < numBands; bandIndex++) {
double[] bandValues = bandAsArray(rasterGeom, bandIndex + 1);
double bandNoDataValue = RasterUtils.getNoDataValue(rasterGeom.getSampleDimension(bandIndex));

if (noDataValue == null) {
noDataValue = maxLim;
}

for (double val : bandValues) {
if (val != bandNoDataValue) {
if (normalizeAcrossBands) {
globalMin = Math.min(globalMin, val);
globalMax = Math.max(globalMax, val);
} else {
minValues[bandIndex] = Math.min(minValues[bandIndex], val);
maxValues[bandIndex] = Math.max(maxValues[bandIndex], val);
}
}
}
}
} else {
globalMin = minValue;
globalMax = maxValue;
}

// Normalize each band
for (int bandIndex = 0; bandIndex < numBands; bandIndex++) {
double[] bandValues = bandAsArray(rasterGeom, bandIndex + 1);
double bandNoDataValue = RasterUtils.getNoDataValue(rasterGeom.getSampleDimension(bandIndex));
double currentMin = normalizeAcrossBands ? globalMin : (minValue != null ? minValue : minValues[bandIndex]);
double currentMax = normalizeAcrossBands ? globalMax : (maxValue != null ? maxValue : maxValues[bandIndex]);

if (Double.compare(currentMax, currentMin) == 0) {
Arrays.fill(bandValues, minLim);
} else {
for (int i = 0; i < bandValues.length; i++) {
if (bandValues[i] != bandNoDataValue) {
double normalizedValue = minLim + ((bandValues[i] - currentMin) * (maxLim - safetyTrigger - minLim)) / (currentMax - currentMin);
bandValues[i] = castRasterDataType(normalizedValue, rasterDataType);
} else {
bandValues[i] = noDataValue;
}
}
}

// Update the raster with the normalized band and noDataValue
rasterGeom = addBandFromArray(rasterGeom, bandValues, bandIndex+1);
rasterGeom = RasterBandEditors.setBandNoDataValue(rasterGeom, bandIndex+1, noDataValue);
}

return rasterGeom;
}

private static double castRasterDataType(double value, int dataType) {
switch (dataType) {
case DataBuffer.TYPE_BYTE:
return (byte) value;
case DataBuffer.TYPE_SHORT:
return (short) value;
case DataBuffer.TYPE_INT:
return (int) value;
case DataBuffer.TYPE_USHORT:
return (char) value;
case DataBuffer.TYPE_FLOAT:
return (float) value;
case DataBuffer.TYPE_DOUBLE:
default:
return value;
}
}

}
Loading

0 comments on commit 89e17c8

Please sign in to comment.