Label Studio - Local Image Annotation Tool
Label Studio - Local Image Annotation Tool
Label Studio is an open-source data labeling platform that runs locally. It's useful for annotating images with bounding boxes, polygons, keypoints, and more - then exporting structured JSON for use in other tools or machine learning pipelines.
Installation
macOS (Homebrew)
bash# Add the HumanSignal tap brew tap humansignal/tap # Install Label Studio brew install humansignal/tap/label-studio
Alternative: pip
bashpip install label-studio
Data locations
Label Studio stores everything in your user Application Support folder:
| Item | Path |
|---|---|
| SQLite database | ~/Library/Application Support/label-studio/label_studio.sqlite3 |
| Environment config | ~/Library/Application Support/label-studio/.env |
| Uploaded media | ~/Library/Application Support/label-studio/media/ |
| Exports | ~/Library/Application Support/label-studio/export/ |
Starting the server
bash# Start on default port 8080 label-studio start # Start on custom port label-studio start --port 9090 # Start with specific data directory label-studio start --data-dir /path/to/data
The web interface opens automatically at http://localhost:8080.
First-time setup
- Navigate to
http://localhost:8080 - Click Sign Up to create a local account
- Create your first project
Creating a project for image annotation
- Click Create Project
- Name your project (e.g., "UI Element Annotations")
- Go to Labeling Setup tab
- Choose a template or create custom XML config
Example: UI element annotation config
xml<View> <Image name="image" value="$image" zoom="true" zoomControl="true"/> <!-- Keypoints for precise positions --> <KeyPointLabels name="keypoints" toName="image"> <Label value="point" background="#FF0000"/> <Label value="knob_center" background="#00FF00"/> </KeyPointLabels> <!-- Rectangles for buttons/regions --> <RectangleLabels name="rectangles" toName="image"> <Label value="button" background="#FF00FF"/> <Label value="display" background="#00FFFF"/> </RectangleLabels> <!-- Ellipses for knobs/dials --> <EllipseLabels name="ellipses" toName="image"> <Label value="knob" background="#00FF00"/> </EllipseLabels> <!-- Polygons for irregular shapes --> <PolygonLabels name="polygons" toName="image"> <Label value="region" background="#FFA500"/> </PolygonLabels> </View>
Web UI vs CLI configuration
You can configure the labeling interface two ways:
Option A: Web UI (Project Settings β Labeling Interface)
| Pros | Cons |
|---|---|
| Visual preview as you edit | Manual per-project setup |
| Browse Templates for quick start | Config lives in database only |
| Visual tab for no-code editing | Harder to version control |
| Immediate feedback | Can't batch-apply to new projects |
Option B: CLI/API (programmatic)
| Pros | Cons |
|---|---|
| Scriptable, repeatable | No live preview |
| Version control your configs | Requires API token setup |
| Batch-create identical projects | More initial setup |
Integrate with ui-annotate wrapper | Learning curve |
Recommendation:
- Prototyping: Use Web UI to experiment with templates
- Production: Export working config, use API to create projects programmatically
Exporting your config from Web UI
Once you've built a config you like in the Web UI:
- Go to Settings β Labeling Interface
- Copy the XML from the Code tab
- Save to a file (e.g.,
ui-elements.xml) - Use in API calls or
ui-annotatewrapper
Importing images
Option 1: Upload via UI
- Go to your project
- Click Import
- Drag and drop images or click to browse
Option 2: Local file storage
For large datasets, configure local storage:
- Go to Project Settings β Cloud Storage
- Click Add Source Storage
- Choose Local files
- Set the absolute path to your images folder
Before using local storage, set environment variables:
bashexport LABEL_STUDIO_LOCAL_FILES_SERVING_ENABLED=true export LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT=/path/to/images label-studio start
Exporting annotations
Via UI
- Go to your project
- Click Export
- Choose format:
- JSON - Full Label Studio format
- JSON-MIN - Simplified format
- COCO - For object detection
- YOLO - For YOLO models
- Pascal VOC - XML format
Via API
bash# Get your API token from Account & Settings TOKEN="your-api-token" PROJECT_ID=1 # Export as JSON curl -X GET "http://localhost:8080/api/projects/${PROJECT_ID}/export?exportType=JSON" \ -H "Authorization: Token ${TOKEN}" \ -o annotations.json
JSON export format
Exported annotations look like this:
json[ { "id": 1, "data": { "image": "/data/upload/1/image.png" }, "annotations": [ { "id": 1, "result": [ { "id": "abc123", "type": "rectanglelabels", "value": { "x": 10.5, "y": 20.3, "width": 15.2, "height": 8.7, "rectanglelabels": ["button"] }, "from_name": "rectangles", "to_name": "image" } ] } ] } ]
Note: Coordinates are percentages (0-100) of image dimensions, not pixels.
Keyboard shortcuts
| Action | Shortcut |
|---|---|
| Submit annotation | Ctrl+Enter |
| Undo | Ctrl+Z |
| Redo | Ctrl+Shift+Z |
| Delete selected | Backspace |
| Select all | Ctrl+A |
| Toggle labels panel | l |
API basics
Get API token
- Click your email (top right)
- Select Account & Settings
- Copy the Access Token
List projects
bashcurl -X GET "http://localhost:8080/api/projects/" \ -H "Authorization: Token ${TOKEN}"
Create project programmatically
bashcurl -X POST "http://localhost:8080/api/projects/" \ -H "Authorization: Token ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "title": "My Project", "label_config": "<View><Image name=\"image\" value=\"$image\"/></View>" }'
Import task (image)
bashcurl -X POST "http://localhost:8080/api/projects/1/import" \ -H "Authorization: Token ${TOKEN}" \ -H "Content-Type: application/json" \ -d '[{"data": {"image": "https://example.com/image.jpg"}}]'
Running as background service
Start detached
bashnohup label-studio start --port 8080 > ~/label-studio.log 2>&1 &
Check if running
bashcurl -s http://localhost:8080/api/health # Returns: {"status": "UP"}
Stop
bashpkill -f label-studio
Troubleshooting
Port already in use
bash# Find what's using port 8080 lsof -i :8080 # Kill it kill -9 <PID>
Database reset
bash# Delete database to start fresh (WARNING: loses all data) rm "~/Library/Application Support/label-studio/label_studio.sqlite3"
View logs
bash# If running in background with nohup tail -f ~/label-studio.log
ui-annotate CLI wrapper
A custom CLI wrapper exists at ~/Code/github.com/theslyprofessor/ui-annotate/ that simplifies common annotation workflows.
Installation
bash# Already installed and symlinked ls -la ~/.local/bin/ui-annotate
Usage
bash# Start Label Studio and open image for annotation ui-annotate ~/Downloads/screenshot.png # Export annotations for an image ui-annotate --export ~/Downloads/screenshot.png
How it works
- Server management: Starts Label Studio as detached process if not running
- Single shared project: All images go to "UI Annotations" project (not per-image projects)
- Multipart upload: Uploads images via form data (not file:// URLs)
- Task tracking: Maps image paths to task IDs in
~/.ui-annotate/projects.json
Configuration files
| File | Purpose |
|---|---|
~/.ui-annotate/api_token | Stored JWT refresh token |
~/.ui-annotate/projects.json | Image path β task ID mappings |
Simplified annotation configs
Named points only (for maps/buildings)
For annotating locations with just named points and text labels:
xml<View> <Image name="image" value="$image" zoom="true" zoomControl="true"/> <KeyPointLabels name="points" toName="image"> <Label value="location" background="#FF0000"/> </KeyPointLabels> <TextArea name="description" toName="image" editable="true" perRegion="true" placeholder="Enter location name..."/> </View>
This config:
- Allows placing red point markers
- Attaches text description to each point
- Ideal for: building maps, floor plans, landmarks
Export format for points
json{ "result": [ { "type": "keypointlabels", "value": { "x": 45.2, "y": 32.1, "keypointlabels": ["location"] } }, { "type": "textarea", "value": { "text": ["Bottom of J"] } } ] }
API authentication deep dive
Token types
Label Studio uses JWT tokens:
| Token | Purpose | Lifetime |
|---|---|---|
| Access token | API requests | Short (hours) |
| Refresh token | Get new access tokens | Long (days) |
Getting tokens
- UI method: Account & Settings β Access Token (gives refresh token)
- API method: Login returns both tokens
Refreshing access token
bashREFRESH_TOKEN="your-refresh-token" # Exchange refresh for access token curl -X POST "http://localhost:8080/api/token/refresh/" \ -H "Content-Type: application/json" \ -d "{\"refresh\": \"${REFRESH_TOKEN}\"}" # Returns: {"access": "new-access-token"}
Using access token
bashACCESS_TOKEN="your-access-token" curl -X GET "http://localhost:8080/api/projects/" \ -H "Authorization: Bearer ${ACCESS_TOKEN}"
Note: Use Bearer prefix for JWT access tokens, Token prefix for legacy API keys.
Uploading images via multipart form
The most reliable way to add local images (not file:// URLs):
bashPROJECT_ID=1 ACCESS_TOKEN="your-access-token" IMAGE_PATH="/path/to/image.png" curl -X POST "http://localhost:8080/api/projects/${PROJECT_ID}/import" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -F "file=@${IMAGE_PATH}"
This:
- Uploads the image to Label Studio's media storage
- Creates a task automatically
- Returns the task ID for tracking