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 3db2a56..3ef14c4 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala @@ -20,7 +20,7 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { implicit val timeout = Timeout(30, TimeUnit.SECONDS) override def setSearchedListener(listenerRef: ActorRef) = { - + this.searchedListener = listenerRef; } override def setProcessedListener(listenerRef: ActorRef) = { @@ -61,29 +61,15 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { } val f = engineSimilarityController ? EngineGetSimilarityResults val result = Await.result(f, timeout.duration).asInstanceOf[List[SimilarImages]] - //process the result into a list we want in cleanedSimilarImages - var count = 0 - 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 - } - } - } + val cleanedSimilarImages = this.processSimilarities(result) var similarCount = 0 for (similarImage <- cleanedSimilarImages) { similarCount += 1 + similarImage.similarImages.size } info(s"Finished processing ${images.size} images. Found $similarCount similar images") - cleanedSimilarImages.toList + cleanedSimilarImages } def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { 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 086634d..5afe288 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala @@ -40,13 +40,16 @@ abstract class Engine extends Logging { val files = directory.listFiles(imageFilter) if (files != null) { fileList ++= files - info(s"Found ${files.length} files that are images in directory: $directoryPath") + debug(s"Found ${files.length} files that are images in directory: $directoryPath") if (recursive) { val directoryFilter = new DirectoryFilter val directories = directory.listFiles(directoryFilter) for (directory <- directories) { fileList ++= getAllImageFiles(directory.getAbsolutePath, recursive, recursiveDepth - 1) + this.searchedListener ! SubmitMessage(s"Found ${fileList.length} files to process") } + } else { + this.searchedListener ! SubmitMessage(s"Found ${fileList.length} files to process") } } } @@ -73,6 +76,41 @@ abstract class Engine extends Logging { deleteImage(image) } } + + /** + * Go through the list of similarities and group them so that they represent actual similarities + * + * For example. A is similar to B and C is similar to B but A is was not considered similar to C. Therefore A B and C should be considered similar unless otherwise noted. + * + * @param similarList a list of detected similar images + * @return a grouped and combined list of similar images + */ + def processSimilarities(similarList: List[SimilarImages]): List[SimilarImages] = { + //process the result into a list we want in cleanedSimilarImages + var count = 0 + val cleanedSimilarImages = new mutable.MutableList[SimilarImages]() + val ignoreSet = new mutable.HashSet[Image]() + for (similarImages <- similarList) { + count += 1 + if (count % 25 == 0 || count == similarList.length) debug(s"Cleaning similar image $count/${similarList.length} ${similarList.length - count} left to clean") + if (!ignoreSet.contains(similarImages.rootImage)) { + cleanedSimilarImages += similarImages + ignoreSet += similarImages.rootImage + for (image <- similarImages.similarImages) { + ignoreSet += image + } + } + //rewrite todo: + /* + Go through all the images. If a similar image for the image doesn't exist, create it. if it does, + add it to that similar image. The end result is that all images should be grouped according to + their similar images and images that are similar to them. + */ + } + + //return a non mutable cleaned list + cleanedSimilarImages.toList + } } case class SubmitMessage(message: String) 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 74a141f..b795f44 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala @@ -66,7 +66,7 @@ class SequentialEngine extends Engine with Logging { } } info(s"Finished processing ${images.size} images. Found $similarCount similar images") - allSimilarImages.toList + this.processSimilarities(allSimilarImages.toList) } def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { diff --git a/gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTilePane.scala b/gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTilePane.scala index 5979e25..49f0004 100644 --- a/gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTilePane.scala +++ b/gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTilePane.scala @@ -165,24 +165,6 @@ class ImageTilePaneSelectionModel[ImageTile](parentTilePane: ImageTilePane) exte this.selectedIndexes.add(0) } - private def clearSelectionFormatting() = { - val iterator = this.parentTilePane.getChildren.iterator() - while (iterator.hasNext) { - //remove the selection styling - val imageTile: VBox = iterator.next().asInstanceOf[VBox] - imageTile.setBorder(Border.EMPTY) - } - } - - private def setSelectionFormatting(index: Int): Unit = { - setSelectionFormatting(this.parentTilePane.getChildren.get(index).asInstanceOf[ImageTile]) - } - - private def setSelectionFormatting(imageTile: ImageTile) = { - val borderStroke = new BorderStroke(Color.BLUE, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN) - imageTile.asInstanceOf[VBox].setBorder(new Border(borderStroke)) - } - override def selectLast(): Unit = { clearSelectionFormatting this.selectedIndexes.clear() @@ -239,6 +221,15 @@ class ImageTilePaneSelectionModel[ImageTile](parentTilePane: ImageTilePane) exte } } + private def setSelectionFormatting(index: Int): Unit = { + setSelectionFormatting(this.parentTilePane.getChildren.get(index).asInstanceOf[ImageTile]) + } + + private def setSelectionFormatting(imageTile: ImageTile) = { + val borderStroke = new BorderStroke(Color.BLUE, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN) + imageTile.asInstanceOf[VBox].setBorder(new Border(borderStroke)) + } + override def select(obj: ImageTile): Unit = { if (this.parentTilePane.getChildren.contains(obj)) { clearSelectionFormatting @@ -248,6 +239,15 @@ class ImageTilePaneSelectionModel[ImageTile](parentTilePane: ImageTilePane) exte } } + private def clearSelectionFormatting() = { + val iterator = this.parentTilePane.getChildren.iterator() + while (iterator.hasNext) { + //remove the selection styling + val imageTile: VBox = iterator.next().asInstanceOf[VBox] + imageTile.setBorder(Border.EMPTY) + } + } + override def isEmpty: Boolean = { this.parentTilePane.getChildren.isEmpty } diff --git a/gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala b/gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala index d54892c..0fd308c 100644 --- a/gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala +++ b/gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala @@ -66,6 +66,7 @@ class AppController extends Logging { // configure the listener guiListener ! SetupListener(progressBar, progressLabel) // tell the engine to use our listener + this.engine.setSearchedListener(guiListener) this.engine.setProcessedListener(guiListener) this.engine.setSimilarityListener(guiListener) // Initialize the progress label @@ -282,44 +283,12 @@ class AppController extends Logging { } } - @FXML - def showSimilarImages(event: ActionEvent) = { - resetPaginator() - imageTilePane.getChildren.setAll(new java.util.ArrayList[Node]()) - - val f: Future[List[Image]] = Future { - val similarImages = engine.getSimilarImagesForDirectory(currentDirectory, recursive = doRecursiveProcessing.isSelected) - val tempImages = new mutable.MutableList[Image]() - for (similarImage <- similarImages) { - debug(s"Adding similar images ${similarImage.rootImage.toString} to app") - tempImages += similarImage.rootImage - similarImage.similarImages.foreach(image => tempImages += image) - } - tempImages.toList - } - - f onComplete { - case Success(images) => - info(s"Displaying ${images.length} similar images") - Platform.runLater(new Runnable() { - override def run() { - setPagesContent(images) - showPage(0) - } - }) - case Failure(t) => - error("An Error Occurred", t) - } - } - - //endregion - def resetPaginator() = { this.paginator.setDisable(true) this.paginator.setPageCount(1) } - //todo: include a templating engine for rendering information + //endregion def setPagesContent(images: List[Image]) = { this.currentImages = images @@ -330,6 +299,8 @@ class AppController extends Logging { this.paginator.setDisable(false) } + //todo: include a templating engine for rendering information + def showPage(pageIndex: Integer) = { val itemsPerPage = PropertiesService.get("app.ui.thumbsPerPage", "100").toInt val startIndex = pageIndex * itemsPerPage @@ -353,6 +324,36 @@ class AppController extends Logging { this.imageTilePane } + @FXML + def showSimilarImages(event: ActionEvent) = { + resetPaginator() + imageTilePane.getChildren.setAll(new java.util.ArrayList[Node]()) + + val f: Future[List[Image]] = Future { + val similarImages = engine.getSimilarImagesForDirectory(currentDirectory, recursive = doRecursiveProcessing.isSelected) + val tempImages = new mutable.MutableList[Image]() + for (similarImage <- similarImages) { + debug(s"Adding similar images ${similarImage.rootImage.toString} to app") + tempImages += similarImage.rootImage + similarImage.similarImages.foreach(image => tempImages += image) + } + tempImages.toList + } + + f onComplete { + case Success(images) => + info(s"Displaying ${images.length} similar images") + Platform.runLater(new Runnable() { + override def run() { + setPagesContent(images) + showPage(0) + } + }) + case Failure(t) => + error("An Error Occurred", t) + } + } + /** * Show a plain text utility dialog *