From 783a3a4abaa77a0cb4b597411ba8a7cb4ce50554 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Tue, 26 Aug 2014 17:11:39 -0500 Subject: [PATCH] More refactoring --- cli/pom.xml | 34 +- cli/src/includes/logback.xml | 18 +- .../java/com/sothr/imagetools/cli/AppCLI.java | 6 +- cli/version.info | 2 +- engine/pom.xml | 41 +-- .../sothr/imagetools/engine/AppConfig.java | 62 ++-- .../engine/errors/ImageToolsException.java | 21 +- .../engine/util/ResourceLoader.java | 34 +- engine/src/main/resources/application.conf | 2 +- engine/src/main/resources/ehcache.xml | 2 +- engine/src/main/resources/hibernate.cfg.xml | 9 +- .../main/resources/hibernate/Image.hbm.xml | 3 +- .../main/resources/logback-minimum-config.xml | 4 +- .../imagetools/engine/ConcurrentEngine.scala | 322 +++++++++--------- .../com/sothr/imagetools/engine/Engine.scala | 46 +-- .../imagetools/engine/SequentialEngine.scala | 19 +- .../imagetools/engine/dao/HibernateUtil.scala | 16 +- .../imagetools/engine/dao/ImageDAO.scala | 14 +- .../imagetools/engine/dto/ImageHashDTO.scala | 53 ++- .../sothr/imagetools/engine/hash/AHash.scala | 78 ++--- .../sothr/imagetools/engine/hash/DHash.scala | 38 +-- .../imagetools/engine/hash/HashService.scala | 80 ++--- .../sothr/imagetools/engine/hash/PHash.scala | 112 +++--- .../engine/hash/PerceptualHasher.scala | 2 +- .../sothr/imagetools/engine/image/Image.scala | 98 ++++-- .../imagetools/engine/image/ImageFilter.scala | 4 +- .../engine/image/ImageService.scala | 67 ++-- .../engine/image/SimilarImages.scala | 22 +- .../engine/util/PropertiesService.scala | 49 +-- .../sothr/imagetools/engine/util/Timing.scala | 20 +- .../imagetools/engine/util/Version.scala | 46 +-- .../com/sothr/imagetools/engine/AppTest.java | 46 ++- engine/src/test/resources/application.conf | 2 +- engine/src/test/resources/hibernate.cfg.xml | 9 +- .../test/resources/hibernate/Image.hbm.xml | 3 +- .../test/resources/logback-minimum-config.xml | 12 +- .../sothr/imagetools/engine/EngineTest.scala | 32 +- .../sothr/imagetools/engine/TestParams.scala | 6 +- .../engine/hash/HashServiceTest.scala | 145 +++++--- .../engine/image/ImageFilterTest.scala | 22 +- gui/pom.xml | 34 +- gui/src/includes/logback.xml | 18 +- .../java/com/sothr/imagetools/ui/App.java | 114 +++---- .../main/resources/fxml/mainapp/MainApp.fxml | 170 +++++---- .../imagetools/ui/component/ImageTile.scala | 6 +- .../ui/component/ImageTileFactory.scala | 27 +- .../ui/controller/AppController.scala | 74 ++-- gui/version.info | 2 +- parent/pom.xml | 46 +-- pom.xml | 2 +- 50 files changed, 1128 insertions(+), 966 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index dd229cc..696d902 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -1,5 +1,5 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -27,16 +27,16 @@ ImageTools-Engine - ch.qos.logback - logback-core + ch.qos.logback + logback-core - ch.qos.logback - logback-classic + ch.qos.logback + logback-classic - ch.qos.logback - logback-access + ch.qos.logback + logback-access org.slf4j @@ -80,18 +80,18 @@ - - maven-antrun-plugin - 1.4 - + + maven-antrun-plugin + 1.4 + prepare - process-resources - + process-resources + - - - + + + @@ -112,7 +112,7 @@ run - + diff --git a/cli/src/includes/logback.xml b/cli/src/includes/logback.xml index 4d0b07f..077b4d1 100644 --- a/cli/src/includes/logback.xml +++ b/cli/src/includes/logback.xml @@ -4,10 +4,10 @@ - - false + + false [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n - + INFO @@ -16,9 +16,9 @@ ImageTools.debug - false + false [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + DEBUG @@ -34,9 +34,9 @@ ImageTools.info - false + false [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + INFO @@ -52,9 +52,9 @@ ImageTools.err - false + false [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + ERROR diff --git a/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java b/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java index f3ec666..44bd8c4 100644 --- a/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java +++ b/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java @@ -39,8 +39,8 @@ class AppCLI { AppConfig.shutdown(); } System.exit(0); - } catch (Exception ex) { - logger.error("Unhandled exception in AppCLI",ex); + } catch (Exception ex) { + logger.error("Unhandled exception in AppCLI", ex); } } @@ -97,7 +97,7 @@ class AppCLI { Boolean recursive = false; Integer recursiveDepth = 500; if (cmd.hasOption('r')) { - recursive = true; + recursive = true; } if (cmd.hasOption('d')) { recursiveDepth = Integer.parseInt(cmd.getOptionValue('d')); diff --git a/cli/version.info b/cli/version.info index 2f32a90..f205476 100644 --- a/cli/version.info +++ b/cli/version.info @@ -1 +1 @@ -0.1.1-DEV-13-661ba6d +0.1.1-DEV-15-15ce4f1 diff --git a/engine/pom.xml b/engine/pom.xml index 0591e4e..0d58ac1 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -1,5 +1,5 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -33,16 +33,16 @@ test - ch.qos.logback - logback-core + ch.qos.logback + logback-core - ch.qos.logback - logback-classic + ch.qos.logback + logback-classic - ch.qos.logback - logback-access + ch.qos.logback + logback-access org.slf4j @@ -105,8 +105,8 @@ hibernate-ehcache - net.sf.ehcache - ehcache-core + net.sf.ehcache + ehcache-core org.hibernate @@ -180,29 +180,30 @@ - - maven-antrun-plugin - 1.4 - + + maven-antrun-plugin + 1.4 + prepare - process-resources - + process-resources + - + - - - + + + run - + diff --git a/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java b/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java index a378001..94ff970 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java @@ -37,53 +37,59 @@ public class AppConfig { // The Main App private static Stage primaryStage = null; - public static Stage getPrimaryStage() { return primaryStage; } - public static void setPrimaryStage(Stage newPrimaryStage) { primaryStage = newPrimaryStage; } + + public static Stage getPrimaryStage() { + return primaryStage; + } + + public static void setPrimaryStage(Stage newPrimaryStage) { + primaryStage = newPrimaryStage; + } public static ActorSystem getAppActorSystem() { return appSystem; } public static void configureApp() { - logger = (Logger)LoggerFactory.getLogger(AppConfig.class); + logger = (Logger) LoggerFactory.getLogger(AppConfig.class); loadProperties(); configLogging(); configCache(); } - private static void configLogging(String location) { + private static void configLogging(String location) { //Logging Config //remove previous configuration if it exists - Logger rootLogger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); LoggerContext context = rootLogger.getLoggerContext(); context.reset(); File file = new File(location); Boolean fromFile = false; if (file.exists()) { - fromFile = true; - try { - JoranConfigurator configurator = new JoranConfigurator(); - configurator.setContext(context); - // Call context.reset() to clear any previous configuration, e.g. default - // configuration. For multi-step configuration, omit calling context.reset(). - context.reset(); - configurator.doConfigure(location); - } catch (JoranException je) { - // StatusPrinter will handle this - } - StatusPrinter.printInCaseOfErrorsOrWarnings(context); + fromFile = true; + try { + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + // Call context.reset() to clear any previous configuration, e.g. default + // configuration. For multi-step configuration, omit calling context.reset(). + context.reset(); + configurator.doConfigure(location); + } catch (JoranException je) { + // StatusPrinter will handle this + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); } else { - try { - JoranConfigurator configurator = new JoranConfigurator(); - configurator.setContext(context); - // Call context.reset() to clear any previous configuration, e.g. default - // configuration. For multi-step configuration, omit calling context.reset(). - context.reset(); - configurator.doConfigure(ResourceLoader.get().getResourceStream("logback-minimum-config.xml")); - } catch (JoranException je) { - // StatusPrinter will handle this - } - StatusPrinter.printInCaseOfErrorsOrWarnings(context); + try { + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + // Call context.reset() to clear any previous configuration, e.g. default + // configuration. For multi-step configuration, omit calling context.reset(). + context.reset(); + configurator.doConfigure(ResourceLoader.get().getResourceStream("logback-minimum-config.xml")); + } catch (JoranException je) { + // StatusPrinter will handle this + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); } String message = fromFile ? "From File" : "From Defaults"; logger.info(String.format("Configured Logger %s", message)); diff --git a/engine/src/main/java/com/sothr/imagetools/engine/errors/ImageToolsException.java b/engine/src/main/java/com/sothr/imagetools/engine/errors/ImageToolsException.java index 5f2bde1..b4ae892 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/errors/ImageToolsException.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/errors/ImageToolsException.java @@ -2,14 +2,25 @@ package com.sothr.imagetools.engine.errors; /** * Simple Exception - * + *

* Created by drew on 12/31/13. */ public class ImageToolsException extends Exception { - public ImageToolsException() { super(); } - public ImageToolsException(String message) { super(message); } - public ImageToolsException(String message, Throwable cause) { super(message, cause); } - public ImageToolsException(Throwable cause) { super(cause); } + public ImageToolsException() { + super(); + } + + public ImageToolsException(String message) { + super(message); + } + + public ImageToolsException(String message, Throwable cause) { + super(message, cause); + } + + public ImageToolsException(Throwable cause) { + super(cause); + } } diff --git a/engine/src/main/java/com/sothr/imagetools/engine/util/ResourceLoader.java b/engine/src/main/java/com/sothr/imagetools/engine/util/ResourceLoader.java index 3c132e4..c85e6a5 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/util/ResourceLoader.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/util/ResourceLoader.java @@ -8,31 +8,31 @@ import java.net.URL; /** * Seamlessly handle resource loading - * + *

* Created by drew on 1/5/14. */ public class ResourceLoader { - private static final ResourceLoader instance = new ResourceLoader(); + private static final ResourceLoader instance = new ResourceLoader(); - private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private ResourceLoader() { - logger.info("Created Resource Loader"); - } + private ResourceLoader() { + logger.info("Created Resource Loader"); + } - public static ResourceLoader get() { - return instance; - } + public static ResourceLoader get() { + return instance; + } - public URL getResource(String location) { - logger.debug(String.format("Attempting to load resource: %s", location)); - return Thread.currentThread().getContextClassLoader().getResource(location); - } + public URL getResource(String location) { + logger.debug(String.format("Attempting to load resource: %s", location)); + return Thread.currentThread().getContextClassLoader().getResource(location); + } - public InputStream getResourceStream(String location) { - logger.debug(String.format("Attempting to get stream for resource: %s",location)); - return Thread.currentThread().getContextClassLoader().getResourceAsStream(location); - } + public InputStream getResourceStream(String location) { + logger.debug(String.format("Attempting to get stream for resource: %s", location)); + return Thread.currentThread().getContextClassLoader().getResourceAsStream(location); + } } diff --git a/engine/src/main/resources/application.conf b/engine/src/main/resources/application.conf index 420807b..bab83ff 100644 --- a/engine/src/main/resources/application.conf +++ b/engine/src/main/resources/application.conf @@ -23,7 +23,7 @@ app { differenceThreshold = 0.90 //control generation of hashes for new images. hash { - precision=64 + precision = 64 } ahash { use = true diff --git a/engine/src/main/resources/ehcache.xml b/engine/src/main/resources/ehcache.xml index 08679a7..ef3a96d 100644 --- a/engine/src/main/resources/ehcache.xml +++ b/engine/src/main/resources/ehcache.xml @@ -1,4 +1,4 @@ - + diff --git a/engine/src/main/resources/hibernate.cfg.xml b/engine/src/main/resources/hibernate.cfg.xml index 96f1af7..84f210f 100644 --- a/engine/src/main/resources/hibernate.cfg.xml +++ b/engine/src/main/resources/hibernate.cfg.xml @@ -25,15 +25,18 @@ org.hibernate.cache.ehcache.EhCacheRegionFactory - org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + true 1 - 100 + 100 + 50 0 5 - 100 + 100 + diff --git a/engine/src/main/resources/hibernate/Image.hbm.xml b/engine/src/main/resources/hibernate/Image.hbm.xml index 2a19c3e..c9c2457 100644 --- a/engine/src/main/resources/hibernate/Image.hbm.xml +++ b/engine/src/main/resources/hibernate/Image.hbm.xml @@ -11,6 +11,7 @@ - + \ No newline at end of file diff --git a/engine/src/main/resources/logback-minimum-config.xml b/engine/src/main/resources/logback-minimum-config.xml index ab0887d..3d5fd20 100644 --- a/engine/src/main/resources/logback-minimum-config.xml +++ b/engine/src/main/resources/logback-minimum-config.xml @@ -3,9 +3,9 @@ ImageTools.err - false + false [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n - + ERROR 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 47626e5..fb87c22 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala @@ -8,8 +8,7 @@ import akka.pattern.ask import akka.routing.{Broadcast, RoundRobinRouter, SmallestMailboxRouter} import akka.util.Timeout import com.sothr.imagetools.engine.hash.HashService -import com.sothr.imagetools.engine.image.{SimilarImages, ImageService, Image} -import com.sothr.imagetools.image.SimilarImages +import com.sothr.imagetools.engine.image.{Image, ImageService, SimilarImages} import com.sothr.imagetools.engine.util._ import scala.collection.mutable @@ -28,10 +27,10 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { engineSimilarityController ! SetNewListener(listenerRef) } - def getImagesForDirectory(directoryPath:String, recursive:Boolean=false, recursiveDepth:Int=500):List[Image] = { + def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { debug(s"Looking for images in directory: $directoryPath") val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) - val images:mutable.MutableList[Image] = new mutable.MutableList[Image]() + val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() // make sure the engine is listening engineProcessingController ! EngineStart for (file <- imageFiles) { @@ -39,18 +38,18 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { } engineProcessingController ! EngineNoMoreFiles var doneProcessing = false - while(!doneProcessing) { + while (!doneProcessing) { val f = engineProcessingController ? EngineIsProcessingFinished val result = Await.result(f, timeout.duration).asInstanceOf[Boolean] result match { case true => doneProcessing = true debug("Processing Complete") - case false => + case false => debug("Still Processing") //sleep thread Thread.sleep(5000L) - //val future = Future { blocking(Thread.sleep(5000L)); "done" } + //val future = Future { blocking(Thread.sleep(5000L)); "done" } } } val f = engineProcessingController ? EngineGetProcessingResults @@ -60,7 +59,7 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { } //needs to be rebuilt - def getSimilarImagesForDirectory(directoryPath:String, recursive:Boolean=false, recursiveDepth:Int=500):List[SimilarImages] = { + def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = { debug(s"Looking for similar images in directory: $directoryPath") val images = getImagesForDirectory(directoryPath, recursive, recursiveDepth) info(s"Searching ${images.length} images for similarities") @@ -73,7 +72,7 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { //tell the comparison engine there's nothing left to compare engineSimilarityController ! EngineNoMoreComparisons var doneProcessing = false - while(!doneProcessing) { + while (!doneProcessing) { val f = engineSimilarityController ? EngineIsSimilarityFinished val result = Await.result(f, timeout.duration).asInstanceOf[Boolean] result match { @@ -94,17 +93,17 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { val cleanedSimilarImages = new mutable.MutableList[SimilarImages]() val ignoreSet = new mutable.HashSet[Image]() for (similarImages <- result) { - count += 1 - if (count % 25 == 0 || count == result.length) debug(s"Cleaning similar image $count/${result.length} ${result.length - count} left to clean") - if (!ignoreSet.contains(similarImages.rootImage)) { - cleanedSimilarImages += similarImages - ignoreSet += similarImages.rootImage - for (image <- similarImages.similarImages) { - ignoreSet += image - } + count += 1 + if (count % 25 == 0 || count == result.length) debug(s"Cleaning similar image $count/${result.length} ${result.length - count} left to clean") + if (!ignoreSet.contains(similarImages.rootImage)) { + cleanedSimilarImages += similarImages + ignoreSet += similarImages.rootImage + for (image <- similarImages.similarImages) { + ignoreSet += image } + } } - + var similarCount = 0 for (similarImage <- cleanedSimilarImages) { similarCount += 1 + similarImage.similarImages.size @@ -118,164 +117,176 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { // external cases // case class SetNewListener(listenerType: ActorRef) + case object EngineStart // processing files into images -case class EngineProcessFile(file:File) +case class EngineProcessFile(file: File) + case object EngineNoMoreFiles + case object EngineIsProcessingFinished + case object EngineGetProcessingResults //internal cases -case class EngineFileProcessed(image:Image) +case class EngineFileProcessed(image: Image) + case object EngineActorProcessingFinished + case object EngineActorReactivate class ConcurrentEngineProcessingController extends Actor with ActorLogging { - val numOfRouters = { - val max = PropertiesService.get(PropertyEnum.ConcurrentProcessingLimit.toString).toInt - val processors = Runtime.getRuntime.availableProcessors() - var threads = 0 - if (processors > max) threads = max else if (processors > 1) threads = processors - 1 else threads = 1 - threads - } - var router = context.actorOf(Props[ConcurrentEngineProcessingActor].withRouter(SmallestMailboxRouter(nrOfInstances = numOfRouters))) - - var images:mutable.MutableList[Image] = new mutable.MutableList[Image]() - var toProcess = 0 - var processed = 0 - - var processorsFinished = 0 - var listener = context.actorOf(Props[DefaultLoggingEngineListener], - name = "ProcessedEngineListener") - - def setListener(newListener: ActorRef) = { - //remove the old listener - this.listener ! PoisonPill - //setup the new listener - this.listener = newListener - } + val numOfRouters = { + val max = PropertiesService.get(PropertyEnum.ConcurrentProcessingLimit.toString).toInt + val processors = Runtime.getRuntime.availableProcessors() + var threads = 0 + if (processors > max) threads = max else if (processors > 1) threads = processors - 1 else threads = 1 + threads + } + var router = context.actorOf(Props[ConcurrentEngineProcessingActor].withRouter(SmallestMailboxRouter(nrOfInstances = numOfRouters))) - override def preStart() = { - log.info("Staring the controller for processing images") - log.info("Using {} actors to process images", numOfRouters) - } - - override def receive = { - case command:SetNewListener => setListener(command.listenerType) - case command:EngineProcessFile => processFile(command) - case command:EngineFileProcessed => fileProcessed(command) - case EngineStart => startEngine() - case EngineNoMoreFiles => requestWrapup() - case EngineActorProcessingFinished => actorProcessingFinished() - case EngineIsProcessingFinished => checkIfProcessingIsFinished() - case EngineGetProcessingResults => checkForResults() - case _ => log.info("received unknown message") - } + var images: mutable.MutableList[Image] = new mutable.MutableList[Image]() + var toProcess = 0 + var processed = 0 - override def postStop() = { - super.postStop() - this.listener ! PoisonPill - } + var processorsFinished = 0 + var listener = context.actorOf(Props[DefaultLoggingEngineListener], + name = "ProcessedEngineListener") - def startEngine() = { - router ! Broadcast(EngineActorReactivate) - } + def setListener(newListener: ActorRef) = { + //remove the old listener + this.listener ! PoisonPill + //setup the new listener + this.listener = newListener + } - def processFile(command:EngineProcessFile) = { - log.debug(s"Started evaluating ${command.file.getAbsolutePath}") - toProcess += 1 - router ! command - } - - def fileProcessed(command:EngineFileProcessed) = { - processed += 1 - if (processed % 25 == 0 || processed == toProcess) { - //log.info(s"Processed $processed/$toProcess") - listener ! ComparedFileCount(processed,toProcess) - } - if (command.image != null) { - log.debug(s"processed image: ${command.image.imagePath}") - images += command.image - } - } - - def requestWrapup() = { - router ! Broadcast(EngineNoMoreFiles) + override def preStart() = { + log.info("Staring the controller for processing images") + log.info("Using {} actors to process images", numOfRouters) + } + + override def receive = { + case command: SetNewListener => setListener(command.listenerType) + case command: EngineProcessFile => processFile(command) + case command: EngineFileProcessed => fileProcessed(command) + case EngineStart => startEngine() + case EngineNoMoreFiles => requestWrapup() + case EngineActorProcessingFinished => actorProcessingFinished() + case EngineIsProcessingFinished => checkIfProcessingIsFinished() + case EngineGetProcessingResults => checkForResults() + case _ => log.info("received unknown message") + } + + override def postStop() = { + super.postStop() + this.listener ! PoisonPill + } + + def startEngine() = { + router ! Broadcast(EngineActorReactivate) + } + + def processFile(command: EngineProcessFile) = { + log.debug(s"Started evaluating ${command.file.getAbsolutePath}") + toProcess += 1 + router ! command + } + + def fileProcessed(command: EngineFileProcessed) = { + processed += 1 + if (processed % 25 == 0 || processed == toProcess) { + //log.info(s"Processed $processed/$toProcess") + listener ! ComparedFileCount(processed, toProcess) } - - /* - * Record that a processor is done - */ - def actorProcessingFinished() = { - processorsFinished += 1 + if (command.image != null) { + log.debug(s"processed image: ${command.image.imagePath}") + images += command.image } - - /* - * Check if processing is done - */ - def checkIfProcessingIsFinished() = { - try { - if (processorsFinished >= numOfRouters) sender ! true else sender ! false - } catch { - case e: Exception ⇒ - sender ! akka.actor.Status.Failure(e) - throw e - } + } + + def requestWrapup() = { + router ! Broadcast(EngineNoMoreFiles) + } + + /* + * Record that a processor is done + */ + def actorProcessingFinished() = { + processorsFinished += 1 + } + + /* + * Check if processing is done + */ + def checkIfProcessingIsFinished() = { + try { + if (processorsFinished >= numOfRouters) sender ! true else sender ! false + } catch { + case e: Exception ⇒ + sender ! akka.actor.Status.Failure(e) + throw e } - - /* - * Get the results of the processing - */ - def checkForResults() = { - try { - processorsFinished = 0 - toProcess = 0 - processed = 0 - sender ! images.toList - images.clear() - } catch { - case e: Exception ⇒ - sender ! akka.actor.Status.Failure(e) - throw e - } + } + + /* + * Get the results of the processing + */ + def checkForResults() = { + try { + processorsFinished = 0 + toProcess = 0 + processed = 0 + sender ! images.toList + images.clear() + } catch { + case e: Exception ⇒ + sender ! akka.actor.Status.Failure(e) + throw e } + } } class ConcurrentEngineProcessingActor extends Actor with ActorLogging { - var ignoreMessages = false - override def receive = { - case command:EngineProcessFile => processFile(command) - case EngineNoMoreFiles => finishedProcessingFiles() - case EngineActorReactivate => ignoreMessages = false - case _ => log.info("received unknown message") - } - - def processFile(command:EngineProcessFile) = { - if (!ignoreMessages) { - val image = ImageService.getImage(command.file) - if (image != null) { - sender ! EngineFileProcessed(image) - } else { - log.debug(s"Failed to process image: ${command.file.getAbsolutePath}") - } - } + var ignoreMessages = false + + override def receive = { + case command: EngineProcessFile => processFile(command) + case EngineNoMoreFiles => finishedProcessingFiles() + case EngineActorReactivate => ignoreMessages = false + case _ => log.info("received unknown message") + } + + def processFile(command: EngineProcessFile) = { + if (!ignoreMessages) { + val image = ImageService.getImage(command.file) + if (image != null) { + sender ! EngineFileProcessed(image) + } else { + log.debug(s"Failed to process image: ${command.file.getAbsolutePath}") + } } - - def finishedProcessingFiles() = { - if (!ignoreMessages) { - ignoreMessages = true - sender ! EngineActorProcessingFinished - } + } + + def finishedProcessingFiles() = { + if (!ignoreMessages) { + ignoreMessages = true + sender ! EngineActorProcessingFinished } + } } //finding similarities between images -case class EngineCompareImages(image1:Image, images:List[Image]) -case class EngineCompareImagesComplete(similarImages:SimilarImages) +case class EngineCompareImages(image1: Image, images: List[Image]) + +case class EngineCompareImagesComplete(similarImages: SimilarImages) + case object EngineNoMoreComparisons + case object EngineIsSimilarityFinished + case object EngineGetSimilarityResults + case object EngineActorCompareImagesFinished class ConcurrentEngineSimilarityController extends Actor with ActorLogging { @@ -310,9 +321,9 @@ class ConcurrentEngineSimilarityController extends Actor with ActorLogging { } override def receive = { - case command:SetNewListener => setListener(command.listenerType) - case command:EngineCompareImages => findSimilarities(command) - case command:EngineCompareImagesComplete => similarityProcessed(command) + case command: SetNewListener => setListener(command.listenerType) + case command: EngineCompareImages => findSimilarities(command) + case command: EngineCompareImagesComplete => similarityProcessed(command) case EngineStart => startEngine() case EngineNoMoreComparisons => requestWrapup() case EngineActorCompareImagesFinished => actorProcessingFinished() @@ -330,22 +341,22 @@ class ConcurrentEngineSimilarityController extends Actor with ActorLogging { router ! Broadcast(EngineActorReactivate) } - def findSimilarities(command:EngineCompareImages) = { + def findSimilarities(command: EngineCompareImages) = { log.debug(s"Finding similarities between {} and {} images", command.image1.imagePath, command.images.length) toProcess += 1 if (toProcess % 250 == 0) { - //log.info("Sent {} images to be processed for similarites", toProcess) - listener ! SubmitMessage(s"Sent $toProcess images to be processed for similarites") + //log.info("Sent {} images to be processed for similarites", toProcess) + listener ! SubmitMessage(s"Sent $toProcess images to be processed for similarites") } //just relay the command to our workers router ! command } - def similarityProcessed(command:EngineCompareImagesComplete) = { + def similarityProcessed(command: EngineCompareImagesComplete) = { processed += 1 if (processed % 25 == 0 || processed == toProcess) { //log.info(s"Processed $processed/$toProcess") - listener ! ScannedFileCount(processed,toProcess) + listener ! ScannedFileCount(processed, toProcess) } if (command.similarImages != null) { log.debug(s"Found similar images: ${command.similarImages}") @@ -399,14 +410,15 @@ class ConcurrentEngineSimilarityController extends Actor with ActorLogging { class ConcurrentEngineSimilarityActor extends Actor with ActorLogging { var ignoreMessages = false + override def receive = { - case command:EngineCompareImages => compareImages(command) + case command: EngineCompareImages => compareImages(command) case EngineNoMoreComparisons => finishedComparisons() case EngineActorReactivate => ignoreMessages = false case _ => log.info("received unknown message") } - def compareImages(command:EngineCompareImages) = { + def compareImages(command: EngineCompareImages) = { if (!ignoreMessages) { val similarImages = new mutable.MutableList[Image]() for (image <- command.images) { diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala b/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala index dd30a7a..17d1abc 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala @@ -3,9 +3,7 @@ package com.sothr.imagetools.engine import java.io.File import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem} -import AppConfig -import com.sothr.imagetools.engine.image.{SimilarImages, ImageFilter, Image} -import com.sothr.imagetools.image.SimilarImages +import com.sothr.imagetools.engine.image.{Image, ImageFilter, SimilarImages} import com.sothr.imagetools.engine.util.DirectoryFilter import grizzled.slf4j.Logging @@ -18,16 +16,17 @@ import scala.collection.mutable */ abstract class Engine extends Logging { val system = ActorSystem("EngineActorSystem") - val imageFilter:ImageFilter = new ImageFilter() + val imageFilter: ImageFilter = new ImageFilter() val imageCache = AppConfig.cacheManager.getCache("images") def setProcessedListener(listenerType: ActorRef) + def setSimilarityListener(listenerType: ActorRef) - def getAllImageFiles(directoryPath:String, recursive:Boolean=false, recursiveDepth:Int=500):List[File] = { + def getAllImageFiles(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[File] = { val fileList = new mutable.MutableList[File]() if (directoryPath != null && directoryPath != "") { - val directory:File = new File(directoryPath) + val directory: File = new File(directoryPath) val imageFilter = new ImageFilter if (directory.isDirectory) { val files = directory.listFiles(imageFilter) @@ -38,7 +37,7 @@ abstract class Engine extends Logging { val directoryFilter = new DirectoryFilter val directories = directory.listFiles(directoryFilter) for (directory <- directories) { - fileList ++= getAllImageFiles(directory.getAbsolutePath, recursive, recursiveDepth-1) + fileList ++= getAllImageFiles(directory.getAbsolutePath, recursive, recursiveDepth - 1) } } } @@ -50,28 +49,33 @@ abstract class Engine extends Logging { /** * Get all images for a directory with hashes */ - def getImagesForDirectory(directoryPath:String, recursive:Boolean=false, recursiveDepth:Int=500):List[Image] - + def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] + /** * Get all similar images for a directory with hashes */ - def getSimilarImagesForDirectory(directoryPath:String, recursive:Boolean=false, recursiveDepth:Int=500):List[SimilarImages] + def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] } -case class SubmitMessage(message:String) -case class ScannedFileCount(count:Integer, total:Integer, message:String=null) -case class ComparedFileCount(count:Integer,total:Integer, message:String=null) +case class SubmitMessage(message: String) + +case class ScannedFileCount(count: Integer, total: Integer, message: String = null) + +case class ComparedFileCount(count: Integer, total: Integer, message: String = null) + abstract class EngineListener extends Actor with ActorLogging { override def receive: Actor.Receive = { - case command:SubmitMessage => handleMessage(command) - case command:ScannedFileCount => handleScannedFileCount(command) - case command:ComparedFileCount => handleComparedFileCount(command) + case command: SubmitMessage => handleMessage(command) + case command: ScannedFileCount => handleScannedFileCount(command) + case command: ComparedFileCount => handleComparedFileCount(command) case _ => log.info("received unknown message") } - def handleMessage(command:SubmitMessage) - def handleScannedFileCount(command:ScannedFileCount) - def handleComparedFileCount(command:ComparedFileCount) + def handleMessage(command: SubmitMessage) + + def handleScannedFileCount(command: ScannedFileCount) + + def handleComparedFileCount(command: ComparedFileCount) } /** @@ -82,14 +86,14 @@ class DefaultLoggingEngineListener extends EngineListener with ActorLogging { if (command.message != null) { log.info(command.message) } - log.info("Processed {}/{}",command.count,command.total) + log.info("Processed {}/{}", command.count, command.total) } override def handleScannedFileCount(command: ScannedFileCount): Unit = { if (command.message != null) { log.info(command.message) } - log.info("Scanned {}/{} For Similarities",command.count,command.total) + log.info("Scanned {}/{} For Similarities", command.count, command.total) } override def handleMessage(command: SubmitMessage): Unit = { diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala b/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala index 3be7aab..0e2ea3b 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala @@ -3,8 +3,7 @@ package com.sothr.imagetools.engine import java.io.File import akka.actor.{ActorRef, Props} -import com.sothr.imagetools.engine.image.{SimilarImages, ImageService, Image} -import com.sothr.imagetools.image.SimilarImages +import com.sothr.imagetools.engine.image.{Image, ImageService, SimilarImages} import grizzled.slf4j.Logging import scala.collection.mutable @@ -30,17 +29,17 @@ class SequentialEngine extends Engine with Logging { this.similarityListener = listenerRef } - def getImagesForDirectory(directoryPath:String, recursive:Boolean=false, recursiveDepth:Int=500):List[Image] = { + def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { debug(s"Looking for images in directory: $directoryPath") - val images:mutable.MutableList[Image] = new mutable.MutableList[Image]() + val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) - val directory:File = new File(directoryPath) + val directory: File = new File(directoryPath) var count = 0 for (file <- imageFiles) { count += 1 if (count % 25 == 0) { //info(s"Processed ${count}/${imageFiles.size}") - processedListener ! ScannedFileCount(count,imageFiles.size) + processedListener ! ScannedFileCount(count, imageFiles.size) } val image = ImageService.getImage(file) if (image != null) { @@ -50,7 +49,7 @@ class SequentialEngine extends Engine with Logging { images.toList } - def getSimilarImagesForDirectory(directoryPath:String, recursive:Boolean=false, recursiveDepth:Int=500):List[SimilarImages] = { + def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = { debug(s"Looking for similar images in directory: $directoryPath") val images = getImagesForDirectory(directoryPath, recursive, recursiveDepth) info(s"Searching ${images.length} images for similarities") @@ -61,9 +60,9 @@ class SequentialEngine extends Engine with Logging { for (rootImage <- images) { if (!ignoreSet.contains(rootImage)) { if (processedCount % 25 == 0) { - //info(s"Processed ${processedCount}/${images.length - ignoreSet.size} About ${images.length - - // processedCount} images to go") - similarityListener ! ScannedFileCount(processedCount,images.length-ignoreSet.size) + //info(s"Processed ${processedCount}/${images.length - ignoreSet.size} About ${images.length - + // processedCount} images to go") + similarityListener ! ScannedFileCount(processedCount, images.length - ignoreSet.size) } debug(s"Looking for images similar to: ${rootImage.imagePath}") ignoreSet += rootImage diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala b/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala index 0a2db9a..186b1c8 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala @@ -14,10 +14,10 @@ import org.hibernate.service.ServiceRegistry */ object HibernateUtil extends Logging { - private val sessionFactory:SessionFactory = buildSessionFactory() - private var serviceRegistry:ServiceRegistry = null + private val sessionFactory: SessionFactory = buildSessionFactory() + private var serviceRegistry: ServiceRegistry = null - private def buildSessionFactory():SessionFactory = { + private def buildSessionFactory(): SessionFactory = { try { // Create the SessionFactory from hibernate.cfg.xml val configuration = new Configuration().configure("hibernate.cfg.xml") @@ -27,14 +27,14 @@ object HibernateUtil extends Logging { serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties).build configuration.buildSessionFactory(serviceRegistry) } catch { - case ex:Throwable => - // Make sure you log the exception, as it might be swallowed - error("Initial SessionFactory creation failed.", ex) - throw new ExceptionInInitializerError(ex) + case ex: Throwable => + // Make sure you log the exception, as it might be swallowed + error("Initial SessionFactory creation failed.", ex) + throw new ExceptionInInitializerError(ex) } } - def getSessionFactory:SessionFactory = { + def getSessionFactory: SessionFactory = { sessionFactory } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/dao/ImageDAO.scala b/engine/src/main/scala/com/sothr/imagetools/engine/dao/ImageDAO.scala index 6abaed6..fc5887e 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/dao/ImageDAO.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/dao/ImageDAO.scala @@ -10,25 +10,25 @@ import org.hibernate.{Session, SessionFactory} */ class ImageDAO { - private val sessionFactory:SessionFactory = HibernateUtil.getSessionFactory + private val sessionFactory: SessionFactory = HibernateUtil.getSessionFactory - def find(path:String):Image = { - val session:Session = sessionFactory.getCurrentSession + def find(path: String): Image = { + val session: Session = sessionFactory.getCurrentSession session.getTransaction.begin() val result = session.get(classOf[Image], path).asInstanceOf[Image] session.getTransaction.commit() result } - def save(image:Image) = { - val session:Session = sessionFactory.getCurrentSession + def save(image: Image) = { + val session: Session = sessionFactory.getCurrentSession session.getTransaction.begin() session.saveOrUpdate(image) session.getTransaction.commit() } - def save(images:List[Image]) = { - val session:Session = sessionFactory.getCurrentSession + def save(images: List[Image]) = { + val session: Session = sessionFactory.getCurrentSession session.getTransaction.begin() for (image <- images) session.saveOrUpdate(image) session.getTransaction.commit() diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/dto/ImageHashDTO.scala b/engine/src/main/scala/com/sothr/imagetools/engine/dto/ImageHashDTO.scala index 740c225..e73e038 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/dto/ImageHashDTO.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/dto/ImageHashDTO.scala @@ -6,30 +6,49 @@ import grizzled.slf4j.Logging @Entity @Table(name = "ImageHash") -class ImageHashDTO(var ahash:Long, var dhash:Long, var phash:Long, var md5:String) extends Serializable with Logging { +class ImageHashDTO(var ahash: Long, var dhash: Long, var phash: Long, var md5: String) extends Serializable with Logging { - def this() = this (0l, 0l, 0l, "") + def this() = this(0l, 0l, 0l, "") @Id @GeneratedValue(strategy = GenerationType.AUTO) - var id:Int = _ - def getId:Int = id - def setId(newId:Int) = { id = newId } + var id: Int = _ - 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 getMd5:String = md5 - def setMd5(hash:String) = { md5 = hash} + def getId: Int = id - def cloneHashes:ImageHashDTO = { - new ImageHashDTO(ahash,dhash,phash,md5) + def setId(newId: Int) = { + id = newId } - override def hashCode():Int = { + 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 getMd5: String = md5 + + def setMd5(hash: String) = { + md5 = hash + } + + def cloneHashes: ImageHashDTO = { + new ImageHashDTO(ahash, dhash, phash, md5) + } + + 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 @@ -37,7 +56,7 @@ class ImageHashDTO(var ahash:Long, var dhash:Long, var phash:Long, var md5:Strin result } - override def toString:String = { + override def toString: String = { s"MD5: $md5 ahash: $ahash dhash: $dhash phash: $phash" } } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/hash/AHash.scala b/engine/src/main/scala/com/sothr/imagetools/engine/hash/AHash.scala index 97b255a..8fda977 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/hash/AHash.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/hash/AHash.scala @@ -3,46 +3,46 @@ package com.sothr.imagetools.engine.hash import grizzled.slf4j.Logging /** - * Created by dev on 1/22/14. - */ + * Created by dev on 1/22/14. + */ object AHash extends PerceptualHasher with Logging { - def getHash(imageData: Array[Array[Int]]): Long = { - //debug("Generating AHash") - val width = imageData.length - val height = imageData(0).length - //debug(s"Image data size: ${width}x${height}") + def getHash(imageData: Array[Array[Int]]): Long = { + //debug("Generating AHash") + val width = imageData.length + val height = imageData(0).length + //debug(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) + //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) - //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 - } + //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 + } - 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 + //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 + } +} diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/hash/DHash.scala b/engine/src/main/scala/com/sothr/imagetools/engine/hash/DHash.scala index 0eebb88..305282c 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/hash/DHash.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/hash/DHash.scala @@ -13,16 +13,16 @@ object DHash extends PerceptualHasher with Logging { val width = imageData.length val height = imageData(0).length //debug(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) + 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) + 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) { @@ -36,19 +36,19 @@ object DHash extends PerceptualHasher with Logging { 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) - } + + 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") diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/hash/HashService.scala b/engine/src/main/scala/com/sothr/imagetools/engine/hash/HashService.scala index 6dcdb21..33073da 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/hash/HashService.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/hash/HashService.scala @@ -18,18 +18,18 @@ import resource._ */ object HashService extends Logging { - def getImageHashes(imagePath:String):ImageHashDTO = { + def getImageHashes(imagePath: String): ImageHashDTO = { //debug(s"Creating hashes for $imagePath") getImageHashes(ImageIO.read(new File(imagePath)), imagePath) } - def getImageHashes(image:BufferedImage, imagePath:String):ImageHashDTO = { + def getImageHashes(image: BufferedImage, imagePath: String): ImageHashDTO = { //debug("Creating hashes for an image") - var ahash:Long = 0L - var dhash:Long = 0L - var phash:Long = 0L - val md5:String = getMD5(imagePath) + var ahash: Long = 0L + var dhash: Long = 0L + var phash: Long = 0L + val md5: String = getMD5(imagePath) //Get Image Data val grayImage = ImageService.convertToGray(image) @@ -50,9 +50,9 @@ object HashService extends Logging { hashes } - def getAhash(image:BufferedImage, alreadyGray:Boolean = false):Long = { + def getAhash(image: BufferedImage, alreadyGray: Boolean = false): Long = { //debug("Started generating an AHash") - var grayImage:BufferedImage = null + var grayImage: BufferedImage = null if (alreadyGray) { grayImage = image } else { @@ -63,9 +63,9 @@ object HashService extends Logging { AHash.getHash(imageData) } - def getDhash(image:BufferedImage, alreadyGray:Boolean = false):Long = { + def getDhash(image: BufferedImage, alreadyGray: Boolean = false): Long = { //debug("Started generating an DHash") - var grayImage:BufferedImage = null + var grayImage: BufferedImage = null if (alreadyGray) { grayImage = image } else { @@ -76,9 +76,9 @@ object HashService extends Logging { DHash.getHash(imageData) } - def getPhash(image:BufferedImage, alreadyGray:Boolean = false):Long = { + def getPhash(image: BufferedImage, alreadyGray: Boolean = false): Long = { //debug("Started generating an PHash") - var grayImage:BufferedImage = null + var grayImage: BufferedImage = null if (alreadyGray) { grayImage = image } else { @@ -89,35 +89,35 @@ object HashService extends Logging { PHash.getHash(imageData) } - def getMD5(filePath:String):String = { + def getMD5(filePath: String): String = { managed(new FileInputStream(filePath)) acquireAndGet { input => - DigestUtils.md5Hex(input) + DigestUtils.md5Hex(input) } } - - def areAhashSimilar(ahash1:Long, ahash2:Long):Boolean = { + + def areAhashSimilar(ahash1: Long, ahash2: Long): Boolean = { val tolerence = PropertiesService.aHashTolerance val distance = Hamming.getDistance(ahash1, ahash2) //debug(s"hash1: $ahash1 hash2: $ahash2 tolerence: $tolerence hamming distance: $distance") if (distance <= tolerence) true else false } - - def areDhashSimilar(dhash1:Long, dhash2:Long):Boolean = { + + def areDhashSimilar(dhash1: Long, dhash2: Long): Boolean = { val tolerence = PropertiesService.dHashTolerance val distance = Hamming.getDistance(dhash1, dhash2) //debug(s"hash1: $dhash1 hash2: $dhash2 tolerence: $tolerence hamming distance: $distance") if (distance <= tolerence) true else false } - - def arePhashSimilar(phash1:Long, phash2:Long):Boolean = { + + def arePhashSimilar(phash1: Long, phash2: Long): Boolean = { val tolerence = PropertiesService.pHashTolerance val distance = Hamming.getDistance(phash1, phash2) //debug(s"hash1: $phash1 hash2: $phash2 tolerence: $tolerence hamming distance: $distance") if (distance <= tolerence) true else false } - def getWeightedHashSimilarity(imageHash1:ImageHashDTO, imageHash2:ImageHashDTO):Float = { + def getWeightedHashSimilarity(imageHash1: ImageHashDTO, imageHash2: ImageHashDTO): Float = { //ahash val aHashTolerance = PropertiesService.aHashTolerance val aHashWeight = PropertiesService.aHashWeight @@ -132,36 +132,33 @@ object HashService extends Logging { val usePhash = PropertiesService.useAhash //calculate weighted values - var weightedHammingTotal:Float = 0 + var weightedHammingTotal: Float = 0 var methodsTotal = 0 - if (useAhash) - { + if (useAhash) { val hamming = Hamming.getDistance(imageHash1.ahash, imageHash2.ahash) weightedHammingTotal += hamming * aHashWeight //debug(s"hash1: ${imageHash1.ahash} hash2: ${imageHash1.ahash} tolerence: $aHashTolerance hamming distance: $hamming weight: $aHashWeight") - methodsTotal+=1 + methodsTotal += 1 } - if (useDhash) - { + if (useDhash) { val hamming = Hamming.getDistance(imageHash1.dhash, imageHash2.dhash) weightedHammingTotal += hamming * dHashWeight //debug(s"hash1: ${imageHash1.dhash} hash2: ${imageHash1.dhash} tolerence: $dHashTolerance hamming distance: $hamming weight: $dHashWeight") - methodsTotal+=1 + methodsTotal += 1 } - if (usePhash) - { + if (usePhash) { val hamming = Hamming.getDistance(imageHash1.phash, imageHash2.phash) weightedHammingTotal += hamming * pHashWeight //debug(s"hash1: ${imageHash1.phash} hash2: ${imageHash1.phash} tolerence: $pHashTolerance hamming distance: $hamming weight: $pHashWeight") - methodsTotal+=1 + methodsTotal += 1 } val weightedHammingMean = weightedHammingTotal / methodsTotal //debug(s"Calculated Weighted Hamming Mean: $weightedHammingMean") weightedHammingMean } - def getWeightedHashTolerence:Float = { + def getWeightedHashTolerence: Float = { //ahash val aHashTolerance = PropertiesService.aHashTolerance val aHashWeight = PropertiesService.aHashWeight @@ -176,33 +173,30 @@ object HashService extends Logging { val usePhash = PropertiesService.useAhash //calculate weighted values - var weightedToleranceTotal:Float = 0 + var weightedToleranceTotal: Float = 0 var methodsTotal = 0 - if (useAhash) - { + if (useAhash) { weightedToleranceTotal += aHashTolerance * aHashWeight //debug(s"Ahash Tolerance: $aHashTolerance Current Weighted Tolerance: $weightedToleranceTotal") - methodsTotal+=1 + methodsTotal += 1 } - if (useDhash) - { + if (useDhash) { weightedToleranceTotal += dHashTolerance * dHashWeight //debug(s"Dhash Tolerance: $dHashTolerance Current Weighted Tolerance: $weightedToleranceTotal") - methodsTotal+=1 + methodsTotal += 1 } - if (usePhash) - { + if (usePhash) { weightedToleranceTotal += pHashTolerance * pHashWeight //debug(s"Phash Tolerance: $pHashTolerance Current Weighted Tolerance: $weightedToleranceTotal") - methodsTotal+=1 + methodsTotal += 1 } val weightedTolerance = weightedToleranceTotal / methodsTotal //debug(s"Calculated Weighted Tolerance: $weightedTolerance") weightedTolerance } - def areImageHashesSimilar(imageHash1:ImageHashDTO, imageHash2:ImageHashDTO):Boolean = { + def areImageHashesSimilar(imageHash1: ImageHashDTO, imageHash2: ImageHashDTO): Boolean = { val weightedHammingMean = getWeightedHashSimilarity(imageHash1, imageHash2) val weightedToleranceMean = getWeightedHashTolerence if (weightedHammingMean <= weightedToleranceMean) true else false diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/hash/PHash.scala b/engine/src/main/scala/com/sothr/imagetools/engine/hash/PHash.scala index 4a2192e..49aac58 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/hash/PHash.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/hash/PHash.scala @@ -4,66 +4,66 @@ import edu.emory.mathcs.jtransforms.dct.FloatDCT_2D import grizzled.slf4j.Logging /** - * Created by dev on 1/22/14. - */ + * 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 - //debug(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 + //debug(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 + } + } + //debug("Copied image data to float array for transform") + //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) - //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) + //debug("Converted image data into DCT") + //debug(s"\n${imageDataFloat.deep.mkString("\n")}") - //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 - } + //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 + } - 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 + } +} diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/hash/PerceptualHasher.scala b/engine/src/main/scala/com/sothr/imagetools/engine/hash/PerceptualHasher.scala index b375ff7..c160f0d 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/hash/PerceptualHasher.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/hash/PerceptualHasher.scala @@ -7,6 +7,6 @@ package com.sothr.imagetools.engine.hash */ trait PerceptualHasher { - def getHash(imageData:Array[Array[Int]]):Long + def getHash(imageData: Array[Array[Int]]): Long } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala index 0ab2660..9db8b53 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala @@ -8,48 +8,74 @@ import grizzled.slf4j.Logging @Entity @Table(name = "Image") -class Image(val image:String, val thumbnail:String, val size:(Int, Int), val imageHashes:ImageHashDTO = null) extends Serializable with Logging { +class Image(val image: String, val thumbnail: String, val size: (Int, Int), val imageHashes: ImageHashDTO = null) extends Serializable with Logging { - def this() = this ("", "", (0,0), null) + def this() = this("", "", (0, 0), null) @Id - var imagePath:String = image - def getImagePath:String = imagePath - def setImagePath(path:String) = { imagePath = path} - var thumbnailPath:String = thumbnail - def getThumbnailPath:String = thumbnailPath - def setThumbnailPath(path:String) = { thumbnailPath = path} - var width:Int = size._1 - def getWidth:Int = width - def setWidth(size:Int) = { width = size} - var height:Int = size._2 - def getHeight:Int = height - def setHeight(size:Int) = { height = size} - var hashes:ImageHashDTO = imageHashes - def getHashes:ImageHashDTO = hashes - def setHashes(newHashes:ImageHashDTO) = { hashes = newHashes} + var imagePath: String = image + + def getImagePath: String = imagePath + + def setImagePath(path: String) = { + imagePath = path + } + + var thumbnailPath: String = thumbnail + + def getThumbnailPath: String = thumbnailPath + + def setThumbnailPath(path: String) = { + thumbnailPath = path + } + + var width: Int = size._1 + + def getWidth: Int = width + + def setWidth(size: Int) = { + width = size + } + + var height: Int = size._2 + + def getHeight: Int = height + + def setHeight(size: Int) = { + height = size + } + + var hashes: ImageHashDTO = imageHashes + + def getHashes: ImageHashDTO = hashes + + def setHashes(newHashes: ImageHashDTO) = { + hashes = newHashes + } @transient - var imageSize:(Int, Int) = { new Tuple2(width, height) } + var imageSize: (Int, Int) = { + new Tuple2(width, height) + } @transient - var imageName:String = "" + var imageName: String = "" - var imageType:ImageType = ImageType.SingleFrameImage + var imageType: ImageType = ImageType.SingleFrameImage - def getName:String = { - if(this.imageName.length < 1) { + def getName: String = { + if (this.imageName.length < 1) { this.imageName = this.getImagePath.split('/').last } this.imageName } - def isSimilarTo(otherImage:Image):Boolean = { + def isSimilarTo(otherImage: Image): Boolean = { //debug(s"Checking $imagePath for similarities with ${otherImage.imagePath}") - HashService.areImageHashesSimilar(this.hashes,otherImage.hashes) + HashService.areImageHashesSimilar(this.hashes, otherImage.hashes) } - def getSimilarity(otherImage:Image):Float = { + def getSimilarity(otherImage: Image): Float = { HashService.getWeightedHashSimilarity(this.hashes, otherImage.hashes) } @@ -57,27 +83,27 @@ class Image(val image:String, val thumbnail:String, val size:(Int, Int), val ima }*/ - def cloneImage:Image = { - new Image(imagePath,thumbnailPath,imageSize,hashes.cloneHashes) + def cloneImage: Image = { + new Image(imagePath, thumbnailPath, imageSize, hashes.cloneHashes) } - override def toString:String = { + override def toString: String = { s"Image: $imagePath Thumbnail: $thumbnailPath Image Size: ${imageSize._1}x${imageSize._2} Hashes: $hashes" } - override def equals(obj:Any) = { - obj match { - case that:Image => - that.hashCode.equals(this.hashCode) - case _ => false - } + override def equals(obj: Any) = { + obj match { + case that: Image => + that.hashCode.equals(this.hashCode) + case _ => false + } } - override def hashCode:Int = { + override def hashCode: Int = { var result = 365 result = 37 * result + imagePath.hashCode result = 41 * result + hashes.hashCode() result } - + } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageFilter.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageFilter.scala index 7269531..f5a8fd7 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageFilter.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageFilter.scala @@ -13,11 +13,11 @@ import scala.collection.immutable.HashSet */ class ImageFilter extends FilenameFilter { - private val extensions:HashSet[String] = new HashSet[String]() ++ Array("png", "bmp", "gif", "jpg", "jpeg") + private val extensions: HashSet[String] = new HashSet[String]() ++ Array("png", "bmp", "gif", "jpg", "jpeg") def accept(dir: File, name: String): Boolean = { val splitName = name.split('.') - val extension = if (splitName.length > 1) splitName(splitName.length-1) else "" + val extension = if (splitName.length > 1) splitName(splitName.length - 1) else "" if (extensions.contains(extension)) true else false } } 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 c1f1b7a..93b0e8e 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 @@ -3,6 +3,7 @@ package com.sothr.imagetools.engine.image import java.awt.image.{BufferedImage, ColorConvertOp, DataBufferByte} import java.io.{File, IOException} import javax.imageio.ImageIO + import com.sothr.imagetools.engine.AppConfig import com.sothr.imagetools.engine.dao.ImageDAO import com.sothr.imagetools.engine.hash.HashService @@ -16,8 +17,8 @@ object ImageService extends Logging { val imageCache = AppConfig.cacheManager.getCache("images") private val imageDAO = new ImageDAO() - private def lookupImage(file:File):Image = { - var image:Image = null + private def lookupImage(file: File): Image = { + var image: Image = null var found = false //get from memory cache if possible try { @@ -26,7 +27,7 @@ object ImageService extends Logging { found = true } } catch { - case npe:NullPointerException => debug(s"\'${file.getAbsolutePath}\' was supposed to be in the cache, but was not") + case npe: NullPointerException => debug(s"\'${file.getAbsolutePath}\' was supposed to be in the cache, but was not") } //get from datastore if possible if (!found) { @@ -34,25 +35,25 @@ object ImageService extends Logging { val tempImage = imageDAO.find(file.getAbsolutePath) if (tempImage != null) image = tempImage } catch { - case ex:Exception => error(s"Error looking up \'${file.getAbsolutePath}\' was supposed to be in the database, but was not", ex) + case ex: Exception => error(s"Error looking up \'${file.getAbsolutePath}\' was supposed to be in the database, but was not", ex) } } image } - - private def saveImage(image:Image):Image = { + + private def saveImage(image: Image): Image = { //save to cache imageCache.put(new Element(image.imagePath, image)) //save to datastore try { imageDAO.save(image) } catch { - case ex:Exception => error(s"Error saving \'${image.imagePath}\' to database", ex) + case ex: Exception => error(s"Error saving \'${image.imagePath}\' to database", ex) } image } - def getImage(file:File):Image = { + def getImage(file: File): Image = { try { val image = lookupImage(file) if (image != null) { @@ -64,34 +65,36 @@ object ImageService extends Logging { val hashes = HashService.getImageHashes(bufferedImage, file.getAbsolutePath) var thumbnailPath = lookupThumbnailPath(hashes.md5) if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.md5) - val imageSize = { (bufferedImage.getWidth, bufferedImage.getHeight) } + val imageSize = { + (bufferedImage.getWidth, bufferedImage.getHeight) + } val image = new Image(file.getAbsolutePath, thumbnailPath, imageSize, hashes) debug(s"Created image: $image") return saveImage(image) } } catch { - case ioe:IOException => error(s"Error processing ${file.getAbsolutePath}", ioe) - case ex:Exception => error(s"Error processing ${file.getAbsolutePath}", ex) + case ioe: IOException => error(s"Error processing ${file.getAbsolutePath}", ioe) + case ex: Exception => error(s"Error processing ${file.getAbsolutePath}", ex) } null } - def calculateThumbPath(md5:String):String = { + def calculateThumbPath(md5: String): String = { //break the path down into 4 char parts val subPath = md5.substring(0, 3) - var path:String = s"${PropertiesService.get(PropertyEnum.ThumbnailDirectory.toString)}${PropertiesService.get(PropertyEnum.ThumbnailSize.toString)}/$subPath/" + var path: String = s"${PropertiesService.get(PropertyEnum.ThumbnailDirectory.toString)}${PropertiesService.get(PropertyEnum.ThumbnailSize.toString)}/$subPath/" try { val dir = new File(path) if (!dir.exists()) dir.mkdirs() } catch { - case ioe:IOException => error(s"Unable to create dirs for path: \'$path\'", ioe) + case ioe: IOException => error(s"Unable to create dirs for path: \'$path\'", ioe) } path += md5 + ".jpg" path } - def lookupThumbnailPath(md5:String):String = { - var thumbPath:String = null + def lookupThumbnailPath(md5: String): String = { + var thumbPath: String = null if (md5 != null) { //check for the actual file val checkPath = calculateThumbPath(md5) @@ -102,9 +105,9 @@ object ImageService extends Logging { thumbPath } - def getThumbnail(image:BufferedImage, md5:String):String = { + def getThumbnail(image: BufferedImage, md5: String): String = { //create thumbnail - val thumb = resize(image, PropertiesService.get(PropertyEnum.ThumbnailSize.toString).toInt, forced=false) + val thumb = resize(image, PropertiesService.get(PropertyEnum.ThumbnailSize.toString).toInt, forced = false) //calculate path val path = calculateThumbPath(md5) // save thumbnail to path @@ -112,7 +115,7 @@ object ImageService extends Logging { ImageIO.write(thumb, "jpg", new File(path)) debug(s"Wrote thumbnail to $path") } catch { - case ioe:IOException => error(s"Unable to save thumbnail to $path", ioe) + case ioe: IOException => error(s"Unable to save thumbnail to $path", ioe) } // return path path @@ -121,7 +124,7 @@ object ImageService extends Logging { /** * Get the raw data for an image */ - def getImageData(image:BufferedImage):Array[Array[Int]] = { + def getImageData(image: BufferedImage): Array[Array[Int]] = { convertTo2DWithoutUsingGetRGB(image) } @@ -131,10 +134,10 @@ object ImageService extends Logging { * @param image image to convert to greyscale * @return */ - def convertToGray(image:BufferedImage):BufferedImage = { + def convertToGray(image: BufferedImage): BufferedImage = { //debug("Converting an image to grayscale") val grayImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_BYTE_GRAY) - + //create a color conversion operation val op = new ColorConvertOp( image.getColorModel.getColorSpace, @@ -142,19 +145,19 @@ object ImageService extends Logging { //convert the image to grey val result = op.filter(image, grayImage) - + //val g = image.getGraphics //g.drawImage(image,0,0,null) //g.dispose() result } - def resize(image:BufferedImage, size:Int, forced:Boolean=false):BufferedImage = { + 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 + Thumbnails.of(image).forceSize(size, size).asBufferedImage } else { - Thumbnails.of(image).size(size,size).asBufferedImage + Thumbnails.of(image).size(size, size).asBufferedImage } } @@ -164,17 +167,17 @@ object ImageService extends Logging { * @param image image to convert without using RGB * @return */ - private def convertTo2DWithoutUsingGetRGB(image:BufferedImage):Array[Array[Int]] = { + private def convertTo2DWithoutUsingGetRGB(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 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) + val result = Array.ofDim[Int](height, width) if (isSingleChannel) { //debug(s"Processing Single Channel Image") val pixelLength = 1 @@ -183,7 +186,7 @@ object ImageService extends Logging { //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 + val argb: Int = pixels(pixel).toInt //singleChannel //debug(s"Pixel data: $argb") result(row)(col) = argb col += 1 @@ -201,7 +204,7 @@ object ImageService extends Logging { //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 + var argb: Int = 0 argb += pixels(pixel).toInt << 24 //alpha argb += pixels(pixel + 1).toInt //blue argb += pixels(pixel + 2).toInt << 8 //green @@ -221,7 +224,7 @@ object ImageService extends Logging { //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 + var argb: Int = 0 argb += -16777216; // 255 alpha argb += pixels(pixel).toInt //blue argb += pixels(pixel + 1).toInt << 8 //green diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala index 4255820..1030b5b 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala @@ -7,9 +7,9 @@ import grizzled.slf4j.Logging * * Created by drew on 1/26/14. */ -class SimilarImages(val rootImage:Image, val similarImages:List[Image]) extends Logging { +class SimilarImages(val rootImage: Image, val similarImages: List[Image]) extends Logging { - protected def getPrettySimilarImagesList:String = { + protected def getPrettySimilarImagesList: String = { val sb = new StringBuilder() for (image <- similarImages) { sb.append(image.imagePath) @@ -17,17 +17,17 @@ class SimilarImages(val rootImage:Image, val similarImages:List[Image]) extends } sb.toString() } - - override def hashCode:Int = { - val prime = 7 - var result = prime * 1 + rootImage.hashCode - for (similarImage <- similarImages) { - result = prime * result + similarImage.hashCode - } - result + + override def hashCode: Int = { + val prime = 7 + var result = prime * 1 + rootImage.hashCode + for (similarImage <- similarImages) { + result = prime * result + similarImage.hashCode + } + result } - override def toString:String = { + override def toString: String = { s"""RootImage: ${rootImage.imagePath} Similar Images: $getPrettySimilarImagesList""".stripMargin 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 92f13fd..e28d3e1 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 @@ -11,15 +11,16 @@ import grizzled.slf4j.Logging */ object PropertiesService extends Logging { - private var defaultConf:Config = null - private var userConf:Config = null - private var newUserConf:Properties = new Properties() - private var version:Version = null - def getVersion:Version = this.version + private var defaultConf: Config = null + private var userConf: Config = null + private var newUserConf: Properties = new Properties() + private var version: Version = null + + def getVersion: Version = this.version //specific highly used properties - var TimingEnabled:Boolean = false - + var TimingEnabled: Boolean = false + //ahash var aHashPrecision = 0 var aHashTolerance = 0 @@ -39,7 +40,7 @@ object PropertiesService extends Logging { /* * Load the properties file from the specified location */ - def loadProperties(defaultLocation:String, userLocation:String = null) = { + def loadProperties(defaultLocation: String, userLocation: String = null) = { info(s"Attempting to load properties from: $defaultLocation") defaultConf = ConfigFactory.load(defaultLocation) if (userLocation != null) { @@ -50,10 +51,10 @@ object PropertiesService extends Logging { } version = new Version(get(PropertyEnum.Version.toString)) info(s"Detected Version: $version") - + //load special properties TimingEnabled = get(PropertyEnum.Timed.toString).toBoolean - + //ahash aHashPrecision = get(PropertyEnum.AhashPrecision.toString).toInt aHashTolerance = get(PropertyEnum.AhashTolerance.toString).toInt @@ -72,7 +73,7 @@ object PropertiesService extends Logging { info("Loaded Special Properties") } - private def cleanAndPrepareNewUserProperties():Properties = { + private def cleanAndPrepareNewUserProperties(): Properties = { //insert special keys here newUserConf.setProperty(PropertyEnum.PreviousVersion.toString, version.parsableToString()) //remove special keys here @@ -80,13 +81,13 @@ object PropertiesService extends Logging { newUserConf } - private def getCleanedMergedUserConf:Config = { - ConfigFactory.parseProperties(cleanAndPrepareNewUserProperties()) withFallback userConf + private def getCleanedMergedUserConf: Config = { + ConfigFactory.parseProperties(cleanAndPrepareNewUserProperties()) withFallback userConf } - def saveConf(location:String) = { + def saveConf(location: String) = { info(s"Saving user properties to $location") - val out:PrintStream = new PrintStream(new FileOutputStream(location, false)) + val out: PrintStream = new PrintStream(new FileOutputStream(location, false)) val userConfToSave = getCleanedMergedUserConf //print to the output stream out.print(userConfToSave.root.render) @@ -94,34 +95,34 @@ object PropertiesService extends Logging { out.close() } - def has(key:String):Boolean = { + def has(key: String): Boolean = { var result = false if (newUserConf.containsKey(key) - || userConf.hasPath(key) - || defaultConf.hasPath(key)) { + || userConf.hasPath(key) + || defaultConf.hasPath(key)) { result = true } result } - def get(key:String, defaultValue:String=null):String = { - var result:String = defaultValue + def get(key: String, defaultValue: String = null): String = { + var result: String = defaultValue //check the latest properties if (newUserConf.containsKey(key)) { - result = newUserConf.getProperty(key) + result = newUserConf.getProperty(key) } //check the loaded user properties else if (userConf.hasPath(key)) { - result = userConf.getString(key) + result = userConf.getString(key) } //check the default properties else if (defaultConf.hasPath(key)) { - result = defaultConf.getString(key) + result = defaultConf.getString(key) } result } - def set(key:String, value:String) = { + def set(key: String, value: String) = { newUserConf.setProperty(key, value) } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/Timing.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/Timing.scala index 50eafe1..d57ff31 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/Timing.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/Timing.scala @@ -2,34 +2,34 @@ package com.sothr.imagetools.engine.util import grizzled.slf4j.Logging -trait Timing extends Logging{ - - def time[R](block: => R): R = { +trait Timing extends Logging { + + def time[R](block: => R): R = { val t0 = System.currentTimeMillis - val result = block // call-by-name + val result = block // call-by-name val t1 = System.currentTimeMillis info("Elapsed time: " + (t1 - t0) + "ms") result } - - def getTime[R](block: => R):Long = { + + def getTime[R](block: => R): Long = { val t0 = System.currentTimeMillis - val result = block // call-by-name + val result = block // call-by-name val t1 = System.currentTimeMillis info("Elapsed time: " + (t1 - t0) + "ms") t1 - t0 } - def getMean(times:Long*):Long = { + def getMean(times: Long*): Long = { getMean(times.toArray[Long]) } - def getMean(times:Array[Long]):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/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala index 1bc9745..be5038a 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala @@ -7,18 +7,18 @@ import grizzled.slf4j.Logging * * Created by drew on 1/6/14. */ -class Version(val versionString:String) extends Logging{ +class Version(val versionString: String) extends Logging { //parse version into parts //typical version string i.e. 0.1.0-DEV-27-060aec7 - val (major,minor,patch,buildTag,buildNumber,buildHash) = { - var version:(Int, Int, Int, String, Int, String) = (0,0,0,"DEV",0,"asdfzxcv") + val (major, minor, patch, buildTag, buildNumber, buildHash) = { + var version: (Int, Int, Int, String, Int, String) = (0, 0, 0, "DEV", 0, "asdfzxcv") try { - val splitVersion = versionString.split("""\.""") - val splitType = splitVersion(splitVersion.length-1).split("""-""") - version = (splitVersion(0).toInt,splitVersion(1).toInt,splitType(0).toInt,splitType(1),splitType(2).toInt,splitType(3)) + val splitVersion = versionString.split( """\.""") + val splitType = splitVersion(splitVersion.length - 1).split( """-""") + version = (splitVersion(0).toInt, splitVersion(1).toInt, splitType(0).toInt, splitType(1), splitType(2).toInt, splitType(3)) } catch { - case nfe:NumberFormatException => error(s"Error parsing number from version string '$versionString'", nfe) - case e:Exception => error(s"Unexpected error parsing version string '$versionString'", e) + case nfe: NumberFormatException => error(s"Error parsing number from version string '$versionString'", nfe) + case e: Exception => error(s"Unexpected error parsing version string '$versionString'", e) } version } @@ -33,33 +33,33 @@ class Version(val versionString:String) extends Logging{ * 3 = this.patch > that.patch * 4 = this.buildTag != that.buildTag */ - def compare(that:Version):Integer = { + def compare(that: Version): Integer = { //Identical Versions if (this.hashCode == that.hashCode) { 0 - // This is at least a major version ahead + // This is at least a major version ahead } else if (this.major > that.major) { 1 - // This is at least a major version behind - } else if (this.major < that.major){ + // This is at least a major version behind + } else if (this.major < that.major) { -1 - // major is the same + // major is the same } else { // This is at least a minor version ahead if (this.minor > that.minor) { 2 - // This is at least a minor version behind + // This is at least a minor version behind } else if (this.minor < that.minor) { -2 - // major.minor are the same + // major.minor are the same } else { // This is at least a patch version ahead if (this.patch > that.patch) { 3 - // This is at least a patch version version + // This is at least a patch version version } else if (this.patch < that.patch) { -3 - //major.minor.patch are all the same + //major.minor.patch are all the same } else { // This is a different build if (this.buildTag != that.buildTag) { @@ -72,18 +72,18 @@ class Version(val versionString:String) extends Logging{ } } - def parsableToString():String = { + def parsableToString(): String = { s"$major.$minor.$patch-$buildTag-$buildNumber-$buildHash" } - override def toString:String = { + override def toString: String = { s"$major.$minor.$patch-$buildTag build:$buildNumber code:$buildHash" } - + override def hashCode(): Int = { - val prime:Int = 37 - val result:Int = 255 - var hash:Int = major + val prime: Int = 37 + val result: Int = 255 + var hash: Int = major hash += minor hash += patch hash += buildTag.hashCode diff --git a/engine/src/test/java/com/sothr/imagetools/engine/AppTest.java b/engine/src/test/java/com/sothr/imagetools/engine/AppTest.java index 5906105..2a3cdba 100644 --- a/engine/src/test/java/com/sothr/imagetools/engine/AppTest.java +++ b/engine/src/test/java/com/sothr/imagetools/engine/AppTest.java @@ -7,31 +7,27 @@ import junit.framework.TestSuite; /** * Unit test for simple App. */ -public class AppTest extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } +public class AppTest extends TestCase { + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest(String testName) { + super(testName); + } - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } + /** + * @return the suite of tests being tested + */ + public static Test suite() { + return new TestSuite(AppTest.class); + } - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } + /** + * Rigourous Test :-) + */ + public void testApp() { + assertTrue(true); + } } diff --git a/engine/src/test/resources/application.conf b/engine/src/test/resources/application.conf index 8bc8020..22d5d4c 100644 --- a/engine/src/test/resources/application.conf +++ b/engine/src/test/resources/application.conf @@ -23,7 +23,7 @@ app { differenceThreshold = 0.90 //control generation of hashes for new images. hash { - precision=64 + precision = 64 } ahash { use = true diff --git a/engine/src/test/resources/hibernate.cfg.xml b/engine/src/test/resources/hibernate.cfg.xml index 829720d..0e71575 100644 --- a/engine/src/test/resources/hibernate.cfg.xml +++ b/engine/src/test/resources/hibernate.cfg.xml @@ -22,15 +22,18 @@ org.hibernate.cache.ehcache.EhCacheRegionFactory - org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + true 1 - 100 + 100 + 50 0 5 - 100 + 100 + diff --git a/engine/src/test/resources/hibernate/Image.hbm.xml b/engine/src/test/resources/hibernate/Image.hbm.xml index 2a19c3e..c9c2457 100644 --- a/engine/src/test/resources/hibernate/Image.hbm.xml +++ b/engine/src/test/resources/hibernate/Image.hbm.xml @@ -11,6 +11,7 @@ - + \ No newline at end of file diff --git a/engine/src/test/resources/logback-minimum-config.xml b/engine/src/test/resources/logback-minimum-config.xml index d4622c7..ace343d 100644 --- a/engine/src/test/resources/logback-minimum-config.xml +++ b/engine/src/test/resources/logback-minimum-config.xml @@ -3,9 +3,9 @@ ImageTools.debug - false + false [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + DEBUG @@ -20,9 +20,9 @@ ImageTools.info - false + false [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + INFO @@ -37,9 +37,9 @@ ImageTools.err - false + false [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + ERROR diff --git a/engine/src/test/scala/com/sothr/imagetools/engine/EngineTest.scala b/engine/src/test/scala/com/sothr/imagetools/engine/EngineTest.scala index 8b6451d..3c45255 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/EngineTest.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/EngineTest.scala @@ -5,28 +5,40 @@ package com.sothr.imagetools.engine * * Created by drew on 1/26/14. */ -class EngineTest extends BaseTest{ +class EngineTest extends BaseTest { test("SequentialEngine Test getImagesForDirectory for sample directory") { - val engine:Engine = new SequentialEngine() - assertResult(3) { engine.getImagesForDirectory("sample").length } + val engine: Engine = new SequentialEngine() + assertResult(3) { + engine.getImagesForDirectory("sample").length + } } test("SequentialEngine Test getSimilarImagesForDirectory for sample directory") { val engine = new SequentialEngine() val similarImages = engine.getSimilarImagesForDirectory("sample") - assertResult(1) { similarImages.length } - assertResult(2) { similarImages(0).similarImages.length } + assertResult(1) { + similarImages.length + } + assertResult(2) { + similarImages(0).similarImages.length + } } - + test("ConcurrentEngine Test getImagesForDirectory for sample directory") { - val engine:Engine = new ConcurrentEngine() - assertResult(3) { engine.getImagesForDirectory("sample").length } + val engine: Engine = new ConcurrentEngine() + assertResult(3) { + engine.getImagesForDirectory("sample").length + } } test("ConcurrentEngine Test getSimilarImagesForDirectory for sample directory") { val engine = new ConcurrentEngine() val similarImages = engine.getSimilarImagesForDirectory("sample") - assertResult(1) { similarImages.length } - assertResult(2) { similarImages(0).similarImages.length } + assertResult(1) { + similarImages.length + } + assertResult(2) { + similarImages(0).similarImages.length + } } } diff --git a/engine/src/test/scala/com/sothr/imagetools/engine/TestParams.scala b/engine/src/test/scala/com/sothr/imagetools/engine/TestParams.scala index 235df0d..b82d2eb 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/TestParams.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/TestParams.scala @@ -1,7 +1,7 @@ package com.sothr.imagetools.engine object TestParams { - val LargeSampleImage1 = "sample/sample_01_large.jpg" - val MediumSampleImage1 = "sample/sample_01_medium.jpg" - val SmallSampleImage1 = "sample/sample_01_small.jpg" + 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/engine/src/test/scala/com/sothr/imagetools/engine/hash/HashServiceTest.scala b/engine/src/test/scala/com/sothr/imagetools/engine/hash/HashServiceTest.scala index ed7fef7..be5da28 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/hash/HashServiceTest.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/hash/HashServiceTest.scala @@ -3,9 +3,8 @@ package com.sothr.imagetools.engine.hash import java.io.File import javax.imageio.ImageIO -import com.sothr.imagetools.engine.{TestParams, BaseTest, AppConfig} import com.sothr.imagetools.engine.dto.ImageHashDTO -import com.sothr.imagetools.TestParams +import com.sothr.imagetools.engine.{AppConfig, BaseTest, TestParams} import net.sf.ehcache.Element import scala.collection.mutable @@ -20,10 +19,10 @@ class HashServiceTest extends BaseTest { // Define the number of runs the benchmarking tests should use val benchmarkRuns = 10 - def dhashTestCase(filePath:String):Long = { - val sample = new File(filePath) - val image = ImageIO.read(sample) - HashService.getDhash(image) + def dhashTestCase(filePath: String): Long = { + val sample = new File(filePath) + val image = ImageIO.read(sample) + HashService.getDhash(image) } test("Benchmark DHash") { @@ -31,21 +30,27 @@ class HashServiceTest extends BaseTest { info("DHash Large Image 3684x2736") val time = new mutable.MutableList[Long]() for (runNum <- 0 until benchmarkRuns) { - time += getTime { dhashTestCase(TestParams.LargeSampleImage1) } + 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 (runNum <- 0 until benchmarkRuns) { - time += getTime { dhashTestCase(TestParams.MediumSampleImage1) } + 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 (runNum <- 0 until benchmarkRuns) { - time += getTime { dhashTestCase(TestParams.SmallSampleImage1) } + 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") @@ -54,15 +59,15 @@ class HashServiceTest extends BaseTest { } 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 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) @@ -78,7 +83,7 @@ class HashServiceTest extends BaseTest { 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) @@ -89,7 +94,7 @@ class HashServiceTest extends BaseTest { 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) @@ -100,17 +105,17 @@ class HashServiceTest extends BaseTest { 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.areDhashSimilar(largeHash,mediumHash)) - assert(HashService.areDhashSimilar(largeHash,smallHash)) - assert(HashService.areDhashSimilar(mediumHash,smallHash)) + assert(HashService.areDhashSimilar(largeHash, mediumHash)) + assert(HashService.areDhashSimilar(largeHash, smallHash)) + assert(HashService.areDhashSimilar(mediumHash, smallHash)) } - def ahashTestCase(filePath:String):Long = { + def ahashTestCase(filePath: String): Long = { val sample = new File(filePath) val image = ImageIO.read(sample) HashService.getAhash(image) @@ -121,21 +126,27 @@ class HashServiceTest extends BaseTest { info("AHash Large Image 3684x2736") val time = new mutable.MutableList[Long]() for (runNum <- 0 until benchmarkRuns) { - time += getTime { ahashTestCase(TestParams.LargeSampleImage1) } + 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 (runNum <- 0 until benchmarkRuns) { - time += getTime { ahashTestCase(TestParams.MediumSampleImage1) } + 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 (runNum <- 0 until benchmarkRuns) { - time += getTime { ahashTestCase(TestParams.SmallSampleImage1) } + 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") @@ -180,12 +191,12 @@ class HashServiceTest extends BaseTest { val largeHash = ahashTestCase(TestParams.LargeSampleImage1) val mediumHash = ahashTestCase(TestParams.MediumSampleImage1) val smallHash = ahashTestCase(TestParams.SmallSampleImage1) - assert(HashService.areAhashSimilar(largeHash,mediumHash)) - assert(HashService.areAhashSimilar(largeHash,smallHash)) - assert(HashService.areAhashSimilar(mediumHash,smallHash)) + assert(HashService.areAhashSimilar(largeHash, mediumHash)) + assert(HashService.areAhashSimilar(largeHash, smallHash)) + assert(HashService.areAhashSimilar(mediumHash, smallHash)) } - def phashTestCase(filePath:String):Long = { + def phashTestCase(filePath: String): Long = { val sample = new File(filePath) val image = ImageIO.read(sample) HashService.getPhash(image) @@ -196,21 +207,27 @@ class HashServiceTest extends BaseTest { info("PHash Large Image 3684x2736") val time = new mutable.MutableList[Long]() for (runNum <- 0 until benchmarkRuns) { - time += getTime { phashTestCase(TestParams.LargeSampleImage1) } + 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 (runNum <- 0 until benchmarkRuns) { - time += getTime { phashTestCase(TestParams.MediumSampleImage1) } + 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 (runNum <- 0 until benchmarkRuns) { - time += getTime { phashTestCase(TestParams.SmallSampleImage1) } + 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") @@ -255,12 +272,12 @@ class HashServiceTest extends BaseTest { val largeHash = phashTestCase(TestParams.LargeSampleImage1) val mediumHash = phashTestCase(TestParams.MediumSampleImage1) val smallHash = phashTestCase(TestParams.SmallSampleImage1) - assert(HashService.arePhashSimilar(largeHash,mediumHash)) - assert(HashService.arePhashSimilar(largeHash,smallHash)) - assert(HashService.arePhashSimilar(mediumHash,smallHash)) + assert(HashService.arePhashSimilar(largeHash, mediumHash)) + assert(HashService.arePhashSimilar(largeHash, smallHash)) + assert(HashService.arePhashSimilar(mediumHash, smallHash)) } - def md5TestCase(filePath:String):String = { + def md5TestCase(filePath: String): String = { HashService.getMD5(filePath) } @@ -269,21 +286,27 @@ class HashServiceTest extends BaseTest { info("MD5 Large Image 3684x2736") val time = new mutable.MutableList[Long]() for (runNum <- 0 until benchmarkRuns) { - time += getTime { md5TestCase(TestParams.LargeSampleImage1) } + 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 (runNum <- 0 until benchmarkRuns) { - time += getTime { md5TestCase(TestParams.MediumSampleImage1) } + 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 (runNum <- 0 until benchmarkRuns) { - time += getTime { md5TestCase(TestParams.SmallSampleImage1) } + 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") @@ -312,19 +335,19 @@ class HashServiceTest extends BaseTest { assert(hash == "b137131bd55896c747286e4d247b845e") } - def imageHashTestWithCacheCase(filePath:String):ImageHashDTO = { + def imageHashTestWithCacheCase(filePath: String): ImageHashDTO = { val cache = AppConfig.cacheManager.getCache("images") - var result:ImageHashDTO = null + var result: ImageHashDTO = null if (cache.get(filePath) != null) { result = cache.get(filePath).getObjectValue.asInstanceOf[ImageHashDTO] } else { result = imageHashTestCase(filePath) - cache.put(new Element(filePath,result)) + cache.put(new Element(filePath, result)) } result } - def imageHashTestCase(filePath:String):ImageHashDTO = { + def imageHashTestCase(filePath: String): ImageHashDTO = { HashService.getImageHashes(filePath) } @@ -333,21 +356,27 @@ class HashServiceTest extends BaseTest { info("getImageHashes with cache Large Image 3684x2736") val time = new mutable.MutableList[Long]() for (runNum <- 0 until benchmarkRuns) { - time += getTime { imageHashTestWithCacheCase(TestParams.LargeSampleImage1) } + time += getTime { + imageHashTestWithCacheCase(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 with cache Medium Image 1824x1368") for (runNum <- 0 until benchmarkRuns) { - time += getTime { imageHashTestWithCacheCase(TestParams.MediumSampleImage1) } + time += getTime { + imageHashTestWithCacheCase(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 with cache Small Image 912x684") for (runNum <- 0 until benchmarkRuns) { - time += getTime { imageHashTestWithCacheCase(TestParams.SmallSampleImage1) } + time += getTime { + imageHashTestWithCacheCase(TestParams.SmallSampleImage1) + } } val smallMean = getMean(time.toArray[Long]) info(s"The mean time of ${time.size} tests for small was: $smallMean ms") @@ -360,21 +389,27 @@ class HashServiceTest extends BaseTest { info("getImageHashes Large Image 3684x2736") val time = new mutable.MutableList[Long]() for (runNum <- 0 until benchmarkRuns) { - time += getTime { imageHashTestCase(TestParams.LargeSampleImage1) } + 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) } + 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) } + 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") @@ -386,9 +421,9 @@ class HashServiceTest extends BaseTest { val largeHash = imageHashTestCase(TestParams.LargeSampleImage1) val mediumHash = imageHashTestCase(TestParams.MediumSampleImage1) val smallHash = imageHashTestCase(TestParams.SmallSampleImage1) - assert(HashService.areImageHashesSimilar(largeHash,mediumHash)) - assert(HashService.areImageHashesSimilar(largeHash,smallHash)) - assert(HashService.areImageHashesSimilar(mediumHash,smallHash)) + assert(HashService.areImageHashesSimilar(largeHash, mediumHash)) + assert(HashService.areImageHashesSimilar(largeHash, smallHash)) + assert(HashService.areImageHashesSimilar(mediumHash, smallHash)) } test("Calculate ImageHash Large Sample Image 1") { diff --git a/engine/src/test/scala/com/sothr/imagetools/engine/image/ImageFilterTest.scala b/engine/src/test/scala/com/sothr/imagetools/engine/image/ImageFilterTest.scala index 657808c..8966525 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/image/ImageFilterTest.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/image/ImageFilterTest.scala @@ -9,10 +9,10 @@ import com.sothr.imagetools.engine.BaseTest * * Created by drew on 1/26/14. */ -class ImageFilterTest extends BaseTest{ +class ImageFilterTest extends BaseTest { test("Confirm ImageFilter Works") { - val filter:ImageFilter = new ImageFilter() + val filter: ImageFilter = new ImageFilter() val bogusDirectory = new File(".") assert(filter.accept(bogusDirectory, "test.png")) assert(filter.accept(bogusDirectory, "test.bmp")) @@ -23,12 +23,20 @@ class ImageFilterTest extends BaseTest{ } test("Confirm ImageFiler Fails") { - val filter:ImageFilter = new ImageFilter() + val filter: ImageFilter = new ImageFilter() val bogusDirectory = new File(".") - assertResult(false) { filter.accept(bogusDirectory,"test") } - assertResult(false) { filter.accept(bogusDirectory,"test.mp4") } - assertResult(false) { filter.accept(bogusDirectory,"test.gif.mp4") } - assertResult(false) { filter.accept(bogusDirectory,"") } + assertResult(false) { + filter.accept(bogusDirectory, "test") + } + assertResult(false) { + filter.accept(bogusDirectory, "test.mp4") + } + assertResult(false) { + filter.accept(bogusDirectory, "test.gif.mp4") + } + assertResult(false) { + filter.accept(bogusDirectory, "") + } } } diff --git a/gui/pom.xml b/gui/pom.xml index 508c64f..b08712a 100644 --- a/gui/pom.xml +++ b/gui/pom.xml @@ -1,5 +1,5 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -27,16 +27,16 @@ ImageTools-Engine - ch.qos.logback - logback-core + ch.qos.logback + logback-core - ch.qos.logback - logback-classic + ch.qos.logback + logback-classic - ch.qos.logback - logback-access + ch.qos.logback + logback-access org.slf4j @@ -84,18 +84,18 @@ - - maven-antrun-plugin - 1.4 - + + maven-antrun-plugin + 1.4 + prepare - process-resources - + process-resources + - - - + + + @@ -116,7 +116,7 @@ run - + diff --git a/gui/src/includes/logback.xml b/gui/src/includes/logback.xml index 4d0b07f..077b4d1 100644 --- a/gui/src/includes/logback.xml +++ b/gui/src/includes/logback.xml @@ -4,10 +4,10 @@ - - false + + false [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n - + INFO @@ -16,9 +16,9 @@ ImageTools.debug - false + false [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + DEBUG @@ -34,9 +34,9 @@ ImageTools.info - false + false [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + INFO @@ -52,9 +52,9 @@ ImageTools.err - false + false [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - + ERROR diff --git a/gui/src/main/java/com/sothr/imagetools/ui/App.java b/gui/src/main/java/com/sothr/imagetools/ui/App.java index 7958dff..e559a0a 100644 --- a/gui/src/main/java/com/sothr/imagetools/ui/App.java +++ b/gui/src/main/java/com/sothr/imagetools/ui/App.java @@ -17,71 +17,69 @@ import java.util.List; /** * Image Tools */ -public class App extends Application -{ +public class App extends Application { - private static Logger logger; + private static Logger logger; - private static final String MAINGUI_FXML = "fxml/mainapp/MainApp.fxml"; + private static final String MAINGUI_FXML = "fxml/mainapp/MainApp.fxml"; - public static void main( String[] args ) - { - AppConfig.configureApp(); + public static void main(String[] args) { + AppConfig.configureApp(); - try { - //try to run the UI - launch(args); - } catch (Exception ex) { - logger.error("A fatal error has occurred: ", ex); - //show popup about the error to the user then exit - } + try { + //try to run the UI + launch(args); + } catch (Exception ex) { + logger.error("A fatal error has occurred: ", ex); + //show popup about the error to the user then exit } + } - @Override - public void init() throws Exception{ - AppConfig.configureApp(); - logger = LoggerFactory.getLogger(this.getClass()); - logger.info("Initializing Image Tools"); - List parameters = this.getParameters().getRaw(); - logger.info(String.format("Application was called with '%s' parameters", parameters.toString())); - super.init(); - } + @Override + public void init() throws Exception { + AppConfig.configureApp(); + logger = LoggerFactory.getLogger(this.getClass()); + logger.info("Initializing Image Tools"); + List parameters = this.getParameters().getRaw(); + logger.info(String.format("Application was called with '%s' parameters", parameters.toString())); + super.init(); + } - @Override - public void start(Stage primaryStage) throws Exception { - logger.info("Image-Tools is starting"); - logger.info(String.format("Launching GUI with FXML file %s", MAINGUI_FXML)); - //store the primary stage globally for reference in popups and the like - AppConfig.setPrimaryStage(primaryStage); - try { - Parent root = FXMLLoader.load(ResourceLoader.get().getResource(MAINGUI_FXML)); - primaryStage.setScene(new Scene(root)); - //config main scene - primaryStage.setTitle("Image Tools"); - primaryStage.setMinHeight(600.0); - primaryStage.setMinWidth(800.0); - primaryStage.setResizable(true); - //show main scene - primaryStage.show(); - } catch (IOException ioe) { - String message = String.format("Unable to load FXML file: %s", MAINGUI_FXML); - ImageToolsException ite = new ImageToolsException(message, ioe); - logger.error(message, ioe); - throw ite; - } catch (Exception ex) { - String message = "An unhandled exception was thrown by the GUI"; - ImageToolsException ite = new ImageToolsException(message, ex); - logger.error(message, ex); - throw ite; - } + @Override + public void start(Stage primaryStage) throws Exception { + logger.info("Image-Tools is starting"); + logger.info(String.format("Launching GUI with FXML file %s", MAINGUI_FXML)); + //store the primary stage globally for reference in popups and the like + AppConfig.setPrimaryStage(primaryStage); + try { + Parent root = FXMLLoader.load(ResourceLoader.get().getResource(MAINGUI_FXML)); + primaryStage.setScene(new Scene(root)); + //config main scene + primaryStage.setTitle("Image Tools"); + primaryStage.setMinHeight(600.0); + primaryStage.setMinWidth(800.0); + primaryStage.setResizable(true); + //show main scene + primaryStage.show(); + } catch (IOException ioe) { + String message = String.format("Unable to load FXML file: %s", MAINGUI_FXML); + ImageToolsException ite = new ImageToolsException(message, ioe); + logger.error(message, ioe); + throw ite; + } catch (Exception ex) { + String message = "An unhandled exception was thrown by the GUI"; + ImageToolsException ite = new ImageToolsException(message, ex); + logger.error(message, ex); + throw ite; } + } - @Override - public void stop() throws Exception { - logger.info("Image-Tools is shutting down"); - AppConfig.shutdown(); - super.stop(); - //force the JVM to close - System.exit(0); - } + @Override + public void stop() throws Exception { + logger.info("Image-Tools is shutting down"); + AppConfig.shutdown(); + super.stop(); + //force the JVM to close + System.exit(0); + } } diff --git a/gui/src/main/resources/fxml/mainapp/MainApp.fxml b/gui/src/main/resources/fxml/mainapp/MainApp.fxml index f9811fc..bda4bee 100644 --- a/gui/src/main/resources/fxml/mainapp/MainApp.fxml +++ b/gui/src/main/resources/fxml/mainapp/MainApp.fxml @@ -5,92 +5,116 @@ - + - +

- + - + - - + + - + - + - + - - - - - +