├── Android ├── README.md ├── ReleaseNote.md ├── SampleCode │ ├── FoodLensTestApplication.iml │ ├── app │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── doinglab │ │ │ │ └── sdk │ │ │ │ └── example │ │ │ │ └── foodlenstestapplication │ │ │ │ └── ExampleInstrumentedTest.java │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── doinglab │ │ │ │ │ └── sdk │ │ │ │ │ └── example │ │ │ │ │ └── foodlenstestapplication │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── listview │ │ │ │ │ ├── ListViewAdapter.java │ │ │ │ │ └── ListViewItem.java │ │ │ └── res │ │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── drawable │ │ │ │ └── ic_launcher_background.xml │ │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ └── list_item.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── doinglab │ │ │ └── sdk │ │ │ └── example │ │ │ └── foodlenstestapplication │ │ │ └── ExampleUnitTest.java │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── local.properties │ └── settings.gradle ├── SampleCode_Kotlin │ ├── .gitignore │ ├── .idea │ │ ├── .gitignore │ │ ├── .name │ │ ├── compiler.xml │ │ ├── gradle.xml │ │ ├── misc.xml │ │ └── vcs.xml │ ├── app │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── doinglab │ │ │ │ └── sdk │ │ │ │ └── example │ │ │ │ └── foodlenstestapplication │ │ │ │ └── ExampleInstrumentedTest.kt │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── doinglab │ │ │ │ │ └── sdk │ │ │ │ │ └── example │ │ │ │ │ └── foodlenstestapplication │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ └── listview │ │ │ │ │ ├── ListViewAdapter.kt │ │ │ │ │ └── ListViewItem.kt │ │ │ └── res │ │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── drawable │ │ │ │ └── ic_launcher_background.xml │ │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ └── list_item.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── 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-night │ │ │ │ └── themes.xml │ │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── themes.xml │ │ │ │ └── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── doinglab │ │ │ └── sdk │ │ │ └── example │ │ │ └── foodlenstestapplication │ │ │ └── ExampleUnitTest.kt │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle └── image │ ├── image_v1_1.png │ ├── image_v1_2.png │ ├── image_v2_1.png │ └── image_v2_2.png ├── IOS ├── Images │ ├── V101.PNG │ ├── V102.PNG │ ├── V201.PNG │ ├── V202.PNG │ ├── infoplist.png │ ├── spm1.png │ └── spm2.png ├── LICENSE ├── README.md ├── README_en.md ├── ReleaseNote.md └── SampleCode │ ├── FoodLensTestApp.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ ├── eddylee.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── hwangchunlee.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── FoodLensTestApp.xcscheme │ └── xcuserdata │ │ └── hwangchunlee.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── FoodLensTestApp.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ ├── eddylee.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── hwangchunlee.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ ├── Breakpoints_v2.xcbkptlist │ │ └── Expressions.xcexplist │ ├── FoodLensTestApp │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── TableViewCell.swift │ ├── Util.swift │ ├── ViewController.swift │ ├── aa2c2043c2984529b77851d25c7bbf6c.jpg │ ├── full_Image.jpeg │ └── parted_image.jpg │ ├── FoodLensTestAppTests │ ├── FoodLensTestAppTests.swift │ └── Info.plist │ ├── FoodLensTestAppUITests │ ├── FoodLensTestAppUITests.swift │ └── Info.plist │ ├── Podfile │ └── Podfile.lock ├── JSON Format ├── JSON Sample ├── LICENSE ├── README.md └── README_En.md /Android/README.md: -------------------------------------------------------------------------------- 1 | # Android용 FoodLens SDK 메뉴얼 2 | 3 | Android용 FoodLens SDK를 사용하여 FoodLens 기능을 이용할 수 있습니다. 4 | FoodLens SDK는 Network SDK와 UI SDK로 이루어 지며, 자체 UI를 작성할 경우는 Network SDK를, Doinglab에서 제공하는 UI화면까지 사용할 경우는 UI SDK를 사용하셔서 FoodLens의 기능을 이용하실 수 있습니다. 5 | 6 | ## [ReleaseNote 바로가기](ReleaseNote.md) 7 | 8 | ## FoodLens SDK 9 | 10 | 11 | 12 | ## 1. 안드로이드 프로젝트 설정 13 | 14 | ### 1.1 gradle 설정 15 | - minSdkVersion은 21 이상을 사용하시기 바랍니다. 16 | 프로젝트에서 app > Gradle Scripts(그래들 스크립트) > build.gradle (Module: app)을 연 후 defaultConfig{} 섹션에 아래와 같은 문구를 추가해 주세요. 17 | ```java 18 | defaultConfig { 19 | .... 20 | minSdkVersion 21 21 | .... 22 | } 23 | ``` 24 | 25 | - Optional. Compile Error 발생시 프로젝트에서 app > Gradle Scripts(그래들 스크립트) > build.gradle (Module: app)을 연 후 android{} 섹션에 아래와 같은 문구를 추가해 주세요. (ex) Java version 1.8 일 경우) 26 | ```java 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | ``` 32 | #### 1.1.1 maven repository설정 33 | 최신 버전 프로젝트로 mavenCentral만 기본 설정일 경우 jcenter 리포지토리를 프로젝트 그래들에 추가 34 | ```java 35 | buildscript { 36 | 37 | repositories { 38 | google() 39 | mavenCentral() 40 | jcenter() 41 | } 42 | dependencies { 43 | classpath 'com.android.tools.build:gradle:7.2.1' 44 | } 45 | } 46 | 47 | allprojects { 48 | repositories { 49 | google() 50 | mavenCentral() 51 | jcenter() 52 | } 53 | } 54 | ``` 55 | #### 1.1.2 FoodLens SDK 버전 설정 56 | - 프로젝트에서 app > Gradle Scripts(그래들 스크립트) > build.gradle (Module: app)을 연 후 dependencies{} 섹션에 아래와 같은 문구를 추가해 주세요. 57 | ```java 58 | implementation "com.doinglab.foodlens:FoodLens:2.6.4" #최신 버전을 릴리즈 노트 확인 59 | ``` 60 | - 예제 61 | ```java 62 | apply plugin: 'com.android.application' 63 | 64 | android { 65 | compileSdkVersion 29 66 | defaultConfig { 67 | applicationId "com.doinglab.sdk.sample" 68 | minSdkVersion 21 69 | targetSdkVersion 29 70 | versionCode 1 71 | versionName "1.0" 72 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 73 | multiDexEnabled true 74 | } 75 | buildTypes { 76 | release { 77 | minifyEnabled false 78 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 79 | } 80 | } 81 | compileOptions { 82 | sourceCompatibility JavaVersion.VERSION_1_8 83 | targetCompatibility JavaVersion.VERSION_1_8 84 | 85 | } 86 | } 87 | 88 | dependencies { 89 | implementation fileTree(dir: 'libs', include: ['*.jar']) 90 | implementation 'com.android.support:appcompat-v7:28.0.0' 91 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 92 | testImplementation 'junit:junit:4.12' 93 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 94 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 95 | implementation "com.doinglab.foodlens:FoodLens:2.6.4" 96 | } 97 | ``` 98 | 99 | - 사용하는 라이브러리의 버전 충돌시 아래와 같이 버전이 충돌되는 라이브러리를 제외하고 SDK가 참조되도록 수정합니다. 100 | ```java 101 | implementation ("com.doinglab.foodlens:FoodLens:$FOODLENS_SDK_VERSION") { 102 | exclude group: 'com.android.support', module: 'appcompat' 103 | exclude group: 'com.android.support', module: 'design' 104 | exclude group: 'com.android.support', module: 'support-v4' 105 | exclude group: 'com.android.support.constraint', module: 'constraint-layout' 106 | } 107 | ``` 108 | 109 | ## 2. 리소스(Resources) 및 메니페스트(Manifests) 110 | AccessToken과 Company, AppToken을 중 한가지만 세팅 합니다. 111 | 112 | ### 2.1 Access Token만 있는 경우 113 | 발급된 AccessToken을 /app/res/values/strings.xml에 추가 합니다. 114 | ```xml 115 | [AccessToken] 116 | ``` 117 | 118 | * Meta data추가 119 | 아래와 같이 메타데이터를 Manifest.xml에 추가해 주세요 120 | ```xml 121 | 122 | ``` 123 | ### 2.2 AppToken, CompanyToken이 있는 경우 124 | 발급된 AppToken, CompanyToken을 /app/res/values/strings.xml에 추가 합니다. 125 | ```xml 126 | [AppToken] 127 | [CompanyToken] 128 | ``` 129 | 130 | * Meta data추가 131 | 아래와 같이 메타데이터를 Manifest.xml에 추가해 주세요 132 | ```xml 133 | 134 | 135 | ``` 136 | 137 | ### 2.3 공통 138 | * ProGuard 설정 139 | 앱에서 proguard를 통한 난독화를 설정할 경우 아래와 깉이 proguard 설정을 설정 파일에 추가해 주세요 140 | ```xml 141 | -keep public class com.doinglab.foodlens.sdk.** { 142 | *; 143 | } 144 | -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken 145 | -keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken 146 | ``` 147 | 148 | ## 3.독립 FoodLens 서버 주소 설정 149 | - 기본 FoodLens 서버가 아닌 독립 서버를 운용할 경우 서버 주소를 설정 할 수 있습니다.(별도 협의) 150 | - Meta data추가 아래와 같이 메타데이터를 Manifest.xml에 추가해 주세요 151 | ```xml 152 | //프로토콜과 및 포트를 제외한 순수 도메인 주소 혹은 IP주소 e.g) www.foodlens.com, 123.222.100.10 153 | 154 | ``` 155 | 156 | 157 | ## 4. SDK 사용법 사용법 158 | 159 | ### 4.1 Network API 사용법 160 | Network API는 FoodLens기능을 이미지 파일기반으로 동작하게 하는 기능입니다. 161 | 결과를 이용하여 각자에 맞는 UI를 직접 개발 할 수 있습니다. 162 | 163 | #### 4.1.1 음식 인식 기능 사용 164 | 1. NetworkService를 생성합니다. 165 | 2. predictMultipleFood 메소드를 호출 합니다. 166 | 파라미터는 Jpeg image와 RecognizeResultHandler 입니다. 167 | Jpeg이미지는 카메라 촬영 원본 이미지를 전달해 줍니다. 168 | ※ 이미지가 작은경우 인식율이 낮아질 수 있습니다. 169 | 3. 코드 예제 170 | ```java 171 | //Create Network Service 172 | final NetworkService ns = FoodLens.createNetworkService(context); 173 | //Call prediction method. 174 | ns.predictMultipleFood(byteData, new RecognizeResultHandler() { 175 | @Override 176 | public void onSuccess(RecognitionResult result) { 177 | List foodPosList = result.getFoodPositions(); //Get food positions 178 | 179 | for(FoodPosition fp : foodPosList) { 180 | List foods = fp.getFoods(); //Get food candidates at this position 181 | for(Food food : foods) { 182 | //Print out food name at this position 183 | Log.i("FoodLens", food.getFoodName()); 184 | } 185 | } 186 | } 187 | 188 | @Override 189 | public void onError(BaseError errorReason) { 190 | Log.e("FoodLens", errorReason.getMessage()); 191 | } 192 | }); 193 | ``` 194 | #### 4.1.2 음식 결과 영양정보 얻기 모드 195 | 옵션에 따라 인식결과의 영양정보를 다르게 얻을 수 있다. 196 | ```java 197 | //Create Network Service 198 | final NetworkService ns = FoodLens.createNetworkService(context); 199 | ns.setNutritionRetrieveMode(NutritionRetrieveMode.TOP1_NUTRITION_ONLY); //예측 값중 예측 우선순위가 가장 높은 1개의 영양 정보만 리턴한다. 200 | ns.predictMultipleFood(byteData, new RecognizeResultHandler() { 201 | ... 202 | ``` 203 | 204 | #### 4.1.3 음식 결과 언어 설정 205 | 옵션에 따라 음식 결과의 언어를 설정 할 수 있습니다. 206 | ```java 207 | //Create Network Service 208 | final NetworkService ns = FoodLens.createNetworkService(context); 209 | //LanguageConfig.EN 음식결과의 리턴값 언어를 영어로 설정 210 | //LanguageConfig.KO 음식결과의 리턴값 언어를 한국어로 설정 211 | //LanguageConfig.DEVICE default로 디바이스 언어 설정을 따라가며 한국어가 아닌경우 영어로 설정, 생략가능 212 | ns.setLanguageConfig(LanguageConfig.EN); 213 | ns.predictMultipleFood(byteData, new RecognizeResultHandler() { 214 | ... 215 | ``` 216 | 217 | #### 4.1.4 음식 영양정보 얻기 218 | 1. NetworkService를 생성합니다. 219 | 2. getNutritionInfo 메소드를 호출 합니다. 220 | 파라미터는 FoodID와 NutritionResultHandler 입니다. 221 | ※ FoodID의 경우 Prediction결과 및 AutoComplete결과에서 획득 할 수 있습니다. 222 | - 코드 예제 223 | ```java 224 | //Create Network Service 225 | final NetworkService ns = FoodLens.createNetworkService(context); 226 | ns.getNutritionInfo([food_id], new NutritionResultHandler() { 227 | @Override 228 | public void onSuccess(NutritionResult result) { 229 | //Print out calorie information 230 | Log.i("FoodLens", String.format("Calorie : %f", result.getNutrition().getCalories())); 231 | } 232 | 233 | @Override 234 | public void onError(BaseError errorReason) { 235 | Log.e("FoodLens", errorReason.getMessage()); 236 | } 237 | }); 238 | ``` 239 | #### 4.1.5 음식항목 검색하기 240 | 1. NetworkService를 생성합니다. 241 | 2. searchFoodsByName 메소드를 호출 합니다. 242 | 파라미터는 음식이름과 SearchResultHandler 입니다. 243 | - 코드 예제 244 | ```java 245 | 246 | ns = FoodLens.createNetworkService(getApplicationContext()); 247 | ns.searchFoodsByName("라면", new SearchResultHandler() { 248 | @Override 249 | public void onSuccess(FoodSearchResult result) { 250 | 251 | } 252 | 253 | @Override 254 | public void onError(BaseError errorReason) { 255 | 256 | } 257 | }); 258 | ``` 259 | 260 | ### 4.2 UI API 사용법 261 | UI API는 FoodLens 에서 제공하는 기본 UI를 활용하여 서비스를 개발 할 수 있는 기능입니다. 262 | UI API는 간단한 화면 Customize기능을 포함하고 있습니다. 263 | 264 | #### 4.2.1 UI Service의 카메라 모듈 및 인식 기능 사용 265 | 1. UIService를 생성합니다. 266 | 2. startFoodLensCamera 메소드를 호출 합니다. 267 | - 코드 예제 268 | ```java 269 | //Define UI Service 270 | private UIService uiService; 271 | 272 | ... 273 | 274 | //Create UI Service 275 | uiService = FoodLens.createUIService(context); 276 | uiService.startFoodLensCamera(MainActivity.this, new UIServiceResultHandler() { 277 | @Override 278 | public void onSuccess(UserSelectedResult result) { 279 | //implement code 280 | } 281 | 282 | @Override 283 | public void onCancel() { 284 | Log.d("MSG_LOG", "Recognition Cancel"); 285 | } 286 | 287 | @Override 288 | public void onError(BaseError error) { 289 | Log.d("MSG_LOG", error.getMessage()); 290 | } 291 | }); 292 | 293 | ``` 294 | 295 | 3. UIService의 startFoodLensCamera를 호출한 Activity의 onActivityResult(Override)에 296 | UIService의 onActivityResult 메소드를 호출합니다. 297 | - UIService setUseActivityResult(false) 설정시 생략 가능합니다. 298 | - 코드예제 299 | ```java 300 | 301 | @Override 302 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 303 | .... 304 | uiService.onActivityResult(requestCode, resultCode, data); 305 | .... 306 | } 307 | ``` 308 | 309 | #### 4.2.2 UI Service의 갤러리 기능 사용 310 | 1. UIService를 생성합니다. 311 | 2. startFoodLensGallery 메소드를 호출 합니다. 312 | - 코드 예제 313 | ```java 314 | //Define UI Service 315 | private UIService uiService; 316 | 317 | ... 318 | 319 | //Create UI Service 320 | uiService = FoodLens.createUIService(context); 321 | uiService.startFoodLensGallery(MainActivity.this, new UIServiceResultHandler() { 322 | @Override 323 | public void onSuccess(UserSelectedResult result) { 324 | //implement code 325 | } 326 | 327 | @Override 328 | public void onCancel() { 329 | Log.d("MSG_LOG", "Recognition Cancel"); 330 | } 331 | 332 | @Override 333 | public void onError(BaseError error) { 334 | Log.d("MSG_LOG", error.getMessage()); 335 | } 336 | }); 337 | 338 | ``` 339 | 340 | 3. UIService의 startFoodLensGallery 호출한 Activity의 onActivityResult(Override)에 341 | UIService의 onActivityResult 메소드를 호출합니다. 342 | - UIService setUseActivityResult(false) 설정시 생략 가능합니다. 343 | - 코드예제 344 | ```java 345 | @Override 346 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 347 | .... 348 | uiService.onActivityResult(requestCode, resultCode, data); 349 | .... 350 | } 351 | ``` 352 | 353 | #### 4.2.3 UI Service의 검색 기능 사용 354 | 1. UIService를 생성합니다. 355 | 2. startFoodLensSearch 메소드를 호출 합니다. 356 | - 코드 예제 357 | ```java 358 | //Define UI Service 359 | private UIService uiService; 360 | 361 | ... 362 | 363 | //Create UI Service 364 | uiService = FoodLens.createUIService(context); 365 | uiService.startFoodLensSearch(MainActivity.this, new UIServiceResultHandler() { 366 | @Override 367 | public void onSuccess(UserSelectedResult result) { 368 | //implement code 369 | } 370 | 371 | @Override 372 | public void onCancel() { 373 | Log.d("MSG_LOG", "Recognition Cancel"); 374 | } 375 | 376 | @Override 377 | public void onError(BaseError error) { 378 | Log.d("MSG_LOG", error.getMessage()); 379 | } 380 | }); 381 | 382 | ``` 383 | 384 | 3. UIService의 startFoodLensSearch 호출한 Activity의 onActivityResult(Override)에 385 | UIService의 onActivityResult 메소드를 호출합니다. 386 | - UIService setUseActivityResult(false) 설정시 생략 가능합니다. 387 | - 코드예제 388 | ```java 389 | 390 | @Override 391 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 392 | .... 393 | uiService.onActivityResult(requestCode, resultCode, data); 394 | .... 395 | } 396 | ``` 397 | 398 | 399 | #### 4.2.4 UI Service의 Data 수정 기능 400 | 1. UIService를 생성합니다. 401 | 2. 4.1.2과 4.2.1에서 획득한 영양정보를 recognitionResult에 저장합니다. 402 | 3. startFoodLensDataEdit 메소드를 호출 합니다. 403 | - 코드 예제 404 | ```java 405 | //Define UI Service 406 | private UIService uiService; 407 | RecognitionResult recognitionResult = null; 408 | 409 | ... 410 | 411 | //Create UI Service 412 | uiService = FoodLens.createUIService(context); 413 | uiService.startFoodLensDataEdit(MainActivity.this, recognitionResult, new UIServiceResultHandler() { 414 | @Override 415 | public void onSuccess(UserSelectedResult result) { 416 | //implement code 417 | } 418 | 419 | @Override 420 | public void onCancel() { 421 | Log.d("MSG_LOG", "Recognition Cancel"); 422 | } 423 | 424 | @Override 425 | public void onError(BaseError error) { 426 | Log.d("MSG_LOG", error.getMessage()); 427 | } 428 | }); 429 | 430 | ``` 431 | 432 | 4. UIService의 startFoodLensDataEdit 호출한 Activity의 onActivityResult(Override)에 433 | UIService의 onActivityResult 메소드를 호출합니다. 434 | - UIService setUseActivityResult(false) 설정시 생략 가능합니다. 435 | - 코드예제 436 | ```java 437 | 438 | @Override 439 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 440 | .... 441 | uiService.onActivityResult(requestCode, resultCode, data); 442 | .... 443 | } 444 | ``` 445 | 446 | #### 4.2.5 onActivityResult 호출 여부 선택 447 | onActivityResult에서 uiService.onActivityResult 호출 여부를 선택 할 수 있다. 448 | ```java 449 | //false 설정시 onActivityResult 에서 uiService.onActivityResult 호출할 필요 없습니다. 450 | //Default 는 true로 onActivityResult 에서 uiService.onActivityResult 호출 해야합니다. 451 | uiService.setUseActivityResult(false) 452 | ``` 453 | 454 | #### 4.2.6 영양정보 추출 모드 455 | 인식 결과를 리턴 받을 때 추천항목의 영양소까지 받을지 여부를 선택 할 수 있다. 456 | ```java 457 | //USER_SELECTED_WITH_CANDIDATES 사용자 선택외 추천된 항목의 모든 영양정보가 반환된다. 458 | //USER_SELECTED_ONLY 선택시 사용자가 선택항 항목의 영양소만 반환된다. 459 | uiService.setUiServiceMode(UIServiceMode.USER_SELECTED_WITH_CANDIDATES); 460 | ``` 461 | 462 | #### 4.2.7 테마 및 옵션 변경 463 | ##### 4.2.7.1 UI 테마 변경 464 | FoodLens UI 의 여러 요소에 개별 색을 적용할 수 있습니다. 465 | ```java 466 | BottomWidgetTheme bottomWidgetTheme = new BottomWidgetTheme(this); 467 | bottomWidgetTheme.setButtonTextColor(0xffffff); 468 | bottomWidgetTheme.setWidgetRadius(30); 469 | 470 | DefaultWidgetTheme defaultWidgetTheme = new DefaultWidgetTheme(this); 471 | defaultWidgetTheme.setWidgetColor(0xffffff); 472 | 473 | ToolbarTheme toolbarTheme = new ToolbarTheme(this); 474 | toolbarTheme.setBackgroundColor(0xffffff); 475 | 476 | uiService.setBottomWidgetTheme(bottomWidgetTheme); 477 | uiService.setDefaultWidgetTheme(defaultWidgetTheme); 478 | uiService.setToolbarTheme(toolbarTheme); 479 | ``` 480 | ##### 4.2.7.2 FoodLens 옵션 변경 481 | FoodLens의 사용 옵션을 변경 할 수 있습니다. 482 | ```java 483 | FoodLensBundle bundle = new FoodLensBundle(); 484 | bundle.setEnableManualInput(true); //검색입력 활성화 여부 485 | bundle.setEatType(1); //식사 타입 수동 선택 486 | bundle.setSaveToGallery(true); //갤러리 기능 활성화 여부 487 | bundle.setUseImageRecordDate(true); //갤러리에 저장된 사진의 사진촬영시간을 입력시간으로 사용할지 여부 488 | bundle.setEnableCameraOrientation(true); //카메라 회전 기능 지원 여부 489 | bundle.setEnablePhotoGallery(true); //카메라 화면 갤러리 버튼 활성화 여부 490 | bundle.setLanguageConfig(LanguageConfig.KO); //음식 결과의 언어 설정, DEVICE(default),EN,KO 선택가능 491 | uiService.setDataBundle(bundle); 492 | 493 | ``` 494 | 495 | ##### 4.2.7.3 식사 타입 자동 설정 496 | 사용자가 setEatType을 이용하여 식사타입 설정을 직접 하지 않은 경우, 음식 식사 타입은 기준 시간을 기준으로 자동설정됨 497 | 설정되는 시간 값 498 | ``` 499 | 아침 : 5시 ~ 10시 500 | 아침간신 : 10 ~ 11시 501 | 점심 : 11시 ~ 13시 502 | 점심간신 : 13시 ~ 17시 503 | 저녁 : 17시 ~ 20시 504 | 야식 : 20시 ~ 5시 505 | ``` 506 | 507 | #### 4.3 RecognitionResult의 JSON변환 508 | RecognitionResult 객체를 JSON 문자열로 변환할 수 있습니다. 509 | ```java 510 | String json = recognitionResult.toJSONString(); //json생성 511 | ``` 512 | 513 | JSON 문자열을 PredictionResult 객체로 변환할 경우, 아래처럼 사용하실 수 있습니다. 514 | ```java 515 | RecognitionResult result = RecognitionResult.create(json); 516 | ``` 517 | 518 | 519 | #### 4.4 섭취한 영양정보 산출 계산식 520 | 영양성분은 1회 기준량으로 제공되며, 같이 제공되는 섭취량을 곱하여 실제 영양성분을 계산할 수 있습니다. 521 | ```java 522 | for(int i = 0; i < recognitionResult.getFoodPositions().size(); i++) { 523 | FoodPosition foodPosition = foodPositions.get(i); 524 | float eatAmount = foodPosition.getEatAmount(); // 사용자가 설정한 1회 섭취량 525 | Nutrition nutrition = foodPosition.getUserSelectedFood().getNutrition(); // 선택한 음식에 대한 Raw 영양정보 데이터 526 | eatAmount * nutrition.getCarbonHydrate(); // 1회 섭취한 음식에 대한 탄수화물 섭취량 527 | eatAmount * nutrition.getProtein(); // 1회 섭취한 음식에 대한 단백질 섭취량 528 | eatAmount * nutrition.getFat(); // 1회 섭취한 음식에 대한 지방 섭취량 529 | ... 530 | } 531 | ``` 532 | 533 | ... 534 | 535 | ## 5. Error Code 536 | ### 401: UNAUTHORIZED 537 | - CompanyToken 혹은 AppToken 이 잘못되었거나 빈값인 경우 538 | ### 402: PAYMENT REQUIRED 539 | - API 호출 횟수가 계약횟수를 초과한 경우 혹은 비용 결제가 안된경우 540 | ### 403: FORBIDDEN 541 | - 사용할 수 없는 기능 542 | ### 404: NOT FOUND 543 | - 지원하지 않는 URL 544 | ### 406: NOT ACCEPTABLE 545 | - 등록한 패키지명과 API를 호출한 서비스의 패키지명이 다른 경우 546 | ### 5xx: SERVER ERROR 547 | - 서버에러 548 | 549 | 550 | ## 6. SDK 상세 스펙 551 | [상세 API 명세](https://doinglab.github.io/android/index.html) 552 | 553 | ## 7. SDK 사용 예제 554 | [Sample 예제](SampleCode/) 555 | 556 | ## 8. JSON Format 557 | [JSON Format](../JSON%20Format) 558 | 559 | [JSON Sample](../JSON%20Sample) 560 | 561 | ## 8. Author 562 | hyunsuk.lee@doinglab.com 563 | 564 | ## 9. License 565 | FoodLens is available under the MIT license. See the LICENSE file for more info. 566 | -------------------------------------------------------------------------------- /Android/ReleaseNote.md: -------------------------------------------------------------------------------- 1 | # Android FoodLensSDK Release Note 2 | 3 | ## v2.6.6 4 | (2024.09.12) 5 | 1. Android 14 앱에서 선택적 권한 추가시 반복적으로 권한 요청 발생하는 현상 수정 6 | 7 | ## v2.6.5 8 | (2024.08.29) 9 | 1. JCenter 의존성을 Maven Central로 마이그레이션 10 | 11 | ## v2.6.4 12 | (2024.04.30) 13 | 1. 식사기록 수정 화면 진입시 인분으로 단위가 변경되는 현상 수정 14 | 15 | ## v2.6.3 16 | (2024.04.02) 17 | 1. 직접입력 바로 진입시 카메라 관련 퍼미션 체크 하지 않도록 수정 18 | 19 | ## v2.6.1 20 | (2024.02.15) 21 | 1. Android 14 선택적 권한에서 권한 없는 사진 선택시 응답없는 문제 수정 22 | 23 | ## v2.6.0 24 | (2024.02.13) 25 | 1. NetworkService 특정 이미지에서 전체 이미지 recycle 되는 현상 수정 26 | 2. NetworkService 큰 사이즈 이미지 요청시 리사이징 하도록 수정(짧은 축 최대 3500) 27 | 28 | ## v2.5.8 29 | (2023.11.24) 30 | 1. 갤럭시 폴드 음식추천/음식검색/내음식 텍스트 붙어보이는 현상 수정 31 | 32 | ## v2.5.7 33 | (2023.11.15) 34 | 1. 날짜 선택시 미래 선택 안되도록 변경 35 | 36 | ## v2.5.6 37 | (2023.10.19) 38 | 1. 음식조회 결과화면 분석 진행중에 입력완료 버튼 클릭 방지 및 토스트 알림 추가 39 | 40 | ## v2.5.5 41 | (2023.10.17) 42 | 1. 음식조회 결과화면 입력완료 버튼 중복 클릭 방지 기능 추가 43 | 44 | ## v2.5.4 45 | (2023.08.11) 46 | 1. uiService setUseActivityResult 기능 추가 47 | 2. 음식 결과 리턴값 foodtype 데이터 추가 48 | 49 | ## v2.5.3 50 | (2023.07.05) 51 | 1. 안전하지 않은 SSL 예외처리 코드 삭제 52 | 53 | ## v2.5.1 54 | (2023.05.08) 55 | 1. uiService 검색, 갤러리 바로가기 기능 추가 56 | 2. 음식 결과 리턴값 언어 설정 기능 추가 57 | 3. 사용하지 않는 라이브러리 삭제(guava) 58 | 59 | ## v2.5.0 60 | (2023.03.20) 61 | 1. 카메라 플래시 오동작 수정 62 | 2. 영양정보 계산법 수정 63 | 64 | ## v2.4.6 65 | (2023.02.09) 66 | 1. 음식 수정시 중복선택되는 문제 수정 67 | 2. 영양 성분표 오기 수정 68 | 69 | ## v2.4.5 70 | (2023.02.01) 71 | 1. 검색 성능 최적화 72 | 73 | ## v2.4.4 74 | (2023.01.31) 75 | 1. 갤러리 활성화 되어 있지 않을때, 갤러리 권한 문제 해결 76 | 77 | ## v2.4.3 78 | (2022.11.26) 79 | 1. 카메라 화면 포토갤러리 비활성화시, 갤러리 권한 요청하지 않도록 수정 80 | 81 | ## v2.4.1 82 | (2022.11.03) 83 | 1. 카메라 화면 포토갤러리 활성화 옵션 추가 84 | 85 | ## v2.4.0 86 | (2022.11.03) 87 | 1. Network Service로 받아온 데이터 Editmode 에서 접근하게 수정 88 | 89 | ## v2.3.9 90 | (2022.11.01) 91 | 1. Android 13 버전 권한 이슈로 카메라 실행시 종료되는 문제 수정 92 | 93 | ## v2.3.8 94 | (2022.10.21) 95 | 1. NetworkService를 활용할때 AccessToken을 사용할 경우 exception이 발생하는 문제 수정 96 | 97 | ## v2.3.7 98 | (2022.10.20) 99 | 1. 검색 화면 간헐적으로 크래쉬 발생하는 문제 수정 100 | 101 | ## v2.3.6 102 | (2022.09.06) 103 | 1. 갤러리에서 이미지 로딩시 촬영일자 사용에 대한 기본 옵션을 off에서 on으로 변경 104 | 105 | ## v2.3.5 106 | (2022.08.24) 107 | 1. NetworkService를 활용할 때 AppToken이 없으면 exception이 발생하는 문제 수정 108 | 109 | ## v2.3.4 110 | (2022.07.29) 111 | 1. Network 서비스 사용시, 네트워크가 연결되어있지 않을 경우 크래쉬가 발생하는 문제 수정 112 | 113 | ## v2.3.3 114 | (2022.05.13) 115 | 1. 간헐적 카메라 화면 백그란드 진입 후 포그란드로 진입할때 발생하는 크래쉬 문제 해결 116 | 117 | ## v2.3.2 118 | (2022.04.27) 119 | 1. NetworkService AccessToken 및 CompanyToken/AppToken 동시 지원 기능 추가 120 | 121 | ## v2.3.1 122 | (2022.04.13) 123 | 1. 검색 화면 배율 조정 (갤러시 폴드 지원) 124 | 2. AccessToken 및 CompanyToken/AppToken 동시 지원 기능 추가 125 | 126 | ## v2.3.0 127 | (2022.03.30) 128 | 1. Private Maven repository에서 public Maven Central로 저장소 이동 CompanyToken, AppToken지원용 129 | 130 | ## v2.2.13 131 | (2022.03.30) 132 | 1. Private Maven repository에서 public Maven Central로 저장소 이동 AccessToken 지원용 133 | 134 | ## v2.2.12 135 | (2021.11.12) 136 | 1. 권한 거부시 비정상동작 수정 (권한 세팅 페이지로 이동) 137 | 138 | ## v2.2.11 139 | (2021.10.07) 140 | 1. 음식양 상세 입력시 입력완료 버튼 위치 변경 141 | 142 | ## v2.2.10 143 | (2021.08.03) 144 | 1. 폴더블폰 비율 대응 145 | 2. 버튼이 카메라 화면에 가리는 문제 146 | 147 | ## v2.2.9 148 | (2021.08.02) 149 | 1. 화면 비율에 따라 버튼이 안보이는 문제 수정 150 | 151 | 152 | ## v2.2.8 153 | (2021.01.27) 154 | 1. 특정 버전 안드로이드에서 크래쉬 문제 해결 155 | 2. API 21이하 TLS 호환성 문제 해결 156 | 157 | ## v2.2.7 158 | (2021.01.26) 159 | 1. 수동 입력시 종료 현상 수정 160 | 2. 수동 입력 화면 비율 문제 수정 161 | 3. 카메라 layout 일부 수정 162 | 163 | ## v2.2.6 164 | (2021.01.19) 165 | 1. 오류를 확인하기 위한 기능 추가 166 | 167 | ## v2.2.5 168 | (2021.01.05) 169 | 1. 오픈소스 라이브러리 최신화 170 | 2. 카메라 세팅 변경 171 | 3. Network SDK 사용시 작은 이미지 처리시 예외 사항 처리 172 | 173 | ## v2.2.4 174 | (2020.12.18) 175 | 1. 인식속도 개선 176 | 177 | ## v2.2.2 178 | (2020.11.23) 179 | 1. 음식 인식 화면 Thumbnail 이미지가 잘못 나오는 문제 해결 180 | 181 | ## v2.2.1 182 | (2020.10.06) 183 | 1. 입력 이미지 크기 기반으로 예측된 음식위치 설정 184 | 185 | ## v2.2.0 186 | (2020.09.27) 187 | 1. Android SDK 29 지원되도록 수정 (Storage 및 Permission관련 수정) 188 | 2. FoodLensSDK compile version 29로 변경 189 | 190 | ## v2.1.4 191 | (2020.10.06) 192 | 1. 입력 이미지 크기 기반으로 예측된 음식위치 설정 193 | 194 | ## v2.1.3 195 | (2020.09.27) 196 | 1. FoodPosition관련 최대 widht, height를 얻을 수 있는 API 추가 197 | 198 | ## v2.1.1 199 | (2020.05.25) 200 | 1. Android 10 관련 스토리지 문제 해결 201 | 202 | ## v2.1.0 203 | (2020.05.12) 204 | 1. 사용자 음식 만들기 추가 205 | 2. 간식 타입 추가 (아침간식, 점심간식, 야식) 206 | 3. 식사 시간을 설정 할 수 있는 기능 추가 207 | 4. 독립서버 주소를 설정 할 수 있는 기능 추가 208 | 209 | ## v2.0.24 210 | (2020-04-16) 211 | 1. 인신된 결과 수정이후 시간정보가 누락되는 문제 수정 212 | 213 | ## v2.0.23 214 | (2020-01-23) 215 | 1. 인식성능 개선 216 | 217 | ## v2.0.22 218 | (2020-01-10) 219 | 1. 카메라화면 이미지 처리관련 안정성 개선 220 | 2. 간식타입 세분화(오전간식, 오후간식, 야식) 221 | 222 | ## v2.0.21 223 | 1. 검색화면 키패드 엔터버튼 기능 수정 224 | 225 | ## v2.0.19 226 | 1. 카메라 화면에서 직접 입력 버튼 숨김 가능하도록 수정 227 | 228 | ## v2.0.18 229 | 1. 네크워크 연결 관련 안정성 개선 230 | 231 | ## v2.0.17 232 | 1. 검색시 통신 알고리즘 안정성 개선 233 | 234 | ## v2.0.16 235 | 1. 검색시 통신 알고리즘 안정성 개선 236 | 237 | ## v2.0.15 238 | 1. retrofit v2.5.0버전 사용시 일부 단말에서 검색시 crash 문제 수정 239 | 240 | ## v2.0.14 241 | 1. FoodLensBundle.setEnableCameraOrientation() 기본값 true로 변경 242 | 243 | ## v2.0.11 244 | 1. 상세입력 EditeText입력값 관련 패턴 검사 로직 추가 245 | 2. 상세입력 EditeText롱 클릭시 갤럭시 일부 단말기에서 죽는 현상 수정 246 | -------------------------------------------------------------------------------- /Android/SampleCode/FoodLensTestApplication.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Android/SampleCode/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Android/SampleCode/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 31 5 | defaultConfig { 6 | applicationId "com.doinglab.sdk.example.foodlenstestapplication" 7 | minSdkVersion 26 8 | targetSdkVersion 30 9 | versionCode 1 10 | versionName "1.0" 11 | multiDexEnabled true 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | namespace 'com.doinglab.sdk.example.foodlenstestapplication' 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation 'androidx.appcompat:appcompat:1.0.0' 31 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 32 | implementation 'androidx.multidex:multidex:2.0.1' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 35 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 36 | implementation 'com.doinglab.foodlens:FoodLens:2.3.3' 37 | } 38 | -------------------------------------------------------------------------------- /Android/SampleCode/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 22 | -------------------------------------------------------------------------------- /Android/SampleCode/app/src/androidTest/java/com/doinglab/sdk/example/foodlenstestapplication/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.doinglab.sdk.example.foodlenstestapplication; 2 | 3 | import android.content.Context; 4 | import androidx.test.platform.app.InstrumentationRegistry; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.doinglab.sdk.example.foodlenstestapplication", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Android/SampleCode/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Android/SampleCode/app/src/main/java/com/doinglab/sdk/example/foodlenstestapplication/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.doinglab.sdk.example.foodlenstestapplication; 2 | 3 | import android.content.Intent; 4 | import android.database.Cursor; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.drawable.BitmapDrawable; 8 | import android.graphics.drawable.Drawable; 9 | import android.net.Uri; 10 | import android.provider.MediaStore; 11 | import androidx.annotation.Nullable; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | import android.os.Bundle; 14 | import android.util.Log; 15 | import android.view.View; 16 | import android.widget.Button; 17 | import android.widget.ListView; 18 | import android.widget.TextView; 19 | import android.widget.Toast; 20 | 21 | import com.doinglab.foodlens.sdk.FoodLens; 22 | import com.doinglab.foodlens.sdk.FoodLensBundle; 23 | import com.doinglab.foodlens.sdk.NetworkService; 24 | 25 | import com.doinglab.foodlens.sdk.network.model.RecognitionResult; 26 | import com.doinglab.foodlens.sdk.UIService; 27 | import com.doinglab.foodlens.sdk.network.model.UserSelectedResult; 28 | import com.doinglab.foodlens.sdk.errors.BaseError; 29 | import com.doinglab.foodlens.sdk.network.model.Box; 30 | import com.doinglab.foodlens.sdk.network.model.Food; 31 | import com.doinglab.foodlens.sdk.network.model.FoodPosition; 32 | import com.doinglab.foodlens.sdk.network.model.Nutrition; 33 | import com.doinglab.foodlens.sdk.network.model.NutritionResult; 34 | import com.doinglab.foodlens.sdk.NutritionResultHandler; 35 | import com.doinglab.foodlens.sdk.RecognizeResultHandler; 36 | 37 | import com.doinglab.foodlens.sdk.UIServiceResultHandler; 38 | import com.doinglab.foodlens.sdk.UIServiceMode; 39 | import com.doinglab.sdk.example.foodlenstestapplication.listview.ListViewAdapter; 40 | 41 | import java.io.File; 42 | import java.io.FileInputStream; 43 | import java.util.List; 44 | 45 | import static com.doinglab.foodlens.sdk.NutritionRetrieveMode.TOP1_NUTRITION_ONLY; 46 | 47 | public class MainActivity extends AppCompatActivity { 48 | 49 | UIService uiService; 50 | NetworkService ns; 51 | 52 | TextView tv_title; 53 | Button btn_run_foodlens, btn_camera; 54 | ListView listview ; 55 | ListViewAdapter adapter; 56 | final int REQ_PICTURE = 0x02; 57 | 58 | RecognitionResult recognitionResult = null; 59 | 60 | @Override 61 | protected void onCreate(Bundle savedInstanceState) { 62 | super.onCreate(savedInstanceState); 63 | setContentView(R.layout.activity_main); 64 | 65 | 66 | uiService = FoodLens.createUIService(this); 67 | uiService.setUiServiceMode(UIServiceMode.USER_SELECTED_WITH_CANDIDATES); 68 | 69 | 70 | try { 71 | FoodLensBundle bundle = new FoodLensBundle(); 72 | bundle.setEnableManualInput(true); 73 | bundle.setSaveToGallery(true); 74 | uiService.setDataBundle(bundle); 75 | } catch (Exception e) { 76 | 77 | } 78 | 79 | 80 | tv_title = (TextView)findViewById(R.id.tv_title); 81 | adapter = new ListViewAdapter() ; 82 | listview = (ListView) findViewById(R.id.listview); 83 | 84 | btn_camera = (Button)findViewById(R.id.btn_run_camera); 85 | btn_camera.setOnClickListener(new View.OnClickListener() { 86 | @Override 87 | public void onClick(View v) { 88 | openPicture(); 89 | 90 | } 91 | }); 92 | 93 | btn_run_foodlens = (Button)findViewById(R.id.btn_run_foodlens); 94 | btn_run_foodlens.setOnClickListener(new View.OnClickListener() { 95 | @Override 96 | public void onClick(View v) { 97 | 98 | uiService.startFoodLensCamera(MainActivity.this, new UIServiceResultHandler() { 99 | @Override 100 | public void onSuccess(UserSelectedResult result) { 101 | recognitionResult = result; 102 | tv_title.setText("Result from UI Service"); 103 | setRecognitionResultData(recognitionResult); 104 | } 105 | 106 | @Override 107 | public void onCancel() { 108 | Log.d("FOODLENS_LOG", "Recognition Cancel"); 109 | Toast.makeText(getApplicationContext(), "Recognition Cancel", Toast.LENGTH_SHORT).show(); 110 | } 111 | 112 | @Override 113 | public void onError(BaseError error) { 114 | 115 | Log.e("FOODLENS_LOG", error.getMessage()); 116 | Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_SHORT).show(); 117 | 118 | } 119 | }); 120 | } 121 | }); 122 | 123 | findViewById(R.id.btn_run_editmode).setOnClickListener(new View.OnClickListener() { 124 | @Override 125 | public void onClick(View v) { 126 | uiService.startFoodLensDataEdit(MainActivity.this, recognitionResult, new UIServiceResultHandler() { 127 | @Override 128 | public void onSuccess(UserSelectedResult result) { 129 | recognitionResult = result; 130 | setRecognitionResultData(recognitionResult); 131 | } 132 | 133 | @Override 134 | public void onCancel() { 135 | Log.d("FOODLENS_LOG", "Recognition Cancel"); 136 | Toast.makeText(getApplicationContext(), "Recognition Cancel", Toast.LENGTH_SHORT).show(); 137 | } 138 | 139 | @Override 140 | public void onError(BaseError error) { 141 | Log.e("FOODLENS_LOG", error.getMessage()); 142 | Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_SHORT).show(); 143 | } 144 | }); 145 | } 146 | }); 147 | } 148 | 149 | private void openPicture() { 150 | 151 | Intent intent = new Intent(Intent.ACTION_PICK, 152 | android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 153 | 154 | startActivityForResult(intent, REQ_PICTURE); 155 | } 156 | 157 | private void setRecognitionResultData(UserSelectedResult recognitionResultData) 158 | { 159 | listview.setAdapter(null); 160 | adapter.clearItems(); 161 | 162 | List foodPositions = recognitionResultData.getFoodPositions(); 163 | 164 | String foodName = ""; 165 | String foodNutritionInfo = ""; 166 | String foodLocation = ""; 167 | for(int i=0; i foodPositions = recognitionResultData.getFoodPositions(); 211 | 212 | String foodName = ""; 213 | String foodNutritionInfo = ""; 214 | String foodLocation = ""; 215 | for(int i=0; i foodList = foodPosition.getFoods(); 224 | 225 | foodName = foodList.get(0).getFoodName(); 226 | Nutrition nutrition = foodList.get(0).getNutrition(); 227 | if(nutrition != null) 228 | { 229 | String carbon = "탄수화물: " + nutrition.getCarbonHydrate(); 230 | String protein = "단백질: " + nutrition.getProtein(); 231 | String fat = "지방: " + nutrition.getFat(); 232 | 233 | foodNutritionInfo += (carbon +" " + protein +" "+fat); 234 | } 235 | 236 | Bitmap bitmap = BitmapFactory.decodeFile(foodPosition.getFoodImagePath()); 237 | Drawable drawable = new BitmapDrawable(getResources(), bitmap); 238 | 239 | 240 | Box box = foodPosition.getImagePosition(); 241 | if(box!=null) 242 | { 243 | foodLocation = "음식 위치: "; 244 | foodLocation += String.valueOf(box.getXmin()) + " " + String.valueOf(box.getXmax()) + " " + String.valueOf(box.getYmin()) + " " + String.valueOf(box.getYmax()); 245 | } 246 | 247 | adapter.addItem(drawable, foodName, foodNutritionInfo, foodLocation); 248 | } 249 | 250 | listview.setAdapter(adapter); 251 | } 252 | 253 | 254 | @Override 255 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 256 | super.onActivityResult(requestCode, resultCode, data); 257 | 258 | if (requestCode == REQ_PICTURE && resultCode == RESULT_OK && null != data) { 259 | Uri selectedImage = data.getData(); 260 | String[] filePathColumn = { MediaStore.Images.Media.DATA }; 261 | 262 | Cursor cursor = getContentResolver().query(selectedImage, 263 | filePathColumn, null, null, null); 264 | cursor.moveToFirst(); 265 | 266 | int columnIndex = cursor.getColumnIndex(filePathColumn[0]); 267 | String picturePath = cursor.getString(columnIndex); 268 | cursor.close(); 269 | 270 | final byte[] byteData = readContentIntoByteArray(new File(picturePath)); 271 | 272 | ns = FoodLens.createNetworkService(getApplicationContext()); 273 | ns.setNutritionRetrieveMode(TOP1_NUTRITION_ONLY); 274 | ns.predictMultipleFood(byteData, new RecognizeResultHandler() { 275 | @Override 276 | public void onSuccess(RecognitionResult result) { 277 | 278 | tv_title.setText("Result from Network Service"); 279 | setRecognitionResultData(result); 280 | 281 | } 282 | 283 | @Override 284 | public void onError(BaseError errorReason) { 285 | Log.e("FOODLENS_LOG", errorReason.getMessage()); 286 | 287 | } 288 | }); 289 | 290 | ns.getNutritionInfo(20, new NutritionResultHandler() { 291 | @Override 292 | public void onSuccess(NutritionResult result) { 293 | 294 | } 295 | 296 | @Override 297 | public void onError(BaseError errorReason) { 298 | 299 | } 300 | }); 301 | } 302 | 303 | uiService.onActivityResult(requestCode, resultCode, data); 304 | } 305 | 306 | private byte[] readContentIntoByteArray(File file) { 307 | FileInputStream fileInputStream = null; 308 | byte[] bFile = new byte[(int) file.length()]; 309 | try { 310 | //convert file into array of bytes 311 | fileInputStream = new FileInputStream(file); 312 | fileInputStream.read(bFile); 313 | fileInputStream.close(); 314 | } catch (Exception e) { 315 | e.printStackTrace(); 316 | } 317 | return bFile; 318 | } 319 | 320 | 321 | } 322 | -------------------------------------------------------------------------------- /Android/SampleCode/app/src/main/java/com/doinglab/sdk/example/foodlenstestapplication/listview/ListViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.doinglab.sdk.example.foodlenstestapplication.listview; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.BaseAdapter; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.doinglab.sdk.example.foodlenstestapplication.R; 13 | 14 | import java.util.ArrayList; 15 | 16 | public class ListViewAdapter extends BaseAdapter { 17 | private ArrayList listViewItemList = new ArrayList() ; 18 | 19 | public ListViewAdapter() { 20 | 21 | } 22 | 23 | public void clearItems() 24 | { 25 | listViewItemList.clear(); 26 | } 27 | 28 | @Override 29 | public int getCount() { 30 | return listViewItemList.size() ; 31 | } 32 | 33 | @Override 34 | public View getView(int position, View convertView, ViewGroup parent) { 35 | final int pos = position; 36 | final Context context = parent.getContext(); 37 | 38 | if (convertView == null) { 39 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 40 | convertView = inflater.inflate(R.layout.list_item, parent, false); 41 | } 42 | 43 | ImageView iconImageView = (ImageView) convertView.findViewById(R.id.img_food) ; 44 | TextView tv_foodname = (TextView) convertView.findViewById(R.id.tv_foodname) ; 45 | TextView tv_food_nutrition_info = (TextView) convertView.findViewById(R.id.tv_food_nutrition_info) ; 46 | TextView tv_food_position = (TextView) convertView.findViewById(R.id.tv_food_position) ; 47 | 48 | 49 | ListViewItem listViewItem = listViewItemList.get(position); 50 | 51 | iconImageView.setImageDrawable(listViewItem.getIcon()); 52 | tv_foodname.setText(listViewItem.getTitle()); 53 | tv_food_nutrition_info.setText(listViewItem.getFoodNutrition()); 54 | tv_food_position.setText(listViewItem.getFoodPosition()); 55 | 56 | return convertView; 57 | } 58 | 59 | @Override 60 | public long getItemId(int position) { 61 | return position ; 62 | } 63 | 64 | @Override 65 | public Object getItem(int position) { 66 | return listViewItemList.get(position) ; 67 | } 68 | 69 | public void addItem(Drawable icon, String title, String nutritonInfo, String boxLoc) { 70 | ListViewItem item = new ListViewItem(); 71 | 72 | if(icon != null) 73 | item.setIcon(icon); 74 | 75 | 76 | if(title != "") 77 | item.setTitle(title); 78 | else 79 | item.setTitle(""); 80 | 81 | if(nutritonInfo != "") 82 | item.setFoodNutrition(nutritonInfo); 83 | else 84 | item.setFoodNutrition(""); 85 | 86 | if(title != "") 87 | item.setFoodPosition(boxLoc); 88 | else 89 | item.setFoodPosition(""); 90 | 91 | listViewItemList.add(item); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Android/SampleCode/app/src/main/java/com/doinglab/sdk/example/foodlenstestapplication/listview/ListViewItem.java: -------------------------------------------------------------------------------- 1 | package com.doinglab.sdk.example.foodlenstestapplication.listview; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | public class ListViewItem { 6 | private Drawable iconDrawable ; 7 | private String titleStr; 8 | private String foodPosition; 9 | private String foodNutrition; 10 | 11 | public void setIcon(Drawable icon) { 12 | iconDrawable = icon ; 13 | } 14 | public void setTitle(String title) { 15 | titleStr = title ; 16 | } 17 | 18 | 19 | public Drawable getIcon() { 20 | return this.iconDrawable ; 21 | } 22 | public String getTitle() { 23 | return this.titleStr ; 24 | } 25 | 26 | public void setFoodNutrition(String foodNutrition) { 27 | this.foodNutrition = foodNutrition; 28 | } 29 | 30 | public void setFoodPosition(String foodPosition) { 31 | this.foodPosition = foodPosition; 32 | } 33 | 34 | public String getFoodNutrition() { 35 | return foodNutrition; 36 | } 37 | 38 | public String getFoodPosition() { 39 | return foodPosition; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Android/SampleCode/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /Android/SampleCode/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /Android/SampleCode/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 13 | 14 | 21 | 22 | 33 | 44 | 55 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 106 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | NSCameraUsageDescription 45 | $(PRODUCT_NAME) camera use 46 | NSPhotoLibraryAddUsageDescription 47 | $(PRODUCT_NAME) Photo use 48 | NSPhotoLibraryUsageDescription 49 | $(PRODUCT_NAME) Photo use 50 | NSAppTransportSecurity 51 | 52 | NSExceptionDomains 53 | 54 | api.foodlens.ai 55 | 56 | NSIncludesSubdomains 57 | 58 | NSTemporaryExceptionAllowsInsecureHTTPLoads 59 | 60 | NSTemporaryExceptionMinimumTLSVersion 61 | TLSv1.1 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestApp/TableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewCell.swift 3 | // FoodLensTestApp 4 | // 5 | // Created by HwangChun Lee on 02/04/2019. 6 | // Copyright © 2019 DoingLab Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TableViewCell: UITableViewCell { 12 | @IBOutlet weak var foodImage: UIImageView! 13 | @IBOutlet weak var foodName: UILabel! 14 | @IBOutlet weak var foodUnit: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | override func setSelected(_ selected: Bool, animated: Bool) { 22 | super.setSelected(selected, animated: animated) 23 | 24 | // Configure the view for the selected state 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestApp/Util.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Util.swift 3 | // FoodLensTestApp 4 | // 5 | // Created by HwangChun Lee on 02/04/2019. 6 | // Copyright © 2019 DoingLab Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class Util { 13 | static func save(image: UIImage, path:String?, fileName:String) -> String? { 14 | let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! 15 | 16 | var targetUrl:URL! = documentsUrl 17 | if path != nil { 18 | targetUrl = documentsUrl.appendingPathComponent(path!) 19 | } 20 | 21 | let fileURL = targetUrl.appendingPathComponent(fileName) 22 | 23 | 24 | if let imageData = image.jpegData(compressionQuality: 1.0) { 25 | do { 26 | try FileManager.default.createDirectory(atPath: targetUrl.path, withIntermediateDirectories: true, attributes: nil) 27 | try imageData.write(to: fileURL, options: .atomic) 28 | return fileURL.absoluteString // ----> Save fileName 29 | } 30 | catch { 31 | print("Error saving image : \(error)") 32 | } 33 | } 34 | //print("Error saving image") 35 | return nil 36 | } 37 | 38 | static func load(path:String?, fileName:String) -> UIImage? { 39 | let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! 40 | var targetUrl:URL! = documentsUrl 41 | if path != nil { 42 | targetUrl = documentsUrl.appendingPathComponent(path!) 43 | } 44 | 45 | let fileURL = targetUrl.appendingPathComponent(fileName) 46 | do { 47 | let imageData = try Data(contentsOf: fileURL) 48 | return UIImage(data: imageData) 49 | } catch { 50 | print("Error loading image : \(error)") 51 | } 52 | return nil 53 | } 54 | 55 | static func deleteDirectory(path : String?) { 56 | let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! 57 | var dataPath:URL 58 | if path != nil { 59 | dataPath = documentsUrl.appendingPathComponent(path!) 60 | } else { 61 | dataPath = documentsUrl 62 | } 63 | 64 | do { 65 | try FileManager.default.removeItem(atPath: dataPath.path) 66 | } catch { 67 | print("Could not clear temp folder: \(error)") 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // FoodLensTestApp 4 | // 5 | // Created by HwangChun Lee on 29/03/2019. 6 | // Copyright © 2019 DoingLab Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import FoodLens 11 | 12 | class ViewController: UIViewController, UserServiceResultHandler, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITableViewDelegate, UITableViewDataSource { 13 | @IBOutlet weak var statusLabel: UILabel! 14 | var result : RecognitionResult? = nil 15 | @IBOutlet weak var tableView: UITableView! 16 | 17 | func onSuccess(_ result: RecognitionResult) { 18 | self.result = result 19 | tableView.reloadData() 20 | NSLog("Success") 21 | } 22 | 23 | func onCancel() { 24 | NSLog("Canceled") 25 | } 26 | 27 | func onError(_ error: BaseError) { 28 | 29 | } 30 | 31 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 32 | return 80.0 33 | } 34 | 35 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 36 | if let result = self.result { 37 | return result.getFoodPositions().count+1 ?? 0 38 | } 39 | else { 40 | return 0 41 | } 42 | } 43 | 44 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 45 | let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell 46 | if indexPath.row == 0 { 47 | if let imagePath = result?.getRecognizedImage()?.split(separator: "/").last { 48 | cell.foodImage.image = Util.load(path: "foodlensStore", fileName: String(imagePath)) 49 | } 50 | cell.foodName.text = "full image" 51 | cell.foodUnit.text = "" 52 | } 53 | else { 54 | if let data = result?.getFoodPositionAtIndex(index: indexPath.row-1) { 55 | if let imageFilename = data.foodImagepath?.split(separator: "/").last { 56 | cell.foodImage.image = Util.load(path: "foodlensStore", fileName: String(imageFilename)) 57 | } 58 | 59 | if let foodname = data.userSelectedFood?.foodName { 60 | cell.foodName.text = data.userSelectedFood?.foodName ?? "" 61 | } 62 | else { 63 | cell.foodName.text = data.foodCandidates[0].foodName 64 | } 65 | 66 | if let calorie = data.userSelectedFood?.nutrition?.calories { 67 | cell.foodUnit.text = "\(calorie) Kcal" 68 | } 69 | else if let calorie = data.foodCandidates[0].nutrition?.calories { 70 | cell.foodUnit.text = "\(calorie) Kcal" 71 | } 72 | 73 | } 74 | } 75 | return cell 76 | } 77 | 78 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 79 | startEditUIService() 80 | } 81 | 82 | var uiService : UIService? = nil 83 | var networkService : NetworkService? = nil 84 | 85 | override func viewDidLoad() { 86 | super.viewDidLoad() 87 | // Do any additional setup after loading the view. 88 | FoodLens.uiServiceMode = .userSelectedWithCandidates 89 | uiService = FoodLens.createUIService(accessToken: "") 90 | networkService = FoodLens.createNetworkService(nutritionRetrieveMode: .allNutirition, accessToken: "") 91 | 92 | print(Locale.current) 93 | 94 | func getPreferredLocale() -> Locale { 95 | guard let preferredIdentifier = Locale.preferredLanguages.first else { 96 | return Locale.current 97 | } 98 | return Locale(identifier: preferredIdentifier) 99 | } 100 | print(getPreferredLocale().languageCode) 101 | } 102 | 103 | @IBAction func startCameraUIService(_ sender: Any) { 104 | statusLabel.text = "" 105 | Util.deleteDirectory(path : "foodlensStore") 106 | uiService?.startCameraUIService(parent: self, completionHandler: self) 107 | } 108 | 109 | @IBAction func startSearchUIService(_ sender: Any) { 110 | uiService?.startSearchUIService(parent: self, completionHandler: self) 111 | } 112 | 113 | @IBAction func startGalleryUIService(_ sender: Any) { 114 | uiService?.startGalleryUIService(parent: self, completionHandler: self) 115 | } 116 | 117 | func saveImage() -> String? { 118 | let path:String = "foodlensStore"; 119 | let image = UIImage(named: "aa2c2043c2984529b77851d25c7bbf6c.jpg") 120 | let filename = "aa2c2043c2984529b77851d25c7bbf6c.jpg" 121 | 122 | let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! 123 | 124 | var targetUrl:URL! = documentsUrl 125 | 126 | targetUrl = documentsUrl.appendingPathComponent(path) 127 | 128 | let fileURL = targetUrl.appendingPathComponent(filename) 129 | 130 | if let imageData = image?.jpegData(compressionQuality: 1.0) { 131 | do { 132 | try FileManager.default.createDirectory(atPath: targetUrl.path, withIntermediateDirectories: true, attributes: nil) 133 | try imageData.write(to: fileURL, options: .atomic) 134 | return fileURL.absoluteString // ----> Save fileName 135 | } 136 | catch { 137 | print("Error saving image : \(error)") 138 | } 139 | } 140 | 141 | return nil 142 | } 143 | 144 | @IBAction func startEditServiceWithDummy(_ sender: Any) { 145 | let mealData = PredictionResult() 146 | 147 | mealData.setRecognizedImagePath(saveImage()!) 148 | 149 | var foodPosition = FoodPosition() 150 | var food = Food() 151 | food.foodName = "아욱된장국" 152 | var nutrition = Nutrition() 153 | nutrition.calories = 5000 154 | food.nutrition = nutrition 155 | foodPosition.imagePosition = Box() 156 | foodPosition.imagePosition?.xmin = 568 157 | foodPosition.imagePosition?.xmax = 1017 158 | foodPosition.imagePosition?.ymin = 442 159 | foodPosition.imagePosition?.ymax = 898 160 | foodPosition.foodCandidates.append(food) 161 | foodPosition.userSelectedFood = foodPosition.foodCandidates[0] 162 | mealData.putFoodPosition(foodPosition) 163 | 164 | foodPosition = FoodPosition() 165 | food = Food() 166 | food.foodName = "깍두기" 167 | nutrition = Nutrition() 168 | nutrition.calories = 5000 169 | food.nutrition = nutrition 170 | foodPosition.imagePosition = Box() 171 | foodPosition.imagePosition?.xmin = 758 172 | foodPosition.imagePosition?.xmax = 1018 173 | foodPosition.imagePosition?.ymin = 74 174 | foodPosition.imagePosition?.ymax = 345 175 | foodPosition.foodCandidates.append(food) 176 | foodPosition.userSelectedFood = foodPosition.foodCandidates[0] 177 | mealData.putFoodPosition(foodPosition) 178 | 179 | foodPosition = FoodPosition() 180 | food = Food() 181 | food.foodName = "고등어조림" 182 | nutrition = Nutrition() 183 | nutrition.calories = 5000 184 | food.nutrition = nutrition 185 | foodPosition.imagePosition = Box() 186 | foodPosition.imagePosition?.xmin = 400 187 | foodPosition.imagePosition?.xmax = 678 188 | foodPosition.imagePosition?.ymin = 177 189 | foodPosition.imagePosition?.ymax = 411 190 | foodPosition.foodCandidates.append(food) 191 | foodPosition.userSelectedFood = foodPosition.foodCandidates[0] 192 | mealData.putFoodPosition(foodPosition) 193 | 194 | let navTheme = NavigationBarTheme(foregroundColor : UIColor.black, backgroundColor : UIColor.blue) 195 | let toolbarTheme = ToolBarButtonTheme(backgroundColor: UIColor.brown, buttonTheme: ButtonTheme(backgroundColor: UIColor.cyan, textColor: UIColor.darkGray, borderColor: UIColor.gray)) 196 | let buttonTheme = ButtonTheme(backgroundColor: UIColor.green, textColor: UIColor.lightGray, borderColor: UIColor.magenta) 197 | let widgetButtonTheme = ButtonTheme(backgroundColor: UIColor.orange, textColor: UIColor.purple, borderColor: UIColor.red) 198 | 199 | let uiService = FoodLens.createUIService(accessToken: "", navigationBarTheme: navTheme, toolbarTheme: toolbarTheme, buttonTheme: buttonTheme, widgetButtonTheme : widgetButtonTheme) 200 | uiService.startEditUIService(mealData, parent: self, completionHandler: self) 201 | } 202 | 203 | func startEditUIService() { 204 | guard let result = result else { 205 | return 206 | } 207 | 208 | let mealData = PredictionResult() 209 | mealData.setRecognizedImagePath(result.getRecognizedImage()!) 210 | for elem in result.getFoodPositions() { 211 | mealData.putFoodPosition(elem) 212 | } 213 | uiService?.startEditUIService(mealData, parent: self, completionHandler: self) 214 | } 215 | 216 | @IBAction func startNetworkService(_ sender: Any) { 217 | statusLabel.text = "" 218 | Util.deleteDirectory(path : "foodlensStore") 219 | let imagePicker = UIImagePickerController() 220 | imagePicker.delegate = self 221 | 222 | imagePicker.allowsEditing = false 223 | imagePicker.sourceType = .photoLibrary 224 | 225 | present(imagePicker, animated: true, completion: nil) 226 | } 227 | 228 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 229 | dismiss(animated: true, completion: nil) 230 | if let pickedImage = info[.originalImage] as? UIImage, 231 | let networkService = self.networkService { 232 | statusLabel.text = "wait for response..." 233 | networkService.predictMultipleFood(image: pickedImage) { (result : PredictionResult?, status : ProcessStatus) in 234 | self.statusLabel.text = "completed" 235 | self.result = result 236 | self.tableView.reloadData() 237 | } 238 | } 239 | } 240 | } 241 | 242 | -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestApp/aa2c2043c2984529b77851d25c7bbf6c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doinglab/FoodLensSDK/267d2a3b6de3c4f4d83c5d73afe4992a55e4da3e/IOS/SampleCode/FoodLensTestApp/aa2c2043c2984529b77851d25c7bbf6c.jpg -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestApp/full_Image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doinglab/FoodLensSDK/267d2a3b6de3c4f4d83c5d73afe4992a55e4da3e/IOS/SampleCode/FoodLensTestApp/full_Image.jpeg -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestApp/parted_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doinglab/FoodLensSDK/267d2a3b6de3c4f4d83c5d73afe4992a55e4da3e/IOS/SampleCode/FoodLensTestApp/parted_image.jpg -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestAppTests/FoodLensTestAppTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FoodLensTestAppTests.swift 3 | // FoodLensTestAppTests 4 | // 5 | // Created by HwangChun Lee on 29/03/2019. 6 | // Copyright © 2019 DoingLab Inc. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import FoodLensTestApp 11 | 12 | class FoodLensTestAppTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestAppUITests/FoodLensTestAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FoodLensTestAppUITests.swift 3 | // FoodLensTestAppUITests 4 | // 5 | // Created by HwangChun Lee on 29/03/2019. 6 | // Copyright © 2019 DoingLab Inc. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FoodLensTestAppUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 20 | XCUIApplication().launch() 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | func testExample() { 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /IOS/SampleCode/FoodLensTestAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /IOS/SampleCode/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '13.0' 3 | 4 | target 'FoodLensTestApp' do 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | use_frameworks! 7 | pod 'FoodLens', '~> 2.6.0' 8 | # When you want to use FoodLens SDK V1, Use below 9 | 10 | # Pods for FoodLensTestApp 11 | 12 | target 'FoodLensTestAppTests' do 13 | inherit! :search_paths 14 | # Pods for testing 15 | end 16 | 17 | target 'FoodLensTestAppUITests' do 18 | inherit! :search_paths 19 | # Pods for testing 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /IOS/SampleCode/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.6.4) 3 | - FoodLens (2.5.0): 4 | - Alamofire (~> 5.5) 5 | 6 | DEPENDENCIES: 7 | - FoodLens (~> 2.5.0) 8 | 9 | SPEC REPOS: 10 | trunk: 11 | - Alamofire 12 | - FoodLens 13 | 14 | SPEC CHECKSUMS: 15 | Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c 16 | FoodLens: 6d7f72dad23806e39e7b3c1c77e200e794b2ab3f 17 | 18 | PODFILE CHECKSUM: c44f4588e246de917de86e4336cbb949e9da1bd9 19 | 20 | COCOAPODS: 1.11.3 21 | -------------------------------------------------------------------------------- /JSON Format: -------------------------------------------------------------------------------- 1 | { 2 | "eatDate": Date(yyyy-MM-dd HH:mm:ss), 3 | //eatType is NOT USED, support Android only 4 | "eatType": Int, //0 : Breakfast, 1 : Lunch, 2 : Dinner, 4 : Morning snack, 5 : Afternoon snack, 6 : Late night snack 5 | "mealType": String, // Return value breakfast, lunch, dinner, snack, morning_snack, afternoon_snack, late_night_snack 6 | "foodImagepath": String, //Local path 7 | "foodPositionList": [ 8 | { 9 | //SDK내의 섭취량 변경시 변경되는 항목(ex: eatAmount * nutrition.carbonhydrate)이 실제 1회 섭취한 량입니다. 10 | "eatAmount": Float, // 1회 제공량 대비 섭취한 음식의 양 11 | "foodCandidates": [ // 입력된 사진으로 추천된 음식정보 12 | { 13 | "foodId": Int, //DoingLab DB 기반 음식ID 14 | "foodName": String, //음식 이름 15 | "keyName": String, // DoingLab DB 기반 음식 코드 16 | "nutrition": { //영양소에 대한 Raw 데이터, 섭취량을 변경하여도 영양정보값은 변경되지 않습니다. 17 | "calcium": Float, //칼슘(단위 : mg) 18 | "calories": Float, // 1회 제공량에 따른 칼로리(단위 : kcal) 19 | "carbonhydrate": Float, //탄수화물(단위 : g) 20 | "cholesterol": Float, //콜레스테롤(단위 : mg) 21 | "dietrayfiber": Float, //식이섬유(단위 : g) 22 | "fat": Float, //지방(단위 : g) 23 | "protein": Float, //단백질(단위 : g) 24 | "saturatedfat": Float, //포화지방(단위 : g) 25 | "sodium": Float, //나트륨(단위 : mg) 26 | "sugar": Float, //설탕(단위 : g) 27 | "totalgram": Float, // 1회 제공량에 따른 섭취량 (단위 : g) 28 | "transfat":Float, //트랜스지방(단위 : g) 29 | "unit": String, // 제공단위(ex : 봉지, 그릇 등..) 30 | "foodtype": String, //음식타입(ex : Alcohol ..) 31 | "vitamina": Float, //비타민 A(단위 : ㎍ RAE) 32 | "vitaminb": Float, //비타민 B1(단위 : mg) 33 | "vitaminc": Float, //비타민 C(단위 : mg) 34 | "vitamind": Float, //비타민 D(단위 : ug) 35 | "vitamine": Float //비타민 E(단위 : mg) 36 | } 37 | }], 38 | "userSelectedFood": { // 사용자가 최종적으로 선택한 음식정보 39 | "foodId": Int, //DoingLab DB 기반 음식ID 40 | "foodName": String, //음식 이름 41 | "nutrition": Nutrition 42 | } 43 | "imagePosition": { //음식이 위치하는 좌표 44 | "xmax": Int, 45 | "xmin": Int, 46 | "ymax": Int, 47 | "ymin": Int 48 | }, 49 | } 50 | ], 51 | "predictedImagePath": String 52 | } 53 | -------------------------------------------------------------------------------- /JSON Sample: -------------------------------------------------------------------------------- 1 | { 2 | "eatDate": "2019-08-14 16:11:27", 3 | "eatType": 3, 4 | "foodPositionList": [ 5 | { 6 | "eatAmount": 1, 7 | "foodCandidates": [ 8 | { 9 | "foodId": 38, 10 | "foodName": "쌀밥", 11 | "keyName": "rice_streamed", 12 | "nutrition": { 13 | "calcium": 6.3, 14 | "calories": 326.7, 15 | "carbonhydrate": 71.55, 16 | "cholesterol": 0, 17 | "dietrayfiber": 0.9, 18 | "fat": 0.36, 19 | "protein": 5.76, 20 | "rawCalories": 326.7, 21 | "rawTotalGram": 210, 22 | "saturatedfat": 0.126, 23 | "sodium": 7.2, 24 | "sugar": 0, 25 | "totalgram": 210, 26 | "transfat": 0, 27 | "unit": "1공기", 28 | "foodtype": "", 29 | "vitamina": 1, 30 | "vitaminb": 0, 31 | "vitaminc": 0, 32 | "vitamind": 0, 33 | "vitamine": 0 34 | } 35 | }, 36 | { 37 | "foodId": 45, 38 | "foodName": "찰밥", 39 | "keyName": "rice_streamed" 40 | }, 41 | { 42 | "foodId": 22, 43 | "foodName": "흰죽", 44 | "keyName": "rice_porridge" 45 | }, 46 | { 47 | "foodId": 32, 48 | "foodName": "현미밥(현미50%)", 49 | "keyName": "rice_steamed_brown" 50 | }, 51 | { 52 | "foodId": 49, 53 | "foodName": "현미밥(현미30%)", 54 | "keyName": "rice_steamed_brown" 55 | }, 56 | { 57 | "foodId": 44, 58 | "foodName": "차조밥", 59 | "keyName": "chajo_bab" 60 | }, 61 | { 62 | "foodId": 12155, 63 | "foodName": "음식아님", 64 | "keyName": "nonefood" 65 | } 66 | ], 67 | "foodImagepath": "/data/user/0/com.doinglab.foodlens.sample/files/temp/0.jpg", 68 | "imagePosition": { 69 | "xmax": 1529, 70 | "xmin": 12, 71 | "ymax": 1223, 72 | "ymin": 240 73 | }, 74 | "userSelectedFood": { 75 | "foodId": 38, 76 | "foodName": "쌀밥", 77 | "nutrition": { 78 | "calcium": 6.3, 79 | "calories": 326.7, 80 | "carbonhydrate": 71.55, 81 | "cholesterol": 0, 82 | "dietrayfiber": 0.9, 83 | "fat": 0.36, 84 | "protein": 5.76, 85 | "rawCalories": 326.7, 86 | "rawTotalGram": 210, 87 | "saturatedfat": 0.126, 88 | "sodium": 7.2, 89 | "sugar": 0, 90 | "totalgram": 210, 91 | "transfat": 0, 92 | "unit": "1공기", 93 | "foodtype": "", 94 | "vitamina": 1, 95 | "vitaminb": 0, 96 | "vitaminc": 0, 97 | "vitamind": 0, 98 | "vitamine": 0 99 | } 100 | } 101 | } 102 | ], 103 | "mealType": "snack", 104 | "predictedImagePath": "/data/user/0/com.doinglab.foodlens.sample/files/temp/orgimg.jpg" 105 | } 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 doinglab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FoodLens SDK 2.x버전은 기술 지원 중단 예정입니다. 2 | # 신규 SDK를 사용하시기 바랍니다. [신규SDK가기](https://github.com/doinglab/FoodLensSDK-V3) 3 | # FoodLens를 사용하기 위한 SDK입니다. 4 | 5 | FoodLens기술은 사진 촬영만으로 영상속의 음식을 인식하고 영양분석까지 수행해 주는 솔루션 입니다. 6 | 7 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/2097YwX2M8M/0.jpg)](https://www.youtube.com/watch?v=2097YwX2M8M) 8 | 9 | - Android용 SDK 10 | [Android SDK 사용설명서](Android/) 11 | 12 | 13 | - IOS용 SDK 14 | [IOS SDK 사용설명서](IOS/) 15 | -------------------------------------------------------------------------------- /README_En.md: -------------------------------------------------------------------------------- 1 | # SDK for using FoodLens. 2 | 3 | FoodLens technology which can provide food name and nutritions from food image. 4 | 5 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/2097YwX2M8M/0.jpg)](https://www.youtube.com/watch?v=2097YwX2M8M) 6 | 7 | - SDK for Android 8 | [SDK Manual for Android ](Android/) 9 | 10 | 11 | - SDK for iOS 12 | [SDK Manual for iOS](IOS/) 13 | --------------------------------------------------------------------------------