feat: use AprilTag instead of QR code
This commit is contained in:
325
design_docs/apriltag_storage_system.md
Normal file
325
design_docs/apriltag_storage_system.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# AprilTag Storage Location System Design
|
||||
|
||||
## Overview
|
||||
Implement a hierarchical storage location system with AprilTag tag36h11 generation and assignment capabilities to enable quick component location entry and filtering.
|
||||
|
||||
## Database Schema
|
||||
|
||||
### 1. Storage Locations Table
|
||||
```sql
|
||||
CREATE TABLE storage_locations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
apriltag_id INTEGER UNIQUE,
|
||||
parent_id INTEGER REFERENCES storage_locations(id),
|
||||
level INTEGER NOT NULL DEFAULT 0,
|
||||
path TEXT NOT NULL, -- Materialized path: "shelf1/drawer2/box3"
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT apriltag_id_range CHECK (apriltag_id >= 0 AND apriltag_id <= 586)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_storage_locations_parent_id ON storage_locations(parent_id);
|
||||
CREATE UNIQUE INDEX idx_storage_locations_apriltag_id ON storage_locations(apriltag_id);
|
||||
CREATE INDEX idx_storage_locations_path ON storage_locations USING gin(path gin_trgm_ops);
|
||||
CREATE UNIQUE INDEX idx_storage_locations_name_parent ON storage_locations(name, parent_id);
|
||||
```
|
||||
|
||||
### 2. Modified Components Table
|
||||
```sql
|
||||
-- Migration to add storage_location_id to components
|
||||
ALTER TABLE components
|
||||
ADD COLUMN storage_location_id INTEGER REFERENCES storage_locations(id),
|
||||
ADD COLUMN legacy_position VARCHAR(255); -- Keep old position data for migration
|
||||
|
||||
-- Move existing position data to legacy_position
|
||||
UPDATE components SET legacy_position = position;
|
||||
```
|
||||
|
||||
## AprilTag Design Strategy
|
||||
|
||||
### AprilTag tag36h11 Family
|
||||
- **Total tags available**: 587 (IDs 0-586)
|
||||
- **Format**: Pre-generated SVG files with human-readable ID labels
|
||||
- **Storage**: `/priv/static/apriltags/tag36h11_id_XXX.svg`
|
||||
- **Assignment**: One-to-one mapping between AprilTag ID and storage location
|
||||
|
||||
### AprilTag Components:
|
||||
- **AprilTag ID**: Integer (0-586) stored in database
|
||||
- **SVG File**: Pre-generated with 2-module margin and ID label
|
||||
- **Human Readable**: "ID XXX" format for easy identification
|
||||
- **Uniqueness**: Database constraint ensures no duplicate assignments
|
||||
|
||||
### Advantages over QR Codes:
|
||||
1. **Robust Detection**: AprilTags designed for reliable multi-tag detection
|
||||
2. **Standard Format**: Well-established robotics/AR standard
|
||||
3. **Better Performance**: More resistant to lighting/angle variations
|
||||
4. **Multi-tag Support**: Designed specifically for multiple tags in same view
|
||||
|
||||
## Multi-AprilTag Detection Strategy
|
||||
|
||||
### 1. Spatial Independence
|
||||
```
|
||||
Unlike QR codes, AprilTags are designed for multi-tag scenarios:
|
||||
- Each tag has unique geometric properties
|
||||
- No interference between nearby tags
|
||||
- Reliable detection at various scales and angles
|
||||
```
|
||||
|
||||
### 2. Detection Priority
|
||||
```
|
||||
Selection Priority:
|
||||
1. User selection (tap/click on detected tag)
|
||||
2. Closest tag to center of frame
|
||||
3. Largest tag in frame (closest to camera)
|
||||
4. Most recent successfully scanned tag
|
||||
```
|
||||
|
||||
### 3. Visual Feedback
|
||||
```
|
||||
Camera Overlay:
|
||||
- Draw bounding boxes around each detected AprilTag
|
||||
- Show AprilTag ID and location name
|
||||
- Highlight "best candidate" with different color
|
||||
- Allow tap-to-select for disambiguation
|
||||
```
|
||||
|
||||
## Implementation Components
|
||||
|
||||
### 1. Elixir Modules
|
||||
|
||||
#### Storage Location Schema
|
||||
```elixir
|
||||
defmodule ComponentsElixir.Inventory.StorageLocation do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "storage_locations" do
|
||||
field :name, :string
|
||||
field :description, :string
|
||||
field :apriltag_id, :integer
|
||||
field :level, :integer, virtual: true
|
||||
field :path, :string, virtual: true
|
||||
field :is_active, :boolean, default: true
|
||||
|
||||
belongs_to :parent, __MODULE__
|
||||
has_many :children, __MODULE__, foreign_key: :parent_id
|
||||
has_many :components, Component
|
||||
|
||||
timestamps()
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### AprilTag Management
|
||||
```elixir
|
||||
defmodule ComponentsElixir.AprilTag do
|
||||
def generate_apriltag_svg(apriltag_id, opts \\ []) do
|
||||
# Generate SVG with actual AprilTag pattern
|
||||
# Include human-readable ID below tag
|
||||
end
|
||||
|
||||
def get_apriltag_url(storage_location) do
|
||||
# Return URL to pre-generated SVG file
|
||||
end
|
||||
|
||||
def available_apriltag_ids() do
|
||||
# Return list of unused AprilTag IDs (0-586)
|
||||
end
|
||||
|
||||
def generate_all_apriltag_svgs() do
|
||||
# Pre-generate all 587 SVG files
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 2. Phoenix LiveView Components
|
||||
|
||||
#### AprilTag Assignment Interface
|
||||
```elixir
|
||||
defmodule ComponentsElixirWeb.StorageLocationsLive do
|
||||
use ComponentsElixirWeb, :live_view
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
socket =
|
||||
socket
|
||||
|> assign(:scanning, false)
|
||||
|> assign(:available_apriltag_ids, AprilTag.available_apriltag_ids())
|
||||
|> assign(:selected_location, nil)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def handle_event("assign_apriltag", %{"apriltag_id" => id}, socket) do
|
||||
# Assign specific AprilTag ID to storage location
|
||||
end
|
||||
|
||||
def handle_event("apriltag_detected", %{"ids" => ids}, socket) do
|
||||
# Handle multiple AprilTag detection
|
||||
parsed_tags = Enum.map(ids, &resolve_storage_location/1)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:detected_tags, parsed_tags)
|
||||
|> maybe_auto_select_location(parsed_tags)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 3. JavaScript AprilTag Detection (Future)
|
||||
|
||||
#### Camera Integration
|
||||
```javascript
|
||||
// assets/js/apriltag_scanner.js
|
||||
import { AprilTagDetector } from "apriltag-js";
|
||||
|
||||
export const AprilTagScanner = {
|
||||
mounted() {
|
||||
this.detector = new AprilTagDetector();
|
||||
this.video = this.el.querySelector('video');
|
||||
this.canvas = this.el.querySelector('canvas');
|
||||
|
||||
this.startCamera();
|
||||
this.scanLoop();
|
||||
},
|
||||
|
||||
scanLoop() {
|
||||
if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) {
|
||||
const detections = this.detector.detect(this.canvas);
|
||||
|
||||
if (detections.length > 0) {
|
||||
const apriltag_ids = detections.map(d => d.id);
|
||||
this.pushEvent("apriltag_detected", { ids: apriltag_ids });
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => this.scanLoop());
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## User Experience Flow
|
||||
|
||||
### 1. Adding Components with AprilTag Scan
|
||||
```
|
||||
1. User clicks "Add Component"
|
||||
2. Position field shows camera icon
|
||||
3. Click camera → AprilTag scanner opens
|
||||
4. Scan storage location AprilTag
|
||||
5. If multiple tags detected:
|
||||
- Show overlay with detected locations
|
||||
- User taps to select specific location
|
||||
6. Location path auto-filled: "Shelf A → Drawer 2 → Box 5"
|
||||
7. Component saved with storage_location_id
|
||||
```
|
||||
|
||||
### 2. Storage Location Management
|
||||
```
|
||||
1. New "Storage Locations" section in admin
|
||||
2. Add/edit locations with AprilTag ID selection
|
||||
3. Auto-assign next available ID or manual selection
|
||||
4. Download AprilTag SVG with location info
|
||||
5. Print labels for physical attachment
|
||||
```
|
||||
|
||||
### 3. AprilTag Assignment
|
||||
```
|
||||
1. Available AprilTag IDs shown in dropdown (0-586)
|
||||
2. Current assignments displayed
|
||||
3. Bulk assignment for initial setup
|
||||
4. Re-assignment with validation
|
||||
5. Conflict prevention (one tag per location)
|
||||
```
|
||||
|
||||
## Handling Multiple AprilTags in Same Image
|
||||
|
||||
### Strategy 1: Designed for Multi-Tag
|
||||
- AprilTags inherently support multiple detection
|
||||
- No spatial interference between tags
|
||||
- Each tag independently detectable
|
||||
|
||||
### Strategy 2: User Selection
|
||||
- Display all detected tags with IDs
|
||||
- Show corresponding location names
|
||||
- Allow tap/click to select intended tag
|
||||
- Remember user preferences
|
||||
|
||||
### Strategy 3: Smart Defaults
|
||||
- Prioritize by distance to frame center
|
||||
- Consider tag size (closer = larger)
|
||||
- Use most recently accessed locations
|
||||
- Provide manual override always
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: Database Migration
|
||||
1. Run migration to add apriltag_id field
|
||||
2. Remove qr_code field and constraints
|
||||
3. Update schema and validation rules
|
||||
|
||||
### Phase 2: AprilTag Generation
|
||||
1. Generate all 587 SVG files using Mix task
|
||||
2. Serve static files via Phoenix
|
||||
3. Update UI to show AprilTag instead of QR code
|
||||
|
||||
### Phase 3: Assignment Interface
|
||||
1. Add AprilTag selection to location forms
|
||||
2. Implement auto-assignment logic
|
||||
3. Create bulk assignment tools
|
||||
4. Add conflict detection
|
||||
|
||||
### Phase 4: Detection (Future)
|
||||
1. Integrate AprilTag detection library
|
||||
2. Implement multi-tag camera interface
|
||||
3. Add disambiguation UI
|
||||
4. Test detection robustness
|
||||
|
||||
## Technical Dependencies
|
||||
|
||||
### Elixir Dependencies
|
||||
```elixir
|
||||
# mix.exs - no additional dependencies needed for generation
|
||||
# Future detection would require:
|
||||
# {:apriltag, "~> 0.1"} # hypothetical
|
||||
```
|
||||
|
||||
### JavaScript Dependencies (Future Detection)
|
||||
```javascript
|
||||
// package.json
|
||||
{
|
||||
"apriltag-js": "^1.0.0", // hypothetical
|
||||
"camera-utils": "^2.0.0" // camera access utilities
|
||||
}
|
||||
```
|
||||
|
||||
## Database Indexes for Performance
|
||||
```sql
|
||||
-- Fast location lookups
|
||||
CREATE INDEX idx_components_storage_location_id ON components(storage_location_id);
|
||||
|
||||
-- Hierarchical queries
|
||||
CREATE INDEX idx_storage_locations_path_gin ON storage_locations USING gin(path gin_trgm_ops);
|
||||
|
||||
-- AprilTag uniqueness and fast lookup
|
||||
CREATE UNIQUE INDEX idx_storage_locations_apriltag_id ON storage_locations(apriltag_id);
|
||||
|
||||
-- Valid AprilTag ID range constraint
|
||||
ALTER TABLE storage_locations ADD CONSTRAINT apriltag_id_range
|
||||
CHECK (apriltag_id >= 0 AND apriltag_id <= 586);
|
||||
```
|
||||
|
||||
## Benefits Over QR Code System
|
||||
|
||||
1. **Robust Multi-Tag Detection**: AprilTags designed specifically for multiple tags in same view
|
||||
2. **Better Performance**: More reliable detection under various lighting and angle conditions
|
||||
3. **Industry Standard**: Widely used in robotics and AR applications
|
||||
4. **No Encoding Needed**: Simple integer ID mapping instead of complex string encoding
|
||||
5. **Collision Avoidance**: 587 unique IDs provide ample space without conflicts
|
||||
6. **Future-Proof**: Ready for advanced detection and tracking features
|
||||
|
||||
This design provides a robust foundation for AprilTag-based storage management while leveraging the inherent advantages of AprilTags for multi-tag detection scenarios.
|
||||
@@ -361,13 +361,7 @@ export const QRScanner = {
|
||||
```
|
||||
|
||||
### JavaScript Dependencies
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"jsqr": "^1.4.0",
|
||||
"qr-scanner": "^1.4.2"
|
||||
}
|
||||
```
|
||||
???
|
||||
|
||||
## Database Indexes for Performance
|
||||
```sql
|
||||
|
||||
Reference in New Issue
Block a user