# Manage Images

`Project` exposes the per-image operations that complement the bulk [`upload_dataset`](/developer/python-sdk/upload-a-dataset.md) flow. Use these when you need finer control over single-image uploads, want to attach annotations after the fact, or are ingesting images one-at-a-time from a stream.

## Upload an image (with optional annotation)

`Project.upload()` is the high-level "do the right thing" helper. It accepts a single image plus an optional matching annotation file and ships both to the project in one call.

```python
import roboflow

rf = roboflow.Roboflow(api_key="YOUR_API_KEY")
project = rf.workspace().project("my-detector")

result = project.upload(
    image_path="./photo.jpg",
    annotation_path="./photo.xml",   # optional; matched VOC / COCO / etc. annotation
    split="train",                    # train | valid | test
    batch_name="ingest-2026-05",     # optional; groups uploads in the web UI
    tag_names=["camera-A", "indoor"], # optional; apply tags
    is_prediction=False,              # set True for model-generated annotations awaiting review
    num_retry_uploads=2,              # retries on transient upload failures
)
print(result)
```

`single_upload()` is a lower-level variant that takes the same arguments and returns the raw API responses for both the image and (if provided) the annotation.

## Upload an image only

```python
project.upload_image(
    image_path="./photo.jpg",
    split="train",
    batch_name="ingest-2026-05",
    tag_names=["camera-A"],
)
```

Useful when annotations don't exist yet and the image goes straight to a labeler.

## Validate an image before uploading

`check_valid_image()` runs Roboflow's local size / format checks without hitting the API:

```python
if project.check_valid_image("./photo.jpg"):
    project.upload_image("./photo.jpg")
```

## Attach an annotation to an existing image

`save_annotation()` posts an annotation against an image that's already in the project. Useful for adding labels created elsewhere, or for promoting a model prediction to ground truth.

```python
project.save_annotation(
    image_id="<image-id>",
    annotation_path="./photo.xml",
    is_prediction=False,
    annotation_overwrite=True,    # replace any existing annotation
)
```

Pass `annotation_labelmap="./labelmap.yaml"` to map class indices into class names if your annotation format requires it.

## Fetch an image's metadata

```python
info = project.image("<image-id>")
print(info["name"], info["split"], info["annotations"])
```

Returns image metadata, current split, and annotation status.

## Delete images

Project-level (only deletes images that belong to this project):

```python
project.delete_images(["<image-id-1>", "<image-id-2>"])
```

Workspace-level (removes images regardless of which projects reference them — use with care):

```python
workspace.delete_images(["<image-id-1>", "<image-id-2>"])
```

## A note on uploads in v1.3.6+

As of `roboflow` 1.3.6, the SDK uploads the original image bytes rather than re-encoding via Pillow. This restores parity with the web uploader and lets the Roboflow server deduplicate uploads by SHA-256. If you have automation that uploads the same image twice (e.g. to add it to multiple batches), you'll see the second upload succeed without consuming additional storage credits.

## REST and CLI equivalents

* REST: see [Manage Images](/developer/rest-api/manage-images.md) (upload, get, search, tag, delete) and [Upload an Annotation](/developer/rest-api/manage-images/upload-an-annotation.md).
* CLI: see the `roboflow image` command group, e.g. [Upload a Dataset (CLI)](/developer/command-line-interface/upload-a-dataset.md).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.roboflow.com/developer/python-sdk/manage-images.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
