Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 245 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

Nutrition-AI SDK

Loading...

Guides

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Visual, Barcode and Packaged Food detection

Note: Some of these detections might not be accessible to you if your license does not include that module

Visual food detection

A DetectedCandidate represents the result from running Passio's neural network, specialized for detecting foods like apples, salads, burgers etc.

class DetectedCandidate(
    val passioID: PassioID,
    val confidence: Float,
    val boundingBox: RectF,
    val croppedImage: Bitmap?
)

The PassioID is a unique identifier for food that is used to query our Nutritional Database. See section Nutritional Database for more details.

Barcode detection

Try to opt in for BARCODE by enabling the flag detectBarcodes and point your device at the image of the barcodes below. The SDK's barcode detector will recognize the barcode and it will return the barcode number inside the BarcodeCandidate object.

data class BarcodeCandidate(val barcode: Barcode, val boundingBox: RectF)

Use the barcode field to query Passio's backend and if the scanned food can be found in Passio's database, a PassioIDAttributes object will be returned.

PassioSDK.instance.fetchPassioIDAttributesForBarcode(barcode) { passioIDAttribute ->
    if (passioIDAttribute == null) {
        return@fetchPassioIDAttributesForBarcode
    }
    Toast.makeText(requestContext(), passioIDAttribute.name, Toast.LENGTH_SHORT).show()
}

Packaged food detection

Passio uses OCR to recognize names of branded foods. Opt in with FoodDetectionConfiguration.detectPackagedFood and if the SDK has recognized a branded food that is present in Passio's Nutritional Database it will return the PackagedFoodCandidate of that food item. To retrieve full information about the item call:

PassioSDK.instance.fetchPassioIDAttributesForPackagedFood(
    packagedFoodCandidate.packagedFoodCode
) { passioIDAttributes ->    
    if (passioIDAttributes == null) {        
        return@fetchPassioIDAttributesForPackagedFood    
    }    
    Toast.makeText(requestContext(), passioIDAttribute.name, Toast.LENGTH_SHORT).show()
}

The FoodCandidate returns a list of VisualCandidates, which object should I choose to display to the user?

The Visual Detection process can detect up to 5 objects per frame. Depending on your UI/UX you can display all the results to the end user, or just choose one of them. The criteria to choose the best candidate from a list is up to you, but the two most common strategies are the biggest bounding box or the highest confidence. This biggest bounding box represents the biggest object on the frame and the highest confidence points to the object that the neural network is most sure about.

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Nutrition-AI SDK Overview

Nutrition-AI SDK Overview

Introduction

Passio Nutrition-AI SDK is an industry leading food recognition and nutrition tracking SDK designed to enable app developers to rapidly add AI-powered food logging capabilities and AI-powered nutrition coaching to their mobile applications.

The SDK is a part fo product and relies heavily to a wide range of APIs and functionality provided in the .

Video Overview

Our Nutrition-AI SDK offers powerful capabilities that are rapidly evolving, so some features may not appear in the overview video below.

Latest SDK vs. Legacy

A key shift from our legacy SDK is the move from real-time on-device food logging to photo logging analyzed via cloud-side LLMs. While real-time scanning is still available as of Q4 2024, it’s no longer the primary mode. Passio now recommends photo logging, voice logging, and text search as the primary modes.

Getting Started

Platform Specific SDKs

Follow our documentation guides to get started and build your AI-powered applications. Please select your platform of choice to get to the documentation.

In Case you're looking for APIs, Hub or Advisor

Nutrition AI SDK

Welcome to Passio Nutrition-AI iOS SDK! When integrated into your app the SDK provides you with food recognition and nutrition assistant technology. The SDK creates a video preview layer and outputs foods recognized by our computer vision technology in the video feed of your live camera along with nutrition data related to the recognized foods.

As the developer, you have complete control of when to turn on/off the SDK and to configure the outputs which includes:

  • food names (e.g. banana, hamburger, fruit salad, quest chocolate bar)

  • lists of alternatives for recognized foods (e.g., soy milk would be an alternative of a visually recognized class milk)

  • barcodes detected on food packages

  • packaged foods recognized by the text detected on food packages

  • nutrition information detected on food packages via Passio's Nutrition Facts reader which returns information written in Nutrition Facts labels

  • nutrition information associated with the foods

  • food weight and volume for certain foods

By default the SDK does not record/store any photos or videos. Instead, as the end user hovers over a food item with his/her camera phone, the SDK recognizes and identifies food items in real time. This hovering action is only transitory/temporary while the end user is pointing the camera at a particular item and is not recorded or stored within the SDK. As a developer, you can configure the SDK to capture images or videos and store them in your app.

Versions

This guide will provide documentation for the latest version of the SDK. If the documentation is needed for an older version, be sure to check the VERSIONS section.

Available versions

3.2.4

3.2.2

3.2.0

3.1.4

Open Food Facts and Terms of Use

Passio Nutrition-AI SDK added data from Open Food Facts (https://en.openfoodfacts.org/). Each food that contains data from Open Food Facts will have the value set in its food origin list. In case you choose to display the these food items you agree to abide by the terms of the Open Food Facts license agreement (https://opendatacommons.org/licenses/odbl/1-0) and their terms of use (https://world.openfoodfacts.org/terms-of-use) and you will have to add to the UI the following license copy:

"This record contains information from Open Food Facts (https://en.openfoodfacts.org), which is made available here under the Open Database License (https://opendatacommons.org/licenses/odbl/1-0)"

Configure the SDK

The configuration process of the SDK has severable responsibilities when configured through SDK key:

  • Validation of the license key

  • Validating the currently present files

  • Downloading or updating the files required by the SDK to do recognition

  • Preparing the files to be used for inference

The SDK is configured using a PassioConfiguration object. This object is used to define the location of the files that the SDK requires.

When the SDK is configured without SDK key (using Proxy URL), the SDK will be remoteOnly. More information on this flag is described in below section.

SDK downloads the models

The default behaviour (or by setting the sdkDownloadsModels field to true) of the SDK is to download the required files for local recognition. If the current files that are present on the device are older than the version of the SDK library, the SDK will download the newer version in the background, but still run the session with the older version of the files. Once the download is finished, the new files will be applied on the next session. Don't forget to add the import statement for the nutrition sdk.

The SDK will not download required files for local recognition when configured without SDK key. The SDK will be in remoteOnly mode.

import PassioNutritionAISDK
import AVFoundation

...

let passioSDK = PassioNutritionAI.shared

override func viewDidLoad() {
    super.viewDidLoad()
    configureSDK()
}

// Configuring SDK using SDK key

func configureSDK() {
    let key = "YOUR_PASSIO_KEY"
    let passioConfig = PassioConfiguration(key: key)
    
    passioSDK.configure(passioConfiguration: passioConfig) { (status) in
        print("Mode = \(status.mode)\n missingfiles = \(String(describing: status.missingFiles))" )
    }
}

// Configuring SDK using Proxy

func configureSDK() {
    var passioConfig = PassioConfiguration()
    // Set the base URL of the target proxy endpoint
    passioConfig.proxyUrl = "https://yourdomain.com/nutrition/"
    // Set headers as per your requirements
    passioConfig.proxyHeaders = ["header" : "value"] 
    
    passioSDK.configure(passioConfiguration: passioConfig) { (status) in
        print("Mode = \(status.mode)\n missingfiles = \(String(describing: status.missingFiles))" )
    }
}
val passioConfiguration = PassioConfiguration(
    this.applicationContext,
    "Your license key here"
).apply {
    sdkDownloadsModels = true // This can be ommited
}

PassioSDK.instance.configure(passioConfiguration) { passioStatus ->
    // Handle PassioStatus states
} 
const [isReady, setIsReady] = useState(false);

// Effect to configure the SDK and request camera permission
// We are adding our developer key in LoadingContainerView
useEffect(() => {
  Promise.all([
    PassioSDK.configure({
      key: 'your-developer-key',
      autoUpdate: true,
    }),
    PassioSDK.requestCameraAuthorization(),
  ]).then(([sdkStatus, cameraAuthorized]) => {
    setIsReady(sdkStatus.mode === 'isReadyForDetection' && cameraAuthorized);
  });
}, []);
import 'package:nutrition_ai/nutrition_ai_sdk.dart';

void configureSDK() async {
   String passioKey = "Your_PassioSDK_Key";
   var configuration = PassioConfiguration(passioKey);
   passioStatus = await NutritionAI.instance.configureSDK(configuration);
   // Handle result of the configuration process.
}

Remote Only configuration

The models required for the SDK to run local recognition can take some time for the first download. If the local recognition is not needed, setting the remoteOnly to true on the PassioConfiguration process will skip the download of the models. Remote recognition functions like recognizeImageRemote or searchForFood will still work.

If remoteOnly is used when configuring the SDK, calling startFoodDetection with visual or packaged food detection enabled will have no effect.

Handling the configure status object

The result of the configuration process is a PassioStatus object, containing a PassioMode attribute along with the missingFiles, debugMessage, error object and the active models number. The attribute that needs to looked at is the mode.

PassioMode is an enum describing the current state of the Passio SDK configuration process:

  • notReady -> The configuration process hasn't started yet.

  • failedToConfigure -> There was an error during the configuration process.

  • isBeingConfigured -> The SDK is still in the configuration process. Normally, you shouldn't receive this mode as a callback to the configure method. If you do please contact our support team.

  • isDownloadingModels -> The files required by the SDK to work are not present and are currently being downloaded.

  • isReadyForDetection -> The SDK is configured with the set of files defined by the activeModels attribute.

The missingFiles variable contains a list of all the files missing for the SDK to work optimally. When this list is not null, this doesn't mean that the SDK is not operational. The SDK may be initialized with older files, but it will advocate the download of the newest files through this list.

The debugMessage usually gives more verbose and human-readable information about the configuration process. It will always give an error message if the mode is FAILED_TO_CONFIGURE.

The activeModels will give the version of the files installed and ran by the SDK if the configuration process ran successfully i.e. if the mode is IS_READY_FOR_DETECTION.

Proxy url and headers

There are cases when the implementing app wants to redirect all of the networking calls from the SDK to their backend first, before relaying the requests to Passio's backend. In this case, the app's backend acts like a proxy between the SDK and it's backend. To achieve this behaviour, the SDK offers two parameters in the PassioConfiguration object: proxyUrl and proxyHeaders.

If supplied, the SDK will use the proxyUrl value as a base url when executing networking calls, and will append the provided proxyHeaders to the request itself. When proxyUrl is provided, the SDK operates in remoteOnly state.

Best practices

  1. Make sure there is internet connection for the configuration process. The SDK needs to download the license file and the required files. If there is no internet connection the configuration status mode will be failedToConfigure.

  2. The PassioStatus mode is notReady -> check debugMessage for the error, usually the wrong license key has been supplied or the files are missing.

  3. The PassioStatus mode is isDownloadingModels -> register a PassioStatusListener through the setPassioStatusListener method as it will enable tracking of the download progress.

  4. The PassioStatus mode is isReadyForDetection -> still check the missingFiles because the SDK might be running with an older version of the files and newer need to be downloaded.

Use Cases

SDK Key and minimum requirements

Using SDK Key

To use the SDK through license key, please make sure you receive your SDK license key from Passio.

To obtain an SDK key, visit https://www.passio.ai/nutrition-ai

Without using SDK Key

If you want a layer of security, where you can authenticate the license and fetch the token on your backend, you don't need the SDK key.

In that case, the SDK will communicate with your backend, and your backend will proxy the requests to Passio's servers, adding the access token to the header. This way, the license keys and tokens are never shared in the app code.

You need to provide following things when configuring the SDK:

  • Proxy URL - Base url of the target proxy endpoint

  • Proxy headers - Required headers to all of the requests

Responisilities of proxy backend:

  • License authentication - the license will be authenticated on the proxy backend

  • Token fetching - token will be fetched on the proxy backend and appended to every call

The configuration for the proxy URL will be covered in the "Configure the SDK" section.

Minimum requirements

These are the minimum requirements for every supported platform

  • The SDK will only run or compile on iOS 13 or newer.

  • Passio SDK can only be used on a device and will not run on a simulator

  • The SDK requires access to iPhone's camera

  • Weight/Volume estimation will run only on iPhones with Dual Wide Camera (not on DualCamera). It will not run on the models below.

    • iPhone 11 Pro & Pro Max

    • iPhone 12 mini, Pro & Pro Max

    • iPhone 13 Pro & Pro Max

    • iPhone 14, Pro, Pro Max & Plus

  • Built with Kotlin version 1.6.10

  • Minimum Android SDK version is 26

  • The SDK requires access to the device's camera

  • The SDK is built upon CameraX, TensorFlow and Firebase's ML Vision so these dependencies will need to be added to the project manually

iOS SDK Docs

Welcome to Passio Nutrition-AI iOS SDK

When integrated into your app the SDK provides you with food recognition and nutrition assistant technology. The SDK creates a video preview layer and outputs foods recognized by Passio's computer vision technology in the video feed of your camera along with nutrition data related to the recognized foods.

As the developer, you have complete control of when to turn on/off the SDK and to configure the outputs which includes:

  • food names (e.g. banana, hamburger, fruit salad, Quest chocolate bar)

  • lists of alternatives for recognized foods (e.g., soy milk would be an alternative of a visually recognized class milk)

  • barcodes detected on food packages

  • packaged foods recognized by the text detected on food packages

  • nutrition information detected on food packages via Passio's Nutrition Facts reader which returns information written in Nutrition Facts labels

  • nutrition information associated with the foods

  • food weight and volume for certain foods

By default the SDK does not record/store any photos or videos. Instead, as the end user hovers over a food item with his/her camera phone, the SDK recognizes and identifies food items in real time. This hovering action is only transitory/temporary while the end user is pointing the camera at a particular item and is not recorded or stored within the SDK. As a developer, you can configure the SDK to capture images or videos and store them in your app.

Creating a project

Hit the big '+' button in your sidebar and select 'New Project' from the menu that pops up. Give your project a name, and you're good to go!

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

iOS SDK Docs
Android SDK Docs
React Native SDK Docs
Flutter SDK Docs

Barcode scanning

Real-time barcode scanning

The SDK can detect barcodes located on the packaging of packaged foods. To implement real-time barcode scanning, the camera setup for continuous image recognition is needed.

Specifically for barcode scanning, these steps are required:

  1. When defining a FoodDetectionConfiguration object, enable the detectBarcode flag.

  2. In the recognitionResults callback, check the property candidate.barcodeCandidates. If this list is empty, no barcodes have been recognised.

  3. If there is a single or multiple barcode candidates, use their value property and invoke this function to fetch the full nutrition data.

public func fetchFoodItemFor(productCode: String, completion: @escaping ((PassioNutritionAISDK.PassioFoodItem?) -> Void))
fun fetchFoodItemForProductCode(
    productCode: String,
    onResult: (foodItem: PassioFoodItem?) -> Unit
)
fetchFoodItemForProductCode(code: Barcode | PackagedFoodCode): Promise<PassioFoodItem | null>
Future<PassioFoodItem?> fetchFoodItemForProductCode(String productCode)

On smaller packages, barcodes can be very small. When trying to scan these barcodes, the user usually brings to package to close to the camera creating focus issues. To avoid this, but the camera zoom level to 2.

Barcode scanning from an image

To recognise a barcode in an image, refer to the recognizeImageRemote function. The result of the barcode scanning process will come in the packagedFoodItem attribute, and the resultType will be Barcode.

UI Example

  1. Set up the camera preview and detection session to recognise barcodes

  2. Create a result view with two states: search and result

  3. If the candidate.barcodeCandidates is empty, show the search state. Else, fetch the PassioFoodItem, and use it's name and icon to show the result

Example of an image that produces multiple BarcodeCandidates:

Barcode example image

Nutrition Facts scanning

There are cases when the barcode scanning item isn't present in the nutritional database. In the cases when the fetchFoodItemForProductCode doesn't yield a response, the user can scan the nutrition facts label on the packaging of the food and extract the data to create a new food item.

Real-time Nutrition Facts scanning

Similar to the real-time barcode scanning, the camera preview must be set up to enable continuous nutrition facts scanning. To start the Nutrition Facts scanning process a NutritionFactsRecognitionListener has to be defined. When the app is done with the nutrition fact scanning process, it should clear out the listener to avoid any unwanted UI updates.

private val listener = object : NutritionFactsRecognitionListener {
    override fun onRecognitionResult(nutritionFacts: PassioNutritionFacts?, text: String) {
        // Display the result
    }
}

override fun onResume() {
    super.onResume()
    PassioSDK.instance.startNutritionFactsDetection(listener)
}

override fun onPause() {
    PassioSDK.instance.stopNutritionFactsDetection()
    super.onPause()
}

The PassioNutritionFacts contains all of the data that can be scanned from a nutrition facts label including:

  • Serving size (quantity and unit)

  • Weight

  • Calories

  • Carbs

  • Protein

  • Fat

  • Sugars

  • Other micronutrients like fiber, cholesterol...

Nutrition Facts scanning from an image

To recognise nutrition facts in an image, refer to the recognizeImageRemote function. The result of the nutrition facts scanning process will come in the packagedFoodItem attribute, and the resultType will be Nutrition Facts. The nutrients will be stored in the PassioNutrients object of the first ingredient, and the serving size info will be stored in the PassioFoodAmount.

There is a separate API called recognizeNutritionFactsRemote that is specifically taylored for this task. The recognizeImageRemote has the ability to recognise multiple different aspects of food scanning, but if there is a use case where the only action is to extract the data from the nutrition facts label, it's advised to use the recognizeNutritionFactsRemote.

UI Example

  1. The barcode scanning process is always the most precise way to get nutritional information about a packaged food. If the barcode is not present in the nutritional database, give the user the option to scan the nutrition facts label.

  1. If the user proceeds with the Nutrition Facts scan, register a listener to start receiving the results from the SDK. When a PassioNutritionFacts is returned from the callback, display the data so that the user can verify it's accuracy.

  1. Enable to user to manually enter all of the other data that the Nutrition Facts scanner did not recognise.

  1. Once a user saves this food item, be sure to include it in the search or barcode scanning results.

Search, Food Icons, RefCode

Search

The Passio Nutrition AI SDK search functionality gives access to over 2.2 million food records in our nutritional database. The search api takes in a text query and returns a list of PassioFoodDataInfo results as well as a list of suggested searches.

There are two different search APIs available in the SDK:

  1. searchForFood - tries to give best string matches based on the input parameter, often providing the same food from different brands. Example: Input parameter "eggs" will provide results like "Trader Joe's eggs", "Eggland's Best eggs", "Eggs in carton, Lucerne", ...

  2. searchForFoodSemantic - will try to contextually match the most similar foods to the input parameter. Example: Iinput parameter "eggs" will provide results like "Cooked eggs", "Scrambled eggs", "Eggs benedict", ...

Both searchForFood and searchForFoodSemantic have the same input parameters and result structures.

The PassioFoodDataInfo is used a reference object to fetch the full nutritional data. It has all of the attributes needed for displaying on a typical search screen: the name of the food, name of the brand (if possible), iconId, and a nutritional preview detailing the macro nutrients for a specified serving size and weight.

To fetch the PassioFoodItem using a search result, invoke the fetchFoodItemForDataInfo.

The search will return 50 items that best match the input term, as well as a list of suggested searches that are semantically or contextually close to the input term.

Example: searching for term "coffee"

  • search results: "Coffee, Brewed", "Coffee, Breakfast Blend", Coffee - Brewed From Grounds", "Coffee, Brewed, Decaffeinated", "Coffee, Instant, Reconstituted Decaffeinated", ...

  • suggested searches: "coffee with whole milk", "coffee with creamer", "iced coffee"

UI Example

  1. Create an input text field to collect the user query

  2. Create a horizontal list scroll to display the suggestions

  3. Create a vertical list to display the results of the search

  4. If the user clicks on a suggestion, pass that string to the search API and reload the UI with the new results

Food Icons

The SDK gives the ability to fetch food icons for a specific passioId. There are two functions that can be used: lookupIconsFor and fetchIconsFor.

  1. lookupIconsFor does a local search of the icons without doing any networking calls. It returns two icons. One is a placeholder icon depending on the type of food associated with the passioId. The second one is cached icon from a previous fetchIconsFor call, it it exists.

  2. fetchIconsFor does a networking call to fetch an icon associated with the passioId, but it will check the local cache and return a previously fetched icon.

If displaying a PassioFoodItem, PassioIngredient or a PassioFoodDataInfo icon, pass the iconId to the appropriate icon function.

Example of how to use both of these functions to first display the placeholder icon, and then fetch the remote icon:

A component for displaying food icons from the Passio SDK.

RefCode

There are several functions that are used to retrieve the PassioFoodItem object, depending on the origin of the data. If the food item is being scanned using an image, the food item is fetched using either fetchFoodItemForPassioID or fetchFoodItemForProductCode. If search is being used to access to food item, the appropriate call is the fetchFoodItemForDataInfo.

But if there is a use case that requires to save just one attribute to a database, that will later on be used to retrieve the full PassioFoodItem object, then the refCode attribute is the answer. The refCode in combination with the fetchFoodItemForRefCode will retrieve the original food item object from Passio's nutritional database.

refCode is a string hash of a complex object and can reach lengths above 180 characters.

Using v2 PassioID in the v3 SDK

If the implementing app is transitioning from generation 2 to generation 3 of the SDK, and has already stored generation 2 PassioIDs, invoking the fetchFoodItemLegacy will retrieve the PassioFoodItem that was previously structured as PassioIDAttributes.

Installation

Follow these steps to include the SDK into your mobile application project.

Install Swift Package for Xcode 14.3 or newer

  1. Open your Xcode project.

  2. Go to File > Swift Packages > Add Package Dependency.

  3. In the "Add Package Dependency" dialog box, paste the URL:

  4. Click "Next". Xcode will validate the package and its dependencies.

  5. In the next dialog box, you'll be asked to specify the version or branch you want to use. You can choose main for the latest version or specify a specific version or branch.

  6. After you've made your selection, click "Next".

  7. You'll then be prompted to select the targets in your project that should include the package. Check the boxes for the targets you want to include.

  8. Click "Finish" to add the package to your project.

  9. Xcode will download and add the PassioNutritionAISDK to your project. You can now import and start using the PassioNutritionAISDK.

Manual Installation For Xcode lower than 14.3

  • Download the "PassioNutritionAISDK.xcframework.zip" file from

  • Unzip it and drag and drop it into your project. Make sure to select "Copy items if needed".

  • In project "General" -> "Frameworks, Libraries and Embedded Content" Change to "Embed & Sign"

Edit your Info.plist

  • If opening from Xcode, right click and select 'open as source code'

  • To allow camera usage add:

The Passio Android SDK is shipped in the form of an file.

1. Add the dependency using Maven

In the build.gradle file add the PassioSDK dependency

Sync Project with Gradle Files and the PassioSDK interface should be available for usage.

2. Or download the .aar from our distribution repository

  • Visit the of the Android-Passio-SDK-Distribution GitHub repository. Download the passiolib-release.aar file from the latest release.

Include the .aar in your Android Studio project

To include the .aar file into your project go to the module that will contain the food recognition feature. Place the .aar file in the libs folder of the module. If the libs folder is missing, create one. Reference the .aar file in the build.gradle file of the implementing module.

Sync Project with Gradle Files and be sure that you can reference the PassioSDK class within your code.

Dependencies

Passio Android SDK is powered by and with the camera being managed by . Add the dependencies to these three projects by adding these lines to the module's build.gradle file.

In order for the SDK to work add the following lines to the android section of the module's build.gradle file.

Running Sync Project with Gradle Files will enable access to the SDK.

1. Create a with read:packages access selected.

2. Create an .npmrc file in the root of your project replacing GITHUB_ACCESS_TOKEN with the token you've created.

3. Add the Passio SDK dependency to your package.json and run npm install or yarn.

or

4. Ensure the native dependencies are linked to your app.

For iOS, run pod install.

If pod install is failed with error message Specs satisfying the ReactNativePassioSDK (from ../node_modules/@passiolife/nutritionai-react-native-sdk-v3) dependency were found, but they required a higher minimum deployment target. , ensure Podfile is configured with minimum ios version '13.0'

For Android, add this implementation line to the dependencies section on app/build.gradle file.

Also for Android, make sure the android project is configured with minSdkVersion 26 or above (check the project's build.gradle file).

The Flutter SDK is available as a package on

Run the command

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import

Now in your Dart code, you can use:

Setup for Android

  • Add to top build.gradle file (Project: android)

  • The nutrition_ai build.gradle file dependency section should look like (Module: nutrition_ai)

Food recognition

The Passio SDK has the ability the recognise anything from simple ingredients like blueberries and almonds to complex cooked dishes like a beef burrito with salad and fries.

There are two types of food recognition, each of them with their own strengths and weaknesses.

Type
Works offline
Precision
Response time

The Remote image recognition approach is good when the accuracy of the results is top priority, and waiting for the response is not an issue. This use case is implemented by taking static images from the camera or the library of the device and sending them for recognition in an asynchronous fashion.

The Local model approach is good when speed is of the essence. This use case is implemented using continuous frame recognition from the camera. A callback is registered to capture the results as they are coming from the camera stream.

Remote image recognition

This API sends an image as base64 format to an LLM on Passio's backend, and returns a list of recognised items.

The API can recognise foods raw or prepared, barcodes or nutrition facts tables. The type of recognition will be shown in the resultType enum.

The default behaviour of this function is to resize the image to 512 pixels (longer dimension is resized, the other is calculated to keep aspect ratio). Using the PassioImageResolution enum, the image can be either resized to 512, 1080 or keep the original resolution.

The response, presented as a list ofPassioAdvisorFoodInfo objects, contains the name, portion and weight in grams recognised by the LLM. These attributes can be used for debugging, but the data from the nutritional database is contained either in the foodDataInfo if the result type is a Food Item, or packagedFoodItem if it's a Barcode or Nutrition Facts. To fetch the for the PassioFoodDataInfo object, use the fetchFoodItemForDataInfo function.

UI Example

  1. Create a screen where the user can snap one or multiple images using the camera of the device

  2. Upon clicking next, the recognizeImageRemote is invoked on each of the images in the list

  3. Wait for all of the responses to come, add each results list to a final list of results. When the last asynchronous function is executed, present the final list to the user.

Local neural network model

To set up the local model and the continuous scanning mode, the camera preview and the recognition session need to be defined.

Camera preview

To start using camera detection the app must first acquire the permission to open the camera from the user. This permission is not handled by the SDK.

Add the UI element that is responsible for rendering the camera frames:

To start using the camera in your Activity/Fragment, implement the PassioCameraViewProvider interface. By implementing this interface the SDK will use that component as the lifecycle owner of the camera (when that component calls onPause() the camera will stop) and also will provide the Context in order for the camera to start. The component implementing the interface must have a in its view hierarchy.

Start by adding the PreviewView to your view hierarchy. Go to your layout.xml and add the following.

Using the PassioCameraViewProvider

This approach is more manual but gives you more flexibility. You need to implement the PassioCameraViewProvider interface and supply the needed LifecycleOwner and the PreviewView added in the initial step.

After the user has granted permission to use the camera, start the SDK camera

Using the PassioCameraFragment

PassioCameraFragment is an abstract class that handles Camera permission at runtime as well as starting the Camera process of the SDK. To use the PassioCameraFragment simply extend it in your own fragment and supply the PreviewView that has been added to the view hierarchy in the previous step.

To show the live camera preview, add the DetectionCameraView to your view

Start food detection

The SDK can detect 3 different categories: VISUAL, BARCODE and PACKAGED. The VISUAL recognition is powered by Passio's neural network and is used to recognize over 4000 food classes. BARCODE, as the name suggests, can be used to scan a barcode located on a branded food. Finally, PACKAGED can detect the name of a branded food. To choose one or more types of detection, a FoodDetectionConfiguration object is defined and the corresponding fields are set. The VISUAL recognition works automatically.

The type of food detection is defined by the FoodDetectionConfiguration object. To start the Food Recognition process a FoodRecognitionListener also has to be defined. The listener serves as a callback for all the different food detection processes defined by the FoodDetectionConfiguration. When the app is done with food detection, it should clear out the listener to avoid any unwanted UI updates.

Implement the delegate FoodRecognitionDelegate:

Add the method startFoodDetection()

In viewWillAppear request authorisation to use the camera and start the recognition:

Stop Food Detection in viewWillDisappear:

Using the listener and the detection options start the food detection by calling the startFoodDetection method of the SDK.

Stop the food recognition in the onStop() lifecycle callback.

Add the method startFoodDetection() and register a FoodRecognitionListener

Stop Food Detection on widget dispose:

The FoodCandidates object that is returned in the recognition callbacks contains three lists:

  • detectedCandidates detailing the result of VISUAL detection

  • barcodeCandidates detailing the result of BARCODE detection

  • packagedFoodCandidates detailing the result of PACKAGED detection

Only the corresponding candidate lists will be populated (e.g. if you define detection types VISUAL and BARCODE, you will never receive a packagedFoodCandidates list in this callback).

Visual detection

A DetectedCandidate represents the result from running Passio's neural network, specialized for detecting foods like apples, salads, burgers etc. The properties of a detected candidate are:

  • name

  • passioID (unique identifier used to query the nutritional databse)

  • confidence (measure of how accurate is the candidate, ranges from 0 to 1)

  • boundingBox (a rectangle detailing the bounds of the recognised item within the image dimensions)

  • alternatives (list of alternative foods that are visually or contextually similar to the recognised food)

  • croppedImage (the image that the recognition was ran on)

To fetch the of a detected candidate use:

UI Example

  1. Implement the camera screen using the steps above

  2. Create a result view that can have two states: scanning and result

  3. If the callback returns an empty list, show the scanning state. If it returns the result, display the name from the detectedCandidate.name

Example of an image that produces a DetectedCandidate:

Speech recognition

The recognizeSpeechRemote function is primarily used to fetch food items using a voice command, although it can be used to extract food items from a recipe in a text form as well.

The input to this function is just a string in free format, so the implementing side needs to do speech-to-text before using the API.

This function will be able to extract several pieces of data:

  • meal action, is this food being added or removed from the logs

  • meal time (breakfast, dinner, lunch or snack)

  • date of log

  • recognised name from the LLM

  • portion and weight from the LLM

  • nutritional data reference as PassioFoodDataInfo

Example of logging a morning breakfast:

The SDK doesn't have the functionality to record the voice session, that has to be handled by the app.

UI Example

  1. Create a screen where the user can record the voice logging command. Make sure to add the appropriate permissions.

  2. The UI should enable the user to start/stop voice recording on a tap of a button. When the voice recording is done collect the string and use the SDK to extract food.

  3. Once the SDK returns the results, show the list to the user with the option to deselect incorrect predictions.

fun searchForFood(
    term: String,
    callback: (result: List<PassioFoodDataInfo>, searchOptions: List<String>) -> Unit
)

data class PassioFoodDataInfo(
    val foodName: String,
    val brandName: String,
    val iconID: PassioID,
    val score: Double,
    val scoredName: String,
    val labelId: String,
    val type: String,
    val resultId: String,
    val isShortName: Boolean,
    val nutritionPreview: PassioSearchNutritionPreview
)

data class PassioSearchNutritionPreview(
    val calories: Int,
    val carbs: Double,
    val protein: Double,
    val fat: Double,
    val servingUnit: String,
    val servingQuantity: Double,
    val weightUnit: String,
    val weightQuantity: Double
)
searchForFood(searchQuery: string): Promise<PassioSearchResult | null>

export interface PassioSearchResult {
  results: PassioFoodDataInfo[] | null
  alternatives?: string[] | null
}

export interface PassioFoodDataInfo {
  brandName?: string
  foodName: string
  iconID: PassioID | string
  labelId: string
  nutritionPreview?: PassioSearchNutritionPreview
  resultId: string
  score?: number
  scoredName?: string
  type?: string
  isShortName?: boolean
}

export interface PassioNutritionPreview {
  calories: number
  servingQuantity?: number
  servingUnit?: string
  weightQuantity: number
  weightUnit: string
  fat: number
  protein: number
  carbs: number
}
Future<PassioSearchResponse> searchForFood(String byText) {
    return NutritionAIPlatform.instance.searchForFood(byText);
}

class PassioSearchResponse {
  final List<PassioFoodDataInfo> results;
  final List<String> alternateNames;
}

class PassioFoodDataInfo {
  final String brandName;
  final String foodName;
  final PassioID iconID;
  final String labelId;
  final PassioSearchNutritionPreview nutritionPreview;
  final String resultId;
  final double score;
  final String scoredName;
  final String type;
  final bool useShortName;
}

class PassioSearchNutritionPreview {
  final int calories;
  final double carbs;
  final double fat;
  final double protein;
  final double servingQuantity;
  final String servingUnit;
  final double weightQuantity;
  final String weightUnit;
}
public extension UIImageView {
    // for tableView Cell.

    func loadPassioIconBy(passioID: PassioID,
                          entityType: PassioIDEntityType,
                          size: IconSize = .px90,
                          completion: @escaping (PassioID, UIImage) -> Void) {
        let (placeHolderIcon, icon) = PassioNutritionAI.shared.lookupIconsFor(passioID: passioID,
                                                                     size: size,
                                                                     entityType: entityType)
        if let icon = icon {
            self.image = icon
        } else {
            self.image = placeHolderIcon
            PassioNutritionAI.shared.fetchIconFor(passioID: passioID) { image in
                if let image = image {
                    completion(passioID, image)
                }
            }
        }
    }
}

// call with your UIImageView
imageFoodIcon.loadPassioIconBy(passioID: passioID, entityType: .item) { id, image in
     DispatchQueue.main.async {
         self.imageFoodIcon.image = image
     }
}
fun ImageView.loadPassioIcon(
    passioID: PassioID,
    type: PassioIDEntityType = PassioIDEntityType.item,
    iconSize: IconSize = IconSize.PX90
) {
    this.tag = passioID
    val localImageResult = PassioSDK.instance.lookupIconsFor(context, passioID, iconSize, type)

    if (localImageResult.second != null) {
        setImageDrawable(localImageResult.second)
        return
    }

    setImageDrawable(localImageResult.first)

    PassioSDK.instance.fetchIconFor(context, passioID, iconSize) { drawable ->
        if (drawable != null && this.tag == passioID) {
            setImageDrawable(drawable)
        }
    }
}
import {
  PassioIconView,
  type IconSize
} from '@passiolife/nutritionai-react-native-sdk-v3'
         <View
            style={{
              height: 24,
              width: 24,
              overflow: 'hidden',
              borderRadius: 12,
            }}
          >
            <PassioIconView
              style={{
                height: 24,
                width: 24,
              }}
              config={{
                passioID: item.passioID,
                iconSize: IconSize.PX180,
              }}
            />
       </View>
class PassioImageWidget extends StatefulWidget {
  const PassioImageWidget({
    required this.iconId,
    this.type = PassioIDEntityType.item,
    this.iconSize = IconSize.px90,
    this.radius = 30,
    this.heroTag,
    super.key,
  });

  final String iconId;
  final PassioIDEntityType type;
  final IconSize iconSize;
  final double radius;
  final Object? heroTag;

  @override
  State<PassioImageWidget> createState() => _PassioImageWidgetState();
}

class _PassioImageWidgetState extends State<PassioImageWidget> {
  final ValueNotifier<PlatformImage?> _image = ValueNotifier(null);

  @override
  void initState() {
    _fetchImage();
    super.initState();
  }

  @override
  void dispose() {
    _image.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<PlatformImage?>(
      valueListenable: _image,
      builder: (context, value, child) => value != null
          ? Hero(
              tag: widget.heroTag ?? UniqueKey(),
              child: CircleAvatar(
                radius: widget.radius,
                backgroundImage: MemoryImage(value.pixels),
              ),
            )
          : const CircularProgressIndicator(),
    );
  }

  Future<void> _fetchImage() async {
    try {
      final result = await NutritionAI.instance.lookupIconsFor(
        widget.iconId,
        iconSize: widget.iconSize,
        type: widget.type,
      );

      setImage(result.cachedIcon ?? result.defaultIcon);
      if (result.cachedIcon != null) {
        return;
      }

      if (result.cachedIcon == null && widget.iconId.isNotEmpty) {
        final fetchedImage = await NutritionAI.instance.fetchIconFor(
          widget.iconId,
          iconSize: widget.iconSize,
        );
        if (fetchedImage != null) {
          setImage(fetchedImage);
        }
      }
    } catch (error) {
      // Handle potential errors during image fetching
      log("Error fetching image: $error");
    }
  }

  void setImage(PlatformImage? image) {
    if (mounted) {
      _image.value = image;
    }
  }
}
PassioNutritionAI.shared.fetchFoodItemFor(passioID: "VEG0025") { (foodItem) in
        if let refCode = foodItem?.refCode {
            PassioNutritionAI.shared.fetchFoodItemFor(refCode: refCode) { refFoodItem in
            // foodItem == refFoodItem
        }
    }
}
PassioSDK.instance.fetchFoodItemForPassioID("VEG0025") { foodItem ->
    // foodItem is the original object from the database
    val refCode = foodItem!!.refCode
    // refCode is stored and used in another session
    PassioSDK.instance.fetchFoodItemForRefCode(refCode) { refFoodItem ->
        // foodItem == refFoodItem
    }
}
const foodItem = await PassioSDK.fetchFoodItemForPassioID('VEG0025')
// foodItem is the original object from the database
const refCode = foodItem?.refCode
// refCode is stored and used in another session
const refCodeFoodItem = await PassioSDK.fetchFoodItemForRefCode(refCode)
final foodItem = await NutritionAI.instance.fetchFoodItemForPassioID('VEG0025');
// foodItem is the original object from the database
final refCode = foodItem!.refCode;
// refCode is stored and used in another session
final refFoodItem = await NutritionAI.instance.fetchFoodItemForRefCode(refCode);
// foodItem == refFoodItem
public func searchForFood(byText: String, completion: @escaping (SearchResponse?) -> Void)

public struct PassioFoodDataInfo: Codable {
    public let brandName: String
    public let foodName: String
    public let iconID: PassioID
    public let labelId: String
    public let resultId: String
    public let score: Double
    public let scoredName: String
    public let type: String
    public let nutritionPreview: PassioSearchNutritionPreview?
    public let isShortName: Bool
}

public struct PassioSearchNutritionPreview: Codable {
    public var calories: Int
    public let carbs: Double
    public let fat: Double
    public let protein: Double
    public var servingUnit: String
    public var servingQuantity: Double
    public var weightUnit: String
    public var weightQuantity: Double
}

Remote image recognition

No, the image is sent to the backend for recognition

Very precise with all types of foods

On average 4-7 seconds

Local neural network model

Yes, the recognition is done on the device

Good with single food items, struggles with complex cooked foods

Depending on the hardware of the device, ranges between 50-300ms

import {
  PassioSDK,
  type PassioAdvisorFoodInfo,
  type PassioFoodDataInfo,
} from '@passiolife/nutritionai-react-native-sdk-v3'

import { launchImageLibrary } from 'react-native-image-picker'

const onScanImage = useCallback(async () => {
    try {
      const { assets } = await launchImageLibrary({ mediaType: 'photo' })
      if (assets) {
        setLoading(true)
        setPassioSpeechRecognitionModel(null)
        PassioSDK.recognizeImageRemote(
          assets?.[0].uri?.replace('file://', '') ?? ''
        )
          .then(async (candidates) => {
            setPassioSpeechRecognitionModel(candidates)
          })
          .catch(() => {
            Alert.alert('Unable to recognized this image')
          })
          .finally(() => {
            setLoading(false)
          })
      }
    } catch (err) {
      setLoading(false)
    }
  }, [])
// Create a File object from the image path
var file = File(imagePath);
// Read the file's bytes asynchronously
var bytes = await file.readAsBytes();
// Send the byte array to the NutritionAI for remote image recognition
final list = await NutritionAI.instance.recognizeImageRemote(bytes);

class PassioAdvisorFoodInfo {
  final PassioFoodDataInfo? foodDataInfo;
  final PassioFoodItem? packagedFoodItem;
  final String portionSize;
  final PassioFoodResultType resultType;
  final String recognisedName;
  final double weightGrams;
}
var videoLayer: AVCaptureVideoPreviewLayer?

func setupPreviewLayer() {
    guard videoLayer == nil else { return }
    if let videoLayer = passioSDK.getPreviewLayer() {
        self.videoLayer = videoLayer
        videoLayer.frame = view.bounds
        view.layer.insertSublayer(videoLayer, at: 0)
    }
}
<androidx.camera.view.PreviewView
    android:id="@+id/myPreviewView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
class MainActivity : AppCompatActivity(), PassioCameraViewProvider {
	
    override fun requestCameraLifecycleOwner(): LifecycleOwner {
        return this
    }

    override fun requestPreviewView(): PreviewView {
        return myPreviewView
    }
}
override fun onStart() {
    super.onStart()
    if (!hasPermissions()) {
        ActivityCompat.requestPermissions(
            this,
            REQUIRED_PERMISSIONS,
            REQUEST_CODE_PERMISSIONS
        )
        return
    } else {
        PassioSDK.instance.startCamera(this /*reference to the PassioCameraViewProvider*/)
    }
}
class MyFragment : PassioCameraFragment() {
	
    override fun getPreviewView(): PreviewView {
	return myPreviewView
    }

    override fun onCameraReady() { 
    	// Proceed with initializing the recognition session
    }

    override fun onCameraPermissionDenied() { 
    	// Explain to the user that the camera is needed for this feature to
        // work and ask for permission again
    }
}
import {
  PassioSDK,
  DetectionCameraView,
} from '@passiolife/nutritionai-react-native-sdk-v2';
<DetectionCameraView style={{flex: 1, width: '100%'}} />
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Stack(
      children: [
        const PassioPreview(),
        ...
      ],
    ),
  );
}
extension PassioQuickStartViewController: FoodRecognitionDelegate {
  func recognitionResults(candidates: FoodCandidates?,
                          image: UIImage?) {
        if let candidates = candidates?.barcodeCandidates,
           let candidate = candidates.first {
            print("Found barcode: \(candidate.value)")
        }
        
        if let candidates = candidates?.packagedFoodCandidates,
           let candidate = candidates.first {
            print("Found packaged food: \(candidate.packagedFoodCode)")
        }
        
        if let candidates = candidates?.detectedCandidates,
           let candidate = candidates.first {
            print("Found detected food: \(candidate.name)")
        }
  }
}
func startFoodDetection() {
    setupPreviewLayer()
                
    let config = FoodDetectionConfiguration(detectVisual: true,
                                            volumeDetectionMode: .none,
                                            detectBarcodes: true,
                                            detectPackagedFood: true)
    passioSDK.startFoodDetection(detectionConfig: config,
                                 foodRecognitionDelegate: self) { ready in
        if !ready {
            print("SDK was not configured correctly")
        }
    }
}
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
        startFoodDetection()
    } else {
        AVCaptureDevice.requestAccess(for: .video) { (granted) in
            if granted {
                DispatchQueue.main.async {
                    self.startFoodDetection()
                }
            } else {
                print("The user didn't grant access to use camera")
            }
        }
    }
}
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    passioSDK.stopFoodDetection()
    videoLayer?.removeFromSuperlayer()
    videoLayer = nil
}
private val foodRecognitionListener = object : FoodRecognitionListener {
    override fun onRecognitionResults(
        candidates: FoodCandidates,
        image: Bitmap?,
    ) {
        // Handle result
    }
}
override fun onStart() {
    super.onStart()
    val options = FoodDetectionConfiguration().apply {
        detectBarcodes = true
    }
    PassioSDK.instance.startFoodDetection(foodListener, options)
}
override fun onStop() {
    PassioSDK.instance.stopFoodDetection()
    super.onStop()
}
const config: FoodDetectionConfig = {
   /**
   * Detect barcodes on packaged food products. Results will be returned
   * as `BarcodeCandidates` in the `FoodCandidates` property of `FoodDetectionEvent`
   */
  detectBarcodes: true,
 
  /**
   * Results will be returned as DetectedCandidate in the `FoodCandidates`and 
   * property of `FoodDetectionEvent`
   */

  detectPackagedFood: true,
};
    
useEffect(() => {
  if (!isReady) {
    return;
  }
  const subscription = PassioSDK.startFoodDetection(
    config,
    async (detection: FoodDetectionEvent) => {

      const { candidates, nutritionFacts } = detection

      if (candidates?.barcodeCandidates?.length) {
         
         // show barcode candidates to the user

      } else if (candidates?.packagedFoodCode?.length) {
        
        // show OCR candidates to the user

      } else if (candidates?.detectedCandidates?.length) {
        
        // show visually recognized candidates to the user

      }
    },
  );
  // stop food detection when component unmounts
  return () => subscription.remove(); 

}, [isReady]);
void _startFoodDetection() {
  var detectionConfig = FoodDetectionConfiguration();
  detectionConfig.detectBarcodes = true;
  NutritionAI.instance.startFoodDetection(detectionConfig, this);
}

@override
void recognitionResults(FoodCandidates? foodCandidates, PlatformImage? image) {
  // Handle result
}
@override
void dispose() {
  NutritionAI.instance.stopFoodDetection();
  super.dispose();
}
public func fetchFoodItemFor(passioID: PassioNutritionAISDK.PassioID, completion: @escaping (PassioNutritionAISDK.PassioFoodItem?) -> Void)
fun fetchFoodItemForPassioID(
    passioID: PassioID,
    onResult: (foodItem: PassioFoodItem?) -> Unit
)
fetchFoodItemForPassioID(passioID: PassioID): Promise<PassioFoodItem | null>
Future<PassioFoodItem?> fetchFoodItemForPassioID(PassioID passioID)
nutritional data
PreviewView
full nutrition data
let image = Bundle.main.path(forResource: "image1", ofType: "png")
PassioNutritionAI.shared.recognizeImageRemote(image: image) { passioAdvisorFoodInfo in
    print("Food Info:- \(passioAdvisorFoodInfo)")
}
public struct PassioAdvisorFoodInfo: Codable {
    public let recognisedName: String
    public let portionSize: String
    public let weightGrams: Double
    public let foodDataInfo: PassioFoodDataInfo
}
val bitmap = loadBitmapFromAssets(assets, "image1.png")
PassioSDK.instance.recognizeImageRemote(bitmap) { result ->
    // display the list
}

data class PassioAdvisorFoodInfo(
    val recognisedName: String,
    val portionSize: String,
    val weightGrams: Double,
    val foodDataInfo: PassioFoodDataInfo? = null,
    val packagedFoodItem: PassioFoodItem? = null,
    val resultType: PassioFoodResultType,
)

enum class PassioFoodResultType {
    FOOD_ITEM,
    BARCODE,
    NUTRITION_FACTS
}
import {
  PassioSDK,
  type PassioSpeechRecognitionModel,
  type PassioFoodDataInfo,
} from '@passiolife/nutritionai-react-native-sdk-v3'

const recognizeSpeech = useCallback(
    async (text: string) => {
      try {
          // Fetch food results from the PassioSDK based on the query
          const recognizedModel = await PassioSDK.recognizeSpeechRemote("I had some scrambled egg whites, turkey bacon, whole grain toast, and a black coffee for breakfast")
          setPassioSpeechRecognitionModel(recognizedModel)
        } catch (error) {
          // Handle errors, e.g., network issues or API failures
        } finally {
          // Reset loading state to indicate the end of the search
        }
    },
    [cleanSearch]
  )
final result = await NutritionAI.instance.recognizeSpeechRemote("I had some scrambled egg whites, turkey bacon, whole grain toast, and a black coffee for breakfast");

class PassioSpeechRecognitionModel {
  final PassioLogAction? action;
  final PassioAdvisorFoodInfo advisorInfo;
  final String date;
  final PassioMealTime? mealTime;
}

enum PassioLogAction {
  add,
  remove,
  none,
}
let speech = "I had some scrambled egg whites, turkey bacon, whole grain toast, and a black coffee for breakfast"
PassioNutritionAI.shared.recognizeSpeechRemote(from: speech) { recognitionResult in
    print("Result:- \(recognitionResult)")
}

public struct PassioSpeechRecognitionModel {
    public let action: PassioLogAction?
    public let meal: PassioMealTime?
    public let date: String!
    public let advisorFoodInfo: PassioAdvisorFoodInfo
}

public enum PassioLogAction: String, Codable, CaseIterable {
    case add
    case remove
    case none
}
PassioSDK.instance.recognizeSpeechRemote(
    "I had some scrambled egg whites, turkey bacon, whole grain toast, and a black coffee for breakfast"
) {
    // Display results
}

data class PassioSpeechRecognitionModel(
    val action: PassioLogAction?,
    val mealTime: PassioMealTime?,
    val date: String,
    val advisorInfo: PassioAdvisorFoodInfo
)

enum class PassioLogAction {
    ADD,
    REMOVE,
    NONE
}

Getting the ml models to the device

The SDK will automatically download the models according to the SDK version, by default the SDK will download compressed files. The download is faster and lighter, and it will take several seconds to decompress on the device. To download uncompressed files and shorter processing on the device you can set a flag below to false.

PassioNutritionAI.shared.requestCompressedFiles = false

After obtaining special approval the developer can also host the models on an internal server.

var passioConfig = PassioConfiguration(key: "your_key")
passioConfig.sdkDownloadsModels = false

In that case, the models will be served directly to the SDK. To find out more about this special configuration please contact your primary point of contact at Passio or reach us via support@passiolife.com

Before getting started

OpenFoodFacts Terms of use

Passio Nutrition-AI SDK added data from Open Food Facts (https://en.openfoodfacts.org/). Each food that contains data from Open Food Facts will be marked by public var isOpenFood: Bool. In case you choose to use foods that are marked by isOpenFood = true you agree to abide by the terms of the Open Food Facts license agreement (https://opendatacommons.org/licenses/odbl/1-0) and their terms of use (https://world.openfoodfacts.org/terms-of-use) and you will have to add to the UI the following license copy:

"This record contains information from Open Food Facts (https://en.openfoodfacts.org), which is made available here under the Open Database License (https://opendatacommons.org/licenses/odbl/1-0)"

If you don't wish to display the Open Food Facts license somewhere in your app, it is your responsibility to ignore results where isOpenFood = true.

SDK key

To use the SDK please make sure you receive your SDK license key from Passio. The SDK WILL NOT WORK without a valid SDK key.

Proxy URL

If you want a layer of security, where you can authenticate the license and fetch the token on your backend, you don't need the SDK key.

In that case, the SDK will communicate with your backend, and your backend will proxy the requests to Passio's servers, adding the access token to the header. This way, the license keys and tokens are never shared in the app code.

You need to provide following things when configuring the SDK:

  • Proxy URL - Base url of the target proxy endpoint

  • Proxy headers - Required headers to all of the requests

Request Access to the GitHub repository

You will have to download the latest releases from the link below. The command "git clone" WILL NOT download the PassioNutritionAISDK.xcframework. https://github.com/Passiolife/Passio-Nutrition-AI-iOS-SDK-Distribution/releases. Download the PassioSDKQuickStart.zip or the PassioSDKFullDemo.zip and either install the framework using the Swift Package Manager directions or copy the PassioNutritionAISDK.xcframework to your project. Make sure you have followed the directions in the README files.

Minimum Requirements

In order to use the PassioSDK your app needs to meet the following minimal requirements:

  • The SDK will only run or compile on iOS 13 or newer.

  • Passio SDK can only be used on a device and will not run on a simulator

  • The SDK requires access to iPhone's camera

  • Weight/Volume estimation will run only on iPhones with Dual Wide Camera (not on DualCamera). It will not run on the models below.

    • iPhone 11 Pro & Pro Max

    • iPhone 12 mini, Pro & Pro Max

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Initialize and configure the SDK

At the top of your view controller import the PassioNutritionAISDK and AVFoundation

import AVFoundation
import PassioNutritionAISDK

Add the following properties to your view controller.

let passioSDK = PassioNutritionAI.shared
var videoLayer: AVCaptureVideoPreviewLayer?
var isSDKConfigured = false
var isProcessingResult = false

Create a function to configure the SDK and call it from viewDidLoad. Use the SDK key you received from Passio. We'll add the function for checkCameraAuthorizationAndStartDetection in the next step, so ignore the error for now.

func configureSDK() {
    // If you are using SDK key
    let key = "Your_SDK_Key"
    let passioConfig = PassioConfiguration(key: key)
    
    // If you are using Proxy URL
    var passioConfig = PassioConfiguration()
    passioConfig.proxyUrl = "https://yourdomain.com/nutrition/"
    passioConfig.proxyHeaders = ["header" : "value"] // Set required headers
    
    passioSDK.configure(passioConfiguration: passioConfig) { [weak self] status in
        guard let self else { return }
        
        print("Mode = \(status.mode)\n missingfiles = \(String(describing: status.missingFiles))" )
        self.isSDKConfigured = (status.mode == .isReadyForDetection)
        if self.isSDKConfigured {
            DispatchQueue.main.async {
                self.checkCameraAuthorizationAndStartDetection()
            }
        } else {
            print("SDK configuration failed. Mode: \(status.mode)")
        }
    }
}
override func viewDidLoad() {
    super.viewDidLoad()
    configureSDK()
}

You will receive the PassioStatus back from the SDK.

public struct PassioStatus {
    public internal(set) var mode: PassioSDK.PassioMode { get }
    public internal(set) var missingFiles: [PassioSDK.FileName]? { get }
    public internal(set) var debugMessage: String? { get }
    public internal(set) var activeModels: Int? { get }
}

public enum PassioMode {
    case notReady
    case isBeingConfigured
    case isDownloadingModels
    case isReadyForDetection
    case failedToConfigure
}

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Start/Stop food detection

Add a function to setup the camera preview layer:

func setupPreviewLayer() {
    guard videoLayer == nil else { return }
    if let videoLayer = passioSDK.getPreviewLayer() {
        self.videoLayer = videoLayer
        videoLayer.frame = view.bounds
        DispatchQueue.main.async { [weak self] in
            self?.view.layer.insertSublayer(videoLayer, at: 0)
        }
    }
}

Add the method startFoodDetection() This code will throw a missing delegate conformance error that we will resolve in the next step, ignore it for now.

func startFoodDetection() {
    setupPreviewLayer()
    DispatchQueue.global(qos: .userInitiated).async { [weak self] in
        guard let self else { return }
        
        let config = FoodDetectionConfiguration(detectVisual: true,
                                                volumeDetectionMode: .none,
                                                detectBarcodes: true,
                                                detectPackagedFood: true)
        passioSDK.startFoodDetection(detectionConfig: config,
                                     foodRecognitionDelegate: self) { ready in
                print("SDK was not configured correctly")
        }
    }
}

Add a function to request authorization to use the camera and start recognition. Call the function from viewWillAppear:

func checkCameraAuthorizationAndStartDetection() {
    if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
        startFoodDetection()
    } else {
        AVCaptureDevice.requestAccess(for: .video) { granted in
            if granted {
                DispatchQueue.main.async { [weak self] in
                    self?.startFoodDetection()
                }
            } else {
                print("The user didn't grant access to use camera")
            }
        }
    }
}
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    checkCameraAuthorizationAndStartDetection()
}

Create a function to stop Food Detection and call it from viewWillDisappear:

func stopFoodDetection() {
    passioSDK.stopFoodDetection()
    videoLayer?.removeFromSuperlayer()
    videoLayer = nil
}
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    stopFoodDetection()
}

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Quick Start Guide

This Quick Start Guide will help you integrate each functionality and API of the Passio SDK using your custom UI. The guide outlines essential steps and provides examples for a smooth implementation.

The quick start demo project for iOS can be found here: https://github.com/Passiolife/Passio-iOS-QuickStart/tree/main/PassioQuickStart

Functionality Overview

In this Quick Start Guide, we will cover the following functionalities:

  1. Installation

  2. Configure the SDK

  3. Recognize Image Remote

  4. Food Detail

Minimum Requirements:

In order to use the PassioSDK, your app needs to meet the following minimal requirements:

  • The SDK will only run or compile on iOS 13 or newer.

  • Passio SDK can only be used on a device and will not run on a simulator

  • The SDK requires access to iPhone's camera

  • Weight/Volume estimation will run only on iPhones with Dual Wide Camera (not on DualCamera). It will not run on the models below.

    • iPhone 11 Pro & Pro Max

    • iPhone 12 mini, Pro & Pro Max

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

To get started, please proceed to Installation

Migration from SDK 1.4.X to 2.x

Before you continue, please make sure you have a new key for version 2.x. The 1.4.x will not work with 2.x SDK!

Remove SDK 1.4.x

  1. Open project and delete the PassioSDKiOS framework

Add the new SDK 2.x

  1. Drag and Drop the new "PassioNutritionAISDK.xcframework" to the project (select copy if needed)

  2. In Projects > Targets > Your App > Frameworks, Libraries, and Embedded Content. Set the "PassioNutritionAISDK.xcframework" to "Embed & Sign"

Find and Replace all

  1. import PassioSDKiOS with import PassioNutritionAISDK

  2. PassioSDK.shared with PassioNutritionAI.shared

  3. detectOCR with detectPackagedFood

  4. ocrCandidates with packagedFoodCandidates

  5. fetchPassioIDAttributesFor(ocrcode: $0 with fetchPassioIDAttributesFor(packagedFoodCode: $0.packagedFoodCode)

  6. autoUpdate with sdkDownloadsModels

  7. isAutoUpdating with isDownloadingModels

  8. isReadyForNutrition was removed use isReadyForDetection

The servingSizeQuantity was modified from

public var servingSizeQuantity: Int

to:

public var servingSizeQuantity: Double

Changes to the functions

  1. The function func searchForFood(byText: String) -> [String] return value was modified to -> [PassioIDAndName].

  2. Removed func lookupPassioIDAttributesFor(name: String) -> PassioIDAttributes? use instead func searchForFood(byText: String) -> [PassioIDAndName] and then use the PassioID to lookup the PassioIDAttributes using func lookupPassioIDAttributesFor(passioID: PassioID) -> PassioIDAttributes?.

the function

public func lookupNameForPassioID(passioID: PassioNutritionAISDK.PassioID) -> String?

was renamed to

public func lookupIconFor(passioID: PassioNutritionAISDK.PassioID, size: PassioNutritionAISDK.IconSize = IconSize.px90, entityType: PassioNutritionAISDK.PassioIDEntityType = .item) -> (UIImage, Bool)

where the Bool Returns: UIImage and a bool, The boolean is true if the icons is food icon or false if it's a placeholder icon. If you get false you can use the asycronous funcion to "fetchIconFor" the icons from

public func fetchIconFor(passioID: PassioNutritionAISDK.PassioID, size: PassioNutritionAISDK.IconSize = IconSize.px90, entityType: PassioNutritionAISDK.PassioIDEntityType = .item, completion: @escaping (UIImage?) -> Void)

Added UIImageView extension please check demo app to check how to use it.

extension UIImageView {
    public func loadPassioIconBy(passioID: PassioNutritionAISDK.PassioID, entityType: PassioNutritionAISDK.PassioIDEntityType, size: PassioNutritionAISDK.IconSize = .px90, completion: @escaping (PassioNutritionAISDK.PassioID, UIImage) -> Void)
}

Removed from the SDK

  1. References to logo detection

  2. status.mode == .isReadyForNutrition use only .isReadyForDetection

all the images from PassioIDAttributes and PassioFoodItemData where removed.

public var image: UIImage? { get }
public var imageName: String { get }

PassioDownloadDelegate was merged into the PassioStatusDelegate

public protocol PassioStatusDelegate : AnyObject {
    func passioStatusChanged(status: PassioNutritionAISDK.PassioStatus)
    func passioProcessing (filesLeft: Int)
    func completedDownloadingAllFiles(filesLocalURLs: [PassioNutritionAISDK.FileLocalURL])
    func completedDownloadingFile(fileLocalURL: PassioNutritionAISDK.FileLocalURL, filesLeft: Int)
    func downloadingError(message: String)
}

Copyright 2022 Passio Inc

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Food Recognition Delegate

We'll now resolve the error introduced earlier by implementing FoodRecognitionDelegate:

extension ViewController: FoodRecognitionDelegate {
    func recognitionResults(candidates: FoodCandidates?, image: UIImage?) {
        guard !isProcessingResult else { return }
        
        if let candidates = candidates?.barcodeCandidates,
           let candidate = candidates.first {
            print("Found barcode: \(candidate.value)")
        }
        
        if let candidates = candidates?.packagedFoodCandidates,
           let candidate = candidates.first {
            print("Found packaged food: \(candidate.packagedFoodCode)")
        }
        
        if let candidates = candidates?.detectedCandidates,
           let candidate = candidates.first {
            isProcessingResult = true
            PassioNutritionAI.shared.fetchFoodItemFor(passioID: candidate.passioID) { [weak self] foodItem in
                if let foodItem {
                    let nutrients250g = foodItem.nutrients(weight: Measurement<UnitMass>(value: 250.0, unit: .grams))
                    
                    if let calories = nutrients250g.calories()?.converted(to: .kilocalories) {
                        print("Food: \(foodItem.foodItemName): \(calories.value) calories")
                    }
                }
                self?.isProcessingResult = false
            }
        }
    }
}

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

`<key>>NSCameraUsageDescription</key><string>For real-time food recognition</string>`.
dependencies {
    implementation 'ai.passio.passiosdk:nutrition-ai:3.2.1-3'
}
implementation files('libs/passiolib-release.aar')
dependencies {
    // TensorFlow
    implementation 'org.tensorflow:tensorflow-lite:2.8.0'
    implementation 'org.tensorflow:tensorflow-lite-metadata:0.4.0'

    // CameraX
    def camerax_version = "1.3.0-alpha12"
    implementation "androidx.camera:camera-core:$camerax_version"
    implementation "androidx.camera:camera-camera2:$camerax_version"
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    implementation 'androidx.camera:camera-view:1.3.0-alpha02'
    implementation 'androidx.camera:camera-extensions:1.3.0-alpha02'

    // Barcode and OCR
    implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.0'
    implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.0'
}
android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
//npm.pkg.github.com/:_authToken=GITHUB_ACCESS_TOKEN
@passiolife:registry=https://npm.pkg.github.com
npm install @passiolife/nutritionai-react-native-sdk-v3
yarn add @passiolife/nutritionai-react-native-sdk-v3
cd ios; pod install
platform :ios, '13.0'
dependencies {
    // Add this line below for Passio SDK library
    implementation files("$rootDir/../node_modules/@passiolife/nutritionai-react-native-sdk-v3/android/libs/passiolib-release.aar")
    ...
}
buildscript {
    ext {
        ...
        minSdkVersion = 26
        ...
    }
}
 $ flutter pub add nutrition_ai
dependencies:
  nutrition_ai: ^3.0.3
import 'package:nutrition_ai/nutrition_ai.dart';
allprojects {
   repositories {
      ...
      flatDir {
         dirs project(':nutrition_ai').file('libs')
      }
   }
}
allprojects {
   repositories {
      ...
      flatDir {
         implementation (name: 'passiolib-release', ext: 'aar')
      }
   }
}
https://github.com/Passiolife/Passio-Nutrition-AI-iOS-SDK-Distribution
https://github.com/Passiolife/Passio-Nutrition-AI-iOS-SDK-Distribution/blob/main/PassioNutritionAISDK.xcframework.zip
.aar
releases page
TensorFlow
FirebaseVision
CameraX
Github Personal Access Token (classic)
with the following lines
pub.dev

User created foods and reports

There could be cases when a certain food is not present in Passio's Nutritional Database. The standard practice in that case would be to guide the users through a creation of user created food items. These items are usually only accessible for that particular user, but there is an API that can be used to notify Passio of such items.

Using the fun submitUserCreatedFoodItem, the app can notify Passio of user created foods.

PassioFoodItems submitted using this API go into our verification queue, and after their data has been verified by our nutrition data experts, they become available in the Nutritional Database.

On the other hand, there could be food items that have incorrect or outdated data. For these cases, the SDK provides an API to report such items, so that our data team can update them in the Nutritional Database.

To report incorrect data in a PassioFoodItem, reportFoodItem can be used. The refCode of the food item must by supplied.

UI Example

  1. Add a button when displaying the food item to report incorrect data.

  2. Show a form where users can add their feedback as well as provide the product code (barcode) of the item.

  3. Upon clicking the "Report" button, invoke the reportFoodItem function.

Report an issue pop-up
Food item header with report issue button

Run the demos first

Quick Start Demo

A fast and easy way to get started with the SDK is to test it inside of PassioSDKQuickStart Demo App included in this package. Here are the steps:

  1. Open the project in Xcode:

  2. Replace the SDK Key in the PassioQuickStartViewController.swift file with the license key you get from Passio

  3. Connect your iPhone and run

  4. Modify the app bundle from "com.PassioDemoApp.demo" to "com.yourcompany...."

  5. Run the demo app on your iPhone.

  6. For support, please contact support@passiolife.com

Testing the SDK During the Integration

You can test the SDK throughout your integration process and even before the integration. To test the SDK before the integration, we encourage you to use Passio's Reference App bundled with the SDK.

After you build the reference app or get the recognition to work inside of your app, you can test that the technology is working properly by pointing the phone at at parts of the image below to test recognition. You can test the following elements:

  • Food (non packaged)

  • Barcode

  • Packaged food

  • Nutrition facts

367KB
Test_Images_OCR_Barcode_NFs.pdf
pdf
Test Cases
Nutrition-AI SDK Quick Test Sheet

You can find a more extensive test of packaged and unpackaged foods below.

4MB
Barcodes_OCR_HNN.pdf
pdf

When testing the reference App, don't forget to add your key to the PassioExternalConnector class

class PassioExternalConnector 

var passioKeyForSDK: String {
    "YourPassioSDKKey"
}

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Nutrition Advisor

Starting from the SDK version 3.1.4 the NutritionAdvisor is only accessible using the NutritionAI Hub key. A key bought for the previous NutritionAI product won't work with this feature.

The Nutrition Advisor is an intelligent chatbot designed to offer personalised guidance on nutrition, diets, and healthy eating habits. Whether you're looking to explore a new diet, understand the nutritional value of certain foods, or find delicious and balanced recipes, the Nutrition Advisor is here to assist. Users can interact with the Nutrition Advisor through text messages, engaging in a conversational experience where they can ask questions about various diets like keto, vegan, Mediterranean, and more. The chatbot provides detailed insights into the benefits, potential drawbacks, and suitability of these diets based on individual goals and preferences.

As opposed to the rest of the SDK, the Nutrition Advisor has a different API entry point: NutritionAdvisor

Conversation flow

Use these steps to initialise the conversation and enable users to send messages:

  1. To start a conversation initConversation has to be invoked. The response will either indicate a success state, which means message can be sent to the advisor. In case of an error, an error message will detail the cause.

  2. Text messages are sent using the sendMessage API. The reply (if successful) will contain a PassioAdvisorResponse response. This object consist of:

  • threadId - identifies the conversation

  • messageId - identifies the specific message

  • markupContent - text response from the advisor denoted with markup tags. This can be used to style the text when rendering

  • rawConent - text response without the markup tags

  • tools - list of actions that can be executed by the advisor. Only should be used by the SDK

  • extracedIngredients - will contain a list of foods if the request to the advisor was an image recognition request or extract ingredients from text message request

  1. Image are sent to recognition using sendImage. As mentioned above, in the PassioAdvisorResponse object, the extractedIngredients property will hold the recognised foods.

NutritionAdvisor.shared.initConversation { advisorResult in
    switch advisorResult {
        case .success:
            print("The advisor can start acceping messages")
        case .failure(let error):
            print("The advisor can not start acceping messages. Error:- \(error.errorMessage)")
    }
}
let message = "Hi! I would like a recipe for a healthy burger"
NutritionAdvisor.shared.sendMessage(message: message) { advisorResponse in
    switch advisorResponse {
        case .success(let response): // Show text in a chat bubble
            print("Response:- \(response.rawContent)")
        case .failure(let error): // Handle error
            print("Chat Error:- \(error.errorMessage)")
    }
}
let image = Bundle.main.path(forResource: "image1", ofType: "png")
NutritionAdvisor.shared.sendImage(image: image) { advisorResponse in
    switch advisorResponse {
        case .success(let response): // Show list of foods
            print("Image Response:- \(response.extractedIngredients)")
        case .failure(let error): // Handle error
            print("Image Chat Error:- \(error.errorMessage)")
    }
}
NutritionAdvisor.instance.initConversation { result ->
    when (result) {
        is PassioResult.Success -> /* the advisor can start acceping messages */
        is PassioResult.Error -> /* handle conversation init error */
    }
}

NutritionAdvisor.instance.sendMessage("Hi! I would like a recipe for a healthy burger") { result ->
    when (result) {
        is PassioResult.Error -> {
            val errorMessage = result.message
            // Handle error
        }
        is PassioResult.Success -> {
            val text = result.value.rawContent
            // Show text in a chat bubble
        }
    }
}

val bitmap = loadBitmap()
NutritionAdvisor.instance.sendImage(bitmap) { result ->
    when (result) {
        is PassioResult.Error -> {
            val errorMessage = result.message
            // Handle error
        }
        is PassioResult.Success -> {
            val foods = result.value.extractedIngredients
            // Show list of foods
        }
    }
}
import { NutritionAdvisor } from '@passiolife/nutritionai-react-native-sdk-v3'

const [loadingState, setLoadingState] = useState<SDKStatus>('Init')
  useEffect(() => {
    async function configure() {
      try {
            NutritionAdvisor.initConversation()
              .then((item) => {
                setLoadingState(
                  item?.status === 'Success' ? 'Success' : 'Error'
                )
              })
              .catch((error) => {
                setLoadingState(error)
              })
      } catch (err) {
        console.error(`PassioSDK Error ${err}`)
        setLoadingState('Error')
      }
    }
    configure()
  }, [])


 const response = await NutritionAdvisor.sendMessage(message)
 switch (response?.status) {
          case 'Success':
            // get your response here {response.response}
            return
          case 'Error':
          // get your erorr response here {response.message}
            return
        }

const response = await NutritionAdvisor.sendImage(imageURI)
 switch (response?.status) {
          case 'Success':
            // get your response here {response.response}
            return
          case 'Error':
          // get your erorr response here {response.message}
            return
        }
final result = await NutritionAdvisor.instance.initConversation();
switch (result) {
  case Success():
    // Initialization succeeded, the advisor can start accepting messages
    break;
  case Error():
    // Handle the init error here
    break;
}

final result = await NutritionAdvisor.instance.sendMessage('Hi! I would like a recipe for a healthy burger');
switch (result) {
  case Success():
    // Parse the response from result.value
    break;
  case Error():
    // Handle the error that occurred while sending the message
    break;
}

Uint8List bytes = loadImage();
final result = await NutritionAdvisor.instance.sendImage(bytes);
switch (result) {
  case Success():
    // The food items are found in imageResult.value.extractedIngredients
    break;
  case Error():
    // Handle the error that occurred while sending the message
    break;
}

Extract ingredients

In the case that a PassioAdvisorResponse contains a tool called searchMatchIngredient, the Advisor has the ability to parse the text response and return the list of foods. If a message that has this tool is passed to the fetchIngredients API, the returned response will contain foods in the extracedIngredients field.

This feature is particularly useful when trying to log foods when directly communicating with the chatbot. A common use case is when user ask for a specific recipe. They can then initiate the extraction of foods from the message, and see their nutrition value and finally log the entire meal in one action.

UI Example

  1. Create a screen where the user can type messages and send images to the NutritionAdvisor. Upon initiating the screen, set up the conversation by calling initConversation.

  2. When the user types a message in the input text field and taps "Send", invoke the sendMessage function. Because this is an asynchronous function, add an animation to indicate this behaviour.

  3. If the response from the advisor contains a tool called searchMatchIngredient show an action button "Find foods" to initiate extraction of the foods. Invoke the fetchIngredients function using the response from the Advisor.

  4. Create a view where the user can see the extracted foods, their serving size and have the ability to log them.

  1. Add the ability for the user to select an image from the library of the phone. Once the image is obtained, invoke the sendImage to get the list of foods. If foods are recognised, the response will contain a list of foods, use the same result view to show them.

Adding Passio SDK into your project

Install Swift Package for Xcode 14.3 or newer

  1. Open your Xcode project.

  2. Go to File > Swift Packages > Add Package Dependency.

  3. In the "Add Package Dependency" dialog box, paste the URL: https://github.com/Passiolife/Passio-Nutrition-AI-iOS-SDK-Distribution

  4. Click "Next". Xcode will validate the package and its dependencies.

  5. In the next dialog box, you'll be asked to specify the version or branch you want to use. You can choose main for the latest version or specify a specific version or branch.

  6. After you've made your selection, click "Next".

  7. You'll then be prompted to select the targets in your project that should include the package. Check the boxes for the targets you want to include.

  8. Click "Finish" to add the package to your project.

  9. Xcode will download and add the PassioNutritionAISDK to your project. You can now import and start using the PassioNutritionAISDK.

Manual Installation For Xcode lower than 14.3

  • Download the "PassioNutritionAISDK.xcframework.zip" file from https://github.com/Passiolife/Passio-Nutrition-AI-iOS-SDK-Distribution/blob/main/PassioNutritionAISDK.xcframework.zip

  • Unzip it and drag and drop it into your project. Make sure to select "Copy items if needed".

  • In project "General" -> "Frameworks, Libraries and Embedded Content" Change to "Embed & Sign"

Edit your Info.plist

  • If opening from Xcode, right click and select 'open as source code'

  • To allow camera usage add:

`<key>>NSCameraUsageDescription</key><string>For real-time food recognition</string>`.

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com


Installation

Using Swift Package Manager (Recommended)

  • To add a Passio SDK as package dependency to your Xcode project, select File > Add Package Dependency and enter following repository URL https://github.com/Passiolife/Passio-Nutrition-AI-iOS-SDK-Distribution

  • Click "Next". Xcode will validate the package and its dependencies.

  • In the next dialog box, you'll be asked to specify the version or branch you want to use. Decide whether your project accepts updates to a package dependency "Up to the next major version" or "Up to the next minor version". To be more restrictive, select a specific version range or an exact version. Major versions tend to have more significant changes than minor versions, and may require you to modify your code when they update.

  • Xcode will download and add the PassioNutritionAISDK to your project. You can now import and start using the PassioNutritionAISDK.

Manual Installation (For Xcode lower than version 14.3)

  • Download the PassioNutritionAISDK.xcframework.zip file from https://github.com/Passiolife/Passio-Nutrition-AI-iOS-SDK-Distribution/blob/main/PassioNutritionAISDK.xcframework.zip

  • Unzip it and drag and drop it into your project. Make sure to select "Copy items if needed".

  • In project "General" -> "Frameworks, Libraries and Embedded Content" Change to "Embed & Sign"

Nutrition data

The main object containing the nutritional information is called PassioFoodItem. This object is fetched from Passio's backend using one of several fetchFoodItemFor... functions.

PassioFoodItem

The PassioFoodItem is a top level object that can represent a single food like a banana, or a complex recipe like caesar salad.

public struct PassioFoodItem: Codable {
    public let id: String
    public let name: String
    public let details: String
    public let iconId: String
    public let licenseCopy: String
    public let amount: PassioNutritionAISDK.PassioFoodAmount
    public let ingredients: [PassioNutritionAISDK.PassioIngredient]
    public let refCode: String?
}
data class PassioFoodItem(
    val id: String,
    val refCode: String,
    val name: String,
    val details: String,
    val iconId: String,
    val amount: PassioFoodAmount,
    val ingredients: List<PassioIngredient>,
)
export interface PassioFoodItem {
  refCode?: RefCode
  name: string
  details?: strin
  iconId: PassioID | string
  amount: PassioFoodAmount
  ingredients?: PassioIngredient[]
  weight: UnitMass
  isOpenFood?: boolean
  openFoodLicense?: string
  id: string
}
class PassioFoodItem {
  final PassioFoodAmount amount;
  final String details;
  final String iconId;
  final String id;
  final List<PassioIngredient> ingredients;
  final String name;
  final String refCode;
  final PassioID? scannedId;
 }
  • The name attribute is used to display the name of the food, while the details contains additional information like the brand of the food or the name of the food which was used to populate the nutritional data

  • iconId is used with the SDK function fetchIconFor to fetch a small image of the food

  • refCode is a parameter that can be used later on to retrieve the full food item again

  • amount details the available serving size as well as the currently selected serving quantity and unit, used when calculating nutrients

There are three functions that can be used to fetch nutrient information for the PassioFoodItem:

PassioNutritionAI.shared.fetchFoodItemFor(passioID: "VEG0025") { (foodItem) in
        
  if let foodItem {
  
      // Getting nutrients for the referent 100 grams
      let nutrientsRef = foodItem.nutrientsReference()
            
      // Getting nutrients for a specific weight
      let nutrients250g = foodItem.nutrients(weight: Measurement<UnitMass>(value: 250.0, unit: .grams))
      
      // Getting nutrients using the amount->selectedQuantity and
      // amount->selectedUnit
      let nutrientsServing = foodItem.nutrientsSelectedSize()
            
      // Weight of the ingredients
      let weight = foodItem.weight()
  }
}
PassioSDK.instance.("VEG0025") { foodItem ->
    // Getting nutrients for the referent 100 grams
    val nutrientsRef = foodItem!!.nutrientsReference()
    // Getting nutrients for a specific weight
    val nutrients250g = foodItem.nutrients(UnitMass(Grams, 250.0))
    // Getting nutrients using the amount->selectedQuantity and
    // amount->selectedUnit
    val nutrientsServing = foodItem.nutrientsSelectedSize()
    // Weight of the ingredients
val weight = foodItem.weight()
}
   const passioFoodItem = await PassioSDK.fetchFoodItemForPassioID("VEG0025")
  
   // Getting nutrients for the referent 100 grams
   const nutrients =  PassioSDK.getNutrientsReferenceOfPassioFoodItem(passioFoodItem)
   
   // Getting nutrients for a specific weight
   const nutrients =  PassioSDK.getNutrientsOfPassioFoodItem(passioFoodItem,{unit:'g',value:250})
  
   // Getting nutrients using the amount->selectedQuantity and
   // amount->selectedUnit
   const nutrients =  PassioSDK.getNutrientsSelectedSizeOfPassioFoodItem(passioFoodItem)
 
   // Weight of the ingredients
   const val weight = passioFoodItem.weight()

    final foodItem = await NutritionAI.instance.fetchFoodItemForPassioID('VEG0025');
    // Getting nutrients for the referent 100 grams
    final nutrientsRef = foodItem!.nutrientsReference();
    // Getting nutrients for a specific weight
    final nutrients250g =
        foodItem.nutrients(UnitMass(250.0, UnitMassType.grams));
    // Getting nutrients using the amount->selectedQuantity and
    // amount->selectedUnit
    final nutrientsServing = foodItem.nutrientsSelectedSize();
    // Weight of the ingredients
    final weight = foodItem.weight();

The weight function is used to fetch the sum mass of all of the ingredients.

PassioFoodAmount

This class holds the information about possible serving sizes and serving units, as well as the currently selected unit and quantity. Example of values for food item "apples"

  • serving units: small (2-3/4" dia), medium, large (3-1/4" dia), cup, oz, gram, ...

  • serving sizes: 1 small (2-3/4" dia), 2 cups, 100 grams, ...

  • selected quantity: 1

  • selected unit: medium

Serving units holds the list of all of the units that the system has the correct weight for. Serving sizes holds the list of predefines matches of a quantity and a serving unit. The selected quantity and unit are used to calculate nutrients on the PassioFoodItem level. While these values can be changed, the initial values are set by the SDK as a default portion.

PassioIngredient

Every PassioFoodItem has a list of ingredients. A food ingredient holds the nutritional information along with the portion size of each ingredient.

public struct PassioIngredient: Codable {
    public let id: String
    public let name: String
    public let iconId: String
    public let amount: PassioFoodAmount
    public let referenceNutrients: PassioNutrients
    public let metadata: PassioFoodMetadata
    public let refCode: String?
}
data class PassioIngredient(
    val id: String,
    val refCode: String,
    val name: String,
    val iconId: String,
    val amount: PassioFoodAmount,
    val referenceNutrients: PassioNutrients,
    val metadata: PassioFoodMetadata,
)
export interface PassioIngredient {
  name: string
  id: PassioID
  iconId: PassioID | string
  weight?: UnitMass
  referenceNutrients?: PassioNutrients
  nutrients?: PassioNutrients
  metadata?: PassioFoodMetadata
  amount?: PassioFoodAmount
}
class PassioIngredient {
  final PassioFoodAmount amount;
  final String iconId;
  final String id;
  final PassioFoodMetadata metadata;
  final String name;
  final String refCode;
  final PassioNutrients referenceNutrients;
}

Similarly to PassioFoodItem, the ingredient also has a name, iconId, refCode, and amount.

But, the most important part of the ingredient class is the PassioNutrients and it's methods. The referenceNutrients attribute holds the referent (in most cases 100 grams) macronutrients and micronutrients, and are used to calculate the resulting nutrient values when changing serving sizes of the whole PassioFoodItem.

Also, an ingredient has PassioMetadata field, storing the information on the origin of the nutritional data, barcode value, possible ingredients (if the PassioFoodItem is a packaged food), and a list of tags.

Fetching nutrients

PassioNutritionAI.shared.fetchFoodItemFor(passioID: "VEG0025") { (foodItem) in
  if let foodItem {
     let nutrients = foodItem.nutrientsReference()
     // Calories for 100 grams, values is in kCal
     let calories = nutrients.calories()?.value
     // Carbs for 100 grams, value is in grams
     let carbs = nutrients.carbs()?.value
     // Potassium for 100 grams, value is in milligrams
     let potassiumMilli = nutrients.potassium()?.value
     // Potassium for 100 grams, value is in grams
     let potassiumGrams = nutrients.potassium()?.gramsValue
  }
}
PassioSDK.instance.fetchFoodItemForPassioID("VEG0025") { foodItem ->
    val nutrients = foodItem!!.nutrientsReference()
    // Calories for 100 grams, values is in kCal
    val calories = nutrients.calories()?.value
    // Carbs for 100 grams, value is in grams
    val carbs = nutrients.carbs()?.value
    // Potassium for 100 grams, value is in milligrams
    val potassiumMilli = nutrients.potassium()?.value
    // Potassium for 100 grams, value is in grams
    val potassiumGrams = nutrients.potassium()?.gramsValue()
}
const passioFoodItem = await PassioSDKBridge.fetchFoodItemForPassioID("VEG0025")

   const nutrients =  PassioSDK.getNutrientsReferenceOfPassioFoodItem(passioFoodItem)
    // Calories for 100 grams, values is in kCal
     let calories = passioNutrients.calories?.value
     // Carbs for 100 grams, value is in grams
     let carbs = passioNutrients.carbs?.value
     // Potassium for 100 grams, value is in milligrams
     let potassiumMilli = passioNutrients.potassium?.value
    final foodItem = await NutritionAI.instance.fetchFoodItemForPassioID('VEG0025');
    final nutrients = foodItem!.nutrientsReference();
    // Calories for 100 grams, values is in kCal
    final calories = nutrients.calories?.value;
    // Carbs for 100 grams, value is in grams
    final carbs = nutrients.carbs?.value;
    // Potassium for 100 grams, value is in milligrams
    final potassiumMilli = nutrients.potassium?.value;
    // Potassium for 100 grams, value is in grams
    final potassiumGrams = nutrients.potassium?.gramsValue();

Single food item vs recipe

The PassioFoodItem class encompasses both single food items like an avocado as well as items with multiple ingredients such as homemade caprese salad. The difference between these two items is how is the PassioFoodAmount calculated from the top-level PassioFoodItem.

Homemade caprese salad:

  • weight(): 176.5 grams

  • amount->selectedQuantity = 1

  • amount->selectedUnit = "serving"

  • ingredients: 1. fresh sliced cheese, 84 grams, amount->selectedQuantity = 3, amount-> selectedUnit = "oz" 2. balsamic vinegar, 16 grams, amount->selectedQuantity = 1, amount-> selectedUnit = "tbsp" 3. fresh basil, 3 grams, amount->selectedQuantity = 6, amount-> selectedUnit = "leaves" 4. tomatoes, 60 grams, amount->selectedQuantity = 3, amount-> selectedUnit = "medium slice" 5. olive oil, 13.5 grams, amount->selectedQuantity = 1, amount-> selectedUnit = "tbsp"

Invoking the function foodItem.nutrientsServingSize() will fetch the nutrients for the whole recipe, calculated by the weight of the serving size "1 serving".

Avocado:

  • weight(): 201 grams

  • amount->selectedQuantity = 1

  • amount->selectedUnit = "avocado"

  • ingredients: 1. avodaco, raw, 201 grmas, amount->selectedQuantity = 1, amount->selectedUnit = "avocado"

A single food item will always have only one ingredient. Also, the amount object of the PassioFoodItem will be the same as the amount of the first ingredient. The ingredient is the item in the database used for nutritional data. For example, If the SDK recognises "milk" during the visual detection process, the default food item for "milk" is "milk, whole, 3.25% milkfat, without added vitamin a and vitamin d".

UI Example

  1. Fetch the PassioFoodItem

  2. Use the name, details and iconId to create the food header view

  3. Use the nutrientsSelectedSize->calories, carbs, protein and fat to show the marcos

  4. Use the amount class to create the serving size view

  5. Give the user the option to log the food

Suggestions and Meal Plans

Suggestions

The SDK provides the ability to fetch a predefined set of suggested foods for logging, depending on the current time of day

public enum PassioMealTime: String, Codable {
    case breakfast
    case lunch
    case dinner
    case snack
}

public func fetchSuggestions(mealTime: PassioMealTime,  completion: @escaping ([PassioFoodDataInfo]) -> Void)
enum class PassioMealTime(val mealName: String) {
    BREAKFAST("breakfast"),
    LUNCH("lunch"),
    DINNER("dinner"),
    SNACK("snack")
}

fun fetchSuggestions(
    mealTime: PassioMealTime,
    callback: (results: List<PassioFoodDataInfo>) -> Unit
)
export type PassioMealTime = 'breakfast' | 'lunch' | 'dinner' | 'snack'  

fetchSuggestions(
  mealTime: PassioMealTime
): Promise<PassioFoodDataInfo[] | null>
enum PassioMealTime {
  breakfast,
  lunch,
  dinner,
  snack,
}

Future<List<PassioFoodDataInfo>> fetchSuggestions(PassioMealTime mealTime)

Example of fetching suggestions for snack meal time:

banana, coffee, black tea, coke, cheddar cheese, chocolate chip cookies, potato chips, blueberry muffin, plain yogurt, strawberries, ...

UX Tip: Combine these predefined suggestions with user meal logs to provide a list of "quick suggestion" logs. This will enable users to log their meals even faster.

Quick suggestion UI example

Meal Plans

Meal Plans provide users suggestions which foods to consume depending on their dietary preference and time of day. The Meal Plan api is divided into two functions:

  • fetchMealPlans that is used to retrieve all of the possible meal plans. These meal plans usually correlate with a specific diet like "Keto Diet".

  • fetchMealPlanForDay is an api that is used to fetch specific foods recommended for a certain day of a target meal plan.

public func fetchMealPlans(completion: @escaping ([PassioMealPlan]) -> Void)

public func fetchMealPlanForDay(mealPlanLabel: String,
                                day: Int,
                                completion: @escaping ([PassioMealPlanItem]) -> Void)

public struct PassioMealPlan: Codable, Equatable {
    public var mealPlanLabel: String?
    public var mealPlanTitle: String?
    public var carbsTarget: Int?
    public var proteinTarget: Int?
    public var fatTarget: Int?
}

public struct PassioMealPlanItem {
    public var dayNumber: Int?
    public var dayTitle: String?
    public var mealTime: PassioMealTime?
    public var meal: PassioFoodDataInfo?
}
fun fetchMealPlans(callback: (result: List<PassioMealPlan>) -> Unit)

fun fetchMealPlanForDay(
    mealPlanLabel: String,
    day: Int,
    callback: (result: List<PassioMealPlanItem>) -> Unit
)

data class PassioMealPlan(
    val mealPlanTitle: String,
    val mealPlanLabel: String,
    val carbTarget: Int,
    val proteinTarget: Int,
    val fatTarget: Int
)

data class PassioMealPlanItem(
    val dayNumber: Int,
    val dayTitle: String,
    val mealTime: PassioMealTime,
    val meal: PassioFoodDataInfo
)
fetchMealPlans(): Promise<PassioMealPlan[] | null>

fetchMealPlanForDay(
  mealPlanLabel: string,
  day: number
): Promise<PassioMealPlanItem[] | null>

export interface PassioMealPlan {
  carbsTarget?: number
  fatTarget?: number
  mealPlanLabel: string
  mealPlanTitle: string
  proteinTarget?: number
}

export interface PassioMealPlanItem {
  dayNumber: number
  dayTitle: string
  mealTime: PassioMealTime
  meal: PassioFoodDataInfo
}
Future<List<PassioMealPlan>> fetchMealPlans()

Future<List<PassioMealPlanItem>> fetchMealPlanForDay(String mealPlanLabel, int day)

class PassioMealPlan {
  final int carbTarget;
  final int fatTarget;
  final String mealPlanLabel;
  final String mealPlanTitle;
  final int proteinTarget;
}

class PassioMealPlanItem {
  final int dayNumber;
  final String dayTitle;
  final PassioFoodDataInfo meal;
  final PassioMealTime mealTime;
}

Some of the available meal plans:

Heart Healthy Diet, Ketogenic Diet, Managing Obesity, Managing Type 2 Diabetes, Low FODMAP, Healthy Kidney Diet, Balanced Diet, DASH Diet, PCOS Diet, Mediterranean Diet

Example of meal plan UI

SDK API

Copyright 2022 Passio Inc

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Food Details

Fetching PassioFoodItem from PassioAdvisorFoodInfo

To display the food details, PassioFoodItem is required.

  • Fetch PassioFoodItem from PassioFoodDataInfo

Showing the Food Detail and Macro-Nutrients

We build a UI component to display macro-nutrients like calories, carbohydrates, protein, and fat. We get these macro nutrients for the selected size of the food.

Edit Serving Size

The setSelectedQuantity() function handles changes to the serving quantity. It calculates the corresponding weight based on the selected serving unit and updates the passioFoodItem object accordingly.

For example, once user changes the quantity from a textfield or from a slider, we can call setSelectedQuantity() function on passioFoodItem.

Edit Serving Unit

The setSelectedUnit() function handles changes to the serving unit.

For example, once user changes the unit (e.g. Packet, Cup, Gram) from a list of units, we can call setSelectedQuantity() function on passioFoodItem.

To get list of all units for a food item:

Include the library

The Passio Android SDK is shipped in the form of an file.

Download the .aar from our distribution repository

  • Visit the of the Android-Passio-SDK-Distribution GitHub repository. Download the passiolib-release.aar file from the latest release.

  • If you don't have access to the repository please contact our support team at support@passiolife.com

Include the .aar in your Android Studio project

To include the .aar file into your project go to the module that will contain the food recognition feature. Place the .aar file in the libs folder of the module. If the libs folder is missing, create one. Reference the .aar file in the build.gradle file of the implementing module.

Sync Project with Gradle Files and be sure that you can reference the PassioSDK class within your code.

Dependencies

Passio Android SDK is powered by and with the camera being managed by . Add the dependencies to these three projects by adding these lines to the module's build.gradle file.

In order for the SDK to work add the following lines to the android section of the module's build.gradle file.

Running Sync Project with Gradle Files will enable access to the SDK.

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Recognise food using image

The Quick start project demonstrate recognising food items from an image, either by taking a photo using the camera or selecting an image from the gallery.

Include the Camera and Gallery usage keys

Include the and keys in your app’s Info.plist file. It tells the user why the app is requesting access to the device’s camera and photo library.

Ask User for Camera Permission

  • Once the Passio SDK status is isReadyForDetection, we can start capturing image or picking it from gallery. Implement following code for asking user for Camera permission. Once user grants the permission, we can configure the camera for capturing images.

Take a Picture

  • Above the viewDidLoad method, where you create variables you want to be accessible anywhere in the ViewController file, create the following Instance Variables

  • Set up the Camera session and configure Input and Output

  • Configure the Live Preview and start the Session on the background thread

  • On click of capture button, provide a setting and a deleget to deliver the capturedPhoto to. This delegate will be this ViewController so we also need to conform to the protocol AVCapturePhotoCaptureDelegate

  • The AVCapturePhotoOutput will deliver the captured photo to the assigned delegate which is our current ViewController by a delegate method

Pick Picture from Gallery

  • Ask for Photo Gallery permission:

  • Present the PHPickerViewController with configuration

  • Implement the delegate for getting user picked images

Recognise the picture using SDK

The following function is responsible for sending a captured image to the Passio SDK for remote image recognition . It sends image to a remote server for recognition, and after receiving the response it updates the table view which will present a list of recognised food.

Display the recognition results in the UI

For each food recognised, we can create a table view cell e.g. FoodListCell and pass the PassioAdvisorFoodInfo object

Camera

To start using the camera in your Activity/Fragment, implement the PassioCameraViewProvider interface. By implementing this interface the SDK will use that component as the lifecycle owner of the camera (when that component calls onPause() the camera will stop) and also will provide the Context in order for the camera to start. The component implementing the interface must have a in its view hierarchy.

Start by adding the PreviewView to your view hierarchy. Go to your layout.xml and add the following.

The SDK requires the device's Camera to work. To ensure that the Camera is working and sending frames to analyze, you need the user's permission to access the camera. As of now, only the back camera is supported by the SDK. There are two ways to implement Camera usage along with the permission request. Review both of these strategies and choose what best suits your code.

Using the PassioCameraFragment

PassioCameraFragment is an abstract class that handles Camera permission at runtime as well as starting the Camera process of the SDK. To use the PassioCameraFragment simply extend it in your own fragment and supply the PreviewView that has been added to the view hierarchy in the previous step.

Using the PassioCameraViewProvider

This approach is more manual but gives you more flexibility. You need to implement the PassioCameraViewProvider interface and supply the needed LifecycleOwner and the PreviewView added in the initial step.

Acquire the user's permission to use the Camera with these

Finally, after the user has granted permission to use the camera, start the SDK camera

Run the code to make sure that the preview is working

The recognition is still not set up at this point so you still won't be able to recognize foods. This step ensures that the camera is properly feeding frames to the SDK system.

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Configure the SDK (UIKit)

  • To configure the SDK using SDK key, we need to provide a SDK license key. Obtain your Passio key from the Passio developer portal.

  • To configure the SDK using Proxy URL, the SDK key is not required. The SDK will be on remoteOnly mode in that case. You need to provide following things when configuring the SDK using Proxy URL:

    • Proxy URL - Base url of the target proxy endpoint

    • Proxy headers - Required headers to all of the requests

  • Create a new or open your existing project in Xcode and import the framework inside the View Controller

  • Define reference of SDK inside your View Controller

  • Configure the SDK using PassioConfiguration shown in below code. Call configurePassioSDK() when you need to configure SDK e.g. inside viewDidLoad() function.

The models required for the SDK to run local recognition can take some time for the first download. If the local recognition is not needed, setting the remoteOnly to true on the PassioConfiguration process will skip the download of the models. Remote recognition functions like recognizeImageRemote or searchForFood will still work.

When the SDK is configured without SDK key (using Proxy URL), the SDK will be remoteOnly.

  • Implement the PassioStatusDelegate protocol to receive configuration status updates. While the files are being downloaded, we can show in UI how many files are remanning for the download.

  • Update the UI based on the configuration status. The isReadyForDetection status indicated that the SDK is setup successfully and it is ready to use. The implementation of self.askForCapturePermission() method will be covered in the next section

SDK API

import ARKit
import AVFoundation
import Accelerate
import Combine
import CommonCrypto
import CoreML
import CoreMedia
import CoreMotion
import Foundation
import Metal
import MetalPerformanceShaders
import SQLite3
import UIKit
import VideoToolbox
import Vision
import _Concurrency
import simd

/// Returning all information of Amount estimation and directions how to move
/// the device for better estimation
public protocol AmountEstimate {

    /// Scanned Volume estimate in ml
    var volumeEstimate: Double? { get }

    /// Scanned Amount in grams
    var weightEstimate: Double? { get }

    /// The quality of the estimate (eventually for feedback to the user or
    /// SDK-based app developer)
    var estimationQuality: PassioNutritionAISDK.EstimationQuality? { get }

    /// Hints how to move the device for better estimation.
    var moveDevice: PassioNutritionAISDK.MoveDirection? { get }

    /// The Angle in radians from the perpendicular surface.
    var viewingAngle: Double? { get }
}

/// Barcode (typealias String) is the string representation of the barcode id
public typealias Barcode = String

/// The BarcodeCandidate protocol returns the barcode candidate.
public protocol BarcodeCandidate {

    /// Passio ID recognized by the model
    var value: String { get }

    /// boundingBox CGRect representing the predicted bounding box in 
    /// normalized coordinates.
    var boundingBox: CGRect { get }
}

/// Implement the BarcodeDetectionDelegate protocol to receive delegate methods
/// from the object detection. Barcode detection is optional and initiated when
/// starting Object Detection or Classification.
public protocol BarcodeDetectionDelegate : AnyObject {

    /// Called when a barcode is detected.
    /// - Parameter barcodes: Array of BarcodeCandidate
    func barcodeResult(barcodes: [PassioNutritionAISDK.BarcodeCandidate])
}

/// The ClassificationCandidate protocol returns the classification candidate
/// result delegate.
public protocol ClassificationCandidate {

    /// PassioID recognized by the MLModel
    var passioID: PassioNutritionAISDK.PassioID { get }

    /// Confidence (0.0 to 1.0) of the associated PassioID recognized by the
    /// MLModel
    var confidence: Double { get }
}

/// The visual food candidates
public protocol DetectedCandidate {

    /// PassioID recognized by the MLModel
    var passioID: PassioNutritionAISDK.PassioID { get }

    /// Confidence (0.0 to 1.0) of the associated PassioID recognized by the
    /// MLModel
    var confidence: Double { get }

    /// boundingBox CGRect representing the predicted bounding box in
    /// normalized coordinates.
    var boundingBox: CGRect { get }

    /// The image that the detection was performed upon
    var croppedImage: UIImage? { get }

    /// Scanned AmountEstimate
    var amountEstimate: PassioNutritionAISDK.AmountEstimate? { get }
}

public enum EstimationQuality : String {

    case good

    case fair

    case poor

    case noEstimation

    /// Creates a new instance with the specified raw value.
    ///
    /// If there is no value of the type that corresponds with the specified raw
    /// value, this initializer returns `nil`. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     print(PaperSize(rawValue: "Legal"))
    ///     // Prints "Optional("PaperSize.Legal")"
    ///
    ///     print(PaperSize(rawValue: "Tabloid"))
    ///     // Prints "nil"
    ///
    /// - Parameter rawValue: The raw value to use for the new instance.
    public init?(rawValue: String)

    /// The raw type that can be used to represent all values of the conforming
    /// type.
    ///
    /// Every distinct value of the conforming type has a corresponding unique
    /// value of the `RawValue` type, but there may be values of the `RawValue`
    /// type that don't have a corresponding value of the conforming type.
    public typealias RawValue = String

    /// The corresponding value of the raw type.
    ///
    /// A new instance initialized with `rawValue` will be equivalent to this
    /// instance. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     let selectedSize = PaperSize.Letter
    ///     print(selectedSize.rawValue)
    ///     // Prints "Letter"
    ///
    ///     print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!)
    ///     // Prints "true"
    public var rawValue: String { get }
}

extension EstimationQuality : Equatable {
}

extension EstimationQuality : Hashable {
}

extension EstimationQuality : RawRepresentable {
}

public typealias FileLocalURL = URL

public typealias FileName = String

/// The FoodCandidates protocol returns all four potential candidates. If
/// FoodDetectionConfiguration is not set only visual candidates will be
/// returned.
public protocol FoodCandidates {

    /// The visual candidates returned from the recognition
    var detectedCandidates: [PassioNutritionAISDK.DetectedCandidate] { get }

    /// The Barcode candidates if available
    var barcodeCandidates: [PassioNutritionAISDK.BarcodeCandidate]? { get }

    /// The packaged food candidates if available
    var packagedFoodCandidates: [PassioNutritionAISDK.PackagedFoodCandidate]? { get }
}

/// FoodDetectionConfiguration is needed to configure food detection
public struct FoodDetectionConfiguration {

    /// Only set to false if you don't want to use the ML Models to detect food.
    public var detectVisual: Bool

    /// Select the right Volume Detection Mode
    public var volumeDetectionMode: PassioNutritionAISDK.VolumeDetectionMode

    /// Set to true for detecting barcodes
    public var detectBarcodes: Bool

    /// Set to true for detecting packaged foods
    public var detectPackagedFood: Bool

    /// Detect and decipher the Nutrition Facts label
    public var detectNutritionFacts: Bool

    /// Change this if you would like to control the resolution of the image
    /// you get back in the delegate. Changing this value will not change the
    /// visual recognition results.
    public var sessionPreset: AVCaptureSession.Preset

    /// The frequency of sending images for the recognition models. The default
    /// is set to two per second. Increasing this value will require more
    /// resources from the device.
    public var framesPerSecond: PassioNutritionAISDK.PassioNutritionAI.FramesPerSecond

    public init(
        detectVisual: Bool = true, 
        volumeDetectionMode: PassioNutritionAISDK.VolumeDetectionMode = .none, 
        detectBarcodes: Bool = false, 
        detectPackagedFood: Bool = false, 
        nutritionFacts: Bool = false
    )
}

/// Implement the FoodRecognitionDelegate protocol to receive delegate methods
/// from the FoodRecognition
public protocol FoodRecognitionDelegate : AnyObject {

    /// Delegate function for food recognition
    /// - Parameters:
    ///   - candidates: Food candidates
    ///   - image: Image used for detection
    func recognitionResults(
        candidates: PassioNutritionAISDK.FoodCandidates?, 
        image: UIImage?, 
        nutritionFacts: PassioNutritionAISDK.PassioNutritionFacts?
    )
}

public enum IconSize : String {

    case px90

    case px180

    case px360

    /// Creates a new instance with the specified raw value.
    ///
    /// If there is no value of the type that corresponds with the specified raw
    /// value, this initializer returns `nil`. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     print(PaperSize(rawValue: "Legal"))
    ///     // Prints "Optional("PaperSize.Legal")"
    ///
    ///     print(PaperSize(rawValue: "Tabloid"))
    ///     // Prints "nil"
    ///
    /// - Parameter rawValue: The raw value to use for the new instance.
    public init?(rawValue: String)

    /// The raw type that can be used to represent all values of the conforming
    /// type.
    ///
    /// Every distinct value of the conforming type has a corresponding unique
    /// value of the `RawValue` type, but there may be values of the `RawValue`
    /// type that don't have a corresponding value of the conforming type.
    public typealias RawValue = String

    /// The corresponding value of the raw type.
    ///
    /// A new instance initialized with `rawValue` will be equivalent to this
    /// instance. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     let selectedSize = PaperSize.Letter
    ///     print(selectedSize.rawValue)
    ///     // Prints "Letter"
    ///
    ///     print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!)
    ///     // Prints "true"
    public var rawValue: String { get }
}

extension IconSize : Equatable {
}

extension IconSize : Hashable {
}

extension IconSize : RawRepresentable {
}

public struct KetoFood {

    public let passioID: String

    public var color: UIColor

    public static var ketoFoods: [PassioNutritionAISDK.KetoFood] { get }
}

public struct KetoMealPlan {

    public let mealTime: PassioNutritionAISDK.MealTime

    public let ketoFood: [PassioNutritionAISDK.KetoFood]

    public static var mealPlans: [PassioNutritionAISDK.KetoMealPlan] { get }
}

public struct MealSuggestion {

    public let mealTime: PassioNutritionAISDK.MealTime

    public let ketoFoods: [PassioNutritionAISDK.KetoFood]

    public static var mealSuggestions: [PassioNutritionAISDK.MealSuggestion] { get }
}

public enum MealTime {

    case breakfast

    case lunch

    case dinner

    case snack

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.MealTime, 
        b: PassioNutritionAISDK.MealTime
    ) -> Bool

    /// Hashes the essential components of this value by feeding them into the
    /// given hasher.
    ///
    /// Implement this method to conform to the `Hashable` protocol. The
    /// components used for hashing must be the same as the components compared
    /// in your type's `==` operator implementation. Call `hasher.combine(_:)`
    /// with each of these components.
    ///
    /// - Important: Never call `finalize()` on `hasher`. Doing so may become a
    ///   compile-time error in the future.
    ///
    /// - Parameter hasher: The hasher to use when combining the components
    ///   of this instance.
    public func hash(into hasher: inout Hasher)

    /// The hash value.
    ///
    /// Hash values are not guaranteed to be equal across different executions
    /// of your program. Do not save hash values to use during a future execution.
    ///
    /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
    ///   conform to `Hashable`, implement the `hash(into:)` requirement instead.
    public var hashValue: Int { get }
}

extension MealTime : Equatable {
}

extension MealTime : Hashable {
}

public struct MeasurementIU {

    public var value: Double

    public let unit: String
}

public enum MoveDirection : String {

    case away

    case ok

    case up

    case down

    case around

    /// Creates a new instance with the specified raw value.
    ///
    /// If there is no value of the type that corresponds with the specified raw
    /// value, this initializer returns `nil`. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     print(PaperSize(rawValue: "Legal"))
    ///     // Prints "Optional("PaperSize.Legal")"
    ///
    ///     print(PaperSize(rawValue: "Tabloid"))
    ///     // Prints "nil"
    ///
    /// - Parameter rawValue: The raw value to use for the new instance.
    public init?(rawValue: String)

    /// The raw type that can be used to represent all values of the conforming
    /// type.
    ///
    /// Every distinct value of the conforming type has a corresponding unique
    /// value of the `RawValue` type, but there may be values of the `RawValue`
    /// type that don't have a corresponding value of the conforming type.
    public typealias RawValue = String

    /// The corresponding value of the raw type.
    ///
    /// A new instance initialized with `rawValue` will be equivalent to this
    /// instance. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     let selectedSize = PaperSize.Letter
    ///     print(selectedSize.rawValue)
    ///     // Prints "Letter"
    ///
    ///     print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!)
    ///     // Prints "true"
    public var rawValue: String { get }
}

extension MoveDirection : Equatable {
}

extension MoveDirection : Hashable {
}

extension MoveDirection : RawRepresentable {
}

/// The ObjectDetectionCandidate protocol returns the object detection result
public protocol ObjectDetectionCandidate : PassioNutritionAISDK.ClassificationCandidate {

    /// boundingBox CGRect representing the predicted bounding box in
    /// normalized coordinates.
    var boundingBox: CGRect { get }
}

public protocol PackagedFoodCandidate {

    var packagedFoodCode: PassioNutritionAISDK.PackagedFoodCode { get }

    var confidence: Double { get }
}

/// packagedFoodCode (typealias String) is the string representation of
/// the PackagedFoodCode id
public typealias PackagedFoodCode = String

/// PassioAlternative is an alternative to a food from the Database
public struct PassioAlternative : Codable, Equatable, Hashable {

    public let passioID: PassioNutritionAISDK.PassioID

    public var name: String { get }

    public let quantity: Double?

    public let unitName: String?

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioAlternative, 
        b: PassioNutritionAISDK.PassioAlternative
    ) -> Bool

    /// Hashes the essential components of this value by feeding them into the
    /// given hasher.
    ///
    /// Implement this method to conform to the `Hashable` protocol. The
    /// components used for hashing must be the same as the components compared
    /// in your type's `==` operator implementation. Call `hasher.combine(_:)`
    /// with each of these components.
    ///
    /// - Important: Never call `finalize()` on `hasher`. Doing so may become a
    ///   compile-time error in the future.
    ///
    /// - Parameter hasher: The hasher to use when combining the components
    ///   of this instance.
    public func hash(into hasher: inout Hasher)

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// The hash value.
    ///
    /// Hash values are not guaranteed to be equal across different executions
    /// of your program. Do not save hash values to use during a future execution.
    ///
    /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
    ///   conform to `Hashable`, implement the `hash(into:)` requirement instead.
    public var hashValue: Int { get }

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

/// PassioConfiguration is needed configure the SDK with the following options:
public struct PassioConfiguration : Equatable {

    /// This is the key you have received from Passio. A valid key must be used.
    public var key: String

    /// If you have chosen to remove the files from the SDK and provide the SDK
    /// different URLs for the files please use this variable.
    public var filesLocalURLs: [PassioNutritionAISDK.FileLocalURL]?

    /// If you set this option to true, the SDK will download the models
    /// relevant for this version from Passio's bucket.
    public var sdkDownloadsModels: Bool

    /// If you have problems configuring the SDK, set debugMode = 1 to get more
    /// debugging information.
    public var debugMode: Int

    /// If you set allowInternetConnection = false without working with Passio
    /// the SDK will not work. The SDK will not connect to the internet for key
    /// validations, barcode data and packaged food data.
    public var allowInternetConnection: Bool

    /// Only use latest models. Don't use models previously installed.
    public var onlyUseLatestModels: Bool

    public init(key: String)

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioConfiguration, 
        b: PassioNutritionAISDK.PassioConfiguration
    ) -> Bool
}

/// PassioFoodItemData contains all the information pertaining to a food item.
public struct PassioFoodItemData : Equatable, Codable {

    public var passioID: PassioNutritionAISDK.PassioID { get }

    public var name: String { get }

    public var selectedQuantity: Double { get }

    public var selectedUnit: String { get }

    public var entityType: PassioNutritionAISDK.PassioIDEntityType { get }

    public var servingUnits: [PassioNutritionAISDK.PassioServingUnit] { get }

    public var servingSizes: [PassioNutritionAISDK.PassioServingSize] { get }

    public var ingredientsDescription: String? { get }

    public var barcode: PassioNutritionAISDK.Barcode? { get }

    public var foodOrigins: [PassioNutritionAISDK.PassioFoodOrigin]? { get }

    public var isOpenFood: Bool { get }

    public var computedWeight: Measurement<UnitMass> { get }

    public var parents: [PassioNutritionAISDK.PassioAlternative]? { get }

    public var parentsPassioID: [PassioNutritionAISDK.PassioID]? { get }

    public var children: [PassioNutritionAISDK.PassioAlternative]? { get }

    public var childrenPassioID: [PassioNutritionAISDK.PassioID]? { get }

    public var siblings: [PassioNutritionAISDK.PassioAlternative]? { get }

    public var siblingsPassioID: [PassioNutritionAISDK.PassioID]? { get }

    public var totalCalories: Measurement<UnitEnergy>? { get }

    public var totalCarbs: Measurement<UnitMass>? { get }

    public var totalFat: Measurement<UnitMass>? { get }

    public var totalProteins: Measurement<UnitMass>? { get }

    public var totalSaturatedFat: Measurement<UnitMass>? { get }

    public var totalTransFat: Measurement<UnitMass>? { get }

    public var totalMonounsaturatedFat: Measurement<UnitMass>? { get }

    public var totalPolyunsaturatedFat: Measurement<UnitMass>? { get }

    public var totalCholesterol: Measurement<UnitMass>? { get }

    public var totalSodium: Measurement<UnitMass>? { get }

    public var totalFibers: Measurement<UnitMass>? { get }

    public var totalSugars: Measurement<UnitMass>? { get }

    public var totalSugarsAdded: Measurement<UnitMass>? { get }

    public var totalVitaminD: Measurement<UnitMass>? { get }

    public var totalCalcium: Measurement<UnitMass>? { get }

    public var totalIron: Measurement<UnitMass>? { get }

    public var totalPotassium: Measurement<UnitMass>? { get }

    public var totalVitaminA: PassioNutritionAISDK.MeasurementIU? { get }

    public var totalVitaminC: Measurement<UnitMass>? { get }

    public var totalAlcohol: Measurement<UnitMass>? { get }

    public var totalSugarAlcohol: Measurement<UnitMass>? { get }

    public var totalVitaminB12Added: Measurement<UnitMass>? { get }

    public var totalVitaminB12: Measurement<UnitMass>? { get }

    public var totalVitaminB6: Measurement<UnitMass>? { get }

    public var totalVitaminE: Measurement<UnitMass>? { get }

    public var totalVitaminEAdded: Measurement<UnitMass>? { get }

    public var totalMagnesium: Measurement<UnitMass>? { get }

    public var totalPhosphorus: Measurement<UnitMass>? { get }

    public var totalIodine: Measurement<UnitMass>? { get }

    public var summary: String { get }

    public mutating func setFoodItemDataServingSize(
        unit: String, 
        quantity: Double
    ) -> Bool

    public mutating func setServingUnitKeepWeight(unitName: String) -> Bool

    public init(upcProduct: PassioNutritionAISDK.UPCProduct) throws

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioFoodItemData, 
        b: PassioNutritionAISDK.PassioFoodItemData
    ) -> Bool

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

public enum PassioFoodItemDataError : LocalizedError {

    case noUnitMassInServingSizes

    /// A localized message describing what error occurred.
    public var errorDescription: String? { get }

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioFoodItemDataError, 
        b: PassioNutritionAISDK.PassioFoodItemDataError
    ) -> Bool

    /// Hashes the essential components of this value by feeding them into the
    /// given hasher.
    ///
    /// Implement this method to conform to the `Hashable` protocol. The
    /// components used for hashing must be the same as the components compared
    /// in your type's `==` operator implementation. Call `hasher.combine(_:)`
    /// with each of these components.
    ///
    /// - Important: Never call `finalize()` on `hasher`. Doing so may become a
    ///   compile-time error in the future.
    ///
    /// - Parameter hasher: The hasher to use when combining the components
    ///   of this instance.
    public func hash(into hasher: inout Hasher)

    /// The hash value.
    ///
    /// Hash values are not guaranteed to be equal across different executions
    /// of your program. Do not save hash values to use during a future execution.
    ///
    /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
    ///   conform to `Hashable`, implement the `hash(into:)` requirement instead.
    public var hashValue: Int { get }
}

extension PassioFoodItemDataError : Equatable {
}

extension PassioFoodItemDataError : Hashable {
}

public struct PassioFoodOrigin : Codable, Equatable {

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioFoodOrigin, 
        b: PassioNutritionAISDK.PassioFoodOrigin
    ) -> Bool

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

/// PassioFoodRecipe contains the list of ingredients and their amounts 
public struct PassioFoodRecipe : Equatable, Codable {

    public var passioID: PassioNutritionAISDK.PassioID { get }

    public var name: String { get }

    public var servingSizes: [PassioNutritionAISDK.PassioServingSize] { get }

    public var servingUnits: [PassioNutritionAISDK.PassioServingUnit] { get }

    public var selectedUnit: String { get }

    public var selectedQuantity: Double { get }

    public var isOpenFood: Bool { get }

    public var foodItems: [PassioNutritionAISDK.PassioFoodItemData] { get }

    public var computedWeight: Measurement<UnitMass> { get }

    public init(
        passioID: PassioNutritionAISDK.PassioID, 
        name: String, 
        foodItems: [PassioNutritionAISDK.PassioFoodItemData], 
        selectedUnit: String, 
        selectedQuantity: Double, 
        servingSizes: [PassioNutritionAISDK.PassioServingSize], 
        servingUnits: [PassioNutritionAISDK.PassioServingUnit]
    )

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioFoodRecipe, 
        b: PassioNutritionAISDK.PassioFoodRecipe
    ) -> Bool

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

/// PassioID (typealias String) is used throughout the SDK, food and other
/// objects are identified by PassioID. All attributes (names, nutrition etc..)
/// are referred to by PassioID.
public typealias PassioID = String

public struct PassioIDAndName {

    public let passioID: PassioNutritionAISDK.PassioID

    public let name: String

    public init(passioID: PassioNutritionAISDK.PassioID, name: String)
}

/// PassioIDAttributes contains all the attributes for a PassioID.
public struct PassioIDAttributes : Equatable, Codable {

    public var passioID: PassioNutritionAISDK.PassioID { get }

    public var name: String { get }

    public var entityType: PassioNutritionAISDK.PassioIDEntityType { get }

    public var parents: [PassioNutritionAISDK.PassioAlternative]? { get }

    public var children: [PassioNutritionAISDK.PassioAlternative]? { get }

    public var siblings: [PassioNutritionAISDK.PassioAlternative]? { get }

    public var passioFoodItemData: PassioNutritionAISDK.PassioFoodItemData? { get }

    public var recipe: PassioNutritionAISDK.PassioFoodRecipe? { get }

    public var isOpenFood: Bool { get }

    public init(
        passioID: PassioNutritionAISDK.PassioID, 
        name: String, 
        foodItemDataForDefault: PassioNutritionAISDK.PassioFoodItemData?, 
        entityType: PassioNutritionAISDK.PassioIDEntityType = .barcode
    )

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioIDAttributes, 
        b: PassioNutritionAISDK.PassioIDAttributes
    ) -> Bool

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

/// PassioIDEntityType is The entity Type of the PassioID Attributes.
public enum PassioIDEntityType : String, CaseIterable, Codable {

    case group

    case item

    case recipe

    case barcode

    case packagedFoodCode

    case favorite

    case nutritionFacts

    /// Creates a new instance with the specified raw value.
    ///
    /// If there is no value of the type that corresponds with the specified raw
    /// value, this initializer returns `nil`. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     print(PaperSize(rawValue: "Legal"))
    ///     // Prints "Optional("PaperSize.Legal")"
    ///
    ///     print(PaperSize(rawValue: "Tabloid"))
    ///     // Prints "nil"
    ///
    /// - Parameter rawValue: The raw value to use for the new instance.
    public init?(rawValue: String)

    /// A type that can represent a collection of all values of this type.
    public typealias AllCases = [PassioNutritionAISDK.PassioIDEntityType]

    /// The raw type that can be used to represent all values of the conforming
    /// type.
    ///
    /// Every distinct value of the conforming type has a corresponding unique
    /// value of the `RawValue` type, but there may be values of the `RawValue`
    /// type that don't have a corresponding value of the conforming type.
    public typealias RawValue = String

    /// A collection of all values of this type.
    public static var allCases: [PassioNutritionAISDK.PassioIDEntityType] { get }

    /// The corresponding value of the raw type.
    ///
    /// A new instance initialized with `rawValue` will be equivalent to this
    /// instance. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     let selectedSize = PaperSize.Letter
    ///     print(selectedSize.rawValue)
    ///     // Prints "Letter"
    ///
    ///     print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!)
    ///     // Prints "true"
    public var rawValue: String { get }
}

extension PassioIDEntityType : Equatable {
}

extension PassioIDEntityType : Hashable {
}

extension PassioIDEntityType : RawRepresentable {
}

/// PassioMode will report the mode the SDK is currently in.
public enum PassioMode {

    case notReady

    case isBeingConfigured

    case isDownloadingModels

    case isReadyForDetection

    case failedToConfigure

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioMode, 
        b: PassioNutritionAISDK.PassioMode
    ) -> Bool

    /// Hashes the essential components of this value by feeding them into the
    /// given hasher.
    ///
    /// Implement this method to conform to the `Hashable` protocol. The
    /// components used for hashing must be the same as the components compared
    /// in your type's `==` operator implementation. Call `hasher.combine(_:)`
    /// with each of these components.
    ///
    /// - Important: Never call `finalize()` on `hasher`. Doing so may become a
    ///   compile-time error in the future.
    ///
    /// - Parameter hasher: The hasher to use when combining the components
    ///   of this instance.
    public func hash(into hasher: inout Hasher)

    /// The hash value.
    ///
    /// Hash values are not guaranteed to be equal across different executions
    /// of your program. Do not save hash values to use during a future execution.
    ///
    /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
    ///   conform to `Hashable`, implement the `hash(into:)` requirement instead.
    public var hashValue: Int { get }
}

extension PassioMode : Equatable {
}

extension PassioMode : Hashable {
}

/// Passio SDK - Copyright © 2022 Passio Inc. All rights reserved.
public class PassioNutritionAI {

    /// The latest models and files version the SDK will request.
    final public let filesVersion: Int

    /// Shared Instance
    public class var shared: PassioNutritionAISDK.PassioNutritionAI { get }

    public var requestCompressedFiles: Bool

    /// Get the PassioStatus directly or implement the PassioStatusDelegate
    /// for updates.
    public var status: PassioNutritionAISDK.PassioStatus { get }

    /// Delegate to track PassioStatus changes. You will get the same status
    /// via the configure function.
    weak public var statusDelegate: PassioNutritionAISDK.PassioStatusDelegate?

    /// Available frames per second. The default is set to 2 fps.
    public enum FramesPerSecond : Int32 {

        case one

        case two

        case three

        case four

        /// Creates a new instance with the specified raw value.
        ///
        /// If there is no value of the type that corresponds with the
        /// specified raw value, this initializer returns `nil`. For example:
        ///
        ///     enum PaperSize: String {
        ///         case A4, A5, Letter, Legal
        ///     }
        ///
        ///     print(PaperSize(rawValue: "Legal"))
        ///     // Prints "Optional("PaperSize.Legal")"
        ///
        ///     print(PaperSize(rawValue: "Tabloid"))
        ///     // Prints "nil"
        ///
        /// - Parameter rawValue: The raw value to use for the new instance.
        public init?(rawValue: Int32)

        /// The raw type that can be used to represent all values of the
        /// conforming type.
        ///
        /// Every distinct value of the conforming type has a corresponding
        /// unique value of the `RawValue` type, but there may be values of the
        /// `RawValue` type that don't have a corresponding value of the
        /// conforming type.
        public typealias RawValue = Int32

        /// The corresponding value of the raw type.
        ///
        /// A new instance initialized with `rawValue` will be equivalent to
        /// this instance. For example:
        ///
        ///     enum PaperSize: String {
        ///         case A4, A5, Letter, Legal
        ///     }
        ///
        ///     let selectedSize = PaperSize.Letter
        ///     print(selectedSize.rawValue)
        ///     // Prints "Letter"
        ///
        ///     print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!)
        ///     // Prints "true"
        public var rawValue: Int32 { get }
    }

    @available(iOS 13.0, *)
    public func configure(
        passioConfiguration: PassioNutritionAISDK.PassioConfiguration, 
        completion: @escaping (PassioNutritionAISDK.PassioStatus) -> Void
    )

    /// Shut down the Passio SDK and release all resources
    public func shutDownPassioSDK()

    @available(iOS 13.0, *)
    public func startFoodDetection(
        detectionConfig: PassioNutritionAISDK.FoodDetectionConfiguration = FoodDetectionConfiguration(), 
        foodRecognitionDelegate: PassioNutritionAISDK.FoodRecognitionDelegate, 
        completion: @escaping (Bool) -> Void
    )

    /// Stops food detection. To completely remove the camera, call
    /// removeVideoLayer()
    public func stopFoodDetection()

    ///
    /// - Parameters:
    ///   - image:
    ///   - detectionConfig: Detection configuration
    ///   - completion: Array of detection [FoodCandidates]
    @available(iOS 13.0, *)
    public func detectFoodIn(
        image: UIImage, 
        detectionConfig: PassioNutritionAISDK.FoodDetectionConfiguration = FoodDetectionConfiguration(), 
        slicingRects: [CGRect]? = nil, 
        completion: @escaping (PassioNutritionAISDK.FoodCandidates?) -> Void
    )

    /// Detect barcodes "BarcodeCandidate" in an image
    /// - Parameter image: Image for the detection
    /// - Parameter completion: Receives back Array of "BarcodeCandidate" for
    ///   that image
    public func detectBarcodesIn(
        image: UIImage, 
        completion: @escaping ([PassioNutritionAISDK.BarcodeCandidate]
    ) -> Void)

    /// List all food enabled for weight estimations
    /// - Returns: List of PassioIDs
    public func listFoodEnabledForAmountEstimation() -> [PassioNutritionAISDK.PassioID]

    /// use getPreviewLayer if you don't plan to rotate the PreviewLayer.
    /// - Returns: AVCaptureVideoPreviewLayer
    public func getPreviewLayer() -> AVCaptureVideoPreviewLayer?

    /// use getPreviewLayerWithGravity if you plan to rotate the PreviewLayer.
    /// - Returns: AVCaptureVideoPreviewLayer
    public func getPreviewLayerWithGravity(
        sessionPreset: AVCaptureSession.Preset = .hd1920x1080, 
        volumeDetectionMode: PassioNutritionAISDK.VolumeDetectionMode = .none,
        videoGravity: AVLayerVideoGravity = .resizeAspectFill
    ) -> AVCaptureVideoPreviewLayer?

    /// Don't call this function if you need to use the Passio layer again.
    /// Only call this function to set the PassioSDK Preview layer to nil
    public func removeVideoLayer()

    /// Use this function to get the bounding box relative to the
    /// previewLayerBonds
    /// - Parameter boundingBox: The bounding box from the delegate
    /// - Parameter preview: The preview layer bounding box
    public func transformCGRectForm(boundingBox: CGRect, toRect: CGRect) -> CGRect

    /// Use this call to add personalizedAlternative to a Passio ID
    /// - Parameter personalizedAlternative:
    public func addToPersonalization(
        personalizedAlternative: PassioNutritionAISDK.PersonalizedAlternative
    )

    /// Lookup Personalized Alternative For PassioID
    /// - Parameter passioID: PassioID
    /// - Returns: PersonalizedAlternative
    public func lookupPersonalizedAlternativeFor(
        passioID: PassioNutritionAISDK.PassioID
    ) -> PassioNutritionAISDK.PersonalizedAlternative?

    /// Clean records for one PassioID
    /// - Parameter passioID: PassioID
    public func cleanPersonalizationForVisual(passioID: PassioNutritionAISDK.PassioID)

    /// Clean all records
    public func cleanAllPersonalization()

    /// Lookup PassioIDAttributes from PassioID
    /// - Parameter passioID: PassioID
    /// - Returns: PassioIDAttributes
    public func lookupPassioIDAttributesFor(
        passioID: PassioNutritionAISDK.PassioID
    ) -> PassioNutritionAISDK.PassioIDAttributes?

    /// Lookup Name For PassioID
    /// - Parameter passioID: PassioID
    /// - Returns: Name : String?
    public func lookupNameFor(passioID: PassioNutritionAISDK.PassioID) -> String?

    /// Search for food will return a list of potential food items ordered and
    /// optimized for user selection.
    /// - Parameters:
    ///   - byText: User typed in search term
    ///   - completion: All potential PassioIDAndName.
    public func searchForFood(
        byText: String, 
        completion: @escaping ([PassioNutritionAISDK.PassioIDAndName]
    ) -> Void)

    /// Fetch from Passio web-service the PassioIDAttributes for a barcode by
    /// its number
    /// - Parameter barcode: Barcode number
    /// - Parameter completion: Receive a closure with optional PassioIDAttributes
    public func fetchPassioIDAttributesFor(
        barcode: PassioNutritionAISDK.Barcode, 
        completion: @escaping ((PassioNutritionAISDK.PassioIDAttributes?
    ) -> Void))

    /// Lookup for an icon for a PassioID. You will receive an icon and a bool,
    /// The boolean is true if the icon is a food icon or false if it's a
    /// placeholder icon. If you get false you can use the asynchronous
    /// function to "fetchIconFor" the icons from the web.
    /// - Parameters:
    ///   - passioID: PassioID
    ///   - size: 90, 180 or 360 px
    ///   - entityType: PassioEntityType to return the right placeholder.
    /// - Returns: UIImage and a bool, the boolean is true if the icon is a food
    ///   icon or false if it's a placeholder icon. If you get false you can
    ///   use the asynchronous function to "fetchIconFor" the icons from the web.
    public func lookupIconFor(
        passioID: PassioNutritionAISDK.PassioID, 
        size: PassioNutritionAISDK.IconSize = IconSize.px90, 
        entityType: PassioNutritionAISDK.PassioIDEntityType = .item
    ) -> (UIImage, Bool)

    /// Fetch icons from the web.
    /// - Parameters:
    ///   - passioID: PassioID
    ///   - size: 90, 180 or 360 px
    ///   - entityType: PassioEntityType to return the right placeholder.
    ///   - completion: Optional Icon.
    public func fetchIconFor(
    passioID: PassioNutritionAISDK.PassioID, 
    size: PassioNutritionAISDK.IconSize = IconSize.px90, 
    completion: @escaping (UIImage?) -> Void)

    /// Fetch from Passio web-service the PassioIDAttributes for a
    /// packagedFoodCode by its number
    /// - Parameters:
    ///   - packagedFoodCode: packagedFoodCode
    ///   - completion: Receive a closure with optional PassioIDAttributes
    public func fetchPassioIDAttributesFor(
        packagedFoodCode: PassioNutritionAISDK.PackagedFoodCode, 
        completion: @escaping ((PassioNutritionAISDK.PassioIDAttributes?) -> Void)
    )

    /// lookupAllDescendantsFor PassioID
    /// - Parameter passioID: PassioID
    /// - Returns: PassioID Array of all Descendants
    public func lookupAllDescendantsFor(passioID: PassioNutritionAISDK.PassioID) -> [PassioNutritionAISDK.PassioID]

    /// Return a sorted list of available Volume Detection modes.
    /// Recommended mode is .auto
    public var availableVolumeDetectionModes: [PassioNutritionAISDK.VolumeDetectionMode] { get }

    /// Return the best Volume detection Mode for this iPhone
    public var bestVolumeDetectionMode: PassioNutritionAISDK.VolumeDetectionMode { get }

    public var version: String { get }
}

extension PassioNutritionAI : PassioNutritionAISDK.PassioStatusDelegate {

    public func completedDownloadingAllFiles(filesLocalURLs: [PassioNutritionAISDK.FileLocalURL])

    public func completedDownloadingFile(
        fileLocalURL: PassioNutritionAISDK.FileLocalURL, 
        filesLeft: Int
    )

    public func downloadingError(message: String)

    public func passioStatusChanged(status: PassioNutritionAISDK.PassioStatus)

    public func passioProcessing(filesLeft: Int)
}

extension PassioNutritionAI.FramesPerSecond : Equatable {
}

extension PassioNutritionAI.FramesPerSecond : Hashable {
}

extension PassioNutritionAI.FramesPerSecond : RawRepresentable {
}

/// This object will decipher the Nutrition Facts table on packaged food
public class PassioNutritionFacts {

    public init()

    public enum ServingSizeUnit : String {

        case g

        case ml

        /// Creates a new instance with the specified raw value.
        ///
        /// If there is no value of the type that corresponds with the
        /// specified raw value, this initializer returns `nil`. For example:
        ///
        ///     enum PaperSize: String {
        ///         case A4, A5, Letter, Legal
        ///     }
        ///
        ///     print(PaperSize(rawValue: "Legal"))
        ///     // Prints "Optional("PaperSize.Legal")"
        ///
        ///     print(PaperSize(rawValue: "Tabloid"))
        ///     // Prints "nil"
        ///
        /// - Parameter rawValue: The raw value to use for the new instance.
        public init?(rawValue: String)

        /// The raw type that can be used to represent all values of the
        /// conforming type.
        ///
        /// Every distinct value of the conforming type has a corresponding
        /// unique value of the `RawValue` type, but there may be values of the
        /// `RawValue` type that don't have a corresponding value of the
        /// conforming type.
        public typealias RawValue = String

        /// The corresponding value of the raw type.
        ///
        /// A new instance initialized with `rawValue` will be equivalent to
        /// this instance. For example:
        ///
        ///     enum PaperSize: String {
        ///         case A4, A5, Letter, Legal
        ///     }
        ///
        ///     let selectedSize = PaperSize.Letter
        ///     print(selectedSize.rawValue)
        ///     // Prints "Letter"
        ///
        ///     print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!)
        ///     // Prints "true"
        public var rawValue: String { get }
    }

    public var foundNutritionFactsLabel: Bool { get }

    final public let titleNutritionFacts: String

    final public let titleServingSize: String

    final public let titleCalories: String

    final public let titleTotalFat: String

    final public let titleTotalCarbs: String

    final public let titleProtein: String

    public var servingSizeQuantity: Double

    public var servingSizeUnitName: String?

    public var servingSizeGram: Double?

    public var servingSizeUnit: PassioNutritionAISDK.PassioNutritionFacts.ServingSizeUnit

    public var calories: Double?

    public var fat: Double?

    public var carbs: Double?

    public var protein: Double?

    public var isManuallyEdited: Bool

    public var servingSizeText: String { get }

    public var caloriesText: String { get }

    public var fatText: String { get }

    public var carbsText: String { get }

    public var proteinText: String { get }

    public var isCompleted: Bool { get }

    public var description: String { get }

    public func clearAll()
}

extension PassioNutritionFacts {
    public func createPassioIDAttributes(foodName: String) -> PassioNutritionAISDK.PassioIDAttributes
}

extension PassioNutritionFacts.ServingSizeUnit : Equatable {
}

extension PassioNutritionFacts.ServingSizeUnit : Hashable {
}

extension PassioNutritionFacts.ServingSizeUnit : RawRepresentable {
}

/// PassioSDKError will return the error with errorDescription if the
/// configuration has failed. 
public enum PassioSDKError : LocalizedError {

    case canNotRunOnSimulator

    case keyNotValid

    case licensedKeyHasExpired

    case modelsNotValid

    case modelsDownloadFailed

    case noModelsFilesFound

    case noInternetConnection

    case notLicensedForThisProject

    /// A localized message describing what error occurred.
    public var errorDescription: String? { get }

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioSDKError, 
        b: PassioNutritionAISDK.PassioSDKError
    ) -> Bool

    /// Hashes the essential components of this value by feeding them into the
    /// given hasher.
    ///
    /// Implement this method to conform to the `Hashable` protocol. The
    /// components used for hashing must be the same as the components compared
    /// in your type's `==` operator implementation. Call `hasher.combine(_:)`
    /// with each of these components.
    ///
    /// - Important: Never call `finalize()` on `hasher`. Doing so may become a
    ///   compile-time error in the future.
    ///
    /// - Parameter hasher: The hasher to use when combining the components
    ///   of this instance.
    public func hash(into hasher: inout Hasher)

    /// The hash value.
    ///
    /// Hash values are not guaranteed to be equal across different executions
    /// of your program. Do not save hash values to use during a future execution.
    ///
    /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
    ///   conform to `Hashable`, implement the `hash(into:)` requirement instead.
    public var hashValue: Int { get }
}

extension PassioSDKError : Equatable {
}

extension PassioSDKError : Hashable {
}

/// PassioServingSize for food Item Data
public struct PassioServingSize : Codable, Equatable, Hashable {

    public let quantity: Double

    public let unitName: String

    public init(quantity: Double, unitName: String)

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioServingSize, 
        b: PassioNutritionAISDK.PassioServingSize
    ) -> Bool

    /// Hashes the essential components of this value by feeding them into the
    /// given hasher.
    ///
    /// Implement this method to conform to the `Hashable` protocol. The
    /// components used for hashing must be the same as the components compared
    /// in your type's `==` operator implementation. Call `hasher.combine(_:)`
    /// with each of these components.
    ///
    /// - Important: Never call `finalize()` on `hasher`. Doing so may become a
    ///   compile-time error in the future.
    ///
    /// - Parameter hasher: The hasher to use when combining the components
    ///   of this instance.
    public func hash(into hasher: inout Hasher)

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// The hash value.
    ///
    /// Hash values are not guaranteed to be equal across different executions
    /// of your program. Do not save hash values to use during a future execution.
    ///
    /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
    ///   conform to `Hashable`, implement the `hash(into:)` requirement instead.
    public var hashValue: Int { get }

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

/// PassioServingUnit for food Item Data
public struct PassioServingUnit : Equatable, Codable {

    public let unitName: String

    public let weight: Measurement<UnitMass>

    public init(unitName: String, weight: Measurement<UnitMass>)

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PassioServingUnit, 
        b: PassioNutritionAISDK.PassioServingUnit
    ) -> Bool

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

/// PassioStatus is returned at the end of the configuration of the SDK.
public struct PassioStatus {

    /// The mode has several values, .isReadyForDetection means the library was
    /// successfully configured
    public var mode: PassioNutritionAISDK.PassioMode { get }

    /// missingFiles will contain any files the SDK is missing or new files
    /// that can be used for an update.
    public var missingFiles: [PassioNutritionAISDK.FileName]? { get }

    /// A string with more verbose information related to the configuration of
    /// the SDK
    public var debugMessage: String? { get }

    /// The error in case the SDK failed to configure
    public var error: PassioNutritionAISDK.PassioSDKError? { get }

    /// The version of the latest models that are now used by the SDK.
    public var activeModels: Int? { get }
}

/// Implement the protocol to receive status updates
public protocol PassioStatusDelegate : AnyObject {

    func passioStatusChanged(status: PassioNutritionAISDK.PassioStatus)

    func passioProcessing(filesLeft: Int)

    func completedDownloadingAllFiles(filesLocalURLs: [PassioNutritionAISDK.FileLocalURL])

    func completedDownloadingFile(
        fileLocalURL: PassioNutritionAISDK.FileLocalURL, 
        filesLeft: Int
    )

    func downloadingError(message: String)
}

public struct PersonalizedAlternative : Codable, Equatable {

    public let visualPassioID: PassioNutritionAISDK.PassioID

    public let nutritionalPassioID: PassioNutritionAISDK.PassioID

    public var servingUnit: String?

    public var servingSize: Double?

    public init(
        visualPassioID: PassioNutritionAISDK.PassioID, 
        nutritionalPassioID: PassioNutritionAISDK.PassioID, 
        servingUnit: String?, 
        servingSize: Double?
    )

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (
        a: PassioNutritionAISDK.PersonalizedAlternative, 
        b: PassioNutritionAISDK.PersonalizedAlternative
    ) -> Bool

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

/// UPC Product decoding struct
public struct UPCProduct : Codable {

    public let id: String

    public let name: String

    public let nutrients: [PassioNutritionAISDK.UPCProduct.NutrientUPC]?

    public let branded: PassioNutritionAISDK.UPCProduct.Branded?

    public let origin: [PassioNutritionAISDK.UPCProduct.Origin]?

    public let portions: [PassioNutritionAISDK.UPCProduct.Portion]?

    public let qualityScore: String?

    public let licenseCopy: String?

    /// Component of UPC Product decoding struct
    public struct NutrientUPC : Codable {

        public let id: Double?

        public let nutrient: PassioNutritionAISDK.UPCProduct.InternalNutrient?

        public let amount: Double?

        /// Encodes this value into the given encoder.
        ///
        /// If the value fails to encode anything, `encoder` will encode an
        /// empty keyed container in its place.
        ///
        /// This function throws an error if any values are invalid for the
        /// given encoder's format.
        ///
        /// - Parameter encoder: The encoder to write data to.
        public func encode(to encoder: Encoder) throws

        /// Creates a new instance by decoding from the given decoder.
        ///
        /// This initializer throws an error if reading from the decoder fails,
        /// or if the data read is corrupted or otherwise invalid.
        ///
        /// - Parameter decoder: The decoder to read data from.
        public init(from decoder: Decoder) throws
    }

    /// Component of UPC Product decoding struct
    public struct InternalNutrient : Codable {

        public let name: String?

        public let unit: String?

        public let shortName: String?

        /// Encodes this value into the given encoder.
        ///
        /// If the value fails to encode anything, `encoder` will encode an
        /// empty keyed container in its place.
        ///
        /// This function throws an error if any values are invalid for the
        /// given encoder's format.
        ///
        /// - Parameter encoder: The encoder to write data to.
        public func encode(to encoder: Encoder) throws

        /// Creates a new instance by decoding from the given decoder.
        ///
        /// This initializer throws an error if reading from the decoder fails,
        /// or if the data read is corrupted or otherwise invalid.
        ///
        /// - Parameter decoder: The decoder to read data from.
        public init(from decoder: Decoder) throws
    }

    /// Component of UPC Product decoding struct
    public struct Branded : Codable {

        public let owner: String?

        public let upc: String?

        public let productCode: String?

        public let ingredients: String?

        /// Encodes this value into the given encoder.
        ///
        /// If the value fails to encode anything, `encoder` will encode an
        /// empty keyed container in its place.
        ///
        /// This function throws an error if any values are invalid for the
        /// given encoder's format.
        ///
        /// - Parameter encoder: The encoder to write data to.
        public func encode(to encoder: Encoder) throws

        /// Creates a new instance by decoding from the given decoder.
        ///
        /// This initializer throws an error if reading from the decoder fails,
        /// or if the data read is corrupted or otherwise invalid.
        ///
        /// - Parameter decoder: The decoder to read data from.
        public init(from decoder: Decoder) throws
    }

    /// Component of UPC Product decoding struct
    public struct Origin : Codable {

        public let source: String?

        public let id: String?

        /// Encodes this value into the given encoder.
        ///
        /// If the value fails to encode anything, `encoder` will encode an
        /// empty keyed container in its place.
        ///
        /// This function throws an error if any values are invalid for the
        /// given encoder's format.
        ///
        /// - Parameter encoder: The encoder to write data to.
        public func encode(to encoder: Encoder) throws

        /// Creates a new instance by decoding from the given decoder.
        ///
        /// This initializer throws an error if reading from the decoder fails,
        /// or if the data read is corrupted or otherwise invalid.
        ///
        /// - Parameter decoder: The decoder to read data from.
        public init(from decoder: Decoder) throws
    }

    /// Component of UPC Product decoding struct
    public struct Portion : Codable {

        public let weight: PassioNutritionAISDK.UPCProduct.Weight?

        public let name: String?

        public let quantity: Double?

        public let suggestedQuantity: [Double]?

        /// Encodes this value into the given encoder.
        ///
        /// If the value fails to encode anything, `encoder` will encode an
        /// empty keyed container in its place.
        ///
        /// This function throws an error if any values are invalid for the
        /// given encoder's format.
        ///
        /// - Parameter encoder: The encoder to write data to.
        public func encode(to encoder: Encoder) throws

        /// Creates a new instance by decoding from the given decoder.
        ///
        /// This initializer throws an error if reading from the decoder fails,
        /// or if the data read is corrupted or otherwise invalid.
        ///
        /// - Parameter decoder: The decoder to read data from.
        public init(from decoder: Decoder) throws
    }

    /// Component of UPC Product decoding struct
    public struct Weight : Codable {

        public let unit: String?

        public let value: Double?

        /// Encodes this value into the given encoder.
        ///
        /// If the value fails to encode anything, `encoder` will encode an
        /// empty keyed container in its place.
        ///
        /// This function throws an error if any values are invalid for the
        /// given encoder's format.
        ///
        /// - Parameter encoder: The encoder to write data to.
        public func encode(to encoder: Encoder) throws

        /// Creates a new instance by decoding from the given decoder.
        ///
        /// This initializer throws an error if reading from the decoder fails,
        /// or if the data read is corrupted or otherwise invalid.
        ///
        /// - Parameter decoder: The decoder to read data from.
        public init(from decoder: Decoder) throws
    }

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    public func encode(to encoder: Encoder) throws

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    public init(from decoder: Decoder) throws
}

/// VolumeDetectionMode for detection volume.
public enum VolumeDetectionMode : String {

    /// Using the best technology available in this order: dualWideCamera,
    /// dualCamera or none if the device is not capable of detecting volume.
    case auto

    /// If dualWideCamera is not available the mode will not fall back to dualCamera
    case dualWideCamera

    case none

    /// Creates a new instance with the specified raw value.
    ///
    /// If there is no value of the type that corresponds with the specified raw
    /// value, this initializer returns `nil`. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     print(PaperSize(rawValue: "Legal"))
    ///     // Prints "Optional("PaperSize.Legal")"
    ///
    ///     print(PaperSize(rawValue: "Tabloid"))
    ///     // Prints "nil"
    ///
    /// - Parameter rawValue: The raw value to use for the new instance.
    public init?(rawValue: String)

    /// The raw type that can be used to represent all values of the conforming
    /// type.
    ///
    /// Every distinct value of the conforming type has a corresponding unique
    /// value of the `RawValue` type, but there may be values of the `RawValue`
    /// type that don't have a corresponding value of the conforming type.
    public typealias RawValue = String

    /// The corresponding value of the raw type.
    ///
    /// A new instance initialized with `rawValue` will be equivalent to this
    /// instance. For example:
    ///
    ///     enum PaperSize: String {
    ///         case A4, A5, Letter, Legal
    ///     }
    ///
    ///     let selectedSize = PaperSize.Letter
    ///     print(selectedSize.rawValue)
    ///     // Prints "Letter"
    ///
    ///     print(selectedSize == PaperSize(rawValue: selectedSize.rawValue)!)
    ///     // Prints "true"
    public var rawValue: String { get }
}

extension VolumeDetectionMode : Equatable {
}

extension VolumeDetectionMode : Hashable {
}

extension VolumeDetectionMode : RawRepresentable {
}

extension UIImageView {
    public func loadPassioIconBy(
        passioID: PassioNutritionAISDK.PassioID, 
        entityType: PassioNutritionAISDK.PassioIDEntityType, 
        size: PassioNutritionAISDK.IconSize = .px90, 
        completion: @escaping (PassioNutritionAISDK.PassioID, UIImage) -> Void
    )
}

extension simd_float4x4 : ContiguousBytes {

    /// Calls the given closure with the contents of underlying storage.
    ///
    /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that
    ///         the same buffer pointer will be passed in every time.
    /// - warning: The buffer argument to the body should not be stored or used
    ///            outside of the lifetime of the call to the closure.
    public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R
}

infix operator .+ : DefaultPrecedence

infix operator ./ : DefaultPrecedence

Copyright 2022 Passio Inc
// We already have PassioAdvisorFoodInfo 
var food: PassioAdvisorFoodInfo?
// We will fetch PassioFoodItem from PassioAdvisorFoodInfo
var foodItem: PassioFoodItem?

func fetchPassioFoodItem() {
    
    guard let food = food else { return }
    
    if let packagedFoodItem = food.packagedFoodItem {
        self.foodItem = packagedFoodItem
        tableView.reloadData()
    }
    else {
        if let foodDataInfo = food.foodDataInfo {
            let servingQuantity = foodDataInfo.nutritionPreview?.servingQuantity
            let servingUnit = foodDataInfo.nutritionPreview?.servingUnit
            
            updateLoader(show: true)
            PassioNutritionAI.shared.fetchFoodItemFor(foodDataInfo: foodDataInfo,
                                                      servingQuantity: servingQuantity,
                                                      servingUnit: servingUnit) { passioFoodItem in
                DispatchQueue.main.async {
                    self.updateLoader(show: false)
                    if let foodItem = passioFoodItem {
                        self.foodItem = foodItem
                        tableView.reloadData()
                    }
                }
            }
        }
    }
}
func setup(foodItem: PassioFoodItem) {

    // 1. Name & short name
    labelName.text = foodItem.name
    labelShortName.text = foodItem.details
    let entityType: PassioIDEntityType = foodItem.ingredients.count > 1 ? .recipe : .item
    
    if labelName.text?.lowercased() == self.labelShortName.text?.lowercased()
        || entityType == .recipe {
        labelShortName.isHidden = true
    } else {
        labelShortName.isHidden = false
    }
    
    // 2. Food image
    imageFood.loadIcon(id: foodItem.iconId)
    
    // 3. Calories
    let calories = foodItem.nutrientsSelectedSize().calories()?.value ?? 0
    let carbs = foodItem.nutrientsSelectedSize().carbs()?.value ?? 0
    let protein = foodItem.nutrientsSelectedSize().protein()?.value ?? 0
    let fat = foodItem.nutrientsSelectedSize().fat()?.value ?? 0

    caloriesLabel.text = calories.noDigit
    carbsLabel.text = carbs.oneDigit + " \(UnitsTexts.g)"
    protienLabel.text = protein.oneDigit + " \(UnitsTexts.g)"
    fatLabel.text = fat.oneDigit + " \(UnitsTexts.g)"
    
    // 4. Percentage
    let total = [c,p,f].reduce(0, { $0 + $1.percent })
    if total > 0 {
        fatPercentLabel.text = "(\(f.percent.oneDigit)%)"
        protienPercentLabel.text = "(\(p.percent.oneDigit)%)"
        carbsPercentLabel.text = "(\(c.percent.oneDigit)%)"
    } else {
        fatPercentLabel.text = "(0%)"
        protienPercentLabel.text = "(0%)"
        carbsPercentLabel.text = "(0%)"
    }
    
    // 5. Chart
    let percents = macronutrientPercentages(carbsG: carbs,
                                            fatG: fat,
                                            proteinG: protein,
                                            totalCalories: calories)
    let c = DonutChartView.ChartDatasource(color: .lightBlue,
                                         percent: percents.carbPercentage)
    let p = DonutChartView.ChartDatasource(color: .green500,
                                         percent: percents.proteinPercentage)
    let f = DonutChartView.ChartDatasource(color: .purple500,
                                         percent: percents.fatPercentage)
    nutritionView.updateData(data: [c,p,f])
}

func macronutrientPercentages(carbsG: Double,
                              fatG: Double,
                              proteinG: Double,
                              totalCalories: Double) -> (carbPercentage: Double,
                                                         fatPercentage: Double,
                                                         proteinPercentage: Double)
{
    // Calculate calories contributed by each macronutrient
    let carbCalories = carbsG * 4
    let fatCalories = fatG * 9
    let proteinCalories = proteinG * 4
    
    // Calculate total calories from macronutrients
    let totalMacronutrientCalories = carbCalories + fatCalories + proteinCalories
    
    // Calculate percentages
    let carbPercentage = (carbCalories / totalMacronutrientCalories) * 100
    let fatPercentage = (fatCalories / totalMacronutrientCalories) * 100
    let proteinPercentage = (proteinCalories / totalMacronutrientCalories) * 100
    return (carbPercentage: carbPercentage,
            fatPercentage: fatPercentage,
            proteinPercentage: proteinPercentage)
}
let quantity = Double(textField.text) 
self.foodItem?.setSelectedQuantity(quantity)
// Refresh data
tableView.reloadData()
let unit = // User selected unit
self.foodItem?.setSelectedUnit(unit)
// Refresh data
tableView.reloadData()
guard let servingUnits = foodItem?.amount.servingUnits else { return }
let items = servingUnits.map { $0.unitName }
import PassioNutritionAISDK
private let passioSDK = PassioNutritionAI.shared
// Using SDK key

func configurePassioSDK() {
    
    passioSDK.statusDelegate = self
    
    let PASSIO_KEY = "YOUR_PASSIO_KEY"
    var passioConfig = PassioConfiguration(key: PASSIO_KEY)
    passioConfig.remoteOnly = true

    passioSDK.configure(passioConfiguration: passioConfig) { status in
        print("Mode = \(status.mode)")
        print("Missing files = \(String(describing: status.missingFiles))")
    }
}
// Using Proxy URL

func configurePassioSDK() {
    
    passioSDK.statusDelegate = self
    
    var passioConfig = PassioConfiguration()
    // Set the base URL of the target proxy endpoint
    passioConfig.proxyUrl = "https://yourdomain.com/nutrition/"
    // Set headers as per your requirements
    passioConfig.proxyHeaders = ["header" : "value"] 

    passioSDK.configure(passioConfiguration: passioConfig) { status in
        print("Mode = \(status.mode)")
        print("Missing files = \(String(describing: status.missingFiles))")
    }
}
extension ImageSelectionVC: PassioStatusDelegate {
    
    func passioStatusChanged(status: PassioStatus) {
        print("Status changed: \(status)")
        configureUI(status: status)
    }
    
    func passioProcessing(filesLeft: Int) {
        print("Files to download: \(filesLeft)")
    }
    
    func completedDownloadingAllFiles(filesLocalURLs: [FileLocalURL]) {
        print("All files downloaded")
    }
    
    func completedDownloadingFile(fileLocalURL: FileLocalURL, filesLeft: Int) {
        print("File downloaded: \(fileLocalURL)")
    }
    
    func downloadingError(message: String) {
        print("Downloading error: \(message)")
    }
}
func configureUI(status: PassioStatus) {
    
    DispatchQueue.main.async {
    
        switch status.mode {
        
        case .isReadyForDetection:
            // The SDK is ready for detection. Hide the status view.
            self.statusView.isHidden = true  
            // This will be covered in the next section
            self.askForCapturePermission()
            
        case .isBeingConfigured:
            self.statusLabel.text = "Configuraing SDK..."
            
        case .failedToConfigure:
            self.statusLabel.text = "SDK failed to configure!"
            
        case .isDownloadingModels:
            self.statusLabel.text = "SDK is downloading files..."
            
        case .notReady:
            self.statusLabel.text = "SDK is not ready!"
            
        @unknown default:
            self.statusLabel.text = "Unknown passio status!"
        }
    }
}
implementation files('libs/passiolib-release.aar')
dependencies {
    // TensorFlow
    implementation 'org.tensorflow:tensorflow-lite:2.8.0'
    implementation 'org.tensorflow:tensorflow-lite-metadata:0.4.0'

    // CameraX
    def camerax_version = "1.3.0-alpha12"
    implementation "androidx.camera:camera-core:$camerax_version"
    implementation "androidx.camera:camera-camera2:$camerax_version"
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    implementation 'androidx.camera:camera-view:1.3.0-alpha02'
    implementation 'androidx.camera:camera-extensions:1.3.0-alpha02'

    // Barcode and OCR
    implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
    implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0'
}
android {
    aaptOptions {
        noCompress "passiosecure"
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
.aar
releases page
TensorFlow
FirebaseVision
CameraX
<androidx.camera.view.PreviewView
    android:id="@+id/myPreviewView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
class MyFragment : PassioCameraFragment() {
	
    override fun getPreviewView(): PreviewView {
	return myPreviewView
    }

    override fun onCameraReady() { 
    	// Proceed with initializing the recognition session
    }

    override fun onCameraPermissionDenied() { 
    	// Explain to the user that the camera is needed for this feature to
        // work and ask for permission again
    }
}
class MainActivity : AppCompatActivity(), PassioCameraViewProvider {
	
    override fun requestCameraLifecycleOwner(): LifecycleOwner {
        return this
    }

    override fun requestPreviewView(): PreviewView {
        return myPreviewView
    }
}
override fun onStart() {
    super.onStart()
    if (!hasPermissions()) {
        ActivityCompat.requestPermissions(
            this,
            REQUIRED_PERMISSIONS,
            REQUEST_CODE_PERMISSIONS
        )
        return
    } else {
        PassioSDK.instance.startCamera(this /*reference to the PassioCameraViewProvider*/)
    }
}
PreviewView
steps
import AVFoundation
func askForCapturePermission() {
    if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
        configureCamera()
    } else {
        AVCaptureDevice.requestAccess(for: .video) { granted in
            DispatchQueue.main.async {
                if granted {
                    self.configureCamera()
                } else {
                    self.statusLabel.text = "Please grant permission from Settings to use camera."
                }
            }
        }
    }
}
var captureSession: AVCaptureSession!
var stillImageOutput: AVCapturePhotoOutput!
var videoPreviewLayer: AVCaptureVideoPreviewLayer!
func configureCamera() {
    
    statusLabel.text = "Setting up camera..."
    captureSession = AVCaptureSession()
    captureSession.sessionPreset = .photo
    
    guard let backCamera = AVCaptureDevice.default(for: .video) else {
        statusLabel.text = "Unable to access back camera!"
        return
    }
    do {
        let input = try AVCaptureDeviceInput(device: backCamera)
        stillImageOutput = AVCapturePhotoOutput()
        
        if captureSession.canAddInput(input) &&
            captureSession.canAddOutput(stillImageOutput) {
            captureSession.addInput(input)
            captureSession.addOutput(stillImageOutput)
            setupLivePreview()
        }
    }
    catch let error {
        statusLabel.text = "Error Unable to initialize back camera:  \(error.localizedDescription)"
    }
}
func setupLivePreview() {
    
    statusView.isHidden = true
    
    videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    videoPreviewLayer.videoGravity = .resizeAspectFill
    videoPreviewLayer.connection?.videoOrientation = .portrait
    self.captureView.layer.insertSublayer(videoPreviewLayer, at: 0)
    
    DispatchQueue.global(qos: .userInitiated).async { [weak self] in
        guard let self = self else { return }
        self.captureSession.startRunning()
        DispatchQueue.main.async {
            self.videoPreviewLayer.frame = self.captureView.bounds
        }
    }
}
@IBAction func captureButtonTapped(_ sender: UIButton) {
    captureImage()
}
func captureImage() {
    let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCode
    stillImageOutput.capturePhoto(with: settings, delegate: self)
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    guard let imageData = photo.fileDataRepresentation() else { return }
    guard let image = UIImage(data: imageData) else { return }
    // Use this image
}
PHPhotoLibrary.requestAuthorization() { status in
    DispatchQueue.main.async {
        if status == .authorized {
            self.presentImagePicker()
        } else {
            // Permission denied by user.
        }
    }
}
func presentImagePicker() {
    var configuration = PHPickerConfiguration()
    configuration.selectionLimit = 1 // or any number
    configuration.filter = .images
    
    let picker = PHPickerViewController(configuration: configuration)
    picker.isModalInPresentation = true
    picker.delegate = self
    
    DispatchQueue.main.async {
        self.present(picker, animated: true)
    }
}
func picker(_ picker: PHPickerViewController, 
            didFinishPicking results: [PHPickerResult]) {
    
    picker.dismiss(animated: true) { [weak self] in
        
        if results.count < 1 { return }
        
        var selectedImages: [UIImage] = []
        let itemProviders = results.map(\.itemProvider)
        let dispatchGroup = DispatchGroup()
        
        for itemProvider in itemProviders {
            dispatchGroup.enter()
            if itemProvider.canLoadObject(ofClass: UIImage.self) {
                itemProvider.loadObject(ofClass: UIImage.self) { image , error  in
                    if let image = image as? UIImage {
                        selectedImages.append(image)
                        dispatchGroup.leave()
                    } else {
                        dispatchGroup.leave()
                    }
                }
            } else {
                dispatchGroup.leave()
            }
        }
        dispatchGroup.notify(queue: .main) { [weak self] in
            guard let self else { return }
            if selectedImages.count < 1 { return }
            // Use these images
        }
    }
}
@IBOutlet weak var foodListTable: UITableView!
var foodInfo: [PassioAdvisorFoodInfo] = []
func fetchData() {
    // Pass the image we just picked from above steps
    guard let image = selectedImage else { return }
    updateLoader(show: true)
    PassioNutritionAI.shared.recognizeImageRemote(image: image) { passioAdvisorFoodInfo in
        DispatchQueue.main.async {
            self.updateLoader(show: false)
            self.didFetch(foods: passioAdvisorFoodInfo)
        }
    }
}
func didFetch(foods: [PassioAdvisorFoodInfo]) {
    if foods.count < 1 {
        // No food detected. Present the alert.
        return
    }
    foodInfo = foods
    foodListTable.reloadData()
}
@IBOutlet weak var foodImageView: UIImageView!
@IBOutlet weak var foodNameLabel: UILabel!
@IBOutlet weak var foodDetailsLabel: UILabel!

func configure(food: PassioAdvisorFoodInfo) {
    
    if let foodInfo = food.foodDataInfo {
        
        foodImageView.loadIcon(id: foodInfo.iconID)
        foodNameLabel.text = foodInfo.foodName.capitalized
        
        if let nutritionPreview = foodInfo.nutritionPreview {
            let servingQuantity = nutritionPreview.servingQuantity.twoDigits
            foodDetailsLabel.text = "\(servingQuantity) \(nutritionPreview.servingUnit) | \(nutritionPreview.calories) cal"
        } else {
            foodDetailsLabel.text = ""
        }
    }
    
    else if let packagedFoodItem = food.packagedFoodItem {
        
        foodNameLabel.text = packagedFoodItem.name
        
        let servingQuantity = packagedFoodItem.amount.selectedQuantity.twoDigits
        let calories = packagedFoodItem.nutrientsReference().calories()?.value ?? 0
        foodDetailsLabel.text = "\(servingQuantity) \(packagedFoodItem.amount.selectedUnit) | \(calories.twoDigits) cal"
    }
    
    else {
        foodNameLabel.text = ""
        foodDetailsLabel.text = ""
    }
NSCameraUsageDescription
NSPhotoLibraryUsageDescription

Sandbox app

With every shipped version of the SDK there will also be a PassioSDKSandbox containing the latest files and showcasing the basic setup of an app using the SDK. In this section we will cover the architecture of the small app and all the calls to the SDK keeping in mind where they are located and when they are called.

The app consists of one MainActivity that holds 2 fragments: RecognizerFragment and PermissionFragment. The latter will be displayed to the user if they deny access to the device camera. The RecognitionFragment is used to display the camera preview, start the recognition session and display the results through a view called FoodResultView.

PassioSDK library

The .aar file has already been imported as a separate module called passiolib-release. If you want to see how Android Studio structures the module, you can right-click on the passiolib-release module in the project viewer and choose the option "Open in". The passiolib-release module has been added as a dependency to the app modules, where we will use the SDK.

Camera

As mentioned above, the camera preview will be used in the RecognizerFragment. If you follow that fragment's onCreateView method you will see that the fragment inflates the view hierarchy found in the file fragment_recognizer.xml. Located in that view hierarchy we have the CameraX's PreviewView with an ID of "recognizerPreviewView". Taking a look back to the RecognizerFragment we can see that this component implements the PassioCameraViewProvider interface. Also, in the fragment's onStart lifecycle callback we are calling the

PassioSDK.instance.startCamera(this)

to start the preview and link the camera to the SDK. We don't need to stop the camera on onStop because we are passing the LifecycleObserver reference to the startCamera method. The camera will stop automatically when it needs to.

SDK Initialization

The SDK is initialized and configured in the onCreate method in the MainActivity.

val config = PassioConfiguration(this.applicationContext, "your_key")
PassioSDK.instance.configure(config) { passioStatus ->
    when (passioStatus.mode) {
        PassioMode.NOT_READY -> onPassioSDKError(passioStatus.debugMessage)
        PassioMode.IS_BEING_CONFIGURED -> onPassioSDKError(passioStatus.debugMessage)
        PassioMode.FAILED_TO_CONFIGURE -> onPassioSDKError(passioStatus.debugMessage)
        PassioMode.IS_DOWNLOADING_MODELS -> onPassioSDKError("Auto updating")
        PassioMode.IS_READY_FOR_DETECTION -> onPassioSDKReady()
    }
}

This configure method means that we are using the files located in the assets folder of the app module. Verify that there are 4 files in the assets folder with the extension .passiosecure.

If you take a closer look, the second parameter of the PassioConfiguration object is your SDK license key. Be sure to edit this method and supply your own license key. If you run the app without providing the key, the onPassioSDKError callback will be called with a message about an invalid key.

Detection session

The session is defined in the onStart and onStop lifecycle callbacks of the RecognizerFragment.

override fun onStart() {
    super.onStart()
    ...
    PassioSDK.instance.startCamera(this)
    val detectionConfig = FoodDetectionConfiguration().apply {
        detectBarcodes = true
    }
    PassioSDK.instance.startFoodDetection(foodListener, detectionConfig)
}

override fun onStop() {
    PassioSDK.instance.stopFoodDetection()
    super.onStop()
}

As described in the section Food detection session, we define a FoodDetectionConfiguration object and through it opt in for VISUAL and BARCODE detection. We then pass this object to startFoodDetection to receive results through the foodListener. When this fragment's onStop is called we are stopping the food detection process so that any results generated in the SDK after this point won't be delivered causing leaks.

private val foodListener = object : FoodRecognitionListener {
    override fun onRecognitionResults(candidates: FoodCandidates, image: Bitmap?, nutritionFacts: PassioNutritionFacts?) {
        val visualCandidates = candidates.detectedCandidates!!
        val barcodeCandidates = candidates.barcodeCandidates!!
        if (visualCandidates.isEmpty() && barcodeCandidates.isEmpty()) {
            recognizerFoodResultView.setSearching()
        } else if (barcodeCandidates.isNotEmpty()) {
            val bestBarcodeResult = barcodeCandidates.maxByOrNull { it.boundingBox.width() * it.boundingBox.height() } ?: return
            recognizerFoodResultView.setBarcodeResult(bestBarcodeResult.barcode)
        } else {
            val bestVisualCandidate = visualCandidates.maxByOrNull { it.confidence } ?: return
            recognizerFoodResultView.setFoodResult(bestVisualCandidate.passioID)
        }
    }

The foodListener above only takes into account the candidates from the detectedCandidates and barcodeCandidate lists because we only opted in for VISUAL and BARCODE detection. The other files of the candidates object received in the onRecognitionResults method will be null. Depending on the candidates within these two lists we will change the state of the FoodResultView denoted as recognizerFoodResultView.

Taking a closer look at the FoodResultView we can see how to get food details such as the name or the icon depending on the source of the detection. If the passioID comes from the FOOD detection we can obtain the PassioIDAttributes through the

PassioSDK.instance.lookupPassioAttributesFor(passioID)

On the other hand, if the results come from the BARCODE detection, we can get the PassioIDAttributes from the networking call

PassioSDK.instance.fetchPassioIDAttributesForBarcode(barcode) { passioIDAttributes ->
    ...
}

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Nutritional Database

Once the food detection process has returned a PassioID, it is time to query the database and see what information Passio has on the detected food.

All the calls to the database are synchronous and are executed on the caller thread. For better performance execute these calls on a background thread.

The most detailed information can be found in the PassioIDAttributes object. To retrieve this object from a given PassioID, simply call

val attributes = PassioSDK.instance.lookupPassioAttributesFor(passioID)

This call may return null if the given PassioID can't be found in the database. Be sure to check the nullability of the return object before proceeding.

PassioIDAttributes

data class PassioIDAttributes(
    var passioID: PassioID,
    var name: String,
    var imageName: String,
    val entityType: PassioIDEntityType,
    var parents: List<PassioAlternative>?,
    var children: List<PassioAlternative>?,
    var siblings: List<PassioAlternative>?,
    val passioFoodItemDataForDefault: PassioFoodItemData?,
    val passioFoodRecipe: PassioFoodRecipe?,
    val condiments: List<PassioFoodItemData>? = null
)

Here we will cover the fields that are not self-explanatory:

  1. imageName - An identifier for the image of the food. To retrieve the Drawable for the given image name use

val drawable = lookupImageForFilename(context, imageName)

The image for a food can also be retrieved using the PassioID and the method

val drawable = lookupImageFor(context, passioID)
  1. parents, children, siblings - All the foods in Passio's Nutritional Database are organized in a tree-like structure with each PassioID being a node in that tree. Let's say we scanned a "mandarin". A child of a "mandarin" is a "tangerine", and its parent is a "whole citrus fruit". We can offer these alternatives to the user in case the user is pointing to a similar food to a mandarin.

  2. passioFoodItemDataForDefault, passioFoodRecipe - The SDK differentiates single food items and food recipes. A single item represents just one particular food like an apple or peas. A food recipe consists of more than one single food item. For example a recipe is a Caprese salad which consists of mozzarella, tomatoes, basil, salt, black pepper and olive oil.

PassioFoodItemData

data class PassioFoodItemData(
    var passioID: PassioID,
    var name: String,
    var imageName: String
) {
    var referenceWeight = UnitMass(Grams(), 0.0)
    var fat: Double?
    var satFat: Double?
    var monounsaturatedFat: Double?
    var polyunsaturatedFat: Double?
    var proteins: Double?
    var carbs: Double?
    var calories: Double?
    var saturatedFat: Double?
    var cholesterol: Double?
    var sodium: Double?
    var fibers: Double?
    var transFat: Double?
    var sugars: Double?
    var sugarsAdded: Double?
    var alcohol: Double?
    var iron: Double?
    var vitaminC: Double?
    var vitaminA: Double?
    var vitaminD: Double?
    var calcium: Double?
    var potassium: Double?
    var sugarAlcohol: Double?

    var servingSizes: List<PassioServingSize>
    var servingUnits: List<PassioServingUnit>
    var entityType: PassioIDEntityType 
    var alternatives: List<PassioAlternative>?
    var foodOrigins: List<PassioFoodOrigin>?
}

PassioFoodItemData holds the nutritional information for a food item like calories, carbs, protein, fat etc. It also has a reference weight on which these values are calculated and contains serving sizes and serving units. Serving sizes offer you quick options to select the size of the food with a given quantity and unit.

PassioFoodRecipe

Recipes should be viewed as a collection of PassioFoodItemData objects.

data class PassioFoodRecipe(
    var passioID: PassioID,
    var name: String,
    var filenameIcon: String,
    var foodItems: MutableList<PassioFoodItemData>,
) {
    var servingSizes: List<PassioServingSize>
    var servingUnits: List<PassioServingUnit>
}

Other useful queries

If you have a PassioID returned from the detection process and you are just interested in the name of the corresponding food, use

fun lookupPassioIDFor(name: String): PassioID?

To query the names of the foods from the database by a filter use

fun searchForFood(byText: String): List<Pair<PassioID, String>>

The returned list contains a PassioID paired with the name of the food.

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

SDK Initialization and Configuration

Every call to the SDK is made through the singleton object obtained with PassioSDK.instance call. The SDK is designed to be called from the main thread, and it will deliver results to the main thread. Its internal tasks will be run by Passio threads, so you don't need to manage the threading execution.

Two things are required for the SDK to work: a proper license key and the neural network files that the SDK uses to run the recognition process.

  • The license key can be requsted at support@passiolife.com

  • The files are shipped inside the PassioSDKSandbox project on the releases page of the Android-Passio-SDK-Distribution repository. Unzip the PassioSDKSandbox project and locate the files in the assets folder: /PassioSDKSandbox/app/src/main/assets. Files that the SDK needs have an extension .passiosecure and are encrypted

There are several ways to initialize the SDK depending on the location of the needed files:

  1. Files are in the assets folder of your Android project

  2. Let the SDK manage file download and update

  3. Host the files on your own server, download the files, store them on the device's memory and point the SDK to the URIs of the needed files

For a first time integration the first approach is recommended

1. Files are located in the Assets folder

  • If the assets folder is not already present in your Android project, create one in the module where the SDK dependencies are located under the main directory (e.g. app/src/main/assets)

  • Transfer the .passiosecure files to the assets folder

  • Create a PassioConfiguration object with the application context and the license key

  • Call the configure method of the PassioSDK without changing the default PassioConfiguration object

val passioConfiguration = PassioConfiguration(
    this.applicationContext,
    "Your license key here"
)

PassioSDK.instance.configure(passioConfiguration) { passioStatus ->
    // Handle PassioStatus states
}

2. The SDK will manage the download and updates

By setting the sdkDownloadsModels field to true the SDK will do the initial download of the files. If the current files that are present on the device are older than the version of the SDK library, the SDK will download the newer version in the background, but still run the session with the older version of the files. Once the download is finished, the new files will be applied on the next session.

val passioConfiguration = PassioConfiguration(
    this.applicationContext,
    "Your license key here"
).apply {
    sdkDownloadsModels = true
}

PassioSDK.instance.configure(passioConfiguration) { passioStatus ->
    // Handle PassioStatus states
} 

To track the download process you can implement a PassioStatusListener (recommended to define after the PassioStatus mode returns IS_DOWNLOADING_MODELS)

3. Files are located in local device storage

This approach is recommended if you are responsible for the download of the files. In this case the SDK does not care where the files are hosted and how they are downloaded, just where they are stored after the download has completed. Use the PassioConfiguration object to modify the initialization process with local files and pass the URIs of the files to the localFiles field.

val passioConfiguration = PassioConfiguration(
    this.applicationContext,
    "Your license key here"
).apply {
    localFiles = /*List of files URIs*/
}

PassioSDK.instance.configure(passioConfiguration) { passioStatus ->
    // Handle PassioStatus states
} 

After the configuration process is successful, delete the files from local device storage. The SDK will copy them over to its internal folder.

Handle the configuration response

When you call the configure method of the Passio SDK with the PassioConfiguration object, you will need to define a callback to handle the result of the configuration process. This result is comprised in the PassioStatus object.

class PassioStatus {
    var mode: PassioMode
    var missingFiles: List<FileName>?
    var debugMessage: String?
    var activeModels: Int?
}

The mode of PassioStatus defines what the current status of the configuration process is. There are 5 different modes, and they all should be handled by the implementing side.

enum class PassioMode {
    NOT_READY,
    FAILED_TO_CONFIGURE,
    IS_BEING_CONFIGURED,
    IS_DOWNLOADING_MODELS,
    IS_READY_FOR_DETECTION
}
  • NOT_READY -> The configuration process hasn't started yet.

  • FAILED_TO_CONFIGURE -> The configuration process resulted in an error. Be sure to check the debugMessage of the PassioStatus object to get more insight into the nature of the error.

  • IS_BEING_CONFIGURED -> The SDK is still in the configuration process. Normally, you shouldn't receive this mode as a callback to the configure method.

  • IS_DOWNLOADING_MODELS -> The files required for the SDK to work are not present and are currently being downloaded.

  • IS_READY_FOR_DETECTION -> The configuration process is over and all the SDKs functionalities are available.

The missingFiles variable contains a list of all the files missing for the SDK to work optimally. When this list is not null, this doesn't mean that the SDK is not operational. The SDK may be initialized with older files, but it will advocate the download of the newest files through this list.

The debugMessage usually gives more verbose and human-readable information about the configuration process. It will always give an error message if the mode is FAILED_TO_CONFIGURE.

The activeModels will give the version of the files installed and ran by the SDK if the configuration process ran successfully i.e. if the mode is IS_READY_FOR_DETECTION.

Best practices

  1. The PassioStatus mode is NOT_READY -> check debugMessage for the error, usually the wrong license key has been supplied or the files are missing.

  2. The PassioStatus mode is IS_DOWNLOADING_MODELS -> register a PassioStatusListener through the PassioSDK.instance.setPassioStatusListener() method as it will enable tracking of the download progress.

  3. The PassioStatus mode is IS_READY_FOR_DETECTION -> still check the missingFiles because the SDK might be running with an older version of the files and newer need to be downloaded.

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Android SDK Docs

Passio Nutrition AI Android SDK Documentation

Getting Started

Welcome to the Passio Nutrition-AI React Native SDK!

This project provides React Native bindings for the Passio SDK.

Requirements:

  • React Native v0.68.0 or higher

  • Xcode 14.3 or higher

  • iOS 13 or higher

  • Android API level 26 or higher

  • Cocoapods 1.10.1 or higher

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Migration from SDK version 1.4.x to 2.x

Before you continue, please make sure you have a new key for version 2.x. The 1.4.x will not work with 2.x SDK!

Dependencies

Changes to the build.gradle file:

// TensorFlow    
implementation 'org.tensorflow:tensorflow-lite:2.8.0'
implementation 'org.tensorflow:tensorflow-lite-metadata:0.4.0'

// Barcode and OCR
implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.0'
implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.0.0'

API Changes

  • PassioMode.IS_AUTO_UPDATING renamed to PassioMode.IS_DOWNLOADING_MODELS

  • PassioMode.IS_READY_FOR_NUTRITION was removed.

  • PassioSDK.instance.setDownloadListener was removed. Instead, the download process is traced through the PassioStatusListener

  • FoodDetectionConfiguration.detectOCR renamed to detectPackagedFood

  • FoodDetectionConfiguration.detectLogos was removed.

  • PassioSDK.instance.lookupImageForFilename, lookupPassioIDFor, lookupAlternativesFor, lookupAllSiblingsFor, lookupParentFor, lookupAllFoodItemsInDatabase, lookupAllPassioIDs, lookupAllVisuallyRecognizableFoodItems were removed

  • PassioSDK.instance.searchForFood now returns a list of Pair<passioID, food name>

  • PassioSDK.instance.lookupImageFor was separated into two functions: 1. Local image lookup: lookupIconFor 2. Network image fetch: fetchIconFor

  • Nutrients in the PassioFoodItemData have been refactored from Double to UnitMass

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Food Details

The FoodDetailsFragment is a core component designed to display detailed information about a selected food item, including its name, icon, calories, macronutrients, and a list of micronutrients. Users can adjust serving sizes and quantities to view nutrient values accordingly.

The primary components in FoodDetailsFragment include:

  1. Food Item Information: Displays the name, icon, and serving size details.

  2. Serving Size Selector: Allows users to select a preferred serving size or unit.

  3. Nutritional Information: Shows calorie count and macronutrient breakdown (Carbs, Protein, Fat).

  4. Micronutrient List: Provides detailed nutrient information for vitamins, minerals, and other nutrients.

PassioFoodItem

The PassioFoodItem model is the foundation of food details and includes the following properties:

Property
Type
Description

id

String

Unique identifier for the food item.

refCode

String

Reference code for the item.

name

String

Name of the food item.

details

String

Description/details of the food.

iconId

String

ID for loading the food icon.

amount

PassioFoodAmount

Serving size and unit info.

ingredients

List<PassioIngredient>

List of ingredients in the food.

Example:

The food item name, icon, and details are rendered using PassioFoodItem's properties.

kotlinCopy codebinding.tvFoodName.text = foodItem.name
binding.foodIcon.loadPassioIcon(foodItem.iconId)

PassioFoodAmount

This model defines possible serving sizes and units. PassioFoodAmount includes:

Property
Type
Description

servingSizes

List<PassioServingSize>

Available serving sizes for the food

servingUnits

List<PassioServingUnit>

Units available for the food item

Example:

The servingSizes property is used to populate the RecyclerView adapter ServingSizeAdapter, allowing users to select different serving sizes.

PassioServingSize and PassioServingUnit

These classes are components of PassioFoodAmount that define the quantity and units for serving sizes:

PassioServingSize

Property
Type
Description

quantity

Double

Quantity of the unit

unitName

String

Name of the serving unit

PassioServingUnit

Property
Type
Description

unitName

String

Linked to the PassioServingSize

weight

UnitMass

Weight in grams or other units

Setting Serving Size Quantity

The setQuantityAndUnit function allows users to specify a desired serving quantity and unit, updating the displayed nutrient values dynamically.

foodItem.setQuantityAndUnit(quantity, foodItem.amount.selectedUnit)

FoodDetailsFragment.kt

package com.passioai.quickstart.ui.details

import ai.passio.passiosdk.passiofood.data.model.PassioFoodItem
import ai.passio.passiosdk.passiofood.data.model.PassioServingSize
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.KeyEvent
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import com.passioai.quickstart.databinding.FragmentFoodDetailsBinding
import com.passioai.quickstart.ui.details.MicroNutrient.Companion.getMicroNutrients
import com.passioai.quickstart.util.loadPassioIcon
import com.passioai.quickstart.util.twoDecimal

class FoodDetailsFragment : Fragment() {

    private lateinit var _binding: FragmentFoodDetailsBinding
    private val binding get() = _binding

    private val foodItem = passioFoodItem?.copy()


    companion object {

        private var passioFoodItem: PassioFoodItem? = null
        fun setPassioFoodItem(passioFoodItem: PassioFoodItem) {
            this.passioFoodItem = passioFoodItem
        }

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentFoodDetailsBinding.inflate(inflater, container, false)
        return binding.root
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        if (foodItem == null)
            return

        with(binding)
        {
            servingQty.setOnEditorActionListener { _, actionId, event ->
                // Check if the action is "done" or if the action is triggered by the keyboard
                if (actionId == EditorInfo.IME_ACTION_DONE ||
                    (event != null && event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)
                ) {
                    // Handle the done action here
                    try {
                        val quantity = servingQty.text.toString().toDouble()

                        foodItem.setQuantityAndUnit(
                            quantity,
                            foodItem.amount.selectedUnit
                        )
                        showFoodDetails()
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                    // You can do something with inputText, like showing a Toast
                    // Optionally clear the text
                    true // Return true to indicate the action was handled
                } else {
                    false // Return false to indicate the action was not handled
                }
            }

            rvServingSizes.adapter =
                ServingSizeAdapter(
                    foodItem.amount.servingSizes,
                    foodItem.amount.selectedUnit,
                    ::onServingSizeSelected
                )
        }

        showFoodDetails()

    }

    private fun onServingSizeSelected(passioServingSize: PassioServingSize) {
        foodItem?.setQuantityAndUnit(passioServingSize.quantity, passioServingSize.unitName)
        showFoodDetails()
    }

    @SuppressLint("SetTextI18n")
    private fun showFoodDetails() {
        if (foodItem == null) return

        val passioNutrients = foodItem.nutrientsSelectedSize()
        with(binding)
        {
            tvFoodName.text = foodItem.name
            servingSizeValue.text = "(${foodItem.amount.weightGrams()} g)"
            servingQty.setText(foodItem.amount.selectedQuantity.toString())
            foodIcon.loadPassioIcon(foodItem.iconId)
            tvCaloriesVal.text =
                "${passioNutrients.calories()?.value?.twoDecimal()} ${passioNutrients.calories()?.unit?.symbol}"
            tvCarbsVal.text = "${passioNutrients.carbs()?.gramsValue()?.twoDecimal()} g"
            tvProteinVal.text = "${passioNutrients.protein()?.gramsValue()?.twoDecimal()} g"
            tvFatVal.text = "${passioNutrients.fat()?.gramsValue()?.twoDecimal()} g"

            rvNutrients.adapter = NutrientsAdapter(passioNutrients.getMicroNutrients())
        }
    }


}

Check out the full code here. FULL CODE

Importing the Android SDK to a project

To include the Passio SDK in your project, you’ll need to:

  1. Add the SDK dependency using Maven: Ensure you have the correct repository and dependency in your build.gradle file.

In the build.gradle file add the PassioSDK dependency

dependencies {
    implementation 'ai.passio.passiosdk:nutrition-ai:3.2.1-3'
}

Dependencies

dependencies {
    // TensorFlow
    implementation 'org.tensorflow:tensorflow-lite:2.8.0'
    implementation 'org.tensorflow:tensorflow-lite-metadata:0.4.0'

    // CameraX
    def camerax_version = "1.3.4"
    implementation "androidx.camera:camera-core:$camerax_version"
    implementation "androidx.camera:camera-camera2:$camerax_version"
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    implementation "androidx.camera:camera-view:$camerax_version"
    implementation "androidx.camera:camera-extensions:$camerax_version"

    // Barcode and OCR
    implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.0'
    implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.0'
}

  1. Set up SDK Key: Obtain your SDK key from Passio’s Developer Portal. Add it to your BuildConfig or securely store it as an environment variable in your project.

Check out the full code here. FULL CODE

Quick Start Guide

This Quick Start Guide will help you integrate image recognition of the Passio SDK using your custom UI. The guide outlines essential steps and provides examples for a smooth implementation.

Check out the full code here. FULL CODE

Configure SDK and handle the result

In this example, we'll use a Fragment to set up the SDK configuration. Add a new Fragment class named ConfigFragment.kt to handle the configuration process.

Here is the ConfigFragment that configures the Passio SDK:

package com.passioai.quickstart.ui.config

import ai.passio.passiosdk.core.config.PassioConfiguration
import ai.passio.passiosdk.core.config.PassioMode
import ai.passio.passiosdk.passiofood.PassioSDK
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.passioai.quickstart.BuildConfig
import com.passioai.quickstart.databinding.FragmentConfigBinding

class ConfigFragment : Fragment() {

    private lateinit var _binding: FragmentConfigBinding
    private val binding get() = _binding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentConfigBinding.inflate(inflater, container, false)
        return _binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        configureSDK()
    }

    private fun configureSDK() {
        val passioConfiguration = PassioConfiguration(
            requireActivity().applicationContext,
            BuildConfig.SDK_KEY
        ).apply {
            sdkDownloadsModels = true
            debugMode = 0
            remoteOnly = true
        }

        PassioSDK.instance.configure(passioConfiguration) { passioStatus ->
            when (passioStatus.mode) {
                PassioMode.NOT_READY -> onSDKError("Not ready")
                PassioMode.FAILED_TO_CONFIGURE -> onSDKError(getString(passioStatus.error!!.errorRes))
                PassioMode.IS_READY_FOR_DETECTION -> onSDKReadyToUse()
                PassioMode.IS_BEING_CONFIGURED -> {
                    binding.tvSDKStatus.text = "Configuring..."
                }
                PassioMode.IS_DOWNLOADING_MODELS -> {
                    binding.tvSDKStatus.text = "Downloading models..."
                }
            }
        }
    }

    private fun onSDKError(message: String) {
        binding.tvSDKStatus.text = message
    }

    private fun onSDKReadyToUse() {
        binding.tvSDKStatus.text = "Configured and ready to use"
        findNavController().navigate(ConfigFragmentDirections.configToImageRecognitions())
    }
}

Key Points to Note

  1. PassioConfiguration:

    • Set up PassioConfiguration with your context and SDK key.

    • Enable sdkDownloadsModels to allow the SDK to download necessary models.

    • remoteOnly = true enables the SDK to access remote resources if required.

  2. PassioMode States: The SDK configuration callback provides different statuses:

    • NOT_READY: SDK is not ready, usually due to an issue.

    • FAILED_TO_CONFIGURE: Configuration failed.

    • IS_READY_FOR_DETECTION: SDK is configured and ready.

    • IS_BEING_CONFIGURED: SDK is currently being configured.

    • IS_DOWNLOADING_MODELS: SDK is downloading required models.

  3. Navigation on Ready:

    • On successful configuration, the app navigates to ImageRecognitionsFragment.

Layout Setup

To display the configuration status, add a TextView (e.g., tvSDKStatus) in the fragment’s XML layout file.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".ui.config.ConfigFragment">


    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvSDKStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Hello World!" />

</androidx.constraintlayout.widget.ConstraintLayout>

Error Handling

The SDK setup can fail due to configuration or network issues. The onSDKError function in the ConfigFragment updates the UI with the relevant error message to help troubleshoot.

Check out the full code here. FULL CODE

Troubleshooting on Android

Getting started

Welcome to the Passio Nutrition-AI Android SDK!

Food detection session

The SDK can detect 3 different categories: VISUAL, BARCODE and PACKAGED. The VISUAL recognition is powered by Passio's neural network and is used to recognize over 3000 food classes. BARCODE, as the name suggests, can be used to scan a barcode located on a branded food. Finally, PACKAGED can detect the name of a branded food. To choose one or more types of detection, a FoodDetectionConfiguration object is defined and the corresponding fields are set. The VISUAL recognition works automatically.

val options = FoodDetectionConfiguration().apply {
    detectBarcodes = true
}

To start the Food Recognition process a FoodRecognitionListener also has to be defined. The listener serves as a callback for all the different food detection processes defined by the FoodDetectionConfiguration.

private val foodRecognitionListener = object : FoodRecognitionListener {
    override fun onRecognitionResults(
        candidates: FoodCandidates,
        image: Bitmap?,
        nutritionFacts: PassioNutritionFacts?
    ) {
        val detectedCandidates = candidates.detectedCandidates!!
        val barcodeCandidates = candidates.barcodeCandidates!!
        val passioID = detectedCandidates.first().passioID
        val foodName = PassioSDK.instance.lookupNameFor(passioID)
        Toast.makeText(requestContext(), foodName, Toast.LENGTH_SHORT).show()
    }
}

Only the corresponding candidate lists will be populated (e.g. if you define detection types VISUAL and BARCODE, you will never receive a packagedFoodCandidates list in this callback).

Using the listener and the detection options start the food detection by calling the startFoodDetection method of the SDK.

override fun onStart() {
    super.onStart()
    PassioSDK.instance.startFoodDetection(foodRecognitionListener)
}

Stop the food recognition in the onStop() lifecycle callback.

override fun onStop() {
    PassioSDK.instance.stopFoodDetection()
    super.onStop()
}

Try to run the code containing the foodListener defined above. Point the phone at the image below and see if you are getting the correct food printed on the screen (it should be red apples, but if it isn't don't worry, we'll cover the data structure later).

apple_img

When starting the food detection with the FoodRecognitionListener as the callback, on every frame analyzed you will receive a FoodCandidates object. This is the structure of that data class:

data class FoodCandidates(
    val detectedCandidates: List<DetectedCandidate>? = null,
    val barcodeCandidates: List<BarcodeCandidate>? = null,
    val packagedFoodCandidates: List<PackagedFoodCandidate>? = null
)

You can see the structure of all the detection classes in the file called PassioAPI.kt

Depending on how you structured the FoodDetectionConfiguration object, some fields of the FoodCandidates object might be null. If the field is null it means that that type of detection has not been run. On the other hand, if the detection process ran to completion and the list is empty, then that type of detection could not recognize anything from the given frame.

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

API Reference

PassioSDK provide support to below api's

RecognizeImageRemote

Create an ImageRecognitionsFragment to handle image capture, gallery selection, and food item recognition.

Step 1: Select or Capture Image

For selecting or capturing an image, use PhotoPicker and PhotoCapture functions in ImageRecognitionsFragment. Refer to Android's official documentation for detailed instructions:

Step 2: Recognize Food Item

The fetchResult() method sends the selected image to the Passio SDK for recognition.

PassioSDK.instance.recognizeImageRemote

The recognizeImageRemote function takes an image in Bitmap format, analyzes it remotely using Passio's servers, and returns a list of food items recognized within the image. This function is asynchronous and utilizes a callback to return the result once the analysis is complete.

Output

The function returns a list of PassioAdvisorFoodInfo objects through a callback

The structure of PassioAdvisorFoodInfo is as follows:

  • recognisedName (String): The name of the recognized food item.

  • portionSize (String): Suggested portion size for the recognized food.

  • weightGrams (Double): Estimated weight of the food item in grams.

  • foodDataInfo (PassioFoodDataInfo?): An optional field containing more detailed nutritional information if available.

  • packagedFoodItem (PassioFoodItem?): An optional field containing packaged food item data if the recognized item is a packaged product.

  • resultType (PassioFoodResultType): The type of result, indicating if the food is freshly prepared, packaged, or a general item.

Step 3: Display Food Details

Navigate to a details screen upon item selection.

PassioSDK.instance.fetchFoodItemForDataInfo

The fetchFoodItemForDataInfo function retrieves detailed food information from Passio's database based on provided data fields. This function is also asynchronous and accepts a callback that returns a PassioFoodItem object with the full nutritional profile of the requested food item.

Input Parameters

  • foodDataInfo (PassioFoodDataInfo): A data object containing metadata about the food item, including its ID and a preview of its nutrition details.

  • servingQuantity (Double): The quantity or portion size of the food.

  • servingUnit (String): The unit of measurement for the serving size, such as grams, ounces, or pieces.

Output

The function returns a PassioFoodItem through the callback. This object contains the full nutritional breakdown of the food item, which can include:

  • Nutritional values (e.g., calories, protein, fat, carbohydrates)

  • Serving size details

  • Icons and images associated with the item

Check out the full code here.

searchForFood & searchForFoodSemantic

Search the database of foods with a given search term.

searchForFood now returns PassioSearchResult and a list of search options. The PassioSearchResult represent a specific food item associated with the search term, while search options provide a list of possible suggested search terms connected to the input term.

Import the PassioSDK

You can utilize the searchForFood API from the SDK to retrieve food records based on a search string. This API returns a PassioSearchResult object containing both results and alternatives.

The results property holds an array of food search results, while the alternatives property provides additional suggestions or alternative options if available.

How to use it?

How to get PassioFoodItem using SearchFood Response?

Installation

1. Create a with read:packages access selected.

2. Create an .npmrc file in the root of your project replacing GITHUB_ACCESS_TOKEN with the token you've created.

3. Add the Passio SDK dependency to your package.json and run npm install or yarn.

or

4. Ensure the native dependencies are linked to your app.

If your project uses Expo, follow the steps below to work with a rebuild. Ignore the following command if your project is not based on Expo.

For iOS, run pod install.

If pod install is failed with error message Specs satisfying the ReactNativePassioSDK (from ../node_modules/@passiolife/nutritionai-react-native-sdk-v3) dependency were found, but they required a higher minimum deployment target. , ensure Podfile is configured with minimum ios version '13.0'

Also for Android, make sure the android project is configured with minSdkVersion 26 or above (check the project's build.gradle file).

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

If your project uses Expo, follow the steps below to work with a rebuild:

SDK Initialization and Configuration

For the SDK to work, it requires a proper license key.

  • The license key can be requested at support@passiolife.com

SDK Initialization

When your component mounts, configure the SDK using your Passio provided developer key and start food detection.

autoUpdate: true : Set to true to enable the download of AI models from Passio's servers.

`remoteOnly:true` With this flag enabled, the SDK won't download the files needed for local recognition. In this case only remote recognition is possible

The possible states of the SDK after calling configure. Switch on sdkStatus.mode to access the data associated with each state.

MODE: notReady, isReadyForDetection isBeingConfigured and error

notReady : SDK is not ready due to missing model files. Please download the specified file and call configure again, passing in the localFileURLs of the downloaded files.

isReadyForDetection: SDK configured successfully. This status much be reached before calling startFoodDetection

error: SDK failed to configure in an unrecoverable way. Please read the error message for more information

Proxy

The SDK uses the proxyUrl as the base URL for all network calls and appends the specified proxyHeaders to each request. When proxyUrl is set, the SDK functions exclusively in a remoteOnly mode.

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

recognizeImageRemote

Recognize and your meals from a single food image, or use multiple food images.

Import

Example

It's return []

  /**
   * Configure the SDK with the given options.
   *
   * @param options - The configuration options
   * @returns A `Promise` resolving with a `PassioStatus` indicating the current state of the SDK.
   */
  configure(options: ConfigurationOptions): Promise<PassioStatus>
 /**
   * Prompt the user for camera authorization if not already granted.
   * @remarks Your app's Info.plist must inclue an `NSCameraUsageDescription` value or this method will crash.
   * @returns A `Promise` resolving to `true` if authorization has been granted or `false` if not.
   */
  requestCameraAuthorization(): Promise<boolean>
 /**
   * This method indicating downloading file status if model download from passio server.
   * @param callback - A callback to receive for dowloading file lefts from queue.
   * @param callback - A callback to receive dowload file failed for some reason.
   * @returns A `Callback` that should be retained by the caller while dowloading is running. Call `remove` on the callback to terminate listeners and relase from memory.
   */
  onDowloadingPassioModelCallBacks: (
    downloadModelCallBack: DownloadModelCallBack
  ) => Callback
  /**
   * Begin food detection using the device's camera.
   * @param options - An object to determine which types of scanning should be performed.
   * @param callback - A callback to repeatedly receive food detection events as they occur.
   * @returns A `Subscription` that should be retained by the caller while food detection is running. Call `remove` on the subscription to terminate food detection.
   */
  startFoodDetection(
    options: FoodDetectionConfig,
    callback: (detection: FoodDetectionEvent) => void
  ): Subscription
/**
   * Begin nutrition fact label detection using the device's camera.
   * @param callback - A callback to repeatedly receive nutrition detection events as they occur.
   * @returns A `Subscription` that should be retained by the caller while nutrition detection is running. Call `remove` on the subscription to terminate nutrition detection.
   */
  startNutritionFactsDetection(
    callback: (detection: NutritionDetectionEvent) => void
  ): Subscription
 /**
   * Look up the food item result for a given Passio ID.
   * @param passioID - The Passio ID for the  query.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchFoodItemForPassioID(passioID: PassioID): Promise<PassioFoodItem | null>
 /**
   * Look up the food item result for a given by barcode or packagedFoodCode.
   * @param barcode  - barcode for the  query.
   * or
   * @param packageFoodCode  - packageFoodCode for the query.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchFoodItemForProductCode(
    code: Barcode | PackagedFoodCode
  ): Promise<PassioFoodItem | null>

  /**
   * Search the database of foods with a given search term.
   * @param searchQuery - The search term to match against food item names.
   * @returns A `Promise` resolving to an array of food item names.
   */
  searchForFood(searchQuery: string): Promise<PassioSearchResult | null>
  /**
   * Search for food semantic will return a list of alternate search and search result
   * @param searchQuery - User typed text.
   * @returns A `Promise` resolving to an array of food item result.
   */
  searchForFoodSemantic(searchQuery: string): Promise<PassioSearchResult | null>
  /**
   * gives a list of predicted next ingredients based on the current list of ingredients in the recipe
   * @param currentIngredients - List of food ingredients name.
   * @returns A `Promise` resolving to an array of food data info.
   */
  predictNextIngredients(
    currentIngredients: string[]
  ): Promise<PassioFoodDataInfo[] | null>
/**
   * Data info of the food with a given result.
   * @param passioFoodDataInfo - Provide `PassioFoodDataInfo` object get `PassioFoodItem` detail.
   * @param servingQuantity - Provide `servingQuantity` number to get `PassioFoodItem` detail.
   * @param servingUnit - Provide `servingUnit` unit to get `PassioFoodItem` detail.
   * @returns A `Promise` resolving to `PassioFoodItem` detail.
   */
  fetchFoodItemForDataInfo(
    passioFoodDataInfo: PassioFoodDataInfo,
    servingQuantity?: number,
    servingUnit?: String
  ): Promise<PassioFoodItem | null>

  /**
   * Retrieving food info using image uri,
   * Smaller resolutions will result in faster response times
   * while higher resolutions should provide more accurate results
   * @param imageUri - The local URI of the image.
   * @param message - An optional message to indicate the context of the image.
   * @param resolution - enables the caller to set the target resolution of the image uploaded to the server, Default is RES_512
   * @returns A `Promise` resolving to an array of `PassioAdvisorFoodInfo`.
   */

  recognizeImageRemote(
    imageUri: string,
    message?: string,
    resolution?: PassioImageResolution
  ): Promise<PassioAdvisorFoodInfo[] | null>

  /**
   * Retrieving nutrition facts using image uri,
   * Smaller resolutions will result in faster response times
   * while higher resolutions should provide more accurate results
   * @param imageUri - The local URI of the image.
   * @param resolution - enables the caller to set the target resolution of the image uploaded to the server, Default is RES_512
   * @returns A `Promise` resolving to an array of `PassioFoodItem`.
   */

  recognizeNutritionFactsRemote(
    imageUri: string,
    resolution?: PassioImageResolution
  ): Promise<PassioFoodItem | null>
 /**
   * @param text - Provide `text` to get `PassioSpeechRecognitionModel` list detail.
   * @returns A `Promise` resolving to `PassioSpeechRecognitionModel` list.
   */
  recognizeSpeechRemote(
    text: string
  ): Promise<PassioSpeechRecognitionModel[] | null>
  /**
   * This method adds personalized alternative to local database.
   * Status: This method is experimental and only available in the iOS SDK.
   * @param personalizedAlternative - The personalized alternative to add.
   * @returns A `boolean` value indicating if the personalized alternative was added successfully.
   */
  addToPersonalization(
    visualCandidate: DetectedCandidate,
    alternative: DetectedCandidate
  ): boolean

  /**
   * This method fetches tags for a given Passio ID.
   * @param passioID - The Passio ID for the tags query.
   * @returns A `string` array of tags if the record exists in the database or `null` if not.
   */
  fetchTagsForPassioID(passioID: PassioID): Promise<string[]>
/**
   * Look up the food item result for a given refCode.
   * @param refCode - The refCode for the  query.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchFoodItemForRefCode(refCode: RefCode): Promise<PassioFoodItem | null>
  /**
   * fetch a map of nutrients for a 100 grams of a specific food item
   * @param passioID - The Passio ID for the attributes query.
   * @returns A `Promise` resolving to a `PassioNutrient` object if the record exists in the database or `null` if not.
   */
  fetchNutrientsFor(passioID: PassioID): Promise<PassioNutrient[] | null>
  /**
   * fetch a suggestions for particular meal time  'breakfast' | 'lunch' | 'dinner' | 'snack' and returning results.
   * @param mealTime - 'breakfast' | 'lunch' | 'dinner' | 'snack',
   * @returns A `Promise` resolving to a `PassioFoodDataInfo` object if the record exists in the database or `null` if not.
   */
  fetchSuggestions(
    mealTime: PassioMealTime
  ): Promise<PassioFoodDataInfo[] | null>
  /**
   * fetch list of all meal Plans
   * @returns A `Promise` resolving to a `PassioMealPlan` array if the record exists in the database or `null` if not.
   */
  fetchMealPlans(): Promise<PassioMealPlan[] | null>

  /**
   * fetch list of all meal Plan item
   * @param mealPlanLabel - query for type of mealPlan.
   * @param day - for which day meal plan is needed
   * @returns A `Promise` resolving to a `PassioMealPlanItem` array if the record exists in the database or `null` if not.
   */
  fetchMealPlanForDay(
    mealPlanLabel: string,
    day: number
  ): Promise<PassioMealPlanItem[] | null>
/**
   * fetch a map of nutrients for passio Food Item with calculated weight
   * @param passioFoodItem - The passioFoodItem for the attributes query.
   * @param weight - The weight for the query.
   * @returns A `Promise` resolving to a `PassioNutrient` object if the record exists in the database or `null` if not.
   */
  fetchNutrientsForPassioFoodItem(
    passioFoodItem: PassioFoodItem,
    weight: UnitMass
  ): PassioNutrients

  /**
   * fetch a map of nutrients for passio Food Item with default selected size
   * @param passioFoodItem - The passioFoodItem for the attributes query.
   * @returns A `Promise` resolving to a `PassioNutrient` object if the record exists in the database or `null` if not.
   */

  fetchNutrientsSelectedSizeForPassioFoodItem(
    passioFoodItem: PassioFoodItem
  ): PassioNutrients

  /**
   * fetch a map of nutrients for passio Food Item with reference weight Unit("gram",100)
   * @param passioFoodItem - The passioFoodItem for the attributes query.
   * @returns A `Promise` resolving to a `PassioNutrient` object if the record exists in the database or `null` if not.
   */
  fetchNutrientsReferenceForPassioFoodItem(
    passioFoodItem: PassioFoodItem
  ): PassioNutrients
 /**
   * @param passioID  - passioID for the query.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchFoodItemLegacy(passioID: PassioID): Promise<PassioFoodItem | null>
  /**
   * Added support for localized content.
   * with a two digit ISO 639-1 language code will transform the food names and serving sizes in the SDK responses
   *
   * @param languageCode - with a two digit ISO 639-1 language code
   */
  updateLanguage(languageCode: string): Promise<Boolean>
/**
   * fetch list of possible hidden ingredients for a given food name.
   * @param foodName - query for foodName.
   * @returns A `Promise` resolving to a `PassioFetchAdvisorInfoResult`.
   */
  fetchHiddenIngredients(
    foodName: string
  ): Promise<PassioFetchAdvisorInfoResult>
/**
   * fetch list of possible visual alternatives for a given food name.
   * @param foodName - query for foodName.
   * @returns A `Promise` resolving to a `PassioFetchAdvisorInfoResult`.
   */
  fetchVisualAlternatives(
    foodName: string
  ): Promise<PassioFetchAdvisorInfoResult>
 /**
   * fetch list of possible ingredients if a more complex food for a given food name.
   * @param foodName - query for foodName.
   * @returns A `Promise` resolving to a `PassioFetchAdvisorInfoResult`.
   */
  fetchPossibleIngredients(
    foodName: string
  ): Promise<PassioFetchAdvisorInfoResult>
  /**
   * Added support for localized content.
   * with a two digit ISO 639-1 language code will transform the food names and serving sizes in the SDK responses
   *
   * @param languageCode - with a two digit ISO 639-1 language code
   */
  updateLanguage(languageCode: string): Promise<Boolean>
/**
   * setAccountListener to track account usage updates. Used to monitor total monthly
   * tokens, used tokens and how many tokens the last request used.
   */
  setAccountListener: (passioAccountListener: PassioAccountListener) => Callback
  /**
   * Use this method to report incorrect food item
   * Precondition: Either `refCode` or `productCode` must be present
   * @param refCode - Reference code of food item
   * @param productCode - Product code
   * @param notes - Note if any (optional)
   * @returns It returns `PassioResult` that can be either an `errorMessage` or the `boolean` noting the success of the operation.
   */
  reportFoodItem(report: PassioReport): Promise<PassioResult>
import {
  PassioSDK,
  type FoodSearchResult,
} from '@passiolife/nutritionai-react-native-sdk-v3'

  /**
   * Search the database of foods with a given search term.
   * @param searchQuery - The search term to match against food item names.
   * @returns A `Promise` resolving to an array of food item names.
   */
   searchForFood(searchQuery: string): Promise<PassioSearchResult | null>
  /**
   * Search for food semantic will return a list of alternate search and search result
   * @param searchQuery - User typed text.
   * @returns A `Promise` resolving to an array of food item result.
   */
  searchForFoodSemantic(searchQuery: string): Promise<PassioSearchResult | null>
      try {
          // Fetch food results from the PassioSDK based on the query
          const searchFoods = await PassioSDK.searchForFood(query)
        } catch (error) {
          // Handle errors, e.g., network issues or API failures
        }
try {
          // Fetch food results from the PassioSDK based on the query
          const searchFoods = await PassioSDK.searchForFoodSemantic(query)
        } catch (error) 
          // Handle errors, e.g., network issues or API failures
        }
   try {
          // Fetch food results from the PassioSDK based on the query
          const passioFoodItem = await PassioSDK.fetchFoodItemForDataInfo(passioFoodDataInfo)
        } catch (error) {
          // Handle errors, e.g., network issues or API failures
        }
const [loadingState, setLoadingState] = useState();
const [isCameraAuthorized, setCameraAuthorized] = useState(false);


  useEffect(() => {
    async function configure() {
      try {
        const status = await PassioSDK.configure({
          key: key,
          debugMode: debugMode,
          autoUpdate: autoUpdate,
          remoteOnly: true,
        })
        switch (status.mode) {
          case 'notReady':
            return
          case 'isReadyForDetection':
            setLoadingState('ready')
            return
          case 'error':
            console.error(`PassioSDK Error ${status.errorMessage}`)
            setLoadingState('error')
            return
        }
      } catch (err) {
        console.error(`PassioSDK Error ${err}`)
        setLoadingState('error')
      }
    }
    configure()
  }, [key, debugMode, autoUpdate])


  
 //request camera permission
useEffect(() => {
  PassioSDK.requestCameraAuthorization().then((cameraAuthorized) => {
    setCameraAuthorized(cameraAuthorized);
  });
}, []);
proxy: {
            proxyUrl: '${base_url}',
            proxyHeaders: { ['Authentication']: '${token}' },
        },
package com.passioai.quickstart.ui.recognitions

import ai.passio.passiosdk.passiofood.PassioSDK
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import com.passioai.quickstart.databinding.FragmentImageRecognitionsBinding
import com.passioai.quickstart.util.PhotoCaptureManager
import com.passioai.quickstart.util.PhotoPickerManager
import com.passioai.quickstart.util.uriToBitmap

class ImageRecognitionsFragment : Fragment() {

    private lateinit var _binding: FragmentImageRecognitionsBinding
    private val binding get() = _binding
    private val photoPickerManager = PhotoPickerManager()
    private val photoCaptureManager = PhotoCaptureManager()
    private var bitmap: Bitmap? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentImageRecognitionsBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        photoPickerManager.init(this) { uris -> onImagePicked(uris) }
        photoCaptureManager.init(this) { uri -> onImageCaptured(uri) }

        binding.btnCamera.setOnClickListener { photoCaptureManager.capturePhoto() }
        binding.btnGallery.setOnClickListener { photoPickerManager.pickSingleImage() }
        binding.btnSubmit.setOnClickListener { fetchResult() }
    }

    private fun onImagePicked(uris: List<Uri>) {
        if (uris.isNotEmpty()) {
            uriToBitmap(requireContext(), uris[0])?.let {
                bitmap = it
                binding.ivPhoto.load(bitmap)
            }
        }
    }

    private fun onImageCaptured(uri: Uri) {
        uriToBitmap(requireContext(), uri)?.let {
            bitmap = it
            binding.ivPhoto.load(bitmap)
        }
    }
}
private fun fetchResult() {
    if (bitmap == null) {
        Toast.makeText(requireContext(), "Please select an image", Toast.LENGTH_SHORT).show()
        return
    }
    binding.loader.isVisible = true
    PassioSDK.instance.recognizeImageRemote(bitmap!!) { result ->
        binding.loader.isVisible = false
        val adapter = ImageRecognitionsAdapter(result) { info -> onShowDetails(info) }
        binding.list.adapter = adapter
    }
}
private fun onShowDetails(passioAdvisorFoodInfo: PassioAdvisorFoodInfo) {
        if (passioAdvisorFoodInfo.foodDataInfo != null) {

            PassioSDK.instance.fetchFoodItemForDataInfo(passioAdvisorFoodInfo.foodDataInfo!!, passioAdvisorFoodInfo.foodDataInfo!!.nutritionPreview.servingQuantity, passioAdvisorFoodInfo.foodDataInfo!!.nutritionPreview.servingUnit){ passioFoodItem->
                passioFoodItem?.let {
                    navigateToDetails(it)
                }
            }
        }
        else if (passioAdvisorFoodInfo.packagedFoodItem != null) {
            navigateToDetails(passioAdvisorFoodInfo.packagedFoodItem!!)

        }

}

private fun navigateToDetails(foodItem: PassioFoodItem) {
    FoodDetailsFragment.setPassioFoodItem(foodItem)
    findNavController().navigate(ImageRecognitionsFragmentDirections.imageRecognitionsToFoodDetails())
}
Image Picker (Android)
Camera Intent (Android)
FULL CODE
//npm.pkg.github.com/:_authToken=GITHUB_ACCESS_TOKEN
@passiolife:registry=https://npm.pkg.github.com
npm install @passiolife/nutritionai-react-native-sdk-v3
yarn add @passiolife/nutritionai-react-native-sdk-v3
npx expo prebuild --clean
cd ios; pod install
platform :ios, '13.0'
buildscript {
    ext {
        ...
        minSdkVersion = 26
        ...
    }
}
Github Personal Access Token (classic)
with the following lines
import {
  PassioSDK,
  type PassioAdvisorFoodInfo,
  type PassioFoodDataInfo,
} from '@passiolife/nutritionai-react-native-sdk-v3'

import { launchImageLibrary } from 'react-native-image-picker'
 
  /**
   * Retrieving food info using image uri,
   * Smaller resolutions will result in faster response times
   * while higher resolutions should provide more accurate results
   * @param imageUri - The local URI of the image.
   * @param message - An optional message to indicate the context of the image.
   * @param resolution - enables the caller to set the target resolution of the image uploaded to the server, Default is RES_512
   * @returns A `Promise` resolving to an array of `PassioAdvisorFoodInfo`.
   */

  recognizeImageRemote(
    imageUri: string,
    message?: string,
    resolution?: PassioImageResolution
  ): Promise<PassioAdvisorFoodInfo[] | null>

 const onScanImage = useCallback(async () => {
    try {
      const { assets } = await launchImageLibrary({ mediaType: 'photo' })
      if (assets) {
        setLoading(true)
        setPassioSpeechRecognitionModel(null)
        PassioSDK.recognizeImageRemote(
          assets?.[0].uri?.replace('file://', '') ?? ''
        )
          .then(async (candidates) => {
            setPassioSpeechRecognitionModel(candidates)
          })
          .catch(() => {
            Alert.alert('Unable to recognized this image')
          })
          .finally(() => {
            setLoading(false)
          })
      }
    } catch (err) {
      setLoading(false)
    }
  }, [])
PassioAdvisorFoodInfo

fetchFoodItemForRefCode

Import

import {
  RefCode,
  type PassioFoodItem,
  type PackagedFoodCode,
} from '@passiolife/nutritionai-react-native-sdk-v3'

/**
   * Look up the food item result for a given refCode.
   * @param refCode - The refCode for the  query.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchFoodItemForRefCode(
    refCode: RefCode
  ): Promise<PassioFoodItem | null>

Example

Its' return PassioFoodItem

const getFoodItem = async (code: RefCode) => {
  try {
    const passioFoodItem = await PassioSDK.fetchFoodItemForRefCode(code)
  } catch (error) {
    console.log('error', error)
  }
}

configure & requestCameraAuthorization

Import

import {
  PassioSDK,
  type PassioFoodItem,
  type PackagedFoodCode,
  type PassioSearchResult,
  type FoodSearchResult,
} from '@passiolife/nutritionai-react-native-sdk-v3'
 /**
   * Configure the SDK with the given options.
   *
   * @param options - The configuration options
   * @returns A `Promise` resolving with a `PassioStatus` indicating the current state of the SDK.
   */
  configure(options: ConfigurationOptions): Promise<PassioStatus>
  
  /**
   * Prompt the user for camera authorization if not already granted.
   * @remarks Your app's Info.plist must inclue an `NSCameraUsageDescription` value or this method will crash.
   * @returns A `Promise` resolving to `true` if authorization has been granted or `false` if not.
   */
  requestCameraAuthorization(): Promise<boolean>

Example

const [isReady, setIsReady] = useState(false);
const [isCameraAuthorized, setCameraAuthorized] = useState(false);

// Effect to configure the SDK and request camera permission

useEffect(() => {
    const callBack = PassioSDK.configure(
      {
        key: "your passio license key",
        debugMode: debugMode,
        autoUpdate: autoUpdate,
      },
      (status: PassioStatus) => {
        switch (status.mode) {
          case 'notReady':
            return
          case 'isBeingConfigured':
            return
          case 'isDownloadingModels':
            return
          case 'isReadyForDetection':
            setIsReady(true)
            return
          case 'error':
            setIsReady(false)
            return
        }
      }
    )
    return () => callBack.remove()
  }, [key, debugMode, autoUpdate])

useEffect(() => {
  PassioSDK.requestCameraAuthorization().then((cameraAuthorized) => {
    setCameraAuthorized(cameraAuthorized);
  });
}, []);

fetchFoodItemForProductCode

Import

import {
  PassioSDK,
  type PassioFoodItem,
  type PackagedFoodCode,
} from '@passiolife/nutritionai-react-native-sdk-v3'

  /**
   * Query Passio's web service for nutrition attributes given an package food identifier.
   * @param packagedFoodCode - The code identifier for the attributes query, taken from the list of package food candidates on a `FoodDetectionEvent`.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchFoodItemForProductCode(
    code: Barcode | PackagedFoodCode
  ): Promise<PassioFoodItem | null>

Example

Its' return PassioFoodItem

const getFoodItem = async (code: Barcode | PackagedFoodCodeackagedFoodCode) => {
  try {
    const passioFoodItem = await PassioSDK.fetchFoodItemForProductCode(code)
  } catch (error) {
    console.log('error', error)
  }
}

FoodDetectionEvent

When starting food detection with PassioSDK.startFoodDetection you will receive a FoodDetectionEvent. This is the structure of that data class:

export interface FoodDetectionEvent {
    /**
     * A collection of food candidates detected by the models.
     */
    candidates?: FoodCandidates;
}
/**
 * A collection of food candidates detected by the models.
 */
export interface FoodCandidates {
  /**
   * Food candidate results from visual scanning. The array is sorted by confidence, with the most confident result at index 0.
   */
  detectedCandidates: DetectedCandidate[]

  /**
   * Food candidate results from barcode scanning.
   */
  barcodeCandidates?: BarcodeCandidate[]

  /**
   * Food candidate results from packagedFoodCode scanning.
   */
  packagedFoodCode?: PackagedFoodCode[]
}

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

Fetch Nutrients

get nutrients value using PassioFoodItemNutrients

To retrieve all macro and micronutrient values using fetchNutrientsForPassioFoodItem, by passing a passioFoodItem, you would typically follow these steps:

 /**
   * fetch a map of nutrients for passio Food Item with calculated weight
   * @param passioFoodItem - The passioFoodItem for the attributes query.
   * @param weight - The weight for the query.
   * @returns A `Promise` resolving to a `PassioNutrient` object if the record exists in the database or `null` if not.
   */
  fetchNutrientsForPassioFoodItem(
    passioFoodItem: PassioFoodItem,
    weight: UnitMass
  ): PassioNutrients
 /**
   * fetch a map of nutrients for passio Food Item with default selected size
   * @param passioFoodItem - The passioFoodItem for the attributes query.
   * @returns A `Promise` resolving to a `PassioNutrient` object if the record exists in the database or `null` if not.
   */

  fetchNutrientsSelectedSizeForPassioFoodItem(
    passioFoodItem: PassioFoodItem
  ): PassioNutrients
/**
   * fetch a map of nutrients for passio Food Item with reference weight Unit("gram",100)
   * @param passioFoodItem - The passioFoodItem for the attributes query.
   * @returns A `Promise` resolving to a `PassioNutrient` object if the record exists in the database or `null` if not.
   */
  fetchNutrientsReferenceForPassioFoodItem(
    passioFoodItem: PassioFoodItem
  ): PassioNutrients
PassioSDK.fetchNutrientsForPassioFoodItem(passioFoodItem,weight)
PassioSDK.fetchNutrientsSelectedSizeForPassioFoodItemFoodItem(passioFoodItem)
PassioSDK.fetchNutrientsReferenceForPassioFoodItem(passioFoodItem)

You will receive PassioNutrients

interface PassioNutrients {
  weight?: UnitMass
  vitaminA?: UnitMass
  alcohol?: UnitMass
  calcium?: UnitMass
  calories?: UnitMass
  carbs?: UnitMass
  cholesterol?: UnitMass
  fat?: UnitMass
  fibers?: UnitMass
  iodine?: UnitMass
  iron?: UnitMass
  magnesium?: UnitMass
  monounsaturatedFat?: UnitMass
  phosphorus?: UnitMass
  polyunsaturatedFat?: UnitMass
  potassium?: UnitMass
  protein?: UnitMass
  satFat?: UnitMass
  sodium?: UnitMass
  sugarAlcohol?: UnitMass
  sugars?: UnitMass
  sugarsAdded?: UnitMass
  transFat?: UnitMass
  vitaminB12?: UnitMass
  vitaminB12Added?: UnitMass
  vitaminB6?: UnitMass
  vitaminC?: UnitMass
  vitaminD?: UnitMass
  vitaminE?: UnitMass
  vitaminEAdded?: UnitMass
}

recognizeSpeechRemote

Import

import {
  PassioSDK,
  type PassioSpeechRecognitionModel,
  type PassioFoodDataInfo,
} from '@passiolife/nutritionai-react-native-sdk-v3'
/**
   * @param text - Provide `text` to get `PassioSpeechRecognitionModel` list detail.
   * @returns A `Promise` resolving to `PassioSpeechRecognitionModel` list.
   */
  recognizeSpeechRemote(
    text: string
  ): Promise<PassioSpeechRecognitionModel[] | null>

Example

It's return PassioSpeechRecognitionModel[]

const recognizeSpeech = useCallback(
    async (text: string) => {
      try {
          // Fetch food results from the PassioSDK based on the query
          const recognizedModel = await PassioSDK.recognizeSpeechRemote(text)
          setPassioSpeechRecognitionModel(recognizedModel)
        } catch (error) {
          // Handle errors, e.g., network issues or API failures
        } finally {
          // Reset loading state to indicate the end of the search
        }
    },
    [cleanSearch]
  )

onDowloadingPassioModelCallBacks

This method indicating downloading file status if model download from passio server.

Import the PassioSDK

import {
  PassioSDK,
  type Callback,
  type DownloadingError,
  type CompletedDownloadingFile
} from '@passiolife/nutritionai-react-native-sdk-v3'
  /**
   * This method indicating downloading file status if model download from passio server.
   * @param callback - A callback to receive for dowloading file lefts from queue.
   * @param callback - A callback to receive dowload file failed for some reason.
   * @returns A `Callback` that should be retained by the caller while dowloading is running. Call `remove` on the callback to terminate listeners and relase from memory.
   */
  onDowloadingPassioModelCallBacks: (
    downloadModelCallBack: DownloadModelCallBack
  ) => Callback

Example


const [leftFile, setDownloadingLeft] = useState<number | null>(null)
  
 useEffect(() => {
    const callBacks = PassioSDK.onDowloadingPassioModelCallBacks({
      completedDownloadingFile: ({ filesLeft }: CompletedDownloadingFile) => {
        setDownloadingLeft(filesLeft)
      },
      downloadingError: ({ message }: DownloadingError) => {
        console.log('DownloadingError ===>', message)
      },
    })
    return () => callBacks.remove()
  }, [])

detectFoodFromImageURI

The image uri to detect food.

Import the PassioSDK

import {
  PassioSDK,
  type FoodCandidates,
} from '@passiolife/nutritionai-react-native-sdk-v3'
  /**
   * This method detect food from image uri.
   * @param imageUri - The image uri to detect food.
   * @returns A `Promise` resolving to a `FoodCandidates` object if the record exists in the database or `null` if not.
   */
  detectFoodFromImageURI(imageUri: string): Promise<FoodCandidates | null>

Example

  const [candidates, setCandidates] = useState<FoodCandidates | null>()
  
  useEffect(() => {
    PassioSDK.detectFoodFromImageURI(imageUri).then((foodCandidates) => {
      setCandidates(foodCandidates)
    })
  }, [props.imageUri])

fetchFoodItemForPassioID

Import

import {
  PassioSDK,
  type PassioFoodItem,
  type PassioID,
} from '@passiolife/nutritionai-react-native-sdk-v3'
/**
   * Look up the nutrition attributes for a given Passio ID.
   * @param passioID - The Passio ID for the attributes query.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchFoodItemForPassioID(passioID: PassioID): Promise<PassioFoodItem | null>

Example

It's return PassioFoodItem

const getFoodItem = async (passioID: PassioID) => {
  try {
    const passioFoodItem = await PassioSDK.fetchFoodItemForPassioID(passioID)
  } catch (error) {
    console.log('error', error)
  }
}

fetchFoodItemForDataInfo

It's provide whole passio food item using short passioFoodDataInfo Object.

Import

import {
  RefCode,
  type PassioFoodItem,
  type PackagedFoodCode,
} from '@passiolife/nutritionai-react-native-sdk-v3'

  /**
   * Data info of the food with a given result.
   * @param passioFoodDataInfo - Provide `PassioFoodDataInfo` object get `PassioFoodItem` detail.
   * @param servingQuantity - Provide `servingQuantity` number to get `PassioFoodItem` detail.
   * @param servingUnit - Provide `servingUnit` unit to get `PassioFoodItem` detail.
   * @returns A `Promise` resolving to `PassioFoodItem` detail.
   */
  fetchFoodItemForDataInfo(
    passioFoodDataInfo: PassioFoodDataInfo,
    servingQuantity?: number,
    servingUnit?: String
  ): Promise<PassioFoodItem | null>

Example

Its' return PassioFoodItem

const getFoodItem = async (data: PassioFoodDataInfo) => {
  try {
    const passioFoodItem = await PassioSDK.fetchFoodItemForDataInfo(data)
  } catch (error) {
    console.log('error', error)
  }
}

React Native SDK Docs

Passio Nutrition AI React Native SDK Documentation

fetchHiddenIngredients

fetch list of possible hidden ingredients for a given food name.

  /**
   * fetch list of possible hidden ingredients for a given food name.
   * @param foodName - query for foodName.
   * @returns A `Promise` resolving to a `PassioFetchAdvisorInfoResult`.
   */
  fetchHiddenIngredients(
    foodName: string
  ): Promise<PassioFetchAdvisorInfoResult>
 try {
          // Fetch food results from the PassioSDK based on the query
          const result = await PassioSDK.fetchHiddenIngredients("apple")
          console.warn(result)
        } catch (error) {
        }

fetchVisualAlternatives

fetch list of possible visual alternatives for a given food name.

 /**
   * fetch list of possible visual alternatives for a given food name.
   * @param foodName - query for foodName.
   * @returns A `Promise` resolving to a `PassioFetchAdvisorInfoResult`.
   */
  fetchVisualAlternatives(
    foodName: string
  ): Promise<PassioFetchAdvisorInfoResult>
 try {
          // Fetch food results from the PassioSDK based on the query
          const result = await PassioSDK.fetchVisualAlternatives("apple")
          console.warn(result)
        } catch (error) {
        }

addToPersonalization

The image uri to detect food.

Import the PassioSDK

import {
  PassioSDK,
  type PersonalizedAlternative,
} from '@passiolife/nutritionai-react-native-sdk-v3'
  /**
   * This method adds personalized alternative to local database.
   * Status: This method is experimental and only available in the iOS SDK.
   * @param personalizedAlternative - The personalized alternative to add.
   * @returns A `boolean` value indicating if the personalized alternative was added successfully.
   */
  addToPersonalization(
    personalizedAlternative: PersonalizedAlternative
  ): boolean

Example

const personalizedAlternative = {
  visualPassioID: "PassioID"
  nutritionalPassioID: "PassioID"
  servingUnit: "g"
  servingSize: 10
}
const addToPersonalization = async (personalizedAlternative: PersonalizedAlternative) => {
  try {
    const isPersonalization = await PassioSDK.addToPersonalization(personalizedAlternative)
  } catch (error) {
    console.log('error', error)
  }
}

updateLanguage

Added support for localized content

Initialize Passio configuration at the entry point to enable localized content.

  /**
   * Added support for localized content.
   * with a two digit ISO 639-1 language code will transform the food names and serving sizes in the SDK responses
   *
   * @param languageCode - with a two digit ISO 639-1 language code
   */
  updateLanguage(languageCode: string): Promise<Boolean>
 try {
          // Fetch food results from the PassioSDK based on the query
          const result = await PassioSDK.updateLanguage("fr")
          console.warn(result)
        } catch (error) {
        }

Properties

startFoodDetection

Once the SDK is ready, you can start food detection.

Camera

Android Platform: You need the user's permission to access the camera.

<uses-permission android:name="android.permission.CAMERA">

IOS Platform: Enter a value for NSCameraUsageDescription in your Info.plist so the camera may be utilized.

Import the SDK

import {
  PassioSDK,
  DetectionCameraView,
} from '@passiolife/nutritionai-react-native-sdk-v3';

To show the live camera preview, add the DetectionCameraView to your view

<DetectionCameraView style={{flex: 1, width: '100%'}} />

The SDK can detect 4 different categories: VISUAL, BARCODE, PACKAGED FOOD

const config: FoodDetectionConfig = {
   /**
   * Detect barcodes on packaged food products. Results will be returned
   * as `BarcodeCandidates` in the `FoodCandidates` property of `FoodDetectionEvent`
   */
  detectBarcodes: true,
 
  /**
   * Results will be returned as DetectedCandidate in the `FoodCandidates`and 
   * property of `FoodDetectionEvent`
   */

  detectPackagedFood: true,
};
    
useEffect(() => {
  if (!isReady) {
    return;
  }
  const subscription = PassioSDK.startFoodDetection(
    config,
    async (detection: FoodDetectionEvent) => {

      const { candidates } = detection

      if (candidates?.barcodeCandidates?.length) {
         
         // show barcode candidates to the user

      } else if (candidates?.packagedFoodCode?.length) {
        
        // show OCR candidates to the user

      } else if (candidates?.detectedCandidates?.length) {
        
        // show visually recognized candidates to the user

      }
    },
  );
  // stop food detection when component unmounts
  return () => subscription.remove(); 

}, [isReady]);

Try to run the above code in component and Point the phone to the image below and see if you are getting the correct food in console log Got food detection event

apple_img

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

startNutritionFactsDetection

Once the SDK is ready, you can start nutrition label detection.

Camera

Android Platform: You need the user's permission to access the camera.

<uses-permission android:name="android.permission.CAMERA">

IOS Platform: Enter a value for NSCameraUsageDescription in your Info.plist so the camera may be utilized.

Import the SDK

import {
  PassioSDK,
  DetectionCameraView,
} from '@passiolife/nutritionai-react-native-sdk-v3';

To show the live camera preview, add the DetectionCameraView to your view

<DetectionCameraView style={{flex: 1, width: '100%'}} />

useEffect(() => {
   // Fistst check sdk configuration is ready for detection
  if (!isReady) {
    return;
  }
  const subscription = PassioSDK.startNutritionFactsDetection(
    async (detection: NutritionDetectionEvent) => {
     const { nutritionFacts: facts } = detection
      if (facts !== undefined) {
        setNutritionFacts(facts)
        return
      }
    },
  );
  // stop food detection when component unmounts
  return () => subscription.remove(); 

}, [isReady]);

Try to run the above code in the component and Point the phone to the image below and see if you are getting the correct nutrients lable in the console log

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

PassioIngredient

Attribute
Description

name

The name of the ingredient.

refCode

The unique identifier of the ingredient.

iconId

The identifier of the icon associated with the ingredient.

weight

The weight of the ingredient.

referenceNutrients

: The reference nutrients of the ingredient.

metadata

PassioFoodMetadata: The metadata associated with the ingredient.

amount

The amount of the ingredient.

export interface PassioIngredient {
  // Reference code of Passio food item
  refCode: RefCode
  /** The name of the ingredient. */
  name: string
  /** The unique identifier of the ingredient. */
  id: string
  /** The identifier of the icon associated with the ingredient. */
  iconId: PassioID | string
  /** The weight of the ingredient. */
  weight: UnitMass
  /** The reference nutrients of the ingredient. */
  referenceNutrients: PassioNutrients
  /** The metadata associated with the ingredient. */
  metadata?: PassioFoodMetadata
  /** The amount of the ingredient. */
  amount: PassioFoodAmount
}

PassioFoodDataInfo

Interface representing the structure of a food search result

Attribute
Description

brandName

Property for the brand name of the food (optional).

foodName

Property for the name of the food.

iconID

Property for the icon ID of the food.

labelId

Property for the label ID of the food.

nutritionPreview

: Property for the nutrition preview of the food (optional).

resultId

Property for the result ID of the food.

score

Property for the score of the food (optional).

scoredName

Property for the scored name of the food (optional).

type

Property for the type of the food (optional).

isShortName

Property for indicating whether to use short name for the info

export interface PassioFoodDataInfo {
  // Property for the brand name of the food
  brandName?: string
  // Property for the name of the food
  foodName: string
  // Property for the icon ID of the food
  iconID: PassioID | string
  // Property for the label ID of the food
  labelId: string
  // Property for the nutrition preview of the food
  nutritionPreview?: PassioNutritionPreview
  // Property for the result ID of the food
  resultId: string
  // Property for the score of the food
  score?: number
  // Property for the scored name of the food
  scoredName?: string
  // Property for the type of the food
  type?: string
  // Property for indicating whether to use short name for the info
  isShortName?: boolean
  // tags
  tags?: string
}

PassioFoodAmount

Attribute
Description

UnitMass

The unit measuring the serving size of a food item

Attribute
Description

PassioFoodItem

Field
Description
Type

fetchPossibleIngredients

fetch list of possible ingredients if a more complex food for a given food name.

PassioNutrients

Attribute
Description

ServingSize

The serving amount of food item

Attribute
Description

PassioNutritionPreview

Interface representing the nutrition preview of a food search result

PassioSearchResult

Interface representing the structure of a Passio search result

Attribute
Description

PassioSpeechRecognitionModel

NutritionFacts

Nutrition facts scanned from the nutrition label on a package food item

ServingUnit

The unit measuring the serving size of a food item

Attribute
Description

selectedUnit

The selected unit for the food amount.

selectedQuantity

The quantity of the selected unit.

weightGrams

The weight of the food amount in grams.

servingUnits

An array of serving units available for the food.

servingSizes

An array of serving sizes available for the food.

weight

The weight of the food amount using a specified unit of mass.

export interface PassioFoodAmount {
  /** The selected unit for the food amount. */
  selectedUnit: string
  /** The quantity of the selected unit. */
  selectedQuantity: number
  /** The weight of the food amount in grams. */
  weightGrams?: number
  /** An array of serving units available for the food. */
  servingUnits?: ServingUnit[]
  /** An array of serving sizes available for the food. */
  servingSizes?: ServingSize[]
  /** The weight of the food amount using a specified unit of mass. */
  weight: UnitMass
}

unit

The name of a unit (e.g., ounce, slice, container).

value

The mass of a unit in grams (1 container = 60g).


/*
 * The unit measuring the serving size of a food item
 */
export interface UnitMass {
  // The name of a unit (e.g. ounce, slice, container)
  unit: string
  // The mass of a  unit in grams (1 container = 60g)
  value: number
}

refCode

unique identifier for the food item

RefCode

name

Name of the food item

string

details

Additional details or description of the food item

string

iconId

Icon identifier for the food item

PassioID | string

amount

Amount of the food item (e.g., quantity, volume)

PassioFoodAmount

ingredients

List of ingredients used in the food item

PassioIngredient[]

ingredientWeight

Weight of the food item, measured in units of mass

UnitMass

isOpenFood

Boolean indicating whether the data is sourced from openfood.org

boolean

id

string



export interface PassioFoodItem {
  // Reference code of Passio food item
  refCode: RefCode

  // Name of the food item
  name: string

  // Additional details or description of the food item
  details?: string

  // Icon identifier for the food item
  iconId: PassioID | string

  // Amount of the food item (e.g., quantity, volume)
  amount: PassioFoodAmount

  // List of ingredients used in the food item
  ingredients?: PassioIngredient[]

  // Weight of the food item, measured in units of mass
  ingredientWeight: UnitMass

  /**
   * food item credits to openfood.org when the data is coming from them
   */
  isOpenFood?: boolean

  /**
   * food item credits to openfood.org when the data is coming from them
   * Show food license name
   */
  openFoodLicense?: string

  id: string
}
  /**
   * fetch list of possible ingredients if a more complex food for a given food name.
   * @param foodName - query for foodName.
   * @returns A `Promise` resolving to a `PassioFetchAdvisorInfoResult`.
   */
  fetchPossibleIngredients(
    foodName: string
  ): Promise<PassioFetchAdvisorInfoResult>
 try {
          // Fetch food results from the PassioSDK based on the query
          const result = await PassioSDK.fetchPossibleIngredients("apple")
          console.warn(result)
        } catch (error) {
        }

weight

The weight of the nutrients.

vitaminA

The amount of vitamin A.

alcohol

The amount of alcohol.

calcium

The amount of calcium.

calories

The amount of calories.

carbs

The amount of carbohydrates.

cholesterol

The amount of cholesterol.

fat

The amount of fat.

fibers

The amount of dietary fiber.

iodine

The amount of iodine.

iron

The amount of iron.

magnesium

The amount of magnesium.

monounsaturatedFat

The amount of monounsaturated fat.

phosphorus

The amount of phosphorus.

polyunsaturatedFat

The amount of polyunsaturated fat.

potassium

The amount of potassium.

protein

The amount of protein.

satFat

The amount of saturated fat.

sodium

The amount of sodium.

sugarAlcohol

The amount of sugar alcohol.

sugars

The amount of sugars.

sugarsAdded

The amount of added sugars.

transFat

The amount of trans fat.

vitaminB12

The amount of vitamin B12.

vitaminB12Added

The amount of added vitamin B12.

vitaminB6

The amount of vitamin B6.

vitaminC

The amount of vitamin C.

vitaminD

The amount of vitamin D.

vitaminE

The amount of vitamin E.

vitaminEAdded

The amount of added vitamin E.

export interface PassioNutrients {
  /** The weight of the nutrients. */
  weight: UnitMass
  /** The amount of vitamin A. */
  vitaminA?: UnitMass
  /** The amount of alcohol. */
  alcohol?: UnitMass
  /** The amount of calcium. */
  calcium?: UnitMass
  /** The amount of calories. */
  calories?: UnitMass
  /** The amount of carbohydrates. */
  carbs?: UnitMass
  /** The amount of cholesterol. */
  cholesterol?: UnitMass
  /** The amount of fat. */
  fat?: UnitMass
  /** The amount of dietary fiber. */
  fibers?: UnitMass
  /** The amount of iodine. */
  iodine?: UnitMass
  /** The amount of iron. */
  iron?: UnitMass
  /** The amount of magnesium. */
  magnesium?: UnitMass
  /** The amount of monounsaturated fat. */
  monounsaturatedFat?: UnitMass
  /** The amount of phosphorus. */
  phosphorus?: UnitMass
  /** The amount of polyunsaturated fat. */
  polyunsaturatedFat?: UnitMass
  /** The amount of potassium. */
  potassium?: UnitMass
  /** The amount of protein. */
  protein?: UnitMass
  /** The amount of saturated fat. */
  satFat?: UnitMass
  /** The amount of sodium. */
  sodium?: UnitMass
  /** The amount of sugar alcohol. */
  sugarAlcohol?: UnitMass
  /** The amount of sugars. */
  sugars?: UnitMass
  /** The amount of added sugars. */
  sugarsAdded?: UnitMass
  /** The amount of trans fat. */
  transFat?: UnitMass
  /** The amount of vitamin B12. */
  vitaminB12?: UnitMass
  /** The amount of added vitamin B12. */
  vitaminB12Added?: UnitMass
  /** The amount of vitamin B6. */
  vitaminB6?: UnitMass
  /** The amount of vitamin C. */
  vitaminC?: UnitMass
  /** The amount of vitamin D. */
  vitaminD?: UnitMass
  /** The amount of vitamin E. */
  vitaminE?: UnitMass
  /** The amount of added vitamin E. */
  vitaminEAdded?: UnitMass

  /** The amount of added zinc E. */
  zinc?: UnitMass
  /** The amount of added selenium. */
  selenium?: UnitMass
  /** The amount of added folicAcid. */
  folicAcid?: UnitMass
  /** The amount of added vitaminKPhylloquinone. */
  vitaminKPhylloquinone?: UnitMass
  /** The amount of added vitaminKMenaquinone4. */
  vitaminKMenaquinone4?: UnitMass
  /** The amount of added vitaminKDihydrophylloquinone. */
  vitaminKDihydrophylloquinone?: UnitMass
  /** The amount of added chromium. */
  chromium?: UnitMass
  /** The amount of added vitaminARAE. */
  vitaminARAE?: UnitMass
}

quantity

The quantity of the serving size.

unitName

The name of the serving unit (e.g., ounce, slice, container).

/*
 * The serving amount of food item
 */
export interface ServingSize {
  /** The quantity of the serving size. */
  quantity: number
  /** The name of the serving unit (e.g., ounce, slice, container). */
  unitName: string
}
export interface PassioNutritionPreview {
  // Property for the calories of the food
  calories: number
  // Property for the serving quantity of the food
  servingQuantity?: number
  // Property for the serving unit of the food
  servingUnit: string
  // Property for the carbs of the food
  carbs: number
  // Property for the protein of the food
  protein: number
  // Property for the fat of the food
  fat: number
  // Property for the fiber of the food
  fiber: number
  // Property for the weight unit of the food
  weightUnit: string
  // Property for the weight quantity of the food
  weightQuantity: number
}

results

Property for an array of FoodSearchResult objects or null.

alternatives

Property for an array of alternative strings or null.

// Interface representing the structure of a Passio search result
export interface PassioSearchResult {
  // Property for an array of FoodSearchResult objects or null
  results: PassioFoodDataInfo[] | null
  // Property for an array of alternative strings or null
  alternatives?: string[] | null
}
export interface PassioSpeechRecognitionModel {
  date: string
  meal: PassioMealTime
  action: PassioLogAction
  advisorInfo: PassioAdvisorFoodInfo
}
export interface NutritionFacts {
  servingSizeQuantity?: number
  servingSizeUnitName?: string
  servingSizeUnit: ServingSizeUnit
  calories?: number
  fat?: number
  carbs?: number
  protein?: number
  saturatedFat?: number
  transFat?: number
  cholesterol?: number
  sugars?: number
  sugarAlcohol?: number
  servingSizeGram?: number
  dietaryFiber?: number
  sodium?: number
}

unitName

The name of a serving unit (e.g., ounce, slice, container).

value

The mass of a serving unit in grams (1 container = 60g).

/*
 * The unit measuring the serving size of a food item
 */
export interface ServingUnit {
  // The name of a serving unit (e.g. ounce, slice, container)
  unitName: string
  // The mass of a serving unit in grams (1 container = 60g)
  value: number
  unit: string
}
PassioNutrients
PassioNutritionPreview

FoodCandidates

A collection of food candidates detected by the models.

Attribute
Description

detectedCandidates

Food candidate results from visual scanning. The array is sorted by confidence.

barcodeCandidates

Food candidate results from barcode scanning (optional).

packagedFoodCode

Food candidate results from packagedFoodCode scanning (optional).


export interface FoodCandidates {
  /**
   * Food candidate results from visual scanning. The array is sorted by confidence, with the most confident result at index 0.
   */
  detectedCandidates: DetectedCandidate[]

  /**
   * Food candidate results from barcode scanning.
   */
  barcodeCandidates?: BarcodeCandidate[]

  /**
   * Food candidate results from packagedFoodCode scanning.
   */
  packagedFoodCode?: PackagedFoodCode[]
}

PassioLogAction

export type PassioLogAction = 'add' | 'remove' | 'none'

BarcodeCandidate

Attribute
Description

barcode

The value of the scanned barcode.

boundingBox

A box describing the location of the barcode in the camera view.


/**
 * A UPC code captured by scanning a barcode
 */
export type Barcode = string

/**
 * A candidate resulting from barcode scanning
 */
export interface BarcodeCandidate {
  /**
   * The value of the scanned barcode
   */
  barcode: Barcode

  /**
   * A box describing the location of the barcode in the camera view
   */
  boundingBox: BoundingBox
}

FoodDetectionEvent

When starting food detection with PassioSDK.startFoodDetection you will receive a FoodDetectionEvent. This is the structure of that data class:

export interface FoodDetectionEvent {
    /**
     * A collection of food candidates detected by the models.
     */
    candidates?: FoodCandidates;
}
/**
 * A collection of food candidates detected by the models.
 */
export interface FoodCandidates {
  /**
   * Food candidate results from visual scanning. The array is sorted by confidence, with the most confident result at index 0.
   */
  detectedCandidates: DetectedCandidate[]

  /**
   * Food candidate results from barcode scanning.
   */
  barcodeCandidates?: BarcodeCandidate[]

  /**
   * Food candidate results from packagedFoodCode scanning.
   */
  packagedFoodCode?: PackagedFoodCode[]
}

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

DetectedCandidate

A food candidate detected from visual scanning

Attribute
Description

passioID

The ID of the detected item.

confidence

Confidence of the classification candidate, ranging from 0.0 to 1.0.

boundingBox

A box describing a detected object's location in the camera view.

amountEstimate

Scanned AmountEstimate (supported only in iOS) (optional).

foodName

The nutritional data for this item in the database (optional).

alternatives

Related items above this item in the food hierarchy (more generic) as an array of DetectedCandidate objects, or null (optional).

croppedImage

Cropped images related to the detected item as an array of ImagesInfo objects, or null (optional).


export interface DetectedCandidate {
  /**
   * The ID of the detected item
   */
  passioID: PassioID

  /**
   * Confidence of the classification candidate, ranging from 0.0 to 1.0
   */
  confidence: number

  /**
   * A box describing a detected object's location in the camera view
   */
  boundingBox: BoundingBox

  /**
   * Supported only in IOS
   * Scanned AmountEstimate
   */
  amountEstimate?: AmountEstimate

  /**
   * The nutritional data for this item in the database
   */
  foodName?: string

  /**
   * Related items above this item in the food heirarchy (more generic)
   */
  alternatives?: DetectedCandidate[] | null

  croppedImage?: ImagesInfo[] | null
}

PassioAdvisorFoodInfo

export interface PassioAdvisorFoodInfo {
  portionSize: string
  weightGrams: number
  recognisedName: string
  foodDataInfo?: PassioFoodDataInfo
  packagedFoodItem?: PassioFoodItem
  resultType: PassioFoodResultType
}

AmountEstimate

Returning all information of Amount estimation and directions how to move the device for better estimation

Attribute
Description

estimationQuality

The quality of the estimate (eventually for feedback to the user or SDK-based app developer).

moveDevice

Hints on how to move the device for better estimation (optional).

viewingAngle

The angle in radians from the perpendicular surface (optional).

volumeEstimate

Scanned volume estimate in milliliters (optional).

weightEstimate

Scanned amount in grams (optional).


/**
 * Returning all information of Amount estimation and directions how to move the device for better estimation
 */
export interface AmountEstimate {
  /**
   * The quality of the estimate (eventually for feedback to the user or SDK-based app developer)
   */
  estimationQuality: EstimationQuality

  /**
   * Hints how to move the device for better estimation.
   */
  moveDevice?: MoveDirection

  /**
   * The Angel in radians from the perpendicular surface.
   */
  viewingAngle?: number

  /**
   * Scanned Volume estimate in ml
   */
  volumeEstimate?: number

  /**
   *  Scanned Amount in grams
   */
  weightEstimate?: number
}

ImagesInfo

export interface ImagesInfo {
  base64: string | null
}

NutritionDetectionEvent

export interface NutritionDetectionEvent {
  nutritionFacts?: NutritionFacts
  text?: string
}

If at any point you need help from the Passio team, please reach out to us at support@passiolife.com

FoodDetectionEvent

An object provided in the callback for food detection containing food candidates as well as nutrition facts, if found

Attribute
Description

candidates

A collection of food candidates detected by the models (optional).

import type { FoodCandidates } from '.'

/**
 * An object provided in the callback for food detection containing
 * food candidates as well as nutrition facts, if found
 */
export interface FoodDetectionEvent {
  /**
   * A collection of food candidates detected by the models.
   */
  candidates?: FoodCandidates

}

PackagedFoodCode

packagedFoodCode (typealias String) is the string representation of the PackagedFoodCode id

/**
 * packagedFoodCode (typealias String) is the string representation of the PackagedFoodCode id
 */
export type PackagedFoodCode = string

NutritionFacts

Nutrition facts scanned from the nutrition label on a package food item


/**
 * Nutrition facts scanned from the nutrition label on a package food item
 */
export interface NutritionFacts {
  servingSizeQuantity?: number
  servingSizeUnitName?: string
  servingSizeUnit: ServingSizeUnit
  calories?: number
  fat?: number
  carbs?: number
  protein?: number
  saturatedFat?: number
  transFat?: number
  cholesterol?: number
  sugars?: number
  sugarAlcohol?: number
  servingSizeGram?: number
  dietaryFiber?: number
  sodium?: number
}

Nutriton Advisor

Fuel Business Growth with Passio's AI Advisor Modules in Nutrition

PassioMealPlan

PassioMealPlanItem

export interface PassioMealPlan {
  carbsTarget?: number
  fatTarget?: number
  mealPlanLabel: string
  mealPlanTitle: string
  proteinTarget?: number
}

import type { PassioFoodDataInfo } from '../PassioFoodDataInfo'
import type { PassioMealTime } from './MealTime'

export interface PassioMealPlanItem {
  dayNumber: number
  dayTitle: string
  mealTime: PassioMealTime
  meal: PassioFoodDataInfo
}

PassioStatus

/**
 * SDK is not ready due to missing model files. Please download the specified files
 * and call `configure` again, passing in the localFileURLs of the downloaded files.
 */
export type SDKNotReady = {
  mode: 'notReady'
  missingFiles: string[]
}

/**
 * SDK configuration successfully. This status much be reached before calling `startFoodDetection`.
 * It is possible that missing files may still be reported in the event that the SDK is aware of newer
 * model versions than the ones currently loaded. The SDK should still function normally in this case.
 */
export type SDKReadyForDetection = {
  mode: 'isReadyForDetection'
  activeModels: number
  missingFiles: string[]
}

/**
 * SDK failed to configure in an unrecoverable way. Please read the error message for more inforation.
 */
export type SDKError = {
  mode: 'error'
  errorMessage: string
}

export type SDKBeingConfigured = {
  mode: 'isBeingConfigured'
  activeModels: number
  missingFiles: string[]
}

export type SDKDownloadingModels = {
  mode: 'isDownloadingModels'
  activeModels: number
  missingFiles: string[]
}

/**
 * The possible states of the SDK after calling configure. Switch on status.mode to
 * access the data associated with each state.
 */
export type PassioStatus =
  | SDKNotReady
  | SDKReadyForDetection
  | SDKError
  | SDKBeingConfigured
  | SDKDownloadingModels
Passio's Nutrition-AI Hub
Nutrition-AI Hub
Nutrition-AI Hub
Nutrition Advisor API