Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
### v1.1.0 - 30/10/2025

### Changed
* Project structure refactored from **core + extensions** to **core + ml**.
* Imports updated accordingly:
- **core** → minimal runtime: shared interfaces, data classes, embeddings, media helpers, processor execution, and efficient batch/concurrent processing.
- **ml** → ML infrastructure and models: model loaders, base models, embedding providers (e.g., CLIP), and few-shot classifiers. Optional or experimental ML-related features can be added under `ml/providers`.
- Both modules organize contracts and data classes under their own `data/` packages.

* All `IEmbeddingProviders` must now implement `embedBatch`
* `ClipImageEmbedder` and `ClipTextEmbedder` now accept context instead of resources
* `BatchProcessor` now accepts a `Context` (uses `applicationContext` internally).

### Removed

* `Organiser` class removed.

### Notes
This release replaces the old `core` and `extensions` structure.
If you are upgrading from ≤1.0.4, update imports and Gradle dependencies.


## v1.0.4 – 19/10/2025

### Changed
Expand Down
77 changes: 41 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@
* [Installation](#installation)

+ [1. Install Core Module](#1-install-core-module)
+ [2. Install Extensions Module (Optional)](#2-install-extensions-module-optional)
+ [2. Install ML Module (Optional)](#2-install-ml-module-optional)
* [Design Choices](#design-choices)

+ [Core and Extensions](#core-and-extensions)
+ [Core and ML](#core-and-ml)``
+ [Constraints](#constraints)
+ [Model](#model)
+ [Embedding Storage](#embedding-storage)
- [Benchmark Summary](#benchmark-summary)

* [Gradle / Kotlin Setup Notes](#gradle--kotlin-setup-notes)

<a name="overview"></a>

## **Overview**

Expand All @@ -29,12 +28,10 @@ SmartScanSdk is a modular Android SDK that powers the **SmartScan app**. It prov
* On-device ML inference
* Semantic media indexing and search
* Few shot classification
* Efficient batch processing

The SDK is **extensible**, allowing developers to add ML models or features without bloating the core runtime.

**Long-term vision:** SmartScanSdk was designed with the goal of becoming a **C++ cross-platform SDK** for **search and classification**, capable of running both **offline on edge devices** and in **bulk cloud environments**.

> **Note:** Because of its long-term cross-platform goals, some features may be experimental (extensions). However, the SDK is generally considered stable, as it is actively used in the SmartScan app, aswell as others``.
> **Note:** The SDK is designed to be flexible, but its primary use is for the SmartScan app and other apps I am developing. It is also subject to rapid experimental changes.

---

Expand All @@ -43,52 +40,60 @@ The SDK is **extensible**, allowing developers to add ML models or features with
```
SmartScanSdk/
├─ core/ # Essential functionality
│ ├─ ml/ # On-device ML infra + models
│ │ ├─ embeddings/ # Generic + CLIP embeddings
│ │ └─ models/ # Model base + loaders
│ ├─ processors/ # Batch processing + pipelines
│ └─ utils/ # General-purpose helpers
├─ extensions/ # Experimental / Optional features
│ ├─ embeddings/ # File-based or custom embedding stores
│ ├─ indexers/ # Media indexers
│ └─ organisers/ # Higher-level orchestration
│ ├─ data/ # Data classes and processor interfaces
│ ├─ embeddings/ # Embedding utilities and file-based stores
│ ├─ indexers/ # Image and video indexers
│ ├─ media/ # Media helpers (image/video utils)
│ └─ processors/ # Batch processing and memory helpers
├─ build.gradle
└─ settings.gradle
└─ ml/ # On-device ML infrastructure + models
├─ data/ # Model loaders and data classes
└─ models/ # Base ML models and providers
└─ providers/
└─ embeddings/ # Embedding providers
├─ clip/ # CLIP image & text embedder
└─ FewShotClassifier.kt # Few-shot classifier

├─ build.gradle
└─ settings.gradle
```

**Notes:**

* `core` and `extensions` are standalone Gradle modules.
* `core` and `ml` are standalone Gradle modules.
* Both are set up for **Maven publishing**.
* The structure replaces the old `core` and `extensions` module in versions ≤1.0.4

---
---

## **Installation**

### **1. Install Core Module**

```gradle
implementation("com.github.dev-diaries41:smartscan-core:1.0.0")
implementation("com.github.dev-diaries41.smartscan-sdk:smartscan-core:1.1.0")
```

### **2. Install Extensions Module (Optional)**
### **2. Install ML Module (Optional)**

```gradle
implementation("com.github.dev-diaries41:smartscan-extensions:1.0.0")
implementation("com.github.dev-diaries41.smartscan-sdk:smartscan-ml:1.1.0")
```

> `extensions` depends on `core`, so including it is enough if you need both.
> `ml` depends on `core`, so including it is enough if you need both.

---

## **Design Choices**

### Core and Extensions
### Core and ML

* **core** → minimal runtime: shared interfaces, data classes, embeddings, media helpers, processor execution, and efficient batch/concurrent processing.
* **ml** → ML infrastructure and models: model loaders, base models, embedding providers (e.g., CLIP), and few-shot classifiers. Optional or experimental ML-related features can be added under `ml/providers`.

* **core** → minimal runtime: shared interfaces, embeddings, model execution, efficient batch/concurrent processing.
* **extensions** → implementations: indexers, retrievers, organisers, embedding stores, and other optional features.
This structure replaces the old `core` and `extensions` modules from versions 1.0.4 and below. It provides more clarity and allows consumers to use core non-ML functionality independently. For the most part, the code itself remains unchanged; only the file organization has been updated. Documentation will be updated shortly.

---

### Constraints

Expand All @@ -107,7 +112,7 @@ Supports models stored locally or bundled in the app.

### Embedding Storage

The SDK only provides a file based implementation of `IEmbeddingStore`, `FileEmbeddingStore` (in extensions) because the following benchmarks below show much better performance for the loading of embeddings
The SDK only provides a file based implementation of `IEmbeddingStore`, `FileEmbeddingStore` (in core) because the following benchmarks below show much better performance for the loading of embeddings

#### **Benchmark Summary**

Expand Down Expand Up @@ -138,13 +143,13 @@ ___
## **Gradle / Kotlin Setup Notes**

* Java 17 / Kotlin JVM 17
* compileSdk = 36, targetSdk = 34, minSdk = 30
* `core` exposes `androidx.core:core-ktx` and ONNX runtime
* `extensions` depends on `core`
* Maven:
* `compileSdk = 36`, `targetSdk = 34`, `minSdk = 30`
* `core` exposes `androidx.core:core-ktx`
* `ml` depends on `core` and ONNX Runtime
* Maven publishing:

* `groupId`: `com.github.dev-diaries41`
* `artifactId`: `core` or `extensions`
* `artifactId`: `core` or `ml`
* `version`: configurable (`publishVersion`, default `1.0.0`)

---
---
43 changes: 22 additions & 21 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import java.io.ByteArrayOutputStream

plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("maven-publish")
id("com.google.devtools.ksp")

}

android {
Expand All @@ -13,15 +13,14 @@ android {
defaultConfig {
minSdk = 30
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

}

packaging {
resources.excludes.addAll(
listOf(
"META-INF/LICENSE.md",
"META-INF/LICENSE-notice.md",
)
)
)
}

Expand All @@ -40,13 +39,10 @@ android {
jvmTarget = "17"
}

buildFeatures {
// Add any enabled features here if needed
}

lint {
targetSdk = 34
}

testOptions {
unitTests {
isIncludeAndroidResources = true
Expand All @@ -55,6 +51,7 @@ android {
}
}
}

}

java {
Expand All @@ -65,25 +62,28 @@ java {
}

dependencies {
implementation(libs.androidx.documentfile)
implementation(libs.onnxruntime.android)

// Expose core-ktx and onnxruntime to consumers of core or extensions
// Expose core-ktx to consumers of core or extensions
api(libs.androidx.core.ktx)

// JVM unit tests
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
testImplementation("io.mockk:mockk:1.14.5")
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
testImplementation(libs.mockk)
testImplementation(kotlin("test"))

// Android instrumented tests
androidTestImplementation("androidx.test:core:1.7.0")
androidTestImplementation("androidx.test.ext:junit-ktx:1.3.0")
androidTestImplementation("androidx.test:runner:1.6.1")
androidTestImplementation("io.mockk:mockk-android:1.14.5")
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
androidTestImplementation(libs.androidx.core)
androidTestImplementation(libs.androidx.junit.ktx)
androidTestImplementation(libs.androidx.runner)
androidTestImplementation(libs.mockk.android)
androidTestImplementation(libs.kotlinx.coroutines.test)

androidTestImplementation(libs.androidx.room.runtime)
androidTestImplementation(libs.androidx.room.ktx)
androidTestImplementation(libs.androidx.room.testing)
ksp(libs.androidx.room.compiler)

}

val gitVersion: String by lazy {
Expand All @@ -96,6 +96,7 @@ val gitVersion: String by lazy {
}.getOrDefault("1.0.0")
}


publishing {
publications {
register<MavenPublication>("release") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.fpf.smartscansdk.extensions.data.images
package com.fpf.smartscansdk.core.data.images

import androidx.room.*

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.fpf.smartscansdk.extensions.data.images
package com.fpf.smartscansdk.core.data.images

import androidx.room.*

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.fpf.smartscansdk.extensions.data.images
package com.fpf.smartscansdk.core.data.images

import android.app.Application
import androidx.room.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.fpf.smartscansdk.extensions.data.images
package com.fpf.smartscansdk.core.data.images

import androidx.room.*
import com.fpf.smartscansdk.core.ml.embeddings.Embedding
import com.fpf.smartscansdk.core.data.Embedding

@Entity(tableName = "image_embeddings")
data class ImageEmbeddingEntity(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.fpf.smartscansdk.extensions.embeddings
package com.fpf.smartscansdk.core.embeddings

import android.app.Application
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.fpf.smartscansdk.extensions.data.images.ImageEmbeddingDatabase
import com.fpf.smartscansdk.extensions.data.images.ImageEmbeddingEntity
import com.fpf.smartscansdk.extensions.data.images.toEmbedding
import com.fpf.smartscansdk.core.data.images.ImageEmbeddingDatabase
import com.fpf.smartscansdk.core.data.images.ImageEmbeddingEntity
import com.fpf.smartscansdk.core.data.images.toEmbedding
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.runBlocking
import org.junit.Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.fpf.smartscansdk.core.data

sealed class ClassificationResult {
data class Success(val classId: String, val similarity: Float ): ClassificationResult()
data class Failure(val error: ClassificationError ): ClassificationResult()
}

enum class ClassificationError{MINIMUM_CLASS_SIZE, THRESHOLD, CONFIDENCE_MARGIN}

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.fpf.smartscansdk.core.ml.embeddings
package com.fpf.smartscansdk.core.data


import android.graphics.Bitmap

Expand Down Expand Up @@ -39,6 +40,7 @@ interface IEmbeddingProvider<T> {
val embeddingDim: Int? get() = null
fun closeSession() = Unit
suspend fun embed(data: T): FloatArray
suspend fun embedBatch(data: List<T>): List<FloatArray>
}


Expand Down
30 changes: 30 additions & 0 deletions core/src/main/java/com/fpf/smartscansdk/core/data/Processors.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.fpf.smartscansdk.core.data

import android.content.Context

interface IProcessorListener<Input, Output> {
suspend fun onActive(context: Context) = Unit
suspend fun onBatchComplete(context: Context, batch: List<Output>) = Unit
suspend fun onComplete(context: Context, metrics: Metrics.Success) = Unit
suspend fun onProgress(context: Context, progress: Float) = Unit
fun onError(context: Context, error: Exception, item: Input) = Unit
suspend fun onFail(context: Context, failureMetrics: Metrics.Failure) = Unit
}

sealed class Metrics {
data class Success(val totalProcessed: Int = 0, val timeElapsed: Long = 0L) : Metrics()
data class Failure(val processedBeforeFailure: Int, val timeElapsed: Long, val error: Exception) : Metrics()
}

data class MemoryOptions(
val lowMemoryThreshold: Long = 800L * 1024 * 1024,
val highMemoryThreshold: Long = 1_600L * 1024 * 1024,
val minConcurrency: Int = 1,
val maxConcurrency: Int = 4
)

data class ProcessOptions(
val memory: MemoryOptions = MemoryOptions(),
val batchSize: Int = 10
)

Loading