Skip to content

Commit

Permalink
Update for fw 1.13. See changelog for details
Browse files Browse the repository at this point in the history
* Use random udp ports by default to simplify running multiple clients
* Dynamically adjust ambient and intensity exposure in visualizer
* Fix crash on failed udp socket call
  • Loading branch information
Dima Garbuzov committed Mar 17, 2020
1 parent 1cf6faf commit 68ad03c
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 77 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## [1.13.0] - 2020-03-16
### Added
- post-processing to improve ambient image uniformity in visualizer
- make timestamp mode configurable via the client (PR #97)

### Changed
- turn on position-independent code by default to make using code in libraries
easier (PR #65)
- use random ports for lidar and imu data by default when unspecified

### Fixed
- prevent legacy tf prefix from making invalid frame names (PR #56)
- use `iterator_traits` to make `batch_to_iter` work with more types (PR #70)
- use correct name for json dependency in `package.xml` (PR #116)
- handle udp socket creation error gracefully in client

## [1.12.0] - 2019-05-02
### Added
- install directives for `ouster_ros` build (addresses #50)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ See the `README.md` in each subdirectory for details.

## Sample Data
* Sample sensor output usable with the provided ROS code is available
[here](https://data.ouster.io/sample-data-1.12)
[here](https://data.ouster.io/sample-data-1.13)
13 changes: 6 additions & 7 deletions ouster_client/include/ouster/os1.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ timestamp_mode timestamp_mode_of_string(const std::string& s);
* @param imu_port port on which the sensor will send imu data
* @return pointer owning the resources associated with the connection
*/
std::shared_ptr<client> init_client(int lidar_port = 7502, int imu_port = 7503);
std::shared_ptr<client> init_client(int lidar_port = 0, int imu_port = 0);

/**
* Connect to and configure the sensor and start listening for data
Expand All @@ -146,10 +146,10 @@ std::shared_ptr<client> init_client(const std::string& hostname,
const std::string& udp_dest_host,
lidar_mode mode = MODE_1024x10,
timestamp_mode ts_mode = TIME_FROM_INTERNAL_OSC,
int lidar_port = 7502, int imu_port = 7503);
int lidar_port = 0, int imu_port = 0);

/**
* Block for up to timeout_sec until either data is ready or an error occurs.
* Block for up to timeout_sec until either data is ready or an error occurs
* @param cli client returned by init_client associated with the connection
* @param timeout_sec seconds to block while waiting for data
* @return client_state s where (s & ERROR) is true if an error occured, (s &
Expand All @@ -159,7 +159,7 @@ std::shared_ptr<client> init_client(const std::string& hostname,
client_state poll_client(const client& cli, int timeout_sec = 1);

/**
* Read lidar data from the sensor. Will not block.
* Read lidar data from the sensor. Will not block
* @param cli client returned by init_client associated with the connection
* @param buf buffer to which to write lidar data. Must be at least
* lidar_packet_bytes + 1 bytes
Expand All @@ -168,7 +168,7 @@ client_state poll_client(const client& cli, int timeout_sec = 1);
bool read_lidar_packet(const client& cli, uint8_t* buf);

/**
* Read imu data from the sensor. Will not block.
* Read imu data from the sensor. Will not block
* @param cli client returned by init_client associated with the connection
* @param buf buffer to which to write imu data. Must be at least
* imu_packet_bytes + 1 bytes
Expand All @@ -186,8 +186,7 @@ std::string get_metadata(const client& cli);
/**
* Parse metadata text blob from the sensor into a sensor_info struct. String
* and vector fields will have size 0 if the parameter cannot be found or
* parsed,
* while lidar_mode will be set to 0 (invalid).
* parsed, while lidar_mode will be set to 0 (invalid)
* @throw runtime_error if the text is not valid json
* @param metadata a text blob returned by get_metadata above
* @return a sensor_info struct populated with a subset of the metadata
Expand Down
54 changes: 41 additions & 13 deletions ouster_client/src/os1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,23 @@ const std::array<std::pair<timestamp_mode, std::string>, 3>
{TIME_FROM_SYNC_PULSE_IN, "TIME_FROM_SYNC_PULSE_IN"},
{TIME_FROM_PTP_1588, "TIME_FROM_PTP_1588"}}};

int32_t get_sock_port(int sock_fd) {
struct sockaddr_storage ss;
socklen_t addrlen = sizeof ss;

if (getsockname(sock_fd, (struct sockaddr*)&ss, &addrlen) < 0) {
std::cerr << "udp getsockname(): " << std::strerror(errno) << std::endl;
return -1;
}

if (ss.ss_family == AF_INET)
return ntohs(((struct sockaddr_in*)&ss)->sin_port);
else if (ss.ss_family == AF_INET6)
return ntohs(((struct sockaddr_in6*)&ss)->sin6_port);
else
return -1;
}

int udp_data_socket(int port) {
struct addrinfo hints, *info_start, *ai;

Expand Down Expand Up @@ -97,6 +114,7 @@ int udp_data_socket(int port) {

if (fcntl(sock_fd, F_SETFL, fcntl(sock_fd, F_GETFL, 0) | O_NONBLOCK) < 0) {
std::cerr << "udp fcntl(): " << std::strerror(errno) << std::endl;
close(sock_fd);
return -1;
}

Expand Down Expand Up @@ -179,7 +197,7 @@ bool do_tcp_cmd(int sock_fd, const std::vector<std::string>& cmd_tokens,
void update_json_obj(Json::Value& dst, const Json::Value& src) {
for (const auto& key : src.getMemberNames()) dst[key] = src[key];
}
}
} // namespace

std::string to_string(version v) {
if (v == invalid_version) return "UNKNOWN";
Expand Down Expand Up @@ -313,10 +331,12 @@ sensor_info parse_metadata(const std::string& meta) {
std::shared_ptr<client> init_client(int lidar_port, int imu_port) {
auto cli = std::make_shared<client>();

int lidar_fd = udp_data_socket(lidar_port);
int imu_fd = udp_data_socket(imu_port);
cli->lidar_fd = lidar_fd;
cli->imu_fd = imu_fd;
cli->lidar_fd = udp_data_socket(lidar_port);
cli->imu_fd = udp_data_socket(imu_port);

if (cli->lidar_fd < 0 || cli->imu_fd < 0)
return std::shared_ptr<client>();

return cli;
}

Expand All @@ -325,6 +345,12 @@ std::shared_ptr<client> init_client(const std::string& hostname,
lidar_mode mode, timestamp_mode ts_mode,
int lidar_port, int imu_port) {
auto cli = init_client(lidar_port, imu_port);
if (!cli) return std::shared_ptr<client>();

// update requested ports to actual bound ports
lidar_port = get_sock_port(cli->lidar_fd);
imu_port = get_sock_port(cli->imu_fd);
if (lidar_port == -1 || imu_port == -1) return std::shared_ptr<client>();

int sock_fd = cfg_socket(hostname.c_str());

Expand All @@ -342,9 +368,10 @@ std::shared_ptr<client> init_client(const std::string& hostname,
do_tcp_cmd(sock_fd, {"set_config_param", "udp_ip", udp_dest_host}, res);
success &= res == "set_config_param";

success &= do_tcp_cmd(sock_fd, {"set_config_param", "udp_port_lidar",
std::to_string(lidar_port)},
res);
success &= do_tcp_cmd(
sock_fd,
{"set_config_param", "udp_port_lidar", std::to_string(lidar_port)},
res);
success &= res == "set_config_param";

success &= do_tcp_cmd(
Expand Down Expand Up @@ -419,9 +446,9 @@ client_state poll_client(const client& c, const int timeout_sec) {
return res;
}

static bool recv_fixed(int fd, void* buf, size_t len) {
ssize_t n = recvfrom(fd, buf, len + 1, 0, NULL, NULL);
if (n == (ssize_t)len)
static bool recv_fixed(int fd, void* buf, ssize_t len) {
ssize_t n = recv(fd, (char*)buf, len + 1, 0);
if (n == len)
return true;
else if (n == -1)
std::cerr << "recvfrom: " << std::strerror(errno) << std::endl;
Expand All @@ -437,5 +464,6 @@ bool read_lidar_packet(const client& cli, uint8_t* buf) {
bool read_imu_packet(const client& cli, uint8_t* buf) {
return recv_fixed(cli.imu_fd, buf, imu_packet_bytes);
}
}
}

} // namespace OS1
} // namespace ouster
4 changes: 2 additions & 2 deletions ouster_ros/os1.launch
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

<arg name="os1_hostname" default="" doc="hostname or IP in dotted decimal form of the sensor"/>
<arg name="os1_udp_dest" default="" doc="hostname or IP where the sensor will send data packets"/>
<arg name="os1_lidar_port" default="7502" doc="port to which the sensor should send lidar data"/>
<arg name="os1_imu_port" default="7503" doc="port to which the sensor should send imu data"/>
<arg name="os1_lidar_port" default="0" doc="port to which the sensor should send lidar data"/>
<arg name="os1_imu_port" default="0" doc="port to which the sensor should send imu data"/>
<arg name="replay" default="false" doc="do not connect to a sensor; expect /os1_node/{lidar,imu}_packets from replay"/>
<arg name="lidar_mode" default="" doc="resolution and rate: either 512x10, 512x20, 1024x10, 1024x20, or 2048x10"/>
<arg name="timestamp_mode" default="" doc="method used to timestamp measurements: TIME_FROM_INTERNAL_OSC, TIME_FROM_SYNC_PULSE_IN, TIME_FROM_PTP_1588"/>
Expand Down
4 changes: 2 additions & 2 deletions ouster_ros/src/os1_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ int main(int argc, char** argv) {
// empty indicates "not set" since roslaunch xml can't optionally set params
auto hostname = nh.param("os1_hostname", std::string{});
auto udp_dest = nh.param("os1_udp_dest", std::string{});
auto lidar_port = nh.param("os1_lidar_port", 7501);
auto imu_port = nh.param("os1_imu_port", 7502);
auto lidar_port = nh.param("os1_lidar_port", 0);
auto imu_port = nh.param("os1_imu_port", 0);
auto replay = nh.param("replay", false);
auto lidar_mode = nh.param("lidar_mode", std::string{});
auto timestamp_mode = nh.param("timestamp_mode", std::string{});
Expand Down
11 changes: 5 additions & 6 deletions ouster_viz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,20 @@
and CMake 3.1 or newer
* Requires VTK6 and Eigen3 libraries
* Using Ubuntu: sudo apt-get install libvtk6-dev libeigen3-dev
* Using Fedora: sudo apt-get update yum install vtk-devel.x86_64
eigen3-devel.noarch
* Using Fedora: sudo yum install vtk-devel.x86_64 eigen3-devel.noarch

## Building the Visualizer:
* In the following instruction steps, `/path/to/ouster_example` is where you've cloned the repository
* In the following instruction steps, `/path/to/ouster_example` is where you've cloned the repository
* Run the following command `export CMAKE_PREFIX_PATH=/path/to/ouster_example`
* Build with `cd /path/to/ouster_example/ouster_viz && mkdir build &&
cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make`

## Running the Visualizer
* An executable called `simple_viz` is generated in the build directory
* An executable called `viz` is generated in the build directory
* Note: if compiling in an environment with ROS, the location of the
executable will be different
* To run: `./simple_viz <flags> <os1_hostname> <udp_data_dest_ip>`
* For help, run `./simple_viz -h`
* To run: `./viz <flags> <os1_hostname> <udp_data_dest_ip>`
* For help, run `./viz -h`

## Command Line Arguments
* `<os1_hostname>` the hostname or IP address of the OS1 sensor
Expand Down
52 changes: 52 additions & 0 deletions ouster_viz/include/ouster/autoexposure.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @file
* @brief Adjust brightness image brightness and apply gamma correction
*
* Functor that adjusts brightness so that 1st percentile pixel is black
* and 99th percentile pixel is white, while applying basic gamma correction
* of 2.0.
* Stores state of the black and white points so that it does not flicker
* rapidly.
*/

#pragma once

#include <Eigen/Eigen>
#include <vector>
#include <cstdlib>

struct AutoExposure {
private:
double lo_state = -1.0;
double hi_state = -1.0;

public:
void operator()(Eigen::Ref<Eigen::ArrayXd> key_eigen) {
const size_t n = key_eigen.rows();
const size_t kth_extreme = n / 100;
std::vector<size_t> indices(n);
for (size_t i = 0; i < n; i++) {
indices[i] = i;
}
auto cmp = [&](const size_t a, const size_t b) {
return key_eigen(a) < key_eigen(b);
};
std::nth_element(indices.begin(), indices.begin() + kth_extreme,
indices.end(), cmp);
const double lo = key_eigen[*(indices.begin() + kth_extreme)];
std::nth_element(indices.begin() + kth_extreme,
indices.end() - kth_extreme, indices.end(), cmp);
const double hi = key_eigen[*(indices.end() - kth_extreme)];
if (lo_state < 0) {
lo_state = lo;
hi_state = hi;
}
lo_state = 0.9 * lo_state + 0.1 * lo;
hi_state = 0.9 * hi_state + 0.1 * hi;
key_eigen -= lo;
key_eigen *= 1.0 / (hi - lo);

// gamma correction
key_eigen = key_eigen.max(0.0).sqrt().min(1.0);
}
};
84 changes: 84 additions & 0 deletions ouster_viz/include/ouster/beam_uniformity.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @file
* @brief Corrects beam uniformity by minimizing median difference between rows
*
*/

#pragma once

#include <Eigen/Eigen>
#include <vector>
#include <algorithm>

class BeamUniformityCorrector {
private:
std::vector<double> dark_count;

std::vector<double> compute_dark_count(
const Eigen::Ref<Eigen::ArrayXXd> image) {
const size_t image_h = image.rows();
const size_t image_w = image.cols();

std::vector<double> tmp(image_w);
std::vector<double> new_dark_count(image_h, 0);

Eigen::ArrayXXd row_diffs =
image.bottomRows(image_h - 1) - image.topRows(image_h - 1);

// compute the median of differences between rows
for (size_t i = 1; i < image_h; i++) {
Eigen::Map<Eigen::Matrix<double, -1, 1>> tmp_map(tmp.data(),
image_w);
tmp_map = row_diffs.row(i - 1);
std::nth_element(tmp.begin(), tmp.begin() + image_w / 2, tmp.end());
new_dark_count[i] = new_dark_count[i - 1] + tmp[image_w / 2];
}

// remove gradients in the entire height of image by doing linear fit
Eigen::Matrix<double, -1, 2> A(image_h, 2);
for (size_t i = 0; i < image_h; i++) {
A(i, 0) = 1;
A(i, 1) = i;
}

Eigen::Vector2d x = A.fullPivLu().solve(
Eigen::Map<Eigen::VectorXd>(new_dark_count.data(), image_h, 1));

Eigen::Map<Eigen::ArrayXd>(new_dark_count.data(), image_h, 1) -=
(A * x).array();

// subtract minimum value
double min_el =
*std::min_element(new_dark_count.begin(), new_dark_count.end());
Eigen::Map<Eigen::ArrayXd>(new_dark_count.data(), image_h, 1) -= min_el;
return new_dark_count;
}

public:
void correct(Eigen::Ref<Eigen::ArrayXXd> image) {
const size_t image_h = image.rows();

if (dark_count.size() == 0) {
dark_count = compute_dark_count(image);
} else {
// update dark_count with a decaying weighted average
const auto new_dark_count = compute_dark_count(image);
Eigen::Map<Eigen::ArrayXd>(dark_count.data(), image_h) *= 0.95;
Eigen::Map<Eigen::ArrayXd>(dark_count.data(), image_h) +=
Eigen::Map<const Eigen::ArrayXd>(new_dark_count.data(),
image_h) *
0.05;
}

// apply the dark count correction row by row
for (size_t i = 0; i < image_h; i++) {
// contains a view of the current row
image.row(i) -= dark_count[i];
image.row(i) = image.row(i).unaryExpr([](double x) {
x = std::max(x, 0.0);
x = std::min(x, (double)UINT32_MAX);
return x;
});
}
}
};
1 change: 1 addition & 0 deletions ouster_viz/include/ouster/lidar_scan.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <iterator>
#include <utility>
#include <vector>
#include <cstdlib>

namespace ouster {

Expand Down
Loading

0 comments on commit 68ad03c

Please sign in to comment.