From 4b31440293480bd6527c585e63a95653583b44eb Mon Sep 17 00:00:00 2001 From: Drew Short Date: Tue, 2 Jan 2018 15:07:54 -0600 Subject: [PATCH] Refactor: Cleanup and performance enhancements * Full imageHash calculation improved between 10-20% with greater performance enhancements for larger images. * Cleaned up code * Reduced code duplication * Adapted engine to hash lib 0.2.0 * Adjusted library versions where appropriate --- .../imagetools/engine/ConcurrentEngine.scala | 3 +- .../engine/image/ImageService.scala | 13 +- .../engine/util/PropertiesService.scala | 29 +- hash/pom.xml | 7 +- .../sothr/imagetools/hash/HashService.scala | 404 +++++++----- .../sothr/imagetools/hash/HashSetting.scala | 8 + .../com/sothr/imagetools/hash/ImageHash.scala | 25 + .../imagetools/hash/dto/HashSettingDTO.scala | 8 - .../imagetools/hash/dto/ImageHashDTO.scala | 46 -- .../sothr/imagetools/hash/type/AHash.scala | 85 +-- .../sothr/imagetools/hash/type/DHash.scala | 98 +-- .../sothr/imagetools/hash/type/PHash.scala | 115 ++-- .../hash/type/PerceptualHasher.scala | 12 +- .../imagetools/hash/util/HammingUtil.scala | 25 +- .../imagetools/hash/util/ImageUtil.scala | 203 +++--- .../imagetools/hash/util/TimingUtil.scala | 49 +- .../com/sothr/imagetools/hash/BaseTest.scala | 9 - .../hash/HashServiceAHashTest.scala | 93 +++ .../imagetools/hash/HashServiceBaseTest.scala | 21 + .../hash/HashServiceDHashTest.scala | 110 ++++ .../imagetools/hash/HashServiceMD5Test.scala | 67 ++ .../hash/HashServicePHashTest.scala | 93 +++ .../imagetools/hash/HashServiceSHA1Test.scala | 67 ++ .../imagetools/hash/HashServiceTest.scala | 580 ++++-------------- .../sothr/imagetools/hash/TestParams.scala | 7 - parent/pom.xml | 4 +- 26 files changed, 1173 insertions(+), 1008 deletions(-) create mode 100644 hash/src/main/scala/com/sothr/imagetools/hash/HashSetting.scala create mode 100644 hash/src/main/scala/com/sothr/imagetools/hash/ImageHash.scala delete mode 100644 hash/src/main/scala/com/sothr/imagetools/hash/dto/HashSettingDTO.scala delete mode 100644 hash/src/main/scala/com/sothr/imagetools/hash/dto/ImageHashDTO.scala delete mode 100644 hash/src/test/scala/com/sothr/imagetools/hash/BaseTest.scala create mode 100644 hash/src/test/scala/com/sothr/imagetools/hash/HashServiceAHashTest.scala create mode 100644 hash/src/test/scala/com/sothr/imagetools/hash/HashServiceBaseTest.scala create mode 100644 hash/src/test/scala/com/sothr/imagetools/hash/HashServiceDHashTest.scala create mode 100644 hash/src/test/scala/com/sothr/imagetools/hash/HashServiceMD5Test.scala create mode 100644 hash/src/test/scala/com/sothr/imagetools/hash/HashServicePHashTest.scala create mode 100644 hash/src/test/scala/com/sothr/imagetools/hash/HashServiceSHA1Test.scala delete mode 100644 hash/src/test/scala/com/sothr/imagetools/hash/TestParams.scala diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala index 7818158..7ad6e0e 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala @@ -7,10 +7,9 @@ import akka.actor.{Actor, ActorLogging, ActorRef, PoisonPill, Props} import akka.pattern.ask import akka.routing.{Broadcast, RoundRobinPool, SmallestMailboxPool} import akka.util.Timeout -import com.sothr.imagetools.hash.HashService +import com.sothr.imagetools.hash.{HashService, ImageHash} import com.sothr.imagetools.engine.image.{Image, ImageService, SimilarImages} import com.sothr.imagetools.engine.util.{PropertiesService, PropertyEnum} -import com.sothr.imagetools.hash.dto.ImageHashDTO import scala.collection.mutable import scala.concurrent.Await diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala index bdaa5f5..7862747 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala @@ -8,8 +8,7 @@ import com.sothr.imagetools.engine.AppConfig import com.sothr.imagetools.engine.dao.ImageDAO import com.sothr.imagetools.engine.util.{PropertiesService, PropertyEnum} import com.sothr.imagetools.engine.vo.ImageHashVO -import com.sothr.imagetools.hash.HashService -import com.sothr.imagetools.hash.dto.ImageHashDTO +import com.sothr.imagetools.hash.{HashService, ImageHash} import com.sothr.imagetools.hash.util.ImageUtil import grizzled.slf4j.Logging import net.sf.ehcache.Element @@ -36,8 +35,8 @@ object ImageService extends Logging { bufferedImage, file.getAbsolutePath) - var thumbnailPath = lookupThumbnailPath(hashes.getFileHash) - if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.getFileHash) + var thumbnailPath = lookupThumbnailPath(hashes.fileHash) + if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.fileHash) val imageSize = { (bufferedImage.getWidth, bufferedImage.getHeight) } @@ -52,11 +51,11 @@ object ImageService extends Logging { null } - def convertToImageHashDTO(imageHashVO: ImageHashVO): ImageHashDTO = { - new ImageHashDTO(imageHashVO.ahash, imageHashVO.dhash, imageHashVO.phash, imageHashVO.fileHash) + def convertToImageHashDTO(imageHashVO: ImageHashVO): ImageHash = { + new ImageHash(imageHashVO.ahash, imageHashVO.dhash, imageHashVO.phash, imageHashVO.fileHash) } - def convertToImageHashVO(imageHashDTO: ImageHashDTO): ImageHashVO = { + def convertToImageHashVO(imageHashDTO: ImageHash): ImageHashVO = { new ImageHashVO(imageHashDTO.ahash, imageHashDTO.dhash, imageHashDTO.phash, imageHashDTO.fileHash) } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala index 5968342..65cdfa9 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala @@ -3,7 +3,7 @@ package com.sothr.imagetools.engine.util import java.io.{File, FileOutputStream, PrintStream} import java.util.Properties -import com.sothr.imagetools.hash.dto.HashSettingDTO +import com.sothr.imagetools.hash.HashSetting import com.typesafe.config.{Config, ConfigFactory, ConfigRenderOptions} import grizzled.slf4j.Logging @@ -13,19 +13,19 @@ import grizzled.slf4j.Logging object PropertiesService extends Logging { //OS information - val OS = System.getProperty("os.name", "UNKNOWN") - val OS_VERSION = System.getProperty("os.version", "UNKNOWN") - val OS_ARCH = System.getProperty("os.arch", "UNKNOWN") + val OS: String = System.getProperty("os.name", "UNKNOWN") + val OS_VERSION: String = System.getProperty("os.version", "UNKNOWN") + val OS_ARCH: String = System.getProperty("os.arch", "UNKNOWN") private val newUserConf: Properties = new Properties() private val configRenderOptions = ConfigRenderOptions.concise().setFormatted(true) //specific highly used properties var TimingEnabled: Boolean = false - var aHashSettings = new HashSettingDTO(false, 0, 0, 0.0f) - var dHashSettings = new HashSettingDTO(false, 0, 0, 0.0f) - var pHashSettings = new HashSettingDTO(false, 0, 0, 0.0f) - private var defaultConf: Config = null - private var userConf: Config = null - private var version: Version = null + var aHashSettings = new HashSetting("AHash", false, 0, 0, 0.0f) + var dHashSettings = new HashSetting("DHash", false, 0, 0, 0.0f) + var pHashSettings = new HashSetting("PHash", false, 0, 0, 0.0f) + private var defaultConf: Config = _ + private var userConf: Config = _ + private var version: Version = _ def getVersion: Version = this.version @@ -47,21 +47,24 @@ object PropertiesService extends Logging { //load special properties TimingEnabled = get(PropertyEnum.Timed.toString).toBoolean - aHashSettings = new HashSettingDTO( + aHashSettings = new HashSetting( + "AHash", get(PropertyEnum.UseAhash.toString).toBoolean, get(PropertyEnum.AhashPrecision.toString).toInt, get(PropertyEnum.AhashTolerance.toString).toInt, get(PropertyEnum.AhashWeight.toString).toFloat ) - dHashSettings = new HashSettingDTO( + dHashSettings = new HashSetting( + "DHash", get(PropertyEnum.UseDhash.toString).toBoolean, get(PropertyEnum.DhashPrecision.toString).toInt, get(PropertyEnum.DhashTolerance.toString).toInt, get(PropertyEnum.DhashWeight.toString).toFloat ) - pHashSettings = new HashSettingDTO( + pHashSettings = new HashSetting( + "PHash", get(PropertyEnum.UsePhash.toString).toBoolean, get(PropertyEnum.PhashPrecision.toString).toInt, get(PropertyEnum.PhashTolerance.toString).toInt, diff --git a/hash/pom.xml b/hash/pom.xml index 872bd40..e864b52 100644 --- a/hash/pom.xml +++ b/hash/pom.xml @@ -12,7 +12,7 @@ 4.0.0 hash - 0.1.1 + 0.2.0 jar ImageTools-Hash @@ -57,6 +57,11 @@ scalatest_${scala.binary.version} test + + ch.qos.logback + logback-classic + test + diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/HashService.scala b/hash/src/main/scala/com/sothr/imagetools/hash/HashService.scala index 1d88805..6230a71 100644 --- a/hash/src/main/scala/com/sothr/imagetools/hash/HashService.scala +++ b/hash/src/main/scala/com/sothr/imagetools/hash/HashService.scala @@ -5,7 +5,6 @@ import java.io.{File, FileInputStream} import javax.imageio.ImageIO import com.sothr.imagetools.hash.`type`.{AHash, DHash, PHash} -import com.sothr.imagetools.hash.dto.{HashSettingDTO, ImageHashDTO} import com.sothr.imagetools.hash.util.{HammingUtil, ImageUtil} import grizzled.slf4j.Logging import org.apache.commons.codec.digest.DigestUtils @@ -18,169 +17,240 @@ import resource.managed */ object HashService extends Logging { - def getImageHashes(ahashSettings: HashSettingDTO, - dhashSettings: HashSettingDTO, - phashSettings: HashSettingDTO, - imagePath: String): ImageHashDTO = { - getImageHashes(ahashSettings, dhashSettings, phashSettings, ImageIO.read(new File(imagePath)), imagePath) - } - - def getImageHashes(ahashSettings: HashSettingDTO, - dhashSettings: HashSettingDTO, - phashSettings: HashSettingDTO, - image: BufferedImage, - imagePath: String): ImageHashDTO = { - - debug(s"Creating hashes for $imagePath") - - var ahash: Long = 0L - var dhash: Long = 0L - var phash: Long = 0L - val sha1: String = getSHA1(imagePath) - - //Get Image Data - val grayImage = ImageUtil.convertToGray(image) - - if (ahashSettings.use) { - ahash = getAhash(ahashSettings, grayImage, alreadyGray = true) - } - if (dhashSettings.use) { - dhash = getDhash(dhashSettings, grayImage, alreadyGray = true) - } - if (phashSettings.use) { - phash = getPhash(phashSettings, grayImage, alreadyGray = true) - } - - val hashes = new ImageHashDTO(ahash, dhash, phash, sha1) - debug(s"Generated hashes: $hashes") - - hashes - } - - def getAhash(hashSettings: HashSettingDTO, image: BufferedImage, alreadyGray: Boolean = false): Long = { - //debug("Started generating an AHash") - var grayImage: BufferedImage = null - if (alreadyGray) { - grayImage = image - } else { - grayImage = ImageUtil.convertToGray(image) - } - val resizedImage = ImageUtil.resize(grayImage, hashSettings.precision, forced = true) - val imageData = ImageUtil.getImageData(resizedImage) - AHash.getHash(imageData) - } - - def getDhash(hashSettings: HashSettingDTO, image: BufferedImage, alreadyGray: Boolean = false): Long = { - //debug("Started generating an DHash") - var grayImage: BufferedImage = null - if (alreadyGray) { - grayImage = image - } else { - grayImage = ImageUtil.convertToGray(image) - } - val resizedImage = ImageUtil.resize(grayImage, hashSettings.precision, forced = true) - val imageData = ImageUtil.getImageData(resizedImage) - DHash.getHash(imageData) - } - - def getPhash(hashSettings: HashSettingDTO, image: BufferedImage, alreadyGray: Boolean = false): Long = { - //debug("Started generating an PHash") - var grayImage: BufferedImage = null - if (alreadyGray) { - grayImage = image - } else { - grayImage = ImageUtil.convertToGray(image) - } - val resizedImage = ImageUtil.resize(grayImage, hashSettings.precision, forced = true) - val imageData = ImageUtil.getImageData(resizedImage) - PHash.getHash(imageData) - } - - def getSHA1(filePath: String): String = { - managed(new FileInputStream(filePath)) acquireAndGet { - input => DigestUtils.sha1Hex(input) - } - } - - def getMD5(filePath: String): String = { - managed(new FileInputStream(filePath)) acquireAndGet { - input => DigestUtils.md5Hex(input) - } - } - - def areHashSimilar(hashSettings: HashSettingDTO, hash1: Long, hash2: Long): Boolean = { - val tolerence = hashSettings.tolerance - val distance = HammingUtil.getDistance(hash1, hash2) - if (distance <= tolerence) true else false - } - - def areImageHashesSimilar(ahashSettings: HashSettingDTO, - dhashSettings: HashSettingDTO, - phashSettings: HashSettingDTO, - imageHash1: ImageHashDTO, - imageHash2: ImageHashDTO): Boolean = { - val weightedHammingMean = getWeightedHashSimilarity(ahashSettings, dhashSettings, phashSettings, imageHash1, imageHash2) - val weightedToleranceMean = getWeightedHashTolerence(ahashSettings, dhashSettings, phashSettings) - if (weightedHammingMean <= weightedToleranceMean) true else false - } - - def getWeightedHashSimilarity(ahashSettings: HashSettingDTO, - dhashSettings: HashSettingDTO, - phashSettings: HashSettingDTO, - imageHash1: ImageHashDTO, - imageHash2: ImageHashDTO): Float = { - //calculate weighted values - var weightedHammingTotal: Float = 0 - var methodsTotal = 0 - - if (ahashSettings.use) { - val hamming = HammingUtil.getDistance(imageHash1.ahash, imageHash2.ahash) - weightedHammingTotal += hamming * ahashSettings.weight - trace(s"hash1: ${imageHash1.ahash} hash2: ${imageHash1.ahash} tolerance: ${ahashSettings.tolerance} hamming distance: $hamming weight: ${ahashSettings.weight}") - methodsTotal += 1 - } - if (dhashSettings.use) { - val hamming = HammingUtil.getDistance(imageHash1.dhash, imageHash2.dhash) - weightedHammingTotal += hamming * dhashSettings.weight - trace(s"hash1: ${imageHash1.dhash} hash2: ${imageHash1.dhash} tolerance: ${dhashSettings.tolerance} hamming distance: $hamming weight: ${dhashSettings.weight}") - methodsTotal += 1 - } - if (phashSettings.use) { - val hamming = HammingUtil.getDistance(imageHash1.phash, imageHash2.phash) - weightedHammingTotal += hamming * phashSettings.weight - trace(s"hash1: ${imageHash1.phash} hash2: ${imageHash1.phash} tolerance: ${phashSettings.tolerance} hamming distance: $hamming weight: ${phashSettings.weight}") - methodsTotal += 1 - } - val weightedHammingMean = weightedHammingTotal / methodsTotal - debug(s"Calculated Weighted HammingUtil Mean: $weightedHammingMean") - weightedHammingMean - } - - def getWeightedHashTolerence(ahashSettings: HashSettingDTO, - dhashSettings: HashSettingDTO, - phashSettings: HashSettingDTO): Float = { - //calculate weighted values - var weightedToleranceTotal: Float = 0 - var methodsTotal = 0 - - if (ahashSettings.use) { - weightedToleranceTotal += ahashSettings.tolerance * ahashSettings.weight - trace(s"Ahash Tolerance: ${ahashSettings.tolerance} Current Weighted Tolerance: $weightedToleranceTotal") - methodsTotal += 1 - } - if (dhashSettings.use) { - weightedToleranceTotal += dhashSettings.tolerance * dhashSettings.weight - trace(s"Dhash Tolerance: ${dhashSettings.tolerance} Current Weighted Tolerance: $weightedToleranceTotal") - methodsTotal += 1 - } - if (phashSettings.use) { - weightedToleranceTotal += phashSettings.tolerance * phashSettings.weight - trace(s"Phash Tolerance: ${phashSettings.tolerance} Current Weighted Tolerance: $weightedToleranceTotal") - methodsTotal += 1 - } - val weightedTolerance = weightedToleranceTotal / methodsTotal - debug(s"Calculated Weighted Tolerance: $weightedTolerance") - weightedTolerance - } - -} + /** + * Given hash settings and an image path, calculate the perceptual hashes + * + * @param ahashSettings { @see com.sothr.imagetools.hash.HashSetting} + * @param dhashSettings { @see com.sothr.imagetools.hash.HashSetting} + * @param phashSettings { @see com.sothr.imagetools.hash.HashSetting} + * @param imagePath Absolute path to the image file + * @return { @see com.sothr.imagetools.hash.ImageHash} + */ + def getImageHashes(ahashSettings: HashSetting, + dhashSettings: HashSetting, + phashSettings: HashSetting, + imagePath: String): ImageHash = { + getImageHashes(ahashSettings, + dhashSettings, + phashSettings, + ImageIO.read(new File(imagePath)), + imagePath) + } + + def getPrecisionMap(precisionSet: Set[Int], grayImage: BufferedImage): Map[Int, Array[Array[Int]]] = { + precisionSet.map(p => p -> getImageData(p, grayImage, alreadyGray = true))(collection.breakOut) + } + + /** + * Given hash settings, a buffered image and an image path, calculate the perceptual hashes + * + * @param ahashSettings { @see com.sothr.imagetools.hash.HashSetting} + * @param dhashSettings { @see com.sothr.imagetools.hash.HashSetting} + * @param phashSettings { @see com.sothr.imagetools.hash.HashSetting} + * @param image { @see java.awt.image.BufferedImage} + * @param imagePath Absolute path to the image file + * @return { @see com.sothr.imagetools.hash.ImageHash} + * + */ + def getImageHashes(ahashSettings: HashSetting, + dhashSettings: HashSetting, + phashSettings: HashSetting, + image: BufferedImage, + imagePath: String): ImageHash = { + + debug(s"Creating hashes for $imagePath") + + //Get Image Data + val grayImage = ImageUtil.convertToGray(image) + + val precisionSet: Set[Int] = getPrecisionSet(ahashSettings, dhashSettings, phashSettings) + val precisionMap: Map[Int, Array[Array[Int]]] = getPrecisionMap(precisionSet, grayImage) + + val ahash: Long = if (ahashSettings.use && precisionMap.contains(ahashSettings.precision)) + getHash(ahashSettings, precisionMap(ahashSettings.precision), AHash.getHash) + else 0l + + val dhash: Long = if (dhashSettings.use && precisionMap.contains(dhashSettings.precision)) + getHash(dhashSettings, precisionMap(dhashSettings.precision), DHash.getHash) + else 0l + + val phash: Long = if (phashSettings.use && precisionMap.contains(phashSettings.precision)) + getHash(phashSettings, precisionMap(phashSettings.precision), PHash.getHash) + else 0l + + val sha1: String = getSHA1(imagePath) + + val hashes = new ImageHash(ahash, dhash, phash, sha1) + debug(s"Generated hashes: $hashes") + + hashes + } + + def getSHA1(filePath: String): String = { + managed(new FileInputStream(filePath)) acquireAndGet { + input => DigestUtils.sha1Hex(input) + } + } + + def getPrecisionSet(ahashSettings: HashSetting, dhashSettings: HashSetting, phashSettings: HashSetting): Set[Int] = { + Set( + if (ahashSettings.use) Option(ahashSettings.precision) else None, + if (dhashSettings.use) Option(dhashSettings.precision) else None, + if (phashSettings.use) Option(phashSettings.precision) else None + ).flatten + } + + def getImageData(precision: Int, image: BufferedImage, alreadyGray: Boolean): Array[Array[Int]] = { + var grayImage: BufferedImage = null + + if (alreadyGray) { + grayImage = image + } else { + grayImage = ImageUtil.convertToGray(image) + } + + val resizedImage = ImageUtil.resize(grayImage, precision, forced = true) + ImageUtil.getImageData(resizedImage) + } + + /** + * Simpler function that only works with the imageData and the hashFunction + * + * @param hashSettings + * @param imageData + * @param hashFunction + * @return + */ + def getHash(hashSettings: HashSetting, imageData: Array[Array[Int]], hashFunction: Array[Array[Int]] => Long): Long = { + if (hashSettings.use) { + val hashResult = hashFunction(imageData) + trace(s"${hashSettings.name} result: $hashResult") + hashResult + } else { + 0l + } + } + + def getAhash(hashSettings: HashSetting, image: BufferedImage, alreadyGray: Boolean = false): Long = { + getHash(hashSettings, image, alreadyGray, AHash.getHash) + } + + /** + * Internal function to retrieve the hash from a processed image, for a given hash function + * + * @param hashSettings A HashSettings that is used for the processing parameters of the Image + * @param image BufferedImage representing the image data to calculate the hash for + * @param alreadyGray Indicator that the grayscale processing has already been applied to the incoming image + * @param hashFunction The function accepting an two dimensional array of Int and returning the hash of the data + * @return + */ + def getHash(hashSettings: HashSetting, image: BufferedImage, alreadyGray: Boolean = false, hashFunction: Array[Array[Int]] => Long): Long = { + if (hashSettings.use) { + var grayImage: BufferedImage = null + + if (alreadyGray) { + grayImage = image + } else { + grayImage = ImageUtil.convertToGray(image) + } + + val resizedImage = ImageUtil.resize(grayImage, hashSettings.precision, forced = true) + val hashResult = hashFunction(ImageUtil.getImageData(resizedImage)) + trace(s"${hashSettings.name} result: $hashResult") + hashResult + } else { + trace(s"${hashSettings.name} result: DISABLED") + 0l + } + } + + def getDhash(hashSettings: HashSetting, image: BufferedImage, alreadyGray: Boolean = false): Long = { + getHash(hashSettings, image, alreadyGray, DHash.getHash) + } + + def getPhash(hashSettings: HashSetting, image: BufferedImage, alreadyGray: Boolean = false): Long = { + getHash(hashSettings, image, alreadyGray, PHash.getHash) + } + + def getMD5(filePath: String): String = { + managed(new FileInputStream(filePath)) acquireAndGet { + input => DigestUtils.md5Hex(input) + } + } + + def areHashSimilar(hashSettings: HashSetting, hash1: Long, hash2: Long): Boolean = { + val tolerance = hashSettings.tolerance + val distance = HammingUtil.getDistance(hash1, hash2) + if (distance <= tolerance) true else false + } + + def areImageHashesSimilar(ahashSettings: HashSetting, + dhashSettings: HashSetting, + phashSettings: HashSetting, + imageHash1: ImageHash, + imageHash2: ImageHash): Boolean = { + val weightedHammingMean = getWeightedHashSimilarity(ahashSettings, dhashSettings, phashSettings, imageHash1, imageHash2) + val weightedToleranceMean = getWeightedHashTolerance(ahashSettings, dhashSettings, phashSettings) + if (weightedHammingMean <= weightedToleranceMean) true else false + } + + def getWeightedHashSimilarity(ahashSettings: HashSetting, + dhashSettings: HashSetting, + phashSettings: HashSetting, + imageHash1: ImageHash, + imageHash2: ImageHash): Float = { + + val (weightedHammingTotal1, methodsTotal1) = sumWeightedHammingAndMethodCount(ahashSettings, imageHash1.ahash, imageHash2.ahash, 0f, 0) + val (weightedHammingTotal2, methodsTotal2) = sumWeightedHammingAndMethodCount(dhashSettings, imageHash1.dhash, imageHash2.dhash, weightedHammingTotal1, methodsTotal1) + val (weightedHammingTotalFinal, methodsTotalFinal) = sumWeightedHammingAndMethodCount(phashSettings, imageHash1.phash, imageHash2.phash, weightedHammingTotal2, methodsTotal2) + + val weightedHammingMean = weightedHammingTotalFinal / methodsTotalFinal + debug(s"Calculated Weighted HammingUtil Mean: $weightedHammingMean") + weightedHammingMean + } + + def sumWeightedHammingAndMethodCount(hashSettings: HashSetting, hash1: Long, hash2: Long, weightedHamming: Float, methodCount: Int): (Float, Int) = { + val (newWeightedHamming, newMethodCount) = getWeightedHamming(hashSettings, hash1, hash2) + (weightedHamming + newWeightedHamming, methodCount + newMethodCount) + } + + def getWeightedHamming(hashSettings: HashSetting, hash1: Long, hash2: Long): (Float, Int) = { + if (hashSettings.use) { + val hamming = HammingUtil.getDistance(hash1, hash2) + trace(s"${hashSettings.name} Hamming: hash1: $hash1 hash2: $hash2 tolerance: ${hashSettings.tolerance} hamming distance: $hamming weight: ${hashSettings.weight}") + (hamming * hashSettings.weight, 1) + } else { + trace(s"${hashSettings.name} Hamming: DISABLED") + (0f, 0) + } + } + + def getWeightedHashTolerance(ahashSettings: HashSetting, + dhashSettings: HashSetting, + phashSettings: HashSetting): Float = { + + val (weightedToleranceTotal1, methodsTotal1) = sumWeightedToleranceAndMethodCount(ahashSettings, 0f, 0) + val (weightedToleranceTotal2, methodsTotal2) = sumWeightedToleranceAndMethodCount(dhashSettings, weightedToleranceTotal1, methodsTotal1) + val (weightedToleranceTotalFinal, methodsTotalFinal) = sumWeightedToleranceAndMethodCount(phashSettings, weightedToleranceTotal2, methodsTotal2) + + val weightedTolerance = weightedToleranceTotalFinal / methodsTotalFinal + debug(s"Calculated Weighted Tolerance: $weightedTolerance") + weightedTolerance + } + + def sumWeightedToleranceAndMethodCount(hashSettings: HashSetting, weightedTolerance: Float, methodCount: Int): (Float, Int) = { + val (newWeightedTolerance, newMethodCount) = getWeightedTolerance(hashSettings) + (weightedTolerance + newWeightedTolerance, methodCount + newMethodCount) + } + + def getWeightedTolerance(hashSettings: HashSetting): (Float, Int) = { + if (hashSettings.use) { + val weightedTolerance = hashSettings.tolerance * hashSettings.weight + trace(s"${hashSettings.name} Tolerance: ${hashSettings.tolerance} Current Weighted Tolerance: $weightedTolerance") + (weightedTolerance, 1) + } else { + trace(s"${hashSettings.name} Tolerance: DISABLED") + (0f, 0) + } + } +} \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/HashSetting.scala b/hash/src/main/scala/com/sothr/imagetools/hash/HashSetting.scala new file mode 100644 index 0000000..a37e2c6 --- /dev/null +++ b/hash/src/main/scala/com/sothr/imagetools/hash/HashSetting.scala @@ -0,0 +1,8 @@ +package com.sothr.imagetools.hash + +class HashSetting(val name: String, + val use: Boolean, + val precision: Int, + val tolerance: Int, + val weight: Float) { +} \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/ImageHash.scala b/hash/src/main/scala/com/sothr/imagetools/hash/ImageHash.scala new file mode 100644 index 0000000..bc628b0 --- /dev/null +++ b/hash/src/main/scala/com/sothr/imagetools/hash/ImageHash.scala @@ -0,0 +1,25 @@ +package com.sothr.imagetools.hash + +import grizzled.slf4j.Logging + +class ImageHash(val ahash: Long, + val dhash: Long, + val phash: Long, + val fileHash: String) extends Serializable with Logging { + + def cloneHashes: ImageHash = { + new ImageHash(ahash, dhash, phash, fileHash) + } + + override def hashCode(): Int = { + var result = 365 + result = 41 * result + (this.ahash ^ (this.ahash >>> 32)).toInt + result = 37 * result + (this.dhash ^ (this.dhash >>> 32)).toInt + result = 2 * result + (this.phash ^ (this.phash >>> 32)).toInt + result + } + + override def toString: String = { + s"fileHash: $fileHash ahash: $ahash dhash: $dhash phash: $phash" + } +} \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/dto/HashSettingDTO.scala b/hash/src/main/scala/com/sothr/imagetools/hash/dto/HashSettingDTO.scala deleted file mode 100644 index eb9e947..0000000 --- a/hash/src/main/scala/com/sothr/imagetools/hash/dto/HashSettingDTO.scala +++ /dev/null @@ -1,8 +0,0 @@ -package com.sothr.imagetools.hash.dto - -class HashSettingDTO( - val use: Boolean, - val precision: Int, - val tolerance: Int, - val weight: Float) { -} diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/dto/ImageHashDTO.scala b/hash/src/main/scala/com/sothr/imagetools/hash/dto/ImageHashDTO.scala deleted file mode 100644 index acad5fd..0000000 --- a/hash/src/main/scala/com/sothr/imagetools/hash/dto/ImageHashDTO.scala +++ /dev/null @@ -1,46 +0,0 @@ -package com.sothr.imagetools.hash.dto - -import grizzled.slf4j.Logging - -class ImageHashDTO(var ahash: Long, var dhash: Long, var phash: Long, var fileHash: String) extends Serializable with Logging { - - def getAhash: Long = ahash - - def setAhash(hash: Long) = { - ahash = hash - } - - def getDhash: Long = dhash - - def setDhash(hash: Long) = { - dhash = hash - } - - def getPhash: Long = phash - - def setPhash(hash: Long) = { - phash = hash - } - - def getFileHash: String = fileHash - - def setFileHash(hash: String) = { - fileHash = hash - } - - def cloneHashes: ImageHashDTO = { - new ImageHashDTO(ahash, dhash, phash, fileHash) - } - - override def hashCode(): Int = { - var result = 365 - result = 41 * result + (this.ahash ^ (this.ahash >>> 32)).toInt - result = 37 * result + (this.dhash ^ (this.dhash >>> 32)).toInt - result = 2 * result + (this.phash ^ (this.phash >>> 32)).toInt - result - } - - override def toString: String = { - s"fileHash: $fileHash ahash: $ahash dhash: $dhash phash: $phash" - } -} diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/type/AHash.scala b/hash/src/main/scala/com/sothr/imagetools/hash/type/AHash.scala index e211a8b..94c5547 100644 --- a/hash/src/main/scala/com/sothr/imagetools/hash/type/AHash.scala +++ b/hash/src/main/scala/com/sothr/imagetools/hash/type/AHash.scala @@ -3,49 +3,50 @@ package com.sothr.imagetools.hash.`type` import grizzled.slf4j.Logging /** - * Speedy AHash Perceptual Hashing - * Uses the average value of the pixels as a baseline - * - * Created by dev on 1/22/14. - */ + * Speedy AHash Perceptual Hashing + * Uses the average value of the pixels as a baseline to determine the structure of the image + * + * Created by dev on 1/22/14. + */ object AHash extends PerceptualHasher with Logging { - def getHash(imageData: Array[Array[Int]]): Long = { - trace("Generating AHash") - val width = imageData.length - val height = imageData(0).length - trace(s"Image data size: ${width}x$height") + def getHash(imageData: Array[Array[Int]]): Long = { + trace("Generating AHash") + val width = imageData.length + val height = imageData(0).length + trace(s"Image data size: ${width}x$height") - //calculate average pixel - var total = 0 - for (row <- 0 until height) { - for (col <- 0 until width) { - total += imageData(row)(col) - } - } - val mean = total / (height * width) + /* + Average Pixel Calculation + */ + var total = 0 + for (row <- 0 until height) { + for (col <- 0 until width) { + total += imageData(row)(col) + } + } + val mean = total / (height * width) - //calculate ahash - var hash = 0L - for (row <- 0 until height by 2) { - //process each column - for (col <- 0 until width by 1) { - hash <<= 1 - val pixel = imageData(row)(col) - //If the current pixel is at or above the mean, store it as a one, else store it as a zero - if (pixel >= mean) hash |= 1 else hash |= 0 - } + /* + For each pixel, if it is at or above the average, store it as a one, else store it as a zero + */ + var hash = 0L + for (row <- 0 until height by 2) { + for (col <- 0 until width by 1) { + hash <<= 1 + val pixel = imageData(row)(col) + if (pixel >= mean) hash |= 1 else hash |= 0 + } - if ((row + 1) < width) { - val nextRow = row + 1 - //process each column - for (col <- (width - 1) to 0 by -1) { - hash <<= 1 - val pixel = imageData(nextRow)(col) - if (pixel >= mean) hash |= 1 else hash |= 0 - } - } - } - debug(s"Computed AHash: $hash from ${width * height} pixels") - hash - } -} + if ((row + 1) < width) { + val nextRow = row + 1 + for (col <- (width - 1) to 0 by -1) { + hash <<= 1 + val pixel = imageData(nextRow)(col) + if (pixel >= mean) hash |= 1 else hash |= 0 + } + } + } + debug(s"Computed AHash: $hash from ${width * height} pixels") + hash + } +} \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/type/DHash.scala b/hash/src/main/scala/com/sothr/imagetools/hash/type/DHash.scala index d2854b6..44317dd 100644 --- a/hash/src/main/scala/com/sothr/imagetools/hash/type/DHash.scala +++ b/hash/src/main/scala/com/sothr/imagetools/hash/type/DHash.scala @@ -3,55 +3,57 @@ package com.sothr.imagetools.hash.`type` import grizzled.slf4j.Logging /** - * DHash algorithm class - * - * Created by Drew on 1/22/14. - */ + * DHash algorithm object + * + * Tracks the differences between pixels in a pattern as the basis of similarity + * + * Created by Drew on 1/22/14. + */ object DHash extends PerceptualHasher with Logging { - def getHash(imageData: Array[Array[Int]]): Long = { - trace("Generating DHash") - val width = imageData.length - val height = imageData(0).length - trace(s"Image data size: ${width}x$height") + def getHash(imageData: Array[Array[Int]]): Long = { + trace("Generating DHash") + val width = imageData.length + val height = imageData(0).length + trace(s"Image data size: ${width}x$height") - //calculate dhash - var hash = 0L - var previousPixel = imageData(height - 1)(width - 1) - var previousLocation = (height - 1, width - 1) - if (height % 2 == 0) { - previousPixel = imageData(height - 1)(0) - previousLocation = (height - 1, 0) - } + //calculate dhash + var hash = 0L + var previousPixel = imageData(height - 1)(width - 1) + var previousLocation = (height - 1, width - 1) + if (height % 2 == 0) { + previousPixel = imageData(height - 1)(0) + previousLocation = (height - 1, 0) + } - for (row <- 0 until height by 2) { - //process each column - for (col <- 0 until width by 1) { - hash <<= 1 - val pixel = imageData(row)(col) - //debug(s"previousPixel: $previousPixel currentPixel: $pixel previousLocation: $previousLocation currentLocation: (${row},${col})") - //binary of the current bit based on whether the value - //of the current pixel is greater or equal to the previous pixel - if (pixel >= previousPixel) hash |= 1 else hash |= 0 - //debug(s"(${row},${col})=$pixel hash=${hash.toBinaryString}") - previousPixel = pixel - previousLocation = (row, col) - } + for (row <- 0 until height by 2) { + //process each column + for (col <- 0 until width by 1) { + hash <<= 1 + val pixel = imageData(row)(col) + //debug(s"previousPixel: $previousPixel currentPixel: $pixel previousLocation: $previousLocation currentLocation: (${row},${col})") + //binary of the current bit based on whether the value + //of the current pixel is greater or equal to the previous pixel + if (pixel >= previousPixel) hash |= 1 else hash |= 0 + //debug(s"(${row},${col})=$pixel hash=${hash.toBinaryString}") + previousPixel = pixel + previousLocation = (row, col) + } - if ((row + 1) < width) { - val nextRow = row + 1 - //process each column - for (col <- (width - 1) to 0 by -1) { - hash <<= 1 - val pixel = imageData(nextRow)(col) - //debug(s"previousPixel: $previousPixel currentPixel: $pixel previousLocation: $previousLocation currentLocation: (${nextRow},${col})") - if (pixel >= previousPixel) hash |= 1 else hash |= 0 - //debug(s"(${row},${col})=$pixel hash=${hash.toBinaryString}") - previousPixel = pixel - previousLocation = (nextRow, col) - } - } - } - debug(s"Computed DHash: $hash from ${width * height} pixels") - hash - } -} + if ((row + 1) < width) { + val nextRow = row + 1 + //process each column + for (col <- (width - 1) to 0 by -1) { + hash <<= 1 + val pixel = imageData(nextRow)(col) + //debug(s"previousPixel: $previousPixel currentPixel: $pixel previousLocation: $previousLocation currentLocation: (${nextRow},${col})") + if (pixel >= previousPixel) hash |= 1 else hash |= 0 + //debug(s"(${row},${col})=$pixel hash=${hash.toBinaryString}") + previousPixel = pixel + previousLocation = (nextRow, col) + } + } + } + debug(s"Computed DHash: $hash from ${width * height} pixels") + hash + } +} \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/type/PHash.scala b/hash/src/main/scala/com/sothr/imagetools/hash/type/PHash.scala index 5660974..137c9fb 100644 --- a/hash/src/main/scala/com/sothr/imagetools/hash/type/PHash.scala +++ b/hash/src/main/scala/com/sothr/imagetools/hash/type/PHash.scala @@ -4,69 +4,66 @@ import edu.emory.mathcs.jtransforms.dct.FloatDCT_2D import grizzled.slf4j.Logging /** - * Complex perceptual hash - * Uses FFT to get - * - * Created by dev on 1/22/14. - */ + * Complex perceptual hash + * Uses FFT to get structural data out of the image, which can represent the perception of similarity + * + * Created by dev on 1/22/14. + */ object PHash extends PerceptualHasher with Logging { - def getHash(imageData: Array[Array[Int]]): Long = { - //convert the imageData into a FloatArray - val width = imageData.length - val height = imageData(0).length - trace(s"Starting with image of ${height}x$width for PHash") + def getHash(imageData: Array[Array[Int]]): Long = { + //convert the imageData into a FloatArray + val width = imageData.length + val height = imageData(0).length + trace(s"Starting with image of ${height}x$width for PHash") - val imageDataFloat: Array[Array[Float]] = Array.ofDim[Float](height, width) - for (row <- 0 until height) { - for (col <- 0 until width) { - imageDataFloat(row)(col) = imageData(row)(col).toFloat - } - } - //debug("Copied image data to float array for transform") - //debug(s"\n${imageDataFloat.deep.mkString("\n")}") + val imageDataFloat: Array[Array[Float]] = Array.ofDim[Float](height, width) + for (row <- 0 until height) { + for (col <- 0 until width) { + imageDataFloat(row)(col) = imageData(row)(col).toFloat + } + } - //perform transform on the data - val dct: FloatDCT_2D = new FloatDCT_2D(height, width) - dct.forward(imageDataFloat, true) - //debug("Converted image data into DCT") - //debug(s"\n${imageDataFloat.deep.mkString("\n")}") + //perform transform on the data + val dct: FloatDCT_2D = new FloatDCT_2D(height, width) + dct.forward(imageDataFloat, true) - //extract the DCT data - val dctDataWidth: Int = width / 4 - val dctDataHeight: Int = height / 4 + //extract the DCT data + val dctDataWidth: Int = width / 4 + val dctDataHeight: Int = height / 4 - //calculate the mean - var total = 0.0f - for (row <- 0 until dctDataHeight) { - for (col <- 0 until dctDataWidth) { - total += imageDataFloat(row)(col) - } - } - val mean = total / (dctDataHeight * dctDataWidth) - //debug(s"Calculated mean as $mean from ${total}/${dctDataHeight * dctDataWidth}") + //calculate the mean + var total = 0.0f + for (row <- 0 until dctDataHeight) { + for (col <- 0 until dctDataWidth) { + total += imageDataFloat(row)(col) + } + } + val mean = total / (dctDataHeight * dctDataWidth) + //debug(s"Calculated mean as $mean from ${total}/${dctDataHeight * dctDataWidth}") - //calculate the hash - var hash = 0L - for (row <- 0 until dctDataHeight by 2) { - //process each column - for (col <- 0 until dctDataWidth by 1) { - hash <<= 1 - val pixel = imageDataFloat(row)(col) - //If the current pixel is at or above the mean, store it as a one, else store it as a zero - if (pixel >= mean) hash |= 1 else hash |= 0 - } + /* + For each pixel, if it is at or above the mean, store it as a one, else store it as a zero + */ + var hash = 0L + for (row <- 0 until dctDataHeight by 2) { + for (col <- 0 until dctDataWidth by 1) { + hash <<= 1 + val pixel = imageDataFloat(row)(col) + // + if (pixel >= mean) hash |= 1 else hash |= 0 + } - if ((row + 1) < dctDataWidth) { - val nextRow = row + 1 - //process each column - for (col <- (dctDataWidth - 1) to 0 by -1) { - hash <<= 1 - val pixel = imageDataFloat(nextRow)(col) - if (pixel >= mean) hash |= 1 else hash |= 0 - } - } - } - debug(s"Computed PHash: $hash from ${dctDataWidth * dctDataHeight} pixels") - hash - } -} + if ((row + 1) < dctDataWidth) { + val nextRow = row + 1 + //process each column + for (col <- (dctDataWidth - 1) to 0 by -1) { + hash <<= 1 + val pixel = imageDataFloat(nextRow)(col) + if (pixel >= mean) hash |= 1 else hash |= 0 + } + } + } + debug(s"Computed PHash: $hash from ${dctDataWidth * dctDataHeight} pixels") + hash + } +} \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/type/PerceptualHasher.scala b/hash/src/main/scala/com/sothr/imagetools/hash/type/PerceptualHasher.scala index 9af8b3a..ee168fa 100644 --- a/hash/src/main/scala/com/sothr/imagetools/hash/type/PerceptualHasher.scala +++ b/hash/src/main/scala/com/sothr/imagetools/hash/type/PerceptualHasher.scala @@ -1,12 +1,12 @@ package com.sothr.imagetools.hash.`type` /** - * Interface for perceptual hashing - * - * Created by drew on 1/22/14. - */ + * Interface for perceptual hashing + * + * Created by drew on 1/22/14. + */ trait PerceptualHasher { - def getHash(imageData: Array[Array[Int]]): Long + def getHash(imageData: Array[Array[Int]]): Long -} +} \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/util/HammingUtil.scala b/hash/src/main/scala/com/sothr/imagetools/hash/util/HammingUtil.scala index 2651471..c8023d5 100644 --- a/hash/src/main/scala/com/sothr/imagetools/hash/util/HammingUtil.scala +++ b/hash/src/main/scala/com/sothr/imagetools/hash/util/HammingUtil.scala @@ -2,17 +2,16 @@ package com.sothr.imagetools.hash.util object HammingUtil { - /** - * Calculate the hamming distance between two longs - * - * @param hash1 The first hash to compare - * @param hash2 The second hash to compare - * @return - */ - def getDistance(hash1: Long, hash2: Long): Int = { - //The XOR of hash1 and hash2 is converted to a binary string - //then the number of '1's is counted. This is the hamming distance - (hash1 ^ hash2).toBinaryString.count(_ == '1') - } - + /** + * Calculate the hamming distance between two longs + * + * @param hash1 The first hash to compare + * @param hash2 The second hash to compare + * @return + */ + def getDistance(hash1: Long, hash2: Long): Int = { + //The XOR of hash1 and hash2 is converted to a binary string + //then the number of '1's is counted. This is the hamming distance + (hash1 ^ hash2).toBinaryString.count(_ == '1') + } } \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/util/ImageUtil.scala b/hash/src/main/scala/com/sothr/imagetools/hash/util/ImageUtil.scala index 38f7fd3..e63a285 100644 --- a/hash/src/main/scala/com/sothr/imagetools/hash/util/ImageUtil.scala +++ b/hash/src/main/scala/com/sothr/imagetools/hash/util/ImageUtil.scala @@ -7,111 +7,110 @@ import net.coobird.thumbnailator.Thumbnails object ImageUtil extends Logging { - /** - * Quickly convert an image to grayscale - * - * @param image image to convert to greyscale - * @return - */ - def convertToGray(image: BufferedImage): BufferedImage = { - val grayImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_BYTE_GRAY) + /** + * Quickly convert an image to grayscale + * + * @param image image to convert to greyscale + * @return + */ + def convertToGray(image: BufferedImage): BufferedImage = { + val grayImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_BYTE_GRAY) - val op = new ColorConvertOp( - image.getColorModel.getColorSpace, - grayImage.getColorModel.getColorSpace, - null - ) + val op = new ColorConvertOp( + image.getColorModel.getColorSpace, + grayImage.getColorModel.getColorSpace, + null + ) - op.filter(image, grayImage) - } + op.filter(image, grayImage) + } - def resize(image: BufferedImage, size: Int, forced: Boolean = false): BufferedImage = { - //debug(s"Resizing an image to size: ${size}x${size} forced: $forced") - if (forced) { - Thumbnails.of(image).forceSize(size, size).asBufferedImage - } else { - Thumbnails.of(image).size(size, size).asBufferedImage - } - } + def resize(image: BufferedImage, size: Int, forced: Boolean = false): BufferedImage = { + //debug(s"Resizing an image to size: ${size}x${size} forced: $forced") + if (forced) { + Thumbnails.of(image).forceSize(size, size).asBufferedImage + } else { + Thumbnails.of(image).size(size, size).asBufferedImage + } + } - /** - * Get the raw data for an image - * Convert a buffered image into a 2d pixel data array - * - * @param image image to convert without using RGB - * @return - */ - def getImageData(image: BufferedImage): Array[Array[Int]] = { + /** + * Get the raw data for an image + * Convert a buffered image into a 2d pixel data array + * + * @param image image to convert without using RGB + * @return + */ + def getImageData(image: BufferedImage): Array[Array[Int]] = { - val pixels = image.getRaster.getDataBuffer.asInstanceOf[DataBufferByte].getData - val numPixels = pixels.length - val width = image.getWidth - val height = image.getHeight - val isSingleChannel = if (numPixels == (width * height)) true else false - val hasAlphaChannel = image.getAlphaRaster != null - //debug(s"Converting image to 2d. width:$width height:$height") + val pixels = image.getRaster.getDataBuffer.asInstanceOf[DataBufferByte].getData + val numPixels = pixels.length + val width = image.getWidth + val height = image.getHeight + val isSingleChannel = if (numPixels == (width * height)) true else false + val hasAlphaChannel = image.getAlphaRaster != null + //debug(s"Converting image to 2d. width:$width height:$height") - val result = Array.ofDim[Int](height, width) - if (isSingleChannel) { - //debug(s"Processing Single Channel Image") - val pixelLength = 1 - var row = 0 - var col = 0 - //debug(s"Processing pixels 0 until $numPixels by $pixelLength") - for (pixel <- 0 until numPixels by pixelLength) { - //debug(s"Processing pixel: $pixel/${numPixels - 1}") - val argb: Int = pixels(pixel).toInt //singleChannel - //debug(s"Pixel data: $argb") - result(row)(col) = argb - col += 1 - if (col == width) { - col = 0 - row += 1 - } - } - } - else if (hasAlphaChannel) { - //debug(s"Processing Four Channel Image") - val pixelLength = 4 - var row = 0 - var col = 0 - //debug(s"Processing pixels 0 until $numPixels by $pixelLength") - for (pixel <- 0 until numPixels by pixelLength) { - //debug(s"Processing pixel: $pixel/${numPixels - 1}") - var argb: Int = 0 - argb += pixels(pixel).toInt << 24 //alpha - argb += pixels(pixel + 1).toInt //blue - argb += pixels(pixel + 2).toInt << 8 //green - argb += pixels(pixel + 3).toInt << 16 //red - result(row)(col) = argb - col += 1 - if (col == width) { - col = 0 - row += 1 - } - } - } else { - //debug(s"Processing Three Channel Image") - val pixelLength = 3 - var row = 0 - var col = 0 - //debug(s"Processing pixels 0 until $numPixels by $pixelLength") - for (pixel <- 0 until numPixels by pixelLength) { - //debug(s"Processing pixel: $pixel/${numPixels - 1}") - var argb: Int = 0 - argb += -16777216; // 255 alpha - argb += pixels(pixel).toInt //blue - argb += pixels(pixel + 1).toInt << 8 //green - argb += pixels(pixel + 2).toInt << 16 //red - result(row)(col) = argb - col += 1 - if (col == width) { - col = 0 - row += 1 - } - } - } - result - } - -} + val result = Array.ofDim[Int](height, width) + if (isSingleChannel) { + //debug(s"Processing Single Channel Image") + val pixelLength = 1 + var row = 0 + var col = 0 + //debug(s"Processing pixels 0 until $numPixels by $pixelLength") + for (pixel <- 0 until numPixels by pixelLength) { + //debug(s"Processing pixel: $pixel/${numPixels - 1}") + val argb: Int = pixels(pixel).toInt //singleChannel + //debug(s"Pixel data: $argb") + result(row)(col) = argb + col += 1 + if (col == width) { + col = 0 + row += 1 + } + } + } + else if (hasAlphaChannel) { + //debug(s"Processing Four Channel Image") + val pixelLength = 4 + var row = 0 + var col = 0 + //debug(s"Processing pixels 0 until $numPixels by $pixelLength") + for (pixel <- 0 until numPixels by pixelLength) { + //debug(s"Processing pixel: $pixel/${numPixels - 1}") + var argb: Int = 0 + argb += pixels(pixel).toInt << 24 //alpha + argb += pixels(pixel + 1).toInt //blue + argb += pixels(pixel + 2).toInt << 8 //green + argb += pixels(pixel + 3).toInt << 16 //red + result(row)(col) = argb + col += 1 + if (col == width) { + col = 0 + row += 1 + } + } + } else { + //debug(s"Processing Three Channel Image") + val pixelLength = 3 + var row = 0 + var col = 0 + //debug(s"Processing pixels 0 until $numPixels by $pixelLength") + for (pixel <- 0 until numPixels by pixelLength) { + //debug(s"Processing pixel: $pixel/${numPixels - 1}") + var argb: Int = 0 + argb += -16777216; // 255 alpha + argb += pixels(pixel).toInt //blue + argb += pixels(pixel + 1).toInt << 8 //green + argb += pixels(pixel + 2).toInt << 16 //red + result(row)(col) = argb + col += 1 + if (col == width) { + col = 0 + row += 1 + } + } + } + result + } +} \ No newline at end of file diff --git a/hash/src/main/scala/com/sothr/imagetools/hash/util/TimingUtil.scala b/hash/src/main/scala/com/sothr/imagetools/hash/util/TimingUtil.scala index d566032..78a8efe 100644 --- a/hash/src/main/scala/com/sothr/imagetools/hash/util/TimingUtil.scala +++ b/hash/src/main/scala/com/sothr/imagetools/hash/util/TimingUtil.scala @@ -4,32 +4,31 @@ import grizzled.slf4j.Logging trait TimingUtil extends Logging { - def time[R](block: => R): R = { - val t0 = System.currentTimeMillis - val result = block // call-by-name - val t1 = System.currentTimeMillis - debug("Elapsed time: " + (t1 - t0) + "ms") - result - } + def time[R](block: => R): R = { + val t0 = System.currentTimeMillis + val result = block // call-by-name + val t1 = System.currentTimeMillis + debug("Elapsed time: " + (t1 - t0) + "ms") + result + } - def getTime[R](block: => R): Long = { - val t0 = System.currentTimeMillis - val result = block // call-by-name - val t1 = System.currentTimeMillis - debug("Elapsed time: " + (t1 - t0) + "ms") - t1 - t0 - } + def getTime[R](block: => R): Long = { + val t0 = System.currentTimeMillis + val result = block // call-by-name + val t1 = System.currentTimeMillis + debug("Elapsed time: " + (t1 - t0) + "ms") + t1 - t0 + } - def getMean(times: Long*): Long = { - getMean(times.toArray[Long]) - } - - def getMean(times: Array[Long]): Long = { - var ag = 0L - for (i <- times.indices) { - ag += times(i) - } - ag / times.length - } + def getMean(times: Long*): Long = { + getMean(times.toArray[Long]) + } + def getMean(times: Array[Long]): Long = { + var ag = 0L + for (i <- times.indices) { + ag += times(i) + } + ag / times.length + } } \ No newline at end of file diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/BaseTest.scala b/hash/src/test/scala/com/sothr/imagetools/hash/BaseTest.scala deleted file mode 100644 index 11e7b7c..0000000 --- a/hash/src/test/scala/com/sothr/imagetools/hash/BaseTest.scala +++ /dev/null @@ -1,9 +0,0 @@ -package com.sothr.imagetools.hash - -import com.sothr.imagetools.hash.util.TimingUtil -import grizzled.slf4j.Logging -import org.scalatest.{BeforeAndAfter, FunSuite, Inside, Inspectors, Matchers, OptionValues} - -abstract class BaseTest extends FunSuite with Matchers with OptionValues with Inside with Inspectors with BeforeAndAfter with Logging with TimingUtil { - -} diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceAHashTest.scala b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceAHashTest.scala new file mode 100644 index 0000000..da7ee71 --- /dev/null +++ b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceAHashTest.scala @@ -0,0 +1,93 @@ +package com.sothr.imagetools.hash + +import java.io.File +import javax.imageio.ImageIO + +import scala.collection.mutable + +class HashServiceAHashTest extends HashServiceBaseTest { + + def ahashTestCase(filePath: String): Long = { + val sample = new File(filePath) + val image = ImageIO.read(sample) + HashService.getAhash(ahashSetting, image) + } + + test("Calculate AHash Large Sample Image 1") { + debug("Starting 'Calculate AHash Large Sample Image 1' test") + val sample = new File(LargeSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getAhash(ahashSetting, image) + debug(s"Testing that $hash = 36070299219713907L") + assert(hash == 36070299219713907L) + } + + test("Calculate AHash Medium Sample Image 1") { + debug("Starting 'Calculate AHash Medium Sample Image 1' test") + val sample = new File(MediumSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getAhash(ahashSetting, image) + debug(s"Testing that $hash = 36070299219713907L") + assert(hash == 36070299219713907L) + } + + test("Calculate AHash Small Sample Image 1") { + debug("Starting 'Calculate AHash Small Sample Image 1' test") + val sample = new File(SmallSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getAhash(ahashSetting, image) + debug(s"Testing that $hash = 36070299219713907L") + assert(hash == 36070299219713907L) + } + + test("AHash Of Large, Medium, And Small Sample 1 Must Be Similar") { + val largeHash = ahashTestCase(LargeSampleImage1) + val mediumHash = ahashTestCase(MediumSampleImage1) + val smallHash = ahashTestCase(SmallSampleImage1) + assert(HashService.areHashSimilar(ahashSetting, largeHash, mediumHash)) + assert(HashService.areHashSimilar(ahashSetting, largeHash, smallHash)) + assert(HashService.areHashSimilar(ahashSetting, mediumHash, smallHash)) + } + + test("Benchmark AHash") { + info("Benchmarking AHash") + info("AHash Large Image 3684x2736") + val time = new mutable.MutableList[Long]() + for (_ <- 0 until benchmarkRuns) { + time += getTime { + ahashTestCase(LargeSampleImage1) + } + } + + val largeMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for large was: $largeMean ms") + time.clear() + info("AHash Medium Image 1824x1368") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + ahashTestCase(MediumSampleImage1) + } + } + + val mediumMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") + time.clear() + info("AHash Small Image 912x684") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + ahashTestCase(SmallSampleImage1) + } + } + + val smallMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for small was: $smallMean ms") + time.clear() + assert(true) + } +} \ No newline at end of file diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceBaseTest.scala b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceBaseTest.scala new file mode 100644 index 0000000..16967b4 --- /dev/null +++ b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceBaseTest.scala @@ -0,0 +1,21 @@ +package com.sothr.imagetools.hash + +import com.sothr.imagetools.hash.util.TimingUtil +import grizzled.slf4j.Logging +import org.scalatest.{BeforeAndAfter, FunSuite, Inside, Inspectors, Matchers, OptionValues} + +abstract class HashServiceBaseTest extends FunSuite with Matchers with OptionValues with Inside with Inspectors with BeforeAndAfter with Logging with TimingUtil { + + // Define the number of runs the benchmarking tests should use + val benchmarkRuns = 10 + + // The Standard Parameters Used To Benchmark Perceptual hashes + val ahashSetting = new HashSetting("AHash", true, 8, 8, 0.75f) + val dhashSetting = new HashSetting("DHash", true, 8, 8, 0.85f) + val phashSetting = new HashSetting("PHash", true, 32, 8, 1.0f) + + // Base Test Images + val LargeSampleImage1 = "sample/sample_01_large.jpg" + val MediumSampleImage1 = "sample/sample_01_medium.jpg" + val SmallSampleImage1 = "sample/sample_01_small.jpg" +} \ No newline at end of file diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceDHashTest.scala b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceDHashTest.scala new file mode 100644 index 0000000..1d62ade --- /dev/null +++ b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceDHashTest.scala @@ -0,0 +1,110 @@ +package com.sothr.imagetools.hash + +import java.io.File +import javax.imageio.ImageIO + +import com.sothr.imagetools.hash.`type`.DHash + +import scala.collection.mutable + +class HashServiceDHashTest extends HashServiceBaseTest { + + def dhashTestCase(filePath: String): Long = { + val sample = new File(filePath) + val image = ImageIO.read(sample) + HashService.getDhash(dhashSetting, image) + } + + test("Confirm Largest DHash Output ") { + val testData: Array[Array[Int]] = Array( + Array(1, 2, 3, 4, 5, 6, 7, 8), + Array(16, 15, 14, 13, 12, 11, 10, 9), + Array(17, 18, 19, 20, 21, 22, 23, 24), + Array(32, 31, 30, 29, 28, 27, 26, 25), + Array(33, 34, 35, 36, 37, 38, 39, 40), + Array(48, 47, 46, 45, 44, 43, 42, 41), + Array(49, 50, 51, 52, 53, 54, 55, 56), + Array(64, 63, 62, 61, 60, 59, 58, 57)) + val hash = DHash.getHash(testData) + debug(s"Hash of test array: $hash") + assert(hash == Long.MaxValue) + } + + test("Calculate DHash Large Sample Image 1") { + debug("Starting 'Calculate DHash Large Sample Image 1' test") + val sample = new File(LargeSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getDhash(dhashSetting, image) + debug(s"Testing that $hash = 4004374827879799635L") + assert(hash == 4004374827879799635L) + } + + test("Calculate DHash Medium Sample Image 1") { + debug("Starting 'Calculate DHash Medium Sample Image 1' test") + val sample = new File(MediumSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getDhash(dhashSetting, image) + debug(s"Testing that $hash = 4004374827879799635L") + assert(hash == 4004374827879799635L) + } + + test("Calculate DHash Small Sample Image 1") { + debug("Starting 'Calculate DHash Small Sample Image 1' test") + val sample = new File(SmallSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getDhash(dhashSetting, image) + debug(s"Testing that $hash = 4004383623972821843L") + assert(hash == 4004383623972821843L) + } + + test("DHash Of Large, Medium, And Small Sample 1 Must Be Similar") { + val largeHash = dhashTestCase(LargeSampleImage1) + val mediumHash = dhashTestCase(MediumSampleImage1) + val smallHash = dhashTestCase(SmallSampleImage1) + assert(HashService.areHashSimilar(dhashSetting, largeHash, mediumHash)) + assert(HashService.areHashSimilar(dhashSetting, largeHash, smallHash)) + assert(HashService.areHashSimilar(dhashSetting, mediumHash, smallHash)) + } + + test("Benchmark DHash") { + info("Benchmarking DHash") + info("DHash Large Image 3684x2736") + val time = new mutable.MutableList[Long]() + for (_ <- 0 until benchmarkRuns) { + time += getTime { + dhashTestCase(LargeSampleImage1) + } + } + + val largeMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for large was: $largeMean ms") + time.clear() + info("DHash Medium Image 1824x1368") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + dhashTestCase(MediumSampleImage1) + } + } + + val mediumMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") + time.clear() + info("DHash Small Image 912x684") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + dhashTestCase(SmallSampleImage1) + } + } + + val smallMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for small was: $smallMean ms") + time.clear() + assert(true) + } +} \ No newline at end of file diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceMD5Test.scala b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceMD5Test.scala new file mode 100644 index 0000000..f95a9e2 --- /dev/null +++ b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceMD5Test.scala @@ -0,0 +1,67 @@ +package com.sothr.imagetools.hash + +import scala.collection.mutable + +class HashServiceMD5Test extends HashServiceBaseTest { + + def md5TestCase(filePath: String): String = { + HashService.getMD5(filePath) + } + + test("Calculate MD5 Large Sample Image 1") { + debug("Starting 'Calculate MD5 Large Sample Image 1' test") + val hash = HashService.getMD5(LargeSampleImage1) + debug(s"Testing that $hash = 3fbccfd5faf3f991435b827ee5961862") + assert(hash == "3fbccfd5faf3f991435b827ee5961862") + } + + test("Calculate MD5 Medium Sample Image 1") { + debug("Starting 'Calculate MD5 Medium Sample Image 1' test") + val hash = HashService.getMD5(MediumSampleImage1) + debug(s"Testing that $hash = a95e2cc4610307eb957e9c812429c53e") + assert(hash == "a95e2cc4610307eb957e9c812429c53e") + } + + test("Calculate MD5 Small Sample Image 1") { + debug("Starting 'Calculate MD5 Small Sample Image 1' test") + val hash = HashService.getMD5(SmallSampleImage1) + debug(s"Testing that $hash = b137131bd55896c747286e4d247b845e") + assert(hash == "b137131bd55896c747286e4d247b845e") + } + + test("Benchmark MD5") { + info("Benchmarking MD5") + info("MD5 Large Image 3684x2736") + val time = new mutable.MutableList[Long]() + for (_ <- 0 until benchmarkRuns) { + time += getTime { + md5TestCase(LargeSampleImage1) + } + } + + val largeMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for large was: $largeMean ms") + time.clear() + info("MD5 Medium Image 1824x1368") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + md5TestCase(MediumSampleImage1) + } + } + + val mediumMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") + time.clear() + info("MD5 Small Image 912x684") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + md5TestCase(SmallSampleImage1) + } + } + + val smallMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for small was: $smallMean ms") + time.clear() + assert(true) + } +} \ No newline at end of file diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/HashServicePHashTest.scala b/hash/src/test/scala/com/sothr/imagetools/hash/HashServicePHashTest.scala new file mode 100644 index 0000000..3650eab --- /dev/null +++ b/hash/src/test/scala/com/sothr/imagetools/hash/HashServicePHashTest.scala @@ -0,0 +1,93 @@ +package com.sothr.imagetools.hash + +import java.io.File +import javax.imageio.ImageIO + +import scala.collection.mutable + +class HashServicePHashTest extends HashServiceBaseTest { + + def phashTestCase(filePath: String): Long = { + val sample = new File(filePath) + val image = ImageIO.read(sample) + HashService.getPhash(phashSetting, image) + } + + test("Calculate PHash Large Sample Image 1") { + debug("Starting 'Calculate PHash Large Sample Image 1' test") + val sample = new File(LargeSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getPhash(phashSetting, image) + debug(s"Testing that $hash = -9154554603604154117L") + assert(hash == -9154554603604154117L) + } + + test("Calculate PHash Medium Sample Image 1") { + debug("Starting 'Calculate PHash Medium Sample Image 1' test") + val sample = new File(MediumSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getPhash(phashSetting, image) + debug(s"Testing that $hash = -9154589787976242949L") + assert(hash == -9154589787976242949L) + } + + test("Calculate PHash Small Sample Image 1") { + debug("Starting 'Calculate PHash Small Sample Image 1' test") + val sample = new File(SmallSampleImage1) + debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") + val image = ImageIO.read(sample) + debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") + val hash = HashService.getPhash(phashSetting, image) + debug(s"Testing that $hash = -9154589787976242949L") + assert(hash == -9154589787976242949L) + } + + test("PHash Of Large, Medium, And Small Sample 1 Must Be Similar") { + val largeHash = phashTestCase(LargeSampleImage1) + val mediumHash = phashTestCase(MediumSampleImage1) + val smallHash = phashTestCase(SmallSampleImage1) + assert(HashService.areHashSimilar(phashSetting, largeHash, mediumHash)) + assert(HashService.areHashSimilar(phashSetting, largeHash, smallHash)) + assert(HashService.areHashSimilar(phashSetting, mediumHash, smallHash)) + } + + test("Benchmark PHash") { + info("Benchmarking PHash") + info("PHash Large Image 3684x2736") + val time = new mutable.MutableList[Long]() + for (_ <- 0 until benchmarkRuns) { + time += getTime { + phashTestCase(LargeSampleImage1) + } + } + + val largeMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for large was: $largeMean ms") + time.clear() + info("PHash Medium Image 1824x1368") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + phashTestCase(MediumSampleImage1) + } + } + + val mediumMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") + time.clear() + info("PHash Small Image 912x684") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + phashTestCase(SmallSampleImage1) + } + } + + val smallMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for small was: $smallMean ms") + time.clear() + assert(true) + } +} \ No newline at end of file diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceSHA1Test.scala b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceSHA1Test.scala new file mode 100644 index 0000000..e44a063 --- /dev/null +++ b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceSHA1Test.scala @@ -0,0 +1,67 @@ +package com.sothr.imagetools.hash + +import scala.collection.mutable + +class HashServiceSHA1Test extends HashServiceBaseTest { + + def sha1TestCase(filePath: String): String = { + HashService.getSHA1(filePath) + } + + test("Calculate SHA1 Large Sample Image 1") { + debug("Starting 'Calculate SHA1 Large Sample Image 1' test") + val hash = HashService.getSHA1(LargeSampleImage1) + debug(s"Testing that $hash = 4beb6f2d852b75a313863916a1803ebad13a3196") + assert(hash == "4beb6f2d852b75a313863916a1803ebad13a3196") + } + + test("Calculate SHA1 Medium Sample Image 1") { + debug("Starting 'Calculate SHA1 Medium Sample Image 1' test") + val hash = HashService.getSHA1(MediumSampleImage1) + debug(s"Testing that $hash = edc718ce8e3556a39592ffdc214d0f636529be9f") + assert(hash == "edc718ce8e3556a39592ffdc214d0f636529be9f") + } + + test("Calculate SHA1 Small Sample Image 1") { + debug("Starting 'Calculate SHA1 Small Sample Image 1' test") + val hash = HashService.getSHA1(SmallSampleImage1) + debug(s"Testing that $hash = 1a91d2b5327f0aad258419f76b87d4c0bc343443") + assert(hash == "1a91d2b5327f0aad258419f76b87d4c0bc343443") + } + + test("Benchmark SHA1") { + info("Benchmarking SHA1") + info("SHA1 Large Image 3684x2736") + val time = new mutable.MutableList[Long]() + for (_ <- 0 until benchmarkRuns) { + time += getTime { + sha1TestCase(LargeSampleImage1) + } + } + + val largeMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for large was: $largeMean ms") + time.clear() + info("SHA1 Medium Image 1824x1368") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + sha1TestCase(MediumSampleImage1) + } + } + + val mediumMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") + time.clear() + info("SHA1 Small Image 912x684") + for (_ <- 0 until benchmarkRuns) { + time += getTime { + sha1TestCase(SmallSampleImage1) + } + } + + val smallMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for small was: $smallMean ms") + time.clear() + assert(true) + } +} \ No newline at end of file diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala index c295e59..ceda0e1 100644 --- a/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala +++ b/hash/src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala @@ -1,10 +1,12 @@ package com.sothr.imagetools.hash import java.io.File +import java.util.NoSuchElementException import javax.imageio.ImageIO -import com.sothr.imagetools.hash.`type`.DHash -import com.sothr.imagetools.hash.dto.{HashSettingDTO, ImageHashDTO} +import com.sothr.imagetools.hash.HashService.getHash +import com.sothr.imagetools.hash.`type`.{AHash, DHash, PHash} +import com.sothr.imagetools.hash.util.ImageUtil import scala.collection.mutable @@ -13,453 +15,127 @@ import scala.collection.mutable * * Created by dev on 1/23/14. */ -class HashServiceTest extends BaseTest { - - // Define the number of runs the benchmarking tests should use - val benchmarkRuns = 10 - val ahashSetting = new HashSettingDTO(true, 8, 8, 0.75f) - val dhashSetting = new HashSettingDTO(true, 8, 8, 0.85f) - val phashSetting = new HashSettingDTO(true, 32, 8, 1.0f) - - def dhashTestCase(filePath: String): Long = { - val sample = new File(filePath) - val image = ImageIO.read(sample) - HashService.getDhash(dhashSetting, image) - } - - test("Benchmark DHash") { - info("Benchmarking DHash") - info("DHash Large Image 3684x2736") - val time = new mutable.MutableList[Long]() - for (_ <- 0 until benchmarkRuns) { - time += getTime { - dhashTestCase(TestParams.LargeSampleImage1) - } - } - val largeMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for large was: $largeMean ms") - time.clear() - info("DHash Medium Image 1824x1368") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - dhashTestCase(TestParams.MediumSampleImage1) - } - } - val mediumMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") - time.clear() - info("DHash Small Image 912x684") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - dhashTestCase(TestParams.SmallSampleImage1) - } - } - val smallMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for small was: $smallMean ms") - time.clear() - assert(true) - } - - test("Confirm Largest DHash Output ") { - val testData: Array[Array[Int]] = Array( - Array(1, 2, 3, 4, 5, 6, 7, 8), - Array(16, 15, 14, 13, 12, 11, 10, 9), - Array(17, 18, 19, 20, 21, 22, 23, 24), - Array(32, 31, 30, 29, 28, 27, 26, 25), - Array(33, 34, 35, 36, 37, 38, 39, 40), - Array(48, 47, 46, 45, 44, 43, 42, 41), - Array(49, 50, 51, 52, 53, 54, 55, 56), - Array(64, 63, 62, 61, 60, 59, 58, 57)) - val hash = DHash.getHash(testData) - debug(s"Hash of test array: $hash") - assert(hash == Long.MaxValue) - } - - test("Calculate DHash Large Sample Image 1") { - debug("Starting 'Calculate DHash Large Sample Image 1' test") - val sample = new File(TestParams.LargeSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getDhash(dhashSetting, image) - debug(s"Testing that $hash = 4004374827879799635L") - assert(hash == 4004374827879799635L) - } - - test("Calculate DHash Medium Sample Image 1") { - debug("Starting 'Calculate DHash Medium Sample Image 1' test") - val sample = new File(TestParams.MediumSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getDhash(dhashSetting, image) - debug(s"Testing that $hash = 4004374827879799635L") - assert(hash == 4004374827879799635L) - } - - test("Calculate DHash Small Sample Image 1") { - debug("Starting 'Calculate DHash Small Sample Image 1' test") - val sample = new File(TestParams.SmallSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getDhash(dhashSetting, image) - debug(s"Testing that $hash = 4004383623972821843L") - assert(hash == 4004383623972821843L) - } - - test("DHash Of Large, Medium, And Small Sample 1 Must Be Similar") { - val largeHash = dhashTestCase(TestParams.LargeSampleImage1) - val mediumHash = dhashTestCase(TestParams.MediumSampleImage1) - val smallHash = dhashTestCase(TestParams.SmallSampleImage1) - assert(HashService.areHashSimilar(dhashSetting, largeHash, mediumHash)) - assert(HashService.areHashSimilar(dhashSetting, largeHash, smallHash)) - assert(HashService.areHashSimilar(dhashSetting, mediumHash, smallHash)) - } - - def ahashTestCase(filePath: String): Long = { - val sample = new File(filePath) - val image = ImageIO.read(sample) - HashService.getAhash(ahashSetting, image) - } - - test("Benchmark AHash") { - info("Benchmarking AHash") - info("AHash Large Image 3684x2736") - val time = new mutable.MutableList[Long]() - for (_ <- 0 until benchmarkRuns) { - time += getTime { - ahashTestCase(TestParams.LargeSampleImage1) - } - } - val largeMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for large was: $largeMean ms") - time.clear() - info("AHash Medium Image 1824x1368") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - ahashTestCase(TestParams.MediumSampleImage1) - } - } - val mediumMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") - time.clear() - info("AHash Small Image 912x684") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - ahashTestCase(TestParams.SmallSampleImage1) - } - } - val smallMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for small was: $smallMean ms") - time.clear() - assert(true) - } - - test("Calculate AHash Large Sample Image 1") { - debug("Starting 'Calculate AHash Large Sample Image 1' test") - val sample = new File(TestParams.LargeSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getAhash(ahashSetting, image) - debug(s"Testing that $hash = 36070299219713907L") - assert(hash == 36070299219713907L) - } - - test("Calculate AHash Medium Sample Image 1") { - debug("Starting 'Calculate AHash Medium Sample Image 1' test") - val sample = new File(TestParams.MediumSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getAhash(ahashSetting, image) - debug(s"Testing that $hash = 36070299219713907L") - assert(hash == 36070299219713907L) - } - - test("Calculate AHash Small Sample Image 1") { - debug("Starting 'Calculate AHash Small Sample Image 1' test") - val sample = new File(TestParams.SmallSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getAhash(ahashSetting, image) - debug(s"Testing that $hash = 36070299219713907L") - assert(hash == 36070299219713907L) - } - - test("AHash Of Large, Medium, And Small Sample 1 Must Be Similar") { - val largeHash = ahashTestCase(TestParams.LargeSampleImage1) - val mediumHash = ahashTestCase(TestParams.MediumSampleImage1) - val smallHash = ahashTestCase(TestParams.SmallSampleImage1) - assert(HashService.areHashSimilar(ahashSetting, largeHash, mediumHash)) - assert(HashService.areHashSimilar(ahashSetting, largeHash, smallHash)) - assert(HashService.areHashSimilar(ahashSetting, mediumHash, smallHash)) - } - - def phashTestCase(filePath: String): Long = { - val sample = new File(filePath) - val image = ImageIO.read(sample) - HashService.getPhash(phashSetting, image) - } - - test("Benchmark PHash") { - info("Benchmarking PHash") - info("PHash Large Image 3684x2736") - val time = new mutable.MutableList[Long]() - for (_ <- 0 until benchmarkRuns) { - time += getTime { - phashTestCase(TestParams.LargeSampleImage1) - } - } - val largeMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for large was: $largeMean ms") - time.clear() - info("PHash Medium Image 1824x1368") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - phashTestCase(TestParams.MediumSampleImage1) - } - } - val mediumMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") - time.clear() - info("PHash Small Image 912x684") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - phashTestCase(TestParams.SmallSampleImage1) - } - } - val smallMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for small was: $smallMean ms") - time.clear() - assert(true) - } - - test("Calculate PHash Large Sample Image 1") { - debug("Starting 'Calculate PHash Large Sample Image 1' test") - val sample = new File(TestParams.LargeSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getPhash(phashSetting, image) - debug(s"Testing that $hash = -9154554603604154117L") - assert(hash == -9154554603604154117L) - } - - test("Calculate PHash Medium Sample Image 1") { - debug("Starting 'Calculate PHash Medium Sample Image 1' test") - val sample = new File(TestParams.MediumSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getPhash(phashSetting, image) - debug(s"Testing that $hash = -9154589787976242949L") - assert(hash == -9154589787976242949L) - } - - test("Calculate PHash Small Sample Image 1") { - debug("Starting 'Calculate PHash Small Sample Image 1' test") - val sample = new File(TestParams.SmallSampleImage1) - debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}") - val image = ImageIO.read(sample) - debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}") - val hash = HashService.getPhash(phashSetting, image) - debug(s"Testing that $hash = -9154589787976242949L") - assert(hash == -9154589787976242949L) - } - - test("PHash Of Large, Medium, And Small Sample 1 Must Be Similar") { - val largeHash = phashTestCase(TestParams.LargeSampleImage1) - val mediumHash = phashTestCase(TestParams.MediumSampleImage1) - val smallHash = phashTestCase(TestParams.SmallSampleImage1) - assert(HashService.areHashSimilar(phashSetting, largeHash, mediumHash)) - assert(HashService.areHashSimilar(phashSetting, largeHash, smallHash)) - assert(HashService.areHashSimilar(phashSetting, mediumHash, smallHash)) - } - - def md5TestCase(filePath: String): String = { - HashService.getMD5(filePath) - } - - test("Benchmark MD5") { - info("Benchmarking MD5") - info("MD5 Large Image 3684x2736") - val time = new mutable.MutableList[Long]() - for (_ <- 0 until benchmarkRuns) { - time += getTime { - md5TestCase(TestParams.LargeSampleImage1) - } - } - val largeMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for large was: $largeMean ms") - time.clear() - info("MD5 Medium Image 1824x1368") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - md5TestCase(TestParams.MediumSampleImage1) - } - } - val mediumMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") - time.clear() - info("MD5 Small Image 912x684") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - md5TestCase(TestParams.SmallSampleImage1) - } - } - val smallMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for small was: $smallMean ms") - time.clear() - assert(true) - } - - test("Calculate MD5 Large Sample Image 1") { - debug("Starting 'Calculate MD5 Large Sample Image 1' test") - val hash = HashService.getMD5(TestParams.LargeSampleImage1) - debug(s"Testing that $hash = 3fbccfd5faf3f991435b827ee5961862") - assert(hash == "3fbccfd5faf3f991435b827ee5961862") - } - - test("Calculate MD5 Medium Sample Image 1") { - debug("Starting 'Calculate MD5 Medium Sample Image 1' test") - val hash = HashService.getMD5(TestParams.MediumSampleImage1) - debug(s"Testing that $hash = a95e2cc4610307eb957e9c812429c53e") - assert(hash == "a95e2cc4610307eb957e9c812429c53e") - } - - test("Calculate MD5 Small Sample Image 1") { - debug("Starting 'Calculate MD5 Small Sample Image 1' test") - val hash = HashService.getMD5(TestParams.SmallSampleImage1) - debug(s"Testing that $hash = b137131bd55896c747286e4d247b845e") - assert(hash == "b137131bd55896c747286e4d247b845e") - } - - def sha1TestCase(filePath: String): String = { - HashService.getSHA1(filePath) - } - - test("Benchmark SHA1") { - info("Benchmarking SHA1") - info("SHA1 Large Image 3684x2736") - val time = new mutable.MutableList[Long]() - for (_ <- 0 until benchmarkRuns) { - time += getTime { - sha1TestCase(TestParams.LargeSampleImage1) - } - } - val largeMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for large was: $largeMean ms") - time.clear() - info("SHA1 Medium Image 1824x1368") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - sha1TestCase(TestParams.MediumSampleImage1) - } - } - val mediumMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") - time.clear() - info("SHA1 Small Image 912x684") - for (_ <- 0 until benchmarkRuns) { - time += getTime { - sha1TestCase(TestParams.SmallSampleImage1) - } - } - val smallMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for small was: $smallMean ms") - time.clear() - assert(true) - } - - test("Calculate SHA1 Large Sample Image 1") { - debug("Starting 'Calculate SHA1 Large Sample Image 1' test") - val hash = HashService.getSHA1(TestParams.LargeSampleImage1) - debug(s"Testing that $hash = 4beb6f2d852b75a313863916a1803ebad13a3196") - assert(hash == "4beb6f2d852b75a313863916a1803ebad13a3196") - } - - test("Calculate SHA1 Medium Sample Image 1") { - debug("Starting 'Calculate SHA1 Medium Sample Image 1' test") - val hash = HashService.getSHA1(TestParams.MediumSampleImage1) - debug(s"Testing that $hash = edc718ce8e3556a39592ffdc214d0f636529be9f") - assert(hash == "edc718ce8e3556a39592ffdc214d0f636529be9f") - } - - test("Calculate SHA1 Small Sample Image 1") { - debug("Starting 'Calculate SHA1 Small Sample Image 1' test") - val hash = HashService.getSHA1(TestParams.SmallSampleImage1) - debug(s"Testing that $hash = 1a91d2b5327f0aad258419f76b87d4c0bc343443") - assert(hash == "1a91d2b5327f0aad258419f76b87d4c0bc343443") - } - - def imageHashTestCase(filePath: String): ImageHashDTO = { - HashService.getImageHashes(ahashSetting, dhashSetting, phashSetting, filePath) - } - - test("Benchmark getImageHashes") { - info("Benchmarking getImageHashes") - info("getImageHashes Large Image 3684x2736") - val time = new mutable.MutableList[Long]() - for (runNum <- 0 until benchmarkRuns) { - time += getTime { - imageHashTestCase(TestParams.LargeSampleImage1) - } - } - val largeMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for large was: $largeMean ms") - time.clear() - info("getImageHashes Medium Image 1824x1368") - for (runNum <- 0 until benchmarkRuns) { - time += getTime { - imageHashTestCase(TestParams.MediumSampleImage1) - } - } - val mediumMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") - time.clear() - info("getImageHashes Small Image 912x684") - for (runNum <- 0 until benchmarkRuns) { - time += getTime { - imageHashTestCase(TestParams.SmallSampleImage1) - } - } - val smallMean = getMean(time.toArray[Long]) - info(s"The mean time of ${time.size} tests for small was: $smallMean ms") - time.clear() - assert(true) - } - - test("ImageHash Of Large, Medium, And Small Sample 1 Must Be Similar") { - val largeHash = imageHashTestCase(TestParams.LargeSampleImage1) - val mediumHash = imageHashTestCase(TestParams.MediumSampleImage1) - val smallHash = imageHashTestCase(TestParams.SmallSampleImage1) - assert(HashService.areImageHashesSimilar(ahashSetting, dhashSetting, phashSetting, largeHash, mediumHash)) - assert(HashService.areImageHashesSimilar(ahashSetting, dhashSetting, phashSetting, largeHash, smallHash)) - assert(HashService.areImageHashesSimilar(ahashSetting, dhashSetting, phashSetting, mediumHash, smallHash)) - } - - test("Calculate ImageHash Large Sample Image 1") { - debug("Starting 'Calculate ImageHash Large Sample Image 1' test") - val hash = HashService.getImageHashes(ahashSetting, dhashSetting, phashSetting, TestParams.LargeSampleImage1) - debug(s"Testing that ${hash.hashCode()} = -812844858") - assert(hash.hashCode == -812844858) - } - - test("Calculate ImageHash Medium Sample Image 1") { - debug("Starting 'Calculate ImageHash Medium Sample Image 1' test") - val hash = HashService.getImageHashes(ahashSetting, dhashSetting, phashSetting, TestParams.MediumSampleImage1) - debug(s"Testing that ${hash.hashCode()} = -812836666") - assert(hash.hashCode == -812836666) - } - - test("Calculate ImageHash Small Sample Image 1") { - debug("Starting 'Calculate ImageHash Small Sample Image 1' test") - val hash = HashService.getImageHashes(ahashSetting, dhashSetting, phashSetting, TestParams.SmallSampleImage1) - debug(s"Testing that ${hash.hashCode()} = -812840762") - assert(hash.hashCode == -812840762) - } - -} +class HashServiceTest extends HashServiceBaseTest { + + def imageHashTestCase(filePath: String): ImageHash = { + HashService.getImageHashes(ahashSetting, dhashSetting, phashSetting, filePath) + } + + test("ImageHash Of Large, Medium, And Small Sample 1 Must Be Similar") { + val largeHash = imageHashTestCase(LargeSampleImage1) + val mediumHash = imageHashTestCase(MediumSampleImage1) + val smallHash = imageHashTestCase(SmallSampleImage1) + assert(HashService.areImageHashesSimilar(ahashSetting, dhashSetting, phashSetting, largeHash, mediumHash)) + assert(HashService.areImageHashesSimilar(ahashSetting, dhashSetting, phashSetting, largeHash, smallHash)) + assert(HashService.areImageHashesSimilar(ahashSetting, dhashSetting, phashSetting, mediumHash, smallHash)) + } + + test("Calculate ImageHash Large Sample Image 1") { + debug("Starting 'Calculate ImageHash Large Sample Image 1' test") + val hash = HashService.getImageHashes(ahashSetting, dhashSetting, phashSetting, LargeSampleImage1) + debug(s"Testing that ${hash.hashCode()} = -812844858") + assert(hash.hashCode == -812844858) + } + + test("Calculate ImageHash Medium Sample Image 1") { + debug("Starting 'Calculate ImageHash Medium Sample Image 1' test") + val hash = HashService.getImageHashes(ahashSetting, dhashSetting, phashSetting, MediumSampleImage1) + debug(s"Testing that ${hash.hashCode()} = -812836666") + assert(hash.hashCode == -812836666) + } + + test("Calculate ImageHash Small Sample Image 1") { + debug("Starting 'Calculate ImageHash Small Sample Image 1' test") + val hash = HashService.getImageHashes(ahashSetting, dhashSetting, phashSetting, SmallSampleImage1) + debug(s"Testing that ${hash.hashCode()} = -812840762") + assert(hash.hashCode == -812840762) + } + + test("Benchmark getImageHashes") { + info("Benchmarking getImageHashes") + info("getImageHashes Large Image 3684x2736") + val time = new mutable.MutableList[Long]() + for (runNum <- 0 until benchmarkRuns) { + time += getTime { + imageHashTestCase(LargeSampleImage1) + } + } + + val largeMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for large was: $largeMean ms") + time.clear() + info("getImageHashes Medium Image 1824x1368") + for (runNum <- 0 until benchmarkRuns) { + time += getTime { + imageHashTestCase(MediumSampleImage1) + } + } + + val mediumMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for medium was: $mediumMean ms") + time.clear() + info("getImageHashes Small Image 912x684") + for (runNum <- 0 until benchmarkRuns) { + time += getTime { + imageHashTestCase(SmallSampleImage1) + } + } + + val smallMean = getMean(time.toArray[Long]) + info(s"The mean time of ${time.size} tests for small was: $smallMean ms") + time.clear() + assert(true) + } + + test("GetPrecisionSet - Empty Precision Set") { + val emptyPrecisionSet = HashService.getPrecisionSet( + new HashSetting("Test1", false, 8, 0, 0), + new HashSetting("Test2", false, 16, 0, 0), + new HashSetting("Test3", false, 32, 0, 0)) + assert(emptyPrecisionSet.isEmpty) + } + + test("GetPrecisionSet - Overlapping Precision Set") { + val precisionSet = HashService.getPrecisionSet( + new HashSetting("Test1", true, 8, 0, 0), + new HashSetting("Test2", true, 8, 0, 0), + new HashSetting("Test3", true, 16, 0, 0)) + assert(precisionSet.nonEmpty) + assert(2 == precisionSet.size) + } + + test("GetPrecisionMap - Empty Precision Set") { + val emptyPrecisionSet = HashService.getPrecisionSet( + new HashSetting("Test1", false, 8, 0, 0), + new HashSetting("Test2", false, 16, 0, 0), + new HashSetting("Test3", false, 32, 0, 0)) + val grayImage = ImageUtil.convertToGray(ImageIO.read(new File(SmallSampleImage1))) + val emptyPrecisionMap = HashService.getPrecisionMap(emptyPrecisionSet, grayImage) + assert(emptyPrecisionMap.isEmpty) + } + + test("GetPrecisionMap - Overlapping Precision Set") { + val precisionSet = HashService.getPrecisionSet( + new HashSetting("Test1", true, 8, 0, 0), + new HashSetting("Test2", true, 8, 0, 0), + new HashSetting("Test3", true, 16, 0, 0)) + val grayImage = ImageUtil.convertToGray(ImageIO.read(new File(SmallSampleImage1))) + val precisionMap = HashService.getPrecisionMap(precisionSet, grayImage) + assert(precisionMap.nonEmpty) + assert(precisionMap.size == 2) + } + + test("getHash - Empty Precision Set") { + val emptyPrecisionSet = HashService.getPrecisionSet( + new HashSetting("Test1", false, 8, 0, 0), + new HashSetting("Test2", false, 16, 0, 0), + new HashSetting("Test3", false, 32, 0, 0)) + val grayImage = ImageUtil.convertToGray(ImageIO.read(new File(SmallSampleImage1))) + val emptyPrecisionMap = HashService.getPrecisionMap(emptyPrecisionSet, grayImage) + assertThrows[NoSuchElementException] { + val ahash: Long = getHash(ahashSetting, emptyPrecisionMap(ahashSetting.precision), AHash.getHash) + val dhash: Long = getHash(dhashSetting, emptyPrecisionMap(dhashSetting.precision), DHash.getHash) + val phash: Long = getHash(phashSetting, emptyPrecisionMap(phashSetting.precision), PHash.getHash) + } + } +} \ No newline at end of file diff --git a/hash/src/test/scala/com/sothr/imagetools/hash/TestParams.scala b/hash/src/test/scala/com/sothr/imagetools/hash/TestParams.scala deleted file mode 100644 index 1d79b1c..0000000 --- a/hash/src/test/scala/com/sothr/imagetools/hash/TestParams.scala +++ /dev/null @@ -1,7 +0,0 @@ -package com.sothr.imagetools.hash - -object TestParams { - val LargeSampleImage1 = "sample/sample_01_large.jpg" - val MediumSampleImage1 = "sample/sample_01_medium.jpg" - val SmallSampleImage1 = "sample/sample_01_small.jpg" -} \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index 373fa5e..48a4e25 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -70,8 +70,10 @@ - 0.1.1 + + 0.2.0 0.1.3 + UTF-8 1.8 2.12