# Snaps

Snaps let your application reliably send visual data (images, videos, pointclouds) and optional annotations to Luxonis Hub. Once
stored as snaps, this data can be browsed, filtered, or downloaded, making snaps the primary building block for data collection in
Luxonis apps. More information about snaps can be found on the [Hub's snaps
page](https://docs.luxonis.com/cloud/features/event-storage/snaps.md).

Under the hood, snaps are implemented as a special type of event sent via the EventsManager. Sending raw events directly is not
supported; as a depthai user, you interact with snaps only.

## Getting Started

To send snaps from your app, you first create and reuse a single
[EventsManager](https://docs.luxonis.com/software-v3/depthai/api/python.md) instance. In a typical application, you:

 1. Create the EventsManager once (for example, next to your pipeline / device setup).
 2. Keep a reference to it in your main application class or context.
 3. Call sendSnap() whenever you want to upload visual data to Luxonis Hub.

Each snap must have a defined name and at least one associated file. The following parameters can be included:

| Parameter | Description | Example |
| --- | --- | --- |
| `Name` | The primary identifier for a snap | `car_detected`, `gate_open`, `image` |
| `Files` | Attachments uploaded with the snap | `ImgFrame`, `ImgDetections` |
| `Tags` (Optional) | Additional filters for grouping and filtering | `dataset_collection`, `Dataset 1`, `Location 72`, `Assembly
Line 17` |
| `Extras` (Optional) | Additional filters for grouping and filtering | `Car brand:volvo`, `State: open`, `Location: New York` |
| `Success Callback` (Optional) | Callback invoked after a successful upload attempt | `on_success` |
| `Failure Callback` (Optional) | Callback invoked after a failed upload attempt | `on_failure` |

### Snap Limits

When sending snaps, be aware of the following limits:

| Field | Rule |
| --- | --- |
| `Name` | 1–56 characters (required) |
| `Files` | Max 20 files per snap (Min 1 required) |
| `Tags` | Max 20 tags; each 1–56 characters |
| `Extras` | Max 25 entries; extras.key 1–40 characters; extras.value 0–100 characters |

Additionally, file uploads may be rejected if they exceed team quota limits enforced by the Luxonis Hub (which can be found on
your Hub billing page):

 * Maximum file size
 * Remaining storage
 * Hourly limits for file uploads (number of files and bandwidth)
 * Hourly limits for events and snaps sent

> **Note:**
> Calling
> `sendSnap()`
> only validates basic parameters and queues the snap for upload. It does not guarantee that the snap has been sent to the Hub.
> `sendSnap()`
> returns a local snap ID (string) when the snap is queued successfully, and
> `None`
> /
> `nullopt`
> when it is not. A successfully queued snap does not confirm delivery or file uploads. To be notified when the upload attempt
finishes, pass
> `successCallback`
> and
> `failureCallback`
> arguments to
> `sendSnap()`
> .

### Authentication

If you are using oakctl to run your application, the authentication is handled on your behalf. You do not provide an API key
yourself.

For oakctl run-script this uses the team you logged into using oakctl hub login. For oakctl app run this uses the team where the
device is connected.

To send snaps, your team's API Key must be defined. This is typically set using the DEPTHAI_HUB_API_KEY environment variable or by
passing it when creating the [EventsManager](https://docs.luxonis.com/software-v3/depthai/api/python.md) instance. When running in
standalone mode and adopted to Hub, DEPTHAI_HUB_API_KEY is pre-populated.

The [EventsManager](https://docs.luxonis.com/software-v3/depthai/api/python.md) constructor API key parameter is optional and
defaults to an empty string ("") when not set. If you pass a non-empty API key in the constructor, it is used instead of
DEPTHAI_HUB_API_KEY. Set the constructor API key only if DEPTHAI_HUB_API_KEY is not set, or if you intentionally want to override
it.

API Key good practices with practical examples can be [found
here](https://docs.luxonis.com/software-v3/oak-apps/apikey-good-practices.md).

## Sending Snaps with ImgFrames and ImgDetections

This section demonstrates creating an [EventsManager](https://docs.luxonis.com/software-v3/depthai/api/python.md) instance and
using it to send snaps containing either a single image or an image with detections in a Luxonis DepthAI application.

#### Python

```python
# Create the EventsManager instance
eventMan = dai.EventsManager()

# Send snap with ImgFrame and ImgDetections
localSnapID = eventMan.sendSnap(
    name="snap_name",
    fileTag="file_tag",
    imgFrame=inImgFrame,
    imgDetections=inImgDetections,
    tags=["examples", "python"],
    extras={"confidence": "0.75", "location": "01"},
    successCallback=uploadSuccessCallback,
    failureCallback=uploadFailureCallback
)
```

#### C++

```cpp
// Create the EventsManager instance
auto eventsManager = std::make_shared<dai::utility::EventsManager>();

// Send snap with ImgFrame and ImgDetections
auto localSnapID = eventsManager->sendSnap(
    "snap_name", 
    "file_tag", 
    inImgFrame, 
    inImgDetections, 
    {"examples", "C++"}, 
    {{"confidence", "0.75"}, {"location", "01"}},
    uploadSuccessCallback,
    uploadFailureCallback
);
```

Some parameters in [sendSnap()](https://docs.luxonis.com/software-v3/depthai/api/python.md) are optional. For example, you can
send a snap with only a snap name and an
[ImgFrame](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/img_frame.md), omitting the file tag,
detections, tags, extras, and callbacks:

#### Python

```python
# Create the EventsManager instance
eventMan = dai.EventsManager()

# Send snap with ImgFrame
localSnapID = eventMan.sendSnap(
    name="snap_name",
    fileTag=None,
    imgFrame=inImgFrame,
    imgDetections=None,
    tags=[],
    extras={}
)
```

#### C++

```cpp
// Create the EventsManager instance
auto eventsManager = std::make_shared<dai::utility::EventsManager>();

// Send snap with ImgFrame
auto localSnapID = eventsManager->sendSnap(
    "snap_name", 
    std::nullopt, 
    inImgFrame
);
```

### Wait for Pending Uploads Before Exit

sendSnap() queues uploads which are then processed in the background. If the
[EventsManager](https://docs.luxonis.com/software-v3/depthai/api/python.md) instance is destroyed while uploads are still pending,
those uploads are discarded.

Before your application exits, call [waitForPendingUploads(timeoutMs)](https://docs.luxonis.com/software-v3/depthai/api/python.md)
to keep EventsManager alive while pending uploads are processed. The timeoutMs parameter is optional and defaults to 0. A value of
0 means the call waits indefinitely until uploads finish, the connection drops, or the manager is stopped.

waitForPendingUploads(timeoutMs) returns true when all pending uploads finish. It returns false if the chosen timeout is reached,
the connection drops, or the manager is stopped.

> **Note:**
> This wait can take a long time when many snaps are pending (queued or cached).

### Upload Callback Arguments (Optional)

sendSnap() can invoke the optional successCallback and failureCallback arguments once the upload attempt completes. Use these if
you need confirmation of the snap upload or additional information from SendSnapCallbackResult, such as its Hub ID (if
successfully uploaded), payload, or upload status.

#### Python

```python
# Define the optional callback functions
def uploadSuccessCallback(sendSnapResult):
    print(f"Successfully uploaded Snap: {sendSnapResult.snapName} to the hub.")

def uploadFailureCallback(sendSnapResult):
    print(f"Upload of Snap: {sendSnapResult.snapName} to the hub has failed.")

# Create the EventsManager instance
eventMan = dai.EventsManager()

# Send snap with ImgFrame and ImgDetections
localSnapID = eventMan.sendSnap(
    name="snap_name",
    fileTag="file_tag",
    imgFrame=inImgFrame,
    imgDetections=inImgDetections,
    tags=["examples", "python"],
    extras={"confidence": "0.75", "location": "01"},
    successCallback=uploadSuccessCallback,
    failureCallback=uploadFailureCallback
)
```

#### C++

```cpp
// Define the optional callback functions
void uploadSuccessCallback(const dai::utility::SendSnapCallbackResult sendSnapResult) {
    std::cout << "Successfully uploaded Snap: " << sendSnapResult.snapName << 
    " to the hub." << std::endl;
}

void uploadFailureCallback(const dai::utility::SendSnapCallbackResult sendSnapResult) {
    std::cout << "Upload of Snap: " << sendSnapResult.snapName << 
    " to the hub has failed." << std::endl;
}

// Create the EventsManager instance
auto eventsManager = std::make_shared<dai::utility::EventsManager>();

// Send snap with ImgFrame and ImgDetections
auto localSnapID = eventsManager->sendSnap(
    "snap_name", 
    "file_tag", 
    inImgFrame, 
    inImgDetections, 
    {"examples", "C++"}, 
    {{"confidence", "0.75"}, {"location", "01"}},
    uploadSuccessCallback,
    uploadFailureCallback
);
```

Detailed examples of concrete use cases can be found in the following links:

### Sending snaps (python)

[Sending snaps (python)](https://github.com/luxonis/depthai-core/blob/main/examples/python/Events/events.py)

### Sending snaps (C++)

[Sending snaps (C++)](https://github.com/luxonis/depthai-core/blob/main/examples/cpp/Events/events.cpp)

## Advanced Use: Sending Snaps using FileGroups

In the examples above, a FileGroup is created automatically from the image (and detections) when using
[sendSnap()](https://docs.luxonis.com/software-v3/depthai/api/python.md). You can also create a FileGroup explicitly. When sending
a FileGroup, all included files are uploaded to the Luxonis Hub together. The group's upload will either fully succeed or fail,
depending on your allocated storage capacity - it must be large enough to accommodate all files in the group.

The example below demonstrates the same functionality using the FileGroup object explicitly. This approach involves creating a
FileGroup instance and adding files to it. Common file pairs, such as
[ImgFrame](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/img_frame.md) and
[ImgDetections](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/img_detections.md), can be added
simultaneously, or separately as individual files. More information can be found in the FileGroup class documentation.

#### Python

```python
# Create the EventsManager instance
eventMan = dai.EventsManager()

# Create the FileGroup instance
fileGroup = dai.FileGroup()

# Add files to fileGroup
fileGroup.addImageDetectionsPair("file_tag", inImgFrame, inImgDetections)

# Send snap with the fileGroup
localSnapID = eventMan.sendSnap(
    name="snap_name",
    fileGroup=fileGroup,
    tags=["examples", "python"],
    extras={"confidence": "0.75", "location": "01"}
)
```

#### C++

```cpp
// Create the EventsManager instance
auto eventsManager = std::make_shared<dai::utility::EventsManager>();

// Create the FileGroup instance
auto fileGroup = std::make_shared<dai::utility::FileGroup>();

// Add files to fileGroup
fileGroup->addImageDetectionsPair("file_tag", inImgFrame, inImgDetections);

// Send snap with the fileGroup
auto localSnapID = eventsManager->sendSnap(
    "snap_name", 
    fileGroup, 
    {"examples", "C++"}, 
    {{"confidence", "0.75"}, {"location", "01"}}
);
```

Detailed examples of concrete use cases can be found in the following links:

### Sending snaps using a FileGroup (python)

[Sending snaps using a FileGroup
(python)](https://github.com/luxonis/depthai-core/blob/main/examples/python/Events/events_file_group.py)

### Sending snaps using a FileGroup (C++)

[Sending snaps using a FileGroup
(C++)](https://github.com/luxonis/depthai-core/blob/main/examples/cpp/Events/events_file_group.cpp)

## Caching

When sending snaps to Luxonis Hub, network connectivity is a critical dependency. However, DepthAI provides robust caching
mechanisms to ensure that visual data is not lost due to temporary network outages or connection failures. This section explains
how to configure and use caching for snaps.

### Overview

By default, when sendSnap() is called and the snap cannot be delivered to the Hub after a number of retry attempts, the snap is
dropped and discarded. If you want to preserve snaps during network disruptions, you can enable local caching. Once enabled, snaps
that fail to upload are stored locally and can be retransmitted when connectivity is restored.

### Enabling Caching

To enable caching, call the setCacheIfCannotSend() method on your EventsManager instance:

#### Python

```python
# Create the EventsManager instance
eventMan = dai.EventsManager()

# Enable caching for snaps that cannot be sent
eventMan.setCacheIfCannotSend(True)
```

#### C++

```cpp
// Create the EventsManager instance
auto eventsManager = std::make_shared<dai::utility::EventsManager>();

// Enable caching for snaps that cannot be sent
eventsManager->setCacheIfCannotSend(true);
```

### Configuring the Cache Directory

By default, cached snaps are stored in a default directory. To specify a custom location for the cache, use the setCacheDir()
method:

#### Python

```python
# Create the EventsManager instance
eventMan = dai.EventsManager()

# Enable caching
eventMan.setCacheIfCannotSend(True)

# Set a custom cache directory
eventMan.setCacheDir("/path/to/cache/directory")
```

#### C++

```cpp
// Create the EventsManager instance
auto eventsManager = std::make_shared<dai::utility::EventsManager>();

// Enable caching
eventsManager->setCacheIfCannotSend(true);

// Set a custom cache directory
eventsManager->setCacheDir("/path/to/cache/directory");
```

### Uploading Previously Cached Snaps on Application Startup

When your application restarts, previously cached snaps (if any) can be automatically uploaded to the Hub during the next
application run. To enable this behavior, set the uploadCachedOnStart flag to true when constructing the EventsManager:

#### Python

```python
# Create the EventsManager instance with uploadCachedOnStart enabled
eventMan = dai.EventsManager(uploadCachedOnStart=True)
```

#### C++

```cpp
// Create the EventsManager instance with uploadCachedOnStart enabled
auto eventsManager = std::make_shared<dai::utility::EventsManager>("", true);
```

The uploadCachedOnStart flag determines whether cached snaps persist across application restarts. If uploadCachedOnStart is not
enabled (false or not set) and cached data exists in local storage, it will be automatically cleared when the EventsManager is
initialized. To preserve cached snaps across application restarts and have them uploaded to the Hub during the next run, you must
explicitly set uploadCachedOnStart to true. Otherwise, cached data will be deleted.

### Upload Rate Management and Upload Priority

Cached snaps are uploaded gradually alongside newly added snaps, with newly added snaps receiving priority in the upload queue.
Rather than uploading cached snaps as quickly as possible, DepthAI deliberately throttles the upload rate to respect the upload
limits and quota configuration enforced by Luxonis Hub.

This approach ensures that:

 * Your application can continue sending fresh snaps without being blocked by the backlog of cached data
 * Hub rate limits (hourly file upload counts, bandwidth restrictions) are not exceeded
 * Cached snaps are eventually uploaded without degrading real-time data collection

As your application continues to generate new snaps, cached snaps are gradually uploaded in the background, allowing recovery from
network outages without disrupting normal operations or violating Hub constraints.

## How and when are snaps uploaded to the Hub?

DepthAI uploads and sends events in batches, which means multiple FileGroup instances or snaps are grouped together in a single
request. Batches are sent at regular intervals (by default every 30 seconds), which may cause a brief delay before snaps appear in
the Hub. For increased batch frequency, please contact support.
