diff --git a/docs/book/.gitbook/assets/pigeon.png b/docs/book/.gitbook/assets/pigeon.png new file mode 100644 index 00000000000..eea58d0228f Binary files /dev/null and b/docs/book/.gitbook/assets/pigeon.png differ diff --git a/docs/book/stacks-and-components/component-guide/annotators/annotators.md b/docs/book/stacks-and-components/component-guide/annotators/annotators.md index 4dac1bc4450..51e929b8a97 100644 --- a/docs/book/stacks-and-components/component-guide/annotators/annotators.md +++ b/docs/book/stacks-and-components/component-guide/annotators/annotators.md @@ -55,11 +55,12 @@ The core parts of the annotation workflow include: ### List of available annotators For production use cases, some more flavors can be found in specific `integrations` modules. In terms of annotators, -ZenML features an integration with `label_studio`. +ZenML features integrations with `label_studio` and `pigeon`. | Annotator | Flavor | Integration | Notes | |-----------------------------------------|----------------|----------------|----------------------------------------------------------------------| | [LabelStudioAnnotator](label-studio.md) | `label_studio` | `label_studio` | Connect ZenML with Label Studio | +| [PigeonAnnotator](pigeon.md) | `pigeon` | `pigeon` | Connect ZenML with Pigeon. Notebook only & for image and text classification tasks. | | [ProdigyAnnotator](prodigy.md) | `prodigy` | `prodigy` | Connect ZenML with [Prodigy](https://prodi.gy/) | | [Custom Implementation](custom.md) | _custom_ | | Extend the annotator abstraction and provide your own implementation | @@ -71,9 +72,11 @@ zenml annotator flavor list ### How to use it -The available implementation of the annotator is built on top of the Label Studio integration, which means that using an -annotator currently is no different from what's described on -the [Label Studio page: How to use it?](label-studio.md#how-do-you-use-it). +The available implementation of the annotator is built on top of the Label +Studio integration, which means that using an annotator currently is no +different from what's described on the [Label Studio page: How to use +it?](label-studio.md#how-do-you-use-it). ([Pigeon](pigeon.md) is also supported, but has a +very limited functionality and only works within Jupyter notebooks.) ### A note on names diff --git a/docs/book/stacks-and-components/component-guide/annotators/pigeon.md b/docs/book/stacks-and-components/component-guide/annotators/pigeon.md new file mode 100644 index 00000000000..4bd615949c8 --- /dev/null +++ b/docs/book/stacks-and-components/component-guide/annotators/pigeon.md @@ -0,0 +1,113 @@ +--- +description: Annotating data using Pigeon. +--- + +# Pigeon + +Pigeon is a lightweight, open-source annotation tool designed for quick and easy labeling of data directly within Jupyter notebooks. It provides a simple and intuitive interface for annotating various types of data, including: + +* Text Classification +* Image Classification +* Text Captioning + +### When would you want to use it? + +![Pigeon annotator interface](../../../.gitbook/assets/pigeon.png) + +If you need to label a small to medium-sized dataset as part of your ML workflow and prefer the convenience of doing it directly within your Jupyter notebook, Pigeon is a great choice. It is particularly useful for: + +* Quick labeling tasks that don't require a full-fledged annotation platform +* Iterative labeling during the exploratory phase of your ML project +* Collaborative labeling within a Jupyter notebook environment + +### How to deploy it? + +To use the Pigeon annotator, you first need to install the ZenML Pigeon integration: + +```shell +zenml integration install pigeon +``` + +Next, register the Pigeon annotator with ZenML, specifying the output directory where the annotation files will be stored: + +```shell +zenml annotator register pigeon --flavor pigeon --output_dir="path/to/dir" +``` + +Note that the `output_dir` is relative to the repository or notebook root. + +Finally, add the Pigeon annotator to your stack and set it as the active stack: + +```shell +zenml stack update --annotator pigeon +``` + +Now you're ready to use the Pigeon annotator in your ML workflow! + +### How do you use it? + +With the Pigeon annotator registered and added to your active stack, you can easily access it using the ZenML client within your Jupyter notebook. + +For text classification tasks, you can launch the Pigeon annotator as follows: + +````python +from zenml.client import Client + +annotator = Client().active_stack.annotator + +annotations = annotator.launch( + data=[ + 'I love this movie', + 'I was really disappointed by the book' + ], + options=[ + 'positive', + 'negative' + ] +) +```` + +For image classification tasks, you can provide a custom display function to render the images: + +````python +from zenml.client import Client +from IPython.display import display, Image + +annotator = Client().active_stack.annotator + +annotations = annotator.launch( + data=[ + '/path/to/image1.png', + '/path/to/image2.png' + ], + options=[ + 'cat', + 'dog' + ], + display_fn=lambda filename: display(Image(filename)) +) +```` + +The `launch` method returns the annotations as a list of tuples, where each tuple contains the data item and its corresponding label. + +You can also use the `zenml annotator dataset` commands to manage your datasets: + +* `zenml annotator dataset list` - List all available datasets +* `zenml annotator dataset delete ` - Delete a specific dataset +* `zenml annotator dataset stats ` - Get statistics for a specific dataset + +Annotation files are saved as JSON files in the specified output directory. Each +annotation file represents a dataset, with the filename serving as the dataset +name. + +## Acknowledgements + +Pigeon was created by [Anastasis Germanidis](https://github.com/agermanidis) and +released as a [Python package](https://pypi.org/project/pigeon-jupyter/) and +[Github repository](https://github.com/agermanidis/pigeon). It is licensed under +the Apache License. It has been updated to work with more recent `ipywidgets` +versions and some small UI improvements were added. We are grateful to Anastasis +for creating this tool and making it available to the community. + + +
ZenML Scarf
diff --git a/docs/book/toc.md b/docs/book/toc.md index ecf58e3415f..fc244090d61 100644 --- a/docs/book/toc.md +++ b/docs/book/toc.md @@ -164,6 +164,7 @@ * [Develop a Custom Feature Store](stacks-and-components/component-guide/feature-stores/custom.md) * [Annotators](stacks-and-components/component-guide/annotators/annotators.md) * [Label Studio](stacks-and-components/component-guide/annotators/label-studio.md) + * [Pigeon](stacks-and-components/component-guide/annotators/pigeon.md) * [Prodigy](stacks-and-components/component-guide/annotators/prodigy.md) * [Develop a Custom Annotator](stacks-and-components/component-guide/annotators/custom.md) * [Image Builders](stacks-and-components/component-guide/image-builders/image-builders.md) diff --git a/scripts/install-zenml-dev.sh b/scripts/install-zenml-dev.sh index 77210841e7f..d8970cc723d 100755 --- a/scripts/install-zenml-dev.sh +++ b/scripts/install-zenml-dev.sh @@ -32,7 +32,8 @@ install_integrations() { # figure out the python version python_version=$(python -c "import sys; print('.'.join(map(str, sys.version_info[:2])))") - ignore_integrations="feast label_studio bentoml seldon pycaret skypilot_aws skypilot_gcp skypilot_azure prodigy" + ignore_integrations="feast label_studio bentoml seldon pycaret skypilot_aws skypilot_gcp skypilot_azure pigeon prodigy" + # if python version is 3.11, exclude all integrations depending on kfp # because they are not yet compatible with python 3.11 if [ "$python_version" = "3.11" ]; then diff --git a/src/zenml/integrations/__init__.py b/src/zenml/integrations/__init__.py index cdf4fc36dc8..492f90abba7 100644 --- a/src/zenml/integrations/__init__.py +++ b/src/zenml/integrations/__init__.py @@ -17,8 +17,6 @@ support. This includes orchestrators like Apache Airflow, visualization tools like the ``facets`` library, as well as deep learning libraries like PyTorch. """ -import sys - from zenml.integrations.airflow import AirflowIntegration # noqa from zenml.integrations.aws import AWSIntegration # noqa from zenml.integrations.azure import AzureIntegration # noqa @@ -49,8 +47,9 @@ from zenml.integrations.neptune import NeptuneIntegration # noqa from zenml.integrations.neural_prophet import NeuralProphetIntegration # noqa from zenml.integrations.openai import OpenAIIntegration # noqa +from zenml.integrations.pigeon import PigeonIntegration # noqa from zenml.integrations.pillow import PillowIntegration # noqa -from zenml.integrations.polars import PolarsIntegration +from zenml.integrations.polars import PolarsIntegration # noqa from zenml.integrations.prodigy import ProdigyIntegration # noqa from zenml.integrations.pycaret import PyCaretIntegration # noqa from zenml.integrations.pytorch import PytorchIntegration # noqa diff --git a/src/zenml/integrations/constants.py b/src/zenml/integrations/constants.py index 288f2f6c60e..9d2b08634d5 100644 --- a/src/zenml/integrations/constants.py +++ b/src/zenml/integrations/constants.py @@ -44,6 +44,7 @@ NEPTUNE = "neptune" NEURAL_PROPHET = "neural_prophet" OPEN_AI = "openai" +PIGEON = "pigeon" PILLOW = "pillow" PLOTLY = "plotly" POLARS = "polars" diff --git a/src/zenml/integrations/pigeon/__init__.py b/src/zenml/integrations/pigeon/__init__.py new file mode 100644 index 00000000000..070eaa75225 --- /dev/null +++ b/src/zenml/integrations/pigeon/__init__.py @@ -0,0 +1,44 @@ +# Copyright (c) ZenML GmbH 2024. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. +"""Initialization of the Pigeon integration.""" +from typing import List, Type + +from zenml.integrations.constants import PIGEON +from zenml.integrations.integration import Integration +from zenml.stack import Flavor + +PIGEON_ANNOTATOR_FLAVOR = "pigeon" + + +class PigeonIntegration(Integration): + """Definition of Pigeon integration for ZenML.""" + + NAME = PIGEON + REQUIREMENTS = ["ipywidgets>=8.0.0"] + + @classmethod + def flavors(cls) -> List[Type[Flavor]]: + """Declare the stack component flavors for the Pigeon integration. + + Returns: + List of stack component flavors for this integration. + """ + from zenml.integrations.pigeon.flavors import ( + PigeonAnnotatorFlavor, + ) + + return [PigeonAnnotatorFlavor] + + +PigeonIntegration.check_installation() diff --git a/src/zenml/integrations/pigeon/annotators/__init__.py b/src/zenml/integrations/pigeon/annotators/__init__.py new file mode 100644 index 00000000000..bbfd9e96e27 --- /dev/null +++ b/src/zenml/integrations/pigeon/annotators/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) ZenML GmbH 2024. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. +"""Initialization of the Pigeon annotators submodule.""" + +from zenml.integrations.pigeon.annotators.pigeon_annotator import ( + PigeonAnnotator, +) + +__all__ = ["PigeonAnnotator"] diff --git a/src/zenml/integrations/pigeon/annotators/pigeon_annotator.py b/src/zenml/integrations/pigeon/annotators/pigeon_annotator.py new file mode 100644 index 00000000000..f24343300d4 --- /dev/null +++ b/src/zenml/integrations/pigeon/annotators/pigeon_annotator.py @@ -0,0 +1,330 @@ +# Copyright (c) ZenML GmbH 2024. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. +"""Pigeon annotator. + +Credit for the implementation of this code to @agermanidis in the +Pigeon package and library. This code has been slightly modified to +fit the ZenML framework. We use the modified code directly here because +the original package (and code) is no longer compatible with more recent +versions of ipywidgets. + +https://github.com/agermanidis/pigeon +""" + +import json +import os +from datetime import datetime +from typing import Any, List, Optional, Tuple, cast + +import ipywidgets as widgets # type: ignore +from IPython.core.display_functions import clear_output, display + +from zenml.annotators.base_annotator import BaseAnnotator +from zenml.integrations.pigeon.flavors.pigeon_annotator_flavor import ( + PigeonAnnotatorConfig, +) +from zenml.logger import get_logger + +logger = get_logger(__name__) + + +class PigeonAnnotator(BaseAnnotator): + """Annotator for using Pigeon in Jupyter notebooks.""" + + @property + def config(self) -> PigeonAnnotatorConfig: + """Get the Pigeon annotator config. + + Returns: + The Pigeon annotator config. + """ + return cast(PigeonAnnotatorConfig, self._config) + + def get_url(self) -> str: + """Get the URL of the Pigeon annotator. + + Raises: + NotImplementedError: Pigeon annotator does not have a URL. + """ + raise NotImplementedError("Pigeon annotator does not have a URL.") + + def get_url_for_dataset(self, dataset_name: str) -> str: + """Get the URL of the Pigeon annotator for a specific dataset. + + Args: + dataset_name: Name of the dataset (annotation file). + + Raises: + NotImplementedError: Pigeon annotator does not have a URL. + """ + raise NotImplementedError("Pigeon annotator does not have a URL.") + + def get_datasets(self) -> List[str]: + """Get a list of datasets (annotation files) in the output directory. + + Returns: + A list of dataset names (annotation file names) (or empty list when no datasets are present). + """ + output_dir = self.config.output_dir + try: + return [f for f in os.listdir(output_dir) if f.endswith(".txt")] + except FileNotFoundError: + return [] + + def get_dataset_names(self) -> List[str]: + """List dataset names (annotation file names) in the output directory. + + Returns: + A list of dataset names (annotation file names). + """ + return self.get_datasets() + + def get_dataset_stats(self, dataset_name: str) -> Tuple[int, int]: + """List labeled and unlabeled examples in a dataset (annotation file). + + Args: + dataset_name: Name of the dataset (annotation file). + + Returns: + A tuple containing (num_labeled_examples, num_unlabeled_examples). + """ + dataset_path = os.path.join(self.config.output_dir, dataset_name) + num_labeled_examples = 0 + # Placeholder as logic to determine this is not implemented + num_unlabeled_examples = 0 + + try: + with open(dataset_path, "r") as file: + num_labeled_examples = sum(1 for _ in file) + except FileNotFoundError: + logger.error(f"File not found: {dataset_path}") + + return num_labeled_examples, num_unlabeled_examples + + def _annotate( + self, + data: List[Any], + options: List[str], + display_fn: Optional[Any] = None, + ) -> List[Tuple[Any, Any]]: + """Internal method to build an interactive widget for annotating. + + Args: + data: List of examples to annotate. + options: List of labels to choose from. + display_fn: Optional function to display examples. + + Returns: + A list of tuples containing (example, label) for each annotated example. + """ + examples = list(data) + annotations = [] + current_index = 0 + out = widgets.Output() + + def show_next() -> None: + nonlocal current_index + if current_index >= len(examples): + with out: + clear_output(wait=True) + logger.info("Annotation done.") + return + with out: + clear_output(wait=True) + if display_fn: + display_fn(examples[current_index]) + else: + display(examples[current_index]) + + def add_annotation(btn: widgets.Button) -> None: + """Add an annotation to the list of annotations. + + Args: + btn: The button that triggered the event. + """ + nonlocal current_index + annotation = btn.description + annotations.append((examples[current_index], annotation)) + current_index += 1 + show_next() + + def submit_annotations(btn: widgets.Button) -> None: + """Submit all annotations and save them to a file. + + Args: + btn: The button that triggered the event. + """ + self._save_annotations(annotations) + with out: + clear_output(wait=True) + logger.info("Annotations saved.") + + count_label = widgets.Label() + display(count_label) + + buttons = [] + for label in options: + btn = widgets.Button(description=label) + btn.on_click(add_annotation) + buttons.append(btn) + + submit_btn = widgets.Button( + description="Save labels", button_style="success" + ) + submit_btn.on_click(submit_annotations) + buttons.append(submit_btn) + + navigation_box = widgets.HBox(buttons) + display(navigation_box) + display(out) + show_next() + + return annotations + + def launch(self, **kwargs: Any) -> None: + """Launch the Pigeon annotator in the Jupyter notebook. + + Args: + **kwargs: Additional keyword arguments to pass to the annotation client. + + Raises: + NotImplementedError: Pigeon annotator does not support launching with a URL. + """ + raise NotImplementedError( + "Pigeon annotator does not support launching with a URL." + ) + + def annotate( + self, + data: List[Any], + options: List[str], + display_fn: Optional[Any] = None, + ) -> List[Tuple[Any, Any]]: + """Annotate with the Pigeon annotator in the Jupyter notebook. + + Args: + data: List of examples to annotate. + options: List of labels to choose from. + display_fn: Optional function to display examples. + + Returns: + A list of tuples containing (example, label) for each annotated example. + """ + annotations = self._annotate(data, options, display_fn) + return annotations + + def _save_annotations(self, annotations: List[Tuple[Any, Any]]) -> None: + """Save annotations to a file with a unique date-time suffix. + + Args: + annotations: List of tuples containing (example, label) for each annotated example. + """ + output_dir = self.config.output_dir + os.makedirs(output_dir, exist_ok=True) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + output_file = os.path.join(output_dir, f"annotations_{timestamp}.json") + with open(output_file, "w") as f: + json.dump(annotations, f) + + def add_dataset(self, **kwargs: Any) -> Any: + """Add a dataset (annotation file) to the Pigeon annotator. + + Args: + **kwargs: keyword arguments. + + Raises: + NotImplementedError: Pigeon annotator does not support adding datasets. + """ + raise NotImplementedError( + "Pigeon annotator does not support adding datasets." + ) + + def delete_dataset(self, **kwargs: Any) -> None: + """Delete a dataset (annotation file). + + Takes the `dataset_name` argument from the kwargs. + + Args: + **kwargs: Keyword arguments containing the `dataset_name` to delete. + + Raises: + ValueError: Dataset name is required to delete a dataset. + """ + dataset_name = kwargs.get("dataset_name") + if not dataset_name: + raise ValueError( + "Dataset name (`dataset_name`) is required to delete a dataset." + ) + dataset_path = os.path.join(self.config.output_dir, dataset_name) + os.remove(dataset_path) + + def get_dataset(self, **kwargs: Any) -> List[Tuple[Any, Any]]: + """Get the annotated examples from a dataset (annotation file). + + Takes the `dataset_name` argument from the kwargs. + + Args: + **kwargs: Keyword arguments containing the `dataset_name` to retrieve. + + Returns: + A list of tuples containing (example, label) for each annotated + example. + + Raises: + ValueError: Dataset name is required to retrieve a dataset. + """ + dataset_name = kwargs.get("dataset_name") + if not dataset_name: + raise ValueError( + "Dataset name (`dataset_name`) is required to retrieve a dataset." + ) + dataset_path = os.path.join(self.config.output_dir, dataset_name) + with open(dataset_path, "r") as f: + annotations = json.load(f) + return cast(List[Tuple[Any, Any]], annotations) + + def get_labeled_data(self, **kwargs: Any) -> List[Tuple[Any, Any]]: + """Get the labeled examples from a dataset (annotation file). + + Takes the `dataset_name` argument from the kwargs. + + Args: + **kwargs: Keyword arguments containing the `dataset_name` to retrieve. + + Returns: + A list of tuples containing (example, label) for each labeled + example. + + Raises: + ValueError: Dataset name is required to retrieve labeled data. + """ + if dataset_name := kwargs.get("dataset_name"): + return self.get_dataset(dataset_name=dataset_name) + else: + raise ValueError( + "Dataset name (`dataset_name`) is required to retrieve labeled data." + ) + + def get_unlabeled_data(self, **kwargs: Any) -> Any: + """Get the unlabeled examples from a dataset (annotation file). + + Args: + **kwargs: keyword arguments. + + Raises: + NotImplementedError: Pigeon annotator does not support retrieving unlabeled data. + """ + raise NotImplementedError( + "Pigeon annotator does not support retrieving unlabeled data." + ) diff --git a/src/zenml/integrations/pigeon/flavors/__init__.py b/src/zenml/integrations/pigeon/flavors/__init__.py new file mode 100644 index 00000000000..bbb746c9c9e --- /dev/null +++ b/src/zenml/integrations/pigeon/flavors/__init__.py @@ -0,0 +1,24 @@ +# Copyright (c) ZenML GmbH 2024. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. +"""Pigeon integration flavors.""" + +from zenml.integrations.pigeon.flavors.pigeon_annotator_flavor import ( + PigeonAnnotatorConfig, + PigeonAnnotatorFlavor, +) + +__all__ = [ + "PigeonAnnotatorFlavor", + "PigeonAnnotatorConfig", +] diff --git a/src/zenml/integrations/pigeon/flavors/pigeon_annotator_flavor.py b/src/zenml/integrations/pigeon/flavors/pigeon_annotator_flavor.py new file mode 100644 index 00000000000..43e0f1c6f87 --- /dev/null +++ b/src/zenml/integrations/pigeon/flavors/pigeon_annotator_flavor.py @@ -0,0 +1,104 @@ +# Copyright (c) ZenML GmbH 2024. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. +"""Pigeon annotator flavor.""" + +from typing import TYPE_CHECKING, Optional, Type + +from zenml.annotators.base_annotator import ( + BaseAnnotatorConfig, + BaseAnnotatorFlavor, +) +from zenml.config.base_settings import BaseSettings +from zenml.integrations.pigeon import PIGEON_ANNOTATOR_FLAVOR +from zenml.stack.authentication_mixin import AuthenticationConfigMixin + +if TYPE_CHECKING: + from zenml.integrations.pigeon.annotators import PigeonAnnotator + + +class PigeonAnnotatorSettings(BaseSettings): + """Settings for the Pigeon annotator.""" + + +class PigeonAnnotatorConfig( # type: ignore[misc] # https://github.com/pydantic/pydantic/issues/4173 + BaseAnnotatorConfig, PigeonAnnotatorSettings, AuthenticationConfigMixin +): + """Config for the Pigeon annotator. + + Attributes: + output_dir: The directory to store the annotations. + notebook_only: Whether the annotator only works within a notebook. + """ + + output_dir: str = "annotations" + + +class PigeonAnnotatorFlavor(BaseAnnotatorFlavor): + """Pigeon annotator flavor.""" + + @property + def name(self) -> str: + """Name of the flavor. + + Returns: + The name of the flavor. + """ + return PIGEON_ANNOTATOR_FLAVOR + + @property + def docs_url(self) -> Optional[str]: + """A url to point at docs explaining this flavor. + + Returns: + A flavor docs url. + """ + return self.generate_default_docs_url() + + @property + def sdk_docs_url(self) -> Optional[str]: + """A url to point at SDK docs explaining this flavor. + + Returns: + A flavor SDK docs url. + """ + return self.generate_default_sdk_docs_url() + + @property + def logo_url(self) -> str: + """A url to represent the flavor in the dashboard. + + Returns: + The flavor logo. + """ + return "https://public-flavor-logos.s3.eu-central-1.amazonaws.com/annotator/pigeon.png" + + @property + def config_class(self) -> Type[PigeonAnnotatorConfig]: + """Returns `PigeonAnnotatorConfig` config class. + + Returns: + The config class. + """ + return PigeonAnnotatorConfig + + @property + def implementation_class(self) -> Type["PigeonAnnotator"]: + """Implementation class for this flavor. + + Returns: + The implementation class. + """ + from zenml.integrations.pigeon.annotators import PigeonAnnotator + + return PigeonAnnotator