Metadata-Version: 2.4
Name: citall
Version: 0.2.4
Summary: Interactive 3D PCA visualization from PyTorch embeddings with Plotly
Author: citall contributors
Project-URL: Homepage, https://example.com/citall
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: torch>=2.0
Requires-Dist: plotly>=5.18
Requires-Dist: numpy>=1.24

# citall

Python library for turning PyTorch embedding tensors into interactive, shareable 3D PCA explorers — with hover metadata and image previews — in a single function call.

## Install

```bash
pip install citall
# or with uv
uv add citall
```

## Quick Start

```python
from citall import pca3d_explorer

pca3d_explorer(
    vectors_pt="embeddings.pt",
    output_html="explorer.html",
    open_browser=True,
)
```

That's it. Open `explorer.html` in any browser — no server required.

## What It Produces

A self-contained HTML file with:
- A **3D scatter plot** of your embeddings projected onto their first 3 principal components, with per-axis variance percentages in the labels.
- **Hover tooltips** showing metadata fields you choose.
- A **click side-panel** showing detailed metadata and image previews for whichever point you select.
- **Color-by** support: numeric fields get a continuous Viridis gradient; categorical fields get a discrete legend.

## API Reference

### `pca3d_explorer`

| Parameter | Type | Default | Description |
|---|---|---|---|
| `vectors_pt` | `str \| Path \| dict` | required | Your embeddings. Accepts a path to a `.pt` file, or a Python dict. See **Inputs** for all supported shapes. |
| `metadata_path` | `str \| Path` | `None` | Path to a `.json` or `.jsonl` file containing per-embedding metadata. Each record is matched to a vector via `metadata_key` + `rows_key`. |
| `metadata_key` | `str \| list[str]` | `None` | The field (or list of fields) in the **metadata file** used as the join key. Required when `metadata_path` is provided. Supports pipe syntax: `"object_id\|uid"`. |
| `rows_key` | `str \| list[str]` | `None` | The field (or list of fields) in the **embedding rows** used as the join key. Defaults to the vector id when omitted. |
| `images_dir` | `str \| Path` | `None` | A directory of images. citall looks for `<id>`, `<id>.png`, `<id>.jpg`, `<id>.jpeg`, `<id>.webp`, or `<id>.gif`, and falls back to using the `filename` field from the row. If `<id>` is itself a subdirectory, all images inside it are collected (up to `max_images_per_embedding`). |
| `image_dir_key` | `str` | `None` | A field name in metadata/rows whose value is a path to a **folder of images** for that embedding. Relative paths are resolved against `images_dir` when set. Takes priority over `images_dir` fallback. |
| `max_images_per_embedding` | `int` | `6` | Maximum number of images loaded and shown in the side panel per point. Set to `None` to load all. |
| `hover_fields` | `list[str]` | `None` | Metadata fields to show in the hover tooltip. When `None`, up to 6 fields are shown automatically. |
| `click_fields` | `list[str]` | `None` | Metadata fields to show in the click side panel. When `None`, all available metadata is shown. |
| `color_by` | `str` | `None` | A metadata field used to color the points. Numeric values produce a **continuous Viridis gradient**; string values produce **discrete legend groups**. Points missing the field appear in grey. |
| `output_html` | `str \| Path` | `None` | Where to write the HTML file. Defaults to `citall_plot.html` in the current directory. When `open_browser=True` and this is `None`, a temporary file is used instead. |
| `open_browser` | `bool` | `False` | Automatically open the generated HTML in your default browser after saving. |
| `return_summary` | `bool` | `False` | When `True`, the function returns `(Path, dict)` instead of just `Path`. The dict contains diagnostic counts and PCA statistics — see **Summary Dict** below. |

```python
from citall import pca3d_explorer

output_path = pca3d_explorer(
    vectors_pt="embeddings.pt",
    metadata_path="meta.jsonl",
    metadata_key="id",
    hover_fields=["label", "split"],
    color_by="label",
    output_html="explorer.html",
)
```

When `return_summary=True`, the return value is `(Path, dict)`:

```python
output_path, summary = pca3d_explorer(..., return_summary=True)
print(summary["num_points"])
print(summary["pca_explained_variance_ratio"])
```

---

### `CitallExplorer` (class API)

For multi-step workflows or when you want to reuse the same dataset:

```python
from citall import CitallExplorer

explorer = CitallExplorer.from_files(
    vectors_pt=...,
    metadata_path=...,
    metadata_key=...,
    rows_key=...,
    images_dir=...,
    image_dir_key=...,
    max_images_per_embedding=6,
)

explorer.compute_pca()           # runs PCA, stores result internally
explorer.save_html("out.html", hover_fields=["label"], color_by="label")
print(explorer.summary())
```

## Inputs

### Vectors (`vectors_pt`)

Accepts any of:

| What you pass | How it's interpreted |
|---|---|
| Path to a `.pt` file containing a `Tensor` of shape `(N, D)` | Ids become `"0"`, `"1"`, … |
| Path to a `.pt` dict with `vectors`/`embeddings`/`tensor`/`data` key | Ids from `ids`/`keys`/`labels`/`names` key, or auto-numbered |
| Dict with an `embeddings` (or `vectors`) tensor + a `rows` list of dicts | Ids from `row["uid"]` or `row["id"]`, else index |
| Any of the above passed as a Python `dict` directly (no file needed) | Same as above |

### Metadata (`metadata_path`)

`.json` or `.jsonl` file. Supported formats:

- **JSONL** — one object per line
- **JSON list** — `[{...}, {...}]`
- **JSON map** — `{"id1": {...}, "id2": {...}}` (the key is injected as `"id"`)

Metadata is joined to vectors by matching `metadata_key` against `rows_key`. If both your rows and metadata contain an `object_id` field, the join happens automatically with no extra config.

### Images

Two ways to attach images to points:

| Parameter | What it does |
|---|---|
| `images_dir` | A flat folder or folder-per-id. citall looks for `<id>`, `<id>.png`, `<id>.jpg`, etc., and also checks the `filename` field as a fallback. |
| `image_dir_key` | A metadata/row field whose value is a path to a folder of images for that embedding. Combined with `images_dir` as a base when the path is relative. |

`max_images_per_embedding` caps how many images are loaded (and shown in the side panel) per point. Default is `6`.

## Coloring Points

```python
pca3d_explorer(..., color_by="label")
```

- **Numeric field** → continuous Viridis gradient with a colorbar.
- **Categorical field** → discrete legend groups.
- Points missing the field → rendered in neutral grey, shown as a separate legend entry.

## Summary Dict

When `return_summary=True`, you get back a dict with:

| Key | Description |
|---|---|
| `num_points` | Total points in the plot |
| `vector_shape` | Shape of the input tensor |
| `metadata_rows_matched` | Points that have at least one metadata row |
| `images_matched` | Points that have at least one resolved image |
| `matched_vectors` | Vectors successfully joined to metadata |
| `unmatched_vectors` | Vectors with no metadata match |
| `missing_images` | Points where image resolution failed |
| `pca_explained_variance_ratio` | `[pc1%, pc2%, pc3%]` as floats |

## Examples

### Minimal — tensor only

```python
import torch
from citall import pca3d_explorer

vectors = torch.randn(500, 128)
pca3d_explorer(vectors_pt={"embeddings": vectors, "rows": []}, output_html="out.html")
```

### With metadata and coloring

```python
pca3d_explorer(
    vectors_pt="embeddings.pt",
    metadata_path="meta.jsonl",
    metadata_key="id",
    hover_fields=["label", "source"],
    click_fields=["label", "filename", "score"],
    color_by="label",
    output_html="explorer.html",
    open_browser=True,
)
```

### Raw dict payload + images per embedding

```python
pca3d_explorer(
    vectors_pt={"embeddings": tensor, "rows": rows},   # rows is a list of dicts
    images_dir="./data/images",      # base folder — citall looks for ./data/images/<id>.jpg (or a folder named <id>)
    image_dir_key="folder",          # if a row has a "folder" field, citall loads images from that folder instead
    max_images_per_embedding=4,
    color_by="category",
    output_html="explorer.html",
)
```
