Repository navigation
Software Architecture
- Robot Software: runs onboard (ROS 2 nodes, SLAM, Nav2)
- Ingestion Service: receives session data/video/map uploads from robot
- Object Storage: raw 3D maps, video, images
- Processing Service: turns raw scans into usable 3D tiles/reports
- Web / Mobile Application: client-facing teleoperation + dashboard
See External Libraries for the full list (ROS 2, Nav2, SLAM framework, COLMAP, OpenMVS, Three.js, Cesium 3D Tiles, React, React Native, PostgreSQL, Node.js).
The most up to date version of the use case diagrams can be viewed here. Please contact @RealBJr for editing access to this artifact.
Limited to use cases the actors (User / On-Site Admin) directly interact with in the RoamView application layer; backend service internals (Processing Service, Ingestion Service) excluded. See this diagram for more details. Specific requirement details are available in the Software Specification section.
Diagrams: GU = General User Operations | OA = On-Site Admin Operations | SA = System Admin
Actors: User, On-Site Admin, Admin(System Admin)
| ID | Diagram | Use Case | Actor(s) | Relationship | Related Use Case(s) | Related Card(s) |
|---|---|---|---|---|---|---|
| UC-GU01 | GU | Live Walkthrough | User, On-Site Admin, System Admins | Include | UC-GU08 | — |
| UC-GU02 | GU | Auth | User, On-Site Admin, System Admins | — | — | — |
| UC-GU03 | GU | Load publicly available Meshes | User, On-Site Admin, System Admins | — | — | — |
| UC-GU04 | GU | Load authorized private Meshes | User, On-Site Admin, System Admins | Extend | UC-GU06 | — |
| UC-GU05 | GU | Load mesh scans history | User, On-Site Admin, System Admins | Extend | UC-GU07 | — |
| UC-GU06 | GU | Compare mesh version | User, On-Site Admin, System Admins | — | — | — |
| UC-GU07 | GU | Send Video Stream | Robot | — | — | — |
| UC-GU08 | GU | Send Video Recording | Robot | — | — | — |
| UC-GU09 | GU | Compare mesh version | Robot | — | — | — |
| UC-GU10 | GU | Send 3D mesh | Robot | — | — | — |
| UC-GU11 | GU | Annotate Generated Mesh | User, On-Site Admin, System Admins | Extend | UC-GU03, UC-GU04 | — |
| UC-OA01 | OA | Schedule Inspections | On-Site Admin | Include | UC-OA02 | — |
| UC-OA02 | OA | Auth | On-Site Admin | — | — | — |
| UC-OA03 | OA | Load Generated Meshes | On-Site Admin | Include | UC-OA04 | — |
| UC-SA01 | SA | Load Stats and Historical Data | System Admins | — | — | — |
| UC-SA02 | SA | Privatize Meshes | System Admins | — | — | — |
| UC-SA03 | SA | Load Stats and Historical Data | Manage Accounts | — | — | — |
classDiagram
direction TB
class Organization {
+UUID id
+String name
}
class User {
+UUID id
+String email
+Role role
+authenticate() bool
}
class Role {
<<enumeration>>
USER
ON_SITE_ADMIN
}
class Site {
+UUID id
+String name
+String address
}
class Robot {
+UUID id
+String serialNumber
+RobotStatus status
+isOnline() bool
}
class Session {
+UUID id
+SessionMode mode
+SessionStatus status
+String videoStorageKey
+start() void
+end() void
}
class SessionMode {
<<enumeration>>
AUTONOMOUS
LIVE_WALKTHROUGH
}
class Mesh {
+UUID id
+MeshVisibility visibility
+String objectStorageKey
+String tilesetKey
+int version
+getSignedUrl() String
}
class Annotation {
+UUID id
+Geometry position
+String label
+Severity severity
}
class Measurement {
+UUID id
+float valueMeters
}
class InspectionReport {
+UUID id
+String pdfStorageKey
+generate() void
}
class ScheduledInspection {
+UUID id
+DateTime runAt
+createJob() InspectionJob
}
class InspectionJob {
+UUID id
+JobStatus status
+store() void
+notifyRobot() void
}
Organization "1" --> "*" User : employs
Organization "1" --> "*" Site : owns
Organization "1" --> "*" Robot : owns
User "1" --> "0..*" Session : operates
Site "1" --> "*" Session : hosts
Site "1" --> "0..1" Robot : assigned
Robot "1" --> "*" Session : performs
Session "1" --> "0..1" Mesh : produces
Mesh "0..1" --> "0..1" Mesh : previousVersion
Mesh "1" --> "*" Annotation : has
Mesh "1" --> "*" Measurement : has
User "1" --> "*" Annotation : authors
Session "1" --> "0..1" InspectionReport : generates
Site "1" --> "*" ScheduledInspection : schedules
Robot "1" --> "*" ScheduledInspection : target
ScheduledInspection "1" --> "0..1" InspectionJob : creates
InspectionJob "1" --> "0..1" Session : resultsIn
User ..> Role : role
Session ..> SessionMode : mode
classDiagram
direction TB
class Organization {
name
}
class User {
email
role
}
class Site {
name
address
}
class Robot {
serialNumber
status
}
class Session {
mode
status
startedAt
endedAt
}
class Mesh {
<<3D Map>>
visibility
version
objectStorageKey
}
class Annotation {
label
severity
position
}
class Measurement {
type
valueMeters
}
class InspectionReport {
title
generatedAt
}
class ScheduledInspection {
runAt
status
}
class InspectionJob {
status
}
Organization "1" --> "*" User : employs
Organization "1" --> "*" Site : owns
Organization "1" --> "*" Robot : owns
User "1" --> "0..*" Session : operates
Site "1" --> "*" Session : hosts
Site "1" --> "0..1" Robot : assigned
Robot "1" --> "*" Session : performs
Session "1" --> "0..1" Mesh : produces
Mesh "0..1" --> "0..1" Mesh : previousVersion
Mesh "1" --> "*" Annotation : has
Mesh "1" --> "*" Measurement : has
User "1" --> "*" Annotation : authors
User "1" --> "*" Measurement : authors
Session "1" --> "0..1" InspectionReport : generates
Site "1" --> "*" ScheduledInspection : schedules
Robot "1" --> "*" ScheduledInspection : target
ScheduledInspection "1" --> "0..1" InspectionJob : creates
InspectionJob "1" --> "0..1" Session : resultsIn
Organization — Tenant that owns users, sites, and robots.
User — Signs in to RoamView. Role is User or On-Site Admin.
Site — Physical location. At most one robot is assigned to a site.
Robot — On-site unit. Runs an autonomous inspection or a live walkthrough.
Session — One walkthrough. Mode is autonomous or live walkthrough. Status moves from recording through processing to ready.
Mesh — 3D map from a session. Public or private. previousVersion links maps for compare-mesh-version. File bytes are in object storage; this entity holds the key.
Annotation / Measurement — Pins and distances on a generated mesh.
Inspection report — Summary of a session. PDF is in object storage.
Scheduled inspection / Inspection job — On-site admin books a run; that creates a job; the robot is notified; a completed job becomes a session.
erDiagram
direction TB
organizations {
uuid id PK
string name
string slug
timestamp created_at
}
users {
uuid id PK
uuid org_id FK
string email
string password_hash
string role
timestamp created_at
}
sites {
uuid id PK
uuid org_id FK
string name
string address
geometry footprint
timestamp created_at
}
robots {
uuid id PK
uuid org_id FK
uuid site_id FK
string name
string serial_number
string status
int battery_pct
timestamp last_seen_at
}
sessions {
uuid id PK
uuid site_id FK
uuid robot_id FK
uuid operator_id FK
string mode
string status
string video_storage_key
timestamp started_at
timestamp ended_at
}
meshes {
uuid id PK
uuid session_id FK
uuid previous_mesh_id FK
string visibility
int version
string object_storage_key
string tileset_key
string preview_key
timestamp created_at
}
annotations {
uuid id PK
uuid mesh_id FK
uuid author_id FK
geometry position
string label
string severity
text notes
timestamp created_at
}
measurements {
uuid id PK
uuid mesh_id FK
uuid author_id FK
string type
jsonb points
float value_meters
timestamp created_at
}
reports {
uuid id PK
uuid session_id FK
uuid author_id FK
string title
string pdf_storage_key
timestamp generated_at
}
scheduled_inspections {
uuid id PK
uuid site_id FK
uuid robot_id FK
timestamp run_at
string status
}
inspection_jobs {
uuid id PK
uuid schedule_id FK
uuid session_id FK
string status
}
organizations ||--o{ users : has
organizations ||--o{ sites : owns
organizations ||--o{ robots : owns
sites ||--o| robots : assigned
users ||--o{ sessions : operates
sites ||--o{ sessions : hosts
robots ||--o{ sessions : performs
sessions ||--o| meshes : produces
sessions ||--o| reports : generates
meshes |o--o| meshes : previous_version
meshes ||--o{ annotations : has
meshes ||--o{ measurements : has
users ||--o{ annotations : authors
users ||--o{ measurements : authors
sites ||--o{ scheduled_inspections : schedules
robots ||--o{ scheduled_inspections : target
scheduled_inspections ||--o| inspection_jobs : creates
inspection_jobs ||--o| sessions : results_in
3D maps are not stored as PostgreSQL BLOBs. meshes.object_storage_key and meshes.tileset_key point at object storage. The same pattern applies to sessions.video_storage_key and reports.pdf_storage_key. The API returns a signed URL; the client streams bytes from object storage / CDN.
- PostgreSQL stores structured metadata. Large binaries (3D maps, video, tiles) are stored in object storage and referenced by key.
Crow’s-foot marks show how many rows participate in a relationship.
| Symbol | Meaning |
|---|---|
|| |
Exactly one (required) |
○| |
Zero or one (optional) |
○< |
Zero or more (optional many) |
PK is the primary key (unique id of a row). FK is a foreign key (points at another table’s PK).
RoamView keeps data in two storage systems:
| System | Technology | What it holds | Access pattern |
|---|---|---|---|
| General storage (metadata) | PostgreSQL | Structured, queryable rows: organizations, users, sites, robots, sessions, jobs, meshes, annotations, reports | Small rows, transactional, joined and filtered by the API |
| Object storage (BLOBs) | S3-compatible object store (e.g. AWS S3 / MinIO) | Large binaries: raw photo sets, depth frames, telemetry bags, video, meshes, 3D tilesets, report PDFs | Written once, read many times, streamed to clients through signed URLs / CDN |
Rule: PostgreSQL never stores file bytes. Tables store the object key (the file's path in object storage), as in the ER diagram on Software Architecture. This replaces the "BLOB storage for 3D maps" note in External Libraries.
The task card uses early names. This page uses the names from Software Architecture and the Domain Model.
| Task card term | Name in this model |
|---|---|
| Facility | sites |
| User | users |
| ScanJob | inspection_jobs |
| Tile |
meshes.tileset_key (the tileset's root file). Each tile file exists only in object storage (see 3D tiles) |
The tables from the ER diagram on Software Architecture are unchanged. One table is added, storage_objects, which lists every file in object storage. It does not replace any existing column. It sits alongside them and is matched to them by key (see section 3).
erDiagram
direction TB
organizations {
uuid id PK
string name
string slug
timestamp created_at
}
users {
uuid id PK
uuid org_id FK
string email
string password_hash
string role
timestamp created_at
}
sites {
uuid id PK
uuid org_id FK
string name
string address
geometry footprint
timestamp created_at
}
robots {
uuid id PK
uuid org_id FK
uuid site_id FK
string name
string serial_number
string status
int battery_pct
timestamp last_seen_at
}
sessions {
uuid id PK
uuid site_id FK
uuid robot_id FK
uuid operator_id FK
string mode
string status
string video_storage_key
timestamp started_at
timestamp ended_at
}
meshes {
uuid id PK
uuid session_id FK
uuid previous_mesh_id FK
string visibility
int version
string object_storage_key
string tileset_key
string preview_key
timestamp created_at
}
annotations {
uuid id PK
uuid mesh_id FK
uuid author_id FK
geometry position
string label
string severity
text notes
timestamp created_at
}
measurements {
uuid id PK
uuid mesh_id FK
uuid author_id FK
string type
jsonb points
float value_meters
timestamp created_at
}
reports {
uuid id PK
uuid session_id FK
uuid author_id FK
string title
string pdf_storage_key
timestamp generated_at
}
scheduled_inspections {
uuid id PK
uuid site_id FK
uuid robot_id FK
timestamp run_at
string status
}
inspection_jobs {
uuid id PK
uuid schedule_id FK
uuid session_id FK
string status
}
storage_objects {
uuid id PK
uuid org_id FK
uuid session_id FK "nullable"
string bucket
string object_key UK "matches the *_key columns"
string kind "RGB_SET | DEPTH_SET | TELEMETRY | SLAM_MAP | VIDEO | MESH | TILESET | PREVIEW | REPORT | INTERMEDIATE"
string content_type
bigint size_bytes
string sha256
string status "PENDING | AVAILABLE | ARCHIVED | DELETED"
timestamp expires_at "from retention policy"
timestamp created_at
}
organizations ||--o{ users : has
organizations ||--o{ sites : owns
organizations ||--o{ robots : owns
sites ||--o| robots : assigned
users ||--o{ sessions : operates
sites ||--o{ sessions : hosts
robots ||--o{ sessions : performs
sessions ||--o| meshes : produces
sessions ||--o| reports : generates
meshes |o--o| meshes : previous_version
meshes ||--o{ annotations : has
meshes ||--o{ measurements : has
users ||--o{ annotations : authors
users ||--o{ measurements : authors
sites ||--o{ scheduled_inspections : schedules
robots ||--o{ scheduled_inspections : target
scheduled_inspections ||--o| inspection_jobs : creates
inspection_jobs ||--o| sessions : results_in
organizations ||--o{ storage_objects : "owns files"
sessions |o--o{ storage_objects : "raw capture, video"
The existing *_key columns already say where each file is. storage_objects adds what those columns can't:
| Need | How storage_objects covers it |
|---|---|
| Capacity tracking |
size_bytes per file → total usage per organization, site or session with one query |
| Upload status |
status shows whether an upload is pending, finished, archived or deleted |
| Integrity |
sha256 checks that a file arrived undamaged |
| Retention and cleanup |
expires_at + status drive the lifecycle process (section 3) |
| Raw capture link | Raw photos, depth frames, telemetry and SLAM maps have no column in the existing tables. session_id + kind links them to their session |
-
UNIQUE (bucket, object_key). -
(session_id, kind)to find all files of a session. -
(status, expires_at)for the retention sweeper.
Object storage has no tables or foreign keys. Its structure comes from buckets and key prefixes (folders). The diagram shows that structure as entities. Each "attribute" is the key pattern, file format or object metadata.
| Bucket | Content | Access | Lifecycle |
|---|---|---|---|
roamview-raw |
Raw capture from the robot: RGB frames, depth frames, telemetry bag, SLAM map | Write: Ingestion Service. Read: Processing Service only | Standard tier for 30 days → cold/infrequent-access tier → delete at 365 days |
roamview-work |
Processing intermediates (COLMAP sparse model, OpenMVS dense cloud) | Processing Service only | Delete after 7 days |
roamview-assets |
Client-facing 3D data: mesh, tileset + tiles, preview image | Read by clients through signed URLs / CDN | Kept while the mesh is kept (365 days by default, see SP-GU05.03) |
roamview-media |
Session video recordings, inspection report PDFs | Read by clients through signed URLs | Video: 90 days. Reports: 365 days |
Keys use IDs, never names. Renaming a site does not move files. The orgs/{org_id}/ prefix makes per-tenant access policies and usage totals simple.
erDiagram
direction TB
BUCKET {
string name "roamview-raw | -work | -assets | -media"
string region
string encryption "SSE (AES-256)"
string lifecycle_rules
}
ORG_PREFIX {
string key "orgs/{org_id}/"
}
SITE_PREFIX {
string key "orgs/{org_id}/sites/{site_id}/"
}
SESSION_PREFIX {
string key "…/sessions/{session_id}/"
}
CAPTURE_MANIFEST {
string key "…/capture/manifest.json"
json frames "seq, file, timestamp, pose, sha256"
string meta "x-amz-meta-session-id"
}
RGB_FRAME {
string key "…/capture/rgb/{seq:06d}.jpg"
string format "JPEG 3840x2160"
float size_mb "~2.5"
}
DEPTH_FRAME {
string key "…/capture/depth/{seq:06d}.png"
string format "PNG uint16 640x480"
float size_mb "~0.3"
}
TELEMETRY_BAG {
string key "…/telemetry/session.mcap"
string format "ROS 2 MCAP (LiDAR, IMU, odom, tf)"
float size_mb "~0.05 per second"
}
SLAM_MAP {
string key "…/slam/map.pgm + map.yaml"
float size_mb "~1"
}
VIDEO_RECORDING {
string key "…/video/recording.mp4"
string format "H.264 720p30 ~2.5 Mbps"
}
WORK_PREFIX {
string key "…/sessions/{session_id}/work/"
}
SPARSE_MODEL {
string key "…/sparse/ (cameras, images, points3D)"
}
DENSE_CLOUD {
string key "…/dense/scene_dense.ply"
}
MESH_VERSION_PREFIX {
string key "…/meshes/{mesh_id}/v{version}/"
}
MESH_FILE {
string key "…/mesh.glb"
string format "glTF binary, textured"
float size_mb "~150"
}
TILESET_ROOT {
string key "…/tiles/tileset.json"
string format "3D Tiles 1.1"
}
TILE {
string key "…/tiles/{level}/{x}_{y}_{z}.glb"
string format "glTF content per LOD node"
}
PREVIEW {
string key "…/preview.webp"
}
REPORT_PDF {
string key "orgs/{org_id}/reports/{report_id}.pdf"
}
BUCKET ||--o{ ORG_PREFIX : contains
ORG_PREFIX ||--o{ SITE_PREFIX : contains
ORG_PREFIX ||--o{ REPORT_PDF : "contains (media)"
SITE_PREFIX ||--o{ SESSION_PREFIX : "contains (raw, media, work)"
SITE_PREFIX ||--o{ MESH_VERSION_PREFIX : "contains (assets)"
SESSION_PREFIX ||--o| CAPTURE_MANIFEST : "raw"
CAPTURE_MANIFEST ||--|{ RGB_FRAME : lists
CAPTURE_MANIFEST ||--o{ DEPTH_FRAME : lists
SESSION_PREFIX ||--o| TELEMETRY_BAG : "raw"
SESSION_PREFIX ||--o| SLAM_MAP : "raw"
SESSION_PREFIX ||--o| VIDEO_RECORDING : "media"
SESSION_PREFIX ||--o| WORK_PREFIX : "work"
WORK_PREFIX ||--o| SPARSE_MODEL : contains
WORK_PREFIX ||--o| DENSE_CLOUD : contains
MESH_VERSION_PREFIX ||--|| MESH_FILE : contains
MESH_VERSION_PREFIX ||--o| TILESET_ROOT : contains
TILESET_ROOT ||--|{ TILE : references
MESH_VERSION_PREFIX ||--o| PREVIEW : contains
A tileset can hold hundreds or thousands of tile files. A database row per tile would add rows without adding anything the API needs. The client only needs the root tileset.json, and Cesium follows its references to fetch tiles. For that reason:
-
meshes.tileset_keypoints at the roottileset.json, as in the existing ER diagram. -
storage_objectshas one row of kindTILESETfor that root. Itssize_bytesis the total of the wholetiles/prefix. - A signed URL (or CDN signed cookie) is issued for the
tiles/prefix so the client can fetch child tiles.
The raw photo set follows the same approach: one manifest.json row, not one row per frame.
flowchart LR
subgraph PG["PostgreSQL (metadata)"]
SES["sessions<br/>video_storage_key"]
MSH["meshes<br/>object_storage_key · tileset_key · preview_key"]
REP["reports<br/>pdf_storage_key"]
SO[("storage_objects<br/>bucket + object_key<br/>size, sha256, status")]
end
subgraph OS["Object storage"]
RAW["roamview-raw<br/>capture/manifest.json · rgb/* · depth/*<br/>telemetry/session.mcap · slam/map.*"]
WORK["roamview-work<br/>work/sparse · dense"]
AST["roamview-assets<br/>meshes/{id}/v{n}/mesh.glb · tiles/* · preview.webp"]
MED["roamview-media<br/>video/recording.mp4 · reports/{id}.pdf"]
end
SES -- "key = object_key" --> SO
MSH -- "key = object_key" --> SO
REP -- "key = object_key" --> SO
SES -- "session_id (raw capture)" --> SO
SO -. "(bucket, object_key)" .-> RAW
SO -. "(bucket, object_key)" .-> WORK
SO -. "(bucket, object_key)" .-> AST
SO -. "(bucket, object_key)" .-> MED
Links in both directions:
-
Metadata → object: a domain row's
*_keycolumn holds the object key. The same key appears instorage_objects.object_key, which adds size, checksum and status. Raw capture files are found throughstorage_objects.session_id. -
Object → metadata: every object is uploaded with the headers
x-amz-meta-storage-object-id,x-amz-meta-org-idandx-amz-meta-session-id. The key prefix also contains the org, site and session IDs. An orphaned file can therefore be traced back to its rows without a database lookup.
sequenceDiagram
autonumber
participant R as Robot (Pi 5)
participant I as Ingestion Service
participant DB as PostgreSQL
participant S3 as Object Storage
participant P as Processing Service
participant C as Web / Mobile client
R->>I: POST /sessions/{id}/uploads (kind, size, sha256)
I->>DB: INSERT storage_objects (status = PENDING)
I-->>R: pre-signed PUT URL(s)
R->>S3: PUT objects (multipart, resumable)
R->>I: POST /uploads/{object_id}/complete
I->>S3: HEAD object (check size + checksum)
I->>DB: UPDATE storage_objects SET status = AVAILABLE<br/>UPDATE sessions SET video_storage_key, status = PROCESSING
I->>P: enqueue processing for session
P->>S3: GET raw capture, PUT intermediates + mesh + tiles
P->>DB: INSERT storage_objects + meshes (object_storage_key,<br/>tileset_key, preview_key) in one transaction<br/>UPDATE sessions SET status = READY
C->>DB: GET /sites/{id}/meshes (via API, auth + visibility check)
DB-->>C: mesh metadata + short-lived signed URL
C->>S3: GET tileset.json → tiles (via CDN)
-
Upload first, then commit. A
*_keycolumn is only filled once the matchingstorage_objectsrow isAVAILABLE.PENDINGrows older than 24 h are marked failed, and their partial uploads are aborted. -
Orphan sweep (daily). Objects without an
AVAILABLE/ARCHIVEDregistry row are deleted. Registry rows whose object is missing are flagged and reported. -
Deletion is status-driven. Retention sets
expires_at. The sweeper deletes the object, then setsstatus = DELETED. The row is kept for audit and usage history. -
Privacy. Every bucket is private and encrypted at rest. Clients never get bucket credentials, only signed URLs that expire within minutes.
PUBLICmeshes skip the ownership check but still use signed URLs. See Ethics & Accountability.
All figures are estimates from stated assumptions. They should be revisited when hardware tests (Hardware Testing) produce measured values. Units: 1 GB = 1000 MB. Script used: capacity.py.
| Parameter | Value | Source / rationale |
|---|---|---|
| Autonomous inspection length | 30 min (1800 s) | ~6 rooms × T_room = 300 s (SP-GU01.03) |
| Live walkthrough length | 6 min (360 s) | T_max control lease (SP-GU01.03) |
| Photogrammetry keyframe rate | 0.5 fps → 900 frames / inspection | 1 frame every 2 s gives enough overlap for COLMAP at walking speed |
| RGB keyframe | 3840×2160 JPEG, ~2.5 MB | OAK-D Lite IMX214 colour sensor (max 4208×3120, 4K30) |
| Depth frame | 640×480 uint16 PNG, ~0.3 MB | OAK-D Lite stereo pair; 614 KB raw, ~50% lossless compression |
| Telemetry bag (MCAP, zstd) | ~0.05 MB/s | Waveshare D500: 5000 samples/s at 10 Hz → ~40 KB/s raw, plus IMU/odom/tf, ~2:1 compression |
| Video recording | 720p30 H.264, 2.5 Mbps | Pi Camera Module 3 Wide. The Pi 5 has no hardware H.264 encoder, so software encoding at 720p is the safe baseline |
| Intermediate files | ~1 GB / inspection | COLMAP sparse model + OpenMVS dense point cloud |
| Textured mesh | ~150 MB | ~1M faces + textures, .glb
|
| 3D tileset | 4/3 × mesh ≈ 200 MB | Quadtree LOD pyramid: 1 + ¼ + 1⁄16 + … = 4/3 |
| Preview / report PDF | 0.5 MB / 5 MB | — |
| Retention | Raw 365 d (hot 30 d, then cold) · intermediates 7 d · video 90 d · mesh, tiles, reports 365 d | Proposed policy. Mesh retention to be confirmed by SP-GU05.03 |
| Object class | Bucket | Autonomous (30 min) | Live walkthrough (6 min) |
|---|---|---|---|
| RGB photo set (900 frames) | raw | 2,250 MB | — |
| Depth frames (900) | raw | 270 MB | — |
| Telemetry bag | raw | 90 MB | 18 MB |
| SLAM map | raw | 1 MB | — |
| Video recording | media | 563 MB | 113 MB |
| Intermediates (temporary) | work | 1,000 MB | — |
| Mesh | assets | 150 MB | — |
| 3D tileset | assets | 200 MB | — |
| Preview + report | assets / media | 6 MB | — |
| Total | ≈ 4.5 GB | ≈ 0.13 GB |
The raw RGB photo set is ~50% of each inspection and ~70% of long-term storage. Its retention period is the main factor in the total.
| Scenario | Sites | Sessions / site / week (auto + live) | Sessions / year | Ingest / month | Hot tier | Cold tier | Total | Total + 20% headroom |
|---|---|---|---|---|---|---|---|---|
| A. Capstone demo | 1 | 2 + 3 | 104 + 156 | 40 GB | 81 GB | 252 GB | 333 GB | 400 GB |
| B. Pilot | 10 | 1 + 5 | 521 + 2,607 | 222 GB | 456 GB | 1.3 TB | 1.75 TB | 2.1 TB |
| C. Growth | 100 | 3 + 10 | 15,643 + 52,143 | 6.4 TB | 12.9 TB | 38.3 TB | 51 TB | 62 TB |
Steady state for each class = daily ingest × retention days. Hot tier = everything except raw capture older than 30 days.
Sensitivity: shortening raw retention from 365 to 90 days reduces Scenario B from 1.75 TB to ≈ 0.69 TB. Reprocessing is then only possible within 90 days.
| Item | Estimate |
|---|---|
| Rows per autonomous inspection | ~46 (1 session, 1 mesh, ~11 storage objects, ~20 annotations, ~10 measurements, 1 report, 1 schedule, 1 job) |
| Rows per live walkthrough | ~3 (1 session, 2 storage objects) |
| Average row size (with indexes) | ~300 B × 2 |
| Year-1 size | A: ~3 MB · B: ~19 MB · C: ~0.5 GB |
The metadata database is small at every scenario. One managed PostgreSQL instance with the smallest storage tier (e.g. 20 GB) and daily backups is enough. The design keeps it small because frames and tiles are not stored as rows.
| Item | Estimate |
|---|---|
| Data to upload per autonomous inspection (raw + video) | ~3.2 GB |
| Upload time at 20 Mbps effective Wi-Fi uplink | ~21 min (an input for SP-GU08.02, "Save Video Recording Job Max Time") |
| Local storage needed | 64 GB microSD/NVMe minus ~16 GB OS/ROS → buffer for ~15 inspections if Wi-Fi is down. Local copies are deleted after the upload is confirmed (status = AVAILABLE) |
The Processing Service turns the raw capture of a finished session (RGB keyframes + depth + telemetry) into a textured mesh and a Cesium 3D tileset. It writes them back to object storage and registers them in PostgreSQL (see Data Model & Storage).
flowchart LR
ING["«component»<br/>Ingestion Service"]
Q[("«component»<br/>Job Queue")]
DB[("«component»<br/>PostgreSQL")]
S3[("«component»<br/>Object Storage")]
API["«component»<br/>API Gateway"]
subgraph PS["«component» Processing Service"]
direction TB
JC["«component»<br/>Job Consumer<br/>pull · heartbeat · retry"]
ORC["«component»<br/>Pipeline Orchestrator<br/>runs stages in order,<br/>updates job status"]
subgraph STG["Pipeline Stages (IStage)"]
direction TB
PREP["«component»<br/>Capture Preparation<br/>verify manifest + sha256,<br/>select keyframes"]
SFM["«component»<br/>SfM Stage — COLMAP<br/>feature_extractor → sequential_matcher<br/>→ mapper → image_undistorter"]
MVS["«component»<br/>MVS Stage — OpenMVS<br/>InterfaceCOLMAP → DensifyPointCloud<br/>→ ReconstructMesh → RefineMesh → TextureMesh"]
EXP["«component»<br/>Export Stage<br/>mesh → mesh.glb,<br/>render preview.webp"]
TIL["«component»<br/>Tiling Stage<br/>mesh.glb → tileset.json + tiles/*.glb"]
PREP --> SFM --> MVS --> EXP --> TIL
end
SA["«component»<br/>Storage Adapter<br/>S3 download / upload"]
MR["«component»<br/>Metadata Repository<br/>sessions · meshes · storage_objects"]
WS[("«artifact»<br/>Local scratch workspace<br/>/work/{session_id}")]
JC --> ORC
ORC --> STG
STG --> WS
ORC --> SA
ORC --> MR
end
ING -- "IJobPublisher<br/>enqueue(session_id)" --> Q
Q -- "IJobSource<br/>next() / ack() / fail()" --> JC
SA -- "IObjectStore<br/>S3 API (HTTPS)" --> S3
MR -- "IMetadataRepo<br/>SQL (TLS)" --> DB
ORC -- "INotifier<br/>session READY / FAILED" --> API
| Component | Responsibility | Interfaces |
|---|---|---|
| Job Consumer | Takes one job at a time from the queue. Sends a heartbeat while the job runs. Retries failed jobs up to 3 times, then gives up | Uses IJobSource
|
| Pipeline Orchestrator | Runs the stages in order, times each stage, updates the job status and sessions.status, and cleans the scratch workspace |
Uses IStage, IObjectStore, IMetadataRepo, INotifier
|
| Capture Preparation | Downloads the capture set using manifest.json, checks each file's checksum and drops blurry or duplicate frames |
Implements IStage
|
| SfM Stage (COLMAP) | Estimates camera positions and a sparse point cloud, then produces undistorted images for OpenMVS | Implements IStage
|
| MVS Stage (OpenMVS) | Builds the dense point cloud, the mesh and the texture | Implements IStage
|
| Export Stage | Converts the textured mesh to mesh.glb (TransformScene) and renders a preview image |
Implements IStage
|
| Tiling Stage | Splits mesh.glb into a 3D Tiles 1.1 tileset with levels of detail (e.g. 3d-tiles-tools / py3dtiles) |
Implements IStage
|
| Storage Adapter | Reads from and writes to the S3 buckets and adds the object metadata headers | Provides IObjectStore
|
| Metadata Repository | Writes storage_objects rows, inserts the meshes row and updates sessions.status, all in one transaction |
Provides IMetadataRepo
|
| # | Stage | Tool / commands | Input | Output | Written to |
|---|---|---|---|---|---|
| 1 | Capture preparation | Python |
capture/manifest.json, rgb/*.jpg, depth/*.png
|
Checked frame list | Scratch |
| 2 | Sparse reconstruction (SfM) | COLMAP feature_extractor, sequential_matcher, mapper, image_undistorter
|
Keyframes |
sparse/ (cameras, images, points3D), undistorted images |
roamview-work/…/work/sparse/ |
| 3 | Dense reconstruction | OpenMVS InterfaceCOLMAP, DensifyPointCloud
|
COLMAP dense folder |
scene.mvs, scene_dense.ply
|
roamview-work/…/work/dense/ |
| 4 | Meshing + texturing | OpenMVS ReconstructMesh, RefineMesh, TextureMesh
|
scene_dense.mvs |
Textured mesh (.ply + texture) |
Scratch |
| 5 | Export | OpenMVS TransformScene + preview render |
Textured mesh |
mesh.glb, preview.webp
|
roamview-assets/…/meshes/{mesh_id}/v{n}/ |
| 6 | Tiling | 3D Tiles tool | mesh.glb |
tiles/tileset.json, tiles/{level}/*.glb
|
roamview-assets/…/meshes/{mesh_id}/v{n}/tiles/ |
| 7 | Register | Metadata Repository | Object keys + sizes |
meshes row (object_storage_key, tileset_key, preview_key), storage_objects rows, sessions.status = READY
|
PostgreSQL |
flowchart LR
subgraph EDGE["On site / user devices"]
direction TB
ROBOT["«device» Robot — Raspberry Pi 5 (4 GB)<br/>«execution env» Ubuntu + ROS 2<br/>«artifact» ROS 2 nodes (SLAM · Nav2 · camera)<br/>«artifact» Session uploader<br/>«artifact» Local buffer 64 GB"]
CLIENT["«device» Client<br/>«execution env» Web browser / iOS / Android<br/>«artifact» React app · React Native app<br/>(Three.js / Cesium viewer)"]
end
subgraph CLOUD["«cloud» RoamView cloud (VPC)"]
direction LR
subgraph APPNODE["«device» App server (CPU)"]
direction TB
GW["«container» API Gateway"]
IOT["«container» IoT Gateway"]
STR["«container» Streaming Service"]
INGC["«container» Ingestion Service (Node.js)"]
end
subgraph DATA["Data tier"]
direction TB
REDIS[("«device» Queue node<br/>«container» Redis")]
PG[("«device» Managed PostgreSQL")]
OBJ[("«device» S3-compatible object storage<br/>raw · work · assets · media")]
CDN["«device» CDN edge"]
end
GPU["«device» GPU processing node (autoscaled 0…N)<br/>«execution env» Docker + NVIDIA Container Toolkit (CUDA)<br/>«container» roamview-processor<br/>«artifact» Processing Service: COLMAP · OpenMVS · 3D Tiles tool<br/>«artifact» Scratch NVMe ≥ 100 GB"]
end
ROBOT -- "WSS / MQTT-TLS: control, telemetry, video" --> IOT
ROBOT -- "HTTPS REST: upload session" --> INGC
ROBOT -- "HTTPS pre-signed PUT" --> OBJ
CLIENT -- "HTTPS / WSS" --> GW
CLIENT -- "HTTPS signed URLs" --> CDN
CDN --> OBJ
IOT --> STR
GW --> STR
GW --> IOT
GW -- "SQL/TLS" --> PG
INGC -- "SQL/TLS" --> PG
INGC -- "enqueue" --> REDIS
REDIS -- "dequeue" --> GPU
GPU -- "S3 API / HTTPS" --> OBJ
GPU -- "SQL/TLS" --> PG
GPU -- "status callback HTTPS" --> GW
| Node | Runs | Notes |
|---|---|---|
| Robot (Raspberry Pi 5) | ROS 2 nodes, session uploader | Buffers about 15 inspections locally if Wi-Fi drops. Uploads straight to object storage using pre-signed URLs |
| Client | React web app, React Native app | Loads meshes and tiles from the CDN using signed URLs |
| App server | API Gateway, IoT Gateway, Streaming Service, Ingestion Service | CPU only. Always on |
| Queue node | Redis | Holds processing jobs between the Ingestion Service and the GPU workers |
| Managed PostgreSQL | Metadata DB | Schema in Data Model & Storage |
| Object storage + CDN | Four buckets | Private buckets. Encrypted at rest |
| GPU processing node |
roamview-processor Docker image |
Starts when jobs are waiting and shuts down when the queue is empty, so the team pays for the GPU only while it's working |
| Resource | Minimum | Recommended | Why |
|---|---|---|---|
| GPU | NVIDIA, CUDA-capable, 8 GB VRAM | 16 GB VRAM (e.g. T4 / RTX-class) | COLMAP SIFT extraction and matching run much faster on GPU. OpenMVS DensifyPointCloud has an optional CUDA build. COLMAP's own dense stereo requires CUDA, but the dense step here uses OpenMVS, so a CPU-only fallback is possible, just much slower |
| CPU | 8 vCPU | 16 vCPU | Meshing and texturing (ReconstructMesh, TextureMesh) run mostly on the CPU |
| RAM | 32 GB | 64 GB | Dense reconstruction of about 900 4K frames. Can be lowered with --resolution-level 1 (half resolution) |
| Scratch disk | 100 GB NVMe | 200 GB NVMe | Raw capture (~2.5 GB), undistorted images, depth maps, dense cloud, mesh |
| Software | Docker, NVIDIA driver + Container Toolkit, CUDA 12 | — | COLMAP, OpenMVS and the tiling tool are packaged in one image |
These are estimates for a 30-minute inspection (900 keyframes, see the capacity section of Data Model & Storage). They should be confirmed by timing one real run on the chosen GPU node.
RoamView Wiki
- Home
- 1. Introduction
- 2. Project Proposal
- 3. Backlog
- 4. Sprint Summaries
- 5. Architecture
- 6. UI Design
- 7. Testing
- 8. Defect Tracking
- 9. Quality Measurements
- 10. Ethics & Accountability