Skip to content

Marker Details

Learn how to work with marker details - the measurement and positioning metrics calculated from marker corner points.

Marker Details is a one-to-one entity that stores calculated spatial measurements for markers. While markers store the raw 3D corner points, marker details provide:

  • Center Location: Average position in longitudinal (Z) and cross (X) directions
  • Distance Metrics: Distances to reference planes/axes
  • Size Measurements: Physical dimensions (width and length)
  • Custom Properties: Additional measurement metadata
interface MarkerDetails {
marker_id: UUID
center_location_long: number // Center Z coordinate
center_location_cross: number // Center X coordinate
x_negative: number // Left distance (signed)
x_positive: number // Right distance (signed)
z_negative: number // Near distance
z_positive: number // Far distance
long_size: number // Length along Z axis
cross_size: number // Width along X axis
custom_props: object // Custom measurements
created_at: timestamp
updated_at: timestamp
}

All metrics are calculated from the four marker corner points: p1, p2, p3, p4.

The average Z coordinate of all four corners.

Formula:

center_location_long = (p1[2] + p2[2] + p3[2] + p4[2]) / 4

Example:

const p1 = [-0.230, 0.060, 0.030]
const p2 = [-0.165, 0.057, 0.037]
const p3 = [-0.167, 0.050, 0.262]
const p4 = [-0.232, 0.053, 0.255]
const centerZ = (0.030 + 0.037 + 0.262 + 0.255) / 4
// Result: 0.146 meters

Purpose: Indicates the marker’s position along the longitudinal axis (forward/backward direction).


The average X coordinate of all four corners.

Formula:

center_location_cross = (p1[0] + p2[0] + p3[0] + p4[0]) / 4

Example:

const centerX = (-0.230 + -0.165 + -0.167 + -0.232) / 4
// Result: -0.198 meters

Purpose: Indicates the marker’s position along the cross axis (left/right direction).


The X coordinate that is closest to zero (preserving sign), indicating which side of the centerline the marker is on.

Formula:

// Find X with minimum absolute value, keep sign
x_negative = X_value where |X_value| = min(|p1[0]|, |p2[0]|, |p3[0]|, |p4[0]|)
// For x_positive, you could use max, or store both extremes

Example:

const xValues = [-0.230, -0.165, -0.167, -0.232]
const absValues = [0.230, 0.165, 0.167, 0.232]
const minIndex = absValues.indexOf(Math.min(...absValues)) // index 1
const xNegative = xValues[minIndex] // -0.165

Interpretation:

  • Negative value: Marker is on the left side
  • Positive value: Marker is on the right side
  • Magnitude: Distance from centerline (X=0)

The minimum and maximum Z coordinates, indicating the span along the longitudinal axis.

Formula:

z_negative = min(p1[2], p2[2], p3[2], p4[2])
z_positive = max(p1[2], p2[2], p3[2], p4[2])

Example:

const zValues = [0.030, 0.037, 0.262, 0.255]
const zNegative = Math.min(...zValues) // 0.030
const zPositive = Math.max(...zValues) // 0.262

Purpose:

  • z_negative: Closest edge to the starting point (Z=0)
  • z_positive: Farthest edge from the starting point

The extent of the marker along the Z axis (longitudinal direction).

Formula:

long_size = z_positive - z_negative

Example:

const longSize = 0.262 - 0.030
// Result: 0.232 meters (23.2 cm)

Purpose: Physical length of the marker along the movement direction.


The extent of the marker along the X axis (cross direction).

Formula:

cross_size = max(p1[0], p2[0], p3[0], p4[0]) - min(p1[0], p2[0], p3[0], p4[0])

Example:

const xValues = [-0.230, -0.165, -0.167, -0.232]
const crossSize = Math.max(...xValues) - Math.min(...xValues)
// Result: -0.165 - (-0.232) = 0.067 meters (6.7 cm)

Purpose: Physical width of the marker perpendicular to movement direction.


Understanding the coordinate system is essential for interpreting metrics:

+Y (Up)
└─────────── +X (Right)
╱ +Z (Forward)

Axis Directions:

  • X-axis: Cross direction (perpendicular to movement)

    • Negative: Left side
    • Positive: Right side
    • Zero: Centerline
  • Y-axis: Vertical direction (height)

    • Zero: Reference plane (e.g., floor)
    • Positive: Upward
  • Z-axis: Longitudinal direction (along movement)

    • Zero: Starting edge/origin
    • Positive: Forward direction
Terminal window
curl -X PUT http://localhost:8080/api/v1/markers/{marker_id}/details \
-H "Content-Type: application/json" \
-d '{
"center_location_long": 0.146,
"center_location_cross": -0.198,
"x_negative": -0.232,
"x_positive": -0.165,
"z_negative": 0.030,
"z_positive": 0.262,
"long_size": 0.232,
"cross_size": 0.067,
"custom_props": {
"measurement_method": "automatic",
"confidence": 0.95
}
}'
Terminal window
# Calculate for single marker
curl -X POST http://localhost:8080/api/v1/markers/{marker_id}/details/calculate
# Calculate for all markers in a session
curl -X POST http://localhost:8080/api/v1/work-sessions/{session_id}/markers/calculate-details
struct MarkerDetailsCalculator {
static func calculate(from marker: Marker) -> UpsertMarkerDetails {
let points = [marker.p1, marker.p2, marker.p3, marker.p4]
// Extract coordinates
let xs = points.map { $0[0] }
let ys = points.map { $0[1] }
let zs = points.map { $0[2] }
// Calculate center
let centerX = Float(xs.reduce(0, +) / 4.0)
let centerZ = Float(zs.reduce(0, +) / 4.0)
// Find X closest to zero (signed)
let xAbs = xs.map { abs($0) }
let minIndex = xAbs.enumerated().min(by: { $0.element < $1.element })!.offset
let xNegative = Float(xs[minIndex])
// Z range
let zNegative = Float(zs.min()!)
let zPositive = Float(zs.max()!)
// Sizes
let longSize = zPositive - zNegative
let crossSize = Float(xs.max()! - xs.min()!)
return UpsertMarkerDetails(
centerLocationLong: centerZ,
centerLocationCross: centerX,
xNegative: xNegative,
xPositive: Float(xs.max()!), // or calculate differently
zNegative: zNegative,
zPositive: zPositive,
longSize: longSize,
crossSize: crossSize,
customProps: [:]
)
}
}

When a space has a calibration vector, markers have both raw and calibrated coordinates. Marker details can be calculated from either:

Calculated from p1, p2, p3, p4 (original AR coordinates)

Calculated from calibrated_data.p1, p2, p3, p4 (transformed coordinates)

Important:

  • Center locations will differ between raw and calibrated (translation applied)
  • Sizes should remain identical (only translation, no rotation/scaling)

Detecting Calibration:

func calibratedDiffers(raw: Double, calibrated: Double) -> Bool {
return abs(calibrated - raw) > 1e-6
}
// Check if center Z differs
let centerZDiffers = calibratedDiffers(
raw: rawCenterZ,
calibrated: calibratedCenterZ
)

Store additional measurement data in custom_props:

{
"measurement_method": "automatic",
"confidence": 0.95,
"inspector": "John Doe",
"notes": "Measured during routine inspection",
"quality_score": 4.5,
"verification_required": false,
"tags": ["verified", "high-accuracy"],
"environmental_conditions": {
"lighting": "good",
"temperature_celsius": 22,
"humidity_percent": 45
}
}
-- Find markers within 0.1m of centerline
SELECT m.*, md.x_negative
FROM markers m
JOIN marker_details md ON m.id = md.marker_id
WHERE ABS(md.x_negative) < 0.1
ORDER BY ABS(md.x_negative);
-- Sort markers by longitudinal position
SELECT m.*, md.center_location_long
FROM markers m
JOIN marker_details md ON m.id = md.marker_id
ORDER BY md.center_location_long;
-- Find large markers (>0.2m in either dimension)
SELECT m.*, md.long_size, md.cross_size
FROM markers m
JOIN marker_details md ON m.id = md.marker_id
WHERE md.long_size > 0.2 OR md.cross_size > 0.2;
func validateMarkerDetails(_ details: MarkerDetails) -> [String] {
var issues: [String] = []
// Check if size is too small
if details.longSize < 0.01 {
issues.append("Length too small: \(details.longSize)m")
}
if details.crossSize < 0.01 {
issues.append("Width too small: \(details.crossSize)m")
}
// Check if position is within expected bounds
if abs(details.centerLocationCross) > 2.0 {
issues.append("Center too far from centerline")
}
return issues
}
struct MarkerDetailsView: View {
let details: MarkerDetails
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Section("Position") {
HStack {
Text("Longitudinal:")
Spacer()
Text("\(details.centerLocationLong, specifier: "%.3f") m")
.monospaced()
}
HStack {
Text("Cross:")
Spacer()
Text("\(details.centerLocationCross, specifier: "%.3f") m")
.monospaced()
}
}
Section("Dimensions") {
HStack {
Text("Length:")
Spacer()
Text("\(details.longSize, specifier: "%.3f") m")
.monospaced()
}
HStack {
Text("Width:")
Spacer()
Text("\(details.crossSize, specifier: "%.3f") m")
.monospaced()
}
}
Section("Distance Metrics") {
HStack {
Text("Z Near:")
Spacer()
Text("\(details.zNegative, specifier: "%.3f") m")
.monospaced()
}
HStack {
Text("Z Far:")
Spacer()
Text("\(details.zPositive, specifier: "%.3f") m")
.monospaced()
}
}
}
.padding()
}
}
Terminal window
# When completing a session, calculate all details at once
curl -X POST http://localhost:8080/api/v1/work-sessions/{session_id}/markers/calculate-details
func createMarkerWithDetails(points: [[Double]], label: String) async throws {
// Create marker
let marker = try await MarkerService.shared.createMarker(
CreateMarker(
workSessionId: sessionId,
label: label,
p1: points[0],
p2: points[1],
p3: points[2],
p4: points[3],
color: "#FF0000"
)
)
// Auto-calculate details
try await MarkerDetailsService.shared.calculateDetails(markerId: marker.id)
}
let details = UpsertMarkerDetails(
centerLocationLong: 1.5,
centerLocationCross: -0.2,
xNegative: -0.25,
xPositive: -0.15,
zNegative: 1.4,
zPositive: 1.6,
longSize: 0.2,
crossSize: 0.1,
customProps: [
"calculated_at": AnyCodable(Date()),
"ar_confidence": AnyCodable(0.95),
"manual_verification": AnyCodable(false)
]
)
func compareMetrics(marker: Marker) {
let rawDetails = MarkerDetailsCalculator.calculate(from: marker)
if let calibrated = marker.calibratedData {
let calibratedMarker = Marker(
/* copy marker with calibrated points */
)
let calibratedDetails = MarkerDetailsCalculator.calculate(from: calibratedMarker)
print("Center Z difference: \(calibratedDetails.centerLocationLong - rawDetails.centerLocationLong)")
print("Size difference: \(calibratedDetails.longSize - rawDetails.longSize)") // Should be ~0
}
}

Problem: Width or length differs between raw and calibrated

Cause: Calibration included rotation or scaling (unexpected)

Solution: Review space calibration vector - should only be translation [x, y, z]


Problem: Center coordinates seem incorrect

Cause: One or more corner points are outliers

Solution: Inspect individual point coordinates, re-capture marker if needed


Problem: Negative sizes or extreme values

Cause: Invalid point order or coordinate system mismatch

Solution: Validate point ordering, ensure consistent coordinate system