> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Image classification and tagging

> Use third-party AI platforms to enrich your data through image classification, also known as image tagging or labeling.

export const SearchQuery = () => <Tooltip tip="The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.">
    search query
  </Tooltip>;

export const Records = () => <Tooltip tip="A record is a searchable object in an Algolia index. Each record consists of named attributes." cta="Algolia records" href="/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records">
    records
  </Tooltip>;

export const Filter = () => <Tooltip tip="A filter is a condition that limits which records Algolia returns. Filters often use one or more facet-value pairs, such as brand:Apple AND color:red. You can also filter by numeric values, dates, tags, booleans, or geographic constraints." cta="Filtering" href="/doc/guides/managing-results/refine-results/faceting">
    filter
  </Tooltip>;

Image classification and tagging lets you extract meta information from product images. Whether you plan on implementing [search by image](/doc/guides/solutions/ecommerce/visual-image-search/tutorials/search-by-image) or not, enriching your ecommerce dataset through image classification can lead to a more relevant search and discovery experience.

<Note>
  You can't store images directly in Algolia.
  Instead, store the image on a content delivery network (CDN) or web server and add the image URL to a field in your <Records />.
  When you retrieve a record from Algolia, use this URL to display the image in your app.
</Note>

## Why use image classification?

Retailers spend a lot of time building their catalogs.
To offer a relevant search and discovery experience, they often manually classify each item, adding meta features like item type, material, or style.
Removing some of this manual work lets you focus on the data that's important to your business—for example price, stock quantity, and popularity.

Visual recognition enables automatic extraction of this information by analyzing each product image.
It makes feature tagging more consistent.
For example, you may have various names for the color "blue" within your product descriptions.
Item descriptions could include "cerulean" or "sapphire," but not "blue".

Without consistently having an attribute with the value "blue" you could fail to surface all relevant products to your users when they <Filter /> on or search for "blue" items.
Image classification lets you add the "blue" tag consistently for all blue products.

Image classification is particularly valuable in C2C marketplaces where users may not describe their products consistently nor fully.
Tags from image classification can increase the number of product attributes, increasing their discoverability.

Image classification is valuable not only in C2C marketplaces, but anywhere your team is manually tagging different features like "type," "neckline," and "sleeve length."

## What does image classification and tagging entail?

This guide outlines how to use a third party API or platform to classify images and enrich your Algolia records using these classifications.

It provides examples for [Google Cloud Vision API](#using-google-vision-api) and [ViSenze](#using-visenze), but the process is the same for other providers like [Amazon Rekognition](https://aws.amazon.com/rekognition).

The goal is to enrich your records so that each one includes additional descriptive text.
This text comes from running the product image through an image classifier, which returns classifications.
Another way of thinking of classifications is "tags" or "labels."
By adding these classifications to your Algolia records, you make it easier to surface them in searches, whether users are searching with text or images.

Enriching your records with classifications is a two-step process:

1. **Image classification** - sending image URLs to a third-party image recognition platform to retrieve classifications.
2. **Indexing** - adding the relevant classification information to your Algolia records.

If your goal is to let users [search by images](/doc/guides/solutions/ecommerce/visual-image-search/tutorials/search-by-image),
you must first enrich your records using the same image recognition platform you plan to use for searching by image.
Without first enriching your records,
when a user provides an image as a <SearchQuery />,
it won't be able to match relevant records with the same or similar image.

## Platform considerations

[Google Cloud Vision API](https://cloud.google.com/vision) is an all-purpose image recognition API. Since it draws from a large corpus of image data, it can give a wide variety of classifications with high accuracy. The downside is that the classifications it provides aren't highly specialized or structured.

All-purpose image recognition platforms can introduce irrelevant classifications. An image of model wearing a t-shirt could return relevant classifications, like "t-shirt" and the color and style of shirt, but it could also return classifications like "neck" and "arm," if these are present in the image. [Google Cloud Vision API](https://cloud.google.com/vision) returns tags and confidence scores of all objects that it identifies in an image.

If a platform exists for your particular use case, for example [ViSenze](#using-visenze) for fashion retail, it's best to use the specialized platform over the general one. Using case specific platforms usually produces better classifications. These platforms tailor their classifications to industry relevant terms and structure them consistently.

For example, [ViSenze](#using-visenze) would take an image of a model wearing a t-shirt and identify all fashion related objects only, excluding objects like "neck" and "arm." For each included item—"t-shirt," for example—it returns relevant attributes like "neckline," "fit," and "sleeve length," and their values: "v-neck," "trim," and "short," respectively. You can be sure that all shirt images retrieve these same attributes in the same structure.

## Before you begin

The following tutorial requires a set of Algolia records, each containing an image URL, and access to an image recognition platform such as [Google Cloud Vision API](https://cloud.google.com/vision).

Algolia doesn't search in your original data source, but in the [data you index to Algolia](/doc/guides/sending-and-managing-data/prepare-your-data). Algolia accepts and stores JSON data, meaning it doesn't store image files. Instead, it's common to index an image URL, so that you can display the image in your results.

```json JSON icon=braces theme={"system"}
{
  "title": "Men's Athletic Shirt",
  "objectID": "807281751",
  "imageURL": "https://images-na.ssl-images-amazon.com/images/I/41uIVaJOLdL.jpg"
}
```

This is a truncated example—your records probably have other attributes like price, stock, sales rank, and other relevant information.

## Image classification

Image classification takes an image and returns a set of classifications or labels for it. As AI advances, image classification is getting better and easier for non-experts to use. When using the Google Cloud Vision API, ViSenze, or other similar platforms, it can be as straightforward as feeding the platform an image URL and receiving the classifications in its response.

### Using Google Vision API

If you haven't already, create a Google Account and [enable the Google Vision API for it](https://cloud.google.com/vision/docs/setup). [Set up authentication](https://cloud.google.com/vision/docs/setup#auth) so that you can retrieve credentials and use [the Vision API client library](https://cloud.google.com/vision/docs/setup#lib). The Google Vision API returns an [array of classifications: JSON objects with different properties](https://cloud.google.com/vision/docs/reference/rest/v1/AnnotateImageResponse). Of these the [`description` and `score`](https://cloud.google.com/vision/docs/reference/rest/v1/AnnotateImageResponse#EntityAnnotation), which is how certain the API is about the description, are particularly useful.

After initializing an instance of Google Cloud Vision's Node.js client, you can write a function to [retrieve labels from an image URL](https://cloud.google.com/vision/docs/labels). The example below creates a `getImageLabels` function that takes a public image URL, the Algolia record's `objectID`, and a `scoreLimit`. The `scoreLimit` is the threshold for how certain the platform must be about an object to include it in the classifications.

Since [`score`](https://cloud.google.com/vision/docs/reference/rest/v1/AnnotateImageResponse#EntityAnnotation) is a number between 0 and 1, the `scoreLimit` should be between 0 and 1 too. The higher the `scoreLimit`, the more certain the API must be about the label for it to be included.

You can write a function to retrieve just these or any other attributes you find useful. The `getImageLabels` example returns an object with a `labels` array. The array contains only label `descriptions` and `score`s, where `score`s were higher than the `scoreLimit`. The returned object also includes the original `imageURL` and `objectID`. The `objectID` is important for sending this data to your Algolia index later.

```js JavaScript icon=code theme={"system"}
// Import the Google Cloud client libraries
const vision = require('@google-cloud/vision');

// Instantiate Google Vision client
const client = new vision.ImageAnnotatorClient();

// Retrieve labels
async function getImageLabels(imageURL, objectID, scoreLimit) {
  const [result] = await client.labelDetection(imageURL);
  const labels = result.labelAnnotations
    .filter((label) => label.score > scoreLimit)
    .map((label) => (
      {
        description: label.description,
        score: label.score,
      }
    ))
  return { imageURL, objectID, labels };
}

const classifiedImage = await getImageLabels("https://images-na.ssl-images-amazon.com/images/I/41uIVaJOLdL.jpg", "439784001", 0.5)
```

Result:

```js JavaScript icon=code theme={"system"}
const classifiedImage = {
  imageURL: "https://images-na.ssl-images-amazon.com/images/I/41uIVaJOLdL.jpg",
  objectID: "439784001",
  labels: [
    {
      "description": "Outerwear",
      "score": 0.9513528,
    },
    {
      "description": "Azure",
      "score": 0.89286935,
    },
    {
      "description": "Sleeve",
      "score": 0.8724504,
    },
    {
      "description": "Bag",
      "score": 0.86443543,
    },
    {
      "description": "Grey",
      "score": 0.8404184,
    }
  ]
}
```

<Note>
  When fetching images from HTTP URLs,
  Google can't guarantee that the request succeeds.
  Your request may fail if the specified host denies the request (for example, due to request throttling or denial of service prevention),
  or if Google throttles requests to the site for abuse prevention.
  Google advises against depending on externally hosted images for production apps.
</Note>

### Using ViSenze

When using a case specific platform like [ViSenze](https://www.visenze.com/),
the general idea is the same.
[Setup an account and credentials](https://developers.visenze.com/api/#sdk-setup), and send public image URLs to their [Recognition API](https://developers.visenze.com/api/#authentication32) to receive classifications. You need to tailor your function to the data structure the platform returns.

For example, the `getImageLabels` function below takes a public image URL, the Algolia record's `objectID`, and a  `scoreLimit`.
The `scoreLimit` is the threshold for how certain the platform must be about an object to include it in the classifications.

Since [`score`](https://developers.visenze.com/api/#response) is a number between 0 and 1,
the `scoreLimit` should be between 0 and 1 too.
The higher the `scoreLimit`,
the more certain the API must be about the label for it to be included.

The function returns an object with an `objects` array.
The `objects` array contains all relevant identified objects (for example, "t-shirt," or "belt") and their coordinates, labels, and scores.

The returned object also includes the original `imageURL` and `objectID`.
The `objectID` is important for sending this data to your Algolia index later.

```js JavaScript icon=code theme={"system"}
// Retrieve labels
async function getImageLabels(imageURL, objectID, scoreLimit) {
  const formData = new FormData();

  formData.append("limit", "30");
  formData.append("tag_group", "fashion_attributes");
  formData.append("url", imageURL);

  return await fetch("https://virecognition.visenze.com/v1/image/recognize", {
    method: "POST",
    headers: {
      Authorization: "Basic YOUR_BASE64_ENCODED_VISENZE_KEY",
    },
    body: formData,
  })
    .then((res) => res.json())
    .then((res) => {
      if (res.status !== "OK" && res.error[0]) {
        console.log("handle ViSenze - recognition error", res.error[0]);
        return;
      }

      const classifiedImage = {
        imageURL,
        objectID,
        objects: [],
      };

      // `res.result[0].objects` contains the objects detected in the image
      res.result[0].objects.forEach((object, index) => {
        // Store coordinates of the current object
        classifiedImage.objects[index] = {
          x1: object.box[0],
          y1: object.box[1],
          x2: object.box[2],
          y2: object.box[3],
        };

        // Format categories, attributes and scores
        object.tags.forEach(({ tag, score }) => {
          const splittedTag = tag.split(":");
          score = parseFloat(score.toFixed(2));

          if (score > scoreLimit) {
            if (!(splittedTag[0] in classifiedImage.objects[index])) {
              classifiedImage.objects[index][splittedTag[0]] = [];
            }

            classifiedImage.objects[index][splittedTag[0]].push({
              label: splittedTag[1],
              score,
            });
          }
        });
      });

      return classifiedImage;
    }).catch((err) => console.error("Image classification error", err));
};

const classifiedImage = await getImageLabels("https://images-na.ssl-images-amazon.com/images/I/41uIVaJOLdL.jpg", "439784001", 0.5)
```

Result:

```js JavaScript icon=code theme={"system"}
const classifiedImage = {
  imageURL: "https://images-na.ssl-images-amazon.com/images/I/41uIVaJOLdL.jpg",
  objectID: "439784001",
  objects: [
      {
        x1: 65,
        y1: 14,
        x2: 729,
        y2: 788,
        apparel: [{ label: "upper_body_garment", score: 0.98 }],
        product_color: [{ label: "blue", score: 0.91 }],
        neckline: [{ label: "round_neck", score: 0.81 }],
        // This is a truncated list of classifications.
        // Other classifications include category, closure_type, denim_wash_color, lower_body_garment, lower_body_length, outerwear, pants_fit_type, product_pattern, rise_type, sleeve_length, sleeve_style, upper_body_garment, upper_body_length
      },
  ],
}
```

## Indexing image classifications

Once you've retrieved the classifications from your third-party image recognition platform, you need to index them to Algolia. You can include classifications either when you initially index your data, or within the context of the [`browse`](/doc/libraries/sdk/v1/methods/browse) method. The [`browse`](/doc/libraries/sdk/v1/methods/browse) lets you retrieve your data and update it according to your needs.

### Using Google Vision API

This example uses of the `getImageLabels` function from the [classification section](#using-google-vision-api) to retrieve labels for each record while using [`browse`](/doc/libraries/sdk/v1/methods/browse). It then uses the [`partialUpdateObjects`](/doc/libraries/sdk/v1/methods/partial-update-objects) method to add the labels to the record.

<CodeGroup>
  ```cs C# theme={"system"}
  namespace Algolia;

  using System;
  using System.Collections.Generic;
  using System.Net.Http;
  using System.Text.Json;
  using Algolia.Search.Clients;
  using Algolia.Search.Http;
  using Algolia.Search.Models.Search;

  class SaveImageClassifications
  {
    class Image
    {
      public required string ImageUrl { get; set; }
      public required string ObjectId { get; set; }
      public required List<Dictionary<string, object>> Objects { get; set; }
    }

    // Retrieve labels
    async Task<Image> GetImageLabels(string imageURL, string objectID, float scoreLimit)
    {
      // Implement your image classification logic here
      return await Task.Run(() =>
        new Image
        {
          ImageUrl = "",
          ObjectId = "",
          Objects = [],
        }
      );
    }

    async Task Main(string[] args)
    {
      try
      {
        // API key ACL should include editSettings / addObject
        var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY"));

        var hits = await client.BrowseObjectsAsync<Image>(
          "INDEX_NAME",
          new BrowseParamsObject()
        );

        var records = hits.Select(hit => GetImageLabels(hit.ImageUrl, hit.ObjectId, 0.5f))
          .Select(src => src.Result)
          .ToList();

        // Update records with image classifications
        await client.PartialUpdateObjectsAsync("INDEX_NAME", records, true);
      }
      catch (Exception e)
      {
        Console.WriteLine(e.Message);
      }
    }
  }

  ```

  ```dart Dart theme={"system"}
  import 'package:algolia_client_search/algolia_client_search.dart';

  final class Image {
    final String imageUrl;
    final String objectID;
    List<Map<String, dynamic>> objects;

    Image(String imageUrl, String objectID, List<Map<String, dynamic>> objects)
        : imageUrl = imageUrl,
          objectID = objectID,
          objects = objects;
  }

  Future<Map<String, dynamic>> getImageLabels(
      String imageURL, String objectID, double scoreLimit) async {
    // Implement your image classification logic here
    return {};
  }

  void saveImageClassifications() async {
    // API key ACL should include editSettings / addObject
    final client =
        SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY');

    final List<Map<String, dynamic>> records = [];

    BrowseResponse? browseResponse;
    do {
      final browseParams =
          BrowseParamsObject(hitsPerPage: 1000, cursor: browseResponse?.cursor);
      browseResponse = await client.browse(
          indexName: "INDEX_NAME", browseParams: browseParams);
      for (final hit in browseResponse.hits) {
        final props = hit.toJson();
        final imageWithLabels =
            await getImageLabels(props["imageUrl"], hit.objectID, 0.5);
        records.add(imageWithLabels);
      }
    } while (browseResponse.cursor != null);

    final batchParams = BatchWriteParams(
        requests: records
            .map((record) => BatchRequest(
                  action: Action.partialUpdateObject,
                  body: record,
                ))
            .toList());

    // Update records with image classifications
    await client.batch(
      indexName: "INDEX_NAME",
      batchWriteParams: batchParams,
    );
  }

  ```

  ```go Go theme={"system"}
  package main

  import (
  	"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
  )

  func saveImageClassifications() {
  	type Image struct {
  		ImageURL string           `json:"imageURL"`
  		ObjectID string           `json:"objectID"`
  		Objects  []map[string]any `json:"objects"`
  	}

  	getImageLabels := func(imageURL, objectID string, _scoreLimit float64) Image {
  		// Implement your image classification logic here
  		return Image{
  			ImageURL: imageURL,
  			ObjectID: objectID,
  			Objects:  []map[string]any{},
  		}
  	}

  	client, err := search.NewClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
  	if err != nil {
  		// The client can fail to initialize if you pass an invalid parameter.
  		panic(err)
  	}

  	images := []Image{}

  	err = client.BrowseObjects("INDEX_NAME", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) {
  		if err != nil {
  			panic(err)
  		}

  		browseRes, ok := res.(search.BrowseResponse)
  		if !ok {
  			panic("Invalid response")
  		}

  		for _, hit := range browseRes.Hits {
  			props := hit.AdditionalProperties
  			imageURL, _ := props["imageURL"].(string)
  			images = append(images, getImageLabels(imageURL, hit.GetObjectID(), 0.5))
  		}
  	}))
  	if err != nil {
  		panic(err)
  	}

  	records := make([]map[string]any, len(images))
  	for i, img := range images {
  		records[i] = map[string]any{
  			"imageURL": img.ImageURL,
  			"objectID": img.ObjectID,
  			"objects":  img.Objects,
  		}
  	}

  	_, err = client.PartialUpdateObjects(
  		"INDEX_NAME", records, search.WithCreateIfNotExists(true))
  	if err != nil {
  		panic(err)
  	}
  }

  ```

  ```java Java theme={"system"}
  package com.algolia;

  import com.algolia.api.SearchClient;
  import com.algolia.config.*;
  import com.algolia.model.search.*;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;

  class saveImageClassifications {

    static class Image {

      public String imageURL;
      public String objectID;
      public List<Map<String, Object>> objects;

      public Image(String imageURL, String objectID, List<Map<String, Object>> objects) {
        this.imageURL = imageURL;
        this.objectID = objectID;
        this.objects = objects;
      }
    }

    // Retrieve labels
    static Image getImageLabels(String imageURL, String objectID, float scoreLimit) {
      // Implement your image classification logic here
      return new Image(imageURL, objectID, new ArrayList<>());
    }

    public static void main(String[] args) throws Exception {
      // API key ACL should include editSettings / addObject
      try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
        var hits = client.browseObjects("INDEX_NAME", Image.class);

        List<Image> records = new ArrayList<>();
        for (var hit : hits) {
          var image = getImageLabels(hit.imageURL, hit.objectID, 0.5f);
          records.add(image);
        }

        // Update records with image classifications
        client.partialUpdateObjects("INDEX_NAME", records, true);
      } catch (Exception e) {
        System.out.println(e.getMessage());
      }
    }
  }

  ```

  ```js JavaScript theme={"system"}
  import { algoliasearch } from 'algoliasearch';

  interface Image extends Record<string, unknown> {
    imageURL: string;
    objectID: string;
    objects: Record<string, unknown>[];
  }

  async function saveImageClassifications() {
    // Retrieve labels
    function getImageLabels(_imageURL, _objectID, _scoreLimit): Image {
      // Implement your image classification logic here
      return { imageURL: '', objectID: '', objects: [] };
    }

    // API key ACL should include editSettings / addObject
    const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');

    const records: Image[] = [];

    await client.browseObjects<Image>({
      indexName: 'INDEX_NAME',
      browseParams: undefined,
      aggregator: (browseResponse) => {
        records.push(
          ...browseResponse.hits.map((hit) => {
            return getImageLabels(hit.imageURL, hit.objectID, 0.5);
          }),
        );
      },
    });

    // Update records with image classifications
    await client.partialUpdateObjects({ indexName: 'products', objects: records, createIfNotExists: true });
  }

  ```

  ```kotlin Kotlin theme={"system"}
  import com.algolia.client.api.SearchClient
  import com.algolia.client.configuration.*
  import com.algolia.client.extensions.*
  import com.algolia.client.model.search.BrowseParamsObject
  import com.algolia.client.transport.*
  import kotlinx.serialization.json.Json
  import kotlinx.serialization.json.encodeToJsonElement
  import kotlinx.serialization.json.jsonObject

  suspend fun saveImageClassifications() {
    data class Image(val imageURL: String, val objectID: String, val objects: List<Map<String, Any>>)

    // Retrieve labels
    fun getImageLabels(imageURL: String, objectID: String, scoreLimit: Float): Image {
      // Implement your image classification logic here
      return Image(imageURL, objectID, emptyList())
    }

    // API key ACL should include editSettings / addObject
    try {
      val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

      val images: MutableList<Image> = mutableListOf()

      client.browseObjects(
        indexName = "INDEX_NAME",
        params = BrowseParamsObject(),
        aggregator = { browseResponse ->
          images.addAll(
            browseResponse.hits.map {
              val props = it.additionalProperties ?: emptyMap()
              return@map getImageLabels(
                props.getOrElse("imageURL") { "" }.toString(),
                it.objectID,
                0.5f,
              )
            }
          )
        },
      )

      val records = images.map { Json.encodeToJsonElement(it).jsonObject }

      // Update records with image classifications
      client.partialUpdateObjects(
        indexName = "INDEX_NAME",
        objects = records,
        createIfNotExists = true,
      )
    } catch (e: Exception) {
      println(e.message)
    }
  }

  ```

  ```php PHP theme={"system"}
  <?php

  use Algolia\AlgoliaSearch\Api\SearchClient;

  $getImageLabels = function ($imageURL, $objectID, $scoreLimit) {
      // Implement your image classification logic here
      return ['objectID' => '', 'imageURL' => '', 'objects' => []];
  };

  $client = SearchClient::create('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');

  $records = [];

  $hits = $client->browseObjects('INDEX_NAME');
  foreach ($hits as $hit) {
      $imageURL = $hit['imageURL'];
      $records[] = $getImageLabels($imageURL, $hit['objectID'], 0.5);
  }

  $client->partialUpdateObjects(
      'INDEX_NAME',
      $records,
      true,
  );

  ```

  ```python Python theme={"system"}
  from algoliasearch.search.client import SearchClientSync

  from algoliasearch.search.models.browse_response import BrowseResponse


  def _get_image_labels(image_url, object_id, score_limit):
      # Implement your image classification logic here
      return {"objectID": "", "imageURL": "", "objects": []}


  _client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")


  records = []


  def _aggregator(res: BrowseResponse):
      for hit in res.hits:
          props = hit.to_dict()
          image_url = props["imageURL"]
          records.append(_get_image_labels(image_url, hit.object_id, 0.5))


  _client.browse_objects("INDEX_NAME", aggregator=_aggregator)

  _client.partial_update_objects(
      index_name="INDEX_NAME",
      objects=records,
      create_if_not_exists=True,
  )

  ```

  ```ruby Ruby theme={"system"}
  require "algolia"

  def get_image_labels(image_url, object_id, score_limit)
    # Implement your image classification logic here
    {"objectID" => "", "imageURL" => "", "objects" => []}
  end

  client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")

  records = []

  client.browse_objects(
    "INDEX_NAME"
  ) do |resp|
    resp.hits.each do |hit|
      image_url = hit["imageURL"]
      records << get_image_labels(image_url, hit["objectID"], 0.5)
    end
  end

  client.partial_update_objects("INDEX_NAME", records, true)

  ```

  ```scala Scala theme={"system"}
  import scala.concurrent.Future
  import scala.concurrent.ExecutionContext.Implicits.global

  import algoliasearch.api.SearchClient
  import algoliasearch.config.*
  import algoliasearch.extension.SearchClientExtensions
  import algoliasearch.search.BrowseParamsObject

  class Image(
      val imageURL: String,
      val objectID: String,
      val objects: Seq[Map[String, Any]]
  )

  def saveImageClassifications(): Future[Unit] = {
    // Retrieve labels
    def getImageLabels(imageURL: String, objectID: String, scoreLimit: Float): Image = {
      // Implement your image classification logic here
      Image(imageURL, objectID, Seq())
    }

    // API key ACL should include editSettings / addObject
    val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

    val records: Seq[Image] = Seq.empty

    client.browseObjects(
      indexName = "INDEX_NAME",
      browseParams = BrowseParamsObject(),
      aggregator = { browseResponse =>
        records.appendedAll(
          browseResponse.hits.map { hit =>
            val props = hit.additionalProperties.getOrElse(List()).toMap
            getImageLabels(props.get("imageURL").toString, hit.objectID, 0.5f)
          }
        )
      }
    )

    // Update records with image classifications
    client
      .partialUpdateObjects(
        indexName = "INDEX_NAME",
        objects = records,
        createIfNotExists = true
      )
      .map { response =>
        println(response)
      }
      .recover { case ex: Exception =>
        println(s"An error occurred: ${ex.getMessage}")
      }
  }

  ```

  ```swift Swift theme={"system"}
  import Foundation
  #if os(Linux) // For linux interop
      import FoundationNetworking
  #endif

  import AlgoliaCore
  import AlgoliaSearch

  func saveImageClassifications() async throws {
      struct Image: Codable {
          var objectID: String
          var imageURL: String
          var objects: [[String: AnyCodable]]
      }

      func getImageLabels(imageURL _: String, objectID _: String, scoreLimit _: Double) -> Image {
          // Implement your image classification logic here
          Image(objectID: "", imageURL: "", objects: [])
      }

      let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY")

      var records: [Image] = []

      try await client.browseObjects(
          indexName: "INDEX_NAME",
          browseParams: BrowseParamsObject(),
          aggregator: { (response: BrowseResponse<Image>) in
              records.append(contentsOf: response.hits.map { hit in
                  let imageURL = hit.imageURL
                  return getImageLabels(imageURL: imageURL, objectID: hit.objectID, scoreLimit: 0.5)
              })
          }
      )

      try await client.partialUpdateObjects(indexName: "INDEX_NAME", objects: records, createIfNotExists: true)
  }

  ```
</CodeGroup>

With this completed, each product record has a `labels` attribute:

```json JSON icon=braces theme={"system"}
{
  "title": "Men's Athletic Shirt",
  "objectID": "807281751",
  "imageURL": "https://images-na.ssl-images-amazon.com/images/I/41uIVaJOLdL.jpg",
  "labels":[
    {
      "description": "Outerwear",
      "score": 0.9513528
    }
  ]
}
```

To enable your users to textually search by label descriptions, you need to add `labels.description` to your [`searchableAttributes`](/doc/guides/managing-results/must-do/searchable-attributes).

To implement [search by image](/doc/guides/solutions/ecommerce/visual-image-search/tutorials/search-by-image), or if you want to filter on labels, you must include `labels.description` in [`attributesForFaceting`](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting).

### Using ViSenze

This example uses of the `getImageLabels` function from the [classification section](#using-visenze) to retrieve labels for each record while using [`browse`](/doc/libraries/sdk/v1/methods/browse). It then uses the [`partialUpdateObjects`](/doc/libraries/sdk/v1/methods/partial-update-objects) method to add the labels to the record.

It then updates the index settings to include each object's labels in [`attributesForFaceting`](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting) and [`searchableAttributes`](/doc/guides/managing-results/must-do/searchable-attributes).

<CodeGroup>
  ```cs C# theme={"system"}
  namespace Algolia;

  using System;
  using System.Collections.Generic;
  using System.Net.Http;
  using System.Text.Json;
  using Algolia.Search.Clients;
  using Algolia.Search.Http;
  using Algolia.Search.Models.Search;

  class SaveImageClassificationsAndSettings
  {
    class Image
    {
      public required string ImageUrl { get; set; }
      public required string ObjectId { get; set; }
      public required List<Dictionary<string, object>> Objects { get; set; }
    }

    // Retrieve labels
    async Task<Image> GetImageLabels(string imageURL, string objectID, float scoreLimit)
    {
      // Implement your image classification logic here
      return await Task.Run(() =>
        new Image
        {
          ImageUrl = "",
          ObjectId = "",
          Objects = [],
        }
      );
    }

    async Task Main(string[] args)
    {
      try
      {
        // API key ACL should include editSettings / addObject
        var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY"));

        var hits = await client.BrowseObjectsAsync<Image>(
          "INDEX_NAME",
          new BrowseParamsObject()
        );

        var images = hits.ToList();
        var records = images
          .Select(hit => GetImageLabels(hit.ImageUrl, hit.ObjectId, 0.5f))
          .Select(src => src.Result)
          .ToList();

        // Update records with image classifications
        await client.PartialUpdateObjectsAsync("INDEX_NAME", records, true);

        List<string> facets = [];
        List<string> attributes = [];

        foreach (var image in images)
        {
          foreach (var obj in image.Objects)
          {
            foreach (var key in obj.Keys)
            {
              if (obj[key] is IEnumerable<object>)
              {
                facets.Add($"searchable(objects.{key}.label)");
                facets.Add($"searchable(objects.{key}.score)");
                attributes.Add($"objects.{key}.label");
              }
            }
          }
        }

        var currentSettings = await client.GetSettingsAsync("INDEX_NAME");

        var settings = new IndexSettings
        {
          SearchableAttributes = currentSettings.SearchableAttributes.Concat(attributes).ToList(),
          AttributesForFaceting = currentSettings.AttributesForFaceting.Concat(facets).ToList(),
        };

        await client.SetSettingsAsync("INDEX_NAME", settings);
      }
      catch (Exception e)
      {
        Console.WriteLine(e.Message);
      }
    }
  }

  ```

  ```dart Dart theme={"system"}
  import 'package:algolia_client_search/algolia_client_search.dart';

  final class Image {
    final String imageUrl;
    final String objectID;
    List<Map<String, dynamic>> objects;

    Image(String imageUrl, String objectID, List<Map<String, dynamic>> objects)
        : imageUrl = imageUrl,
          objectID = objectID,
          objects = objects;
  }

  Future<Map<String, dynamic>> getImageLabels(
      String imageURL, String objectID, double scoreLimit) async {
    // Implement your image classification logic here
    return {};
  }

  void saveImageClassifications() async {
    // API key ACL should include editSettings / addObject
    final client =
        SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY');

    final List<Map<String, dynamic>> records = [];

    BrowseResponse? browseResponse;
    do {
      final browseParams =
          BrowseParamsObject(hitsPerPage: 1000, cursor: browseResponse?.cursor);
      browseResponse = await client.browse(
          indexName: "INDEX_NAME", browseParams: browseParams);
      for (final hit in browseResponse.hits) {
        final props = hit.toJson();
        final imageWithLabels =
            await getImageLabels(props["imageUrl"], hit.objectID, 0.5);
        records.add(imageWithLabels);
      }
    } while (browseResponse.cursor != null);

    final batchParams = BatchWriteParams(
        requests: records
            .map((record) => BatchRequest(
                  action: Action.partialUpdateObject,
                  body: record,
                ))
            .toList());

    // Update records with image classifications
    await client.batch(
      indexName: "INDEX_NAME",
      batchWriteParams: batchParams,
    );

    List<String> facets = [];
    List<String> attributes = [];

    for (var record in records) {
      record["objects"].forEach((object) {
        object.forEach((key, values) {
          if (values is Iterable) {
            facets.add("searchable(objects.$key.label)");
            facets.add("searchable(objects.$key.score)");
            attributes.add("objects.$key.label");
          }
        });
      });
    }

    final currentSettings = await client.getSettings(
      indexName: "INDEX_NAME",
    );

    final settings = IndexSettings(
      searchableAttributes:
          (currentSettings.searchableAttributes ?? []) + attributes,
      attributesForFaceting:
          (currentSettings.attributesForFaceting ?? []) + facets,
    );

    await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: settings,
    );
  }

  ```

  ```go Go theme={"system"}
  package main

  import (
  	"fmt"

  	"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
  )

  func saveImageClassificationsAndSettings() {
  	type Image struct {
  		ImageURL string           `json:"imageURL"`
  		ObjectID string           `json:"objectID"`
  		Objects  []map[string]any `json:"objects"`
  	}

  	getImageLabels := func(imageURL, objectID string, scoreLimit float64) Image {
  		// Implement your image classification logic here
  		return Image{
  			ImageURL: imageURL,
  			ObjectID: objectID,
  			Objects:  []map[string]any{},
  		}
  	}

  	client, err := search.NewClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
  	if err != nil {
  		// The client can fail to initialize if you pass an invalid parameter.
  		panic(err)
  	}

  	images := []Image{}

  	err = client.BrowseObjects("INDEX_NAME", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) {
  		if err != nil {
  			panic(err)
  		}

  		browseRes, ok := res.(search.BrowseResponse)
  		if !ok {
  			panic("Invalid response")
  		}

  		for _, hit := range browseRes.Hits {
  			props := hit.AdditionalProperties
  			imageURL, _ := props["imageURL"].(string)
  			images = append(images, getImageLabels(imageURL, hit.GetObjectID(), 0.5))
  		}
  	}))
  	if err != nil {
  		panic(err)
  	}

  	records := make([]map[string]any, len(images))
  	for i, img := range images {
  		records[i] = map[string]any{
  			"imageURL": img.ImageURL,
  			"objectID": img.ObjectID,
  			"objects":  img.Objects,
  		}
  	}

  	_, err = client.PartialUpdateObjects(
  		"INDEX_NAME", records, search.WithCreateIfNotExists(true))
  	if err != nil {
  		panic(err)
  	}

  	facets := []string{}
  	attributes := []string{}

  	for _, record := range images {
  		for _, obj := range record.Objects {
  			for key, values := range obj {
  				if _, ok := values.([]any); ok {
  					facets = append(facets,
  						fmt.Sprintf("searchable(objects.%s.label)", key),
  						fmt.Sprintf("searchable(objects.%s.score)", key),
  					)
  					attributes = append(attributes, fmt.Sprintf("objects.%s.label)", key))
  				}
  			}
  		}
  	}

  	currentSettings, err := client.GetSettings(client.NewApiGetSettingsRequest(
  		"INDEX_NAME"))
  	if err != nil {
  		panic(err)
  	}

  	settings := search.NewIndexSettings().
  		SetSearchableAttributes(append(currentSettings.SearchableAttributes, attributes...)).
  		SetAttributesForFaceting(append(currentSettings.AttributesForFaceting, facets...))

  	_, err = client.SetSettings(client.NewApiSetSettingsRequest(
  		"INDEX_NAME", settings))
  	if err != nil {
  		panic(err)
  	}
  }

  ```

  ```java Java theme={"system"}
  package com.algolia;

  import com.algolia.api.SearchClient;
  import com.algolia.config.*;
  import com.algolia.model.search.*;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;

  class saveImageClassificationsAndSettings {

    static class Image {

      public String imageURL;
      public String objectID;
      public List<Map<String, Object>> objects;

      public Image(String imageURL, String objectID, List<Map<String, Object>> objects) {
        this.imageURL = imageURL;
        this.objectID = objectID;
        this.objects = objects;
      }
    }

    // Retrieve labels
    static Image getImageLabels(String imageURL, String objectID, float scoreLimit) {
      // Implement your image classification logic here
      return new Image(imageURL, objectID, new ArrayList<>());
    }

    public static void main(String[] args) throws Exception {
      // API key ACL should include editSettings / addObject
      try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
        var hits = client.browseObjects("INDEX_NAME", Image.class);

        List<Image> records = new ArrayList<>();
        for (var hit : hits) {
          var image = getImageLabels(hit.imageURL, hit.objectID, 0.5f);
          records.add(image);
        }

        // Update records with image classifications
        client.partialUpdateObjects("INDEX_NAME", records, true);

        List<String> facets = new ArrayList<>();
        List<String> attributes = new ArrayList<>();

        records.forEach(record -> {
          record.objects.forEach(object -> {
            object.forEach((key, values) -> {
              if (values instanceof Iterable) {
                facets.add("searchable(objects." + key + ".label)");
                facets.add("searchable(objects." + key + ".score)");
                attributes.add("objects." + key + ".label");
              }
            });
          });
        });

        var currentSettings = client.getSettings("INDEX_NAME");

        var attributesForFaceting =
          currentSettings.getAttributesForFaceting() != null ? currentSettings.getAttributesForFaceting() : new ArrayList<String>();
        var searchableAttributes =
          currentSettings.getSearchableAttributes() != null ? currentSettings.getSearchableAttributes() : new ArrayList<String>();

        attributesForFaceting.addAll(facets);
        searchableAttributes.addAll(attributes);

        var settings = new IndexSettings().setAttributesForFaceting(attributesForFaceting).setSearchableAttributes(searchableAttributes);

        client.setSettings("INDEX_NAME", settings);
      } catch (Exception e) {
        System.out.println(e.getMessage());
      }
    }
  }

  ```

  ```js JavaScript theme={"system"}
  import { algoliasearch } from 'algoliasearch';

  interface Image extends Record<string, unknown> {
    imageURL: string;
    objectID: string;
    objects: Record<string, unknown>[];
  }

  async function saveImageClassifications() {
    // Retrieve labels
    function getImageLabels(_imageURL, _objectID, _scoreLimit): Image {
      // Implement your image classification logic here
      return { imageURL: '', objectID: '', objects: [] };
    }

    // API key ACL should include editSettings / addObject
    const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');

    const records: Image[] = [];

    await client.browseObjects<Image>({
      indexName: 'INDEX_NAME',
      browseParams: undefined,
      aggregator: (browseResponse) => {
        records.push(
          ...browseResponse.hits.map((hit) => {
            return getImageLabels(hit.imageURL, hit.objectID, 0.5);
          }),
        );
      },
    });

    // Update records with image classifications
    await client.partialUpdateObjects({ indexName: 'products', objects: records, createIfNotExists: true });

    const facets: string[] = [];
    const attributes: string[] = [];

    records.forEach((record) => {
      record.objects.forEach((obj) => {
        Object.entries(obj).forEach(([key, values]) => {
          if (Array.isArray(values)) {
            // Checking if iterable
            facets.push(`searchable(objects.${key}.label)`);
            facets.push(`searchable(objects.${key}.score)`);
            attributes.push(`objects.${key}.label`);
          }
        });
      });
    });

    const currentSettings = await client.getSettings({ indexName: 'indexName' });

    const settings = {
      searchableAttributes: (currentSettings.searchableAttributes ?? []).concat(attributes ?? []),
      attributesForFaceting: (currentSettings.attributesForFaceting ?? []).concat(facets ?? []),
    };

    await client.setSettings({ indexName: 'indexName', indexSettings: settings });
  }

  ```

  ```kotlin Kotlin theme={"system"}
  import com.algolia.client.api.SearchClient
  import com.algolia.client.configuration.*
  import com.algolia.client.extensions.*
  import com.algolia.client.model.search.BrowseParamsObject
  import com.algolia.client.model.search.IndexSettings
  import com.algolia.client.transport.*
  import kotlinx.serialization.json.Json
  import kotlinx.serialization.json.encodeToJsonElement
  import kotlinx.serialization.json.jsonObject

  suspend fun saveImageClassificationsAndSettings() {
    data class Image(val imageURL: String, val objectID: String, val objects: List<Map<String, Any>>)

    // Retrieve labels
    fun getImageLabels(imageURL: String, objectID: String, scoreLimit: Float): Image {
      // Implement your image classification logic here
      return Image(imageURL, objectID, emptyList())
    }

    // API key ACL should include editSettings / addObject
    try {
      val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

      val images: MutableList<Image> = mutableListOf()

      client.browseObjects(
        indexName = "INDEX_NAME",
        params = BrowseParamsObject(),
        aggregator = { browseResponse ->
          images.addAll(
            browseResponse.hits.map {
              val props = it.additionalProperties ?: emptyMap()
              return@map getImageLabels(
                props.getOrElse("imageURL") { "" }.toString(),
                it.objectID,
                0.5f,
              )
            }
          )
        },
      )

      val records = images.map { Json.encodeToJsonElement(it).jsonObject }

      // Update records with image classifications
      client.partialUpdateObjects(
        indexName = "INDEX_NAME",
        objects = records,
        createIfNotExists = true,
      )

      val facets = mutableListOf<String>()
      val attributes = mutableListOf<String>()

      images.forEach { record ->
        record.objects.forEach { obj ->
          obj.forEach { (key, values) ->
            if (values is Iterable<*>) {
              facets.add("searchable(objects.$key.label)")
              facets.add("searchable(objects.$key.score)")
              attributes.add("objects.$key.label")
            }
          }
        }
      }

      val currentSettings = client.getSettings(indexName = "INDEX_NAME")

      val settings =
        IndexSettings(
          searchableAttributes = currentSettings.searchableAttributes?.plus(attributes),
          attributesForFaceting = currentSettings.attributesForFaceting?.plus(facets),
        )

      client.setSettings(indexName = "INDEX_NAME", indexSettings = settings)
    } catch (e: Exception) {
      println(e.message)
    }
  }

  ```

  ```php PHP theme={"system"}
  <?php

  use Algolia\AlgoliaSearch\Api\SearchClient;
  use Algolia\AlgoliaSearch\Model\Search\IndexSettings;

  $getImageLabels = function ($imageURL, $objectID, $scoreLimit) {
      // Implement your image classification logic here
      return ['objectID' => '', 'imageURL' => '', 'objects' => []];
  };

  $client = SearchClient::create('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');

  $records = [];

  $hits = $client->browseObjects('INDEX_NAME');
  foreach ($hits as $hit) {
      $imageURL = $hit['imageURL'];
      $records[] = $getImageLabels($imageURL, $hit['objectID'], 0.5);
  }

  $client->partialUpdateObjects(
      'INDEX_NAME',
      $records,
      true,
  );

  $facets = [];
  $attributes = [];

  foreach ($records as $record) {
      foreach ($record['objects'] as $obj) {
          foreach ($obj as $key => $values) {
              if (is_array($values)) {
                  $facets[] = "searchable(objects.{$key}.label)";
                  $facets[] = "searchable(objects.{$key}.score)";
                  $attributes[] = "objects.{$key}.label)";
              }
          }
      }
  }

  $currentSettings = $client->getSettings(
      'INDEX_NAME',
  );

  $settings = (new IndexSettings())
      ->setSearchableAttributes(array_merge($currentSettings->searchableAttributes ?? [], $attributes))
      ->setAttributesForFaceting(array_merge($currentSettings->attributesForFaceting ?? [], $facets))
  ;

  $client->setSettings(
      'INDEX_NAME',
      $settings,
  );

  ```

  ```python Python theme={"system"}
  from algoliasearch.search.client import SearchClientSync

  from algoliasearch.search.models.browse_response import BrowseResponse
  from algoliasearch.search.models.index_settings import IndexSettings


  def _get_image_labels(image_url, object_id, score_limit):
      # Implement your image classification logic here
      return {"objectID": "", "imageURL": "", "objects": []}


  _client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")


  records = []


  def _aggregator(res: BrowseResponse):
      for hit in res.hits:
          props = hit.to_dict()
          image_url = props["imageURL"]
          records.append(_get_image_labels(image_url, hit.object_id, 0.5))


  _client.browse_objects("INDEX_NAME", aggregator=_aggregator)

  _client.partial_update_objects(
      index_name="INDEX_NAME",
      objects=records,
      create_if_not_exists=True,
  )

  facets = []
  attributes = []

  for record in records:
      for obj in record["objects"]:
          for key, values in obj.items():
              if isinstance(values, list):
                  facets.extend(
                      [
                          f"searchable(objects.{key}.label)",
                          f"searchable(objects.{key}.score)",
                      ]
                  )
                  attributes.append(f"objects.{key}.label)")

  current_settings = _client.get_settings(
      index_name="INDEX_NAME",
  )

  settings = IndexSettings(
      searchable_attributes=(current_settings.searchable_attributes or []) + attributes,
      attributes_for_faceting=(current_settings.attributes_for_faceting or []) + facets,
  )

  _client.set_settings(
      index_name="INDEX_NAME",
      index_settings=settings,
  )

  ```

  ```ruby Ruby theme={"system"}
  require "algolia"

  def get_image_labels(image_url, object_id, score_limit)
    # Implement your image classification logic here
    {"objectID" => "", "imageURL" => "", "objects" => []}
  end

  client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")

  records = []

  client.browse_objects(
    "INDEX_NAME"
  ) do |resp|
    resp.hits.each do |hit|
      image_url = hit["imageURL"]
      records << get_image_labels(image_url, hit["objectID"], 0.5)
    end
  end

  client.partial_update_objects("INDEX_NAME", records, true)

  facets = []
  attributes = []

  records.each do |record|
    record["objects"].each do |obj|
      obj.each do |key, values|
        if values.is_a?(Array)
          facets.push(
            "searchable(objects.#{key}.label)",
            "searchable(objects.#{key}.score)"
          )
          attributes << "objects.#{key}.label)"
        end
      end
    end
  end

  current_settings = client.get_settings("INDEX_NAME")

  settings = Algolia::Search::IndexSettings.new(
    searchable_attributes: (current_settings.searchable_attributes || []) + attributes,
    attributes_for_faceting: (current_settings.attributes_for_faceting || []) + facets
  )

  client.set_settings("INDEX_NAME", settings)

  ```

  ```scala Scala theme={"system"}
  import scala.concurrent.{Await, Future}
  import scala.concurrent.duration.Duration
  import scala.concurrent.ExecutionContext.Implicits.global

  import algoliasearch.api.SearchClient
  import algoliasearch.config.*
  import algoliasearch.extension.SearchClientExtensions
  import algoliasearch.search.{BrowseParamsObject, IndexSettings}

  def saveImageClassificationsAndSettings(): Future[Unit] = {
    // Retrieve labels
    def getImageLabels(imageURL: String, objectID: String, scoreLimit: Float): Image = {
      // Implement your image classification logic here
      Image(imageURL, objectID, Seq())
    }

    // API key ACL should include editSettings / addObject
    val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

    var records: Seq[Image] = Seq.empty

    client.browseObjects(
      indexName = "INDEX_NAME",
      browseParams = BrowseParamsObject(),
      aggregator = { browseResponse =>
        records = records.appendedAll(
          browseResponse.hits.map { hit =>
            val props = hit.additionalProperties.getOrElse(List()).toMap
            getImageLabels(props.get("imageURL").toString, hit.objectID, 0.5f)
          }
        )
      }
    )

    // Update records with image classifications
    client.partialUpdateObjects(
      indexName = "INDEX_NAME",
      objects = records,
      createIfNotExists = true
    )

    var facets: Seq[String] = Seq.empty
    var attributes: Seq[String] = Seq.empty

    records.foreach(record => {
      record.objects.foreach(obj => {
        obj.foreach { case (key, value) =>
          if (value.isInstanceOf[scala.collection.Iterable[?]]) {
            facets = facets.appended(s"searchable(objects.${key}.label)")
            facets = facets.appended(s"searchable(objects.${key}.score)")
            attributes = attributes.appended(s"objects.${key}.label")
          }
        }
      })
    })

    val currentSettings = Await.result(
      client.getSettings(
        indexName = "INDEX_NAME"
      ),
      Duration(5, "sec")
    )

    val settings = IndexSettings(
      attributesForFaceting = Some(currentSettings.attributesForFaceting.getOrElse(Seq.empty) ++ facets),
      searchableAttributes = Some(currentSettings.searchableAttributes.getOrElse(Seq.empty) ++ attributes)
    )

    client
      .setSettings(
        indexName = "INDEX_NAME",
        indexSettings = settings
      )
      .map { response =>
        println(response)
      }
      .recover { case ex: Exception =>
        println(s"An error occurred: ${ex.getMessage}")
      }
  }

  ```

  ```swift Swift theme={"system"}
  import Foundation
  #if os(Linux) // For linux interop
      import FoundationNetworking
  #endif

  import AlgoliaCore
  import AlgoliaSearch

  func saveImageClassificationsAndSettings() async throws {
      struct Image: Codable {
          var objectID: String
          var imageURL: String
          var objects: [[String: AnyCodable]]
      }

      func getImageLabels(imageURL _: String, objectID _: String, scoreLimit _: Double) -> Image {
          // Implement your image classification logic here
          Image(objectID: "", imageURL: "", objects: [])
      }

      let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY")

      var records: [Image] = []

      try await client.browseObjects(
          indexName: "INDEX_NAME",
          browseParams: BrowseParamsObject(),
          aggregator: { (response: BrowseResponse<Image>) in
              records.append(contentsOf: response.hits.map { hit in
                  let imageURL = hit.imageURL
                  return getImageLabels(imageURL: imageURL, objectID: hit.objectID, scoreLimit: 0.5)
              })
          }
      )

      try await client.partialUpdateObjects(indexName: "INDEX_NAME", objects: records, createIfNotExists: true)

      var facets: [String] = []
      var attributes: [String] = []

      for record in records {
          for obj in record.objects {
              for (key, values) in obj {
                  if values.value is [Any] {
                      facets.append(contentsOf: [
                          "searchable(objects.\(key).label)",
                          "searchable(objects.\(key).score)",
                      ])
                      attributes.append("objects.\(key).label)")
                  }
              }
          }
      }

      let currentSettings = try await client.getSettings(indexName: "INDEX_NAME")

      let settings = IndexSettings(
          attributesForFaceting: (currentSettings.attributesForFaceting ?? []) + facets,
          searchableAttributes: (currentSettings.searchableAttributes ?? []) + attributes
      )

      try await client.setSettings(indexName: "INDEX_NAME", indexSettings: settings)
  }

  ```
</CodeGroup>

With this completed, your records contain the recognized objects with their coordinates, labels, and scores.

```json JSON icon=braces theme={"system"}
{
  "imageURL": "https://images-na.ssl-images-amazon.com/images/I/41uIVaJOLdL.jpg",
  "objectID": "439784001",
  "objects": [
      {
        "x1": 65,
        "y1": 14,
        "x2": 729,
        "y2": 788,
        "apparel": [{ "label": "upper_body_garment", "score": 0.98 }],
        "product_color": [{ "label": "blue", "score": 0.91 }],
        "neckline": [{ "label": "round_neck", "score": 0.81 }]
      }
  ]
}
```

## Next steps

Enriching your data with image classifications can already lead to a more relevant search and discovery experience. You can take it further by building an experience that enables your users to [search for products with images](/doc/guides/solutions/ecommerce/visual-image-search/tutorials/search-by-image).
