Skip to content

Scanner Scripts

dcyoung edited this page Sep 7, 2017 · 14 revisions

Work in Progress...

Scanner Scripts

The scanner/ directory contains various python scripts for scanning and testing. Normally the webapp will call these scripts using node's child process module. However, to make development easy, each script has a simple demo or main method that is callable directly from python like so:

# from inside the scanner/ directory
python script_name.py

For the sake of simplicity, inter-process communication (between the node webapp, and the python scripts) is accomplished by piping the printed output from the python process into the input of the node process. To make parsing the messages simple, anything that is printed from a python script is printed in JSON format. This keeps everything human readable in the terminal, and also makes it incredibly easy to parse the messages in the webapp. Sometimes these JSON messages can even be passed along to the client (browser) side front-end javascript, without requiring any processing.

Currently there are a few bugs in libsweep and sweeppy. Therefore the scripts are rather verbose in attempting to catch errors and shutdown. As the underlying sdk becomes more robust, so too will the scanner code. There is a lot of room to clean things up.

See the scanner/ README for more details about the individual scripts.

The code from the scanner/ directory requires:

  • python + numpy module
  • physical hardware (sweep sensor + raspberry pi + motor HAT + motors + limitswitch)
  • installation of libsweep or sweeppy

Dummy Version

The scanner/ directory contains dummy implementations of the base dependencies which simulate the behavior of the scanner hardware. These dummy implementations are used in place of the normal dependencies when the main node app is passed a dummy flag.

node app.js -d

The scanner scripts can also be tested directly using dummy dependencies:

python script_name.py --use_dummy

Again, all scripts print in formatted JSON so the node webapp can easily parse the output.

When using the dummy dependencies, the scanner code only requires:

  • python + numpy module

The following are NOT required:

  • any physical hardware (sweep sensor + raspberry pi + motor HAT + motors + limitswitch)
  • installation of libsweep, sweeppy, Adafruit Motor HAT python library, GPIO

Theory of Operation

Base Rotation

The Sweep is a 2D scanning LiDAR device which uses a form of adaptive sampling. If you haven't read through the article on the Sweep's theory of operation, do so now: https://support.scanse.io/hc/en-us/articles/115006333327-Theory-of-Operation

The scanner scripts are designed around the Sweep's operation. Therefore it is crucial that you understand:

  • the sweep gathers 2D scans. You can think of this as a 2 dimensional plane.
  • the sweep delimits 360° scans by the reference 0° mark on the face of the device. The LED and thin groove line up with the 0° mark, and this never changes.
  • the sweep operates at an assumed constant motor speed.

Using a 2D scanner to gather 3D scans is accomplished by:

  • mounting the device 90° from the horizontal. You can think of this as shifting the 2D scan plane from the horizontal ground plane to a vertical plane. Picture a piece of paper flat on a table. Then imagine picking the paper up and pressing it against a wall like a poster.
  • rotating the 2D vertical scan plane about its center vertical axis to cover all 3 dimensions. Because the 2D scanner captures an entire plane of information, the 2D scan plane need only rotate 180° to capture a full 3D scan.

So, a 3D scan is comprised of many 2D scans. The 2D scans can be thought of as slices of a sphere, all passing through the sphere's center. The Sweep rotates continuously to gather 2D scans, and the base moves in intervals to build a 3D scan from the 2D scans. In between each 2D scan, the base rotates by a fixed interval such that the next 2D scan represents a different "slice" of the environment. The picture below shows how the base moves such that the spinning 2D sensor captures a different "slice".

homepage

Note: Currently the base actually moves clockwise while scanning, not CCW.

The Sweep uses a form of adaptive sampling, meaning there is no fixed interval between sensor readings in the 2D scan plane (refresh here). Therefore it is impossible to guarantee a consistent angular resolution within a 2D scan plane (slice). However the scanner will try to match the angular spacing between slices with the expected or average angular resolution of a 2D scan. This produces a final scan that looks uniform, because the horizontal and vertical densities are relatively similar.

Recall that the Sweep rotates continuously. But there is an angular deadzone where the beam is passing over the base of the 3D scanner. See the image below. The data from this angular deadzone is useless garbage. To be efficient, the scanner uses this angular deadzone as an opportunity to increment the base angle (ie: rotate to gather the next "slice" of 3D space). Because the 0° mark does not fall within the deadzone, this requires a little bit of housekeeping to guarantee the proper base angle is used when converting each reading. The scanner does not have a query-able encoder, and currently the sweep-sdk only provides complete scans. That means we have no way of knowing the angle of the Sweep's head. But we do know that the device is physically mounted in a consistent orientation, and that the 0° mark (in-line with the LED on the face of the device) is pointed directly upward opposite the deadzone.

The sweep-sdk (more specifically libsweep) outputs a 2D scan consisting of all the sensor readings between [0:360] degrees. The complete scan becomes available just as the head passes over the 0° mark, and libsweep begins collecting the next scan. Therefore, the arrival of a new scan can be considered an indication that the sensor beam has just passed the 0° mark. So we can use the arrival of a new scan as an event and calculate how long we should wait before moving the base, such that the beam is over the deadzone during the movement phase. This is possible because:

  • angular distance between the 0° mark and the beginning of the deadzone is known
  • the motor speed is assumed constant

Consider the following image: homepage

  • new scan arrives at time t0 = 0, we assume the beam has just passed over the 0° mark
  • calculate twait, the time required for the sensor beam to rotate between the 0° mark and START_DEAD_ZONE
  • wait twait seconds... at which point the beam should be crossing over START_DEAD_ZONE
  • move the base quickly, assuming the base will finish moving before the sensor beam crosses over END_DEAD_ZONE

Even if the motor speed fluctuates, this method is robust to drift because base movement is temporally relative to the 0° mark. That is to say that base movement always occurs twait seconds after the most recent scan arrival.

Transforming Coordinates

The scan data is provided by libsweep as a collection of 2D polar coordinates. To yield the appropriate 3D Cartesian coordinates, the data must be transformed.

  1. Transform 2D polar (distance, azimuth) samples to 3D Cartesian coordinates => (x,y,0)
  2. Transform the coordinates to account for the 90 degree mount angle of the sweep => (x,y,z)
  3. Transform the coordinates to account for the base angle => (x,y,z)

Note: The base angle is incremented in the middle of a scan. Therefore, the first half of the scan is transformed using the base angle before the base moves, and the second half of the scan is transformed using the base angle after the base moves.

If you are new to 3D coordinate transformations, check out this wonderful chapter on geometric transformations which breaks things down quite nicely.