> For the complete documentation index, see [llms.txt](https://docs.hello-robot.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hello-robot.com/stretch4_docs/working-with-stretch/sensor_basics/accessing-the-line-sensors.md).

# Accessing the Line Sensors

## Line Sensors

The Stretch 4 base has six downward-facing Pixart line sensors arranged in a ring. They run at about 30 Hz and are used mainly for:

* Cliff and drop-off detection
* Low obstacle detection near the floor (thresholds, cables, bumps)

This page covers visualization, calibration, and direct Python use with `stretch4_body`. ROS 2 integration will be added later.

## Visualize with `stretch_line_sensor_viz_3d`

The 3D viewer is the fastest way to inspect raw data, calibration quality, cliffs, and obstacles. It opens an Open3D window with live sensor points around the base.

```
stretch_line_sensor_viz_3d
```

<figure><img src="/files/IBd5QZeZoB5HlhMGHgNF" alt=""><figcaption></figcaption></figure>

### Command options

<table data-search="false"><thead><tr><th>Flag</th><th>Description</th></tr></thead><tbody><tr><td><code>-a</code>, <code>--annotations</code></td><td>Show annotations (grid, labels)</td></tr><tr><td><code>-s</code>, <code>--sensors</code></td><td>List of sensors to show (e.g. <code>sensor_0 sensor_1</code>)</td></tr><tr><td><code>--cluster</code></td><td>Enable spatial clustering</td></tr><tr><td><code>--cost_map</code></td><td>Enable cost map visualization</td></tr><tr><td><code>--no_calib</code></td><td>Disable calibration (show raw data)</td></tr><tr><td><code>--nice_viz</code></td><td>Photo-studio background, no grid</td></tr><tr><td><code>--turntable</code></td><td>Slowly spin the robot around its Z axis</td></tr><tr><td><code>--odom</code></td><td>Shift the grid to simulate driving based on base odometry (requires <code>--nice_viz</code>)</td></tr></tbody></table>

## Calibrate on a flat floor

Calibration computes a per-beam tare offset so the projected floor sits at `z = 0` in the robot frame. This is required for reliable cliff and obstacle thresholds.

### Before you start

* Place the robot on a flat, hard floor.
* Keep the robot stationary.
* Remove objects from under the sensor ring.
* Avoid glossy or heavily uneven surfaces for the first calibration.

<figure><img src="/files/PjoXzJoooLpj6EBag5IR" alt=""><figcaption></figcaption></figure>

### Run calibration

Calibrate all six sensors:

```
REx_line_sensor_calibrate --all
```

Calibrate one sensor:

```
REx_line_sensor_calibrate -s sensor_0
```

The tool will:

* Record about 500 frames per sensor
* Compute per-beam median offsets against ideal flat-floor geometry
* Save `calibration_tare.yaml` under the fleet calibration directory

### Verify calibration

Re-open the 3D viewer and confirm the lines appears flat and centered around zero height like in the figure below (the robot should on a flat surface):

```
stretch_line_sensor_viz_3d -a
```

<figure><img src="/files/N6HSohwOXdivlYX5sUes" alt=""><figcaption></figcaption></figure>

##

## Use with Python (`stretch4_body`)

There are two common ways to read line sensor data in Python.

### Option A: Through `RobotClient`

Use this when `stretch_body_server` is already running and you have completed [Enable the line sensor subsystem](#enable-the-line-sensor-subsystem).

```python
import time
import numpy as np
from stretch4_body.robot.robot_client import RobotClient
from stretch4_body.subsystem.line_sensor.line_sensor_utils import (
    LineSensorGeometry,
    LineSensorCalibration,
)

with RobotClient() as robot:
    if not hasattr(robot, "line_sensor_loop"):
        raise RuntimeError("Enable line_sensor_loop in robot.server.subsystems")

    ls_params = robot.robot_params["line_sensor_loop"]
    geom = LineSensorGeometry(ls_params["line_sensor_geometry"])
    calib = LineSensorCalibration(robot.line_sensor_loop)
    calib.load_latest_tare()

    while True:
        robot.pull_status()
        status = robot.line_sensor_loop.status

        for idx, name in enumerate(ls_params["sensor_names"]):
            ranges = np.asarray(status[name]["ranges"], dtype=np.float64)
            if ranges.size == 0:
                continue

            corrected = calib.apply_tare(ranges, name)
            points = geom.get_sensor_points_in_robot_frame(idx, corrected)

            print(
                name,
                "points:", points.shape[0],
                "rate_hz:", status[name]["rate_hz"],
            )

        time.sleep(0.05)
```

### Option B: Direct `LineSensorLoop`

Use this for standalone tools, calibration, or the 3D viewer.

```python
import time
import numpy as np
from stretch4_body.subsystem.line_sensor.line_sensor_loop import LineSensorLoop
from stretch4_body.subsystem.line_sensor.line_sensor_utils import (
    LineSensorGeometry,
    LineSensorCalibration,
)

lsl = LineSensorLoop()
if not lsl.startup():
    raise RuntimeError("Failed to start LineSensorLoop")

try:
    geom = LineSensorGeometry(lsl.params["line_sensor_geometry"])
    calib = LineSensorCalibration(lsl)
    calib.load_latest_tare()

    while True:
        lsl.pull_status()
        status = lsl.status

        ranges = np.asarray(status["sensor_0"]["ranges"], dtype=np.float64)
        ranges = calib.apply_tare(ranges, "sensor_0")
        points = geom.get_sensor_points_in_robot_frame(0, ranges)

        print("sensor_0 points:", points.shape[0])
        time.sleep(0.05)
finally:
    lsl.stop()
```

### Useful status fields

| Field              | Meaning                                         |
| ------------------ | ----------------------------------------------- |
| `rate_hz`          | Overall fused frame rate (about 30 Hz expected) |
| `sensor_N/ranges`  | Raw range array in meters                       |
| `sensor_N/rate_hz` | Per-sensor update rate                          |

## Enable the line sensor subsystem

`line_sensor_loop` is a server-only subsystem. It is disabled in the factory defaults and must be turned on in your robot's user parameter file before Python tools or the ROS bridge can read line sensor data.

Edit `$HELLO_FLEET_PATH/$HELLO_FLEET_ID/stretch_user_params.yaml` and add this block under the top-level `robot:` key:

```yaml
robot:
  server:
    subsystems:
      - line_sensor_loop
```

{% hint style="danger" %}
If `stretch_user_params.yaml` already has a `robot:` key, merge `server:` into that existing section. Do not replace the full `robot:` block, or you may remove other robot settings.
{% endhint %}

Before:

```yaml
robot:
  tool: eoa_wrist_dw4_tool_pg4
```

After merging:

```yaml
robot:
  tool: eoa_wrist_dw4_tool_pg4
  server:
    subsystems:
      - line_sensor_loop
```

If `line_sensor_loop` is not enabled, `RobotClient` will not expose line sensor data.

{% hint style="info" %}
Parameter changes take effect on the next server start. Restart the server after saving the file:

```
stretch_body_server --restart
```

{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.hello-robot.com/stretch4_docs/working-with-stretch/sensor_basics/accessing-the-line-sensors.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
