Metadata-Version: 2.4
Name: testatlasvision
Version: 0.1.0
Summary: Python SDK for AtlasVision: multilingual (English, Igbo, Yoruba, Hausa) visual question answering on N-ATLaS + SigLIP2.
Keywords: vqa,multilingual,igbo,yoruba,hausa,vision-language
Classifier: Programming Language :: Python :: 3
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: torch
Requires-Dist: transformers>=4.56
Requires-Dist: peft
Requires-Dist: accelerate
Requires-Dist: safetensors
Requires-Dist: huggingface_hub
Requires-Dist: pillow
Provides-Extra: 4bit
Requires-Dist: bitsandbytes; extra == "4bit"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"

# TestAtlasVision SDK

Python SDK for **AtlasVision** (package `testatlasvision`, client class `TestAtlasVision`): ask questions about images in **English, Igbo, Yoruba and Hausa**.
It wraps SigLIP2 (vision) + N-ATLaS (language) + the AtlasVision projector/LoRA weights, and is a
cleaned-up, standardised version of the `test-atlas-vision` Colab.

> **Needs a GPU.** It will not run on a normal laptop. Test it in Colab with `colab/smoke_test.ipynb`.
> Everything that doesn't need weights (prompts, languages, auth, images, sessions) is covered by
> `pytest` and runs anywhere.

## Install

```bash
pip install testatlasvision            # core (from PyPI)
# or from a source checkout: pip install .
pip install "testatlasvision[4bit]"    # + bitsandbytes (needed on GPUs < 20 GB, e.g. Colab T4)
pip install ".[dev]"     # + pytest (source checkout)
```

## Authentication (N-ATLaS is gated)

1. Create a Hugging Face account and request access at <https://huggingface.co/NCAIR1/N-ATLaS>.
2. Create an access token at <https://huggingface.co/settings/tokens> (see below for which type).
3. Give it to the SDK in any one of these ways (checked in this order):

| Where | How |
|---|---|
| Code | `TestAtlasVision(token="hf_...")` |
| Environment | `export HF_TOKEN=hf_...` |
| Colab | Add a Secret named `HF_TOKEN` (key icon in sidebar, enable *Notebook access*) |
| Cached login | `huggingface-cli login` |
| Prompt | `TestAtlasVision(interactive_login=True)` |

### Which token type?

| Situation | Use |
|---|---|
| Testing in Colab, personal use | **Read**. Simplest. It can read everything your account can, including the gated model. |
| Production / the shared server | **Fine-grained**, so a leak only exposes what you listed. |
| Anything else | **Never** Write, Full Access, Inference or CI/CD. The SDK only downloads weights. |

For a fine-grained token, tick **"Read contents of public gated repos you can access"** (this is what lets it
download N-ATLaS). If the `Modularcomputing/AtlasVision` repo is private, also add it under *Repositories
permissions* with read access. Nothing else is needed. Without the gated-repos permission the token is valid
but gets a 403 on N-ATLaS, which the SDK reports as `GatedModelAccessError`.

Notes: the token type can't be changed after creation (make a new one). Use one token per app, and keep it
out of git. Your account still has to have been granted access on the N-ATLaS model page, whatever the token type.

The client checks access *before* the large downloads and raises a clear error:
`AuthenticationError` (no/invalid token) or `GatedModelAccessError` (token fine, access not granted).
The token is never logged (only a masked form) or written to disk by the SDK.

## Quick start

```python
from testatlasvision import TestAtlasVision, GenerationConfig

model = TestAtlasVision()                       # load ONCE (takes minutes)

model.ask("cats.jpg", "How many animals are in this image?")        # -> str
model.describe("cats.jpg", lang="yo")                               # caption in Yoruba

r = model.chat("cats.jpg", "Kedu ihe dị na foto a?", lang="ig")     # -> ChatResult
r.answer, r.english_question, r.english_answer

# Follow-up questions
s = model.new_session()
model.ask("cats.jpg", "What is the main object?", session=s)
model.ask("cats.jpg", "What colour is it?", session=s)

# Text-only (LoRA off)
model.text("Explain what a positron is.")
model.translate("Good morning", "ha")

# Decoding settings
model.ask("cats.jpg", "Describe it", config=GenerationConfig(max_new_tokens=100, temperature=0.7))
```

`image` can be a PIL image, a file path, raw `bytes`, a file-like object, or an `http(s)` URL.

## API reference

| Method | Returns | Notes |
|---|---|---|
| `TestAtlasVision(token, repo, llm, vision, stage, load_in_4bit, device, interactive_login, allow_remote_images, check_access)` | client | `stage`: `"2b"` (default, recommended), `"2"`, `"1"` (captioner) |
| `ask(image, question, *, session=None, config=None)` | `str` | English only. Pass a `Session` for follow-ups |
| `chat(image, question, *, lang="en", session=None, translate_question=True, config=None)` | `ChatResult` | `lang` in `en/ig/yo/ha` (or names). Non-English = translate, answer, translate back |
| `describe(image, *, lang="en", detailed=True, config=None)` | `str` | Caption |
| `text(prompt, *, config=None)` | `str` | Plain N-ATLaS, no image |
| `translate(text, target, source="en", *, config=None)` | `str` | Between `en/ig/yo/ha` |
| `new_session()` | `Session` | Conversation memory (always stored in English) |
| `info()` | `ModelInfo` | For health endpoints |
| `close()` | None | Free GPU memory |

Errors (all subclass `AtlasVisionError`): `AuthenticationError`, `GatedModelAccessError`,
`ModelLoadError`, `ImageLoadError`, `UnsupportedLanguageError`.

### How the Colab maps to the SDK

| test-atlas-vision | SDK |
|---|---|
| `notebook_login()` | token resolution (`auth.py`), no notebook widget needed |
| `model.history` (one list on the model) | `Session` objects owned by the caller |
| `model.ask(image, q, max_new_tokens=60)` | `model.ask(image, q, config=GenerationConfig(max_new_tokens=60))` |
| `chat()` returning a dict | `chat()` returning `ChatResult` |
| `LANGUAGES.get(x, x)` accepting anything | strict `normalize_language` |
| `!pip uninstall torchao` | still needed in Colab; done in the notebook, not the library |

## Notes for the API layer

- **Load once at startup**, keep one client per GPU. Loading takes minutes.
- **Sessions replace `model.history`.** The Colab kept history on the model object, which would mix
  users together in a server. Store `Session.to_dict()` in Redis/DB keyed by `session_id`.
- **Generation is serialised** by an internal lock (one GPU; the LoRA toggle in `text()` is global).
  Concurrent requests queue. Scale by running more workers/GPUs, not threads.
- **Latency:** a non-English `chat` is up to 3 generations. Set generous timeouts, or consider a
  job/polling pattern for `ig/yo/ha`.
- **Public endpoints:** build the client with `allow_remote_images=False` (a server fetching
  arbitrary URLs is an SSRF risk) and accept uploads instead. `load_image` also caps downloads at 25 MB.
- **Server token:** the server needs its own HF token (env var `HF_TOKEN`); end users never need one.
- `import testatlasvision` doesn't import torch, so request/response schemas can be shared with code that
  runs without a GPU. Map errors to HTTP codes per the table in `exceptions.py`.

## Testing

```bash
pytest                      # offline, no GPU
```

GPU/model behaviour, in Colab (GPU runtime). Two options:

- **No upload needed:** open `colab/smoke_test_standalone.ipynb`. It writes the SDK files itself, installs them, and runs the checks.
- **With the zip:** open `colab/smoke_test.ipynb`, upload `testatlasvision-sdk.zip` (or put it in Google Drive), run all cells.

## Known gaps

- Only the offline parts have automated tests. The model-loading and generation paths were ported
  line-for-line from the Colab but have to be verified in Colab (see the notebook).
- `transformers>=4.56` is pinned because the Colab uses `dtype=`; align it with the version that notebook actually runs.
- No streaming and no batching yet.
