# Anno-Mage, a semi-automatic annotator that proposes boxes from two different models

> Anno-Mage 2.0.5 ships as a PyPI package and as a FastAPI plus React app. Here is how the wheel gets its frontend, what it writes, and where the version numbers disagree.

**virajmavani/semi-auto-image-annotation-tool** — Anno-Mage: A Semi Automatic Image Annotation Tool which helps you in annotating images by suggesting you annotations using user-defined labels

- Repository: https://github.com/virajmavani/semi-auto-image-annotation-tool
- Website: https://virajmavani.com/annomage
- Stars: 596 · Forks: 128
- Language: Python
- License: Apache-2.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/virajmavani-semi-auto-image-annotation-tool

## Two ways in, and the packaged one opens its own browser

The installed route is two lines:

```bash
pip install anno-mage
anno-mage
```

The console script opens a browser automatically and writes annotations under `~/.anno-mage/annotations/`. The development route is different in kind, because it is two processes you start yourself: a backend on port 8000 and a React frontend on port 3000, with the backend started from its own directory and the frontend from another, the latter needing `npm install` first. There is also a convenience script that does both from one place:

```bash
cd web && bash start.sh
```

The distinction matters more than it looks. The installed path is a single process with a browser window and a fixed directory under your home folder, while the development path is two servers with a port each and storage wherever the backend resolves relative to where you launched it. The project points you at a separate document for the web app's installation, usage, and API reference, which is where the development flow is treated in full.

## The wheel ships compiled static assets, not the React source

The packaging configuration is where the two interfaces are reconciled. The wheel includes three package roots: the Python package itself, a `models` package, and the web tree, with one exclusion, the frontend. So the React source is deliberately left out, and what ships in its place is compiled output: package data pulls everything under a `static/` directory into the Python package, and the local build script is what fills it. That script compiles the frontend, copies the build into the Python package's static directory, and produces both a wheel and an sdist in a `dist` directory. Its two prerequisites are the build frontend package and an `npm install` inside the frontend directory. The result is that the installed artifact is self-contained even though the source tree is not.

## Six CSV columns, or one XML file per image

There are exactly two output formats and the claim is that both interfaces produce identical output from them, which is the useful property if you switch between the packaged and development workflows. The first is a single CSV at `annotations/annotations.csv`, with one row per box and the columns `image_path`, `x1`, `y1`, `x2`, `y2`, and `label`. Six columns, axis-aligned coordinates, no confidence score, no polygon. The second is Pascal VOC XML written to a directory, one file per image, which is the format a decade of tooling still reads. The Pascal VOC writer is a declared dependency rather than hand-rolled, and Pillow is there for image handling. Nothing in either format carries a model confidence or a class beyond the label string, so a proposed box and an accepted box are indistinguishable once written.

## RetinaNet for known classes, OWL-v2 for the labels you define

Two detection models do different jobs, and the acknowledgements section names which is which. The PyTorch and Torchvision contribution is credited for the RetinaNet implementation, which is the closed-vocabulary path: the model proposes boxes for classes it already knows. The open-vocabulary path comes from Hugging Face Transformers, credited for the OWL-v2 zero-shot detection model, and that is the one that makes the tool semi-automatic in the sense the project description implies, since you can supply labels the pretrained detector has never seen. The repository description says the tool suggests annotations using user-defined labels, and OWL-v2 is what makes that possible. Other dependencies follow from that: `huggingface_hub` for model fetching, `transformers` for the pipeline, FastAPI and Uvicorn with the standard extra for serving, python-multipart for uploads, and Pydantic for validation.

## Publishing is a tag push, and the credentials live at PyPI

The release path is deliberately short. Push a version tag and a GitHub Actions workflow builds the frontend, packages everything, and publishes to PyPI through Trusted Publishers, which is why no API token is stored anywhere in the repository. The one-time setup names three values: the GitHub repository, the workflow file, and a publishing environment name. After that, a release is two commands:

```bash
git tag v2.0.1
git push origin v2.0.1
```

The workflow named in the setup and in the release instructions is the one in the release workflow file, and it handles everything after the push. Two workflow files are visible at the top level of the repository, one for releases and one for tests, so a pull request is gated separately from a publish. The friction here is close to zero, which is convenient for the maintainer and means a fork has to repeat the PyPI setup under its own project rather than inheriting anything.

## The manifest says 2.0.5, the release example says v2.0.1, and GitHub shows no releases

Three version signals point at three different places. The project metadata declares version 2.0.5. The documented release command tags `v2.0.1`. And the repository has no GitHub releases at all, despite shipping a release workflow and publishing to PyPI automatically, which means the tags are going somewhere without producing a GitHub release page you can read notes on. Nothing here is broken, but if you want to know what is on PyPI versus what is in the branch, the tag history is the only record and there is no changelog linked from the README. The branch was last pushed on 2026-04-18, which is a gap worth accounting for before you file an issue against a version you installed.

## Every dependency has a floor and no ceiling, torch included

The dependency list is short and entirely lower-bounded: FastAPI and Uvicorn with the standard extra for the server, python-multipart for uploads, Pillow for images, torch and torchvision for the detector, the Pascal VOC writer, Pydantic, Transformers, and the Hub client. Not one has an upper bound. For a library that couples to two separate machine learning stacks, that is the line to look at twice, because a major release in either can change a tensor type or a processor signature and your install will pull it without complaint. The supported range is declared instead through classifiers, listing Python 3.10 through 3.14, and the package declares a floor of 3.10. The operating system classifier is independent, which matches a tool whose only heavy dependency is a model.

## Sample annotations, a demo GIF, and model files are committed

The repository carries more than source. There is an `annotations/` directory checked in, a demo GIF, a `snapshots/` directory, and a `models/` directory, and that last one is part of what the wheel includes rather than being ignored at build time. A project with a web frontend, a Python package, a models directory, and a bundled dataset has four ways for a repository to grow large, and none of them are prevented here by a large ignore list. There is also a pull request template and a code of conduct, so the contribution path is set up even though the test suite is narrow: the pytest configuration points test discovery at a single directory inside the web backend, with a comment explaining that the conftest handles the import path rather than a configured path entry.

## Conclusion

Anno-Mage fits someone labelling a few thousand images with boxes who wants the model to do the rectangle and the human to do the accept-or-reject. The open-vocabulary side is what lets you work with a label list that does not exist in a pretrained detector's classes, and the dual output means you can feed the result straight into a YOLO pipeline or a VOC-era tool. It is a poor fit if you need segmentation or polygons, since the formats are boxes only, and a poor fit if you need a pinned dependency set, since nothing has an upper bound. Before you install, decide which of the two interfaces you want, because the packaged one and the development one have different storage paths.

## FAQ

### How do I install Anno-Mage?

Run pip install anno-mage and then the anno-mage command. The app opens in your browser automatically and writes annotations under ~/.anno-mage/annotations/. To run it from source instead, start the backend on port 8000 and the frontend on port 3000 separately, or use the convenience script from the web directory.

### Which detection models does Anno-Mage use?

Two. A RetinaNet implementation from PyTorch and Torchvision handles the closed-vocabulary path, and the open-vocabulary zero-shot model OWL-v2 comes from Hugging Face Transformers. OWL-v2 is what lets the tool suggest annotations for labels a pretrained detector has never seen, which is the point of the user-defined labels.

### What output formats does Anno-Mage write?

Two, and both interfaces produce them identically. A single CSV at annotations/annotations.csv with one row per box and the columns image_path, x1, y1, x2, y2, and label, plus Pascal VOC XML in a separate directory with one file per image. Neither format carries a model confidence or a polygon.

### How is a new Anno-Mage version published?

Push a version tag. A GitHub Actions workflow builds the frontend, packages everything, and publishes to PyPI through Trusted Publishers, so no token is stored in the repository. One-time setup adds a publisher on the PyPI project naming the repository, the release workflow, and a publishing environment.

### Does the Anno-Mage wheel include the React frontend?

Not the source. The frontend package is excluded from what the wheel includes, and instead the compiled output is copied into the Python package's static directory as package data. A local build compiles the frontend, performs that copy, and produces both a wheel and an sdist.

## Sources

- [Issues](https://github.com/virajmavani/semi-auto-image-annotation-tool/issues)
- [License: Apache-2.0](https://github.com/virajmavani/semi-auto-image-annotation-tool/blob/master/LICENSE)
- [Project website](https://virajmavani.com/annomage)
- [README](https://github.com/virajmavani/semi-auto-image-annotation-tool/blob/master/README.md)
- [virajmavani/semi-auto-image-annotation-tool on GitHub](https://github.com/virajmavani/semi-auto-image-annotation-tool)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/virajmavani-semi-auto-image-annotation-tool
