implement img-core a core module
This commit is contained in:
parent
13d3c97d4f
commit
cb00500705
15 changed files with 109 additions and 53 deletions
|
@ -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>
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
20
img-core/build.gradle.kts
Normal 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()
|
||||
}
|
7
img-core/src/main/java/module-info.java
Normal file
7
img-core/src/main/java/module-info.java
Normal 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;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package dev.nuculabs.imagetagger.ai
|
||||
package dev.nuculabs.imagetagger.core.abstractions
|
||||
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.InputStream
|
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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!")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,4 +4,5 @@ plugins {
|
|||
rootProject.name = "ImageTagger-Solution"
|
||||
include "img-ui"
|
||||
include 'img-ai'
|
||||
include 'img-core'
|
||||
|
||||
|
|
Loading…
Reference in a new issue