From 76338d69da2dc93cbfeeb0f920ac2588f2aa8cee Mon Sep 17 00:00:00 2001 From: Anthony Naddeo Date: Thu, 7 Sep 2023 15:49:30 -0700 Subject: [PATCH] Update init to allow suppression The new init system can make for confusing demos for existing users and new use cases. Making this suppression option to allow us to avoid output when we don't need to see it. --- ...gle_Image_Tracing_Profile_to_WhyLabs.ipynb | 3029 +++++++++++++++++ python/test_notebooks/notebook_tests.py | 1 + python/whylogs/api/whylabs/session/config.py | 17 +- python/whylogs/api/whylabs/session/prompts.py | 23 +- .../api/whylabs/session/session_types.py | 45 +- python/whylogs/extras/image_metric.py | 13 +- 6 files changed, 3092 insertions(+), 36 deletions(-) create mode 100644 python/examples/integrations/writers/Single_Image_Tracing_Profile_to_WhyLabs.ipynb diff --git a/python/examples/integrations/writers/Single_Image_Tracing_Profile_to_WhyLabs.ipynb b/python/examples/integrations/writers/Single_Image_Tracing_Profile_to_WhyLabs.ipynb new file mode 100644 index 0000000000..158be15843 --- /dev/null +++ b/python/examples/integrations/writers/Single_Image_Tracing_Profile_to_WhyLabs.ipynb @@ -0,0 +1,3029 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "-NPr95FQN4jX" + }, + "source": [ + "# Uploading and Retrieving Single Image Profiles to WhyLabs\n", + "\n", + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/whylabs/whylogs/blob/dev/felipe/single-image-example/python/examples/integrations/writers/Single_Image_Profile_to_WhyLabs.ipynb)\n", + "\n", + "In this example, we'll walk through the process of uploading single image profiles to WhyLabs.\n", + "\n", + "Additionally, we'll explain how to retrieve the previously uploaded original profiles.\n", + "\n", + "> Note: This example assumes that you have an organization configured for single profile uploads. If you don't have one, please contact the WhyLabs team." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "P9-pHz-N8wur" + }, + "source": [ + "## Installing whylogs" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "ALO8hWy9N4jY" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mDEPRECATION: feast 0.22.4 has a non-standard dependency specifier googleapis-common-protos<2,>=1.52.*. pip 23.3 will enforce this behaviour change. A possible replacement is to upgrade to a newer version of feast or contact the author to suggest that they release a version with a conforming dependency specifiers. Discussion can be found at https://github.com/pypa/pip/issues/12063\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: feast 0.22.4 has a non-standard dependency specifier PyYAML<7,>=5.4.*. pip 23.3 will enforce this behaviour change. A possible replacement is to upgrade to a newer version of feast or contact the author to suggest that they release a version with a conforming dependency specifiers. Discussion can be found at https://github.com/pypa/pip/issues/12063\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: feast 0.22.4 has a non-standard dependency specifier dask<2022.02.0,>=2021.*. pip 23.3 will enforce this behaviour change. A possible replacement is to upgrade to a newer version of feast or contact the author to suggest that they release a version with a conforming dependency specifiers. Discussion can be found at https://github.com/pypa/pip/issues/12063\u001b[0m\u001b[33m\n", + "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install whylogs[embeddings,viz,image] -q" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "w4kzfjmC-y-S" + }, + "source": [ + "## WhyLabs Credentials\n", + "\n", + "Let's set the WhyLabs credentials as environment variables. The WhyLabs Writer will check for the existence of these variables in order to send the profiles to your dashboard." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "LgyC7GsWCzO1", + "outputId": "390dd873-7bfe-4fcd-d360-c3280f59c935" + }, + "outputs": [], + "source": [ + "import whylogs as why\n", + "import getpass\n", + "import os\n", + "\n", + "# We don't need to see the autogenerated upload links in this notebook\n", + "os.environ[\"WHYLOGS_SUPPRESS_LOG_OUTPUT\"] = \"True\"\n", + "\n", + "why.init(allow_anonymous=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "m6BNxmRm8_gi" + }, + "source": [ + "## Defining the images\n", + "\n", + "For this example, we will create two images with the PIL library. We will log each image in single profiles in order to upload them to WhyLabs." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 529 + }, + "id": "4zHh_a7b8Pgv", + "outputId": "977b6d0a-3b3c-41eb-b4cd-9b1471002417" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from PIL import Image\n", + "\n", + "img1 = Image.effect_mandelbrot((256, 256), (-3, -2.5, 2, 2.5), 9)\n", + "img2 = Image.effect_mandelbrot((256, 256), (-3, -2.5, 2, 2.5), 20)\n", + "\n", + "display(img1)\n", + "display(img2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "h6DN2Jdc9YS7" + }, + "source": [ + "We will use the concept of trace id's to be able to trace our single image profiles. Let's generate uuids for both images and use the identifiers as our trace IDs.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Q5s-rTlKOTnR", + "outputId": "c4666b13-9ac9-4b1e-92f9-108c0ec96a65" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trace ids generated: b8f567cd-779e-400d-a99c-a737a4257332, and b393df6b-bde0-45c2-8c5d-b304d8f27d1a\n" + ] + } + ], + "source": [ + "import uuid\n", + "\n", + "# Here we show that you can pass in your own string for a unique identifier as a trace_id\n", + "trace_id1 = str(uuid.uuid4())\n", + "trace_id2 = str(uuid.uuid4())\n", + "print(f\"trace ids generated: {trace_id1}, and {trace_id2}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jxauTm2v-P0b" + }, + "source": [ + "We can now call `log_image` to create profiles for each image. We will set the profile's trace ID by passing the uuids into each profile's metadada:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "lUdxL7EGOaWv", + "outputId": "16c96e86-098a-4399-dd08-034cb06b976c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'whylabs.traceId': 'b8f567cd-779e-400d-a99c-a737a4257332', 'whylogs.creationTimestamp': '1694127199095'}\n", + "{'whylabs.traceId': 'b393df6b-bde0-45c2-8c5d-b304d8f27d1a', 'whylogs.creationTimestamp': '1694127199811'}\n" + ] + } + ], + "source": [ + "from whylogs.extras.image_metric import log_image\n", + "\n", + "profile1 = log_image(img1, trace_id=trace_id1).profile()\n", + "profile2 = log_image(img2, trace_id=trace_id2).profile()\n", + "\n", + "print(profile1.view()._metadata)\n", + "print(profile2.view()._metadata)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jfNXAQ-gFFzT" + }, + "source": [ + "## Retrieving the Profiles\n", + "\n", + "To retrieve the profiles, we need to provide the proper Trace ID and make use of the whylabs client for the `get_profile_traces` API. Let's wrap the necessary steps into a single function to retrieve the profiles based on the WhyLabs writer and trace id:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "lApPPk7QO4fR", + "outputId": "c0344eea-ef61-4fb5-869d-2cfea5418c9a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Downloaded profiles with trace id {'whylogs.creationTimestamp': '1694127199095', 'whylabs.traceId': 'b8f567cd-779e-400d-a99c-a737a4257332'}, {'whylogs.creationTimestamp': '1694127199811', 'whylabs.traceId': 'b393df6b-bde0-45c2-8c5d-b304d8f27d1a'}\n" + ] + } + ], + "source": [ + "from whylogs.api.writer.whylabs import WhyLabsWriter\n", + "from whylabs_client.api.dataset_profile_api import DatasetProfileApi\n", + "from whylabs_client.model.profile_traces_response import ProfileTracesResponse\n", + "from whylogs.core import DatasetProfileView\n", + "import time\n", + "\n", + "whylabs_writer = WhyLabsWriter()\n", + "\n", + "def get_profile_from_whylabs(writer, trace_id:str):\n", + " dataset_api = DatasetProfileApi(writer._api_client)\n", + " download_url = None\n", + " for _ in range(15):\n", + " response: ProfileTracesResponse = dataset_api.get_profile_traces(\n", + " org_id=writer._org_id,\n", + " dataset_id=writer._dataset_id,\n", + " trace_id=trace_id,\n", + " )\n", + " traces = response.get(\"traces\")\n", + " if len(traces) == 0:\n", + " time.sleep(2)\n", + " else:\n", + " download_url = traces[0][\"download_url\"]\n", + " break\n", + " if download_url is None:\n", + " raise Exception(f\"No results found for trace id {trace_id}\")\n", + " headers = {\"Content-Type\": \"application/octet-stream\"}\n", + " downloaded_profile = writer._s3_pool.request(\"GET\", download_url, headers=headers, timeout=writer._timeout_seconds)\n", + " deserialized_view = DatasetProfileView.deserialize(downloaded_profile.data)\n", + " return deserialized_view\n", + "\n", + "downloaded_view_1 = get_profile_from_whylabs(whylabs_writer, trace_id1)\n", + "downloaded_view_2 = get_profile_from_whylabs(whylabs_writer, trace_id2)\n", + "\n", + "print(f\"Downloaded profiles with trace id {downloaded_view_1._metadata}, {downloaded_view_2._metadata}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ITtnwObQFLCk" + }, + "source": [ + "## Comparing the Profiles\n", + "\n", + "Let's make sure that the retrieved profiles are indeed equal to the uploaded ones. To do so, let's visualize each profile for the first image with the __Notebook Profile Visualizer__. This will give us a profile visualization with the calculated metrics for the image.\n", + "\n", + "Here's the report for the original profile:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "NBjHjqI0109t", + "outputId": "edd513bf-e4df-4def-9f25-38f344c64eea" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from whylogs.viz import NotebookProfileVisualizer\n", + "\n", + "visualization = NotebookProfileVisualizer()\n", + "visualization.set_profiles(target_profile_view=profile1.view())\n", + "\n", + "visualization.profile_summary()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Oz8QkkGuFl1D" + }, + "source": [ + "And this is the report for the retrieved profile. We should see the same features and values in both profiles." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "jw1Dw_rA47KE", + "outputId": "6026b4e9-2ffe-497b-d08e-cb6d5e439706" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "visualization.set_profiles(target_profile_view=downloaded_view_1)\n", + "\n", + "visualization.profile_summary()" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + }, + "vscode": { + "interpreter": { + "hash": "d39f874c9b8a97550ecbd783714b95e79c9b905449b34f44c40e3bf053b54b41" + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/python/test_notebooks/notebook_tests.py b/python/test_notebooks/notebook_tests.py index b8ef7bc1b6..eb276a8aee 100644 --- a/python/test_notebooks/notebook_tests.py +++ b/python/test_notebooks/notebook_tests.py @@ -8,6 +8,7 @@ OUTPUT_NOTEBOOK = "output.ipynb" skip_notebooks = [ "Guest Session.ipynb", + "Single_Image_Tracing_Profile_to_WhyLabs", "Pyspark_Profiling.ipynb", "Kafka_Example.ipynb", "Writing_to_WhyLabs.ipynb", diff --git a/python/whylogs/api/whylabs/session/config.py b/python/whylogs/api/whylabs/session/config.py index 04dc1d54a0..65ec9c83bf 100644 --- a/python/whylogs/api/whylabs/session/config.py +++ b/python/whylogs/api/whylabs/session/config.py @@ -292,8 +292,8 @@ def reset_config(self) -> None: def notify_session_type(self) -> None: config_path = self.get_config_file_path() - il.message(f"Initializing session with config {config_path}") - il.message() + il.message(f"Initializing session with config {config_path}", ignore_suppress=True) + il.message(ignore_suppress=True) if self.session_type == SessionType.WHYLABS: self._notify_type_whylabs(self.require_api_key()) elif self.session_type == SessionType.LOCAL: @@ -309,22 +309,23 @@ def _notify_type_whylabs(self, api_key: str) -> None: else: org_id = self.get_org_id() or "not set" # Shouldn't be possible to be None at this point - il.success(f"Using session type: {SessionType.WHYLABS.name}") - il.option(f"org id: {org_id}") - il.option(f"api key: {parsed_api_key.api_key_id}") + il.success(f"Using session type: {SessionType.WHYLABS.name}", ignore_suppress=True) + il.option(f"org id: {org_id}", ignore_suppress=True) + il.option(f"api key: {parsed_api_key.api_key_id}", ignore_suppress=True) if default_dataset_id: il.option(f"default dataset: {default_dataset_id}") def _notify_type_anon(self) -> None: anonymous_session_id = self.get_session_id() - il.success(f"Using session type: {SessionType.WHYLABS_ANONYMOUS.name}") + il.success(f"Using session type: {SessionType.WHYLABS_ANONYMOUS.name}", ignore_suppress=True) id_text = "" if not anonymous_session_id else anonymous_session_id - il.option(f"session id: {id_text}") + il.option(f"session id: {id_text}", ignore_suppress=True) def _notify_type_local(self) -> None: il.success( f"Using session type: {SessionType.LOCAL.name}. " - "Profiles won't be uploaded or written anywhere automatically." + "Profiles won't be uploaded or written anywhere automatically.", + ignore_suppress=True, ) def _determine_session_type_prompt(self, init_config: InitConfig) -> SessionType: diff --git a/python/whylogs/api/whylabs/session/prompts.py b/python/whylogs/api/whylabs/session/prompts.py index ef04b5ebd4..e0a8e6bed7 100644 --- a/python/whylogs/api/whylabs/session/prompts.py +++ b/python/whylogs/api/whylabs/session/prompts.py @@ -11,14 +11,14 @@ def _get_user_choice(prompt: str, options: List[str]) -> int: - il.question(prompt) + il.question(prompt, ignore_suppress=True) for i, option in enumerate(options, 1): - il.option(f"{i}. {option}") + il.option(f"{i}. {option}", ignore_suppress=True) while True: try: sys.stdout.flush() - il.message() + il.message(ignore_suppress=True) choice = int(input("Enter a number from the list: ")) if 1 <= choice <= len(options): return choice @@ -35,6 +35,9 @@ def prompt_session_type(allow_anonymous: bool = True, allow_local: bool = False) if allow_local: options.append("Local. Don't upload data anywhere.") + if len(options) == 1: + return SessionType.WHYLABS + choice = _get_user_choice("What kind of session do you want to use?", options) return [SessionType.WHYLABS, SessionType.WHYLABS_ANONYMOUS, SessionType.LOCAL][choice - 1] @@ -42,7 +45,7 @@ def prompt_session_type(allow_anonymous: bool = True, allow_local: bool = False) def prompt_default_dataset_id() -> Optional[str]: try: sys.stdout.flush() - il.message() + il.message(ignore_suppress=True) default_dataset_id = input("[OPTIONAL] Enter a default dataset id to upload to: ").strip() return default_dataset_id except Exception: @@ -53,14 +56,15 @@ def prompt_api_key() -> ApiKey: while True: try: sys.stdout.flush() - il.message() + il.message(ignore_suppress=True) api_key = input( "Enter your WhyLabs api key. You can find it at https://hub.whylabsapp.com/settings/access-tokens: " ) return parse_api_key(api_key) except Exception: il.warning( - f"Couldn't parse the api key. Expected a key with the format 'key_id.key:org_id'. Got: {api_key}" + f"Couldn't parse the api key. Expected a key with the format 'key_id.key:org_id'. Got: {api_key}", + ignore_suppress=True, ) @@ -68,9 +72,12 @@ def prompt_org_id() -> str: while True: try: sys.stdout.flush() - il.message() + il.message(ignore_suppress=True) org_id = input("Enter your org id. You can find it at https://hub.whylabsapp.com/settings/access-tokens: ") validate_org_id(org_id) return org_id except Exception: - il.warning(f"Couldn't parse the org id. Expected an id that starts with 'org-'. Got: {org_id}") + il.warning( + f"Couldn't parse the org id. Expected an id that starts with 'org-'. Got: {org_id}", + ignore_suppress=True, + ) diff --git a/python/whylogs/api/whylabs/session/session_types.py b/python/whylogs/api/whylabs/session/session_types.py index 1ec96a0ae0..06e6b0a360 100644 --- a/python/whylogs/api/whylabs/session/session_types.py +++ b/python/whylogs/api/whylabs/session/session_types.py @@ -1,4 +1,5 @@ # Various common types to avoid circular dependencies +import os from dataclasses import dataclass from enum import Enum from typing import Callable, Optional, Set, Union @@ -27,22 +28,38 @@ def init_notebook_logging() -> None: InteractiveLogger._is_notebook = True @staticmethod - def message(message: str = "", log_fn: Optional[Callable] = None) -> None: + def __should_log(ignore_suppress: bool = False) -> bool: + """ + Returns true if we should log, false otherwise. + """ + if ignore_suppress: + return InteractiveLogger._is_notebook + else: + return not os.environ.get("WHYLOGS_SUPPRESS_LOG_OUTPUT") and InteractiveLogger._is_notebook + + @staticmethod + def message(message: str = "", log_fn: Optional[Callable] = None, ignore_suppress: bool = False) -> None: """ Log a message only if we're in a notebook environment. + + Args: + message: The message to log + log_fn: A function to log to instead of printing if we're not in a notebook. + ignore_suppress: If true, will log even if WHYLOGS_SUPPRESS_LOG_OUTPUT is set. It still needs + to be in a notebook though or it won't show. """ - if InteractiveLogger._is_notebook: + if InteractiveLogger.__should_log(ignore_suppress=ignore_suppress): print(message) elif log_fn is not None: log_fn(message) @staticmethod - def option(message: str) -> None: + def option(message: str, ignore_suppress: bool = False) -> None: """ Log an option line, which is anything that has multiple related lines in a row like multiple choices or a list things. """ - InteractiveLogger.message(f" โคท {message}") + InteractiveLogger.message(f" โคท {message}", ignore_suppress=ignore_suppress) @staticmethod def inspect(message: str) -> None: @@ -52,39 +69,39 @@ def inspect(message: str) -> None: InteractiveLogger.message(f"๐Ÿ” {message}") @staticmethod - def question(message: str) -> None: + def question(message: str, ignore_suppress: bool = False) -> None: """ Log a question. """ - InteractiveLogger.message(f"โ“ {message}") + InteractiveLogger.message(f"โ“ {message}", ignore_suppress=ignore_suppress) @staticmethod - def success(message: str) -> None: + def success(message: str, ignore_suppress: bool = False) -> None: """ Log a success line, which has a green checkmark. """ - InteractiveLogger.message(f"โœ… {message}") + InteractiveLogger.message(f"โœ… {message}", ignore_suppress=ignore_suppress) @staticmethod - def failure(message: str) -> None: + def failure(message: str, ignore_suppress: bool = False) -> None: """ Log a failure, which has a red x. """ - InteractiveLogger.message(f"โŒ {message}") + InteractiveLogger.message(f"โŒ {message}", ignore_suppress=ignore_suppress) @staticmethod - def warning(message: str, log_fn: Optional[Callable] = None) -> None: + def warning(message: str, log_fn: Optional[Callable] = None, ignore_suppress: bool = False) -> None: """ Log a warning, which has a warning sign. """ - InteractiveLogger.message(f"โš ๏ธ {message}", log_fn=log_fn) + InteractiveLogger.message(f"โš ๏ธ {message}", log_fn=log_fn, ignore_suppress=ignore_suppress) @staticmethod - def warning_once(message: str, log_fn: Optional[Callable] = None) -> None: + def warning_once(message: str, log_fn: Optional[Callable] = None, ignore_suppress: bool = False) -> None: """ Like warning, but only logs once. """ - if not InteractiveLogger._is_notebook: + if not InteractiveLogger.__should_log(ignore_suppress=ignore_suppress): return if hash(message) not in InteractiveLogger.__warnings: diff --git a/python/whylogs/extras/image_metric.py b/python/whylogs/extras/image_metric.py index da881a6fb6..8dfceed174 100644 --- a/python/whylogs/extras/image_metric.py +++ b/python/whylogs/extras/image_metric.py @@ -29,10 +29,10 @@ logger = logging.getLogger(__name__) try: - from PIL.Image import Image as ImageType - from PIL.ImageStat import Stat - from PIL.TiffImagePlugin import IFDRational - from PIL.TiffTags import TAGS + from PIL.Image import Image as ImageType # type: ignore + from PIL.ImageStat import Stat # type: ignore + from PIL.TiffImagePlugin import IFDRational # type: ignore + from PIL.TiffTags import TAGS # type: ignore except ImportError as e: ImageType = None # type: ignore logger.warning(str(e)) @@ -100,7 +100,7 @@ def get_pil_exif_metadata(img: ImageType) -> Dict: return metadata -def image_based_metadata(img): +def image_based_metadata(img: ImageType) -> Dict[str, int]: return { "ImagePixelWidth": img.width, "ImagePixelHeight": img.height, @@ -248,6 +248,7 @@ def log_image( images: Union[ImageType, List[ImageType], Dict[str, ImageType]], default_column_prefix: str = "image", schema: Optional[DatasetSchema] = None, + trace_id: Optional[str] = None, ) -> ResultSet: if isinstance(images, ImageType): images = {default_column_prefix: images} @@ -275,7 +276,7 @@ def resolve(self, name: str, why_type: DataType, column_schema: ColumnSchema) -> if not isinstance(schema.default_configs, ImageMetricConfig): raise ValueError("log_image requires DatasetSchema with an ImageMetricConfig as default_configs") - return why.log(row=images, schema=schema) + return why.log(row=images, schema=schema, trace_id=trace_id) # Register it so Multimetric and ProfileView can deserialize