├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── .gitkeep │ ├── java │ └── io │ │ └── shubham0204 │ │ └── sam_android │ │ ├── MainActivity.kt │ │ ├── MainActivityViewModel.kt │ │ ├── sam │ │ ├── SAMDecoder.kt │ │ └── SAMEncoder.kt │ │ └── ui │ │ ├── components │ │ ├── AppAlertDialog.kt │ │ └── AppProgressDialog.kt │ │ └── theme │ │ ├── Color.kt │ │ ├── Theme.kt │ │ └── Type.kt │ └── res │ ├── drawable │ ├── ic_launcher_background.xml │ └── ic_launcher_foreground.xml │ ├── mipmap-anydpi │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── notebooks └── SAM2_ONNX_Export.ipynb └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .kotlin 4 | /local.properties 5 | /.idea 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | app/release 13 | app/src/main/assets/encoder_base_plus.onnx 14 | app/src/main/assets/decoder_base_plus.onnx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2024 Shubham Panchal 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Segment-Anything (SAM) and SAM v2 Inference In Android 2 | 3 | ![app_snapshots](https://github.com/user-attachments/assets/863b7774-4c89-4b1d-94b8-7b4edadef6d5) 4 | 5 | - On-device inference of SAM/SAM2 with `onnxruntime` 6 | - Clean Kotlin-only implementation, with no additional code compilation 7 | - No support for text-prompt as an input to the model 8 | - The inference time is *quite high* even with `float16` quantization enabled 9 | 10 | Download the [APK](https://github.com/shubham0204/Segment-Anything-Android/releases/tag/release_apk) or [setup the project locally](#setup) 11 | 12 | ## About Segment-Anything 13 | 14 | ![sam_architecture](https://github.com/user-attachments/assets/6a982571-7366-4849-b716-635786207bae) 15 | 16 | - Large-language models have demonstrated significant performance gains in numerous NLP tasks within zero or few-shot problem settings. The prompt or a text given at inference-time to the LLM guides the generation of the output. 17 | - Foundation models like CLIP and ALIGN have been popular due to wide adaptability and fine-tuning capabilities for downstream tasks. 18 | - The goal of the authors is to build a **foundation model for image segmentation**. 19 | 20 | #### Task 21 | - Authors define a **promptable image segmentation task**. 22 | - The **prompt** could be **spatial or textual information** which guides the model to generate the desired segmentation mask. 23 | 24 | #### Model 25 | - A powerful **image encoder** is used to produce image embeddings and a **prompt encoder** embeds prompts, both of which are combined with a **mask decoder**. 26 | - The authors focus on **point, box and mask prompts** with initial results on free-form text prompts. 27 | - **Image Encoder**: MAE (Masked Autoencoder) pre-trained Vision Transformer 28 | - **Prompt Encoder**: Points and boxes are represented by positional encodings, masks are embedded with convolutional layers, and free-form text with an encoder like CLIP 29 | - **Mask Decoder**: Transformer-based decoder model 30 | 31 | #### Data Engine 32 | - To achieve strong generalization on unknown datasets, authors propose a model-in-the-loop data annotation process with three phases. 33 | - In the ***assisted-manual phase***, SAM helps annotators in annotating masks. 34 | - In the ***semi-automatic phase***, SAM automatically generates masks for certain objects, by prompting their locations in the image. 35 | - In the ***fully-automatic phase***, SAM is prompted with a regular grid of foreground points, each of which yields a segmentation mask. 36 | 37 | ## Setup 38 | 39 | 1. Clone the project from GitHub and open the resulting directory in Android Studio. 40 | 41 | ```text 42 | git clone --depth=1 https://github.com/shubham0204/Segment-Anything-Android 43 | ``` 44 | 45 | 2. Android Studio starts building the project automatically. If not, select **Build > Rebuild Project** to start a project build. 46 | 47 | 3. After a successful project build, [connect an Android device](https://developer.android.com/studio/run/device) to your system. Once connected, the name of the device must be visible in top menu-bar in Android Studio. 48 | 49 | 4. Download any `*_encoder.onnx` and corresponding `*_decoder.onnx` models from the [HuggingFace repository](https://huggingface.co/shubham0204/sam2-onnx-models) and place them in the root directory of the project. The models can be stored in one of the two possible methods 50 | 51 | #### Store the ONNX models in the `assets` folder 52 | 53 | By placing the `*_encoder.onnx` and `*_decoder.onnx` in the `app/src/main/assets` folder, the models are packaged with the APK, which increases the overall size of the APK but avoids any additional setup to bring the models to the device. Make sure you change the names of the encoder and decoder models in `MainActivity.kt`, 54 | 55 | ```kotlin 56 | 57 | class MainActivity : ComponentActivity() { 58 | 59 | private val encoder = SAMEncoder() 60 | private val decoder = SAMDecoder() 61 | 62 | // The app will look for models with these file-names 63 | // in the assets folder 64 | private val encoderFileName = "encoder_base_plus.onnx" 65 | private val decoderFileName = "decoder_base_plus.onnx" 66 | 67 | // ... 68 | } 69 | ``` 70 | 71 | #### Store the ONNX models in the device's temporary storage 72 | 73 | Using the `adb` CLI tool, insert the ONNX models in the device's storage, 74 | 75 | ```text 76 | adb push sam2_hiera_small_encoder.onnx /data/local/tmp/sam/encoder.onnx 77 | adb push sam2_hiera_small_decoder.onnx /data/local/tmp/sam/decoder.onnx 78 | ``` 79 | 80 | Replace `sam2_hiera_small_decoder.onnx` and `sam2_hiera_small_encoder.onnx` with the name of the model downloaded from the HF repository in step (4). 81 | 82 | Update the model paths and set other options in `MainActivity.kt`, 83 | 84 | ```kotlin 85 | class MainActivity : ComponentActivity() { 86 | 87 | // ... 88 | 89 | override fun onCreate(savedInstanceState: Bundle?) { 90 | super.onCreate(savedInstanceState) 91 | enableEdgeToEdge() 92 | 93 | setContent { 94 | SAMAndroidTheme { 95 | Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> 96 | Column( 97 | // ... 98 | ) { 99 | 100 | // ... 101 | 102 | LaunchedEffect(0) { 103 | // ... 104 | // The paths below should match the ones 105 | // used in step (5) 106 | encoder.init( 107 | "/data/local/tmp/sam/encoder_fp16.onnx", 108 | useXNNPack = true, // XNNPack delegate for onnxruntime 109 | useFP16 = true 110 | ) 111 | decoder.init( 112 | "/data/local/tmp/sam/decoder_fp16.onnx", 113 | useXNNPack = true, 114 | useFP16 = true 115 | ) 116 | // ... 117 | } 118 | 119 | // ... 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | ``` 127 | 128 | ## Resources 129 | 130 | - [ONNX-SAM2-Segment-Anything](https://github.com/ibaiGorordo/ONNX-SAM2-Segment-Anything): ONNX models were derived from the Colab notebook linked in the `README.md` of this project. 131 | - [Segment Anything - arxiv](https://arxiv.org/abs/2304.02643) 132 | - [SAM 2: Segment Anything in Images and Videos - arxiv](https://arxiv.org/abs/2408.00714) 133 | 134 | ## Citations 135 | 136 | ```text 137 | @misc{ravi2024sam2segmentimages, 138 | title={SAM 2: Segment Anything in Images and Videos}, 139 | author={Nikhila Ravi and Valentin Gabeur and Yuan-Ting Hu and Ronghang Hu and Chaitanya Ryali and Tengyu Ma and Haitham Khedr and Roman Rädle and Chloe Rolland and Laura Gustafson and Eric Mintun and Junting Pan and Kalyan Vasudev Alwala and Nicolas Carion and Chao-Yuan Wu and Ross Girshick and Piotr Dollár and Christoph Feichtenhofer}, 140 | year={2024}, 141 | eprint={2408.00714}, 142 | archivePrefix={arXiv}, 143 | primaryClass={cs.CV}, 144 | url={https://arxiv.org/abs/2408.00714}, 145 | } 146 | ``` 147 | 148 | ```text 149 | @misc{kirillov2023segment, 150 | title={Segment Anything}, 151 | author={Alexander Kirillov and Eric Mintun and Nikhila Ravi and Hanzi Mao and Chloe Rolland and Laura Gustafson and Tete Xiao and Spencer Whitehead and Alexander C. Berg and Wan-Yen Lo and Piotr Dollár and Ross Girshick}, 152 | year={2023}, 153 | eprint={2304.02643}, 154 | archivePrefix={arXiv}, 155 | primaryClass={cs.CV}, 156 | url={https://arxiv.org/abs/2304.02643}, 157 | } 158 | ``` 159 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | alias(libs.plugins.compose.compiler) 5 | } 6 | 7 | android { 8 | namespace = "io.shubham0204.sam_android" 9 | compileSdk = 34 10 | 11 | defaultConfig { 12 | applicationId = "io.shubham0204.sam_android" 13 | minSdk = 26 14 | targetSdk = 34 15 | versionCode = 1 16 | versionName = "1.0" 17 | 18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 19 | vectorDrawables { 20 | useSupportLibrary = true 21 | } 22 | } 23 | 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | proguardFiles( 28 | getDefaultProguardFile("proguard-android-optimize.txt"), 29 | "proguard-rules.pro" 30 | ) 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_1_8 35 | targetCompatibility = JavaVersion.VERSION_1_8 36 | } 37 | kotlinOptions { 38 | jvmTarget = "1.8" 39 | } 40 | buildFeatures { 41 | compose = true 42 | } 43 | composeOptions { 44 | kotlinCompilerExtensionVersion = "1.5.1" 45 | } 46 | packaging { 47 | resources { 48 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 49 | } 50 | } 51 | } 52 | 53 | dependencies { 54 | implementation(libs.androidx.core.ktx) 55 | implementation(libs.androidx.lifecycle.runtime.ktx) 56 | 57 | implementation(platform(libs.androidx.compose.bom)) 58 | implementation(libs.androidx.activity.compose) 59 | implementation(libs.androidx.ui) 60 | implementation(libs.androidx.ui.graphics) 61 | implementation(libs.androidx.compose.material3.icons.extended) 62 | implementation(libs.androidx.compose.runtime.livedata) 63 | implementation(libs.androidx.material3) 64 | implementation(libs.androidx.lifecycle.viewmodel.compose) 65 | 66 | implementation("com.microsoft.onnxruntime:onnxruntime-android:1.17.0") 67 | 68 | implementation(libs.androidx.ui.tooling.preview) 69 | implementation(libs.androidx.exifinterface) 70 | testImplementation(libs.junit) 71 | androidTestImplementation(libs.androidx.junit) 72 | androidTestImplementation(libs.androidx.espresso.core) 73 | androidTestImplementation(platform(libs.androidx.compose.bom)) 74 | androidTestImplementation(libs.androidx.ui.test.junit4) 75 | debugImplementation(libs.androidx.ui.tooling) 76 | debugImplementation(libs.androidx.ui.test.manifest) 77 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/assets/.gitkeep -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.shubham0204.sam_android 18 | 19 | import AppProgressDialog 20 | import android.graphics.Bitmap 21 | import android.graphics.BitmapFactory 22 | import android.graphics.Matrix 23 | import android.graphics.PointF 24 | import android.net.Uri 25 | import android.os.Bundle 26 | import android.util.Log 27 | import android.widget.Toast 28 | import androidx.activity.ComponentActivity 29 | import androidx.activity.compose.rememberLauncherForActivityResult 30 | import androidx.activity.compose.setContent 31 | import androidx.activity.enableEdgeToEdge 32 | import androidx.activity.result.PickVisualMediaRequest 33 | import androidx.activity.result.contract.ActivityResultContracts 34 | import androidx.compose.foundation.Image 35 | import androidx.compose.foundation.background 36 | import androidx.compose.foundation.clickable 37 | import androidx.compose.foundation.gestures.detectTapGestures 38 | import androidx.compose.foundation.layout.Box 39 | import androidx.compose.foundation.layout.Column 40 | import androidx.compose.foundation.layout.Row 41 | import androidx.compose.foundation.layout.Spacer 42 | import androidx.compose.foundation.layout.fillMaxSize 43 | import androidx.compose.foundation.layout.fillMaxWidth 44 | import androidx.compose.foundation.layout.height 45 | import androidx.compose.foundation.layout.padding 46 | import androidx.compose.foundation.lazy.LazyColumn 47 | import androidx.compose.foundation.lazy.itemsIndexed 48 | import androidx.compose.foundation.rememberScrollState 49 | import androidx.compose.foundation.verticalScroll 50 | import androidx.compose.material.icons.Icons 51 | import androidx.compose.material.icons.filled.Close 52 | import androidx.compose.material.icons.filled.Image 53 | import androidx.compose.material.icons.filled.Layers 54 | import androidx.compose.material.icons.filled.Tag 55 | import androidx.compose.material3.Button 56 | import androidx.compose.material3.ExperimentalMaterial3Api 57 | import androidx.compose.material3.Icon 58 | import androidx.compose.material3.IconButton 59 | import androidx.compose.material3.ModalBottomSheet 60 | import androidx.compose.material3.Scaffold 61 | import androidx.compose.material3.Text 62 | import androidx.compose.material3.rememberModalBottomSheetState 63 | import androidx.compose.runtime.Composable 64 | import androidx.compose.runtime.LaunchedEffect 65 | import androidx.compose.runtime.getValue 66 | import androidx.compose.runtime.mutableStateOf 67 | import androidx.compose.runtime.remember 68 | import androidx.compose.runtime.rememberCoroutineScope 69 | import androidx.compose.runtime.setValue 70 | import androidx.compose.ui.Modifier 71 | import androidx.compose.ui.draw.drawWithCache 72 | import androidx.compose.ui.geometry.Offset 73 | import androidx.compose.ui.geometry.Size 74 | import androidx.compose.ui.graphics.Color 75 | import androidx.compose.ui.graphics.asImageBitmap 76 | import androidx.compose.ui.input.pointer.pointerInput 77 | import androidx.compose.ui.layout.ContentScale 78 | import androidx.compose.ui.layout.onGloballyPositioned 79 | import androidx.compose.ui.text.style.TextAlign 80 | import androidx.compose.ui.unit.dp 81 | import androidx.compose.ui.unit.sp 82 | import androidx.compose.ui.unit.toSize 83 | import androidx.exifinterface.media.ExifInterface 84 | import androidx.lifecycle.viewmodel.compose.viewModel 85 | import hideProgressDialog 86 | import io.shubham0204.sam_android.sam.SAMDecoder 87 | import io.shubham0204.sam_android.sam.SAMEncoder 88 | import io.shubham0204.sam_android.ui.components.AppAlertDialog 89 | import io.shubham0204.sam_android.ui.components.createAlertDialog 90 | import io.shubham0204.sam_android.ui.theme.SAMAndroidTheme 91 | import kotlinx.coroutines.CoroutineScope 92 | import kotlinx.coroutines.Dispatchers 93 | import kotlinx.coroutines.launch 94 | import kotlinx.coroutines.withContext 95 | import setProgressDialogText 96 | import showProgressDialog 97 | import java.io.File 98 | import java.nio.FloatBuffer 99 | import java.nio.file.Paths 100 | import kotlin.time.DurationUnit 101 | import kotlin.time.measureTimedValue 102 | 103 | class MainActivity : ComponentActivity() { 104 | private val encoder = SAMEncoder() 105 | private val decoder = SAMDecoder() 106 | private val encoderFileName = "encoder_base_plus.onnx" 107 | private val decoderFileName = "decoder_base_plus.onnx" 108 | 109 | override fun onCreate(savedInstanceState: Bundle?) { 110 | super.onCreate(savedInstanceState) 111 | enableEdgeToEdge() 112 | 113 | setContent { 114 | SAMAndroidTheme { 115 | Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> 116 | Column( 117 | modifier = 118 | Modifier 119 | .verticalScroll(rememberScrollState()) 120 | .padding(innerPadding), 121 | ) { 122 | val viewModel = viewModel() 123 | 124 | var image by remember { mutableStateOf(null) } 125 | val outputImages = remember { viewModel.images } 126 | val points = remember { viewModel.points } 127 | var isReady by remember { mutableStateOf(false) } 128 | var viewPortDims by remember { mutableStateOf(null) } 129 | 130 | LaunchedEffect(0) { 131 | try { 132 | showProgressDialog() 133 | setProgressDialogText("Loading models...") 134 | if (isModelInAssets(encoderFileName) && isModelInAssets(decoderFileName)) { 135 | copyModelToStorage(encoderFileName) 136 | copyModelToStorage(decoderFileName) 137 | encoder.init(Paths.get(filesDir.absolutePath, encoderFileName).toString()) 138 | decoder.init(Paths.get(filesDir.absolutePath, decoderFileName).toString()) 139 | } else { 140 | encoder.init("/data/local/tmp/sam/encoder_base_plus.onnx") 141 | decoder.init("/data/local/tmp/sam/decoder_base_plus.onnx") 142 | } 143 | isReady = true 144 | hideProgressDialog() 145 | } catch (e: Exception) { 146 | hideProgressDialog() 147 | createAlertDialog( 148 | dialogTitle = "Error", 149 | dialogText = "An error occurred: ${e.message}", 150 | dialogPositiveButtonText = "Close", 151 | dialogNegativeButtonText = null, 152 | onPositiveButtonClick = { finish() }, 153 | onNegativeButtonClick = null, 154 | ) 155 | } 156 | } 157 | 158 | val pickMediaLauncher = 159 | rememberLauncherForActivityResult( 160 | contract = ActivityResultContracts.PickVisualMedia(), 161 | ) { 162 | if (it != null) { 163 | val bitmap = getFixedBitmap(it) 164 | image = bitmap 165 | viewModel.reset() 166 | } 167 | } 168 | 169 | Row( 170 | modifier = 171 | Modifier 172 | .padding(horizontal = 8.dp) 173 | .fillMaxWidth(), 174 | ) { 175 | Button( 176 | modifier = 177 | Modifier 178 | .fillMaxWidth() 179 | .padding(4.dp) 180 | .weight(1f), 181 | onClick = { 182 | viewModel.showBottomSheet.value = true 183 | }, 184 | ) { 185 | Icon( 186 | imageVector = Icons.Default.Tag, 187 | contentDescription = "Choose Label For Points", 188 | ) 189 | Text(text = "Choose Label For Points") 190 | } 191 | } 192 | 193 | Row( 194 | modifier = 195 | Modifier 196 | .padding(horizontal = 8.dp) 197 | .fillMaxWidth(), 198 | ) { 199 | Button( 200 | modifier = 201 | Modifier 202 | .fillMaxWidth() 203 | .padding(4.dp) 204 | .weight(1f), 205 | enabled = isReady && (image != null), 206 | onClick = { 207 | image?.let { bitmap -> 208 | processInputPoints( 209 | bitmap, 210 | points, 211 | viewPortDims, 212 | viewModel, 213 | ) 214 | } 215 | }, 216 | ) { 217 | Icon( 218 | imageVector = Icons.Default.Layers, 219 | contentDescription = "Segment", 220 | ) 221 | Text(text = "Segment!") 222 | } 223 | Button( 224 | modifier = 225 | Modifier 226 | .fillMaxWidth() 227 | .padding(4.dp) 228 | .weight(1f), 229 | enabled = isReady, 230 | onClick = { 231 | pickMediaLauncher.launch( 232 | PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly), 233 | ) 234 | }, 235 | ) { 236 | Icon( 237 | imageVector = Icons.Default.Image, 238 | contentDescription = "Select Image", 239 | ) 240 | Text(text = "Select Image") 241 | } 242 | } 243 | 244 | if (image != null) { 245 | Spacer(modifier = Modifier.height(4.dp)) 246 | Text( 247 | text = "Currently selected label: Label ${viewModel.selectedLabelIndex.intValue}", 248 | fontSize = 12.sp, 249 | color = Color.DarkGray, 250 | textAlign = TextAlign.Center, 251 | modifier = 252 | Modifier 253 | .fillMaxWidth() 254 | .padding(4.dp), 255 | ) 256 | Spacer(modifier = Modifier.height(4.dp)) 257 | Box { 258 | Image( 259 | bitmap = image!!.asImageBitmap(), 260 | contentScale = ContentScale.Fit, 261 | contentDescription = "Selected Image", 262 | modifier = 263 | Modifier 264 | .pointerInput(Unit) { 265 | detectTapGestures(onLongPress = { 266 | val newPoints = 267 | points.filter { it.label != viewModel.selectedLabelIndex.intValue } 268 | points.clear() 269 | points.addAll(newPoints) 270 | Toast 271 | .makeText( 272 | this@MainActivity, 273 | "All guide-points removed", 274 | Toast.LENGTH_LONG, 275 | ).show() 276 | }, onTap = { offset -> 277 | points.add( 278 | LabelPoint( 279 | viewModel.selectedLabelIndex.intValue, 280 | PointF(offset.x, offset.y), 281 | ), 282 | ) 283 | }) 284 | }.onGloballyPositioned { 285 | viewPortDims = it.size.toSize() 286 | }, 287 | ) 288 | Spacer( 289 | modifier = 290 | Modifier 291 | .fillMaxSize() 292 | .drawWithCache { 293 | onDrawBehind { 294 | points 295 | .filter { labelPoint -> labelPoint.label == viewModel.selectedLabelIndex.intValue } 296 | .forEach { labelPoint -> 297 | drawCircle( 298 | color = Color.Black, 299 | radius = 15f, 300 | center = 301 | Offset( 302 | labelPoint.point.x, 303 | labelPoint.point.y, 304 | ), 305 | ) 306 | drawCircle( 307 | color = Color.Yellow, 308 | radius = 12f, 309 | center = 310 | Offset( 311 | labelPoint.point.x, 312 | labelPoint.point.y, 313 | ), 314 | ) 315 | } 316 | } 317 | }, 318 | ) 319 | } 320 | Spacer(modifier = Modifier.height(4.dp)) 321 | Text( 322 | text = "Tap on the image to insert a guide-point\nLong-press to remove all guide-points for the current label", 323 | fontSize = 12.sp, 324 | color = Color.DarkGray, 325 | textAlign = TextAlign.Center, 326 | modifier = 327 | Modifier 328 | .fillMaxWidth() 329 | .padding(16.dp), 330 | ) 331 | } 332 | if (outputImages.isNotEmpty()) { 333 | Text( 334 | modifier = Modifier.padding(4.dp), 335 | fontSize = 18.sp, 336 | text = "Segmented Images (${viewModel.inferenceTime.intValue} s)", 337 | ) 338 | } 339 | outputImages.forEach { 340 | Image( 341 | modifier = Modifier.background(Color.Black.copy(green = 1.0f)), 342 | bitmap = it.asImageBitmap(), 343 | contentDescription = "Segmented image", 344 | ) 345 | } 346 | 347 | AppAlertDialog() 348 | AppProgressDialog() 349 | ManageLabelsBottomSheet(viewModel) 350 | } 351 | } 352 | } 353 | } 354 | } 355 | 356 | @OptIn(ExperimentalMaterial3Api::class) 357 | @Composable 358 | private fun ManageLabelsBottomSheet(viewModel: MainActivityViewModel) { 359 | val sheetState = rememberModalBottomSheetState() 360 | val scope = rememberCoroutineScope() 361 | var showBottomSheet by remember { viewModel.showBottomSheet } 362 | val labels = remember { viewModel.labels } 363 | var lastAddedLabel by remember { viewModel.lastAddedLabel } 364 | var selectedLabelIndex by remember { viewModel.selectedLabelIndex } 365 | 366 | if (showBottomSheet) { 367 | ModalBottomSheet( 368 | containerColor = Color.White, 369 | onDismissRequest = { showBottomSheet = false }, 370 | sheetState = sheetState, 371 | ) { 372 | Column( 373 | modifier = Modifier.padding(horizontal = 16.dp), 374 | ) { 375 | Row { 376 | Text( 377 | text = "Manage Labels", 378 | fontSize = 18.sp, 379 | modifier = 380 | Modifier 381 | .padding(8.dp) 382 | .weight(1f), 383 | ) 384 | IconButton(onClick = { 385 | scope.launch { sheetState.hide() }.invokeOnCompletion { 386 | if (!sheetState.isVisible) { 387 | showBottomSheet = false 388 | } 389 | } 390 | }) { 391 | Icon( 392 | imageVector = Icons.Default.Close, 393 | contentDescription = "Close Panel", 394 | ) 395 | } 396 | } 397 | LazyColumn { 398 | itemsIndexed(labels) { index, item -> 399 | Text( 400 | text = item, 401 | modifier = 402 | Modifier 403 | .clickable { 404 | selectedLabelIndex = index 405 | }.background(if (selectedLabelIndex == index) Color.Cyan else Color.White) 406 | .padding(8.dp) 407 | .fillMaxWidth(), 408 | ) 409 | } 410 | } 411 | Spacer(modifier = Modifier.height(4.dp)) 412 | Row { 413 | Button( 414 | modifier = 415 | Modifier 416 | .padding(4.dp) 417 | .fillMaxWidth() 418 | .weight(1f), 419 | onClick = { 420 | lastAddedLabel += 1 421 | labels.add("Label $lastAddedLabel") 422 | }, 423 | ) { 424 | Text(text = "Add Label") 425 | } 426 | Button( 427 | modifier = 428 | Modifier 429 | .padding(4.dp) 430 | .fillMaxWidth() 431 | .weight(1f), 432 | onClick = { 433 | labels.removeAt(selectedLabelIndex) 434 | }, 435 | ) { 436 | Text(text = "Remove Label") 437 | } 438 | } 439 | } 440 | } 441 | } 442 | } 443 | 444 | private fun processInputPoints( 445 | bitmap: Bitmap, 446 | points: List, 447 | viewPortDims: Size?, 448 | viewModel: MainActivityViewModel, 449 | ) { 450 | CoroutineScope(Dispatchers.Default).launch { 451 | try { 452 | showProgressDialog() 453 | setProgressDialogText("Performing image segmentation...") 454 | val pointsGroupByLabel = points.groupBy { it.label } 455 | val maxPoints = pointsGroupByLabel.maxOfOrNull { it.value.size } ?: return@launch 456 | val labelsCount = pointsGroupByLabel.keys.size 457 | 458 | val labelsBuffer = FloatBuffer.allocate(1 * labelsCount * maxPoints) 459 | val pointsBuffer = FloatBuffer.allocate(1 * labelsCount * maxPoints * 2) 460 | 461 | for ((label, labelPoints) in pointsGroupByLabel) { 462 | labelPoints.forEach { 463 | pointsBuffer.put((it.point.x / viewPortDims?.width!!) * 1024f) 464 | pointsBuffer.put((it.point.y / viewPortDims.height) * 1024f) 465 | } 466 | repeat(maxPoints - labelPoints.size) { 467 | pointsBuffer.put(0f) 468 | pointsBuffer.put(0f) 469 | } 470 | repeat(labelPoints.size) { 471 | labelsBuffer.put((label + 1).toFloat()) 472 | } 473 | repeat(maxPoints - labelPoints.size) { 474 | labelsBuffer.put(-1f) 475 | } 476 | } 477 | pointsBuffer.rewind() 478 | labelsBuffer.rewind() 479 | 480 | val (imagesWithMask, time) = 481 | measureTimedValue { 482 | decoder.execute( 483 | encoder.execute(bitmap), 484 | pointsBuffer, 485 | labelsBuffer, 486 | labelsCount.toLong(), 487 | maxPoints.toLong(), 488 | bitmap, 489 | ) 490 | } 491 | withContext(Dispatchers.Main) { 492 | viewModel.inferenceTime.intValue = time.toInt(DurationUnit.SECONDS) 493 | hideProgressDialog() 494 | viewModel.images.clear() 495 | viewModel.images.addAll(imagesWithMask) 496 | } 497 | } catch (e: Exception) { 498 | hideProgressDialog() 499 | createAlertDialog( 500 | dialogTitle = "Error", 501 | dialogText = "An error occurred: ${e.message}", 502 | dialogPositiveButtonText = "Close", 503 | dialogNegativeButtonText = null, 504 | onPositiveButtonClick = { finish() }, 505 | onNegativeButtonClick = null, 506 | ) 507 | } 508 | } 509 | } 510 | 511 | private fun isModelInAssets(modelFileName: String): Boolean = (assets.list("") ?: emptyArray()).contains(modelFileName) 512 | 513 | private fun copyModelToStorage(modelFileName: String) { 514 | val modelFile = File(filesDir, modelFileName) 515 | if (!modelFile.exists()) { 516 | assets.open(modelFileName).use { inputStream -> 517 | openFileOutput(modelFileName, MODE_PRIVATE).use { outputStream -> 518 | inputStream.copyTo(outputStream) 519 | } 520 | } 521 | Log.i(MainActivity::class.simpleName, "$modelFileName copied from assets to app storage") 522 | } 523 | } 524 | 525 | private fun getFixedBitmap(imageFileUri: Uri): Bitmap { 526 | var imageBitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageFileUri)) 527 | val exifInterface = ExifInterface(contentResolver.openInputStream(imageFileUri)!!) 528 | imageBitmap = 529 | when ( 530 | exifInterface.getAttributeInt( 531 | ExifInterface.TAG_ORIENTATION, 532 | ExifInterface.ORIENTATION_UNDEFINED, 533 | ) 534 | ) { 535 | ExifInterface.ORIENTATION_ROTATE_90 -> rotateBitmap(imageBitmap, 90f) 536 | ExifInterface.ORIENTATION_ROTATE_180 -> rotateBitmap(imageBitmap, 180f) 537 | ExifInterface.ORIENTATION_ROTATE_270 -> rotateBitmap(imageBitmap, 270f) 538 | else -> imageBitmap 539 | } 540 | return imageBitmap 541 | } 542 | 543 | private fun rotateBitmap( 544 | source: Bitmap, 545 | degrees: Float, 546 | ): Bitmap { 547 | val matrix = Matrix() 548 | matrix.postRotate(degrees) 549 | return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, false) 550 | } 551 | 552 | data class LabelPoint( 553 | val label: Int, 554 | val point: PointF, 555 | ) 556 | } 557 | -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/MainActivityViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.shubham0204.sam_android 18 | 19 | import android.graphics.Bitmap 20 | import androidx.compose.runtime.mutableIntStateOf 21 | import androidx.compose.runtime.mutableStateListOf 22 | import androidx.compose.runtime.mutableStateOf 23 | import androidx.lifecycle.ViewModel 24 | 25 | class MainActivityViewModel : ViewModel() { 26 | val showBottomSheet = mutableStateOf(false) 27 | val selectedLabelIndex = mutableIntStateOf(0) 28 | val lastAddedLabel = mutableIntStateOf(0) 29 | val labels = mutableStateListOf("Label 0") 30 | val points = mutableStateListOf() 31 | val images = mutableStateListOf() 32 | val inferenceTime = mutableIntStateOf(0) 33 | 34 | fun reset() { 35 | images.clear() 36 | points.clear() 37 | labels.clear() 38 | labels.add("Label 0") 39 | selectedLabelIndex.intValue = 0 40 | lastAddedLabel.intValue = 0 41 | showBottomSheet.value = false 42 | inferenceTime.intValue = 0 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/sam/SAMDecoder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.shubham0204.sam_android.sam 18 | 19 | import ai.onnxruntime.OnnxTensor 20 | import ai.onnxruntime.OrtEnvironment 21 | import ai.onnxruntime.OrtSession 22 | import ai.onnxruntime.providers.NNAPIFlags 23 | import android.content.Context 24 | import android.graphics.Bitmap 25 | import android.graphics.Color 26 | import android.util.Log 27 | import androidx.core.graphics.get 28 | import androidx.core.graphics.set 29 | import kotlinx.coroutines.Dispatchers 30 | import kotlinx.coroutines.joinAll 31 | import kotlinx.coroutines.launch 32 | import kotlinx.coroutines.withContext 33 | import java.io.File 34 | import java.io.FileOutputStream 35 | import java.nio.FloatBuffer 36 | import java.nio.IntBuffer 37 | import java.util.Collections 38 | import java.util.EnumSet 39 | 40 | class SAMDecoder { 41 | private lateinit var ortEnvironment: OrtEnvironment 42 | private lateinit var ortSession: OrtSession 43 | 44 | // input and output node names for the decoder 45 | // ONNX model 46 | private lateinit var maskOutputName: String 47 | private lateinit var scoresOutputName: String 48 | 49 | private lateinit var imageEmbeddingInputName: String 50 | private lateinit var highResFeature0InputName: String 51 | private lateinit var highResFeature1InputName: String 52 | private lateinit var pointCoordinatesInputName: String 53 | private lateinit var pointLabelsInputName: String 54 | private lateinit var maskInputName: String 55 | private lateinit var hasMaskInputName: String 56 | 57 | suspend fun init( 58 | modelPath: String, 59 | useFP16: Boolean = false, 60 | useXNNPack: Boolean = false, 61 | ) = withContext(Dispatchers.IO) { 62 | ortEnvironment = OrtEnvironment.getEnvironment() 63 | val options = 64 | OrtSession.SessionOptions().apply { 65 | if (useFP16) { 66 | addNnapi(EnumSet.of(NNAPIFlags.USE_FP16)) 67 | } 68 | if (useXNNPack) { 69 | addXnnpack( 70 | mapOf( 71 | "intra_op_num_threads" to "2", 72 | ), 73 | ) 74 | } 75 | } 76 | ortSession = ortEnvironment.createSession(modelPath, options) 77 | val decoderInputNames = ortSession.inputNames.toList() 78 | val decoderOutputNames = ortSession.outputNames.toList() 79 | Log.i(SAMDecoder::class.simpleName, "Decoder input names: $decoderInputNames") 80 | Log.i(SAMDecoder::class.simpleName, "Decoder output names: $decoderOutputNames") 81 | imageEmbeddingInputName = decoderInputNames[0] 82 | highResFeature0InputName = decoderInputNames[1] 83 | highResFeature1InputName = decoderInputNames[2] 84 | pointCoordinatesInputName = decoderInputNames[3] 85 | pointLabelsInputName = decoderInputNames[4] 86 | maskInputName = decoderInputNames[5] 87 | hasMaskInputName = decoderInputNames[6] 88 | 89 | maskOutputName = decoderOutputNames[0] 90 | scoresOutputName = decoderOutputNames[1] 91 | } 92 | 93 | suspend fun execute( 94 | encoderResults: SAMEncoder.SAMEncoderResults, 95 | pointCoordinates: FloatBuffer, 96 | pointLabels: FloatBuffer, 97 | numLabels: Long, 98 | numPoints: Long, 99 | inputImage: Bitmap, 100 | ): List = 101 | withContext(Dispatchers.Default) { 102 | val imgHeight = inputImage.height 103 | val imgWidth = inputImage.width 104 | 105 | val imageEmbeddingTensor = 106 | OnnxTensor.createTensor( 107 | ortEnvironment, 108 | encoderResults.imageEmbedding, 109 | longArrayOf(1, 256, 64, 64), 110 | ) 111 | val highResFeature0Tensor = 112 | OnnxTensor.createTensor( 113 | ortEnvironment, 114 | encoderResults.highResFeature0, 115 | longArrayOf(1, 32, 256, 256), 116 | ) 117 | val highResFeature1Tensor = 118 | OnnxTensor.createTensor( 119 | ortEnvironment, 120 | encoderResults.highResFeature1, 121 | longArrayOf(1, 64, 128, 128), 122 | ) 123 | 124 | val pointCoordinatesTensor = 125 | OnnxTensor.createTensor( 126 | ortEnvironment, 127 | pointCoordinates, 128 | longArrayOf(numLabels, numPoints, 2), 129 | ) 130 | val pointLabelsTensor = 131 | OnnxTensor.createTensor( 132 | ortEnvironment, 133 | pointLabels, 134 | longArrayOf(numLabels, numPoints), 135 | ) 136 | 137 | val maskTensor = 138 | OnnxTensor.createTensor( 139 | ortEnvironment, 140 | FloatBuffer.wrap(FloatArray(numLabels.toInt() * 1 * 256 * 256) { 0f }), 141 | longArrayOf(numLabels, 1, 256, 256), 142 | ) 143 | val hasMaskTensor = 144 | OnnxTensor.createTensor( 145 | ortEnvironment, 146 | FloatBuffer.wrap(floatArrayOf(0.0f)), 147 | longArrayOf(1), 148 | ) 149 | val origImageSizeTensor = 150 | OnnxTensor.createTensor( 151 | ortEnvironment, 152 | IntBuffer.wrap(intArrayOf(imgHeight, imgWidth)), 153 | longArrayOf(2), 154 | ) 155 | 156 | val outputs = 157 | ortSession.run( 158 | mapOf( 159 | imageEmbeddingInputName to imageEmbeddingTensor, 160 | highResFeature0InputName to highResFeature0Tensor, 161 | highResFeature1InputName to highResFeature1Tensor, 162 | pointCoordinatesInputName to pointCoordinatesTensor, 163 | pointLabelsInputName to pointLabelsTensor, 164 | maskInputName to maskTensor, 165 | hasMaskInputName to hasMaskTensor, 166 | "orig_im_size" to origImageSizeTensor, 167 | ), 168 | ) 169 | val mask = (outputs[maskOutputName].get() as OnnxTensor).floatBuffer 170 | val scores = (outputs[scoresOutputName].get() as OnnxTensor).floatBuffer.array() 171 | Log.i(SAMDecoder::class.simpleName, "scores: ${scores.contentToString()}") 172 | 173 | // We apply masks to the input image in a parallel manner 174 | // by dispatching each (mask,image) pair to a new coroutine 175 | val bitmaps = Collections.synchronizedList(mutableListOf()) 176 | 177 | val numPredictedMasks = scores.size / numLabels.toInt() 178 | Log.i(SAMDecoder::class.simpleName, "Num predicted masks: $numPredictedMasks") 179 | Log.i(SAMDecoder::class.simpleName, "Mask size: ${mask.capacity()}") 180 | 181 | (0.. 183 | launch(Dispatchers.Default) { 184 | // Apply mask to the input image 185 | // The 'on' pixels (val > 0) in the mask, will deliver an pixel 186 | // with alpha = 0 in the final image 187 | val maskStartIndex = labelIndex * numPredictedMasks * imgHeight * imgWidth 188 | val colorBitmap = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888) 189 | for (i in 0.. 0) { 194 | 0 195 | } else { 196 | 255 197 | }, 198 | Color.red(inputImage[j, i]), 199 | Color.green(inputImage[j, i]), 200 | Color.blue(inputImage[j, i]), 201 | ) 202 | } 203 | } 204 | bitmaps.add(colorBitmap) 205 | } 206 | }.joinAll() 207 | 208 | return@withContext bitmaps 209 | } 210 | 211 | private fun saveBitmap( 212 | context: Context, 213 | image: Bitmap, 214 | name: String, 215 | ) { 216 | val fileOutputStream = FileOutputStream(File(context.filesDir.absolutePath + "/$name.png")) 217 | image.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/sam/SAMEncoder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.shubham0204.sam_android.sam 18 | 19 | import ai.onnxruntime.OnnxTensor 20 | import ai.onnxruntime.OrtEnvironment 21 | import ai.onnxruntime.OrtSession 22 | import ai.onnxruntime.providers.NNAPIFlags 23 | import android.graphics.Bitmap 24 | import android.graphics.Color 25 | import android.util.Log 26 | import androidx.core.graphics.get 27 | import kotlinx.coroutines.Dispatchers 28 | import kotlinx.coroutines.withContext 29 | import java.nio.FloatBuffer 30 | import java.util.EnumSet 31 | 32 | class SAMEncoder { 33 | data class SAMEncoderResults( 34 | val imageEmbedding: FloatBuffer, 35 | val highResFeature0: FloatBuffer, 36 | val highResFeature1: FloatBuffer, 37 | ) 38 | 39 | private val inputDim = 1024 40 | private lateinit var ortEnvironment: OrtEnvironment 41 | private lateinit var ortSession: OrtSession 42 | private lateinit var inputName: String 43 | private lateinit var imageEmbeddingOutputName: String 44 | private lateinit var highResFeature0OutputName: String 45 | private lateinit var highResFeature1OutputName: String 46 | 47 | private val mean = 48 | floatArrayOf( 49 | 0.485f, 50 | 0.456f, 51 | 0.406f, 52 | ) 53 | private val std = 54 | floatArrayOf( 55 | 0.229f, 56 | 0.224f, 57 | 0.225f, 58 | ) 59 | 60 | suspend fun init( 61 | modelPath: String, 62 | useFP16: Boolean = false, 63 | useXNNPack: Boolean = false, 64 | ) = withContext(Dispatchers.IO) { 65 | ortEnvironment = OrtEnvironment.getEnvironment() 66 | val options = 67 | OrtSession.SessionOptions().apply { 68 | if (useFP16) { 69 | addNnapi(EnumSet.of(NNAPIFlags.USE_FP16)) 70 | } 71 | if (useXNNPack) { 72 | addXnnpack( 73 | mapOf( 74 | "intra_op_num_threads" to "2", 75 | ), 76 | ) 77 | } 78 | } 79 | ortSession = ortEnvironment.createSession(modelPath, options) 80 | inputName = ortSession.inputNames.first() 81 | val outputNames = ortSession.outputNames.toList() 82 | Log.i(SAMEncoder::class.simpleName, "Encoder input names: $inputName") 83 | Log.i(SAMEncoder::class.simpleName, "Encoder output names: $outputNames") 84 | highResFeature0OutputName = outputNames[0] 85 | highResFeature1OutputName = outputNames[1] 86 | imageEmbeddingOutputName = outputNames[2] 87 | } 88 | 89 | suspend fun execute(inputImage: Bitmap) = 90 | withContext(Dispatchers.IO) { 91 | // Resize the image to the model's required input size 92 | val resizedImage = 93 | Bitmap.createScaledBitmap( 94 | inputImage, 95 | inputDim, 96 | inputDim, 97 | true, 98 | ) 99 | 100 | // Create a FloatBuffer to store the normalized image pixels 101 | // The model requires the image in the shape (1, C, H, W) 102 | val imagePixels = FloatBuffer.allocate(1 * resizedImage.width * resizedImage.height * 3) 103 | imagePixels.rewind() 104 | for (i in 0 until resizedImage.height) { 105 | for (j in 0 until resizedImage.width) { 106 | imagePixels.put( 107 | ((Color.red(resizedImage[j, i]).toFloat() / 255.0f) - mean[0]) / std[0], 108 | ) 109 | } 110 | } 111 | for (i in 0 until resizedImage.height) { 112 | for (j in 0 until resizedImage.width) { 113 | imagePixels.put( 114 | ((Color.blue(resizedImage[j, i]).toFloat() / 255.0f) - mean[1]) / std[1], 115 | ) 116 | } 117 | } 118 | for (i in 0 until resizedImage.height) { 119 | for (j in 0 until resizedImage.width) { 120 | imagePixels.put( 121 | ((Color.green(resizedImage[j, i]).toFloat() / 255.0f) - mean[2]) / std[2], 122 | ) 123 | } 124 | } 125 | imagePixels.rewind() 126 | 127 | // Perform inference, and return the output tensors 128 | val imageTensor = 129 | OnnxTensor.createTensor( 130 | ortEnvironment, 131 | imagePixels, 132 | longArrayOf(1, 3, inputDim.toLong(), inputDim.toLong()), 133 | ) 134 | val outputs = ortSession.run(mapOf(inputName to imageTensor)) 135 | val highResFeature0 = outputs[highResFeature0OutputName].get() as OnnxTensor 136 | val highResFeature1 = outputs[highResFeature1OutputName].get() as OnnxTensor 137 | val imageEmbedding = outputs[imageEmbeddingOutputName].get() as OnnxTensor 138 | return@withContext SAMEncoderResults( 139 | imageEmbedding.floatBuffer, 140 | highResFeature0.floatBuffer, 141 | highResFeature1.floatBuffer, 142 | ) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/ui/components/AppAlertDialog.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.shubham0204.sam_android.ui.components 18 | 19 | import androidx.compose.material3.AlertDialog 20 | import androidx.compose.material3.Text 21 | import androidx.compose.material3.TextButton 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.runtime.getValue 24 | import androidx.compose.runtime.mutableStateOf 25 | import androidx.compose.runtime.remember 26 | 27 | private var title = "" 28 | private var text = "" 29 | private var positiveButtonText = "" 30 | private var negativeButtonText = "" 31 | private lateinit var positiveButtonOnClick: (() -> Unit) 32 | private lateinit var negativeButtonOnClick: (() -> Unit) 33 | private val alertDialogShowStatus = mutableStateOf(false) 34 | 35 | @Composable 36 | fun AppAlertDialog() { 37 | val visible by remember { alertDialogShowStatus } 38 | if (visible) { 39 | AlertDialog( 40 | title = { Text(text = title) }, 41 | text = { Text(text = text) }, 42 | onDismissRequest = { /* All alert dialogs are non-cancellable */ }, 43 | confirmButton = { 44 | if (positiveButtonText.isNotEmpty()) { 45 | TextButton( 46 | onClick = { 47 | alertDialogShowStatus.value = false 48 | positiveButtonOnClick() 49 | }, 50 | ) { 51 | Text(text = positiveButtonText) 52 | } 53 | } 54 | }, 55 | dismissButton = { 56 | if (negativeButtonText.isNotEmpty()) { 57 | TextButton( 58 | onClick = { 59 | alertDialogShowStatus.value = false 60 | negativeButtonOnClick() 61 | }, 62 | ) { 63 | Text(text = negativeButtonText) 64 | } 65 | } 66 | }, 67 | ) 68 | } 69 | } 70 | 71 | fun createAlertDialog( 72 | dialogTitle: String, 73 | dialogText: String, 74 | dialogPositiveButtonText: String, 75 | dialogNegativeButtonText: String?, 76 | onPositiveButtonClick: (() -> Unit), 77 | onNegativeButtonClick: (() -> Unit)?, 78 | ) { 79 | title = dialogTitle 80 | text = dialogText 81 | positiveButtonOnClick = onPositiveButtonClick 82 | onNegativeButtonClick?.let { negativeButtonOnClick = it } 83 | positiveButtonText = dialogPositiveButtonText 84 | dialogNegativeButtonText?.let { negativeButtonText = it } 85 | alertDialogShowStatus.value = true 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/ui/components/AppProgressDialog.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import androidx.compose.foundation.background 18 | import androidx.compose.foundation.layout.Box 19 | import androidx.compose.foundation.layout.Column 20 | import androidx.compose.foundation.layout.Spacer 21 | import androidx.compose.foundation.layout.fillMaxWidth 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.foundation.shape.RoundedCornerShape 24 | import androidx.compose.material3.LinearProgressIndicator 25 | import androidx.compose.material3.Text 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.runtime.getValue 28 | import androidx.compose.runtime.mutableStateOf 29 | import androidx.compose.runtime.remember 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.graphics.Color 33 | import androidx.compose.ui.text.style.TextAlign 34 | import androidx.compose.ui.unit.dp 35 | import androidx.compose.ui.window.Dialog 36 | 37 | private val progressDialogVisibleState = mutableStateOf(false) 38 | private val progressDialogText = mutableStateOf("") 39 | 40 | @Composable 41 | fun AppProgressDialog() { 42 | val isVisible by remember { progressDialogVisibleState } 43 | if (isVisible) { 44 | Dialog(onDismissRequest = { /* Progress dialogs are non-cancellable */ }) { 45 | Box( 46 | contentAlignment = Alignment.Center, 47 | modifier = 48 | Modifier 49 | .fillMaxWidth() 50 | .background(Color.White, shape = RoundedCornerShape(8.dp)), 51 | ) { 52 | Column( 53 | horizontalAlignment = Alignment.CenterHorizontally, 54 | modifier = Modifier.padding(vertical = 24.dp), 55 | ) { 56 | LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) 57 | Spacer(modifier = Modifier.padding(4.dp)) 58 | Text( 59 | text = progressDialogText.value, 60 | textAlign = TextAlign.Center, 61 | modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), 62 | ) 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | fun setProgressDialogText(message: String) { 70 | progressDialogText.value = message 71 | } 72 | 73 | fun showProgressDialog() { 74 | progressDialogVisibleState.value = true 75 | progressDialogText.value = "" 76 | } 77 | 78 | fun hideProgressDialog() { 79 | progressDialogVisibleState.value = false 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.shubham0204.sam_android.ui.theme 18 | 19 | import androidx.compose.ui.graphics.Color 20 | 21 | val Purple80 = Color(0xFFD0BCFF) 22 | val PurpleGrey80 = Color(0xFFCCC2DC) 23 | val Pink80 = Color(0xFFEFB8C8) 24 | 25 | val Purple40 = Color(0xFF6650a4) 26 | val PurpleGrey40 = Color(0xFF625b71) 27 | val Pink40 = Color(0xFF7D5260) 28 | -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.shubham0204.sam_android.ui.theme 18 | 19 | import android.os.Build 20 | import androidx.compose.foundation.isSystemInDarkTheme 21 | import androidx.compose.material3.MaterialTheme 22 | import androidx.compose.material3.darkColorScheme 23 | import androidx.compose.material3.dynamicDarkColorScheme 24 | import androidx.compose.material3.dynamicLightColorScheme 25 | import androidx.compose.material3.lightColorScheme 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.platform.LocalContext 28 | 29 | private val DarkColorScheme = 30 | darkColorScheme( 31 | primary = Purple80, 32 | secondary = PurpleGrey80, 33 | tertiary = Pink80, 34 | ) 35 | 36 | private val LightColorScheme = 37 | lightColorScheme( 38 | primary = Purple40, 39 | secondary = PurpleGrey40, 40 | tertiary = Pink40, 41 | /* Other default colors to override 42 | background = Color(0xFFFFFBFE), 43 | surface = Color(0xFFFFFBFE), 44 | onPrimary = Color.White, 45 | onSecondary = Color.White, 46 | onTertiary = Color.White, 47 | onBackground = Color(0xFF1C1B1F), 48 | onSurface = Color(0xFF1C1B1F), 49 | */ 50 | ) 51 | 52 | @Composable 53 | fun SAMAndroidTheme( 54 | darkTheme: Boolean = isSystemInDarkTheme(), 55 | // Dynamic color is available on Android 12+ 56 | dynamicColor: Boolean = true, 57 | content: @Composable () -> Unit, 58 | ) { 59 | val colorScheme = 60 | when { 61 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 62 | val context = LocalContext.current 63 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 64 | } 65 | 66 | darkTheme -> DarkColorScheme 67 | else -> LightColorScheme 68 | } 69 | 70 | MaterialTheme( 71 | colorScheme = colorScheme, 72 | typography = Typography, 73 | content = content, 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/io/shubham0204/sam_android/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Shubham Panchal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.shubham0204.sam_android.ui.theme 18 | 19 | import androidx.compose.material3.Typography 20 | import androidx.compose.ui.text.TextStyle 21 | import androidx.compose.ui.text.font.FontFamily 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.unit.sp 24 | 25 | // Set of Material typography styles to start with 26 | val Typography = 27 | Typography( 28 | bodyLarge = 29 | TextStyle( 30 | fontFamily = FontFamily.Default, 31 | fontWeight = FontWeight.Normal, 32 | fontSize = 16.sp, 33 | lineHeight = 24.sp, 34 | letterSpacing = 0.5.sp, 35 | ), 36 | /* Other default text styles to override 37 | titleLarge = TextStyle( 38 | fontFamily = FontFamily.Default, 39 | fontWeight = FontWeight.Normal, 40 | fontSize = 22.sp, 41 | lineHeight = 28.sp, 42 | letterSpacing = 0.sp 43 | ), 44 | labelSmall = TextStyle( 45 | fontFamily = FontFamily.Default, 46 | fontWeight = FontWeight.Medium, 47 | fontSize = 11.sp, 48 | lineHeight = 16.sp, 49 | letterSpacing = 0.5.sp 50 | ) 51 | */ 52 | ) 53 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 176 | 181 | 186 | 187 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 24 | 25 | 31 | 34 | 37 | 38 | 39 | 40 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubham0204/Segment-Anything-Android/5efe3b59b5478c5f3bcd62cda062e95554c53936/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | #FFBB86FC 20 | #FF6200EE 21 | #FF3700B3 22 | #FF03DAC5 23 | #FF018786 24 | #FF000000 25 | #FFFFFFFF 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | SAM-Android 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 |