System designTroubleshooting

Vision offsets: pose_trans vs pose_add and the array index

Why a triggered vision offset moves the robot wrong: the count element in the socket result, pose_trans vs pose_add, and a worked example.

LumaSpec Vision10 min read Share on LinkedIn

On this page 9 sections
  1. 01Read the miss: which bug you have
  2. 02Bug 1: element 0 is the read count
  3. 03Test 1: log the raw list before any motion
  4. 04Bug 2: pose_add sums numbers, pose_trans applies the frame
    1. Worked example: a 90 degree start, a 76 mm miss
  5. 05Fix: the corrected offset pick, step by step
  6. 06Units: micrometres, millimetres and pixels
  7. 07When it is neither bug
  8. 08Cameras and readers that feed the offsets
  9. 09Questions

A look-down camera triggers on the part, reports a dx and a dy, and the robot moves somewhere else. The message parses, the pose builds, the motion runs, and the tool still misses. Almost every such program carries one or both of two specific bugs: the socket result is indexed one slot early, so the count element is consumed as a coordinate, and the offset is composed with pose_add instead of pose_trans, so a delta measured in the part frame gets added along the base axes. You will learn to name the bug from the miss alone, fix the index and the composition in the controller's script language, and verify the corrected target on paper before the first deep move. The same rules hold for a smart camera or a fixed code reader feeding offsets over TCP.

Read the miss: which bug you have

The miss itself tells you which bug you have before you open the program.

A long move or a limit fault right after the read. You requested two floats and consumed elements 0 and 1. Element 0 is the count, the number 2 for a two-value read, and treated as dx in metres it commands a 2 m move along base x. If a limit check stops it, that fault is the better outcome.

The right distance, the wrong direction. Both moves cover about the same length, but the tool walks the offset along base x and y instead of following the rotated part. That is pose_add: it adds the offset's numbers as written, in base axes, and never applies the start pose's rotation.

A runtime error on element 2 of your own two-element list. The socket list holds three elements for two values, but a list you built yourself holds two and indexes from 0. A line like my_offset[2] reads past the end.

Symptom Bug Quick test
Long move or fault immediately after the read Count element used as a coordinate Log the raw result list (Test 1 below)
Right distance, wrong direction on rotated parts pose_add used for a same-frame offset Compose with pose_trans and re-run
Error touching element 2 of a two-element list Self-built list indexed like the socket list Index your own lists from 0

Bug 1: element 0 is the read count

socket_read_ascii_float(number, socket_name="socket_0") reads ASCII floats from a TCP connection, up to 30 values in one call, and returns a list that begins with the number of values it actually read, as the controller's script manual documents for this function. Ask for two values and the list has three elements: [count, dx, dy]. The deltas live at elements 1 and 2.

The camera side must send plain numbers separated by commas and wrapped in parentheses, the format the controller vendor's socket client tutorial walks through. A message of (50000, 20000), dx and dy in micrometres, arrives as the list [2.0, 50000.0, 20000.0].

# result: list of floats; "camera_result_socket" is the connection
# name your program opened earlier — rename it to yours
result = socket_read_ascii_float(2, "camera_result_socket")
# result = [2.0, 50000.0, 20000.0]: element 0 is the read count
if result[0] == 2:
  dx_m = result[1] / 1000000   # micrometres to metres: 50000 becomes 0.05
  dy_m = result[2] / 1000000   # 20000 becomes 0.02
end

The gate on element 0 is the whole fix for bug 1. If the camera sends one value instead of two, element 0 reads 1, dy never arrives, and the program skips the motion instead of guessing.

Test 1: log the raw list before any motion

Place a part off-centre by a known amount, say 50 mm along the camera's x, and watch the raw list in the controller's variable view before any motion is allowed. You expect the count 2, then a value near 50000, then your dy. If element 0 is 1, the camera sent a single value and every index after it shifts. If coordinate-sized numbers sit where the count should be, the camera prefixes its own header and the mapping moves again: log one read and settle it on paper first.

Bug 2: pose_add sums numbers, pose_trans applies the frame

pose_trans(p_from, p_to) composes two poses: p_to is applied inside p_from's coordinate frame. The result's translation is t_from + R_from·t_to and the rotations multiply. pose_add(p_from, p_from_to) instead adds the two translation triples element by element and adds the rotation-vector elements as plain numbers — a vector sum, not a frame transformation.

A vision delta is measured in a frame: your calibration maps the camera's image axes into the same frame the start pose lives in. pose_trans(startposition, offset) means "walk from startposition along its own axes by dx and dy". pose_add cannot express that. It reads the offset's numbers as base-axis metres and leaves the start rotation out of the translation entirely.

pose_trans: t_new = t_start + R_start · t_offset (offset applied in the start frame) pose_add: t_new = t_start + t_offset (offset added as written, in base axes) size check: |R_start · t_offset| = |t_offset| (a frame changes direction, not length)

This is why the bug hides during commissioning. With an unrotated start pose the two translations are identical, so every dry test passes until the first rotated part or rotated fixture arrives. An offset carrying its own rotation is worse still under pose_add: summed rotation vectors match a true composition only when both rotations share one axis.

Worked example: a 90 degree start, a 76 mm miss

startposition = p[0.400, 0.000, 0.200, 0.000, 0.000, 1.5708]: the tool at (0.400, 0.000, 0.200) m, frame rotated 1.5708 rad, or 90 degrees, about z. The camera reports the part 50 mm along its x and 20 mm along its y, so offset = p[0.050, 0.020, 0.000, 0.000, 0.000, 0.000].

pose_trans(startposition, offset): a +90 degree rotation about z maps the start frame's x onto base +y and the start frame's y onto base −x, so (0.050, 0.020, 0.000) becomes (−0.020, 0.050, 0.000) in base coordinates. The composed target is (0.400 − 0.020, 0.000 + 0.050, 0.200) = (0.380, 0.050, 0.200), rotation still 90 degrees.

pose_add(startposition, offset) returns (0.400 + 0.050, 0.000 + 0.020, 0.200) = (0.450, 0.020, 0.200).

Both moves travel the same length, sqrt(0.050² + 0.020²) = sqrt(0.0029) ≈ 0.054 m, because a frame change moves direction, not length. The endpoints differ: sqrt((0.450 − 0.380)² + (0.020 − 0.050)²) = sqrt(0.0049 + 0.0009) = sqrt(0.0058) ≈ 0.076 m. The wrong composition lands the tool 76 mm from the correct target, along an axis the camera never measured.

Fix: the corrected offset pick, step by step

  1. Open the camera's sockets and fire the trigger with your camera's own functions; the TCP trigger and result read covers the messaging half.
  2. Read with the exact value count: result = socket_read_ascii_float(2, "camera_result_socket").
  3. Gate on element 0 equalling 2 and skip the motion otherwise.
  4. Convert units once: dx_m = result[1] / 1000000 and dy_m = result[2] / 1000000 for a micrometre camera.
  5. Build the offset in the camera's frame: offset = p[dx_m, dy_m, 0.0, 0.0, 0.0, 0.0].
  6. Compose, never add: target = pose_trans(startposition, offset).
  7. Dry-run at height and reduced speed, then move to depth.
# startposition: the taught pick pose; dx_m, dy_m: floats in metres
result = socket_read_ascii_float(2, "camera_result_socket")
if result[0] == 2:
  dx_m = result[1] / 1000000
  dy_m = result[2] / 1000000
  offset = p[dx_m, dy_m, 0.0, 0.0, 0.0, 0.0]
  target = pose_trans(startposition, offset)
  lifted = pose_trans(target, p[0.0, 0.0, 0.050, 0.0, 0.0, 0.0])
  MoveL lifted
  MoveL target
end

The lifted waypoint reuses the same composition: 0.050 m up along the target frame's z, which stays vertical for a z-rotated frame. Before the next production run, confirm all five: count gated, units divided once, offset composed with pose_trans, dry-run at height passed, paper mark within a millimetre or two of expected.

Units: micrometres, millimetres and pixels

Script poses are metres, so divide once at the read: micrometres by 1000000, millimetres by 1000. If the camera reports pixel deltas instead, convert through the field of view:

mm_per_px = FOV_width_mm / image_width_px dx_mm = dx_px × mm_per_px

For a 2560-pixel-wide image over a 128 mm field of view, one pixel is 128 / 2560 = 0.05 mm, so a 240-pixel offset is 240 × 0.05 = 12 mm. The field of view follows from focal length, sensor and working distance; the calculator below moves between them and its button opens the lens finder at the result.

Lens focal length, field of view and working distance
Units
Solve for
Focal length
– mm
Field of view
– mm
Working distance
– mm
Magnification
– x
Angle of view
– °
Pixel on the part
– mm

Thin-lens model: m = sensor width / field of view, f = WD × m / (1 + m). Accurate to a few percent above about 150 mm; check the lens's minimum object distance.

Find lenses with this focal length Open the full calculator

Pixel results carry one trap of their own: the conversion is exact only at the calibrated working distance. Move the camera about 20 percent farther away and the same pixel count means about 20 percent more millimetres.

When it is neither bug

If the gate, the units and the composition are all right and the tool still misses, work down this list.

The camera frame is not the start frame. A camera bolted at 90 degrees to the tool, or a calibration taught into another frame, measures axes the start pose does not share. Re-teach the calibration so image x and y land on the start frame's x and y, or remap the delta once before composing. The tool-frame offsets for a gripper-mounted camera cover the eye-in-hand version.

Mirrored y. Image y often grows downward while the robot's y grows away from you. If only the dy term comes out reversed, negate it once in the mapping, never at each use.

Rotation drift, not offset math. A miss that grows with part rotation instead of with the offset size belongs to the locate step. The vision locate that survives rotation set-up and the position drift diagnosis separate the two.

Height changed between teach and run. Offsets are two-dimensional; a taller part shifts the pixel scale and surfaces as x-y error. Re-teach at the running height.

For the surrounding plumbing, see sending camera offsets over a socket, moving relative to a detected part frame and camera offsets with unit conversion; the systems topic page collects the rest.

Cameras and readers that feed the offsets

The pattern needs a camera that hands over two or three ASCII numbers on request. Both families in the shop do this job: smart cameras locate features and parts, and fixed code readers suit the case where the offset is the x-y position of a code on the part. Filter both by interface, shutter and mount in the camera finder, and for the straight-down pick version of this flow the socket handoff to the robot shows the read-and-compose sequence end to end.

Smart cameras & vision sensorsHikrobot MV-SC3050XC-16M-ATN-H 5 MP color vision sensorMV-SC3050XC-16M-ATN-H
Interface
Fast Ethernet
Resolution
5 MP
Sensor format
1/1.45"
Shutter
Global
$1,809.00View part

The MV-SC3050XC-16M-ATN-H is a 5 MP color vision sensor with a global shutter, a 1/1.45" sensor and an M12 mount on Fast Ethernet, with the resolution headroom to keep the millimetre-per-pixel scale fine across a wide field.

Fixed code readersHikrobot MV-ID3013PM-12M-WBN 12mm focal length 1.3 MP Compact Industrial Code ReaderMV-ID3013PM-12M-WBN
Interface
Fast Ethernet
Resolution
Up to 3 MP
Shutter
Global
Lens mount
M12
$1,979.00View part

When the offset is a code's position, the MV-ID3013PM-12M-WBN compact industrial code reader pairs a 12 mm focal length with 1.3 MP resolution, so the read and its x-y position arrive in one message.

Fixed code readersHikrobot MV-ID813M-05S-WBN-SR 1.3 MP resolution, 4.9 mm focal length, White light, SR, GigE, Static cableMV-ID813M-05S-WBN-SR
Interface
GigE
Resolution
Up to 3 MP
Lens mount
M10
$719.00View part

For a wider field at the same working distance, the MV-ID813M-05S-WBN-SR drops to a 4.9 mm focal length at 1.3 MP, on GigE with an M10 mount and built-in white light.

Pick the family by how the part presents itself, wire the trigger, and re-run Test 1 with the new message before the first deep move. The configurator walks camera, lens and trigger in order. If a raw line of your camera's output resists the parser, paste it on the /contact/ page and we will help you map it.

Questions

Why does socket_read_ascii_float return three values when I ask for two?

Element 0 is the number of values the function actually read, not a coordinate. A two-value request therefore returns a three-element list: the count first, then dx, then dy. Gate the motion on element 0 equalling 2, then read the deltas from elements 1 and 2. If a camera update changes the message to three floats, the same gate catches it before the arm moves: element 0 reads 3 and your dy sits one slot later.

When is pose_add actually the right choice?

When the offset is genuinely a base-frame nudge and the start pose carries no rotation, pose_add and pose_trans give the same translation, so it is harmless there. It also fits moves where you want a translation added and orientation left alone. The moment the start pose or the part is rotated, its element-wise sum corresponds to no physical frame, and pose_trans is the only composition that follows the part.

The camera also sends a rotation offset: does the pose math change?

Put it in the offset pose as rz in radians and let pose_trans carry it: the composed rotation becomes the start rotation applied to the offset rotation, which is what a rotated part needs. pose_add sums the rotation-vector elements as plain numbers, which matches a true composition only when both rotations share one axis; a tilt or roll inside the offset twists the target in a direction no part ever rotated.

What changes if the camera sends more than two values?

Only the indices move. Ask for the exact value count in the read call, gate on element 0, and take your values from element 1 onward; one call reads at most 30 values, which bounds the list. One trap to check: if the camera prefixes its message with a count of its own, you get two leading numbers, so log one raw read and agree on which leading number is which before wiring the indices.

Does this change for a camera mounted on the gripper?

The parsing is identical, but the delta is then measured in the camera or tool frame, so compose into that frame rather than into a base-aligned start pose. Calibrate the camera to the tool frame once; the same pose_trans composition then holds for every pick. Re-teach that calibration after any camera, tool or TCP change, because the frame the offsets live in moves with the hardware.