<!-- # hard line break macro for HTML -->

<a id="sample-parsers"></a>

# Using Sample Parsers

This page describes how to use the [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface to add samples to
your FiftyOne dataset from a stream of in-memory data.

The [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface provides native support for loading samples in a
variety of [common formats](#builtin-sample-parser), and it can be easily
extended to import datasets in [custom formats](#custom-sample-parser),
allowing you to automate the dataset loading process.

#### WARNING
The [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface is only recommended for specific use cases.

In most cases, you’ll likely prefer
[adding samples manually](import_datasets.md#loading-custom-datasets) or
[using dataset importers](import_datasets.md#importing-datasets) to load data into
FiftyOne.

<a id="adding-samples-to-datasets"></a>

## Adding samples to datasets

### Basic recipe

The basic recipe for using the [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface to add samples to a
[`Dataset`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset) is to create a parser of the appropriate type and then pass the
parser along with an iterable of samples to the appropriate [`Dataset`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset) method.

Unlabeled images

Labeled images

Unlabeled videos

Labeled videos

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of samples and an UnlabeledImageSampleParser to parse them
samples = ...
sample_parser = foud.ImageSampleParser  # for example

# Add the image samples to the dataset
dataset.add_images(samples, sample_parser)
```

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of samples and a LabeledImageSampleParser to parse them
samples = ...
sample_parser = foud.ImageClassificationSampleParser  # for example

# Add the labeled image samples to the dataset
dataset.add_labeled_images(samples, sample_parser)
```

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of samples and an UnlabeledVideoSampleParser to parse them
samples = ...
sample_parser = foud.VideoSampleParser  # for example

# Add the video samples to the dataset
dataset.add_images(samples, sample_parser)
```

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of samples and a LabeledVideoSampleParser to parse them
samples = ...
sample_parser = foud.FiftyOneVideoLabelsSampleParser  # for example

# Add the labeled video samples to the dataset
dataset.add_labeled_videos(samples, sample_parser)
```

#### NOTE
A typical use case is that `samples` in the above recipe is a
`torch.utils.data.Dataset` or an iterable generated by
`tf.data.Dataset.as_numpy_iterator()`.

### Adding unlabeled images

FiftyOne provides a few convenient ways to add unlabeled images in FiftyOne
datasets.

#### Adding a directory of images

Use [`Dataset.add_images_dir()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_images_dir)
to add a directory of images to a dataset:

```python
import fiftyone as fo

dataset = fo.Dataset()

# A directory of images to add
images_dir = "/path/to/images"

# Add images to the dataset
dataset.add_images_dir(images_dir)
```

#### Adding a glob pattern of images

Use [`Dataset.add_images_patt()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_images_patt)
to add a glob pattern of images to a dataset:

```python
import fiftyone as fo

dataset = fo.Dataset()

# A glob pattern of images to add
images_patt = "/path/to/images/*.jpg"

# Add images to the dataset
dataset.add_images_patt(images_patt)
```

#### Adding images using a SampleParser

Use [`Dataset.add_images()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_images)
to add an iterable of unlabeled images that can be parsed via a specified
[`UnlabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser) to a dataset.

**Example**

FiftyOne provides an
[`ImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.ImageSampleParser)
that handles samples that contain either an image that can be converted to
[numpy format](https://numpy.org) via `np.asarray()` of the path to an
image on disk.

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of images or image paths and the UnlabeledImageSampleParser
# to use to parse them
samples = ...
sample_parser = foud.ImageSampleParser

# Add images to the dataset
dataset.add_images(samples, sample_parser)
```

### Adding labeled images

Use [`Dataset.add_labeled_images()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_labeled_images)
to add an iterable of samples that can be parsed via a specified
[`LabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser) to a dataset.

**Example**

FiftyOne provides an
[`ImageClassificationSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.ImageClassificationSampleParser)
that handles samples that contain `(image_or_path, target)` tuples, where:

- `image_or_path` is either an image that can be converted to numpy
  format via `np.asarray()` or the path to an image on disk
- `target` is either a class ID or a label string

The snippet below adds an iterable of image classification data in the above
format to a dataset:

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of `(image_or_path, target)` tuples and the
# LabeledImageSampleParser to use to parse them
samples = ...
sample_parser = foud.ImageClassificationSampleParser

# Add labeled images to the dataset
dataset.add_labeled_images(samples, sample_parser)
```

### Adding unlabeled videos

FiftyOne provides a few convenient ways to add unlabeled videos in FiftyOne
datasets.

#### Adding a directory of videos

Use [`Dataset.add_videos_dir()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_videos_dir)
to add a directory of videos to a dataset:

```python
import fiftyone as fo

dataset = fo.Dataset()

# A directory of videos to add
videos_dir = "/path/to/videos"

# Add videos to the dataset
dataset.add_videos_dir(videos_dir)
```

#### Adding a glob pattern of videos

Use [`Dataset.add_videos_patt()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_videos_patt)
to add a glob pattern of videos to a dataset:

```python
import fiftyone as fo

dataset = fo.Dataset()

# A glob pattern of videos to add
videos_patt = "/path/to/videos/*.mp4"

# Add videos to the dataset
dataset.add_videos_patt(videos_patt)
```

#### Adding videos using a SampleParser

Use [`Dataset.add_videos()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_videos)
to add an iterable of unlabeled videos that can be parsed via a specified
[`UnlabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser) to a dataset.

**Example**

FiftyOne provides a
[`VideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.VideoSampleParser)
that handles samples that directly contain the path to the video on disk.

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of video paths and the UnlabeledVideoSampleParser to use to
# parse them
samples = ...
sample_parser = foud.VideoSampleParser

# Add videos to the dataset
dataset.add_videos(samples, sample_parser)
```

### Adding labeled videos

Use [`Dataset.add_labeled_videos()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_labeled_videos)
to add an iterable of samples that can be parsed via a specified
[`LabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser) to a dataset.

**Example**

FiftyOne provides a
[`VideoLabelsSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.VideoLabelsSampleParser)
that handles samples that contain `(video_path, video_labels_or_path)`
tuples, where:

- `video_path` is the path to a video on disk
- `video_labels_or_path` is an `eta.core.video.VideoLabels`
  instance, a serialized dict representation of one, or the path to one on disk

The snippet below adds an iterable of labeled video samples in the above format
to a dataset:

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of `(video_path, video_labels_or_path)` tuples and the
# LabeledVideoSampleParser to use to parse them
samples = ...
sample_parser = foud.VideoLabelsSampleParser

# Add labeled videos to the dataset
dataset.add_labeled_videos(samples, sample_parser)
```

<a id="ingesting-samples-into-datasets"></a>

## Ingesting samples into datasets

Creating FiftyOne datasets typically does not create copies of the source media,
since [`Sample`](../api/fiftyone.core.sample.md#fiftyone.core.sample.Sample) instances store the `filepath` to the media, not the media itself.

However, in certain circumstances, such as loading data from binary sources
like [TFRecords](https://www.tensorflow.org/tutorials/load_data/tfrecord)
or creating a FiftyOne dataset from unorganized and/or temporary files on disk,
it can be desirable to *ingest* the raw media for each sample into a common
backing location.

FiftyOne provides support for ingesting samples and their underlying source
media in both [common formats](#builtin-sample-parser) and can be extended
to import datasets in [custom formats](#custom-sample-parser).

### Basic recipe

The basic recipe for ingesting samples and their source media into a [`Dataset`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset)
is to create a [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) of the appropriate type of sample that you’re
loading and then pass the parser along with an iterable of samples to the
appropriate [`Dataset`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset) method.

Unlabeled images

Labeled images

Unlabeled videos

Labeled videos

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# The iterable of samples and the UnlabeledImageSampleParser to use
# to parse them
samples = ...
sample_parser = foud.ImageSampleParser  # for example

# A directory in which the images will be written; If `None`, a default directory
# based on the dataset's `name` will be used
dataset_dir = ...

# Ingest the labeled image samples into the dataset
# The source images are copied into `dataset_dir`
dataset.ingest_images(samples, sample_parser, dataset_dir=dataset_dir)
```

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# The iterable of samples and the LabeledImageSampleParser to use
# to parse them
samples = ...
sample_parser = foud.ImageClassificationSampleParser  # for example

# A directory in which the images will be written; If `None`, a default directory
# based on the dataset's `name` will be used
dataset_dir = ...

# Add the labeled image samples to the dataset
dataset.add_labeled_images(samples, sample_parser, dataset_dir=dataset_dir)
```

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# The iterable of samples and the UnlabeledVideoSampleParser to use
# to parse them
samples = ...
sample_parser = foud.VideoSampleParser  # for example

# A directory in which the videos will be written; If `None`, a default directory
# based on the dataset's `name` will be used
dataset_dir = ...

# Ingest the labeled video samples into the dataset
# The source videos are copied into `dataset_dir`
dataset.ingest_videos(samples, sample_parser, dataset_dir=dataset_dir)
```

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# The iterable of samples and the LabeledVideoSampleParser to use
# to parse them
samples = ...
sample_parser = foud.VideoLabelsSampleParser  # for example

# A directory in which the videos will be written; If `None`, a default directory
# based on the dataset's `name` will be used
dataset_dir = ...

# Add the labeled video samples to the dataset
dataset.add_labeled_videos(samples, sample_parser, dataset_dir=dataset_dir)
```

#### NOTE
A typical use case is that `samples` in the above recipe is a
`torch.utils.data.Dataset` or an iterable generated by
`tf.data.Dataset.as_numpy_iterator()`.

### Ingesting unlabeled images

Use [`Dataset.ingest_images()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.ingest_images)
to ingest an iterable of unlabeled images that can be parsed via a specified
[`UnlabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser) into a dataset.

The [`has_image_path`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser.has_image_path)
property of the parser may either be `True` or `False`. If the parser provides
image paths, the source images will be directly copied from their source
locations into the backing directory for the dataset; otherwise, the image will
be read in-memory via
[`get_image()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser.get_image)
and then written to the backing directory.

**Example**

FiftyOne provides an
[`ImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.ImageSampleParser)
that handles samples that contain either an image that can be converted to
[numpy format](https://numpy.org) via `np.asarray()` of the path to an
image on disk.

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of images or image paths and the UnlabeledImageSampleParser
# to use to parse them
samples = ...
sample_parser = foud.ImageSampleParser

# A directory in which the images will be written; If `None`, a default directory
# based on the dataset's `name` will be used
dataset_dir = ...

# Ingest the images into the dataset
# The source images are copied into `dataset_dir`
dataset.ingest_images(samples, sample_parser, dataset_dir=dataset_dir)
```

### Ingesting labeled images

Use [`Dataset.ingest_labeled_images()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.ingest_labeled_images)
to ingest an iterable of samples that can be parsed via a specified
[`LabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser) into a dataset.

The [`has_image_path`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.has_image_path)
property of the parser may either be `True` or `False`. If the parser provides
image paths, the source images will be directly copied from their source
locations into the backing directory for the dataset; otherwise, the image will
be read in-memory via
[`get_image()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.get_image)
and then written to the backing directory.

**Example**

FiftyOne provides an
[`ImageClassificationSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.ImageClassificationSampleParser)
that handles samples that contain `(image_or_path, target)` tuples, where:

- `image_or_path` is either an image that can be converted to numpy
  format via `np.asarray()` or the path to an image on disk
- `target` is either a class ID or a label string

The snippet below ingests an iterable of image classification data in the above
format intoa a FiftyOne dataset:

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of `(image_or_path, target)` tuples and the
# LabeledImageSampleParser to use to parse them
samples = ...
sample_parser = foud.ImageClassificationSampleParser  # for example

# A directory in which the images will be written; If `None`, a default directory
# based on the dataset's `name` will be used
dataset_dir = ...

# Ingest the labeled images into the dataset
# The source images are copied into `dataset_dir`
dataset.ingest_labeled_images(samples, sample_parser, dataset_dir=dataset_dir)
```

### Ingesting unlabeled videos

Use [`Dataset.ingest_videos()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.ingest_videos)
to ingest an iterable of unlabeled videos that can be parsed via a specified
[`UnlabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser) into a dataset.

The source videos will be directly copied from their source locations into the
backing directory for the dataset.

**Example**

FiftyOne provides a
[`VideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.VideoSampleParser)
that handles samples that directly contain the paths to videos on disk.

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of videos or video paths and the UnlabeledVideoSampleParser
# to use to parse them
samples = ...
sample_parser = foud.VideoSampleParser

# A directory in which the videos will be written; If `None`, a default directory
# based on the dataset's `name` will be used
dataset_dir = ...

# Ingest the videos into the dataset
# The source videos are copied into `dataset_dir`
dataset.ingest_videos(samples, sample_parser, dataset_dir=dataset_dir)
```

### Ingesting labeled videos

Use [`Dataset.ingest_labeled_videos()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.ingest_labeled_videos)
to ingest an iterable of samples that can be parsed via a specified
[`LabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser) into a dataset.

The source videos will be directly copied from their source locations into the
backing directory for the dataset.

**Example**

FiftyOne provides a
[`VideoLabelsSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.VideoLabelsSampleParser)
that handles samples that contain `(video_path, video_labels_or_path)`
tuples, where:

- `video_path` is the path to a video on disk
- `video_labels_or_path` is an `eta.core.video.VideoLabels` instance, a
  serialized dict representation of one, or the path to one on disk

The snippet below ingests an iterable of labeled videos in the above format
into a FiftyOne dataset:

```python
import fiftyone as fo
import fiftyone.utils.data as foud

dataset = fo.Dataset()

# An iterable of `(video_path, video_labels_or_path)` tuples and the
# LabeledVideoSampleParser to use to parse them
samples = ...
sample_parser = foud.VideoLabelsSampleParser  # for example

# A directory in which the videos will be written; If `None`, a default directory
# based on the dataset's `name` will be used
dataset_dir = ...

# Ingest the labeled videos into the dataset
# The source videos are copied into `dataset_dir`
dataset.ingest_labeled_videos(samples, sample_parser, dataset_dir=dataset_dir)
```

<a id="builtin-sample-parser"></a>

## Built-in SampleParser classes

The table below lists the common data formats for which FiftyOne provides
built-in [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) implementations. You can also write a
[custom SampleParser](#custom-sample-parser) to automate the parsing of
samples in your own custom data format.

You can use a [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) to
[add samples to datasets](#adding-samples-to-datasets) and
[ingest samples into datasets](#ingesting-samples-into-datasets).

<a id="custom-sample-parser"></a>

## Writing a custom SampleParser

FiftyOne provides a variety of
[built-in SampleParser classes](#builtin-sample-parser) to parse
data in common formats. However, if your samples are stored in a custom format,
you can provide a custom [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) class and provide it to FiftyOne when
[adding](#adding-samples-to-datasets) or
[ingesting](#ingesting-samples-into-datasets) samples into your datasets.

The [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface provides a mechanism for defining methods that
parse a data sample that is stored in a particular (external to FiftyOne)
format and return various elements of the sample in a format that FiftyOne
understands.

[`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) itself is an abstract interface; the concrete interface that you
should implement is determined by the type of samples that you are importing.
For example, [`LabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser) defines an interface for parsing
information from a labeled image sample, such as the path to the image on
disk, the image itself, metadata about the image, and the label (e.g.,
classification or object detections) associated with the image.

Unlabeled images

Labeled images

Unlabeled videos

Labeled videos

To define a custom parser for unlabeled images, implement the
[`UnlabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser) interface.

The pseudocode below provides a template for a custom
[`UnlabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser):

```python
import fiftyone.utils.data as foud

class CustomUnlabeledImageSampleParser(foud.UnlabeledImageSampleParser):
    """Custom parser for unlabeled image samples."""

    @property
    def has_image_path(self):
        """Whether this parser produces paths to images on disk for samples
        that it parses.
        """
        # Return True or False here
        pass

    @property
    def has_image_metadata(self):
        """Whether this parser produces
        :class:`fiftyone.core.metadata.ImageMetadata` instances for samples
        that it parses.
        """
        # Return True or False here
        pass

    def get_image(self):
        """Returns the image from the current sample.

        Returns:
            a numpy image
        """
        # Return the image in `self.current_sample` here
        pass

    def get_image_path(self):
        """Returns the image path for the current sample.

        Returns:
            the path to the image on disk
        """
        # Return the image path for `self.current_sample` here, or raise
        # an error if `has_image_path == False`
        pass

    def get_image_metadata(self):
        """Returns the image metadata for the current sample.

        Returns:
            a :class:`fiftyone.core.metadata.ImageMetadata` instance
        """
        # Return the image metadata for `self.current_sample` here, or
        # raise an error if `has_image_metadata == False`
        pass
```

When [`Dataset.add_images()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_images)
is called with a custom [`UnlabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser), the import is effectively
performed via the pseudocode below:

```python
import fiftyone as fo

dataset = fo.Dataset(...)
samples = ...
sample_parser = CustomUnlabeledImageSampleParser(...)

for sample in samples:
    sample_parser.with_sample(sample)

    image_path = sample_parser.get_image_path()

    if sample_parser.has_image_metadata:
        metadata = sample_parser.get_image_metadata()
    else:
        metadata = None

    sample = fo.Sample(filepath=image_path, metadata=metadata)

    dataset.add_sample(sample)
```

The base [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface provides a
[`with_sample()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser.with_sample)
method that ingests the next sample and makes it available via the
[`current_sample`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser.current_sample)
property of the parser. Subsequent calls to the parser’s `get_XXX()` methods
return information extracted from the current sample.

The [`UnlabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser) interface provides a
[`has_image_path`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser.has_image_path)
property that declares whether the sample parser can return the path to the
current sample’s image on disk via
[`get_image_path()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser.get_image_path).
Similarly, the
[`has_image_metadata`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser.has_image_metadata)
property that declares whether the sample parser can return an [`ImageMetadata`](../api/fiftyone.core.metadata.md#fiftyone.core.metadata.ImageMetadata)
for the current sample’s image via
[`get_image_metadata()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser.get_image_metadata).

By convention, all [`UnlabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser) implementations must make the
current sample’s image available via
[`get_image()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledImageSampleParser.get_image).

To define a custom parser for labeled images, implement the
[`LabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser) interface.

The pseudocode below provides a template for a custom
[`LabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser):

```python
import fiftyone.utils.data as foud

class CustomLabeledImageSampleParser(foud.LabeledImageSampleParser):
    """Custom parser for labeled image samples."""

    @property
    def has_image_path(self):
        """Whether this parser produces paths to images on disk for samples
        that it parses.
        """
        # Return True or False here
        pass

    @property
    def has_image_metadata(self):
        """Whether this parser produces
        :class:`fiftyone.core.metadata.ImageMetadata` instances for samples
        that it parses.
        """
        # Return True or False here
        pass

    @property
    def label_cls(self):
        """The :class:`fiftyone.core.labels.Label` class(es) returned by this
        parser.

        This can be any of the following:

        -   a :class:`fiftyone.core.labels.Label` class. In this case, the
            parser is guaranteed to return labels of this type
        -   a list or tuple of :class:`fiftyone.core.labels.Label` classes. In
            this case, the parser can produce a single label field of any of
            these types
        -   a dict mapping keys to :class:`fiftyone.core.labels.Label` classes.
            In this case, the parser will return label dictionaries with keys
            and value-types specified by this dictionary. Not all keys need be
            present in the imported labels
        -   ``None``. In this case, the parser makes no guarantees about the
            labels that it may return
        """
        # Return the appropriate value here
        pass

    def get_image(self):
        """Returns the image from the current sample.

        Returns:
            a numpy image
        """
        # Return the image in `self.current_sample` here
        pass

    def get_image_path(self):
        """Returns the image path for the current sample.

        Returns:
            the path to the image on disk
        """
        # Return the image path for `self.current_sample` here, or raise
        # an error if `has_image_path == False`
        pass

    def get_image_metadata(self):
        """Returns the image metadata for the current sample.

        Returns:
            a :class:`fiftyone.core.metadata.ImageMetadata` instance
        """
        # Return the image metadata for `self.current_sample` here, or
        # raise an error if `has_image_metadata == False`
        pass

    def get_label(self):
        """Returns the label for the current sample.

        Returns:
            a :class:`fiftyone.core.labels.Label` instance, or a dictionary
            mapping field names to :class:`fiftyone.core.labels.Label`
            instances, or ``None`` if the sample is unlabeled
        """
        # Return the label for `self.current_sample` here
        pass
```

When [`Dataset.add_labeled_images()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_labeled_images)
is called with a custom [`LabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser), the import is effectively
performed via the pseudocode below:

```python
import fiftyone as fo

dataset = fo.Dataset(...)

samples = ...
sample_parser = CustomLabeledImageSampleParser(...)
label_field = ...

if isinstance(label_field, dict):
    label_key = lambda k: label_field.get(k, k)
elif label_field is not None:
    label_key = lambda k: label_field + "_" + k
else:
    label_field = "ground_truth"
    label_key = lambda k: k

for sample in samples:
    sample_parser.with_sample(sample)

    image_path = sample_parser.get_image_path()

    if sample_parser.has_image_metadata:
        metadata = sample_parser.get_image_metadata()
    else:
        metadata = None

    label = sample_parser.get_label()

    sample = fo.Sample(filepath=image_path, metadata=metadata)

    if isinstance(label, dict):
        sample.update_fields({label_key(k): v for k, v in label.items()})
    elif label is not None:
        sample[label_field] = label

    dataset.add_sample(sample)
```

The base [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface provides a
[`with_sample()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser.with_sample)
method that ingests the next sample and makes it available via the
[`current_sample`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser.current_sample)
property of the parser. Subsequent calls to the parser’s `get_XXX()` methods
return information extracted from the current sample.

The [`LabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser) interface provides a
[`has_image_path`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.has_image_path)
property that declares whether the sample parser can return the path to the
current sample’s image on disk via
[`get_image_path()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.get_image_path).
Similarly, the
[`has_image_metadata`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.has_image_metadata)
property that declares whether the sample parser can return an [`ImageMetadata`](../api/fiftyone.core.metadata.md#fiftyone.core.metadata.ImageMetadata)
for the current sample’s image via
[`get_image_metadata()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.get_image_metadata).
Additionally, the
[`label_cls`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.label_cls)
property of the parser declares the type of label(s) that the parser
will produce.

By convention, all [`LabeledImageSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser) implementations must make the
current sample’s image available via
[`get_image()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.get_image)
, and they must make the current sample’s label available via
[`get_label()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledImageSampleParser.get_label).

To define a custom parser for unlabeled videos, implement the
[`UnlabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser) interface.

The pseudocode below provides a template for a custom
[`UnlabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser):

```python
import fiftyone.utils.data as foud

class CustomUnlabeledVideoSampleParser(foud.UnlabeledVideoSampleParser):
    """Custom parser for unlabeled video samples."""

    @property
    def has_video_metadata(self):
        """Whether this parser produces
        :class:`fiftyone.core.metadata.VideoMetadata` instances for samples
        that it parses.
        """
        # Return True or False here
        pass

    def get_video_path(self):
        """Returns the video path for the current sample.

        Returns:
            the path to the video on disk
        """
        # Return the video path for `self.current_sample` here
        pass

    def get_video_metadata(self):
        """Returns the video metadata for the current sample.

        Returns:
            a :class:`fiftyone.core.metadata.VideoMetadata` instance
        """
        # Return the video metadata for `self.current_sample` here, or
        # raise an error if `has_video_metadata == False`
        pass
```

When [`Dataset.add_videos()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_videos)
is called with a custom [`UnlabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser), the import is effectively
performed via the pseudocode below:

```python
import fiftyone as fo

dataset = fo.Dataset(...)
samples = ...
sample_parser = CustomUnlabeledVideoSampleParser(...)

for sample in samples:
    sample_parser.with_sample(sample)

    video_path = sample_parser.get_video_path()

    if sample_parser.has_image_metadata:
        metadata = sample_parser.get_image_metadata()
    else:
        metadata = None

    sample = fo.Sample(filepath=video_path, metadata=metadata)

    dataset.add_sample(sample)
```

The base [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface provides a
[`with_sample()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser.with_sample)
method that ingests the next sample and makes it available via the
[`current_sample`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser.current_sample)
property of the parser. Subsequent calls to the parser’s `get_XXX()` methods
return information extracted from the current sample.

The [`UnlabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser) interface provides a
[`get_video_path()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser.get_video_path)
to get the video path for the current sample. The
[`has_video_metadata`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser.has_video_metadata)
property that declares whether the sample parser can return a [`VideoMetadata`](../api/fiftyone.core.metadata.md#fiftyone.core.metadata.VideoMetadata)
for the current sample’s video via
[`get_video_metadata()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.UnlabeledVideoSampleParser.get_video_metadata).

To define a custom parser for labeled videos, implement the
[`LabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser) interface.

The pseudocode below provides a template for a custom
[`LabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser):

```python
import fiftyone.utils.data as foud

class CustomLabeledVideoSampleParser(foud.LabeledVideoSampleParser):
    """Custom parser for labeled video samples."""

    @property
    def has_video_metadata(self):
        """Whether this parser produces
        :class:`fiftyone.core.metadata.VideoMetadata` instances for samples
        that it parses.
        """
        # Return True or False here
        pass

    @property
    def label_cls(self):
        """The :class:`fiftyone.core.labels.Label` class(es) returned by this
        parser within the sample-level labels that it produces.

        This can be any of the following:

        -   a :class:`fiftyone.core.labels.Label` class. In this case, the
            parser is guaranteed to return sample-level labels of this type
        -   a list or tuple of :class:`fiftyone.core.labels.Label` classes. In
            this case, the parser can produce a single sample-level label field
            of any of these types
        -   a dict mapping keys to :class:`fiftyone.core.labels.Label` classes.
            In this case, the parser will return sample-level label
            dictionaries with keys and value-types specified by this
            dictionary. Not all keys need be present in the imported labels
        -   ``None``. In this case, the parser makes no guarantees about the
            sample-level labels that it may return
        """
        # Return the appropriate value here
        pass

    @property
    def frame_labels_cls(self):
        """The :class:`fiftyone.core.labels.Label` class(es) returned by this
        parser within the frame labels that it produces.

        This can be any of the following:

        -   a :class:`fiftyone.core.labels.Label` class. In this case, the
            parser is guaranteed to return frame labels of this type
        -   a list or tuple of :class:`fiftyone.core.labels.Label` classes. In
            this case, the parser can produce a single frame label field of any
            of these types
        -   a dict mapping keys to :class:`fiftyone.core.labels.Label` classes.
            In this case, the parser will return frame label dictionaries with
            keys and value-types specified by this dictionary. Not all keys
            need be present in each frame
        -   ``None``. In this case, the parser makes no guarantees about the
            frame labels that it may return
        """
        # Return the appropriate value here
        pass

    def get_video_path(self):
        """Returns the video path for the current sample.

        Returns:
            the path to the video on disk
        """
        # Return the video path for `self.current_sample` here
        pass

    def get_video_metadata(self):
        """Returns the video metadata for the current sample.

        Returns:
            a :class:`fiftyone.core.metadata.VideoMetadata` instance
        """
        # Return the video metadata for `self.current_sample` here, or
        # raise an error if `has_video_metadata == False`
        pass

    def get_label(self):
        """Returns the sample-level labels for the current sample.

        Returns:
            a :class:`fiftyone.core.labels.Label` instance, or a dictionary
            mapping field names to :class:`fiftyone.core.labels.Label`
            instances, or ``None`` if the sample has no sample-level labels
        """
        # Return the sample labels for `self.current_sample` here
        pass

    def get_frame_labels(self):
        """Returns the frame labels for the current sample.

        Returns:
            a dictionary mapping frame numbers to dictionaries that map label
            fields to :class:`fiftyone.core.labels.Label` instances for each
            video frame, or ``None`` if the sample has no frame labels
        """
        # Return the frame labels for `self.current_sample` here
        pass
```

When [`Dataset.add_labeled_videos()`](../api/fiftyone.core.dataset.md#fiftyone.core.dataset.Dataset.add_labeled_videos)
is called with a custom [`LabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser), the import is effectively
performed via the pseudocode below:

```python
import fiftyone as fo

dataset = fo.Dataset(...)
samples = ...
sample_parser = CustomLabeledVideoSampleParser(...)
label_field = ...

if isinstance(label_field, dict):
    label_key = lambda k: label_field.get(k, k)
elif label_field is not None:
    label_key = lambda k: label_field + "_" + k
else:
    label_field = "ground_truth"
    label_key = lambda k: k

for sample in samples:
    sample_parser.with_sample(sample)

    video_path = sample_parser.get_video_path()

    if sample_parser.has_video_metadata:
        metadata = sample_parser.get_video_metadata()
    else:
        metadata = None

    label = sample_parser.get_label()
    frames = sample_parser.get_frame_labels()

    sample = fo.Sample(filepath=video_path, metadata=metadata)

    if isinstance(label, dict):
        sample.update_fields({label_key(k): v for k, v in label.items()})
    elif label is not None:
        sample[label_field] = label

    if frames is not None:
        frame_labels = {}

        for frame_number, _label in frames.items():
            if isinstance(_label, dict):
                frame_labels[frame_number] = {
                    label_key(k): v for k, v in _label.items()
                }
            elif _label is not None:
                frame_labels[frame_number] = {label_field: _label}

        sample.frames.merge(frame_labels)

    dataset.add_sample(sample)
```

The base [`SampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser) interface provides a
[`with_sample()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser.with_sample)
method that ingests the next sample and makes it available via the
[`current_sample`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.SampleParser.current_sample)
property of the parser. Subsequent calls to the parser’s `get_XXX()` methods
return information extracted from the current sample.

The [`LabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser) interface provides a
[`get_video_path()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser.get_video_path)
to get the video path for the current sample. The
[`has_video_metadata`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser.has_video_metadata)
property that declares whether the sample parser can return a [`VideoMetadata`](../api/fiftyone.core.metadata.md#fiftyone.core.metadata.VideoMetadata)
for the current sample’s video via
[`get_video_metadata()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser.get_video_metadata).

The
[`label_cls`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser.label_cls)
property of the parser declares the type of sample-level label(s) that
the parser may produce (if any). The
[`frame_labels_cls`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser.frame_labels_cls)
property of the parser declares the type of frame-level label(s) that
the parser may produce (if any). By convention, all
[`LabeledVideoSampleParser`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser) implementations must make the current
sample’s sample-level labels available via
[`get_label()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser.get_label)
and its frame-level labels available via
[`get_frame_labels()`](../api/fiftyone.utils.data.parsers.md#fiftyone.utils.data.parsers.LabeledVideoSampleParser.get_frame_labels).
