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...
Loading...
Note: Some of these detections might not be accessible to you if your license does not include that module
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?
)
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()
}
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 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
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 .
Our Nutrition-AI SDK offers powerful capabilities that are rapidly evolving, so some features may not appear in the overview video below.
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.
Follow our documentation guides to get started and build your AI-powered applications. Please select your platform of choice to get to the documentation.
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.
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)"
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.
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.
}
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.
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.
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
.
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.
The PassioStatus mode is notReady -> check debugMessage for the error, usually the wrong license key has been supplied or the files are missing.
The PassioStatus mode is isDownloadingModels -> register a PassioStatusListener
through the setPassioStatusListener method as it will enable tracking of the download progress.
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.
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
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.
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
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.
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
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:
When defining a FoodDetectionConfiguration
object, enable the detectBarcode
flag.
In the recognitionResults
callback, check the property candidate.barcodeCandidates
. If this list is empty, no barcodes have been recognised.
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)
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.
Set up the camera preview and detection session to recognise barcodes
Create a result view with two states: search and result
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:
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.
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...
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.
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.
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.
Enable to user to manually enter all of the other data that the Nutrition Facts scanner did not recognise.
Once a user saves this food item, be sure to include it in the search or barcode scanning results.
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:
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", ...
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", ...
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"
Create an input text field to collect the user query
Create a horizontal list scroll to display the suggestions
Create a vertical list to display the results of the search
If the user clicks on a suggestion, pass that string to the search API and reload the UI with the new results
The SDK gives the ability to fetch food icons for a specific passioId
. There are two functions that can be used: lookupIconsFor
and fetchIconsFor
.
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.
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.
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.
If the implementing app is transitioning from generation 2
to generation 3
of the SDK, and has already stored generation 2 PassioID
s, invoking the fetchFoodItemLegacy
will retrieve the PassioFoodItem that was previously structured as PassioIDAttributes.
Follow these steps to include the SDK into your mobile application project.
Open your Xcode project.
Go to File > Swift Packages > Add Package Dependency.
In the "Add Package Dependency" dialog box, paste the URL:
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. You can choose main for the latest version or specify a specific version or branch.
After you've made your selection, click "Next".
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.
Click "Finish" to add the package to your project.
Xcode will download and add the PassioNutritionAISDK to your project. You can now import and start using the PassioNutritionAISDK.
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"
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.
In the build.gradle file add the PassioSDK dependency
Sync Project with Gradle Files and the PassioSDK interface should be available for usage.
Visit the of the Android-Passio-SDK-Distribution GitHub repository. Download the passiolib-release.aar file from the latest release.
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.
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
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.
Now in your Dart code, you can use:
Add to top build.gradle file (Project: android)
The nutrition_ai build.gradle file dependency section should look like (Module: nutrition_ai)
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.
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.
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.
Create a screen where the user can snap one or multiple images using the camera of the device
Upon clicking next, the recognizeImageRemote
is invoked on each of the images in the list
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.
To set up the local model and the continuous scanning mode, the camera preview and the recognition session need to be defined.
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.
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
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
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).
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:
Implement the camera screen using the steps above
Create a result view that can have two states: scanning and result
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:
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.
Create a screen where the user can record the voice logging command. Make sure to add the appropriate permissions.
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.
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
}
No, the image is sent to the backend for recognition
Very precise with all types of foods
On average 4-7 seconds
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)
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
}
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
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
.
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.
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
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.
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
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
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
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
In this Quick Start Guide, we will cover the following functionalities:
Installation
Configure the SDK
Recognize Image Remote
Food Detail
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
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!
Open project and delete the PassioSDKiOS framework
Drag and Drop the new "PassioNutritionAISDK.xcframework" to the project (select copy if needed)
In Projects > Targets > Your App > Frameworks, Libraries, and Embedded Content. Set the "PassioNutritionAISDK.xcframework" to "Embed & Sign"
import PassioSDKiOS
with import PassioNutritionAISDK
PassioSDK.shared
with PassioNutritionAI.shared
detectOCR
with detectPackagedFood
ocrCandidates
with packagedFoodCandidates
fetchPassioIDAttributesFor(ocrcode: $0
with fetchPassioIDAttributesFor(packagedFoodCode: $0.packagedFoodCode)
autoUpdate
with sdkDownloadsModels
isAutoUpdating
with isDownloadingModels
isReadyForNutrition
was removed use isReadyForDetection
The servingSizeQuantity was modified from
public var servingSizeQuantity: Int
to:
public var servingSizeQuantity: Double
The function func searchForFood(byText: String) -> [String]
return value was modified to -> [PassioIDAndName]
.
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)
}
References to logo detection
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 }
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
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')
}
}
}
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.
Add a button when displaying the food item to report incorrect data.
Show a form where users can add their feedback as well as provide the product code (barcode) of the item.
Upon clicking the "Report" button, invoke the reportFoodItem
function.
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:
Open the project in Xcode:
Replace the SDK Key in the PassioQuickStartViewController.swift file with the license key you get from Passio
Connect your iPhone and run
Modify the app bundle from "com.PassioDemoApp.demo" to "com.yourcompany...."
Run the demo app on your iPhone.
For support, please contact support@passiolife.com
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
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
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.
Use these steps to initialise the conversation and enable users to send messages:
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.
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
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;
}
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.
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
.
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.
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.
Create a view where the user can see the extracted foods, their serving size and have the ability to log them.
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.
Open your Xcode project.
Go to File > Swift Packages > Add Package Dependency.
In the "Add Package Dependency" dialog box, paste the 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. You can choose main for the latest version or specify a specific version or branch.
After you've made your selection, click "Next".
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.
Click "Finish" to add the package to your project.
Xcode will download and add the PassioNutritionAISDK to your project. You can now import and start using the PassioNutritionAISDK.
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"
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
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
.
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"
The main object containing the nutritional information is called PassioFoodItem. This object is fetched from Passio's backend using one of several fetchFoodItemFor...
functions.
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.
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.
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.
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();
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".
Fetch the PassioFoodItem
Use the name, details and iconId to create the food header view
Use the nutrientsSelectedSize->calories, carbs, protein and fat to show the marcos
Use the amount class to create the serving size view
Give the user the option to log the food
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.
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
Copyright 2022 Passio Inc
If at any point you need help from the Passio team, please reach out to us at support@passiolife.com
PassioFoodItem
from PassioAdvisorFoodInfo
To display the food details, PassioFoodItem
is required.
Fetch PassioFoodItem
from PassioFoodDataInfo
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.
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
.
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:
The Passio Android SDK is shipped in the form of an file.
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
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.
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
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 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.
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.
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
Ask for Photo Gallery permission:
Present the PHPickerViewController
with configuration
Implement the delegate for getting user picked images
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.
For each food recognised, we can create a table view cell e.g. FoodListCell
and pass the PassioAdvisorFoodInfo
object
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.
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.
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
If at any point you need help from the Passio team, please reach out to us at support@passiolife.com
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.
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
}
}
<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*/)
}
}
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 = ""
}
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.
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.
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.
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.
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
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.
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:
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)
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.
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.
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.
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>
}
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
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:
Files are in the assets folder of your Android project
Let the SDK manage file download and update
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
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
}
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)
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
}
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.
The PassioStatus mode is NOT_READY -> check debugMessage for the error, usually the wrong license key has been supplied or the files are missing.
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.
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
Passio Nutrition AI Android SDK Documentation
This project provides React Native bindings for the Passio SDK.
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
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!
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'
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
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:
Food Item Information: Displays the name, icon, and serving size details.
Serving Size Selector: Allows users to select a preferred serving size or unit.
Nutritional Information: Shows calorie count and macronutrient breakdown (Carbs, Protein, Fat).
Micronutrient List: Provides detailed nutrient information for vitamins, minerals, and other nutrients.
The PassioFoodItem
model is the foundation of food details and includes the following properties:
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:
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
quantity
Double
Quantity of the unit
unitName
String
Name of the serving unit
PassioServingUnit
unitName
String
Linked to the PassioServingSize
weight
UnitMass
Weight in grams or other units
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
To include the Passio SDK in your project, you’ll need to:
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'
}
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
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
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())
}
}
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.
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.
Navigation on Ready:
On successful configuration, the app navigates to ImageRecognitionsFragment
.
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>
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
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).
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
)
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
PassioSDK provide support to below api's
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.
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.
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 get PassioFoodItem
using SearchFood Response?
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:
For the SDK to work, it requires a proper license key.
The license key can be requested at support@passiolife.com
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
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
/**
* 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())
}
//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
...
}
}
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)
}
}, [])
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>
Its' return PassioFoodItem
const getFoodItem = async (code: RefCode) => {
try {
const passioFoodItem = await PassioSDK.fetchFoodItemForRefCode(code)
} catch (error) {
console.log('error', error)
}
}
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>
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);
});
}, []);
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>
Its' return PassioFoodItem
const getFoodItem = async (code: Barcode | PackagedFoodCodeackagedFoodCode) => {
try {
const passioFoodItem = await PassioSDK.fetchFoodItemForProductCode(code)
} catch (error) {
console.log('error', error)
}
}
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
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
}
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>
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]
)
This method indicating downloading file status if model download from passio server.
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
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()
}, [])
The image uri to detect food.
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>
const [candidates, setCandidates] = useState<FoodCandidates | null>()
useEffect(() => {
PassioSDK.detectFoodFromImageURI(imageUri).then((foodCandidates) => {
setCandidates(foodCandidates)
})
}, [props.imageUri])
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>
It's return PassioFoodItem
const getFoodItem = async (passioID: PassioID) => {
try {
const passioFoodItem = await PassioSDK.fetchFoodItemForPassioID(passioID)
} catch (error) {
console.log('error', error)
}
}
It's provide whole passio food item using short passioFoodDataInfo Object.
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>
Its' return PassioFoodItem
const getFoodItem = async (data: PassioFoodDataInfo) => {
try {
const passioFoodItem = await PassioSDK.fetchFoodItemForDataInfo(data)
} catch (error) {
console.log('error', error)
}
}
Passio Nutrition AI React Native SDK Documentation
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) {
}
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) {
}
The image uri to detect food.
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
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)
}
}
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) {
}
Once the SDK is ready, you can start food detection.
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 {
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%'}} />
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
If at any point you need help from the Passio team, please reach out to us at support@passiolife.com
Once the SDK is ready, you can start nutrition label detection.
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 {
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
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
}
Interface representing the structure of a food search result
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
}
The unit measuring the serving size of a food item
fetch list of possible ingredients if a more complex food for a given food name.
The serving amount of food item
Interface representing the nutrition preview of a food search result
Interface representing the structure of a Passio search result
Nutrition facts scanned from the nutrition label on a package food item
The unit measuring the serving size of a food item
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)
ingredients
List of ingredients used in the food item
ingredientWeight
Weight of the food item, measured in units of mass
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
}
A collection of food candidates detected by the models.
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[]
}
export type PassioLogAction = 'add' | 'remove' | 'none'
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
}
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
A food candidate detected from visual scanning
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
}
export interface PassioAdvisorFoodInfo {
portionSize: string
weightGrams: number
recognisedName: string
foodDataInfo?: PassioFoodDataInfo
packagedFoodItem?: PassioFoodItem
resultType: PassioFoodResultType
}
Returning all information of Amount estimation and directions how to move the device for better estimation
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
}
export interface ImagesInfo {
base64: string | null
}
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
An object provided in the callback for food detection containing food candidates as well as nutrition facts, if found
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 (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
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
}
Fuel Business Growth with Passio's AI Advisor Modules in Nutrition
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
}
/**
* 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