-
Notifications
You must be signed in to change notification settings - Fork 364
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1668 from jamesmcclain/feature/histogram-equaliza…
…tion Histogram Equalization
- Loading branch information
Showing
22 changed files
with
690 additions
and
139 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
14 changes: 0 additions & 14 deletions
14
geowave/src/main/scala/geotrellis/spark/io/geowave/package.scala
This file was deleted.
Oops, something went wrong.
77 changes: 77 additions & 0 deletions
77
raster-test/src/test/scala/geotrellis/raster/equalization/HistogramEqualizationSpec.scala
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,77 @@ | ||
/* | ||
* Copyright (c) 2016 Azavea. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package geotrellis.raster.equalization | ||
|
||
import geotrellis.raster._ | ||
|
||
import org.scalatest._ | ||
|
||
|
||
class HistogramEqualizationSpec extends FunSpec with Matchers | ||
{ | ||
val _a = List(0) | ||
val b = List(1) | ||
val c = List(2) | ||
val d = List(4) | ||
val e = List(8) | ||
val f = List(16) | ||
val g = List(32) | ||
val h = List(64) | ||
|
||
val data = (_a ++ b ++ c ++ d ++ e ++ f ++ g ++ h) | ||
|
||
describe("Histogram Equalization") { | ||
|
||
it("should work on floating-point rasters") { | ||
val tile = DoubleArrayTile(data.map(_.toDouble).toArray, 1, 8).equalize | ||
val array = tile.toArrayDouble | ||
|
||
array.head should be (Double.MinValue) | ||
array.last should be (Double.MaxValue) | ||
} | ||
|
||
it("should work on unsigned integral rasters") { | ||
val tile = UShortArrayTile(data.map(_.toShort).toArray, 1, 8, UShortCellType).equalize | ||
val array = tile.toArray | ||
|
||
array.head should be (0) | ||
array.last should be ((1<<16)-1) | ||
} | ||
|
||
it("should work on signed integral rasters") { | ||
val tile = ShortArrayTile(data.map(_.toShort).toArray, 1, 8, ShortCellType).equalize | ||
val array = tile.toArray | ||
|
||
array.head should be (-(1<<15)) | ||
array.last should be ((1<<15)-1) | ||
} | ||
|
||
it("should work on multiband tiles") { | ||
val data1 = (_a ++ b ++ c ++ d ++ List(0,0,0,0)) | ||
val data2 = (List(0,0,0,0) ++ e ++ f ++ g ++ h) | ||
|
||
val tile1 = ShortArrayTile(data1.map(_.toShort).toArray, 1, 8, ShortCellType) | ||
val tile2 = ShortArrayTile(data2.map(_.toShort).toArray, 1, 8, ShortCellType) | ||
val tile = ArrayMultibandTile(tile1, tile2).equalize | ||
val array = tile.bands.flatMap(_.toArray) | ||
|
||
array.head should be (-(1<<15)) | ||
array.last should be ((1<<15)-1) | ||
} | ||
|
||
} | ||
} |
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
119 changes: 119 additions & 0 deletions
119
raster/src/main/scala/geotrellis/raster/equalization/HistogramEqualization.scala
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,119 @@ | ||
package geotrellis.raster.equalization | ||
|
||
import geotrellis.raster._ | ||
import geotrellis.raster.histogram.Histogram | ||
import geotrellis.raster.histogram.StreamingHistogram | ||
|
||
|
||
/** | ||
* Uses the approach given here: | ||
* http://www.math.uci.edu/icamp/courses/math77c/demos/hist_eq.pdf | ||
*/ | ||
object HistogramEqualization { | ||
|
||
/** | ||
* Comparison class for sorting an array of (label, cdf(label)) | ||
* pairs by their label. | ||
*/ | ||
private class _cmp extends java.util.Comparator[(Double, Double)] { | ||
// Compare the (label, cdf(label)) pairs by their labels | ||
def compare(left: (Double, Double), right: (Double, Double)): Int = { | ||
if (left._1 < right._1) -1 | ||
else if (left._1 > right._1) 1 | ||
else 0 | ||
} | ||
} | ||
|
||
private val cmp = new _cmp() | ||
|
||
/** | ||
* An implementation of the transformation T referred to in the | ||
* citation given above. | ||
*/ | ||
private def _T(cellType: CellType, cdf: Array[(Double, Double)])(x: Double): Double = { | ||
val i = java.util.Arrays.binarySearch(cdf, (x, 0.0), cmp) | ||
val bits = cellType.bits | ||
val smallestCdf = cdf(0)._2 | ||
val rawCdf = | ||
if (x < cdf(0)._1) { // x is smaller than any label in the array | ||
smallestCdf // there is almost no mass here, so report 0.0 | ||
} | ||
else if (-1 * i > cdf.length) { // x is larger than any label in the array | ||
1.0 // there is almost no mass here, so report 1.0 | ||
} | ||
else if (i >= 0) { // i is the location of the label x in the array | ||
cdf(i)._2 | ||
} | ||
else { // x is between two labels in the array | ||
val j = (-1 * i - 2) | ||
val cdf0 = cdf(j+0)._2 | ||
val cdf1 = cdf(j+1)._2 | ||
val t = (x - cdf0) / (cdf1 - cdf0) | ||
(1.0-t)*cdf0 + t*cdf1 | ||
} | ||
val normalizedCdf = math.max(0.0, math.min(1.0, (rawCdf - smallestCdf) / (1.0 - smallestCdf))) | ||
|
||
cellType match { | ||
case _: FloatCells => (Float.MaxValue * (2*normalizedCdf - 1.0)) | ||
case _: DoubleCells => (Double.MaxValue * (2*normalizedCdf - 1.0)) | ||
case _: BitCells | _: UByteCells | _: UShortCells => | ||
((1<<bits) - 1) * normalizedCdf | ||
case _: ByteCells | _: ShortCells | _: IntCells => | ||
(((1<<bits) - 1) * normalizedCdf) - (1<<(bits-1)) | ||
} | ||
} | ||
|
||
/** | ||
* Given a [[Tile]], return a Tile with an equalized histogram. | ||
* | ||
* @param tile A singleband tile | ||
* @return A singleband tile with improved contrast | ||
*/ | ||
def apply(tile: Tile): Tile = { | ||
val histogram = StreamingHistogram.fromTile(tile, 1<<17) | ||
HistogramEqualization(tile, histogram) | ||
} | ||
|
||
/** | ||
* Given a [[Tile]] and a [[Histogram]], return a Tile with an | ||
* equalized histogram. | ||
* | ||
* @param tile A singleband tile | ||
* @param histogram The histogram of the tile | ||
* @return A singleband tile with improved contrast | ||
*/ | ||
def apply[T <: AnyVal](tile: Tile, histogram: Histogram[T]): Tile = { | ||
val T = _T(tile.cellType, histogram.cdf)_ | ||
tile.mapDouble(T) | ||
} | ||
|
||
/** | ||
* Given a [[MultibandTile]], return a MultibandTile whose bands | ||
* all have equalized histograms. | ||
* | ||
* @param tile A multiband tile | ||
* @return A multiband tile with improved contrast | ||
*/ | ||
def apply(tile: MultibandTile): MultibandTile = { | ||
val histograms = tile.bands.map({ tile => StreamingHistogram.fromTile(tile, 1<<17) }) | ||
HistogramEqualization(tile, histograms) | ||
} | ||
|
||
/** | ||
* Given a [[MultibandTile]] and a [[Histogram]] for each of its | ||
* bands, return a MultibandTile whose bands all have equalized | ||
* histograms. | ||
* | ||
* @param tile A multiband tile | ||
* @param histograms A sequence of histograms, one for each band | ||
* @return A multiband tile with improved contrast | ||
*/ | ||
def apply[T <: AnyVal](tile: MultibandTile, histograms: Seq[Histogram[T]]): MultibandTile = { | ||
MultibandTile( | ||
tile.bands | ||
.zip(histograms) | ||
.map({ case (tile, histogram) => HistogramEqualization(tile, histogram) }) | ||
) | ||
} | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
raster/src/main/scala/geotrellis/raster/equalization/MultibandEqualizationMethods.scala
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,24 @@ | ||
package geotrellis.raster.equalization | ||
|
||
import geotrellis.raster.histogram.StreamingHistogram | ||
import geotrellis.raster.MultibandTile | ||
import geotrellis.util.MethodExtensions | ||
|
||
|
||
trait MultibandEqualizationMethods extends MethodExtensions[MultibandTile] { | ||
|
||
/** | ||
* Equalize the histograms of the bands of this [[MultibandTile]]. | ||
* | ||
* @param histogram A sequence of [[StreamingHistogram]] objects, one for each band | ||
* @return A multiband tile whose bands have equalized histograms | ||
*/ | ||
def equalize(histograms: Array[StreamingHistogram]): MultibandTile = HistogramEqualization(self, histograms) | ||
|
||
/** | ||
* Equalize the histograms of the bands of this [[MultibandTile]]. | ||
* | ||
* @return A multiband tile whose bands have equalized histograms | ||
*/ | ||
def equalize(): MultibandTile = HistogramEqualization(self) | ||
} |
22 changes: 22 additions & 0 deletions
22
raster/src/main/scala/geotrellis/raster/equalization/SinglebandEqualizationMethods.scala
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,22 @@ | ||
package geotrellis.raster.equalization | ||
|
||
import geotrellis.raster.histogram.StreamingHistogram | ||
import geotrellis.raster.Tile | ||
import geotrellis.util.MethodExtensions | ||
|
||
|
||
trait SinglebandEqualizationMethods extends MethodExtensions[Tile] { | ||
|
||
/** | ||
* Given a [[StreamingHistogram]] derived from this [[Tile]], | ||
* equalize the histogram of this tile. | ||
* | ||
* @params histogram The histogram of this tile | ||
*/ | ||
def equalize(histogram: StreamingHistogram): Tile = HistogramEqualization(self, histogram) | ||
|
||
/** | ||
* Equalize the histogram of this [[Tile]]. | ||
*/ | ||
def equalize(): Tile = HistogramEqualization(self) | ||
} |
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
Oops, something went wrong.