implement img-core a core module

This commit is contained in:
Denis-Cosmin Nutiu 2024-04-27 15:33:37 +03:00
parent 13d3c97d4f
commit cb00500705
15 changed files with 109 additions and 53 deletions

View file

@ -9,6 +9,7 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/img-ai" />
<option value="$PROJECT_DIR$/img-core" />
<option value="$PROJECT_DIR$/img-ui" />
</set>
</option>

View file

@ -14,6 +14,7 @@ repositories {
}
dependencies {
implementation(project(":img-core"))
implementation("com.microsoft.onnxruntime:onnxruntime:1.17.1")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")

View file

@ -3,5 +3,6 @@ module dev.nuculabs.imagetagger.ai {
requires java.desktop;
requires java.logging;
requires kotlin.stdlib;
requires dev.nuculabs.imagetagger.core;
exports dev.nuculabs.imagetagger.ai;
}

View file

@ -3,6 +3,7 @@ package dev.nuculabs.imagetagger.ai
import ai.onnxruntime.OnnxTensor
import ai.onnxruntime.OrtEnvironment
import ai.onnxruntime.OrtSession
import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
import java.awt.image.BufferedImage
import java.io.Closeable
import java.io.IOException

20
img-core/build.gradle.kts Normal file
View file

@ -0,0 +1,20 @@
plugins {
id("java")
kotlin("jvm") version "1.8.22"
id("org.javamodularity.moduleplugin") version "1.8.12"
}
group = "dev.nuculabs.imagetagger.core"
version = "1.1"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}

View file

@ -0,0 +1,7 @@
module dev.nuculabs.imagetagger.core {
requires kotlin.stdlib;
requires java.desktop;
requires java.logging;
exports dev.nuculabs.imagetagger.core;
exports dev.nuculabs.imagetagger.core.abstractions;
}

View file

@ -0,0 +1,52 @@
package dev.nuculabs.imagetagger.core
import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
import java.awt.image.BufferedImage
import java.io.File
import java.util.logging.Logger
import javax.imageio.ImageIO
/**
* AnalyzedImage represents an Analyzed Image inside the ImageTagger application.
*/
class AnalyzedImage(private val file: File, imageTagsPrediction: IImageTagsPrediction) {
private var bufferedImage: BufferedImage? = null
private var predictedTags: List<String> = emptyList()
private val logger: Logger = Logger.getLogger("AnalyzedImage")
private var error: String = ""
/**
* Initializes the analyzed image and predicts its tags.
*/
init {
try {
bufferedImage = ImageIO.read(File(file.absolutePath))
predictedTags = imageTagsPrediction.predictTags(bufferedImage!!)
} catch (e: Exception) {
logger.warning("Error while predicting images $e")
error = e.message.toString()
}
}
/**
* Returns the prediction error
*/
fun errorMessage(): String {
return error;
}
/**
* Returns the absolute file path of the image.
*/
fun absolutePath(): String {
return file.absolutePath;
}
/**
* Returns the predicted tags.
*/
fun tags(): List<String> {
return predictedTags
}
}

View file

@ -1,4 +1,4 @@
package dev.nuculabs.imagetagger.ai
package dev.nuculabs.imagetagger.core.abstractions
import java.awt.image.BufferedImage
import java.io.InputStream

View file

@ -38,6 +38,7 @@ javafx {
dependencies {
implementation(project(":img-ai"))
implementation(project(":img-core"))
implementation('org.controlsfx:controlsfx:11.1.2')
implementation('com.dlsc.formsfx:formsfx-core:11.6.0') {
exclude(group: 'org.openjfx')

View file

@ -14,6 +14,7 @@ module dev.nuculabs.imagetagger.ui {
requires org.kordamp.ikonli.fontawesome5;
requires kotlinx.coroutines.core;
requires dev.nuculabs.imagetagger.ai;
requires dev.nuculabs.imagetagger.core;
opens dev.nuculabs.imagetagger.ui to javafx.fxml, javafx.graphics;
opens dev.nuculabs.imagetagger.ui.controls to javafx.fxml, javafx.graphics;

View file

@ -1,6 +1,6 @@
package dev.nuculabs.imagetagger.ui
import dev.nuculabs.imagetagger.ai.IImageTagsPrediction
import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
/**
* BasicServiceLocator is implemented to avoid polluting the apps with singletons.

View file

@ -1,6 +1,6 @@
package dev.nuculabs.imagetagger.ui
import dev.nuculabs.imagetagger.ai.IImageTagsPrediction
import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
import dev.nuculabs.imagetagger.ai.ImageTagsPrediction
import javafx.application.Application
import javafx.application.Platform

View file

@ -1,5 +1,6 @@
package dev.nuculabs.imagetagger.ui
import dev.nuculabs.imagetagger.core.AnalyzedImage
import dev.nuculabs.imagetagger.ui.controls.ImageTagsEntryControl
import dev.nuculabs.imagetagger.ui.controls.ImageTagsSessionHeader
import javafx.application.Platform
@ -11,13 +12,11 @@ import javafx.scene.layout.HBox
import javafx.scene.layout.Priority
import javafx.scene.layout.VBox
import javafx.stage.FileChooser
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Semaphore
import java.util.concurrent.atomic.AtomicInteger
import java.util.logging.Logger
import javax.imageio.ImageIO
class MainPageController {
@ -34,7 +33,7 @@ class MainPageController {
private val maxImagesPredictionInProgress = Runtime.getRuntime().availableProcessors()
/**
* Semaphore to limit the maximum amount of predictions submitted to the tread pool.
* Semaphore to limit the maximum number of predictions submitted to the tread pool.
*/
private val workerSemaphore: Semaphore = Semaphore(maxImagesPredictionInProgress)
@ -54,8 +53,8 @@ class MainPageController {
private val imageTagsPrediction = BasicServiceLocator.getInstance().imageTagsPrediction
/**
* A boolean that when set to true it will stop the current image tagging operation.
* When a new operation is started the boolean is reset to false.
* A boolean that when set to, true it will stop the current image tagging operation.
* When a new operation is started, the boolean is reset to false.
*/
private var isCurrentTagsOperationCancelled: Boolean = false
@ -112,19 +111,12 @@ class MainPageController {
return@submit
}
predictImageTags(
filePath,
onError = {
workerSemaphore.release()
}
) { imagePath, imageTags ->
// Add newly predicted tags to UI.
Platform.runLater {
// Add image and prediction to the view.
addNewImagePredictionEntry(imagePath, imageTags)
updateProgressBar()
workerSemaphore.release()
}
val analyzedImage = AnalyzedImage(filePath, this.imageTagsPrediction)
workerSemaphore.release()
Platform.runLater {
// Add image and prediction to the view.
addNewImagePredictionEntry(analyzedImage)
updateProgressBar()
}
}
}
@ -142,38 +134,15 @@ class MainPageController {
cancelButton.isVisible = false
}
/**
* Predicts an image tags and executes an action with it.
*
* @param filePath - The image file's absolute path.
*/
fun predictImageTags(
filePath: File,
onError: (Exception) -> Unit,
onSuccess: (String, List<String>) -> Unit
) {
try {
// Get predictions for the image.
val imageFile = ImageIO.read(File(filePath.absolutePath))
val tags: List<String> = imageTagsPrediction.predictTags(imageFile)
onSuccess(filePath.absolutePath, tags)
} catch (e: Exception) {
logger.warning("Error while predicting images $e")
onError(e)
}
}
/**
* Updates the UI with a new ImagePredictionEntry.
*
* @param imagePath - The image path.
* @param imageTags - The image's tags.
* @param analyzedImage - The analyzed image instance.
*/
fun addNewImagePredictionEntry(
imagePath: String,
imageTags: List<String>,
analyzedImage: AnalyzedImage,
) {
verticalBox.children.add(ImageTagsEntryControl(imagePath, imageTags))
verticalBox.children.add(ImageTagsEntryControl(analyzedImage))
verticalBox.children.add(Separator())
}

View file

@ -1,5 +1,6 @@
package dev.nuculabs.imagetagger.ui.controls
import dev.nuculabs.imagetagger.core.AnalyzedImage
import dev.nuculabs.imagetagger.ui.alerts.ErrorAlert
import javafx.fxml.FXML
import javafx.fxml.FXMLLoader
@ -21,7 +22,7 @@ import java.util.logging.Logger
/**
* This class is used to create a custom control for the image prediction entry.
*/
class ImageTagsEntryControl(private val imagePath: String, predictions: List<String>) : HBox() {
class ImageTagsEntryControl(private val image: AnalyzedImage) : HBox() {
private val logger: Logger = Logger.getLogger("ImageTagsEntryControl")
/**
@ -59,8 +60,8 @@ class ImageTagsEntryControl(private val imagePath: String, predictions: List<Str
} catch (exception: IOException) {
throw RuntimeException(exception)
}
setImage(imagePath)
setText(predictions)
setImage(image.absolutePath())
setText(image.tags())
setupEventHandlers()
}
@ -111,11 +112,11 @@ class ImageTagsEntryControl(private val imagePath: String, predictions: List<Str
if (Desktop.isDesktopSupported()) {
val desktop = Desktop.getDesktop()
if (desktop.isSupported(Desktop.Action.OPEN)) {
desktop.open(File(imagePath))
desktop.open(File(image.absolutePath()))
}
} else {
logger.severe("Cannot open image $imagePath. Desktop action not supported!")
ErrorAlert("Can't open file: $imagePath\nOperation is not supported!")
logger.severe("Cannot open image ${image.absolutePath()}. Desktop action not supported!")
ErrorAlert("Can't open file: ${image.absolutePath()}\nOperation is not supported!")
}
}

View file

@ -4,4 +4,5 @@ plugins {
rootProject.name = "ImageTagger-Solution"
include "img-ui"
include 'img-ai'
include 'img-core'