class_docstr: Mixin of helpers to allow upload of custom metadata.
docstr: Uploads custom metadata to the Lightly Platform.
  
  The custom metadata is expected in a format similar to the COCO annotations:
  Under the key "images" there should be a list of dictionaries, each with
  a file_name and id. Under the key "metadata", the custom metadata is stored
  as a list of dictionaries, each with an image ID that corresponds to an image
  under the key "images".
  
  Example:
      >>> custom_metadata = {
      >>>     "images": [
      >>>         {
      >>>             "file_name": "image0.jpg",
      >>>             "id": 0,
      >>>         },
      >>>         {
      >>>             "file_name": "image1.jpg",
      >>>             "id": 1,
      >>>         }
      >>>     ],
      >>>     "metadata": [
      >>>         {
      >>>             "image_id": 0,
      >>>             "number_of_people": 3,
      >>>             "weather": {
      >>>                 "scenario": "cloudy",
      >>>                 "temperature": 20.3
      >>>             }
      >>>         },
      >>>         {
      >>>             "image_id": 1,
      >>>             "number_of_people": 1,
      >>>             "weather": {
      >>>                 "scenario": "rainy",
      >>>                 "temperature": 15.0
      >>>             }
      >>>         }
      >>>     ]
      >>> }
  
  Args:
      custom_metadata:
          Custom metadata as described above.
      verbose:
          If True, displays a progress bar during the upload.
      max_workers:
          Maximum number of concurrent threads during upload.
  
  :meta private:  # Skip docstring generation
docstr_tok: ["Uploads","custom","metadata","to","the","Lightly","Platform",".","The","custom","metadata","is","expected","in","a","format","similar","to","the","COCO","annotations",":","Under","the","key","``","images","''","there","should","be","a","list","of","dictionaries",",","each","with","a","file_name","and","id",".","Under","the","key","``","metadata","''",",","the","custom","metadata","is","stored","as","a","list","of","dictionaries",",","each","with","an","image","ID","that","corresponds","to","an","image","under","the","key","``","images","''",".","Example",":",">",">",">","custom_metadata","=","{",">",">",">","``","images","''",":","[",">",">",">","{",">",">",">","``","file_name","''",":","``","image0.jpg","''",",",">",">",">","``","id","''",":","0",",",">",">",">","}",",",">",">",">","{",">",">",">","``","file_name","''",":","``","image1.jpg","''",",",">",">",">","``","id","''",":","1",",",">",">",">","}",">",">",">","]",",",">",">",">","``","metadata","''",":","[",">",">",">","{",">",">",">","``","image_id","''",":","0",",",">",">",">","``","number_of_people","''",":","3",",",">",">",">","``","weather","''",":","{",">",">",">","``","scenario","''",":","``","cloudy","''",",",">",">",">","``","temperature","''",":","20.3",">",">",">","}",">",">",">","}",",",">",">",">","{",">",">",">","``","image_id","''",":","1",",",">",">",">","``","number_of_people","''",":","1",",",">",">",">","``","weather","''",":","{",">",">",">","``","scenario","''",":","``","rainy","''",",",">",">",">","``","temperature","''",":","15.0",">",">",">","}",">",">",">","}",">",">",">","]",">",">",">","}","Args",":","custom_metadata",":","Custom","metadata","as","described","above",".","verbose",":","If","True",",","displays","a","progress","bar","during","the","upload",".","max_workers",":","Maximum","number","of","concurrent","threads","during","upload",".",":","meta","private",":","#","Skip","docstring","generation"]
code: def upload_custom_metadata(
      self,
      custom_metadata: Dict[str, Any],
      verbose: bool = False,
      max_workers: int = 8,
  ) -> None:
      """Uploads custom metadata to the Lightly Platform.
  
      The custom metadata is expected in a format similar to the COCO annotations:
      Under the key "images" there should be a list of dictionaries, each with
      a file_name and id. Under the key "metadata", the custom metadata is stored
      as a list of dictionaries, each with an image ID that corresponds to an image
      under the key "images".
  
      Example:
          >>> custom_metadata = {
          >>>     "images": [
          >>>         {
          >>>             "file_name": "image0.jpg",
          >>>             "id": 0,
          >>>         },
          >>>         {
          >>>             "file_name": "image1.jpg",
          >>>             "id": 1,
          >>>         }
          >>>     ],
          >>>     "metadata": [
          >>>         {
          >>>             "image_id": 0,
          >>>             "number_of_people": 3,
          >>>             "weather": {
          >>>                 "scenario": "cloudy",
          >>>                 "temperature": 20.3
          >>>             }
          >>>         },
          >>>         {
          >>>             "image_id": 1,
          >>>             "number_of_people": 1,
          >>>             "weather": {
          >>>                 "scenario": "rainy",
          >>>                 "temperature": 15.0
          >>>             }
          >>>         }
          >>>     ]
          >>> }
  
      Args:
          custom_metadata:
              Custom metadata as described above.
          verbose:
              If True, displays a progress bar during the upload.
          max_workers:
              Maximum number of concurrent threads during upload.
  
      :meta private:  # Skip docstring generation
      """
  
      self.verify_custom_metadata_format(custom_metadata)
  
      # For each metadata, we need the corresponding sample_id
      # on the server. The mapping is:
      # metadata -> image_id -> filename -> sample_id
  
      image_id_to_filename = {
          image_info[COCO_ANNOTATION_KEYS.images_id]: image_info[
              COCO_ANNOTATION_KEYS.images_filename
          ]
          for image_info in custom_metadata[COCO_ANNOTATION_KEYS.images]
      }
  
      samples: List[SampleDataModes] = list(
          paginate_endpoint(
              self._samples_api.get_samples_partial_by_dataset_id,
              page_size=25000,  # as this information is rather small, we can request a lot of samples at once
              dataset_id=self.dataset_id,
              mode=SamplePartialMode.FILENAMES,
          )
      )
  
      filename_to_sample_id = {
          sample.file_name: sample.id for sample in samples
      }
  
      upload_requests = []
      for metadata in custom_metadata[
          COCO_ANNOTATION_KEYS.custom_metadata
      ]:
          image_id = metadata[
              COCO_ANNOTATION_KEYS.custom_metadata_image_id
          ]
          filename = image_id_to_filename.get(image_id, None)
          if filename is None:
              hipify.print_as_warning(
                  "No image found for custom metadata annotation "
                  f"with image_id {image_id}. "
                  "This custom metadata annotation is skipped. ",
                  InvalidCustomMetadataWarning,
              )
              continue
          sample_id = filename_to_sample_id.get(filename, None)
          if sample_id is None:
              hipify.print_as_warning(
                  "You tried to upload custom metadata for a sample with "
                  f"filename {{{filename}}}, "
                  "but a sample with this filename "
                  "does not exist on the server. "
                  "This custom metadata annotation is skipped. ",
                  InvalidCustomMetadataWarning,
              )
              continue
          upload_request = (metadata, sample_id)
          upload_requests.append(upload_request)
  
      # retry upload if it times out
      def upload_sample_metadata(upload_request):
          metadata, sample_id = upload_request
          request = SampleUpdateRequest(custom_meta_data=metadata)
          return retry(
              self._samples_api.update_sample_by_id,
              sample_update_request=request,
              dataset_id=self.dataset_id,
              sample_id=sample_id,
          )
  
      # Upload in parallel with a limit on the number of concurrent requests
      with ThreadPoolExecutor(max_workers=max_workers) as executor:
          # get iterator over results
          results = executor.map(
              upload_sample_metadata, upload_requests
          )
          if verbose:
              results = tqdm(
                  results, unit="metadata", total=len(upload_requests)
              )
          # iterate over results to make sure they are completed
          list(results)
  
file_path: lightly/lightly/api/api_workflow_upload_metadata.py
imports: from concurrent.futures import ThreadPoolExecutor
  from typing import Any, Dict, List, Union
  from requests import Response
  from tqdm import tqdm
  from lightly.api.utils import paginate_endpoint, retry
  from lightly.openapi_generated.swagger_client.models import ConfigurationEntry, ConfigurationSetRequest, SampleDataModes, SamplePartialMode, SampleUpdateRequest
  from lightly.utils import hipify
  from lightly.utils.io import COCO_ANNOTATION_KEYS
label_desc: Use image node_id 3 for calling the _UploadCustomMetadataMixin obj's underlying member method code with example usage: obj.upload_custom_metadata(custom_metadata, verbose, max_workers) and returns: retry